Skip to content
Open
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
3 changes: 2 additions & 1 deletion packages/cli-server-api/src/statusPageMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import http from 'http';
import {normalizeProjectRoot} from '@react-native-community/cli-tools';

/**
* Status page so that anyone who needs to can verify that the packager is
Expand All @@ -16,7 +17,7 @@ export default function statusPageMiddleware(
) {
res.setHeader(
'X-React-Native-Project-Root',
new URL(`file:///${process.cwd()}`).pathname.slice(1),
normalizeProjectRoot(process.cwd()),
);
res.end('packager-status:running');
}
69 changes: 69 additions & 0 deletions packages/cli-tools/src/__tests__/findDevServerPort.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import findDevServerPort from '../findDevServerPort';
import isPackagerRunning from '../isPackagerRunning';
import handlePortUnavailable from '../handlePortUnavailable';

jest.mock('../isPackagerRunning');
jest.mock('../handlePortUnavailable');
jest.mock('../port', () => ({
logAlreadyRunningBundler: jest.fn(),
askForPortChange: jest.fn(),
logChangePortInstructions: jest.fn(),
}));

const isPackagerRunningMock = isPackagerRunning as jest.MockedFunction<
typeof isPackagerRunning
>;
const handlePortUnavailableMock = handlePortUnavailable as jest.MockedFunction<
typeof handlePortUnavailable
>;

beforeEach(() => {
jest.clearAllMocks();
handlePortUnavailableMock.mockResolvedValue({port: 8082, packager: true});
});

test('reuses the running dev server when the project root has a space', async () => {
const root = '/Users/me/my app';

isPackagerRunningMock.mockResolvedValue({
status: 'running',
root: '/Users/me/my%20app',
});

await expect(findDevServerPort(8081, root)).resolves.toEqual({
port: 8081,
startPackager: false,
});
expect(handlePortUnavailableMock).not.toHaveBeenCalled();
});

test('reuses the running dev server for a plain ASCII project root', async () => {
const root = '/Users/me/project';

isPackagerRunningMock.mockResolvedValue({
status: 'running',
root: '/Users/me/project',
});

await expect(findDevServerPort(8081, root)).resolves.toEqual({
port: 8081,
startPackager: false,
});
expect(handlePortUnavailableMock).not.toHaveBeenCalled();
});

test('asks for another port when a different project owns the port', async () => {
isPackagerRunningMock.mockResolvedValue({
status: 'running',
root: '/Users/me/other',
});

await expect(findDevServerPort(8081, '/Users/me/project')).resolves.toEqual({
port: 8082,
startPackager: true,
});
expect(handlePortUnavailableMock).toHaveBeenCalledWith(
8081,
'/Users/me/project',
);
});
18 changes: 18 additions & 0 deletions packages/cli-tools/src/__tests__/normalizeProjectRoot.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import normalizeProjectRoot from '../normalizeProjectRoot';

test('leaves a plain posix path untouched', () => {
expect(normalizeProjectRoot('/Users/me/project')).toBe('/Users/me/project');
});

test('percent-encodes characters that are not valid in a header', () => {
expect(normalizeProjectRoot('/Users/me/my app')).toBe('/Users/me/my%20app');
expect(normalizeProjectRoot('/привіт/path')).toBe(
'/%D0%BF%D1%80%D0%B8%D0%B2%D1%96%D1%82/path',
);
});

test('converts a windows path to forward slashes', () => {
expect(normalizeProjectRoot('C:\\Users\\me\\project')).toBe(
'C:/Users/me/project',
);
});
3 changes: 2 additions & 1 deletion packages/cli-tools/src/findDevServerPort.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import handlePortUnavailable from './handlePortUnavailable';
import isPackagerRunning from './isPackagerRunning';
import normalizeProjectRoot from './normalizeProjectRoot';
import {logAlreadyRunningBundler} from './port';

const findDevServerPort = async (
Expand All @@ -18,7 +19,7 @@ const findDevServerPort = async (
typeof packagerStatus === 'object' &&
packagerStatus.status === 'running'
) {
if (packagerStatus.root === root) {
if (packagerStatus.root === normalizeProjectRoot(root)) {
startPackager = false;
logAlreadyRunningBundler(port);
} else {
Expand Down
6 changes: 2 additions & 4 deletions packages/cli-tools/src/getNextPort.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import isPackagerRunning from './isPackagerRunning';
import normalizeProjectRoot from './normalizeProjectRoot';

type Result = {
start: boolean;
Expand All @@ -19,10 +20,7 @@ const getNextPort = async (port: number, root: string): Promise<Result> => {

const isRunning = typeof result === 'object' && result.status === 'running';

if (
isRunning &&
result.root === new URL(`file:///${root}`).pathname.slice(1)
) {
if (isRunning && result.root === normalizeProjectRoot(root)) {
// Found running bundler for this project, so we do not need to start packager!
start = false;
} else if (isRunning || result === 'unrecognized') {
Expand Down
1 change: 1 addition & 0 deletions packages/cli-tools/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './prompt';
export * as link from './doclink';
export {default as startServerInNewWindow} from './startServerInNewWindow';
export {default as findDevServerPort} from './findDevServerPort';
export {default as normalizeProjectRoot} from './normalizeProjectRoot';
export * from './port';
export {default as cacheManager} from './cacheManager';
export {default as runSudo} from './runSudo';
Expand Down
13 changes: 13 additions & 0 deletions packages/cli-tools/src/normalizeProjectRoot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Normalizes a project root to the form used by the dev server's
* `X-React-Native-Project-Root` header.
*
* A header value has to be a valid ISO-8859-1 string, so the dev server
* percent-encodes the path before sending it. Anything comparing a local
* project root against that header has to apply the same transformation,
* otherwise roots containing characters that get encoded (a space, an accent,
* a non-latin script) never match.
*/
export default function normalizeProjectRoot(root: string): string {
return new URL(`file:///${root}`).pathname.slice(1);
}
Loading