text: // @remove-file-on-eject
  /**
   * Copyright (c) 2015-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  'use strict';
  
  // Makes the script crash on unhandled rejections instead of silently
  // ignoring them. In the future, promise rejections that are not handled will
  // terminate the Node.js process with a non-zero exit code.
  process.on('unhandledRejection', err => {
    throw err;
  });
  
  const fs = require('fs-extra');
  const path = require('path');
  const chalk = require('react-dev-utils/chalk');
  const execSync = require('child_process').execSync;
  const spawn = require('react-dev-utils/crossSpawn');
  const { defaultBrowsers } = require('react-dev-utils/browsersHelper');
  const os = require('os');
  const verifyTypeScriptSetup = require('./utils/verifyTypeScriptSetup');
  
  /**
   * 当前文件夹是否已经关联 git 仓库
   */
  function isInGitRepository() {
    try {
      execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
      return true;
    } catch (e) {
      return false;
    }
  }
  
  function isInMercurialRepository() {
    try {
      execSync('hg --cwd . root', { stdio: 'ignore' });
      return true;
    } catch (e) {
      return false;
    }
  }
  
  /**
   * 若 git 没有初始化，则帮初始化 git 并 commit 
   */
  function tryGitInit(appPath) {
    let didInit = false;
    try {
      execSync('git --version', { stdio: 'ignore' });
      if (isInGitRepository() || isInMercurialRepository()) {
        return false;
      }
  
      execSync('git init', { stdio: 'ignore' });
      didInit = true;
  
      execSync('git add -A', { stdio: 'ignore' });
      execSync('git commit -m "Initial commit from Create React App"', {
        stdio: 'ignore',
      });
      return true;
    } catch (e) {
      if (didInit) {
        // If we successfully initialized but couldn't commit,
        // maybe the commit author config is not set.
        // In the future, we might supply our own committer
        // like Ember CLI does, but for now, let's just
        // remove the Git files to avoid a half-done state.
        try {
          // unlinkSync() doesn't work on directories.
          fs.removeSync(path.join(appPath, '.git'));
        } catch (removeErr) {
          // Ignore.
        }
      }
      return false;
    }
  }
  
  module.exports = function(
    appPath,
    appName,
    verbose,
    originalDirectory,
    template
  ) {
    /** react-scripts 的根目录 */
    const ownPath = path.dirname(
      require.resolve(path.join(__dirname, '..', 'package.json'))
    );
    /** 项目的 package */
    const appPackage = require(path.join(appPath, 'package.json'));
    const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock'));
  
    // Copy over some of the devDependencies
    appPackage.dependencies = appPackage.dependencies || {};
  
    const useTypeScript = appPackage.dependencies['typescript'] != null;
  
    // Setup the script rules
    appPackage.scripts = {
      start: 'react-scripts start',
      build: 'react-scripts build',
      test: 'react-scripts test',
      eject: 'react-scripts eject',
    };
  
    // Setup the eslint config
    appPackage.eslintConfig = {
      extends: 'react-app',
    };
  
    // Setup the browsers list
    appPackage.browserslist = defaultBrowsers;
  
    /**
     * 配置写入 package.json
     */
    fs.writeFileSync(
      path.join(appPath, 'package.json'),
      JSON.stringify(appPackage, null, 2) + os.EOL
    );
  
    const readmeExists = fs.existsSync(path.join(appPath, 'README.md'));
    if (readmeExists) {
      fs.renameSync(
        path.join(appPath, 'README.md'),
        path.join(appPath, 'README.old.md')
      );
    }
  
    /**
     * 拷贝 template 下文件到项目目录里
     * 如果提供了 template 参数，则从指定的拷，如果没有则从 react-scripts/template 拷贝
     */
    // Copy the files for the user
    const templatePath = template
      ? path.resolve(originalDirectory, template)
      : path.join(ownPath, useTypeScript ? 'template-typescript' : 'template');
    if (fs.existsSync(templatePath)) {
      fs.copySync(templatePath, appPath);
    } else {
      console.error(
        `Could not locate supplied template: ${chalk.green(templatePath)}`
      );
      return;
    }
  
    /**
     * 重命名 gitignore 为 .gitignore
     * 若 .gitignore 已经存在，读取 gitignore 内容添加到 .gitignore 后面
     */
    // Rename gitignore after the fact to prevent npm from renaming it to .npmignore
    // See: https://github.com/npm/npm/issues/1862
    try {
      fs.moveSync(
        path.join(appPath, 'gitignore'),
        path.join(appPath, '.gitignore'),
        []
      );
    } catch (err) {
      // Append if there's already a `.gitignore` file there
      if (err.code === 'EEXIST') {
        const data = fs.readFileSync(path.join(appPath, 'gitignore'));
        fs.appendFileSync(path.join(appPath, '.gitignore'), data);
        fs.unlinkSync(path.join(appPath, 'gitignore'));
      } else {
        throw err;
      }
    }
  
  
    /**
     * 安装 react react-dom
     * 如果项目目录下有 .template.dependencies.json 文件，会安装这个文件指定的依赖，并删除该文件
     */
    let command;
    let args;
  
    if (useYarn) {
      command = 'yarnpkg';
      args = ['add'];
    } else {
      command = 'npm';
      args = ['install', '--save', verbose && '--verbose'].filter(e => e);
    }
    args.push('react', 'react-dom');
  
    // Install additional template dependencies, if present
    const templateDependenciesPath = path.join(
      appPath,
      '.template.dependencies.json'
    );
    if (fs.existsSync(templateDependenciesPath)) {
      const templateDependencies = require(templateDependenciesPath).dependencies;
      args = args.concat(
        Object.keys(templateDependencies).map(key => {
          return `${key}@${templateDependencies[key]}`;
        })
      );
      fs.unlinkSync(templateDependenciesPath);
    }
  
    
    // Install react and react-dom for backward compatibility with old CRA cli
    // which doesn't install react and react-dom along with react-scripts
    // or template is presetend (via --internal-testing-template)
    if (!isReactInstalled(appPackage) || template) {
      console.log(`Installing react and react-dom using ${command}...`);
      console.log();
  
  
      /**
       * 若 react react-dom 没有安装，则同步安装 react react-dom，以及 .template.dependencies.json 指定的依赖
       */
      const proc = spawn.sync(command, args, { stdio: 'inherit' });
      if (proc.status !== 0) {
        console.error(`\`${command} ${args.join(' ')}\` failed`);
        return;
      }
    }
  
    if (useTypeScript) {
      verifyTypeScriptSetup();
    }
  
    /**
     * 初始化 git
     */
    if (tryGitInit(appPath)) {
      console.log();
      console.log('Initialized a git repository.');
    }
  
    // Display the most elegant way to cd.
    // This needs to handle an undefined originalDirectory for
    // backward compatibility with old global-cli's.
    let cdpath;
    if (originalDirectory && path.join(originalDirectory, appName) === appPath) {
      cdpath = appName;
    } else {
      cdpath = appPath;
    }
  
  
    /**
     * 一切完成后，命令行输入各种提示
     */
    // Change displayed command to yarn instead of yarnpkg
    const displayedCommand = useYarn ? 'yarn' : 'npm';
  
    console.log();
    console.log(`Success! Created ${appName} at ${appPath}`);
    console.log('Inside that directory, you can run several commands:');
    console.log();
    console.log(chalk.cyan(`  ${displayedCommand} start`));
    console.log('    Starts the development server.');
    console.log();
    console.log(
      chalk.cyan(`  ${displayedCommand} ${useYarn ? '' : 'run '}build`)
    );
    console.log('    Bundles the app into static files for production.');
    console.log();
    console.log(chalk.cyan(`  ${displayedCommand} test`));
    console.log('    Starts the test runner.');
    console.log();
    console.log(
      chalk.cyan(`  ${displayedCommand} ${useYarn ? '' : 'run '}eject`)
    );
    console.log(
      '    Removes this tool and copies build dependencies, configuration files'
    );
    console.log(
      '    and scripts into the app directory. If you do this, you can’t go back!'
    );
    console.log();
    console.log('We suggest that you begin by typing:');
    console.log();
    console.log(chalk.cyan('  cd'), cdpath);
    console.log(`  ${chalk.cyan(`${displayedCommand} start`)}`);
    if (readmeExists) {
      console.log();
      console.log(
        chalk.yellow(
          'You had a `README.md` file, we renamed it to `README.old.md`'
        )
      );
    }
    console.log();
    console.log('Happy hacking!');
  };
  
  function isReactInstalled(appPackage) {
    const dependencies = appPackage.dependencies || {};
  
    return (
      typeof dependencies.react !== 'undefined' &&
      typeof dependencies['react-dom'] !== 'undefined'
    );
  }
  