mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Simplify occupied port handling in start command (#39078)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/39078 Simplifies and hardens behaviour for detecting other processes / dev server instances when running `react-native start`. - New flow: - Exits with error message if port is taken by another process (*no longer suggests next port*). - Exits with info message if port is taken by another instance of this dev server (**unchanged**). - Continues if result unknown. - *(No longer logs dedicated message for another RN server running in a different project root.)* - This now checks if the TCP port is in use before attempting an HTTP fetch. Previous behaviour: [`handlePortUnavailable`](https://github.com/react-native-community/cli/blob/734222118707fff41c71463528e4e0c227b31cc6/packages/cli-tools/src/handlePortUnavailable.ts#L8). This decouples us from some lower-level `react-native-community/cli-tools` utils, which remain reused by the `android` and `ios` commands. Changelog: [Internal] Reviewed By: motiz88 Differential Revision: D48433285 fbshipit-source-id: 7056001d5fe2f90faf52143f2777c9e2bdf0646e
This commit is contained in:
committed by
Facebook GitHub Bot
parent
5276599c45
commit
13eda31d43
+4
-39
@@ -18,50 +18,15 @@ declare module '@react-native-community/cli-tools' {
|
||||
constructor(msg: string, originalError?: Error | mixed | string): this;
|
||||
}
|
||||
|
||||
declare export function getPidFromPort(port: number): number | null;
|
||||
|
||||
declare export function handlePortUnavailable(
|
||||
initialPort: number,
|
||||
projectRoot: string,
|
||||
initialPackager?: boolean,
|
||||
): Promise<{
|
||||
port: number,
|
||||
packager: boolean,
|
||||
}>;
|
||||
|
||||
declare export function hookStdout(callback: Function): () => void;
|
||||
|
||||
declare export function isPackagerRunning(
|
||||
packagerPort: string | number | void,
|
||||
): Promise<
|
||||
| {
|
||||
status: 'running',
|
||||
root: string,
|
||||
}
|
||||
| 'not_running'
|
||||
| 'unrecognized',
|
||||
>;
|
||||
|
||||
declare export const logger: $ReadOnly<{
|
||||
success: (...message: Array<string>) => void,
|
||||
debug: (...message: Array<string>) => void,
|
||||
error: (...message: Array<string>) => void,
|
||||
log: (...message: Array<string>) => void,
|
||||
info: (...message: Array<string>) => void,
|
||||
warn: (...message: Array<string>) => void,
|
||||
error: (...message: Array<string>) => void,
|
||||
debug: (...message: Array<string>) => void,
|
||||
log: (...message: Array<string>) => void,
|
||||
setVerbose: (level: boolean) => void,
|
||||
isVerbose: () => boolean,
|
||||
disable: () => void,
|
||||
enable: () => void,
|
||||
...
|
||||
}>;
|
||||
|
||||
declare export function logAlreadyRunningBundler(port: number): void;
|
||||
|
||||
declare export function resolveNodeModuleDir(
|
||||
root: string,
|
||||
packageName: string,
|
||||
): string;
|
||||
|
||||
declare export const version: $ReadOnly<{
|
||||
logIfUpdateAvailable: (projectRoot: string) => Promise<void>,
|
||||
}>;
|
||||
|
||||
@@ -23,14 +23,9 @@ import {
|
||||
createDevServerMiddleware,
|
||||
indexPageMiddleware,
|
||||
} from '@react-native-community/cli-server-api';
|
||||
import {
|
||||
isPackagerRunning,
|
||||
logger,
|
||||
version,
|
||||
logAlreadyRunningBundler,
|
||||
handlePortUnavailable,
|
||||
} from '@react-native-community/cli-tools';
|
||||
import {logger, version} from '@react-native-community/cli-tools';
|
||||
|
||||
import isDevServerRunning from '../../utils/isDevServerRunning';
|
||||
import loadMetroConfig from '../../utils/loadMetroConfig';
|
||||
import attachKeyHandlers from './attachKeyHandlers';
|
||||
|
||||
@@ -58,41 +53,38 @@ async function runServer(
|
||||
ctx: Config,
|
||||
args: StartCommandArgs,
|
||||
) {
|
||||
let port = args.port ?? 8081;
|
||||
let packager = true;
|
||||
const packagerStatus = await isPackagerRunning(port);
|
||||
|
||||
if (
|
||||
typeof packagerStatus === 'object' &&
|
||||
packagerStatus.status === 'running'
|
||||
) {
|
||||
if (packagerStatus.root === ctx.root) {
|
||||
packager = false;
|
||||
logAlreadyRunningBundler(port);
|
||||
} else {
|
||||
const result = await handlePortUnavailable(port, ctx.root, packager);
|
||||
[port, packager] = [result.port, result.packager];
|
||||
}
|
||||
} else if (packagerStatus === 'unrecognized') {
|
||||
const result = await handlePortUnavailable(port, ctx.root, packager);
|
||||
[port, packager] = [result.port, result.packager];
|
||||
}
|
||||
|
||||
if (packager === false) {
|
||||
process.exit();
|
||||
}
|
||||
|
||||
logger.info(`Starting dev server on port ${chalk.bold(String(port))}`);
|
||||
|
||||
const metroConfig = await loadMetroConfig(ctx, {
|
||||
config: args.config,
|
||||
maxWorkers: args.maxWorkers,
|
||||
port: port,
|
||||
port: args.port ?? 8081,
|
||||
resetCache: args.resetCache,
|
||||
watchFolders: args.watchFolders,
|
||||
projectRoot: args.projectRoot,
|
||||
sourceExts: args.sourceExts,
|
||||
});
|
||||
const host = args.host?.length ? args.host : 'localhost';
|
||||
const {
|
||||
projectRoot,
|
||||
server: {port},
|
||||
watchFolders,
|
||||
} = metroConfig;
|
||||
|
||||
const serverStatus = await isDevServerRunning(host, port, projectRoot);
|
||||
|
||||
if (serverStatus === 'matched_server_running') {
|
||||
logger.info(
|
||||
`A dev server is already running for this project on port ${port}. Exiting.`,
|
||||
);
|
||||
return;
|
||||
} else if (serverStatus === 'port_taken') {
|
||||
logger.error(
|
||||
`Another process is running on port ${port}. Please terminate this ` +
|
||||
'process and try again, or use another port with "--port".',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Starting dev server on port ${chalk.bold(String(port))}`);
|
||||
|
||||
if (args.assetPlugins) {
|
||||
// $FlowIgnore[cannot-write] Assigning to readonly property
|
||||
@@ -107,14 +99,14 @@ async function runServer(
|
||||
messageSocketEndpoint,
|
||||
eventsSocketEndpoint,
|
||||
} = createDevServerMiddleware({
|
||||
host: args.host,
|
||||
port: metroConfig.server.port,
|
||||
watchFolders: metroConfig.watchFolders,
|
||||
host,
|
||||
port,
|
||||
watchFolders,
|
||||
});
|
||||
const {middleware, websocketEndpoints} = createDevMiddleware({
|
||||
host: args.host?.length ? args.host : 'localhost',
|
||||
port: metroConfig.server.port,
|
||||
projectRoot: metroConfig.projectRoot,
|
||||
host,
|
||||
port,
|
||||
projectRoot,
|
||||
logger,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import net from 'net';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
/**
|
||||
* Determine whether we can run the dev server.
|
||||
*
|
||||
* Return values:
|
||||
* - `not_running`: The port is unoccupied.
|
||||
* - `matched_server_running`: The port is occupied by another instance of this
|
||||
* dev server (matching the passed `projectRoot`).
|
||||
* - `port_taken`: The port is occupied by another process.
|
||||
* - `unknown`: An error was encountered; attempt server creation anyway.
|
||||
*/
|
||||
export default async function isDevServerRunning(
|
||||
host: string,
|
||||
port: number,
|
||||
projectRoot: string,
|
||||
): Promise<
|
||||
'not_running' | 'matched_server_running' | 'port_taken' | 'unknown',
|
||||
> {
|
||||
try {
|
||||
if (!(await isPortOccupied(host, port))) {
|
||||
return 'not_running';
|
||||
}
|
||||
|
||||
const statusResponse = await fetch(`http://localhost:${port}/status`);
|
||||
const body = await statusResponse.text();
|
||||
|
||||
return body === 'packager-status:running' &&
|
||||
statusResponse.headers.get('X-React-Native-Project-Root') === projectRoot
|
||||
? 'matched_server_running'
|
||||
: 'port_taken';
|
||||
} catch (e) {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
async function isPortOccupied(host: string, port: number): Promise<boolean> {
|
||||
let result = false;
|
||||
const server = net.createServer();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.once('error', e => {
|
||||
server.close();
|
||||
if (e.code === 'EADDRINUSE') {
|
||||
result = true;
|
||||
} else {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
server.once('listening', () => {
|
||||
result = false;
|
||||
server.close();
|
||||
});
|
||||
server.once('close', () => {
|
||||
resolve(result);
|
||||
});
|
||||
server.listen({host, port});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user