Skip to content

Commit 041b482

Browse files
authored
feat: add iOS runner prepare command (#673)
* feat: add ios runner prepare command * fix: shorten snapshot request timeout * fix: refine prepare and snapshot timeouts * test: cover prepare ios runner provider scenario
1 parent 6f637d0 commit 041b482

20 files changed

Lines changed: 358 additions & 9 deletions

src/client-types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ export type CaptureSnapshotOptions = AgentDeviceRequestOverrides &
306306
scope?: string;
307307
raw?: boolean;
308308
forceFull?: boolean;
309+
timeoutMs?: number;
309310
};
310311

311312
export type CaptureSnapshotResult = {
@@ -478,6 +479,11 @@ export type ReactNativeCommandOptions = ClientCommandBaseOptions & {
478479
action: 'dismiss-overlay';
479480
};
480481

482+
export type PrepareCommandOptions = ClientCommandBaseOptions & {
483+
action: 'ios-runner';
484+
timeoutMs?: number;
485+
};
486+
481487
export type AgentDeviceCommandClient = {
482488
wait: (options: WaitCommandOptions) => Promise<WaitCommandResult>;
483489
alert: (options?: AlertCommandOptions) => Promise<AlertCommandResult>;
@@ -489,6 +495,7 @@ export type AgentDeviceCommandClient = {
489495
keyboard: (options?: KeyboardCommandOptions) => Promise<KeyboardCommandResult>;
490496
clipboard: (options: ClipboardCommandOptions) => Promise<ClipboardCommandResult>;
491497
reactNative: (options: ReactNativeCommandOptions) => Promise<CommandRequestResult>;
498+
prepare: (options: PrepareCommandOptions) => Promise<CommandRequestResult>;
492499
};
493500

494501
type SelectorSnapshotCommandOptions = Pick<CaptureSnapshotOptions, 'depth' | 'scope' | 'raw'>;

src/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export function createAgentDeviceClient(
9696
keyboard: async (options = {}) => await executeCommand('keyboard', options),
9797
clipboard: async (options) => await executeCommand('clipboard', options),
9898
reactNative: async (options) => await executeCommand('react-native', options),
99+
prepare: async (options) => await executeCommand('prepare', options),
99100
},
100101
devices: {
101102
list: async (options = {}) => {

src/command-catalog.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const PUBLIC_COMMANDS = {
2626
network: 'network',
2727
open: 'open',
2828
perf: 'perf',
29+
prepare: 'prepare',
2930
press: 'press',
3031
push: 'push',
3132
record: 'record',
@@ -129,6 +130,7 @@ const MCP_UNEXPOSED_CLI_COMMANDS = commandSet(
129130
LOCAL_CLI_COMMANDS.disconnect,
130131
LOCAL_CLI_COMMANDS.mcp,
131132
LOCAL_CLI_COMMANDS.reactDevtools,
133+
PUBLIC_COMMANDS.prepare,
132134
);
133135

134136
const CAPABILITY_EXEMPT_CLI_COMMANDS = commandSet(
@@ -141,6 +143,7 @@ const CAPABILITY_EXEMPT_CLI_COMMANDS = commandSet(
141143
LOCAL_CLI_COMMANDS.reactDevtools,
142144
LOCAL_CLI_COMMANDS.session,
143145
PUBLIC_COMMANDS.appState,
146+
PUBLIC_COMMANDS.prepare,
144147
PUBLIC_COMMANDS.batch,
145148
PUBLIC_COMMANDS.devices,
146149
PUBLIC_COMMANDS.gesture,
@@ -249,6 +252,7 @@ export const DAEMON_COMMAND_GROUPS = {
249252
PUBLIC_COMMANDS.network,
250253
PUBLIC_COMMANDS.open,
251254
PUBLIC_COMMANDS.perf,
255+
PUBLIC_COMMANDS.prepare,
252256
PUBLIC_COMMANDS.push,
253257
PUBLIC_COMMANDS.reinstall,
254258
PUBLIC_COMMANDS.replay,

src/commands/__tests__/command-surface-metadata.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ test('MCP exposed command names have metadata and executable command definitions
1919
assert.ok(executableNames.has(name), `${name} must have an executable command definition`);
2020
}
2121
});
22+
23+
test('CI-only prepare command stays out of MCP tool surface', () => {
24+
assert.equal(listMcpExposedCommandNames().includes('prepare'), false);
25+
});

src/commands/cli-grammar/apps.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ export const appCliReaders = {
2929
...commonInputFromFlags(flags),
3030
headless: flags.headless,
3131
}),
32+
prepare: (positionals, flags) => ({
33+
...commonInputFromFlags(flags),
34+
action: requiredString(positionals[0], 'prepare requires subcommand'),
35+
timeoutMs: flags.timeoutMs,
36+
}),
3237
open: (positionals, flags) => ({
3338
...commonInputFromFlags(flags),
3439
app: positionals[0],
@@ -72,6 +77,9 @@ export const appCliReaders = {
7277
export const appDaemonWriters = {
7378
devices: direct(PUBLIC_COMMANDS.devices),
7479
boot: direct(PUBLIC_COMMANDS.boot),
80+
prepare: direct(PUBLIC_COMMANDS.prepare, (input) => [
81+
requiredDaemonString(input.action, 'prepare requires subcommand'),
82+
]),
7583
apps: direct(PUBLIC_COMMANDS.apps),
7684
open: direct(PUBLIC_COMMANDS.open, openPositionals),
7785
close: direct(PUBLIC_COMMANDS.close, (input) => optionalString(input.app)),

src/commands/cli-grammar/capture.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const captureCliReaders = {
3939
scope: flags.snapshotScope,
4040
raw: flags.snapshotRaw,
4141
forceFull: flags.snapshotForceFull,
42+
timeoutMs: flags.timeoutMs,
4243
}),
4344
screenshot: (positionals, flags) => ({
4445
...commonInputFromFlags(flags),

src/commands/cli-output.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const cliOutputFormatters: Partial<Record<CommandName, CliOutputFormatter>> = {
8282
is: resultOutput(isCliOutput),
8383
find: resultOutput(findCliOutput),
8484
perf: resultOutput(perfCliOutput),
85+
prepare: messageOutput,
8586
logs: resultOutput(logsCliOutput),
8687
network: resultOutput(networkCliOutput),
8788
record: resultOutput(recordCliOutput),

src/commands/client-command-contracts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export const clientCommandDefinitions = [
6262
defineExecutableCommand(metadata('react-native'), (client, input) =>
6363
client.command.reactNative(input),
6464
),
65+
defineExecutableCommand(metadata('prepare'), (client, input) => client.command.prepare(input)),
6566
defineExecutableCommand(metadata('replay'), (client, input) => client.replay.run(input)),
6667
defineExecutableCommand(metadata('test'), (client, input) => client.replay.test(input)),
6768
defineExecutableCommand(metadata('perf'), (client, input) => client.observability.perf(input)),

src/commands/client-command-metadata.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@ const NETWORK_INCLUDE_VALUES = ['summary', 'headers', 'body', 'all'] as const;
3636
const START_STOP_VALUES = ['start', 'stop'] as const;
3737
const REACT_NATIVE_ACTION_VALUES = ['dismiss-overlay'] as const;
3838
const METRO_ACTION_VALUES = ['prepare', 'reload'] as const;
39+
const PREPARE_ACTION_VALUES = ['ios-runner'] as const;
3940

4041
export const clientCommandMetadata = [
4142
defineClientCommandMetadata('devices', {}),
4243
defineClientCommandMetadata('boot', {
4344
headless: booleanField('Boot without showing simulator UI when supported.'),
4445
}),
46+
defineClientCommandMetadata('prepare', {
47+
action: requiredField(enumField(PREPARE_ACTION_VALUES)),
48+
timeoutMs: integerField('Maximum wall-clock time for the prepare command.'),
49+
}),
4550
defineClientCommandMetadata('apps', {
4651
appsFilter: enumField(['user-installed', 'all']),
4752
}),
@@ -100,6 +105,7 @@ export const clientCommandMetadata = [
100105
scope: stringField(),
101106
raw: booleanField(),
102107
forceFull: booleanField(),
108+
timeoutMs: integerField('Maximum wall-clock time for the snapshot command.'),
103109
}),
104110
defineClientCommandMetadata('screenshot', {
105111
path: stringField('Output path.'),

src/commands/command-descriptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const COMMAND_DESCRIPTIONS = {
44
apps: 'List installed apps.',
55
session: 'List active sessions.',
66
open: 'Open an app, deep link, URL, or platform surface.',
7+
prepare: 'Prepare platform helper infrastructure.',
78
close: 'Close an app or end the active session.',
89
install: 'Install an app binary.',
910
reinstall: 'Reinstall an app binary.',

0 commit comments

Comments
 (0)