Skip to content

Commit 52d9707

Browse files
authored
feat: expose apps list in node client (#378)
1 parent 937ce8f commit 52d9707

8 files changed

Lines changed: 58 additions & 1 deletion

File tree

src/__tests__/cli-client-commands.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ function createStubClient(params: {
670670
identifiers: { appId: 'com.example.demo' },
671671
}),
672672
installFromSource: params.installFromSource,
673+
list: async () => [],
673674
open:
674675
params.open ??
675676
(async () => ({

src/__tests__/client.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,30 @@ test('apps.installFromSource derives Android launchTarget from packageName when
294294
});
295295
});
296296

297+
test('apps.list forwards filters and returns daemon app names', async () => {
298+
const setup = createTransport(async () => ({
299+
ok: true,
300+
data: {
301+
apps: ['Settings (com.apple.Preferences)', 'Demo (com.example.demo)', { ignored: true }],
302+
},
303+
}));
304+
const client = createAgentDeviceClient(setup.config, { transport: setup.transport });
305+
306+
const apps = await client.apps.list({
307+
platform: 'ios',
308+
device: 'iPhone 16',
309+
appsFilter: 'user-installed',
310+
});
311+
312+
assert.equal(setup.calls.length, 1);
313+
assert.equal(setup.calls[0]?.command, 'apps');
314+
assert.deepEqual(setup.calls[0]?.positionals, []);
315+
assert.equal(setup.calls[0]?.flags?.platform, 'ios');
316+
assert.equal(setup.calls[0]?.flags?.device, 'iPhone 16');
317+
assert.equal(setup.calls[0]?.flags?.appsFilter, 'user-installed');
318+
assert.deepEqual(apps, ['Settings (com.apple.Preferences)', 'Demo (com.example.demo)']);
319+
});
320+
297321
test('materializations.release forwards materialization identity through the daemon request', async () => {
298322
const setup = createTransport(async () => ({
299323
ok: true,

src/__tests__/close-remote-metro.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ test('close with remote-config stops the managed Metro companion for that projec
6161
installFromSource: async () => {
6262
throw new Error('unexpected call');
6363
},
64+
list: async () => {
65+
throw new Error('unexpected call');
66+
},
6467
open: async () => {
6568
throw new Error('unexpected call');
6669
},
@@ -153,6 +156,9 @@ test('close with remote-config still stops the managed Metro companion when clos
153156
installFromSource: async () => {
154157
throw new Error('unexpected call');
155158
},
159+
list: async () => {
160+
throw new Error('unexpected call');
161+
},
156162
open: async () => {
157163
throw new Error('unexpected call');
158164
},
@@ -251,6 +257,9 @@ test('close app with remote-config stops the managed Metro companion for that se
251257
installFromSource: async () => {
252258
throw new Error('unexpected call');
253259
},
260+
list: async () => {
261+
throw new Error('unexpected call');
262+
},
254263
open: async () => {
255264
throw new Error('unexpected call');
256265
},
@@ -348,6 +357,9 @@ test('close with remote-config still succeeds when the config file is gone befor
348357
installFromSource: async () => {
349358
throw new Error('unexpected call');
350359
},
360+
list: async () => {
361+
throw new Error('unexpected call');
362+
},
351363
open: async () => {
352364
throw new Error('unexpected call');
353365
},

src/client-normalizers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ export function buildFlags(options: InternalRequestOptions): CommandFlags {
267267
snapshotScope: options.scope,
268268
snapshotRaw: options.raw,
269269
overlayRefs: options.overlayRefs,
270+
appsFilter: options.appsFilter,
270271
verbose: options.debug,
271272
}) as CommandFlags;
272273
}

src/client-types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ export type AppInstallFromSourceResult = {
211211
identifiers: AgentDeviceIdentifiers;
212212
};
213213

214+
export type AppListOptions = AgentDeviceRequestOverrides &
215+
AgentDeviceSelectionOptions & {
216+
appsFilter?: 'all' | 'user-installed';
217+
};
218+
214219
export type MaterializationReleaseOptions = AgentDeviceRequestOverrides & {
215220
materializationId: string;
216221
};
@@ -295,6 +300,7 @@ export type InternalRequestOptions = AgentDeviceClientConfig &
295300
depth?: number;
296301
scope?: string;
297302
raw?: boolean;
303+
appsFilter?: 'all' | 'user-installed';
298304
installSource?: DaemonInstallSource;
299305
retainMaterializedPaths?: boolean;
300306
materializedPathRetentionMs?: number;
@@ -322,6 +328,7 @@ export type AgentDeviceClient = {
322328
installFromSource: (
323329
options: AppInstallFromSourceOptions,
324330
) => Promise<AppInstallFromSourceResult>;
331+
list: (options?: AppListOptions) => Promise<string[]>;
325332
open: (options: AppOpenOptions) => Promise<AppOpenResult>;
326333
close: (options?: AppCloseOptions) => Promise<AppCloseResult>;
327334
};

src/client.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
AppCloseOptions,
2727
AppDeployOptions,
2828
AppInstallFromSourceOptions,
29+
AppListOptions,
2930
AppOpenOptions,
3031
CaptureScreenshotOptions,
3132
CaptureSnapshotOptions,
@@ -142,6 +143,12 @@ export function createAgentDeviceClient(
142143
}),
143144
resolveSessionName(config.session, options.session),
144145
),
146+
list: async (options: AppListOptions = {}) => {
147+
const data = await execute('apps', [], options);
148+
return Array.isArray(data.apps)
149+
? data.apps.filter((app): app is string => typeof app === 'string')
150+
: [];
151+
},
145152
open: async (options: AppOpenOptions) => {
146153
const session = resolveSessionName(config.session, options.session);
147154
const positionals = options.url ? [options.app, options.url] : [options.app];
@@ -268,6 +275,7 @@ export type {
268275
AppDeployResult,
269276
AppInstallFromSourceOptions,
270277
AppInstallFromSourceResult,
278+
AppListOptions,
271279
AppOpenOptions,
272280
AppOpenResult,
273281
CaptureScreenshotOptions,

src/utils/__tests__/cli-option-schema.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ test('remote config schema stays aligned with CLI option metadata', () => {
5454
assert.equal(definition.type, field.type);
5555
assert.equal(definition.min, 'min' in field ? field.min : undefined);
5656
assert.equal(definition.max, 'max' in field ? field.max : undefined);
57-
assert.deepEqual(definition.enumValues ?? [], 'enumValues' in field ? field.enumValues ?? [] : []);
57+
assert.deepEqual(
58+
definition.enumValues ?? [],
59+
'enumValues' in field ? (field.enumValues ?? []) : [],
60+
);
5861
}
5962
});
6063

website/docs/docs/client-api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const client = createAgentDeviceClient({
3636
});
3737

3838
const devices = await client.devices.list({ platform: 'ios' });
39+
const apps = await client.apps.list({ platform: 'ios', appsFilter: 'user-installed' });
3940
const ensured = await client.simulators.ensure({
4041
device: 'iPhone 16',
4142
boot: true,

0 commit comments

Comments
 (0)