Skip to content

Add an interactive CLI to npm start #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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,
}
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
134 changes: 113 additions & 21 deletions scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand All @@ -88,37 +102,41 @@ 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
// preceding a much more useful Babel syntax error.
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
Expand All @@ -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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use vorpal.parse(process.argv) to jump straight into a command if extra args are passed into npm start.

Reference on vorpal.parse.

.delimiter('\nAvailable commands: `help`, `open (o)`, `edit (e)`, `verbose (v)`, `exit`\n>')
.show();
}