Skip to content

Commit 33ba6fe

Browse files
committed
Move /status into community-cli-plugin
1 parent f325652 commit 33ba6fe

8 files changed

Lines changed: 138 additions & 6 deletions

File tree

‎packages/community-cli-plugin/src/dev-server/__tests__/createDevServerMiddleware-test.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ beforeEach(async () => {
3131
.use(
3232
createDevServerMiddleware({
3333
serverBaseUrl: new URL(baseUrl),
34+
projectRoot: '/project',
3435
watchFolders: ['/project'],
3536
}),
3637
)
@@ -46,8 +47,15 @@ afterEach(() => {
4647
});
4748

4849
describe('createDevServerMiddleware', () => {
50+
test('serves /status', async () => {
51+
const resp = await fetch(baseUrl + '/status');
52+
53+
expect(resp.status).toBe(200);
54+
expect(await resp.text()).toBe('packager-status:running');
55+
});
56+
4957
test('refuses a cross-origin request before any route', async () => {
50-
const resp = await fetch(baseUrl + '/open-url', {
58+
const resp = await fetch(baseUrl + '/status', {
5159
headers: {Origin: 'https://example.com'},
5260
});
5361

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import isDevServerRunning from '../isDevServerRunning';
12+
import statusMiddleware from '../statusMiddleware';
13+
import connect from 'connect';
14+
import http from 'node:http';
15+
16+
jest.useRealTimers();
17+
18+
test('matches a running dev server for a project root that needs URL encoding', async () => {
19+
const projectRoot = '/root with spaces/ü';
20+
const app = connect().use('/status', statusMiddleware({projectRoot}));
21+
const server = http.createServer(app);
22+
await new Promise(resolve => server.listen(0, 'localhost', resolve));
23+
try {
24+
const address = server.address();
25+
if (address == null || typeof address === 'string') {
26+
throw new Error('Expected the server to listen on a TCP port');
27+
}
28+
const devServerUrl = `http://localhost:${address.port}`;
29+
30+
expect(await isDevServerRunning(devServerUrl, projectRoot)).toBe(
31+
'matched_server_running',
32+
);
33+
expect(await isDevServerRunning(devServerUrl, '/other/root')).toBe(
34+
'port_taken',
35+
);
36+
} finally {
37+
server.closeAllConnections();
38+
await new Promise(resolve => server.close(() => resolve()));
39+
}
40+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import statusMiddleware from '../statusMiddleware';
12+
import connect from 'connect';
13+
import http from 'node:http';
14+
15+
jest.useRealTimers();
16+
17+
async function fetchStatus(projectRoot: string): Promise<Response> {
18+
const app = connect().use('/status', statusMiddleware({projectRoot}));
19+
const server = http.createServer(app);
20+
await new Promise(resolve => server.listen(0, 'localhost', resolve));
21+
try {
22+
const address = server.address();
23+
if (address == null || typeof address === 'string') {
24+
throw new Error('Expected the server to listen on a TCP port');
25+
}
26+
return await fetch(`http://localhost:${address.port}/status`);
27+
} finally {
28+
server.closeAllConnections();
29+
await new Promise(resolve => server.close(() => resolve()));
30+
}
31+
}
32+
33+
describe('/status', () => {
34+
// React Native apps and `isDevServerRunning` depend on this exact response.
35+
test('reports that the packager is running for the project root', async () => {
36+
const resp = await fetchStatus('/mocked/path');
37+
38+
expect(resp.status).toBe(200);
39+
expect(await resp.text()).toBe('packager-status:running');
40+
expect(resp.headers.get('X-React-Native-Project-Root')).toBe(
41+
'/mocked/path',
42+
);
43+
expect(resp.headers.get('Cache-Control')).toBe('no-store');
44+
});
45+
});

‎packages/community-cli-plugin/src/dev-server/createDevServerMiddleware.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ import type {Server} from 'connect';
1313
import openStackFrameMiddleware from './openStackFrameMiddleware';
1414
import openURLMiddleware from './openURLMiddleware';
1515
import securityHeadersMiddleware from './securityHeadersMiddleware';
16+
import statusMiddleware from './statusMiddleware';
1617
import compression from 'compression';
1718
import connect from 'connect';
1819
import nocache from 'nocache';
1920

2021
type Options = Readonly<{
2122
serverBaseUrl: URL,
23+
projectRoot: string,
2224
watchFolders: ReadonlyArray<string>,
2325
}>;
2426

@@ -28,12 +30,14 @@ type Options = Readonly<{
2830
*/
2931
export default function createDevServerMiddleware({
3032
serverBaseUrl,
33+
projectRoot,
3134
watchFolders,
3235
}: Options): Server {
3336
return connect()
3437
.use(securityHeadersMiddleware({serverBaseUrl}))
3538
.use(compression())
3639
.use(nocache())
3740
.use('/open-stack-frame', openStackFrameMiddleware({watchFolders}))
38-
.use('/open-url', openURLMiddleware());
41+
.use('/open-url', openURLMiddleware())
42+
.use('/status', statusMiddleware({projectRoot}));
3943
}

‎packages/community-cli-plugin/src/dev-server/isDevServerRunning.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* @format
99
*/
1010

11+
import {formatProjectRootHeader} from './statusMiddleware';
1112
import net from 'node:net';
1213

1314
/**
@@ -33,12 +34,12 @@ export default async function isDevServerRunning(
3334
return 'not_running';
3435
}
3536

36-
// FIXME: Depends on @react-native-community/cli-server-api
3737
const statusResponse = await fetch(`${devServerUrl}/status`);
3838
const body = await statusResponse.text();
3939

4040
return body === 'packager-status:running' &&
41-
statusResponse.headers.get('X-React-Native-Project-Root') === projectRoot
41+
statusResponse.headers.get('X-React-Native-Project-Root') ===
42+
formatProjectRootHeader(projectRoot)
4243
? 'matched_server_running'
4344
: 'port_taken';
4445
} catch (e) {

‎packages/community-cli-plugin/src/dev-server/loadCommunityMiddleware.js‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ const unusedMiddlewareStub: Server = {};
4141
// (@react-native-community/cli-server-api) and should be migrated into core.
4242
// e.g. used by packages/react-native/Libraries/Core/Devtools/:
4343
// - /symbolicate
44-
// e.g. used by ./isDevServerRunning.js:
45-
// - /status
4644
const communityMiddlewareFallback: DevServerMiddlewareFactory = () => ({
4745
middleware: unusedMiddlewareStub,
4846
websocketEndpoints: {},

‎packages/community-cli-plugin/src/dev-server/runDevServer.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ async function runDevServer(
125125
});
126126
const pluginMiddleware = createDevServerMiddleware({
127127
serverBaseUrl: new URL(devServerUrl),
128+
projectRoot,
128129
watchFolders: [unstable_serverRoot ?? projectRoot, ...watchFolders],
129130
});
130131

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import type {NextHandleFunction} from 'connect';
12+
13+
type Options = Readonly<{
14+
projectRoot: string,
15+
}>;
16+
17+
/**
18+
* Report that this dev server is running for `projectRoot`, so that the app
19+
* and `isDevServerRunning` can tell it from another process on the port.
20+
*/
21+
export default function statusMiddleware({
22+
projectRoot,
23+
}: Options): NextHandleFunction {
24+
const projectRootHeader = formatProjectRootHeader(projectRoot);
25+
26+
return (req, res) => {
27+
res.setHeader('X-React-Native-Project-Root', projectRootHeader);
28+
res.setHeader('Cache-Control', 'no-store');
29+
res.end('packager-status:running');
30+
};
31+
}
32+
33+
export function formatProjectRootHeader(projectRoot: string): string {
34+
return new URL(`file:///${projectRoot}`).pathname.slice(1);
35+
}

0 commit comments

Comments
 (0)