createReactApp.js 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  8. // /!\ DO NOT MODIFY THIS FILE /!\
  9. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  10. //
  11. // create-react-app is installed globally on people's computers. This means
  12. // that it is extremely difficult to have them upgrade the version and
  13. // because there's only one global version installed, it is very prone to
  14. // breaking changes.
  15. //
  16. // The only job of create-react-app is to init the repository and then
  17. // forward all the commands to the local version of create-react-app.
  18. //
  19. // If you need to add a new command, please add it to the scripts/ folder.
  20. //
  21. // The only reason to modify this file is to add more warnings and
  22. // troubleshooting information for the `create-react-app` command.
  23. //
  24. // Do not make breaking changes! We absolutely don't want to have to
  25. // tell people to update their global version of create-react-app.
  26. //
  27. // Also be careful with new language features.
  28. // This file must work on Node 6+.
  29. //
  30. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  31. // /!\ DO NOT MODIFY THIS FILE /!\
  32. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  33. 'use strict';
  34. const chalk = require('chalk');
  35. const commander = require('commander');
  36. const dns = require('dns');
  37. const envinfo = require('envinfo');
  38. const execSync = require('child_process').execSync;
  39. const fs = require('fs-extra');
  40. const hyperquest = require('hyperquest');
  41. const inquirer = require('inquirer');
  42. const os = require('os');
  43. const path = require('path');
  44. const semver = require('semver');
  45. const spawn = require('cross-spawn');
  46. const tmp = require('tmp');
  47. const unpack = require('tar-pack').unpack;
  48. const url = require('url');
  49. const validateProjectName = require('validate-npm-package-name');
  50. const packageJson = require('./package.json');
  51. let projectName;
  52. const program = new commander.Command(packageJson.name)
  53. .version(packageJson.version)
  54. .arguments('<project-directory>')
  55. .usage(`${chalk.green('<project-directory>')} [options]`)
  56. .action(name => {
  57. projectName = name;
  58. })
  59. .option('--verbose', 'print additional logs')
  60. .option('--info', 'print environment debug info')
  61. .option(
  62. '--scripts-version <alternative-package>',
  63. 'use a non-standard version of react-scripts'
  64. )
  65. .option(
  66. '--template <path-to-template>',
  67. 'specify a template for the created project'
  68. )
  69. .option('--use-npm')
  70. .option('--use-pnp')
  71. // TODO: Remove this in next major release.
  72. .option(
  73. '--typescript',
  74. '(this option will be removed in favour of templates in the next major release of create-react-app)'
  75. )
  76. .allowUnknownOption()
  77. .on('--help', () => {
  78. console.log(` Only ${chalk.green('<project-directory>')} is required.`);
  79. console.log();
  80. console.log(
  81. ` A custom ${chalk.cyan('--scripts-version')} can be one of:`
  82. );
  83. console.log(` - a specific npm version: ${chalk.green('0.8.2')}`);
  84. console.log(` - a specific npm tag: ${chalk.green('@next')}`);
  85. console.log(
  86. ` - a custom fork published on npm: ${chalk.green(
  87. 'my-react-scripts'
  88. )}`
  89. );
  90. console.log(
  91. ` - a local path relative to the current working directory: ${chalk.green(
  92. 'file:../my-react-scripts'
  93. )}`
  94. );
  95. console.log(
  96. ` - a .tgz archive: ${chalk.green(
  97. 'https://mysite.com/my-react-scripts-0.8.2.tgz'
  98. )}`
  99. );
  100. console.log(
  101. ` - a .tar.gz archive: ${chalk.green(
  102. 'https://mysite.com/my-react-scripts-0.8.2.tar.gz'
  103. )}`
  104. );
  105. console.log(
  106. ` It is not needed unless you specifically want to use a fork.`
  107. );
  108. console.log();
  109. console.log(` A custom ${chalk.cyan('--template')} can be one of:`);
  110. console.log(
  111. ` - a custom fork published on npm: ${chalk.green(
  112. 'cra-template-typescript'
  113. )}`
  114. );
  115. console.log(
  116. ` - a local path relative to the current working directory: ${chalk.green(
  117. 'file:../my-custom-template'
  118. )}`
  119. );
  120. console.log(
  121. ` - a .tgz archive: ${chalk.green(
  122. 'https://mysite.com/my-custom-template-0.8.2.tgz'
  123. )}`
  124. );
  125. console.log(
  126. ` - a .tar.gz archive: ${chalk.green(
  127. 'https://mysite.com/my-custom-template-0.8.2.tar.gz'
  128. )}`
  129. );
  130. console.log();
  131. console.log(
  132. ` If you have any problems, do not hesitate to file an issue:`
  133. );
  134. console.log(
  135. ` ${chalk.cyan(
  136. 'https://github.com/facebook/create-react-app/issues/new'
  137. )}`
  138. );
  139. console.log();
  140. })
  141. .parse(process.argv);
  142. if (program.info) {
  143. console.log(chalk.bold('\nEnvironment Info:'));
  144. console.log(
  145. `\n current version of ${packageJson.name}: ${packageJson.version}`
  146. );
  147. console.log(` running from ${__dirname}`);
  148. return envinfo
  149. .run(
  150. {
  151. System: ['OS', 'CPU'],
  152. Binaries: ['Node', 'npm', 'Yarn'],
  153. Browsers: ['Chrome', 'Edge', 'Internet Explorer', 'Firefox', 'Safari'],
  154. npmPackages: ['react', 'react-dom', 'react-scripts'],
  155. npmGlobalPackages: ['create-react-app'],
  156. },
  157. {
  158. duplicates: true,
  159. showNotFound: true,
  160. }
  161. )
  162. .then(console.log);
  163. }
  164. if (typeof projectName === 'undefined') {
  165. console.error('Please specify the project directory:');
  166. console.log(
  167. ` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`
  168. );
  169. console.log();
  170. console.log('For example:');
  171. console.log(` ${chalk.cyan(program.name())} ${chalk.green('my-react-app')}`);
  172. console.log();
  173. console.log(
  174. `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
  175. );
  176. process.exit(1);
  177. }
  178. createApp(
  179. projectName,
  180. program.verbose,
  181. program.scriptsVersion,
  182. program.template,
  183. program.useNpm,
  184. program.usePnp,
  185. program.typescript
  186. );
  187. function createApp(
  188. name,
  189. verbose,
  190. version,
  191. template,
  192. useNpm,
  193. usePnp,
  194. useTypeScript
  195. ) {
  196. const unsupportedNodeVersion = !semver.satisfies(process.version, '>=8.10.0');
  197. if (unsupportedNodeVersion && useTypeScript) {
  198. console.error(
  199. chalk.red(
  200. `You are using Node ${process.version} with the TypeScript template. Node 8.10 or higher is required to use TypeScript.\n`
  201. )
  202. );
  203. process.exit(1);
  204. } else if (unsupportedNodeVersion) {
  205. console.log(
  206. chalk.yellow(
  207. `You are using Node ${process.version} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
  208. `Please update to Node 8.10 or higher for a better, fully supported experience.\n`
  209. )
  210. );
  211. // Fall back to latest supported react-scripts on Node 4
  212. version = 'react-scripts@0.9.x';
  213. }
  214. const root = path.resolve(name);
  215. const appName = path.basename(root);
  216. checkAppName(appName);
  217. fs.ensureDirSync(name);
  218. if (!isSafeToCreateProjectIn(root, name)) {
  219. process.exit(1);
  220. }
  221. console.log();
  222. console.log(`Creating a new React app in ${chalk.green(root)}.`);
  223. console.log();
  224. const packageJson = {
  225. name: appName,
  226. version: '0.1.0',
  227. private: true,
  228. };
  229. fs.writeFileSync(
  230. path.join(root, 'package.json'),
  231. JSON.stringify(packageJson, null, 2) + os.EOL
  232. );
  233. const useYarn = useNpm ? false : shouldUseYarn();
  234. const originalDirectory = process.cwd();
  235. process.chdir(root);
  236. if (!useYarn && !checkThatNpmCanReadCwd()) {
  237. process.exit(1);
  238. }
  239. if (!useYarn) {
  240. const npmInfo = checkNpmVersion();
  241. if (!npmInfo.hasMinNpm) {
  242. if (npmInfo.npmVersion) {
  243. console.log(
  244. chalk.yellow(
  245. `You are using npm ${npmInfo.npmVersion} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
  246. `Please update to npm 5 or higher for a better, fully supported experience.\n`
  247. )
  248. );
  249. }
  250. // Fall back to latest supported react-scripts for npm 3
  251. version = 'react-scripts@0.9.x';
  252. }
  253. } else if (usePnp) {
  254. const yarnInfo = checkYarnVersion();
  255. if (!yarnInfo.hasMinYarnPnp) {
  256. if (yarnInfo.yarnVersion) {
  257. console.log(
  258. chalk.yellow(
  259. `You are using Yarn ${yarnInfo.yarnVersion} together with the --use-pnp flag, but Plug'n'Play is only supported starting from the 1.12 release.\n\n` +
  260. `Please update to Yarn 1.12 or higher for a better, fully supported experience.\n`
  261. )
  262. );
  263. }
  264. // 1.11 had an issue with webpack-dev-middleware, so better not use PnP with it (never reached stable, but still)
  265. usePnp = false;
  266. }
  267. }
  268. if (useTypeScript) {
  269. console.log(
  270. chalk.yellow(
  271. 'The --typescript option has been deprecated and will be removed in a future release.'
  272. )
  273. );
  274. console.log(
  275. chalk.yellow(
  276. `In future, please use ${chalk.cyan('--template typescript')}.`
  277. )
  278. );
  279. console.log();
  280. if (!template) {
  281. template = 'typescript';
  282. }
  283. }
  284. if (useYarn) {
  285. let yarnUsesDefaultRegistry = true;
  286. try {
  287. yarnUsesDefaultRegistry =
  288. execSync('yarnpkg config get registry')
  289. .toString()
  290. .trim() === 'https://registry.yarnpkg.com';
  291. } catch (e) {
  292. // ignore
  293. }
  294. if (yarnUsesDefaultRegistry) {
  295. fs.copySync(
  296. require.resolve('./yarn.lock.cached'),
  297. path.join(root, 'yarn.lock')
  298. );
  299. }
  300. }
  301. run(
  302. root,
  303. appName,
  304. version,
  305. verbose,
  306. originalDirectory,
  307. template,
  308. useYarn,
  309. usePnp
  310. );
  311. }
  312. function shouldUseYarn() {
  313. try {
  314. execSync('yarnpkg --version', { stdio: 'ignore' });
  315. return true;
  316. } catch (e) {
  317. return false;
  318. }
  319. }
  320. function install(root, useYarn, usePnp, dependencies, verbose, isOnline) {
  321. return new Promise((resolve, reject) => {
  322. let command;
  323. let args;
  324. if (useYarn) {
  325. command = 'yarnpkg';
  326. args = ['add', '--exact'];
  327. if (!isOnline) {
  328. args.push('--offline');
  329. }
  330. if (usePnp) {
  331. args.push('--enable-pnp');
  332. }
  333. [].push.apply(args, dependencies);
  334. // Explicitly set cwd() to work around issues like
  335. // https://github.com/facebook/create-react-app/issues/3326.
  336. // Unfortunately we can only do this for Yarn because npm support for
  337. // equivalent --prefix flag doesn't help with this issue.
  338. // This is why for npm, we run checkThatNpmCanReadCwd() early instead.
  339. args.push('--cwd');
  340. args.push(root);
  341. if (!isOnline) {
  342. console.log(chalk.yellow('You appear to be offline.'));
  343. console.log(chalk.yellow('Falling back to the local Yarn cache.'));
  344. console.log();
  345. }
  346. } else {
  347. command = 'npm';
  348. args = [
  349. 'install',
  350. '--save',
  351. '--save-exact',
  352. '--loglevel',
  353. 'error',
  354. ].concat(dependencies);
  355. if (usePnp) {
  356. console.log(chalk.yellow("NPM doesn't support PnP."));
  357. console.log(chalk.yellow('Falling back to the regular installs.'));
  358. console.log();
  359. }
  360. }
  361. if (verbose) {
  362. args.push('--verbose');
  363. }
  364. const child = spawn(command, args, { stdio: 'inherit' });
  365. child.on('close', code => {
  366. if (code !== 0) {
  367. reject({
  368. command: `${command} ${args.join(' ')}`,
  369. });
  370. return;
  371. }
  372. resolve();
  373. });
  374. });
  375. }
  376. function run(
  377. root,
  378. appName,
  379. version,
  380. verbose,
  381. originalDirectory,
  382. template,
  383. useYarn,
  384. usePnp
  385. ) {
  386. Promise.all([
  387. getInstallPackage(version, originalDirectory),
  388. getTemplateInstallPackage(template, originalDirectory),
  389. ]).then(([packageToInstall, templateToInstall]) => {
  390. const allDependencies = ['react', 'react-dom', packageToInstall];
  391. console.log('Installing packages. This might take a couple of minutes.');
  392. Promise.all([
  393. getPackageInfo(packageToInstall),
  394. getPackageInfo(templateToInstall),
  395. ])
  396. .then(([packageInfo, templateInfo]) =>
  397. checkIfOnline(useYarn).then(isOnline => ({
  398. isOnline,
  399. packageInfo,
  400. templateInfo,
  401. }))
  402. )
  403. .then(({ isOnline, packageInfo, templateInfo }) => {
  404. let packageVersion = semver.coerce(packageInfo.version);
  405. const templatesVersionMinimum = '3.3.0';
  406. // Assume compatibility if we can't test the version.
  407. if (!semver.valid(packageVersion)) {
  408. packageVersion = templatesVersionMinimum;
  409. }
  410. // Only support templates when used alongside new react-scripts versions.
  411. const supportsTemplates = semver.gte(
  412. packageVersion,
  413. templatesVersionMinimum
  414. );
  415. if (supportsTemplates) {
  416. allDependencies.push(templateToInstall);
  417. } else if (template) {
  418. console.log('');
  419. console.log(
  420. `The ${chalk.cyan(packageInfo.name)} version you're using ${
  421. packageInfo.name === 'react-scripts' ? 'is not' : 'may not be'
  422. } compatible with the ${chalk.cyan('--template')} option.`
  423. );
  424. console.log('');
  425. }
  426. // TODO: Remove with next major release.
  427. if (!supportsTemplates && (template || '').includes('typescript')) {
  428. allDependencies.push(
  429. '@types/node',
  430. '@types/react',
  431. '@types/react-dom',
  432. '@types/jest',
  433. 'typescript'
  434. );
  435. }
  436. console.log(
  437. `Installing ${chalk.cyan('react')}, ${chalk.cyan(
  438. 'react-dom'
  439. )}, and ${chalk.cyan(packageInfo.name)}${
  440. supportsTemplates ? ` with ${chalk.cyan(templateInfo.name)}` : ''
  441. }...`
  442. );
  443. console.log();
  444. return install(
  445. root,
  446. useYarn,
  447. usePnp,
  448. allDependencies,
  449. verbose,
  450. isOnline
  451. ).then(() => ({
  452. packageInfo,
  453. supportsTemplates,
  454. templateInfo,
  455. }));
  456. })
  457. .then(async ({ packageInfo, supportsTemplates, templateInfo }) => {
  458. const packageName = packageInfo.name;
  459. const templateName = supportsTemplates ? templateInfo.name : undefined;
  460. checkNodeVersion(packageName);
  461. setCaretRangeForRuntimeDeps(packageName);
  462. const pnpPath = path.resolve(process.cwd(), '.pnp.js');
  463. const nodeArgs = fs.existsSync(pnpPath) ? ['--require', pnpPath] : [];
  464. await executeNodeScript(
  465. {
  466. cwd: process.cwd(),
  467. args: nodeArgs,
  468. },
  469. [root, appName, verbose, originalDirectory, templateName],
  470. `
  471. var init = require('${packageName}/scripts/init.js');
  472. init.apply(null, JSON.parse(process.argv[1]));
  473. `
  474. );
  475. if (version === 'react-scripts@0.9.x') {
  476. console.log(
  477. chalk.yellow(
  478. `\nNote: the project was bootstrapped with an old unsupported version of tools.\n` +
  479. `Please update to Node >=8.10 and npm >=5 to get supported tools in new projects.\n`
  480. )
  481. );
  482. }
  483. })
  484. .catch(reason => {
  485. console.log();
  486. console.log('Aborting installation.');
  487. if (reason.command) {
  488. console.log(` ${chalk.cyan(reason.command)} has failed.`);
  489. } else {
  490. console.log(
  491. chalk.red('Unexpected error. Please report it as a bug:')
  492. );
  493. console.log(reason);
  494. }
  495. console.log();
  496. // On 'exit' we will delete these files from target directory.
  497. const knownGeneratedFiles = [
  498. 'package.json',
  499. 'yarn.lock',
  500. 'node_modules',
  501. ];
  502. const currentFiles = fs.readdirSync(path.join(root));
  503. currentFiles.forEach(file => {
  504. knownGeneratedFiles.forEach(fileToMatch => {
  505. // This removes all knownGeneratedFiles.
  506. if (file === fileToMatch) {
  507. console.log(`Deleting generated file... ${chalk.cyan(file)}`);
  508. fs.removeSync(path.join(root, file));
  509. }
  510. });
  511. });
  512. const remainingFiles = fs.readdirSync(path.join(root));
  513. if (!remainingFiles.length) {
  514. // Delete target folder if empty
  515. console.log(
  516. `Deleting ${chalk.cyan(`${appName}/`)} from ${chalk.cyan(
  517. path.resolve(root, '..')
  518. )}`
  519. );
  520. process.chdir(path.resolve(root, '..'));
  521. fs.removeSync(path.join(root));
  522. }
  523. console.log('Done.');
  524. process.exit(1);
  525. });
  526. });
  527. }
  528. function getInstallPackage(version, originalDirectory) {
  529. let packageToInstall = 'react-scripts';
  530. const validSemver = semver.valid(version);
  531. if (validSemver) {
  532. packageToInstall += `@${validSemver}`;
  533. } else if (version) {
  534. if (version[0] === '@' && !version.includes('/')) {
  535. packageToInstall += version;
  536. } else if (version.match(/^file:/)) {
  537. packageToInstall = `file:${path.resolve(
  538. originalDirectory,
  539. version.match(/^file:(.*)?$/)[1]
  540. )}`;
  541. } else {
  542. // for tar.gz or alternative paths
  543. packageToInstall = version;
  544. }
  545. }
  546. const scriptsToWarn = [
  547. {
  548. name: 'react-scripts-ts',
  549. message: chalk.yellow(
  550. `The react-scripts-ts package is deprecated. TypeScript is now supported natively in Create React App. You can use the ${chalk.green(
  551. '--template typescript'
  552. )} option instead when generating your app to include TypeScript support. Would you like to continue using react-scripts-ts?`
  553. ),
  554. },
  555. ];
  556. for (const script of scriptsToWarn) {
  557. if (packageToInstall.startsWith(script.name)) {
  558. return inquirer
  559. .prompt({
  560. type: 'confirm',
  561. name: 'useScript',
  562. message: script.message,
  563. default: false,
  564. })
  565. .then(answer => {
  566. if (!answer.useScript) {
  567. process.exit(0);
  568. }
  569. return packageToInstall;
  570. });
  571. }
  572. }
  573. return Promise.resolve(packageToInstall);
  574. }
  575. function getTemplateInstallPackage(template, originalDirectory) {
  576. let templateToInstall = 'cra-template';
  577. if (template) {
  578. if (template.match(/^file:/)) {
  579. templateToInstall = `file:${path.resolve(
  580. originalDirectory,
  581. template.match(/^file:(.*)?$/)[1]
  582. )}`;
  583. } else if (
  584. template.includes('://') ||
  585. template.match(/^.+\.(tgz|tar\.gz)$/)
  586. ) {
  587. // for tar.gz or alternative paths
  588. templateToInstall = template;
  589. } else {
  590. // Add prefix 'cra-template-' to non-prefixed templates, leaving any
  591. // @scope/ intact.
  592. const packageMatch = template.match(/^(@[^/]+\/)?(.+)$/);
  593. const scope = packageMatch[1] || '';
  594. const templateName = packageMatch[2];
  595. if (
  596. templateName === templateToInstall ||
  597. templateName.startsWith(`${templateToInstall}-`)
  598. ) {
  599. // Covers:
  600. // - cra-template
  601. // - @SCOPE/cra-template
  602. // - cra-template-NAME
  603. // - @SCOPE/cra-template-NAME
  604. templateToInstall = `${scope}${templateName}`;
  605. } else if (templateName.startsWith('@')) {
  606. // Covers using @SCOPE only
  607. templateToInstall = `${templateName}/${templateToInstall}`;
  608. } else {
  609. // Covers templates without the `cra-template` prefix:
  610. // - NAME
  611. // - @SCOPE/NAME
  612. templateToInstall = `${scope}${templateToInstall}-${templateName}`;
  613. }
  614. }
  615. }
  616. return Promise.resolve(templateToInstall);
  617. }
  618. function getTemporaryDirectory() {
  619. return new Promise((resolve, reject) => {
  620. // Unsafe cleanup lets us recursively delete the directory if it contains
  621. // contents; by default it only allows removal if it's empty
  622. tmp.dir({ unsafeCleanup: true }, (err, tmpdir, callback) => {
  623. if (err) {
  624. reject(err);
  625. } else {
  626. resolve({
  627. tmpdir: tmpdir,
  628. cleanup: () => {
  629. try {
  630. callback();
  631. } catch (ignored) {
  632. // Callback might throw and fail, since it's a temp directory the
  633. // OS will clean it up eventually...
  634. }
  635. },
  636. });
  637. }
  638. });
  639. });
  640. }
  641. function extractStream(stream, dest) {
  642. return new Promise((resolve, reject) => {
  643. stream.pipe(
  644. unpack(dest, err => {
  645. if (err) {
  646. reject(err);
  647. } else {
  648. resolve(dest);
  649. }
  650. })
  651. );
  652. });
  653. }
  654. // Extract package name from tarball url or path.
  655. function getPackageInfo(installPackage) {
  656. if (installPackage.match(/^.+\.(tgz|tar\.gz)$/)) {
  657. return getTemporaryDirectory()
  658. .then(obj => {
  659. let stream;
  660. if (/^http/.test(installPackage)) {
  661. stream = hyperquest(installPackage);
  662. } else {
  663. stream = fs.createReadStream(installPackage);
  664. }
  665. return extractStream(stream, obj.tmpdir).then(() => obj);
  666. })
  667. .then(obj => {
  668. const { name, version } = require(path.join(
  669. obj.tmpdir,
  670. 'package.json'
  671. ));
  672. obj.cleanup();
  673. return { name, version };
  674. })
  675. .catch(err => {
  676. // The package name could be with or without semver version, e.g. react-scripts-0.2.0-alpha.1.tgz
  677. // However, this function returns package name only without semver version.
  678. console.log(
  679. `Could not extract the package name from the archive: ${err.message}`
  680. );
  681. const assumedProjectName = installPackage.match(
  682. /^.+\/(.+?)(?:-\d+.+)?\.(tgz|tar\.gz)$/
  683. )[1];
  684. console.log(
  685. `Based on the filename, assuming it is "${chalk.cyan(
  686. assumedProjectName
  687. )}"`
  688. );
  689. return Promise.resolve({ name: assumedProjectName });
  690. });
  691. } else if (installPackage.startsWith('git+')) {
  692. // Pull package name out of git urls e.g:
  693. // git+https://github.com/mycompany/react-scripts.git
  694. // git+ssh://github.com/mycompany/react-scripts.git#v1.2.3
  695. return Promise.resolve({
  696. name: installPackage.match(/([^/]+)\.git(#.*)?$/)[1],
  697. });
  698. } else if (installPackage.match(/.+@/)) {
  699. // Do not match @scope/ when stripping off @version or @tag
  700. return Promise.resolve({
  701. name: installPackage.charAt(0) + installPackage.substr(1).split('@')[0],
  702. version: installPackage.split('@')[1],
  703. });
  704. } else if (installPackage.match(/^file:/)) {
  705. const installPackagePath = installPackage.match(/^file:(.*)?$/)[1];
  706. const { name, version } = require(path.join(
  707. installPackagePath,
  708. 'package.json'
  709. ));
  710. return Promise.resolve({ name, version });
  711. }
  712. return Promise.resolve({ name: installPackage });
  713. }
  714. function checkNpmVersion() {
  715. let hasMinNpm = false;
  716. let npmVersion = null;
  717. try {
  718. npmVersion = execSync('npm --version')
  719. .toString()
  720. .trim();
  721. hasMinNpm = semver.gte(npmVersion, '5.0.0');
  722. } catch (err) {
  723. // ignore
  724. }
  725. return {
  726. hasMinNpm: hasMinNpm,
  727. npmVersion: npmVersion,
  728. };
  729. }
  730. function checkYarnVersion() {
  731. const minYarnPnp = '1.12.0';
  732. let hasMinYarnPnp = false;
  733. let yarnVersion = null;
  734. try {
  735. yarnVersion = execSync('yarnpkg --version')
  736. .toString()
  737. .trim();
  738. if (semver.valid(yarnVersion)) {
  739. hasMinYarnPnp = semver.gte(yarnVersion, minYarnPnp);
  740. } else {
  741. // Handle non-semver compliant yarn version strings, which yarn currently
  742. // uses for nightly builds. The regex truncates anything after the first
  743. // dash. See #5362.
  744. const trimmedYarnVersionMatch = /^(.+?)[-+].+$/.exec(yarnVersion);
  745. if (trimmedYarnVersionMatch) {
  746. const trimmedYarnVersion = trimmedYarnVersionMatch.pop();
  747. hasMinYarnPnp = semver.gte(trimmedYarnVersion, minYarnPnp);
  748. }
  749. }
  750. } catch (err) {
  751. // ignore
  752. }
  753. return {
  754. hasMinYarnPnp: hasMinYarnPnp,
  755. yarnVersion: yarnVersion,
  756. };
  757. }
  758. function checkNodeVersion(packageName) {
  759. const packageJsonPath = path.resolve(
  760. process.cwd(),
  761. 'node_modules',
  762. packageName,
  763. 'package.json'
  764. );
  765. if (!fs.existsSync(packageJsonPath)) {
  766. return;
  767. }
  768. const packageJson = require(packageJsonPath);
  769. if (!packageJson.engines || !packageJson.engines.node) {
  770. return;
  771. }
  772. if (!semver.satisfies(process.version, packageJson.engines.node)) {
  773. console.error(
  774. chalk.red(
  775. 'You are running Node %s.\n' +
  776. 'Create React App requires Node %s or higher. \n' +
  777. 'Please update your version of Node.'
  778. ),
  779. process.version,
  780. packageJson.engines.node
  781. );
  782. process.exit(1);
  783. }
  784. }
  785. function checkAppName(appName) {
  786. const validationResult = validateProjectName(appName);
  787. if (!validationResult.validForNewPackages) {
  788. console.error(
  789. chalk.red(
  790. `Cannot create a project named ${chalk.green(
  791. `"${appName}"`
  792. )} because of npm naming restrictions:\n`
  793. )
  794. );
  795. [
  796. ...(validationResult.errors || []),
  797. ...(validationResult.warnings || []),
  798. ].forEach(error => {
  799. console.error(chalk.red(` * ${error}`));
  800. });
  801. console.error(chalk.red('\nPlease choose a different project name.'));
  802. process.exit(1);
  803. }
  804. // TODO: there should be a single place that holds the dependencies
  805. const dependencies = ['react', 'react-dom', 'react-scripts'].sort();
  806. if (dependencies.includes(appName)) {
  807. console.error(
  808. chalk.red(
  809. `Cannot create a project named ${chalk.green(
  810. `"${appName}"`
  811. )} because a dependency with the same name exists.\n` +
  812. `Due to the way npm works, the following names are not allowed:\n\n`
  813. ) +
  814. chalk.cyan(dependencies.map(depName => ` ${depName}`).join('\n')) +
  815. chalk.red('\n\nPlease choose a different project name.')
  816. );
  817. process.exit(1);
  818. }
  819. }
  820. function makeCaretRange(dependencies, name) {
  821. const version = dependencies[name];
  822. if (typeof version === 'undefined') {
  823. console.error(chalk.red(`Missing ${name} dependency in package.json`));
  824. process.exit(1);
  825. }
  826. let patchedVersion = `^${version}`;
  827. if (!semver.validRange(patchedVersion)) {
  828. console.error(
  829. `Unable to patch ${name} dependency version because version ${chalk.red(
  830. version
  831. )} will become invalid ${chalk.red(patchedVersion)}`
  832. );
  833. patchedVersion = version;
  834. }
  835. dependencies[name] = patchedVersion;
  836. }
  837. function setCaretRangeForRuntimeDeps(packageName) {
  838. const packagePath = path.join(process.cwd(), 'package.json');
  839. const packageJson = require(packagePath);
  840. if (typeof packageJson.dependencies === 'undefined') {
  841. console.error(chalk.red('Missing dependencies in package.json'));
  842. process.exit(1);
  843. }
  844. const packageVersion = packageJson.dependencies[packageName];
  845. if (typeof packageVersion === 'undefined') {
  846. console.error(chalk.red(`Unable to find ${packageName} in package.json`));
  847. process.exit(1);
  848. }
  849. makeCaretRange(packageJson.dependencies, 'react');
  850. makeCaretRange(packageJson.dependencies, 'react-dom');
  851. fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + os.EOL);
  852. }
  853. // If project only contains files generated by GH, it’s safe.
  854. // Also, if project contains remnant error logs from a previous
  855. // installation, lets remove them now.
  856. // We also special case IJ-based products .idea because it integrates with CRA:
  857. // https://github.com/facebook/create-react-app/pull/368#issuecomment-243446094
  858. function isSafeToCreateProjectIn(root, name) {
  859. const validFiles = [
  860. '.DS_Store',
  861. '.git',
  862. '.gitattributes',
  863. '.gitignore',
  864. '.gitlab-ci.yml',
  865. '.hg',
  866. '.hgcheck',
  867. '.hgignore',
  868. '.idea',
  869. '.npmignore',
  870. '.travis.yml',
  871. 'docs',
  872. 'LICENSE',
  873. 'README.md',
  874. 'mkdocs.yml',
  875. 'Thumbs.db',
  876. ];
  877. // These files should be allowed to remain on a failed install, but then
  878. // silently removed during the next create.
  879. const errorLogFilePatterns = [
  880. 'npm-debug.log',
  881. 'yarn-error.log',
  882. 'yarn-debug.log',
  883. ];
  884. const isErrorLog = file => {
  885. return errorLogFilePatterns.some(pattern => file.startsWith(pattern));
  886. };
  887. const conflicts = fs
  888. .readdirSync(root)
  889. .filter(file => !validFiles.includes(file))
  890. // IntelliJ IDEA creates module files before CRA is launched
  891. .filter(file => !/\.iml$/.test(file))
  892. // Don't treat log files from previous installation as conflicts
  893. .filter(file => !isErrorLog(file));
  894. if (conflicts.length > 0) {
  895. console.log(
  896. `The directory ${chalk.green(name)} contains files that could conflict:`
  897. );
  898. console.log();
  899. for (const file of conflicts) {
  900. try {
  901. const stats = fs.lstatSync(path.join(root, file));
  902. if (stats.isDirectory()) {
  903. console.log(` ${chalk.blue(`${file}/`)}`);
  904. } else {
  905. console.log(` ${file}`);
  906. }
  907. } catch (e) {
  908. console.log(` ${file}`);
  909. }
  910. }
  911. console.log();
  912. console.log(
  913. 'Either try using a new directory name, or remove the files listed above.'
  914. );
  915. return false;
  916. }
  917. // Remove any log files from a previous installation.
  918. fs.readdirSync(root).forEach(file => {
  919. if (isErrorLog(file)) {
  920. fs.removeSync(path.join(root, file));
  921. }
  922. });
  923. return true;
  924. }
  925. function getProxy() {
  926. if (process.env.https_proxy) {
  927. return process.env.https_proxy;
  928. } else {
  929. try {
  930. // Trying to read https-proxy from .npmrc
  931. let httpsProxy = execSync('npm config get https-proxy')
  932. .toString()
  933. .trim();
  934. return httpsProxy !== 'null' ? httpsProxy : undefined;
  935. } catch (e) {
  936. return;
  937. }
  938. }
  939. }
  940. // See https://github.com/facebook/create-react-app/pull/3355
  941. function checkThatNpmCanReadCwd() {
  942. const cwd = process.cwd();
  943. let childOutput = null;
  944. try {
  945. // Note: intentionally using spawn over exec since
  946. // the problem doesn't reproduce otherwise.
  947. // `npm config list` is the only reliable way I could find
  948. // to reproduce the wrong path. Just printing process.cwd()
  949. // in a Node process was not enough.
  950. childOutput = spawn.sync('npm', ['config', 'list']).output.join('');
  951. } catch (err) {
  952. // Something went wrong spawning node.
  953. // Not great, but it means we can't do this check.
  954. // We might fail later on, but let's continue.
  955. return true;
  956. }
  957. if (typeof childOutput !== 'string') {
  958. return true;
  959. }
  960. const lines = childOutput.split('\n');
  961. // `npm config list` output includes the following line:
  962. // "; cwd = C:\path\to\current\dir" (unquoted)
  963. // I couldn't find an easier way to get it.
  964. const prefix = '; cwd = ';
  965. const line = lines.find(line => line.startsWith(prefix));
  966. if (typeof line !== 'string') {
  967. // Fail gracefully. They could remove it.
  968. return true;
  969. }
  970. const npmCWD = line.substring(prefix.length);
  971. if (npmCWD === cwd) {
  972. return true;
  973. }
  974. console.error(
  975. chalk.red(
  976. `Could not start an npm process in the right directory.\n\n` +
  977. `The current directory is: ${chalk.bold(cwd)}\n` +
  978. `However, a newly started npm process runs in: ${chalk.bold(
  979. npmCWD
  980. )}\n\n` +
  981. `This is probably caused by a misconfigured system terminal shell.`
  982. )
  983. );
  984. if (process.platform === 'win32') {
  985. console.error(
  986. chalk.red(`On Windows, this can usually be fixed by running:\n\n`) +
  987. ` ${chalk.cyan(
  988. 'reg'
  989. )} delete "HKCU\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n` +
  990. ` ${chalk.cyan(
  991. 'reg'
  992. )} delete "HKLM\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n\n` +
  993. chalk.red(`Try to run the above two lines in the terminal.\n`) +
  994. chalk.red(
  995. `To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/`
  996. )
  997. );
  998. }
  999. return false;
  1000. }
  1001. function checkIfOnline(useYarn) {
  1002. if (!useYarn) {
  1003. // Don't ping the Yarn registry.
  1004. // We'll just assume the best case.
  1005. return Promise.resolve(true);
  1006. }
  1007. return new Promise(resolve => {
  1008. dns.lookup('registry.yarnpkg.com', err => {
  1009. let proxy;
  1010. if (err != null && (proxy = getProxy())) {
  1011. // If a proxy is defined, we likely can't resolve external hostnames.
  1012. // Try to resolve the proxy name as an indication of a connection.
  1013. dns.lookup(url.parse(proxy).hostname, proxyErr => {
  1014. resolve(proxyErr == null);
  1015. });
  1016. } else {
  1017. resolve(err == null);
  1018. }
  1019. });
  1020. });
  1021. }
  1022. function executeNodeScript({ cwd, args }, data, source) {
  1023. return new Promise((resolve, reject) => {
  1024. const child = spawn(
  1025. process.execPath,
  1026. [...args, '-e', source, '--', JSON.stringify(data)],
  1027. { cwd, stdio: 'inherit' }
  1028. );
  1029. child.on('close', code => {
  1030. if (code !== 0) {
  1031. reject({
  1032. command: `node ${args.join(' ')}`,
  1033. });
  1034. return;
  1035. }
  1036. resolve();
  1037. });
  1038. });
  1039. }