From f13c65fcbeda3e6911078451188874105f17fe5f Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 29 Oct 2026 19:42:00 +0000 Subject: [PATCH] Move /status into community-cli-plugin --- .../createDevServerMiddleware-test.js | 10 ++++- .../__tests__/statusMiddleware-test.js | 45 +++++++++++++++++++ .../dev-server/createDevServerMiddleware.js | 6 ++- .../src/dev-server/isDevServerRunning.js | 1 - .../src/dev-server/loadCommunityMiddleware.js | 2 - .../src/dev-server/runDevServer.js | 1 + .../src/dev-server/statusMiddleware.js | 31 +++++++++++++ 7 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 packages/community-cli-plugin/src/dev-server/__tests__/statusMiddleware-test.js create mode 100644 packages/community-cli-plugin/src/dev-server/statusMiddleware.js diff --git a/packages/community-cli-plugin/src/dev-server/__tests__/createDevServerMiddleware-test.js b/packages/community-cli-plugin/src/dev-server/__tests__/createDevServerMiddleware-test.js index a439be07e60b..3d10e350356a 100644 --- a/packages/community-cli-plugin/src/dev-server/__tests__/createDevServerMiddleware-test.js +++ b/packages/community-cli-plugin/src/dev-server/__tests__/createDevServerMiddleware-test.js @@ -31,6 +31,7 @@ beforeEach(async () => { .use( createDevServerMiddleware({ serverBaseUrl: new URL(baseUrl), + projectRoot: '/project', watchFolders: ['/project'], }), ) @@ -46,8 +47,15 @@ afterEach(() => { }); describe('createDevServerMiddleware', () => { + test('serves /status', async () => { + const resp = await fetch(baseUrl + '/status'); + + expect(resp.status).toBe(200); + expect(await resp.text()).toBe('packager-status:running'); + }); + test('refuses a cross-origin request before any route', async () => { - const resp = await fetch(baseUrl + '/open-url', { + const resp = await fetch(baseUrl + '/status', { headers: {Origin: 'https://example.com'}, }); diff --git a/packages/community-cli-plugin/src/dev-server/__tests__/statusMiddleware-test.js b/packages/community-cli-plugin/src/dev-server/__tests__/statusMiddleware-test.js new file mode 100644 index 000000000000..54843dba7eb0 --- /dev/null +++ b/packages/community-cli-plugin/src/dev-server/__tests__/statusMiddleware-test.js @@ -0,0 +1,45 @@ +/** + * 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 + */ + +import statusMiddleware from '../statusMiddleware'; +import connect from 'connect'; +import http from 'node:http'; + +jest.useRealTimers(); + +async function fetchStatus(projectRoot: string): Promise { + const app = connect().use('/status', statusMiddleware({projectRoot})); + const server = http.createServer(app); + await new Promise(resolve => server.listen(0, 'localhost', resolve)); + try { + const address = server.address(); + if (address == null || typeof address === 'string') { + throw new Error('Expected the server to listen on a TCP port'); + } + return await fetch(`http://localhost:${address.port}/status`); + } finally { + server.closeAllConnections(); + await new Promise(resolve => server.close(() => resolve())); + } +} + +describe('/status', () => { + // React Native apps and `isDevServerRunning` depend on this exact response. + test('reports that the packager is running for the project root', async () => { + const resp = await fetchStatus('/mocked/path'); + + expect(resp.status).toBe(200); + expect(await resp.text()).toBe('packager-status:running'); + expect(resp.headers.get('X-React-Native-Project-Root')).toBe( + '/mocked/path', + ); + expect(resp.headers.get('Cache-Control')).toBe('no-store'); + }); +}); diff --git a/packages/community-cli-plugin/src/dev-server/createDevServerMiddleware.js b/packages/community-cli-plugin/src/dev-server/createDevServerMiddleware.js index 15b801bc16c7..672eb4b2d820 100644 --- a/packages/community-cli-plugin/src/dev-server/createDevServerMiddleware.js +++ b/packages/community-cli-plugin/src/dev-server/createDevServerMiddleware.js @@ -13,12 +13,14 @@ import type {Server} from 'connect'; import openStackFrameMiddleware from './openStackFrameMiddleware'; import openURLMiddleware from './openURLMiddleware'; import securityHeadersMiddleware from './securityHeadersMiddleware'; +import statusMiddleware from './statusMiddleware'; import compression from 'compression'; import connect from 'connect'; import nocache from 'nocache'; type Options = Readonly<{ serverBaseUrl: URL, + projectRoot: string, watchFolders: ReadonlyArray, }>; @@ -28,6 +30,7 @@ type Options = Readonly<{ */ export default function createDevServerMiddleware({ serverBaseUrl, + projectRoot, watchFolders, }: Options): Server { return connect() @@ -35,5 +38,6 @@ export default function createDevServerMiddleware({ .use(compression()) .use(nocache()) .use('/open-stack-frame', openStackFrameMiddleware({watchFolders})) - .use('/open-url', openURLMiddleware()); + .use('/open-url', openURLMiddleware()) + .use('/status', statusMiddleware({projectRoot})); } diff --git a/packages/community-cli-plugin/src/dev-server/isDevServerRunning.js b/packages/community-cli-plugin/src/dev-server/isDevServerRunning.js index 5cf83ebda690..c580d9c9017e 100644 --- a/packages/community-cli-plugin/src/dev-server/isDevServerRunning.js +++ b/packages/community-cli-plugin/src/dev-server/isDevServerRunning.js @@ -33,7 +33,6 @@ export default async function isDevServerRunning( return 'not_running'; } - // FIXME: Depends on @react-native-community/cli-server-api const statusResponse = await fetch(`${devServerUrl}/status`); const body = await statusResponse.text(); diff --git a/packages/community-cli-plugin/src/dev-server/loadCommunityMiddleware.js b/packages/community-cli-plugin/src/dev-server/loadCommunityMiddleware.js index 901da5955c96..8e39424aeb58 100644 --- a/packages/community-cli-plugin/src/dev-server/loadCommunityMiddleware.js +++ b/packages/community-cli-plugin/src/dev-server/loadCommunityMiddleware.js @@ -41,8 +41,6 @@ const unusedMiddlewareStub: Server = {}; // (@react-native-community/cli-server-api) and should be migrated into core. // e.g. used by packages/react-native/Libraries/Core/Devtools/: // - /symbolicate -// e.g. used by ./isDevServerRunning.js: -// - /status const communityMiddlewareFallback: DevServerMiddlewareFactory = () => ({ middleware: unusedMiddlewareStub, websocketEndpoints: {}, diff --git a/packages/community-cli-plugin/src/dev-server/runDevServer.js b/packages/community-cli-plugin/src/dev-server/runDevServer.js index 1b0489481c0d..b19e6079eb8e 100644 --- a/packages/community-cli-plugin/src/dev-server/runDevServer.js +++ b/packages/community-cli-plugin/src/dev-server/runDevServer.js @@ -125,6 +125,7 @@ async function runDevServer( }); const pluginMiddleware = createDevServerMiddleware({ serverBaseUrl: new URL(devServerUrl), + projectRoot, watchFolders: [unstable_serverRoot ?? projectRoot, ...watchFolders], }); diff --git a/packages/community-cli-plugin/src/dev-server/statusMiddleware.js b/packages/community-cli-plugin/src/dev-server/statusMiddleware.js new file mode 100644 index 000000000000..52fcb963dc55 --- /dev/null +++ b/packages/community-cli-plugin/src/dev-server/statusMiddleware.js @@ -0,0 +1,31 @@ +/** + * 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 + */ + +import type {NextHandleFunction} from 'connect'; + +type Options = Readonly<{ + projectRoot: string, +}>; + +/** + * Report that this dev server is running for `projectRoot`, so that the app + * and `isDevServerRunning` can tell it from another process on the port. + */ +export default function statusMiddleware({ + projectRoot, +}: Options): NextHandleFunction { + const projectRootHeader = new URL(`file:///${projectRoot}`).pathname.slice(1); + + return (req, res) => { + res.setHeader('X-React-Native-Project-Root', projectRootHeader); + res.setHeader('Cache-Control', 'no-store'); + res.end('packager-status:running'); + }; +}