You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

init.js 7.5 KiB

3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. // @remove-file-on-eject
  2. /**
  3. * Copyright (c) 2015-present, Facebook, Inc.
  4. *
  5. * This source code is licensed under the MIT license found in the
  6. * LICENSE file in the root directory of this source tree.
  7. */
  8. 'use strict';
  9. // Makes the script crash on unhandled rejections instead of silently
  10. // ignoring them. In the future, promise rejections that are not handled will
  11. // terminate the Node.js process with a non-zero exit code.
  12. process.on('unhandledRejection', err => {
  13. throw err;
  14. });
  15. const fs = require('fs-extra');
  16. const path = require('path');
  17. const chalk = require('chalk');
  18. const execSync = require('child_process').execSync;
  19. const spawn = require('react-dev-utils/crossSpawn');
  20. const { defaultBrowsers } = require('react-dev-utils/browsersHelper');
  21. const os = require('os');
  22. const verifyTypeScriptSetup = require('./utils/verifyTypeScriptSetup');
  23. function isInGitRepository() {
  24. try {
  25. execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
  26. return true;
  27. } catch (e) {
  28. return false;
  29. }
  30. }
  31. function isInMercurialRepository() {
  32. try {
  33. execSync('hg --cwd . root', { stdio: 'ignore' });
  34. return true;
  35. } catch (e) {
  36. return false;
  37. }
  38. }
  39. function tryGitInit(appPath) {
  40. let didInit = false;
  41. try {
  42. execSync('git --version', { stdio: 'ignore' });
  43. if (isInGitRepository() || isInMercurialRepository()) {
  44. return false;
  45. }
  46. execSync('git init', { stdio: 'ignore' });
  47. didInit = true;
  48. execSync('git add -A', { stdio: 'ignore' });
  49. execSync('git commit -m "Initial commit from Create React App"', {
  50. stdio: 'ignore',
  51. });
  52. return true;
  53. } catch (e) {
  54. if (didInit) {
  55. // If we successfully initialized but couldn't commit,
  56. // maybe the commit author config is not set.
  57. // In the future, we might supply our own committer
  58. // like Ember CLI does, but for now, let's just
  59. // remove the Git files to avoid a half-done state.
  60. try {
  61. // unlinkSync() doesn't work on directories.
  62. fs.removeSync(path.join(appPath, '.git'));
  63. } catch (removeErr) {
  64. // Ignore.
  65. }
  66. }
  67. return false;
  68. }
  69. }
  70. module.exports = function(
  71. appPath,
  72. appName,
  73. verbose,
  74. originalDirectory,
  75. template
  76. ) {
  77. const ownPath = path.dirname(
  78. require.resolve(path.join(__dirname, '..', 'package.json'))
  79. );
  80. const appPackage = require(path.join(appPath, 'package.json'));
  81. const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock'));
  82. // Copy over some of the devDependencies
  83. appPackage.dependencies = appPackage.dependencies || {};
  84. const useTypeScript = appPackage.dependencies['typescript'] != null;
  85. // Setup the script rules
  86. appPackage.scripts = {
  87. start: 'react-scripts start',
  88. build: 'react-scripts build',
  89. test: 'react-scripts test',
  90. eject: 'react-scripts eject',
  91. };
  92. // Setup the eslint config
  93. appPackage.eslintConfig = {
  94. extends: 'react-app',
  95. };
  96. // Setup the browsers list
  97. appPackage.browserslist = defaultBrowsers;
  98. fs.writeFileSync(
  99. path.join(appPath, 'package.json'),
  100. JSON.stringify(appPackage, null, 2) + os.EOL
  101. );
  102. const readmeExists = fs.existsSync(path.join(appPath, 'README.md'));
  103. if (readmeExists) {
  104. fs.renameSync(
  105. path.join(appPath, 'README.md'),
  106. path.join(appPath, 'README.old.md')
  107. );
  108. }
  109. // Copy the files for the user
  110. const templatePath = template
  111. ? path.resolve(originalDirectory, template)
  112. : path.join(ownPath, useTypeScript ? 'template-typescript' : 'template');
  113. if (fs.existsSync(templatePath)) {
  114. fs.copySync(templatePath, appPath);
  115. } else {
  116. console.error(
  117. `Could not locate supplied template: ${chalk.green(templatePath)}`
  118. );
  119. return;
  120. }
  121. // Rename gitignore after the fact to prevent npm from renaming it to .npmignore
  122. // See: https://github.com/npm/npm/issues/1862
  123. try {
  124. fs.moveSync(
  125. path.join(appPath, 'gitignore'),
  126. path.join(appPath, '.gitignore'),
  127. []
  128. );
  129. } catch (err) {
  130. // Append if there's already a `.gitignore` file there
  131. if (err.code === 'EEXIST') {
  132. const data = fs.readFileSync(path.join(appPath, 'gitignore'));
  133. fs.appendFileSync(path.join(appPath, '.gitignore'), data);
  134. fs.unlinkSync(path.join(appPath, 'gitignore'));
  135. } else {
  136. throw err;
  137. }
  138. }
  139. let command;
  140. let args;
  141. if (useYarn) {
  142. command = 'yarnpkg';
  143. args = ['add'];
  144. } else {
  145. command = 'npm';
  146. args = ['install', '--save', verbose && '--verbose'].filter(e => e);
  147. }
  148. args.push('react', 'react-dom');
  149. // Install additional template dependencies, if present
  150. const templateDependenciesPath = path.join(
  151. appPath,
  152. '.template.dependencies.json'
  153. );
  154. if (fs.existsSync(templateDependenciesPath)) {
  155. const templateDependencies = require(templateDependenciesPath).dependencies;
  156. args = args.concat(
  157. Object.keys(templateDependencies).map(key => {
  158. return `${key}@${templateDependencies[key]}`;
  159. })
  160. );
  161. fs.unlinkSync(templateDependenciesPath);
  162. }
  163. // Install react and react-dom for backward compatibility with old CRA cli
  164. // which doesn't install react and react-dom along with react-scripts
  165. // or template is presetend (via --internal-testing-template)
  166. if (!isReactInstalled(appPackage) || template) {
  167. console.log(`Installing react and react-dom using ${command}...`);
  168. console.log();
  169. const proc = spawn.sync(command, args, { stdio: 'inherit' });
  170. if (proc.status !== 0) {
  171. console.error(`\`${command} ${args.join(' ')}\` failed`);
  172. return;
  173. }
  174. }
  175. if (useTypeScript) {
  176. verifyTypeScriptSetup();
  177. }
  178. if (tryGitInit(appPath)) {
  179. console.log();
  180. console.log('Initialized a git repository.');
  181. }
  182. // Display the most elegant way to cd.
  183. // This needs to handle an undefined originalDirectory for
  184. // backward compatibility with old global-cli's.
  185. let cdpath;
  186. if (originalDirectory && path.join(originalDirectory, appName) === appPath) {
  187. cdpath = appName;
  188. } else {
  189. cdpath = appPath;
  190. }
  191. // Change displayed command to yarn instead of yarnpkg
  192. const displayedCommand = useYarn ? 'yarn' : 'npm';
  193. console.log();
  194. console.log(`Success! Created ${appName} at ${appPath}`);
  195. console.log('Inside that directory, you can run several commands:');
  196. console.log();
  197. console.log(chalk.cyan(` ${displayedCommand} start`));
  198. console.log(' Starts the development server.');
  199. console.log();
  200. console.log(
  201. chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}build`)
  202. );
  203. console.log(' Bundles the app into static files for production.');
  204. console.log();
  205. console.log(chalk.cyan(` ${displayedCommand} test`));
  206. console.log(' Starts the test runner.');
  207. console.log();
  208. console.log(
  209. chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}eject`)
  210. );
  211. console.log(
  212. ' Removes this tool and copies build dependencies, configuration files'
  213. );
  214. console.log(
  215. ' and scripts into the app directory. If you do this, you can’t go back!'
  216. );
  217. console.log();
  218. console.log('We suggest that you begin by typing:');
  219. console.log();
  220. console.log(chalk.cyan(' cd'), cdpath);
  221. console.log(` ${chalk.cyan(`${displayedCommand} start`)}`);
  222. if (readmeExists) {
  223. console.log();
  224. console.log(
  225. chalk.yellow(
  226. 'You had a `README.md` file, we renamed it to `README.old.md`'
  227. )
  228. );
  229. }
  230. console.log();
  231. console.log('Happy hacking!');
  232. };
  233. function isReactInstalled(appPackage) {
  234. const dependencies = appPackage.dependencies || {};
  235. return (
  236. typeof dependencies.react !== 'undefined' &&
  237. typeof dependencies['react-dom'] !== 'undefined'
  238. );
  239. }