Skip to content
Merged
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
6 changes: 3 additions & 3 deletions src/__tests__/cli-agent-cdp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ test('cdp wrapper streams through npm exec and returns downstream exit code', as
assert.equal(vi.mocked(runCmdStreaming).mock.calls[0]?.[2]?.allowFailure, true);
});

test('cdp injects remote Metro public url for target discovery', async () => {
test('cdp injects remote React Native dev-server public url for target discovery', async () => {
const args = buildAgentCdpPassthroughArgs(['target', 'list'], {
flags: {
leaseBackend: 'android-instance',
Expand Down Expand Up @@ -133,7 +133,7 @@ test('cdp preserves explicit target url for remote sessions', () => {
]);
});

test('cdp rejects remote bridge target discovery without Metro public url', () => {
test('cdp rejects remote bridge target discovery without React Native dev-server public url', () => {
assert.throws(
() =>
buildAgentCdpPassthroughArgs(['target', 'list'], {
Expand All @@ -147,7 +147,7 @@ test('cdp rejects remote bridge target discovery without Metro public url', () =
'https://bridge.example.test/api/metro/runtimes/runtime-1/index.bundle?platform=android&dev=true',
},
}),
/cdp remote bridge target discovery requires a Metro public base URL/,
/cdp remote bridge target discovery requires a React Native dev-server public base URL/,
);
});

Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/client-metro-startup-cleanup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ test('prepareMetroRuntime stops a spawned Metro process when startup readiness t

const expectedFailure = assert.rejects(
preparePromise,
/Metro did not become ready at http:\/\/127\.0\.0\.1:8081\/status within 30000ms/,
/React Native dev server did not become ready at http:\/\/127\.0\.0\.1:8081\/status within 30000ms/,
);
await vi.advanceTimersByTimeAsync(30_000);
await expectedFailure;
Expand Down
70 changes: 70 additions & 0 deletions src/__tests__/client-metro.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,72 @@ test('prepareMetroRuntime starts Metro, bridges through proxy, and writes runtim
}
});

for (const { configFileName, commandName } of [
{ configFileName: 'rspack.config.ts', commandName: 'rspack-start' },
{ configFileName: 'webpack.config.js', commandName: 'webpack-start' },
]) {
test(`prepareMetroRuntime starts Re.Pack with ${commandName} for ${configFileName}`, async () => {
const tempRoot = path.join(os.tmpdir(), `agent-device-repack-${randomUUID()}`);
const projectRoot = path.join(tempRoot, 'project');
const binDir = path.join(tempRoot, 'bin');
const argsFile = path.join(tempRoot, 'npx-args.json');
const metroPort = await findFreePort();

mkdirSync(projectRoot, { recursive: true });
mkdirSync(binDir, { recursive: true });
writeFileSync(
path.join(projectRoot, 'package.json'),
JSON.stringify({
name: 'repack-runtime-test',
private: true,
dependencies: {
'react-native': '0.0.0-test',
},
devDependencies: {
'@callstack/repack': '5.2.5',
},
}),
);
writeFileSync(path.join(projectRoot, configFileName), 'module.exports = {};\n');
writeFakeNpx(binDir);

let pid = 0;
try {
const result = await prepareMetroRuntime({
projectRoot,
publicBaseUrl: `http://127.0.0.1:${metroPort}`,
metroPort,
reuseExisting: false,
installDependenciesIfNeeded: false,
env: {
...process.env,
AGENT_DEVICE_TEST_NPX_ARGS_FILE: argsFile,
PATH: `${binDir}:${process.env.PATH || ''}`,
},
});

pid = result.pid;
assert.equal(result.kind, 'repack');
assert.equal(result.started, true);
assert.deepEqual(JSON.parse(readFileSync(argsFile, 'utf8')), [
'react-native',
commandName,
'--host',
'0.0.0.0',
'--port',
String(metroPort),
]);
assert.equal(
result.iosRuntime.bundleUrl,
`http://127.0.0.1:${metroPort}/index.bundle?platform=ios&dev=true&minify=false`,
);
} finally {
await stopProcess(pid);
rmSync(tempRoot, { recursive: true, force: true });
}
});
}

test('prepareMetroRuntime rejects incomplete proxy configuration', async () => {
await assert.rejects(
() =>
Expand Down Expand Up @@ -304,8 +370,12 @@ function writeFakeNpx(binDir: string): void {
writeFileSync(
filePath,
`#!/usr/bin/env node
const fs = require("node:fs")
const http = require("node:http")
const args = process.argv.slice(2)
if (process.env.AGENT_DEVICE_TEST_NPX_ARGS_FILE) {
fs.writeFileSync(process.env.AGENT_DEVICE_TEST_NPX_ARGS_FILE, JSON.stringify(args))
}
const portIndex = args.indexOf("--port")
const hostIndex = args.indexOf("--host")
const port = portIndex === -1 ? 8081 : Number(args[portIndex + 1] || "8081")
Expand Down
2 changes: 2 additions & 0 deletions src/__tests__/remote-config-public.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ test('public remote-config helpers resolve file paths and merged profiles', () =
JSON.stringify({
platform: 'ios',
metroProjectRoot: '../project',
metroKind: 'repack',
metroPublicBaseUrl: 'https://public.example.test',
}),
'utf8',
Expand All @@ -35,6 +36,7 @@ test('public remote-config helpers resolve file paths and merged profiles', () =
profile: {
platform: 'ios',
metroProjectRoot: projectRoot,
metroKind: 'repack',
metroPublicBaseUrl: 'https://public.example.test',
metroPreparePort: 9090,
},
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/remote-connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test('deferred Metro config ignores perf-style kind values', () => {
json: true,
help: false,
version: false,
metroKind: 'expo',
metroKind: 'repack',
}),
true,
);
Expand Down Expand Up @@ -1119,7 +1119,7 @@ test('deferred materialization re-prepares runtime when explicit Metro overrides
session: 'adc-android',
platform: 'android',
metroProjectRoot: '/tmp/project-new',
metroKind: 'expo',
metroKind: 'repack',
metroPublicBaseUrl: 'https://sandbox.example.test',
metroProxyBaseUrl: 'https://proxy.example.test',
launchUrl: 'myapp://open',
Expand All @@ -1129,7 +1129,7 @@ test('deferred materialization re-prepares runtime when explicit Metro overrides
prepareRequest = options;
return {
projectRoot: '/tmp/project-new',
kind: 'expo',
kind: 'repack',
dependenciesInstalled: false,
packageManager: null,
started: false,
Expand All @@ -1151,7 +1151,7 @@ test('deferred materialization re-prepares runtime when explicit Metro overrides
});

assert.equal(prepareRequest?.projectRoot, '/tmp/project-new');
assert.equal(prepareRequest?.kind, 'expo');
assert.equal(prepareRequest?.kind, 'repack');
assert.equal(prepareRequest?.publicBaseUrl, 'https://sandbox.example.test');
assert.equal(prepareRequest?.proxyBaseUrl, 'https://proxy.example.test');
assert.equal(prepareRequest?.launchUrl, 'myapp://open');
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/agent-cdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ export function buildAgentCdpPassthroughArgs(
if (!cdpUrl) {
throw new AppError(
'INVALID_ARGS',
'cdp remote bridge target discovery requires a Metro public base URL.',
'cdp remote bridge target discovery requires a React Native dev-server public base URL.',
{
hint: 'Include metroPublicBaseUrl in the remote config so cdp can reach the local or tunneled Metro CDP endpoint without bridge proxy authentication.',
hint: 'Include metroPublicBaseUrl in the remote config so cdp can reach the local or tunneled Metro/Re.Pack CDP endpoint without bridge proxy authentication.',
},
);
}
Expand Down
10 changes: 2 additions & 8 deletions src/cli/commands/connection-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import type { LeaseBackend, SessionRuntimeHints } from '../../kernel/contracts.t
import type { CliFlags } from '../parser/cli-flags.ts';
import type { AgentDeviceClient, Lease } from '../../client/client.ts';
import type { CloudProviderSessionResult } from '../../cloud-artifacts.ts';
import type { MetroPrepareKind } from '../../metro/client-metro.ts';
import { INTERNAL_COMMANDS, PUBLIC_COMMANDS } from '../../command-catalog.ts';
import { readMetroPrepareKind } from '../../commands/metro/prepare-kind.ts';
import { connectionProviderRequiresRemoteDaemon } from '../connection/provider-policy.ts';
import { isCloudWebDriverProviderName } from '../../cloud-webdriver/providers.ts';

Expand Down Expand Up @@ -423,7 +423,7 @@ async function prepareConnectedMetro(
}
const prepared = await client.metro.prepare({
projectRoot: flags.metroProjectRoot,
kind: readDeferredMetroKind(flags.metroKind),
kind: readMetroPrepareKind(flags.metroKind),
publicBaseUrl: flags.metroPublicBaseUrl,
proxyBaseUrl: flags.metroProxyBaseUrl,
bearerToken: flags.metroBearerToken,
Expand Down Expand Up @@ -587,12 +587,6 @@ function isRuntimeCompatibleWithPlatform(
return runtime.platform === platform;
}

function readDeferredMetroKind(value: string | undefined): MetroPrepareKind | undefined {
if (value === undefined) return undefined;
if (value === 'auto' || value === 'react-native' || value === 'expo') return value;
throw new AppError('INVALID_ARGS', 'metro prepare --kind must be auto, react-native, or expo');
}

function isSameMetroCleanup(
left: RemoteConnectionState['metro'] | undefined,
right: RemoteConnectionState['metro'] | undefined,
Expand Down
4 changes: 2 additions & 2 deletions src/cli/parser/__tests__/args-parse-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ test('parseArgs accepts metro prepare arguments', () => {
'--port',
'9090',
'--kind',
'expo',
'repack',
'--runtime-file',
'./.agent-device/metro-runtime.json',
'--no-reuse-existing',
Expand All @@ -524,7 +524,7 @@ test('parseArgs accepts metro prepare arguments', () => {
assert.equal(parsed.flags.metroProxyBaseUrl, 'https://proxy.example.test');
assert.equal(parsed.flags.metroBearerToken, 'secret');
assert.equal(parsed.flags.metroPreparePort, 9090);
assert.equal(parsed.flags.kind, 'expo');
assert.equal(parsed.flags.kind, 'repack');
assert.equal(parsed.flags.metroRuntimeFile, './.agent-device/metro-runtime.json');
assert.equal(parsed.flags.metroNoReuseExisting, true);
assert.equal(parsed.flags.metroNoInstallDeps, true);
Expand Down
2 changes: 1 addition & 1 deletion src/cli/parser/__tests__/cli-help-command-usage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ test('command usage shows record touch-overlay opt-out flag', async () => {
test('command usage keeps detailed descriptions', async () => {
const help = await usageForCommand('metro');
if (help === null) throw new Error('Expected command help text');
assert.match(help, /Prepare a local Metro runtime or ask Metro to reload/);
assert.match(help, /Prepare a local React Native dev-server runtime/);
assert.match(help, /metro reload/);
assert.match(help, /--metro-host <host>/);
assert.match(help, /AGENT_DEVICE_METRO_BEARER_TOKEN/);
Expand Down
11 changes: 7 additions & 4 deletions src/cli/parser/__tests__/cli-help-topics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test('usage includes concise top-level commands', async () => {
assert.match(usageText, /prepare\s{2,}Pre-warm platform helpers/);
assert.match(
usageText,
/metro\s{2,}Prepare Metro reachability for React Native\/Expo apps or trigger app reloads/,
/metro\s{2,}Prepare Metro\/Re\.Pack reachability for React Native\/Expo apps or trigger app reloads/,
);
assert.match(usageText, /batch --steps <json> \| --steps-file <path>/);
assert.match(usageText, /network\s{2,}Inspect HTTP\(S\) traffic parsed from session app logs/);
Expand Down Expand Up @@ -224,7 +224,7 @@ test('usageForCommand resolves workflow help topic', async () => {
assert.match(help, /instead of exact numeric deltas/);
assert.match(help, /prefer isolated gesture pan, gesture pinch, or gesture rotate/);
assert.match(help, /longpress accepts coordinates, @refs, or selectors/);
assert.match(help, /use help react-native for Metro\/Fast Refresh/);
assert.match(help, /use help react-native for Metro\/Re\.Pack Fast Refresh/);
assert.match(help, /iOS Allow Paste prompt cannot be exercised under XCUITest/);
assert.match(help, /Empty replacement is not a supported clear-field command/);
assert.match(help, /do not plan fill <target> ""/);
Expand Down Expand Up @@ -518,7 +518,7 @@ test('usageForCommand resolves react-native help topic', async () => {
assert.match(help, /agent-device doctor --platform android --app com\.example\.app/);
assert.match(help, /agent-device doctor --platform ios/);
assert.match(help, /agent-device doctor --remote --remote-config \.\/remote\.json/);
assert.match(help, /same host context that owns Metro/);
assert.match(help, /same host context that owns the dev server/);
assert.match(help, /sandbox probe is not authoritative/);
assert.match(help, /adb reverse only affects Android device-to-host traffic/);
assert.match(help, /Multiple local worktrees can reuse one native iOS simulator build/);
Expand Down Expand Up @@ -564,7 +564,10 @@ test('usage renders concise commands inline with descriptions', async () => {
assert.match(help, /Commands:[\s\S]*\n boot\s{2,}Boot target device\/simulator/);
assert.match(help, /Commands:[\s\S]*\n shutdown\s{2,}Shutdown target simulator\/emulator/);
assert.match(help, / prepare\s{2,}Pre-warm platform helpers/);
assert.match(help, / metro\s{2,}Prepare Metro reachability for React Native\/Expo apps/);
assert.match(
help,
/ metro\s{2,}Prepare Metro\/Re\.Pack reachability for React Native\/Expo apps/,
);
assert.match(help, / perf\s{2,}Check runtime metrics, frames, memory, CPU profiles/);
assert.match(help, / cdp\s{2,}Inspect React Native CDP targets, JS heap growth/);
assert.match(help, / react-devtools\s{2,}Inspect React Native components, props, hooks/);
Expand Down
21 changes: 11 additions & 10 deletions src/cli/parser/cli-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ const FLAG_DEFINITIONS: readonly FlagDefinition[] = [
key: 'kind',
names: ['--kind'],
type: 'enum',
enumValues: ['auto', 'react-native', 'expo', ...PERF_KIND_VALUES],
enumValues: ['auto', 'react-native', 'expo', 'repack', ...PERF_KIND_VALUES],
usageLabel: '--kind <kind>',
usageDescription:
'Kind selector for commands that support it, such as metro prepare or perf artifact collectors',
Expand All @@ -553,23 +553,23 @@ const FLAG_DEFINITIONS: readonly FlagDefinition[] = [
key: 'metroKind',
names: ['--metro-kind'],
type: 'enum',
enumValues: ['auto', 'react-native', 'expo'],
usageLabel: '--metro-kind auto|react-native|expo',
usageDescription: 'metro prepare: detect or force the Metro launcher kind',
enumValues: ['auto', 'react-native', 'expo', 'repack'],
usageLabel: '--metro-kind auto|react-native|expo|repack',
usageDescription: 'metro prepare: detect or force the React Native dev-server launcher kind',
},
{
key: 'metroPublicBaseUrl',
names: ['--public-base-url'],
type: 'string',
usageLabel: '--public-base-url <url>',
usageDescription: 'metro prepare: public base URL used for direct bundle hints',
usageDescription: 'metro prepare: public base URL used for direct dev-server bundle hints',
},
{
key: 'metroProxyBaseUrl',
names: ['--proxy-base-url'],
type: 'string',
usageLabel: '--proxy-base-url <url>',
usageDescription: 'metro prepare: optional bridge origin for remote Metro access',
usageDescription: 'metro prepare: optional bridge origin for remote dev-server access',
},
{
key: 'metroBearerToken',
Expand All @@ -586,29 +586,30 @@ const FLAG_DEFINITIONS: readonly FlagDefinition[] = [
min: 1,
max: 65535,
usageLabel: '--port <port>',
usageDescription: 'metro prepare: local Metro port (default: 8081)',
usageDescription: 'metro prepare: local dev-server port (default: 8081)',
},
{
key: 'metroListenHost',
names: ['--listen-host'],
type: 'string',
usageLabel: '--listen-host <host>',
usageDescription: 'metro prepare: host Metro listens on (default: 0.0.0.0)',
usageDescription: 'metro prepare: host dev server listens on (default: 0.0.0.0)',
},
{
key: 'metroStatusHost',
names: ['--status-host'],
type: 'string',
usageLabel: '--status-host <host>',
usageDescription: 'metro prepare: host used for local /status polling (default: 127.0.0.1)',
usageDescription:
'metro prepare: host used for local dev-server /status polling (default: 127.0.0.1)',
},
{
key: 'metroStartupTimeoutMs',
names: ['--startup-timeout-ms'],
type: 'int',
min: 1,
usageLabel: '--startup-timeout-ms <ms>',
usageDescription: 'metro prepare: timeout while waiting for Metro to become ready',
usageDescription: 'metro prepare: timeout while waiting for the dev server to become ready',
},
{
key: 'metroProbeTimeoutMs',
Expand Down
Loading
Loading