1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120 |
- /**
- * 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.
- */
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // /!\ DO NOT MODIFY THIS FILE /!\
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- //
- // create-react-app is installed globally on people's computers. This means
- // that it is extremely difficult to have them upgrade the version and
- // because there's only one global version installed, it is very prone to
- // breaking changes.
- //
- // The only job of create-react-app is to init the repository and then
- // forward all the commands to the local version of create-react-app.
- //
- // If you need to add a new command, please add it to the scripts/ folder.
- //
- // The only reason to modify this file is to add more warnings and
- // troubleshooting information for the `create-react-app` command.
- //
- // Do not make breaking changes! We absolutely don't want to have to
- // tell people to update their global version of create-react-app.
- //
- // Also be careful with new language features.
- // This file must work on Node 6+.
- //
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // /!\ DO NOT MODIFY THIS FILE /!\
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 'use strict';
- const chalk = require('chalk');
- const commander = require('commander');
- const dns = require('dns');
- const envinfo = require('envinfo');
- const execSync = require('child_process').execSync;
- const fs = require('fs-extra');
- const hyperquest = require('hyperquest');
- const inquirer = require('inquirer');
- const os = require('os');
- const path = require('path');
- const semver = require('semver');
- const spawn = require('cross-spawn');
- const tmp = require('tmp');
- const unpack = require('tar-pack').unpack;
- const url = require('url');
- const validateProjectName = require('validate-npm-package-name');
- const packageJson = require('./package.json');
- let projectName;
- const program = new commander.Command(packageJson.name)
- .version(packageJson.version)
- .arguments('<project-directory>')
- .usage(`${chalk.green('<project-directory>')} [options]`)
- .action(name => {
- projectName = name;
- })
- .option('--verbose', 'print additional logs')
- .option('--info', 'print environment debug info')
- .option(
- '--scripts-version <alternative-package>',
- 'use a non-standard version of react-scripts'
- )
- .option(
- '--template <path-to-template>',
- 'specify a template for the created project'
- )
- .option('--use-npm')
- .option('--use-pnp')
- // TODO: Remove this in next major release.
- .option(
- '--typescript',
- '(this option will be removed in favour of templates in the next major release of create-react-app)'
- )
- .allowUnknownOption()
- .on('--help', () => {
- console.log(` Only ${chalk.green('<project-directory>')} is required.`);
- console.log();
- console.log(
- ` A custom ${chalk.cyan('--scripts-version')} can be one of:`
- );
- console.log(` - a specific npm version: ${chalk.green('0.8.2')}`);
- console.log(` - a specific npm tag: ${chalk.green('@next')}`);
- console.log(
- ` - a custom fork published on npm: ${chalk.green(
- 'my-react-scripts'
- )}`
- );
- console.log(
- ` - a local path relative to the current working directory: ${chalk.green(
- 'file:../my-react-scripts'
- )}`
- );
- console.log(
- ` - a .tgz archive: ${chalk.green(
- 'https://mysite.com/my-react-scripts-0.8.2.tgz'
- )}`
- );
- console.log(
- ` - a .tar.gz archive: ${chalk.green(
- 'https://mysite.com/my-react-scripts-0.8.2.tar.gz'
- )}`
- );
- console.log(
- ` It is not needed unless you specifically want to use a fork.`
- );
- console.log();
- console.log(` A custom ${chalk.cyan('--template')} can be one of:`);
- console.log(
- ` - a custom fork published on npm: ${chalk.green(
- 'cra-template-typescript'
- )}`
- );
- console.log(
- ` - a local path relative to the current working directory: ${chalk.green(
- 'file:../my-custom-template'
- )}`
- );
- console.log(
- ` - a .tgz archive: ${chalk.green(
- 'https://mysite.com/my-custom-template-0.8.2.tgz'
- )}`
- );
- console.log(
- ` - a .tar.gz archive: ${chalk.green(
- 'https://mysite.com/my-custom-template-0.8.2.tar.gz'
- )}`
- );
- console.log();
- console.log(
- ` If you have any problems, do not hesitate to file an issue:`
- );
- console.log(
- ` ${chalk.cyan(
- 'https://github.com/facebook/create-react-app/issues/new'
- )}`
- );
- console.log();
- })
- .parse(process.argv);
- if (program.info) {
- console.log(chalk.bold('\nEnvironment Info:'));
- console.log(
- `\n current version of ${packageJson.name}: ${packageJson.version}`
- );
- console.log(` running from ${__dirname}`);
- return envinfo
- .run(
- {
- System: ['OS', 'CPU'],
- Binaries: ['Node', 'npm', 'Yarn'],
- Browsers: ['Chrome', 'Edge', 'Internet Explorer', 'Firefox', 'Safari'],
- npmPackages: ['react', 'react-dom', 'react-scripts'],
- npmGlobalPackages: ['create-react-app'],
- },
- {
- duplicates: true,
- showNotFound: true,
- }
- )
- .then(console.log);
- }
- if (typeof projectName === 'undefined') {
- console.error('Please specify the project directory:');
- console.log(
- ` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`
- );
- console.log();
- console.log('For example:');
- console.log(` ${chalk.cyan(program.name())} ${chalk.green('my-react-app')}`);
- console.log();
- console.log(
- `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
- );
- process.exit(1);
- }
- createApp(
- projectName,
- program.verbose,
- program.scriptsVersion,
- program.template,
- program.useNpm,
- program.usePnp,
- program.typescript
- );
- function createApp(
- name,
- verbose,
- version,
- template,
- useNpm,
- usePnp,
- useTypeScript
- ) {
- const unsupportedNodeVersion = !semver.satisfies(process.version, '>=8.10.0');
- if (unsupportedNodeVersion && useTypeScript) {
- console.error(
- chalk.red(
- `You are using Node ${process.version} with the TypeScript template. Node 8.10 or higher is required to use TypeScript.\n`
- )
- );
- process.exit(1);
- } else if (unsupportedNodeVersion) {
- console.log(
- chalk.yellow(
- `You are using Node ${process.version} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
- `Please update to Node 8.10 or higher for a better, fully supported experience.\n`
- )
- );
- // Fall back to latest supported react-scripts on Node 4
- version = 'react-scripts@0.9.x';
- }
- const root = path.resolve(name);
- const appName = path.basename(root);
- checkAppName(appName);
- fs.ensureDirSync(name);
- if (!isSafeToCreateProjectIn(root, name)) {
- process.exit(1);
- }
- console.log();
- console.log(`Creating a new React app in ${chalk.green(root)}.`);
- console.log();
- const packageJson = {
- name: appName,
- version: '0.1.0',
- private: true,
- };
- fs.writeFileSync(
- path.join(root, 'package.json'),
- JSON.stringify(packageJson, null, 2) + os.EOL
- );
- const useYarn = useNpm ? false : shouldUseYarn();
- const originalDirectory = process.cwd();
- process.chdir(root);
- if (!useYarn && !checkThatNpmCanReadCwd()) {
- process.exit(1);
- }
- if (!useYarn) {
- const npmInfo = checkNpmVersion();
- if (!npmInfo.hasMinNpm) {
- if (npmInfo.npmVersion) {
- console.log(
- chalk.yellow(
- `You are using npm ${npmInfo.npmVersion} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
- `Please update to npm 5 or higher for a better, fully supported experience.\n`
- )
- );
- }
- // Fall back to latest supported react-scripts for npm 3
- version = 'react-scripts@0.9.x';
- }
- } else if (usePnp) {
- const yarnInfo = checkYarnVersion();
- if (!yarnInfo.hasMinYarnPnp) {
- if (yarnInfo.yarnVersion) {
- console.log(
- chalk.yellow(
- `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` +
- `Please update to Yarn 1.12 or higher for a better, fully supported experience.\n`
- )
- );
- }
- // 1.11 had an issue with webpack-dev-middleware, so better not use PnP with it (never reached stable, but still)
- usePnp = false;
- }
- }
- if (useTypeScript) {
- console.log(
- chalk.yellow(
- 'The --typescript option has been deprecated and will be removed in a future release.'
- )
- );
- console.log(
- chalk.yellow(
- `In future, please use ${chalk.cyan('--template typescript')}.`
- )
- );
- console.log();
- if (!template) {
- template = 'typescript';
- }
- }
- if (useYarn) {
- let yarnUsesDefaultRegistry = true;
- try {
- yarnUsesDefaultRegistry =
- execSync('yarnpkg config get registry')
- .toString()
- .trim() === 'https://registry.yarnpkg.com';
- } catch (e) {
- // ignore
- }
- if (yarnUsesDefaultRegistry) {
- fs.copySync(
- require.resolve('./yarn.lock.cached'),
- path.join(root, 'yarn.lock')
- );
- }
- }
- run(
- root,
- appName,
- version,
- verbose,
- originalDirectory,
- template,
- useYarn,
- usePnp
- );
- }
- function shouldUseYarn() {
- try {
- execSync('yarnpkg --version', { stdio: 'ignore' });
- return true;
- } catch (e) {
- return false;
- }
- }
- function install(root, useYarn, usePnp, dependencies, verbose, isOnline) {
- return new Promise((resolve, reject) => {
- let command;
- let args;
- if (useYarn) {
- command = 'yarnpkg';
- args = ['add', '--exact'];
- if (!isOnline) {
- args.push('--offline');
- }
- if (usePnp) {
- args.push('--enable-pnp');
- }
- [].push.apply(args, dependencies);
- // Explicitly set cwd() to work around issues like
- // https://github.com/facebook/create-react-app/issues/3326.
- // Unfortunately we can only do this for Yarn because npm support for
- // equivalent --prefix flag doesn't help with this issue.
- // This is why for npm, we run checkThatNpmCanReadCwd() early instead.
- args.push('--cwd');
- args.push(root);
- if (!isOnline) {
- console.log(chalk.yellow('You appear to be offline.'));
- console.log(chalk.yellow('Falling back to the local Yarn cache.'));
- console.log();
- }
- } else {
- command = 'npm';
- args = [
- 'install',
- '--save',
- '--save-exact',
- '--loglevel',
- 'error',
- ].concat(dependencies);
- if (usePnp) {
- console.log(chalk.yellow("NPM doesn't support PnP."));
- console.log(chalk.yellow('Falling back to the regular installs.'));
- console.log();
- }
- }
- if (verbose) {
- args.push('--verbose');
- }
- const child = spawn(command, args, { stdio: 'inherit' });
- child.on('close', code => {
- if (code !== 0) {
- reject({
- command: `${command} ${args.join(' ')}`,
- });
- return;
- }
- resolve();
- });
- });
- }
- function run(
- root,
- appName,
- version,
- verbose,
- originalDirectory,
- template,
- useYarn,
- usePnp
- ) {
- Promise.all([
- getInstallPackage(version, originalDirectory),
- getTemplateInstallPackage(template, originalDirectory),
- ]).then(([packageToInstall, templateToInstall]) => {
- const allDependencies = ['react', 'react-dom', packageToInstall];
- console.log('Installing packages. This might take a couple of minutes.');
- Promise.all([
- getPackageInfo(packageToInstall),
- getPackageInfo(templateToInstall),
- ])
- .then(([packageInfo, templateInfo]) =>
- checkIfOnline(useYarn).then(isOnline => ({
- isOnline,
- packageInfo,
- templateInfo,
- }))
- )
- .then(({ isOnline, packageInfo, templateInfo }) => {
- let packageVersion = semver.coerce(packageInfo.version);
- const templatesVersionMinimum = '3.3.0';
- // Assume compatibility if we can't test the version.
- if (!semver.valid(packageVersion)) {
- packageVersion = templatesVersionMinimum;
- }
- // Only support templates when used alongside new react-scripts versions.
- const supportsTemplates = semver.gte(
- packageVersion,
- templatesVersionMinimum
- );
- if (supportsTemplates) {
- allDependencies.push(templateToInstall);
- } else if (template) {
- console.log('');
- console.log(
- `The ${chalk.cyan(packageInfo.name)} version you're using ${
- packageInfo.name === 'react-scripts' ? 'is not' : 'may not be'
- } compatible with the ${chalk.cyan('--template')} option.`
- );
- console.log('');
- }
- // TODO: Remove with next major release.
- if (!supportsTemplates && (template || '').includes('typescript')) {
- allDependencies.push(
- '@types/node',
- '@types/react',
- '@types/react-dom',
- '@types/jest',
- 'typescript'
- );
- }
- console.log(
- `Installing ${chalk.cyan('react')}, ${chalk.cyan(
- 'react-dom'
- )}, and ${chalk.cyan(packageInfo.name)}${
- supportsTemplates ? ` with ${chalk.cyan(templateInfo.name)}` : ''
- }...`
- );
- console.log();
- return install(
- root,
- useYarn,
- usePnp,
- allDependencies,
- verbose,
- isOnline
- ).then(() => ({
- packageInfo,
- supportsTemplates,
- templateInfo,
- }));
- })
- .then(async ({ packageInfo, supportsTemplates, templateInfo }) => {
- const packageName = packageInfo.name;
- const templateName = supportsTemplates ? templateInfo.name : undefined;
- checkNodeVersion(packageName);
- setCaretRangeForRuntimeDeps(packageName);
- const pnpPath = path.resolve(process.cwd(), '.pnp.js');
- const nodeArgs = fs.existsSync(pnpPath) ? ['--require', pnpPath] : [];
- await executeNodeScript(
- {
- cwd: process.cwd(),
- args: nodeArgs,
- },
- [root, appName, verbose, originalDirectory, templateName],
- `
- var init = require('${packageName}/scripts/init.js');
- init.apply(null, JSON.parse(process.argv[1]));
- `
- );
- if (version === 'react-scripts@0.9.x') {
- console.log(
- chalk.yellow(
- `\nNote: the project was bootstrapped with an old unsupported version of tools.\n` +
- `Please update to Node >=8.10 and npm >=5 to get supported tools in new projects.\n`
- )
- );
- }
- })
- .catch(reason => {
- console.log();
- console.log('Aborting installation.');
- if (reason.command) {
- console.log(` ${chalk.cyan(reason.command)} has failed.`);
- } else {
- console.log(
- chalk.red('Unexpected error. Please report it as a bug:')
- );
- console.log(reason);
- }
- console.log();
- // On 'exit' we will delete these files from target directory.
- const knownGeneratedFiles = [
- 'package.json',
- 'yarn.lock',
- 'node_modules',
- ];
- const currentFiles = fs.readdirSync(path.join(root));
- currentFiles.forEach(file => {
- knownGeneratedFiles.forEach(fileToMatch => {
- // This removes all knownGeneratedFiles.
- if (file === fileToMatch) {
- console.log(`Deleting generated file... ${chalk.cyan(file)}`);
- fs.removeSync(path.join(root, file));
- }
- });
- });
- const remainingFiles = fs.readdirSync(path.join(root));
- if (!remainingFiles.length) {
- // Delete target folder if empty
- console.log(
- `Deleting ${chalk.cyan(`${appName}/`)} from ${chalk.cyan(
- path.resolve(root, '..')
- )}`
- );
- process.chdir(path.resolve(root, '..'));
- fs.removeSync(path.join(root));
- }
- console.log('Done.');
- process.exit(1);
- });
- });
- }
- function getInstallPackage(version, originalDirectory) {
- let packageToInstall = 'react-scripts';
- const validSemver = semver.valid(version);
- if (validSemver) {
- packageToInstall += `@${validSemver}`;
- } else if (version) {
- if (version[0] === '@' && !version.includes('/')) {
- packageToInstall += version;
- } else if (version.match(/^file:/)) {
- packageToInstall = `file:${path.resolve(
- originalDirectory,
- version.match(/^file:(.*)?$/)[1]
- )}`;
- } else {
- // for tar.gz or alternative paths
- packageToInstall = version;
- }
- }
- const scriptsToWarn = [
- {
- name: 'react-scripts-ts',
- message: chalk.yellow(
- `The react-scripts-ts package is deprecated. TypeScript is now supported natively in Create React App. You can use the ${chalk.green(
- '--template typescript'
- )} option instead when generating your app to include TypeScript support. Would you like to continue using react-scripts-ts?`
- ),
- },
- ];
- for (const script of scriptsToWarn) {
- if (packageToInstall.startsWith(script.name)) {
- return inquirer
- .prompt({
- type: 'confirm',
- name: 'useScript',
- message: script.message,
- default: false,
- })
- .then(answer => {
- if (!answer.useScript) {
- process.exit(0);
- }
- return packageToInstall;
- });
- }
- }
- return Promise.resolve(packageToInstall);
- }
- function getTemplateInstallPackage(template, originalDirectory) {
- let templateToInstall = 'cra-template';
- if (template) {
- if (template.match(/^file:/)) {
- templateToInstall = `file:${path.resolve(
- originalDirectory,
- template.match(/^file:(.*)?$/)[1]
- )}`;
- } else if (
- template.includes('://') ||
- template.match(/^.+\.(tgz|tar\.gz)$/)
- ) {
- // for tar.gz or alternative paths
- templateToInstall = template;
- } else {
- // Add prefix 'cra-template-' to non-prefixed templates, leaving any
- // @scope/ intact.
- const packageMatch = template.match(/^(@[^/]+\/)?(.+)$/);
- const scope = packageMatch[1] || '';
- const templateName = packageMatch[2];
- if (
- templateName === templateToInstall ||
- templateName.startsWith(`${templateToInstall}-`)
- ) {
- // Covers:
- // - cra-template
- // - @SCOPE/cra-template
- // - cra-template-NAME
- // - @SCOPE/cra-template-NAME
- templateToInstall = `${scope}${templateName}`;
- } else if (templateName.startsWith('@')) {
- // Covers using @SCOPE only
- templateToInstall = `${templateName}/${templateToInstall}`;
- } else {
- // Covers templates without the `cra-template` prefix:
- // - NAME
- // - @SCOPE/NAME
- templateToInstall = `${scope}${templateToInstall}-${templateName}`;
- }
- }
- }
- return Promise.resolve(templateToInstall);
- }
- function getTemporaryDirectory() {
- return new Promise((resolve, reject) => {
- // Unsafe cleanup lets us recursively delete the directory if it contains
- // contents; by default it only allows removal if it's empty
- tmp.dir({ unsafeCleanup: true }, (err, tmpdir, callback) => {
- if (err) {
- reject(err);
- } else {
- resolve({
- tmpdir: tmpdir,
- cleanup: () => {
- try {
- callback();
- } catch (ignored) {
- // Callback might throw and fail, since it's a temp directory the
- // OS will clean it up eventually...
- }
- },
- });
- }
- });
- });
- }
- function extractStream(stream, dest) {
- return new Promise((resolve, reject) => {
- stream.pipe(
- unpack(dest, err => {
- if (err) {
- reject(err);
- } else {
- resolve(dest);
- }
- })
- );
- });
- }
- // Extract package name from tarball url or path.
- function getPackageInfo(installPackage) {
- if (installPackage.match(/^.+\.(tgz|tar\.gz)$/)) {
- return getTemporaryDirectory()
- .then(obj => {
- let stream;
- if (/^http/.test(installPackage)) {
- stream = hyperquest(installPackage);
- } else {
- stream = fs.createReadStream(installPackage);
- }
- return extractStream(stream, obj.tmpdir).then(() => obj);
- })
- .then(obj => {
- const { name, version } = require(path.join(
- obj.tmpdir,
- 'package.json'
- ));
- obj.cleanup();
- return { name, version };
- })
- .catch(err => {
- // The package name could be with or without semver version, e.g. react-scripts-0.2.0-alpha.1.tgz
- // However, this function returns package name only without semver version.
- console.log(
- `Could not extract the package name from the archive: ${err.message}`
- );
- const assumedProjectName = installPackage.match(
- /^.+\/(.+?)(?:-\d+.+)?\.(tgz|tar\.gz)$/
- )[1];
- console.log(
- `Based on the filename, assuming it is "${chalk.cyan(
- assumedProjectName
- )}"`
- );
- return Promise.resolve({ name: assumedProjectName });
- });
- } else if (installPackage.startsWith('git+')) {
- // Pull package name out of git urls e.g:
- // git+https://github.com/mycompany/react-scripts.git
- // git+ssh://github.com/mycompany/react-scripts.git#v1.2.3
- return Promise.resolve({
- name: installPackage.match(/([^/]+)\.git(#.*)?$/)[1],
- });
- } else if (installPackage.match(/.+@/)) {
- // Do not match @scope/ when stripping off @version or @tag
- return Promise.resolve({
- name: installPackage.charAt(0) + installPackage.substr(1).split('@')[0],
- version: installPackage.split('@')[1],
- });
- } else if (installPackage.match(/^file:/)) {
- const installPackagePath = installPackage.match(/^file:(.*)?$/)[1];
- const { name, version } = require(path.join(
- installPackagePath,
- 'package.json'
- ));
- return Promise.resolve({ name, version });
- }
- return Promise.resolve({ name: installPackage });
- }
- function checkNpmVersion() {
- let hasMinNpm = false;
- let npmVersion = null;
- try {
- npmVersion = execSync('npm --version')
- .toString()
- .trim();
- hasMinNpm = semver.gte(npmVersion, '5.0.0');
- } catch (err) {
- // ignore
- }
- return {
- hasMinNpm: hasMinNpm,
- npmVersion: npmVersion,
- };
- }
- function checkYarnVersion() {
- const minYarnPnp = '1.12.0';
- let hasMinYarnPnp = false;
- let yarnVersion = null;
- try {
- yarnVersion = execSync('yarnpkg --version')
- .toString()
- .trim();
- if (semver.valid(yarnVersion)) {
- hasMinYarnPnp = semver.gte(yarnVersion, minYarnPnp);
- } else {
- // Handle non-semver compliant yarn version strings, which yarn currently
- // uses for nightly builds. The regex truncates anything after the first
- // dash. See #5362.
- const trimmedYarnVersionMatch = /^(.+?)[-+].+$/.exec(yarnVersion);
- if (trimmedYarnVersionMatch) {
- const trimmedYarnVersion = trimmedYarnVersionMatch.pop();
- hasMinYarnPnp = semver.gte(trimmedYarnVersion, minYarnPnp);
- }
- }
- } catch (err) {
- // ignore
- }
- return {
- hasMinYarnPnp: hasMinYarnPnp,
- yarnVersion: yarnVersion,
- };
- }
- function checkNodeVersion(packageName) {
- const packageJsonPath = path.resolve(
- process.cwd(),
- 'node_modules',
- packageName,
- 'package.json'
- );
- if (!fs.existsSync(packageJsonPath)) {
- return;
- }
- const packageJson = require(packageJsonPath);
- if (!packageJson.engines || !packageJson.engines.node) {
- return;
- }
- if (!semver.satisfies(process.version, packageJson.engines.node)) {
- console.error(
- chalk.red(
- 'You are running Node %s.\n' +
- 'Create React App requires Node %s or higher. \n' +
- 'Please update your version of Node.'
- ),
- process.version,
- packageJson.engines.node
- );
- process.exit(1);
- }
- }
- function checkAppName(appName) {
- const validationResult = validateProjectName(appName);
- if (!validationResult.validForNewPackages) {
- console.error(
- chalk.red(
- `Cannot create a project named ${chalk.green(
- `"${appName}"`
- )} because of npm naming restrictions:\n`
- )
- );
- [
- ...(validationResult.errors || []),
- ...(validationResult.warnings || []),
- ].forEach(error => {
- console.error(chalk.red(` * ${error}`));
- });
- console.error(chalk.red('\nPlease choose a different project name.'));
- process.exit(1);
- }
- // TODO: there should be a single place that holds the dependencies
- const dependencies = ['react', 'react-dom', 'react-scripts'].sort();
- if (dependencies.includes(appName)) {
- console.error(
- chalk.red(
- `Cannot create a project named ${chalk.green(
- `"${appName}"`
- )} because a dependency with the same name exists.\n` +
- `Due to the way npm works, the following names are not allowed:\n\n`
- ) +
- chalk.cyan(dependencies.map(depName => ` ${depName}`).join('\n')) +
- chalk.red('\n\nPlease choose a different project name.')
- );
- process.exit(1);
- }
- }
- function makeCaretRange(dependencies, name) {
- const version = dependencies[name];
- if (typeof version === 'undefined') {
- console.error(chalk.red(`Missing ${name} dependency in package.json`));
- process.exit(1);
- }
- let patchedVersion = `^${version}`;
- if (!semver.validRange(patchedVersion)) {
- console.error(
- `Unable to patch ${name} dependency version because version ${chalk.red(
- version
- )} will become invalid ${chalk.red(patchedVersion)}`
- );
- patchedVersion = version;
- }
- dependencies[name] = patchedVersion;
- }
- function setCaretRangeForRuntimeDeps(packageName) {
- const packagePath = path.join(process.cwd(), 'package.json');
- const packageJson = require(packagePath);
- if (typeof packageJson.dependencies === 'undefined') {
- console.error(chalk.red('Missing dependencies in package.json'));
- process.exit(1);
- }
- const packageVersion = packageJson.dependencies[packageName];
- if (typeof packageVersion === 'undefined') {
- console.error(chalk.red(`Unable to find ${packageName} in package.json`));
- process.exit(1);
- }
- makeCaretRange(packageJson.dependencies, 'react');
- makeCaretRange(packageJson.dependencies, 'react-dom');
- fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + os.EOL);
- }
- // If project only contains files generated by GH, it’s safe.
- // Also, if project contains remnant error logs from a previous
- // installation, lets remove them now.
- // We also special case IJ-based products .idea because it integrates with CRA:
- // https://github.com/facebook/create-react-app/pull/368#issuecomment-243446094
- function isSafeToCreateProjectIn(root, name) {
- const validFiles = [
- '.DS_Store',
- '.git',
- '.gitattributes',
- '.gitignore',
- '.gitlab-ci.yml',
- '.hg',
- '.hgcheck',
- '.hgignore',
- '.idea',
- '.npmignore',
- '.travis.yml',
- 'docs',
- 'LICENSE',
- 'README.md',
- 'mkdocs.yml',
- 'Thumbs.db',
- ];
- // These files should be allowed to remain on a failed install, but then
- // silently removed during the next create.
- const errorLogFilePatterns = [
- 'npm-debug.log',
- 'yarn-error.log',
- 'yarn-debug.log',
- ];
- const isErrorLog = file => {
- return errorLogFilePatterns.some(pattern => file.startsWith(pattern));
- };
- const conflicts = fs
- .readdirSync(root)
- .filter(file => !validFiles.includes(file))
- // IntelliJ IDEA creates module files before CRA is launched
- .filter(file => !/\.iml$/.test(file))
- // Don't treat log files from previous installation as conflicts
- .filter(file => !isErrorLog(file));
- if (conflicts.length > 0) {
- console.log(
- `The directory ${chalk.green(name)} contains files that could conflict:`
- );
- console.log();
- for (const file of conflicts) {
- try {
- const stats = fs.lstatSync(path.join(root, file));
- if (stats.isDirectory()) {
- console.log(` ${chalk.blue(`${file}/`)}`);
- } else {
- console.log(` ${file}`);
- }
- } catch (e) {
- console.log(` ${file}`);
- }
- }
- console.log();
- console.log(
- 'Either try using a new directory name, or remove the files listed above.'
- );
- return false;
- }
- // Remove any log files from a previous installation.
- fs.readdirSync(root).forEach(file => {
- if (isErrorLog(file)) {
- fs.removeSync(path.join(root, file));
- }
- });
- return true;
- }
- function getProxy() {
- if (process.env.https_proxy) {
- return process.env.https_proxy;
- } else {
- try {
- // Trying to read https-proxy from .npmrc
- let httpsProxy = execSync('npm config get https-proxy')
- .toString()
- .trim();
- return httpsProxy !== 'null' ? httpsProxy : undefined;
- } catch (e) {
- return;
- }
- }
- }
- // See https://github.com/facebook/create-react-app/pull/3355
- function checkThatNpmCanReadCwd() {
- const cwd = process.cwd();
- let childOutput = null;
- try {
- // Note: intentionally using spawn over exec since
- // the problem doesn't reproduce otherwise.
- // `npm config list` is the only reliable way I could find
- // to reproduce the wrong path. Just printing process.cwd()
- // in a Node process was not enough.
- childOutput = spawn.sync('npm', ['config', 'list']).output.join('');
- } catch (err) {
- // Something went wrong spawning node.
- // Not great, but it means we can't do this check.
- // We might fail later on, but let's continue.
- return true;
- }
- if (typeof childOutput !== 'string') {
- return true;
- }
- const lines = childOutput.split('\n');
- // `npm config list` output includes the following line:
- // "; cwd = C:\path\to\current\dir" (unquoted)
- // I couldn't find an easier way to get it.
- const prefix = '; cwd = ';
- const line = lines.find(line => line.startsWith(prefix));
- if (typeof line !== 'string') {
- // Fail gracefully. They could remove it.
- return true;
- }
- const npmCWD = line.substring(prefix.length);
- if (npmCWD === cwd) {
- return true;
- }
- console.error(
- chalk.red(
- `Could not start an npm process in the right directory.\n\n` +
- `The current directory is: ${chalk.bold(cwd)}\n` +
- `However, a newly started npm process runs in: ${chalk.bold(
- npmCWD
- )}\n\n` +
- `This is probably caused by a misconfigured system terminal shell.`
- )
- );
- if (process.platform === 'win32') {
- console.error(
- chalk.red(`On Windows, this can usually be fixed by running:\n\n`) +
- ` ${chalk.cyan(
- 'reg'
- )} delete "HKCU\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n` +
- ` ${chalk.cyan(
- 'reg'
- )} delete "HKLM\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n\n` +
- chalk.red(`Try to run the above two lines in the terminal.\n`) +
- chalk.red(
- `To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/`
- )
- );
- }
- return false;
- }
- function checkIfOnline(useYarn) {
- if (!useYarn) {
- // Don't ping the Yarn registry.
- // We'll just assume the best case.
- return Promise.resolve(true);
- }
- return new Promise(resolve => {
- dns.lookup('registry.yarnpkg.com', err => {
- let proxy;
- if (err != null && (proxy = getProxy())) {
- // If a proxy is defined, we likely can't resolve external hostnames.
- // Try to resolve the proxy name as an indication of a connection.
- dns.lookup(url.parse(proxy).hostname, proxyErr => {
- resolve(proxyErr == null);
- });
- } else {
- resolve(err == null);
- }
- });
- });
- }
- function executeNodeScript({ cwd, args }, data, source) {
- return new Promise((resolve, reject) => {
- const child = spawn(
- process.execPath,
- [...args, '-e', source, '--', JSON.stringify(data)],
- { cwd, stdio: 'inherit' }
- );
- child.on('close', code => {
- if (code !== 0) {
- reject({
- command: `node ${args.join(' ')}`,
- });
- return;
- }
- resolve();
- });
- });
- }
|