diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index 26d762c3597..c74815a4db9 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -198,29 +198,6 @@ if (openBrowser('http://localhost:3000')) { } ``` -#### `prompt(message: string, isYesDefault: boolean): Promise` - -This function displays a console prompt to the user. - -By convention, "no" should be the conservative choice.
-If you mistype the answer, we'll always take it as a "no".
-You can control the behavior on `` with `isYesDefault`. - -```js -var prompt = require('react-dev-utils/prompt'); - -prompt( - 'Are you sure you want to eat all the candy?', - /* isYesDefault */ false -).then(shouldEat => { - if (shouldEat) { - console.log('You have successfully consumed all the candy.'); - } else { - console.log('Phew, candy is still available!'); - } -}); -``` - #### `webpackHotDevClient.js` This is an alternative client for [WebpackDevServer](https://github.com/webpack/webpack-dev-server) that shows a syntax error overlay. diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 9edb99875c4..918b18ed1ed 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -22,7 +22,6 @@ "launchEditor.js", "openBrowser.js", "openChrome.applescript", - "prompt.js", "WatchMissingNodeModulesPlugin.js", "webpackHotDevClient.js" ], diff --git a/packages/react-dev-utils/prompt.js b/packages/react-dev-utils/prompt.js deleted file mode 100644 index 4257924576d..00000000000 --- a/packages/react-dev-utils/prompt.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -var rl = require('readline'); - -// Convention: "no" should be the conservative choice. -// If you mistype the answer, we'll always take it as a "no". -// You can control the behavior on with `isYesDefault`. -function prompt(question, isYesDefault) { - if (typeof isYesDefault !== 'boolean') { - throw new Error( - 'Provide explicit boolean isYesDefault as second argument.' - ); - } - return new Promise(resolve => { - var rlInterface = rl.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - var hint = isYesDefault === true ? '[Y/n]' : '[y/N]'; - var message = question + ' ' + hint + '\n'; - - rlInterface.question(message, function(answer) { - rlInterface.close(); - - var useDefault = answer.trim().length === 0; - if (useDefault) { - return resolve(isYesDefault); - } - - var isYes = answer.match(/^(yes|y)$/i); - return resolve(isYes); - }); - }); -} - -module.exports = prompt; diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 453697d6d78..9208b713878 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -47,6 +47,7 @@ "fs-extra": "0.30.0", "html-webpack-plugin": "2.28.0", "http-proxy-middleware": "0.17.3", + "inquirer": "3.0.6", "jest": "18.1.0", "object-assign": "4.1.1", "postcss-loader": "1.3.3", diff --git a/packages/react-scripts/scripts/eject.js b/packages/react-scripts/scripts/eject.js index e07a57ba5e7..28a21b9ed01 100644 --- a/packages/react-scripts/scripts/eject.js +++ b/packages/react-scripts/scripts/eject.js @@ -20,181 +20,187 @@ const fs = require('fs-extra'); const path = require('path'); const spawnSync = require('cross-spawn').sync; const chalk = require('chalk'); -const prompt = require('react-dev-utils/prompt'); +const inquirer = require('inquirer'); const paths = require('../config/paths'); const createJestConfig = require('./utils/createJestConfig'); const green = chalk.green; const cyan = chalk.cyan; -prompt( - 'Are you sure you want to eject? This action is permanent.', - false -).then(shouldEject => { - if (!shouldEject) { - console.log(cyan('Close one! Eject aborted.')); - process.exit(1); - } - - console.log('Ejecting...'); - - const ownPath = paths.ownPath; - const appPath = paths.appPath; - - function verifyAbsent(file) { - if (fs.existsSync(path.join(appPath, file))) { - console.error( - `\`${file}\` already exists in your app folder. We cannot ` + - 'continue as you would lose all the changes in that file or directory. ' + - 'Please move or delete it (maybe make a copy for backup) and run this ' + - 'command again.' - ); +inquirer + .prompt({ + type: 'confirm', + name: 'shouldEject', + message: 'Are you sure you want to eject? This action is permanent.', + default: false, + }) + .then(answer => { + if (!answer.shouldEject) { + console.log(cyan('Close one! Eject aborted.')); process.exit(1); } - } - - const folders = ['config', 'config/jest', 'scripts', 'scripts/utils']; - - // Make shallow array of files paths - const files = folders.reduce( - (files, folder) => { - return files.concat( - fs - .readdirSync(path.join(ownPath, folder)) - // set full path - .map(file => path.join(ownPath, folder, file)) - // omit dirs from file list - .filter(file => fs.lstatSync(file).isFile()) - ); - }, - [] - ); - - // Ensure that the app folder is clean and we won't override any files - folders.forEach(verifyAbsent); - files.forEach(verifyAbsent); - - console.log(); - console.log(cyan(`Copying files into ${appPath}`)); - - folders.forEach(folder => { - fs.mkdirSync(path.join(appPath, folder)); - }); - files.forEach(file => { - let content = fs.readFileSync(file, 'utf8'); + console.log('Ejecting...'); + + const ownPath = paths.ownPath; + const appPath = paths.appPath; + + function verifyAbsent(file) { + if (fs.existsSync(path.join(appPath, file))) { + console.error( + `\`${file}\` already exists in your app folder. We cannot ` + + 'continue as you would lose all the changes in that file or directory. ' + + 'Please move or delete it (maybe make a copy for backup) and run this ' + + 'command again.' + ); + process.exit(1); + } + } - // Skip flagged files - if (content.match(/\/\/ @remove-file-on-eject/)) { - return; + const folders = ['config', 'config/jest', 'scripts', 'scripts/utils']; + + // Make shallow array of files paths + const files = folders.reduce( + (files, folder) => { + return files.concat( + fs + .readdirSync(path.join(ownPath, folder)) + // set full path + .map(file => path.join(ownPath, folder, file)) + // omit dirs from file list + .filter(file => fs.lstatSync(file).isFile()) + ); + }, + [] + ); + + // Ensure that the app folder is clean and we won't override any files + folders.forEach(verifyAbsent); + files.forEach(verifyAbsent); + + console.log(); + console.log(cyan(`Copying files into ${appPath}`)); + + folders.forEach(folder => { + fs.mkdirSync(path.join(appPath, folder)); + }); + + files.forEach(file => { + let content = fs.readFileSync(file, 'utf8'); + + // Skip flagged files + if (content.match(/\/\/ @remove-file-on-eject/)) { + return; + } + content = content + // Remove dead code from .js files on eject + .replace( + /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/mg, + '' + ) + // Remove dead code from .applescript files on eject + .replace( + /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/mg, + '' + ) + .trim() + '\n'; + console.log(` Adding ${cyan(file.replace(ownPath, ''))} to the project`); + fs.writeFileSync(file.replace(ownPath, appPath), content); + }); + console.log(); + + const ownPackage = require(path.join(ownPath, 'package.json')); + const appPackage = require(path.join(appPath, 'package.json')); + + console.log(cyan('Updating the dependencies')); + const ownPackageName = ownPackage.name; + if (appPackage.devDependencies[ownPackageName]) { + console.log(` Removing ${cyan(ownPackageName)} from devDependencies`); + delete appPackage.devDependencies[ownPackageName]; } - content = content - // Remove dead code from .js files on eject - .replace( - /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/mg, - '' - ) - // Remove dead code from .applescript files on eject - .replace( - /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/mg, - '' - ) - .trim() + '\n'; - console.log(` Adding ${cyan(file.replace(ownPath, ''))} to the project`); - fs.writeFileSync(file.replace(ownPath, appPath), content); - }); - console.log(); - - const ownPackage = require(path.join(ownPath, 'package.json')); - const appPackage = require(path.join(appPath, 'package.json')); - - console.log(cyan('Updating the dependencies')); - const ownPackageName = ownPackage.name; - if (appPackage.devDependencies[ownPackageName]) { - console.log(` Removing ${cyan(ownPackageName)} from devDependencies`); - delete appPackage.devDependencies[ownPackageName]; - } - if (appPackage.dependencies[ownPackageName]) { - console.log(` Removing ${cyan(ownPackageName)} from dependencies`); - delete appPackage.dependencies[ownPackageName]; - } - - Object.keys(ownPackage.dependencies).forEach(key => { - // For some reason optionalDependencies end up in dependencies after install - if (ownPackage.optionalDependencies[key]) { - return; + if (appPackage.dependencies[ownPackageName]) { + console.log(` Removing ${cyan(ownPackageName)} from dependencies`); + delete appPackage.dependencies[ownPackageName]; } - console.log(` Adding ${cyan(key)} to devDependencies`); - appPackage.devDependencies[key] = ownPackage.dependencies[key]; - }); - console.log(); - console.log(cyan('Updating the scripts')); - delete appPackage.scripts['eject']; - Object.keys(appPackage.scripts).forEach(key => { - Object.keys(ownPackage.bin).forEach(binKey => { - const regex = new RegExp(binKey + ' (\\w+)', 'g'); - appPackage.scripts[key] = appPackage.scripts[key].replace( - regex, - 'node scripts/$1.js' - ); - console.log( - ` Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan(`"node scripts/${key}.js"`)}` - ); - }); - }); - console.log(); - console.log(cyan('Configuring package.json')); - // Add Jest config - console.log(` Adding ${cyan('Jest')} configuration`); - appPackage.jest = createJestConfig( - filePath => path.posix.join('', filePath), - null, - true - ); - - // Add Babel config - console.log(` Adding ${cyan('Babel')} preset`); - appPackage.babel = { - presets: ['react-app'], - }; - - // Add ESlint config - console.log(` Adding ${cyan('ESLint')} configuration`); - appPackage.eslintConfig = { - extends: 'react-app', - }; - - fs.writeFileSync( - path.join(appPath, 'package.json'), - JSON.stringify(appPackage, null, 2) + '\n' - ); - console.log(); - - // "Don't destroy what isn't ours" - if (ownPath.indexOf(appPath) === 0) { - try { - // remove react-scripts and react-scripts binaries from app node_modules + Object.keys(ownPackage.dependencies).forEach(key => { + // For some reason optionalDependencies end up in dependencies after install + if (ownPackage.optionalDependencies[key]) { + return; + } + console.log(` Adding ${cyan(key)} to devDependencies`); + appPackage.devDependencies[key] = ownPackage.dependencies[key]; + }); + console.log(); + console.log(cyan('Updating the scripts')); + delete appPackage.scripts['eject']; + Object.keys(appPackage.scripts).forEach(key => { Object.keys(ownPackage.bin).forEach(binKey => { - fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey)); + const regex = new RegExp(binKey + ' (\\w+)', 'g'); + appPackage.scripts[key] = appPackage.scripts[key].replace( + regex, + 'node scripts/$1.js' + ); + console.log( + ` Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan(`"node scripts/${key}.js"`)}` + ); }); - fs.removeSync(ownPath); - } catch (e) { - // It's not essential that this succeeds + }); + + console.log(); + console.log(cyan('Configuring package.json')); + // Add Jest config + console.log(` Adding ${cyan('Jest')} configuration`); + appPackage.jest = createJestConfig( + filePath => path.posix.join('', filePath), + null, + true + ); + + // Add Babel config + console.log(` Adding ${cyan('Babel')} preset`); + appPackage.babel = { + presets: ['react-app'], + }; + + // Add ESlint config + console.log(` Adding ${cyan('ESLint')} configuration`); + appPackage.eslintConfig = { + extends: 'react-app', + }; + + fs.writeFileSync( + path.join(appPath, 'package.json'), + JSON.stringify(appPackage, null, 2) + '\n' + ); + console.log(); + + // "Don't destroy what isn't ours" + if (ownPath.indexOf(appPath) === 0) { + try { + // remove react-scripts and react-scripts binaries from app node_modules + Object.keys(ownPackage.bin).forEach(binKey => { + fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey)); + }); + fs.removeSync(ownPath); + } catch (e) { + // It's not essential that this succeeds + } } - } - - if (fs.existsSync(paths.yarnLockFile)) { - console.log(cyan('Running yarn...')); - spawnSync('yarnpkg', [], { stdio: 'inherit' }); - } else { - console.log(cyan('Running npm install...')); - spawnSync('npm', ['install'], { stdio: 'inherit' }); - } - console.log(green('Ejected successfully!')); - console.log(); - - console.log(green('Please consider sharing why you ejected in this survey:')); - console.log(green(' http://goo.gl/forms/Bi6CZjk1EqsdelXk1')); - console.log(); -}); + + if (fs.existsSync(paths.yarnLockFile)) { + console.log(cyan('Running yarn...')); + spawnSync('yarnpkg', [], { stdio: 'inherit' }); + } else { + console.log(cyan('Running npm install...')); + spawnSync('npm', ['install'], { stdio: 'inherit' }); + } + console.log(green('Ejected successfully!')); + console.log(); + + console.log( + green('Please consider sharing why you ejected in this survey:') + ); + console.log(green(' http://goo.gl/forms/Bi6CZjk1EqsdelXk1')); + console.log(); + }); diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 465474f2ddc..4448a0407e0 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -30,7 +30,7 @@ const clearConsole = require('react-dev-utils/clearConsole'); const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); const getProcessForPort = require('react-dev-utils/getProcessForPort'); const openBrowser = require('react-dev-utils/openBrowser'); -const prompt = require('react-dev-utils/prompt'); +const inquirer = require('inquirer'); const paths = require('../config/paths'); const config = require('../config/webpack.config.dev'); const devServerConfig = require('../config/webpackDevServer.config'); @@ -114,13 +114,18 @@ detect(DEFAULT_PORT, HOST).then(port => { if (isInteractive) { clearConsole(); const existingProcess = getProcessForPort(DEFAULT_PORT); - const question = chalk.yellow( - `Something is already running on port ${DEFAULT_PORT}.` + - `${existingProcess ? ` Probably:\n ${existingProcess}` : ''}` - ) + '\n\nWould you like to run the app on another port instead?'; - - prompt(question, true).then(shouldChangePort => { - if (shouldChangePort) { + const question = { + type: 'confirm', + name: 'shouldChangePort', + message: chalk.yellow( + `Something is already running on port ${DEFAULT_PORT}.` + + `${existingProcess ? ` Probably:\n ${existingProcess}` : ''}` + ) + '\n\nWould you like to run the app on another port instead?', + default: true, + }; + + inquirer.prompt(question).then(answer => { + if (answer.shouldChangePort) { run(port); } });