From d52d7a1006306bfdb46779c4a98695151341de54 Mon Sep 17 00:00:00 2001 From: Ian McNally Date: Tue, 22 Nov 2016 15:44:21 -0500 Subject: [PATCH] add logging of existing default port process on start (#816) * add logging of existing port process on start * Move port process wording in start command on to next line * Color the named processes as cyan in terminal output * Add handling for multiple processes on a part - With the currently process filtering, if multiple processes are returned as running on port 3000, this command would fail. This splits apart the process IDing and the process naming, to support multiple processes. - One curious thing about the bash command to get processes, is that it'll include browsers with a window open on localhost:3000. May want to reconsider that. * Add process directory to existing port warning - also moved terminal coloring up, when getting the process, to be able to distinguish the process command from the directory * Change output color to all cyan, except "in" * Rename getProcessNameOnPort -> getProcessForPort - better reflects its broadened scope (both command and directory) * Add checking if process is a CRA instance, to customize port running message - moved from using package.json to a regex, for reliability * Move getProcessForPort to react-dev-utils - also allowed for breakdown of commands into helper methods * Add documentation for getProcessForPort * Add getProcessForPort to list of dev-scripts files * Use app's package name when CRA app is running on another port * Filter port process by those listening - Removed the handling of multiple process IDs since you can filtering by listening process (and not have the browser in the list of processes) - Trimmed the terminal outputs for better matching (process id) and better terminal output (directory of process) * Update README on port helpers, to specify only one port returned * Add ignore of stderr when executing process commands - Make sure any potential errors don't leak to the user --- packages/react-dev-utils/README.md | 16 +++++ packages/react-dev-utils/getProcessForPort.js | 61 +++++++++++++++++++ packages/react-dev-utils/package.json | 1 + packages/react-scripts/scripts/start.js | 7 ++- 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 packages/react-dev-utils/getProcessForPort.js diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index 14c69493e5a..4d1e6ad3020 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -142,6 +142,22 @@ compiler.plugin('done', function(stats) { }); ``` +#### `getProcessForPort(port: number): string` + +Finds the currently running process on `port`. +Returns a string containing the name and directory, e.g., + +``` +create-react-app +in /Users/developer/create-react-app +``` + +```js +var getProcessForPort = require('react-dev-utils/getProcessForPort'); + +getProcessForPort(3000); +``` + #### `openBrowser(url: string): boolean` Attempts to open the browser with a given URL. diff --git a/packages/react-dev-utils/getProcessForPort.js b/packages/react-dev-utils/getProcessForPort.js new file mode 100644 index 00000000000..5540fbad47a --- /dev/null +++ b/packages/react-dev-utils/getProcessForPort.js @@ -0,0 +1,61 @@ +var chalk = require('chalk'); +var execSync = require('child_process').execSync; +var path = require('path'); + +var execOptions = { + encoding: 'utf8', + stdio: [ + 'pipe', // stdin (default) + 'pipe', // stdout (default) + 'ignore' //stderr + ] +}; + +function isProcessAReactApp(processCommand) { + return /^node .*react-scripts\/scripts\/start\.js\s?$/.test(processCommand); +} + +function getProcessIdOnPort(port) { + return execSync('lsof -i:' + port + ' -P -t -sTCP:LISTEN', execOptions).trim(); +} + +function getPackageNameInDirectory(directory) { + var packagePath = path.join(directory.trim(), 'package.json'); + + try { + return require(packagePath).name; + } catch(e) { + return null; + } + +} + +function getProcessCommand(processId, processDirectory) { + var command = execSync('ps -o command -p ' + processId + ' | sed -n 2p', execOptions); + + if (isProcessAReactApp(command)) { + const packageName = getPackageNameInDirectory(processDirectory); + return (packageName) ? packageName + '\n' : command; + } else { + return command; + } + +} + +function getDirectoryOfProcessById(processId) { + return execSync('lsof -p '+ processId + ' | grep cwd | awk \'{print $9}\'', execOptions).trim(); +} + +function getProcessForPort(port) { + try { + var processId = getProcessIdOnPort(port); + var directory = getDirectoryOfProcessById(processId); + var command = getProcessCommand(processId, directory); + return chalk.cyan(command) + chalk.blue(' in ') + chalk.cyan(directory); + } catch(e) { + return null; + } +} + +module.exports = getProcessForPort; + diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 1fe5c556c79..c9f2fac2dcf 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -14,6 +14,7 @@ "clearConsole.js", "checkRequiredFiles.js", "formatWebpackMessages.js", + "getProcessForPort.js", "InterpolateHtmlPlugin.js", "openChrome.applescript", "openBrowser.js", diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 5e996c71ddc..e0a41a9fb81 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -26,6 +26,7 @@ var detect = require('detect-port'); var clearConsole = require('react-dev-utils/clearConsole'); var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); +var getProcessForPort = require('react-dev-utils/getProcessForPort'); var openBrowser = require('react-dev-utils/openBrowser'); var prompt = require('react-dev-utils/prompt'); var pathExists = require('path-exists'); @@ -273,9 +274,11 @@ detect(DEFAULT_PORT).then(port => { } clearConsole(); + var existingProcess = getProcessForPort(DEFAULT_PORT); var question = - chalk.yellow('Something is already running on port ' + DEFAULT_PORT + '.') + - '\n\nWould you like to run the app on another port instead?'; + 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) {