/** * 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('') .usage(`${chalk.green('')} [options]`) .action(name => { projectName = name; }) .option('--verbose', 'print additional logs') .option('--info', 'print environment debug info') .option( '--scripts-version ', 'use a non-standard version of react-scripts' ) .option( '--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('')} 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('')}` ); 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(); }); }); }