Skip to content

Suggest another port when 3000 is busy #243

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

Merged
merged 1 commit into from
Jul 27, 2016
Merged
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
2 changes: 1 addition & 1 deletion config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var paths = require('./paths');
module.exports = {
devtool: 'eval',
entry: [
require.resolve('webpack-dev-server/client') + '?http://localhost:3000',
require.resolve('webpack-dev-server/client'),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haha seriously. This also fixes #194 in my testing.
I think this was only necessary because contentBase is configurable in Webpack.. which it isn’t here.

require.resolve('webpack/hot/dev-server'),
require.resolve('./polyfills'),
path.join(paths.appSrc, 'index')
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"chalk": "1.1.3",
"cross-spawn": "4.0.0",
"css-loader": "0.23.1",
"detect-port": "0.1.4",
"eslint": "3.1.1",
"eslint-loader": "1.4.1",
"eslint-plugin-import": "1.10.3",
Expand Down
27 changes: 8 additions & 19 deletions scripts/eject.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,14 @@

var fs = require('fs');
var path = require('path');
var rl = require('readline');
var rimrafSync = require('rimraf').sync;
var spawnSync = require('cross-spawn').sync;
var paths = require('../config/paths');
var prompt = require('./utils/prompt');

var prompt = function(question, cb) {
var rlInterface = rl.createInterface({
input: process.stdin,
output: process.stdout,
});
rlInterface.question(question + '\n', function(answer) {
rlInterface.close();
cb(answer);
})
}

prompt('Are you sure you want to eject? This action is permanent. [y/N]', function(answer) {
var shouldEject = answer && (
answer.toLowerCase() === 'y' ||
answer.toLowerCase() === 'yes'
);
prompt(
'Are you sure you want to eject? This action is permanent.',
false
).then(shouldEject => {
if (!shouldEject) {
console.log('Close one! Eject aborted.');
process.exit(1);
Expand All @@ -52,7 +39,8 @@ prompt('Are you sure you want to eject? This action is permanent. [y/N]', functi
path.join('config', 'webpack.config.prod.js'),
path.join('scripts', 'build.js'),
path.join('scripts', 'start.js'),
path.join('scripts', 'openChrome.applescript')
path.join('scripts', 'utils', 'chrome.applescript'),
path.join('scripts', 'utils', 'prompt.js')
];

// Ensure that the app folder is clean and we won't override any files
Expand All @@ -72,6 +60,7 @@ prompt('Are you sure you want to eject? This action is permanent. [y/N]', functi
fs.mkdirSync(path.join(appPath, 'config'));
fs.mkdirSync(path.join(appPath, 'config', 'flow'));
fs.mkdirSync(path.join(appPath, 'scripts'));
fs.mkdirSync(path.join(appPath, 'scripts', 'utils'));

files.forEach(function(file) {
console.log('Copying ' + file + ' to ' + appPath);
Expand Down
164 changes: 99 additions & 65 deletions scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ var path = require('path');
var chalk = require('chalk');
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('../config/webpack.config.dev');
var execSync = require('child_process').execSync;
var opn = require('opn');
var detect = require('detect-port');
var prompt = require('./utils/prompt');
var config = require('../config/webpack.config.dev');

var DEFAULT_PORT = 3000;
var compiler;

// TODO: hide this behind a flag and eliminate dead code on eject.
// This shouldn't be exposed to the user.
Expand Down Expand Up @@ -63,72 +68,76 @@ function clearConsole() {
process.stdout.write('\x1B[2J\x1B[0f');
}

var compiler = webpack(config, handleCompile);
compiler.plugin('invalid', function () {
clearConsole();
console.log('Compiling...');
});
compiler.plugin('done', function (stats) {
clearConsole();
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();
return;
}
function setupCompiler(port) {
compiler = webpack(config, handleCompile);

var json = stats.toJson();
var formattedErrors = json.errors.map(message =>
'Error in ' + formatMessage(message)
);
var formattedWarnings = json.warnings.map(message =>
'Warning in ' + formatMessage(message)
);
compiler.plugin('invalid', function() {
clearConsole();
console.log('Compiling...');
});

if (hasErrors) {
console.log(chalk.red('Failed to compile.'));
console.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);
compiler.plugin('done', function(stats) {
clearConsole();
var hasErrors = stats.hasErrors();
var hasWarnings = stats.hasWarnings();
if (!hasErrors && !hasWarnings) {
console.log(chalk.green('Compiled successfully!'));
console.log();
});
// If errors exist, ignore warnings.
return;
}
console.log('The app is running at http://localhost:' + port + '/');
console.log();
return;
}

if (hasWarnings) {
console.log(chalk.yellow('Compiled with warnings.'));
console.log();
formattedWarnings.forEach(message => {
console.log(message);
var json = stats.toJson();
var formattedErrors = json.errors.map(message =>
'Error in ' + formatMessage(message)
);
var formattedWarnings = json.warnings.map(message =>
'Warning in ' + formatMessage(message)
);

if (hasErrors) {
console.log(chalk.red('Failed to compile.'));
console.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();
});
// If errors exist, ignore warnings.
return;
}

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.');
}
});
if (hasWarnings) {
console.log(chalk.yellow('Compiled with warnings.'));
console.log();
formattedWarnings.forEach(message => {
console.log(message);
console.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.');
}
});
}

function openBrowser() {
function openBrowser(port) {
if (process.platform === 'darwin') {
try {
// Try our best to reuse existing tab
// on OS X Google Chrome with AppleScript
execSync('ps cax | grep "Google Chrome"');
execSync(
'osascript ' +
path.resolve(__dirname, './openChrome.applescript') +
' http://localhost:3000/'
path.resolve(__dirname, './utils/chrome.applescript') +
' http://localhost:' + port + '/'
);
return;
} catch (err) {
Expand All @@ -137,21 +146,46 @@ function openBrowser() {
}
// Fallback to opn
// (It will always open new tab)
opn('http://localhost:3000/');
opn('http://localhost:' + port + '/');
}

function runDevServer(port) {
new WebpackDevServer(compiler, {
historyApiFallback: true,
hot: true, // Note: only CSS is currently hot reloaded
publicPath: config.output.publicPath,
quiet: true
}).listen(port, (err, result) => {
if (err) {
return console.log(err);
}

clearConsole();
console.log(chalk.cyan('Starting the development server...'));
console.log();
openBrowser(port);
});
}

new WebpackDevServer(compiler, {
historyApiFallback: true,
hot: true, // Note: only CSS is currently hot reloaded
publicPath: config.output.publicPath,
quiet: true
}).listen(3000, function (err, result) {
if (err) {
return console.log(err);
function run(port) {
setupCompiler(port);
runDevServer(port);
}

detect(DEFAULT_PORT).then(port => {
if (port === DEFAULT_PORT) {
run(port);
return;
}

clearConsole();
console.log(chalk.cyan('Starting the development server...'));
console.log();
openBrowser();
var question =
chalk.yellow('Something is already running at port ' + DEFAULT_PORT + '.') +
'\n\nWould you like to run the app at another port instead?';

prompt(question, true).then(shouldChangePort => {
if (shouldChangePort) {
run(port);
}
});
});
File renamed without changes.
40 changes: 40 additions & 0 deletions scripts/utils/prompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* 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.
*/

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 <Enter> with `isYesDefault`.
module.exports = function (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);
});
});
};