From eb0beb1e7358a5ab08efb96b6217aef9cb5cc93a Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 21 Jul 2016 16:55:52 -0700 Subject: [PATCH] added an interactive CLI using Vorpal --- config/webpack.config.dev.js | 6 +- package.json | 1 + scripts/start.js | 134 +++++++++++++++++++++++++++++------ 3 files changed, 119 insertions(+), 22 deletions(-) diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 74a6c9a0b10..4bb49b9aa48 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -20,6 +20,7 @@ var relativePath = isInNodeModules ? '../../..' : '..'; if (process.argv[2] === '--debug-template') { relativePath = '../template'; } +var projectRootPath = path.resolve(__dirname, relativePath); var srcPath = path.resolve(__dirname, relativePath, 'src'); var nodeModulesPath = path.join(__dirname, '..', 'node_modules'); var indexHtmlPath = path.resolve(__dirname, relativePath, 'index.html'); @@ -95,5 +96,8 @@ module.exports = { new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }), // Note: only CSS is currently hot reloaded new webpack.HotModuleReplacementPlugin() - ] + ], + cliInfo: { // NOTE: these options are provided to the CLI tool but not webpack + projectRootPath: projectRootPath, + } }; diff --git a/package.json b/package.json index b65e5100421..944ffce899c 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "rimraf": "2.5.3", "style-loader": "0.13.1", "url-loader": "0.5.7", + "vorpal": "^1.11.2", "webpack": "1.13.1", "webpack-dev-server": "1.14.1" }, diff --git a/scripts/start.js b/scripts/start.js index 08ea9617ec0..cda4354a381 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -16,6 +16,19 @@ var WebpackDevServer = require('webpack-dev-server'); var config = require('../config/webpack.config.dev'); var execSync = require('child_process').execSync; var opn = require('opn'); +var vorpal = require('vorpal')(); + +// NOTE: Use `vorpal.log(...)` instead of `console.log(...)` here to redraw CLI prompts +// correctly. https://github.com/dthree/vorpal/wiki/API-%7C-vorpal#vorpallogstring-strings + +var isInVerboseMode = false; +function maybeShowWebpackLogs(stats) { + if (!isInVerboseMode) { + return; + } + + vorpal.log(stats.toString({colors: true})); +} // TODO: hide this behind a flag and eliminate dead code on eject. // This shouldn't be exposed to the user. @@ -65,17 +78,18 @@ function clearConsole() { var compiler = webpack(config, handleCompile); compiler.plugin('invalid', function () { clearConsole(); - console.log('Compiling...'); + vorpal.log('Compiling...'); }); compiler.plugin('done', function (stats) { clearConsole(); + maybeShowWebpackLogs(stats); var hasErrors = stats.hasErrors(); var hasWarnings = stats.hasWarnings(); if (!hasErrors && !hasWarnings) { - console.log(chalk.green('Compiled successfully!')); - console.log(); - console.log('The app is running at http://localhost:3000/'); - console.log(); + vorpal.log(chalk.green('Compiled successfully!')); + vorpal.log(); + vorpal.log('The app is running at http://localhost:3000/'); + vorpal.log(); return; } @@ -88,8 +102,8 @@ compiler.plugin('done', function (stats) { ); if (hasErrors) { - console.log(chalk.red('Failed to compile.')); - console.log(); + vorpal.log(chalk.red('Failed to compile.')); + vorpal.log(); if (formattedErrors.some(isLikelyASyntaxError)) { // If there are any syntax errors, show just them. // This prevents a confusing ESLint parsing error @@ -97,28 +111,32 @@ compiler.plugin('done', function (stats) { formattedErrors = formattedErrors.filter(isLikelyASyntaxError); } formattedErrors.forEach(message => { - console.log(message); - console.log(); + vorpal.log(message); + vorpal.log(); }); // If errors exist, ignore warnings. return; } if (hasWarnings) { - console.log(chalk.yellow('Compiled with warnings.')); - console.log(); + vorpal.log(chalk.yellow('Compiled with warnings.')); + vorpal.log(); formattedWarnings.forEach(message => { - console.log(message); - console.log(); + vorpal.log(message); + vorpal.log(); }); - console.log('You may use special comments to disable some warnings.'); - console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.'); - console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.'); + vorpal.log('You may use special comments to disable some warnings.'); + vorpal.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.'); + vorpal.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.'); } }); function openBrowser() { + vorpal.log(chalk.blue( + 'Opening http://localhost:3000/ in browser...\n' + )); + if (process.platform === 'darwin') { try { // Try our best to reuse existing tab @@ -139,18 +157,92 @@ function openBrowser() { opn('http://localhost:3000/'); } -new WebpackDevServer(compiler, { +var server = new WebpackDevServer(compiler, { historyApiFallback: true, hot: true, // Note: only CSS is currently hot reloaded publicPath: config.output.publicPath, quiet: true -}).listen(3000, 'localhost', function (err, result) { +}); + +server.listen(3000, 'localhost', function (err, result) { if (err) { - return console.log(err); + return vorpal.log(err); } clearConsole(); - console.log(chalk.cyan('Starting the development server...')); - console.log(); + vorpal.log(chalk.cyan('Starting the development server...')); + vorpal.log(); openBrowser(); + startCLI(); }); + +function startCLI() { + vorpal + .command('open') + .alias('o') + .description('Opens your app in browser.') + .action(function(args, onFinish) { + openBrowser(); + onFinish(); + }); + + vorpal + .command('edit [editor...]') + .alias('e') + .description( + 'Opens your app in editor. Defaults to `$EDITOR`; pass an extra string ' + + 'to specify your editor of choice \n(e.g., `edit sublime text` or `e visual studio code`).' + ) + .action(function(args, onFinish) { + var ctx = this; + + var projectRootPath = config.cliInfo.projectRootPath; + + var editor = ''; + if (args.editor) { + editor = args.editor.join(' '); + } else if (process.env.EDITOR) { + editor = process.env.EDITOR; + } else { + ctx.log( + 'Oops, no editor was found. You can pass an extra string ' + + 'to specify your editor of choice (e.g., `edit sublime text` or `e visual studio code`).' + ); + onFinish(); + } + + ctx.log(chalk.blue( + 'Opening `' + projectRootPath + '` in `' + editor + '`...\n' + )); + + opn(projectRootPath, {app: editor}).catch(function(e) { + ctx.log( + 'Oops, we tried to open your app with `' + editor + '` but it didn\'t ' + + 'work. Please check if `' + editor + '` is an application. ' + + 'You can pass a second argument to specify ' + + 'your editor of choice (e.g., `edit sublime text` or `e visual studio code`).' + ); + }) + onFinish(); + }); + + vorpal + .command('verbose') + .alias('v') + .description('Verbose mode (show all webpack logs).') + .action(function(args, onFinish) { + isInVerboseMode = !isInVerboseMode; + this.log(chalk.blue( + 'Verbose mode ' + (!isInVerboseMode ? + 'off; will only show webpack errors.' : + 'on; will show all webpack logs.') + )); + onFinish(); + }); + + vorpal.history('create-react-app-cli'); + + vorpal + .delimiter('\nAvailable commands: `help`, `open (o)`, `edit (e)`, `verbose (v)`, `exit`\n>') + .show(); +}