@ebowwa/ios-devices 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/sim-ctl.ts ADDED
@@ -0,0 +1,502 @@
1
+ /**
2
+ * SimCtl - Wrapper for xcrun simctl
3
+ * iOS Simulator control
4
+ */
5
+
6
+ import { execSimCtl, type ExecOptions } from './utils.js';
7
+ import {
8
+ type CommandResult,
9
+ type SimulatorDevice,
10
+ type SimulatorRuntime,
11
+ type SimulatorDeviceType,
12
+ } from './types.js';
13
+
14
+ export interface SimCtlOptions {
15
+ timeout?: number;
16
+ }
17
+
18
+ export class SimCtl {
19
+ private options: SimCtlOptions;
20
+
21
+ constructor(options: SimCtlOptions = {}) {
22
+ this.options = options;
23
+ }
24
+
25
+ // ==========================================================================
26
+ // Device Management
27
+ // ==========================================================================
28
+
29
+ /**
30
+ * List simulators
31
+ */
32
+ async list(options?: {
33
+ devices?: boolean;
34
+ deviceTypes?: boolean;
35
+ runtimes?: boolean;
36
+ pairs?: boolean;
37
+ search?: string;
38
+ available?: boolean;
39
+ }): Promise<CommandResult> {
40
+ const args = ['list'];
41
+
42
+ if (options?.devices) args.push('devices');
43
+ else if (options?.deviceTypes) args.push('devicetypes');
44
+ else if (options?.runtimes) args.push('runtimes');
45
+ else if (options?.pairs) args.push('pairs');
46
+
47
+ if (options?.search) args.push(options.search);
48
+ if (options?.available) args.push('--available');
49
+
50
+ return execSimCtl(args, this.options);
51
+ }
52
+
53
+ /**
54
+ * Create a new simulator
55
+ */
56
+ async create(name: string, deviceTypeId: string, runtimeId: string): Promise<CommandResult> {
57
+ return execSimCtl(['create', name, deviceTypeId, runtimeId], this.options);
58
+ }
59
+
60
+ /**
61
+ * Clone a simulator
62
+ */
63
+ async clone(sourceUdid: string, name: string): Promise<CommandResult> {
64
+ return execSimCtl(['clone', sourceUdid, name], this.options);
65
+ }
66
+
67
+ /**
68
+ * Delete simulators
69
+ */
70
+ async delete(udids: string | string[]): Promise<CommandResult> {
71
+ const ids = Array.isArray(udids) ? udids : [udids];
72
+ return execSimCtl(['delete', ...ids], this.options);
73
+ }
74
+
75
+ /**
76
+ * Delete unavailable simulators
77
+ */
78
+ async deleteUnavailable(): Promise<CommandResult> {
79
+ return execSimCtl(['delete', 'unavailable'], this.options);
80
+ }
81
+
82
+ /**
83
+ * Erase simulator
84
+ */
85
+ async erase(udid: string): Promise<CommandResult> {
86
+ return execSimCtl(['erase', udid], this.options);
87
+ }
88
+
89
+ /**
90
+ * Rename simulator
91
+ */
92
+ async rename(udid: string, name: string): Promise<CommandResult> {
93
+ return execSimCtl(['rename', udid, name], this.options);
94
+ }
95
+
96
+ // ==========================================================================
97
+ // Boot/Shutdown
98
+ // ==========================================================================
99
+
100
+ /**
101
+ * Boot simulator
102
+ */
103
+ async boot(udid: string): Promise<CommandResult> {
104
+ return execSimCtl(['boot', udid], this.options);
105
+ }
106
+
107
+ /**
108
+ * Shutdown simulator
109
+ */
110
+ async shutdown(udid: string): Promise<CommandResult> {
111
+ return execSimCtl(['shutdown', udid], this.options);
112
+ }
113
+
114
+ /**
115
+ * Shutdown all simulators
116
+ */
117
+ async shutdownAll(): Promise<CommandResult> {
118
+ return execSimCtl(['shutdown', 'all'], this.options);
119
+ }
120
+
121
+ // ==========================================================================
122
+ // App Management
123
+ // ==========================================================================
124
+
125
+ /**
126
+ * Install app
127
+ */
128
+ async install(udid: string, appPath: string): Promise<CommandResult> {
129
+ return execSimCtl(['install', udid, appPath], this.options);
130
+ }
131
+
132
+ /**
133
+ * Uninstall app
134
+ */
135
+ async uninstall(udid: string, bundleId: string): Promise<CommandResult> {
136
+ return execSimCtl(['uninstall', udid, bundleId], this.options);
137
+ }
138
+
139
+ /**
140
+ * Get app container path
141
+ */
142
+ async getAppContainer(udid: string, bundleId: string, containerType?: string): Promise<CommandResult> {
143
+ const args = ['get_app_container', udid, bundleId];
144
+ if (containerType) args.push(containerType);
145
+ return execSimCtl(args, this.options);
146
+ }
147
+
148
+ /**
149
+ * Install app data
150
+ */
151
+ async installAppData(udid: string, xcappdataPath: string): Promise<CommandResult> {
152
+ return execSimCtl(['install_app_data', udid, xcappdataPath], this.options);
153
+ }
154
+
155
+ // ==========================================================================
156
+ // App Execution
157
+ // ==========================================================================
158
+
159
+ /**
160
+ * Launch app
161
+ */
162
+ async launch(udid: string, bundleId: string, options?: {
163
+ waitForDebugger?: boolean;
164
+ console?: boolean;
165
+ env?: Record<string, string>;
166
+ args?: string[];
167
+ }): Promise<CommandResult> {
168
+ const args = ['launch'];
169
+
170
+ if (options?.waitForDebugger) args.push('--wait-for-debugger');
171
+ if (options?.console) args.push('--console');
172
+
173
+ if (options?.env) {
174
+ for (const [key, value] of Object.entries(options.env)) {
175
+ args.push('--env', `${key}=${value}`);
176
+ }
177
+ }
178
+
179
+ args.push(udid, bundleId);
180
+
181
+ if (options?.args) {
182
+ args.push(...options.args);
183
+ }
184
+
185
+ return execSimCtl(args, this.options);
186
+ }
187
+
188
+ /**
189
+ * Terminate app
190
+ */
191
+ async terminate(udid: string, bundleId: string): Promise<CommandResult> {
192
+ return execSimCtl(['terminate', udid, bundleId], this.options);
193
+ }
194
+
195
+ /**
196
+ * Spawn process
197
+ */
198
+ async spawn(udid: string, executable: string, args?: string[]): Promise<CommandResult> {
199
+ const spawnArgs = ['spawn', udid, executable];
200
+ if (args) spawnArgs.push(...args);
201
+ return execSimCtl(spawnArgs, this.options);
202
+ }
203
+
204
+ // ==========================================================================
205
+ // IO Operations
206
+ // ==========================================================================
207
+
208
+ /**
209
+ * Take screenshot
210
+ */
211
+ async screenshot(udid: string, outputPath: string, options?: {
212
+ type?: 'png' | 'jpeg' | 'tiff';
213
+ display?: string;
214
+ mask?: string;
215
+ }): Promise<CommandResult> {
216
+ const args = ['io', udid, 'screenshot'];
217
+
218
+ if (options?.type) args.push('--type', options.type);
219
+ if (options?.display) args.push('--display', options.display);
220
+ if (options?.mask) args.push('--mask', options.mask);
221
+
222
+ args.push(outputPath);
223
+
224
+ return execSimCtl(args, this.options);
225
+ }
226
+
227
+ /**
228
+ * Record video
229
+ */
230
+ async recordVideo(udid: string, outputPath: string, options?: {
231
+ codec?: 'h264' | 'hevc';
232
+ display?: string;
233
+ mask?: string;
234
+ force?: boolean;
235
+ }): Promise<CommandResult> {
236
+ const args = ['io', udid, 'recordVideo'];
237
+
238
+ if (options?.codec) args.push('--codec', options.codec);
239
+ if (options?.display) args.push('--display', options.display);
240
+ if (options?.mask) args.push('--mask', options.mask);
241
+ if (options?.force) args.push('--force');
242
+
243
+ args.push(outputPath);
244
+
245
+ return execSimCtl(args, this.options);
246
+ }
247
+
248
+ // ==========================================================================
249
+ // Location
250
+ // ==========================================================================
251
+
252
+ /**
253
+ * Set location
254
+ */
255
+ async setLocation(udid: string, latitude: number, longitude: number): Promise<CommandResult> {
256
+ return execSimCtl(['location', udid, 'set', String(latitude), String(longitude)], this.options);
257
+ }
258
+
259
+ /**
260
+ * Reset location
261
+ */
262
+ async resetLocation(udid: string): Promise<CommandResult> {
263
+ return execSimCtl(['location', udid, 'reset'], this.options);
264
+ }
265
+
266
+ // ==========================================================================
267
+ // Status Bar
268
+ // ==========================================================================
269
+
270
+ /**
271
+ * Override status bar
272
+ */
273
+ async statusBar(udid: string, overrides: {
274
+ time?: string;
275
+ dataNetwork?: string;
276
+ wifiMode?: 'active' | 'searching' | 'failed';
277
+ cellularMode?: 'active' | 'searching' | 'failed';
278
+ operatorName?: string;
279
+ batteryLevel?: number;
280
+ batteryState?: 'charging' | 'charged' | 'discharging';
281
+ }): Promise<CommandResult> {
282
+ const args = ['status_bar', udid, 'override'];
283
+
284
+ if (overrides.time) args.push('--time', overrides.time);
285
+ if (overrides.dataNetwork) args.push('--dataNetwork', overrides.dataNetwork);
286
+ if (overrides.wifiMode) args.push('--wifiMode', overrides.wifiMode);
287
+ if (overrides.cellularMode) args.push('--cellularMode', overrides.cellularMode);
288
+ if (overrides.operatorName) args.push('--operatorName', overrides.operatorName);
289
+ if (overrides.batteryLevel !== undefined) args.push('--batteryLevel', String(overrides.batteryLevel));
290
+ if (overrides.batteryState) args.push('--batteryState', overrides.batteryState);
291
+
292
+ return execSimCtl(args, this.options);
293
+ }
294
+
295
+ /**
296
+ * Clear status bar overrides
297
+ */
298
+ async clearStatusBar(udid: string): Promise<CommandResult> {
299
+ return execSimCtl(['status_bar', udid, 'clear'], this.options);
300
+ }
301
+
302
+ // ==========================================================================
303
+ // URL & Notifications
304
+ // ==========================================================================
305
+
306
+ /**
307
+ * Open URL
308
+ */
309
+ async openURL(udid: string, url: string): Promise<CommandResult> {
310
+ return execSimCtl(['openurl', udid, url], this.options);
311
+ }
312
+
313
+ /**
314
+ * Send push notification
315
+ */
316
+ async push(udid: string, bundleId: string, jsonPayload: object): Promise<CommandResult> {
317
+ return execSimCtl(['push', udid, bundleId, JSON.stringify(jsonPayload)], this.options);
318
+ }
319
+
320
+ // ==========================================================================
321
+ // Privacy
322
+ // ==========================================================================
323
+
324
+ /**
325
+ * Grant privacy permission
326
+ */
327
+ async grantPrivacy(udid: string, bundleId: string, service: string): Promise<CommandResult> {
328
+ return execSimCtl(['privacy', udid, 'grant', bundleId, service], this.options);
329
+ }
330
+
331
+ /**
332
+ * Revoke privacy permission
333
+ */
334
+ async revokePrivacy(udid: string, bundleId: string, service: string): Promise<CommandResult> {
335
+ return execSimCtl(['privacy', udid, 'revoke', bundleId, service], this.options);
336
+ }
337
+
338
+ /**
339
+ * Reset privacy permissions
340
+ */
341
+ async resetPrivacy(udid: string, bundleId: string, service: string): Promise<CommandResult> {
342
+ return execSimCtl(['privacy', udid, 'reset', bundleId, service], this.options);
343
+ }
344
+
345
+ // ==========================================================================
346
+ // Pasteboard
347
+ // ==========================================================================
348
+
349
+ /**
350
+ * Copy to pasteboard
351
+ */
352
+ async pbcopy(udid: string, data: string): Promise<CommandResult> {
353
+ // Note: pbcopy reads from stdin
354
+ const result = await execSimCtl(['pbcopy', udid], {
355
+ ...this.options,
356
+ // This would need stdin support, simplified here
357
+ });
358
+ return result;
359
+ }
360
+
361
+ /**
362
+ * Paste from pasteboard
363
+ */
364
+ async pbpaste(udid: string): Promise<CommandResult> {
365
+ return execSimCtl(['pbpaste', udid], this.options);
366
+ }
367
+
368
+ // ==========================================================================
369
+ // Keychain
370
+ // ==========================================================================
371
+
372
+ /**
373
+ * Add keychain item
374
+ */
375
+ async keychainAdd(udid: string, service: string, account: string, password: string, options?: {
376
+ accessGroup?: string;
377
+ accessible?: 'always' | 'unlocked' | 'first-unlock';
378
+ }): Promise<CommandResult> {
379
+ const args = ['keychain', udid, 'add', service, account, password];
380
+ if (options?.accessGroup) args.push('--accessGroup', options.accessGroup);
381
+ if (options?.accessible) args.push('--accessible', options.accessible);
382
+ return execSimCtl(args, this.options);
383
+ }
384
+
385
+ /**
386
+ * Reset keychain
387
+ */
388
+ async keychainReset(udid: string): Promise<CommandResult> {
389
+ return execSimCtl(['keychain', udid, 'reset'], this.options);
390
+ }
391
+
392
+ // ==========================================================================
393
+ // Environment
394
+ // ==========================================================================
395
+
396
+ /**
397
+ * Get environment variable
398
+ */
399
+ async getenv(udid: string, variable?: string): Promise<CommandResult> {
400
+ const args = ['getenv', udid];
401
+ if (variable) args.push(variable);
402
+ return execSimCtl(args, this.options);
403
+ }
404
+
405
+ // ==========================================================================
406
+ // Diagnostics
407
+ // ==========================================================================
408
+
409
+ /**
410
+ * Collect diagnostics
411
+ */
412
+ async diagnose(outputPath?: string): Promise<CommandResult> {
413
+ const args = ['diagnose'];
414
+ if (outputPath) args.push('--output', outputPath);
415
+ return execSimCtl(args, { ...this.options, timeout: 120000 });
416
+ }
417
+
418
+ // ==========================================================================
419
+ // iCloud
420
+ // ==========================================================================
421
+
422
+ /**
423
+ * Trigger iCloud sync
424
+ */
425
+ async icloudSync(udid: string): Promise<CommandResult> {
426
+ return execSimCtl(['icloud_sync', udid], this.options);
427
+ }
428
+
429
+ // ==========================================================================
430
+ // Media
431
+ // ==========================================================================
432
+
433
+ /**
434
+ * Add media (photos, videos, contacts)
435
+ */
436
+ async addMedia(udid: string, mediaPaths: string | string[]): Promise<CommandResult> {
437
+ const paths = Array.isArray(mediaPaths) ? mediaPaths : [mediaPaths];
438
+ return execSimCtl(['addmedia', udid, ...paths], this.options);
439
+ }
440
+
441
+ // ==========================================================================
442
+ // Runtime
443
+ // ==========================================================================
444
+
445
+ /**
446
+ * Upgrade simulator runtime
447
+ */
448
+ async upgrade(udid: string, runtimeId: string): Promise<CommandResult> {
449
+ return execSimCtl(['upgrade', udid, runtimeId], { ...this.options, timeout: 300000 });
450
+ }
451
+
452
+ // ==========================================================================
453
+ // Pairs (Watch)
454
+ // ==========================================================================
455
+
456
+ /**
457
+ * Create watch/phone pair
458
+ */
459
+ async pair(watchUdid: string, phoneUdid: string): Promise<CommandResult> {
460
+ return execSimCtl(['pair', watchUdid, phoneUdid], this.options);
461
+ }
462
+
463
+ /**
464
+ * Unpair watch/phone
465
+ */
466
+ async unpair(watchUdid: string, phoneUdid: string): Promise<CommandResult> {
467
+ return execSimCtl(['unpair', watchUdid, phoneUdid], this.options);
468
+ }
469
+
470
+ /**
471
+ * Activate pair
472
+ */
473
+ async pairActivate(watchUdid: string, phoneUdid: string): Promise<CommandResult> {
474
+ return execSimCtl(['pair_activate', watchUdid, phoneUdid], this.options);
475
+ }
476
+
477
+ // ==========================================================================
478
+ // UI Options
479
+ // ==========================================================================
480
+
481
+ /**
482
+ * Get UI options
483
+ */
484
+ async getUI(udid: string): Promise<CommandResult> {
485
+ return execSimCtl(['ui', udid, 'appearance'], this.options);
486
+ }
487
+
488
+ /**
489
+ * Set UI appearance
490
+ */
491
+ async setUI(udid: string, options: {
492
+ appearance?: 'light' | 'dark';
493
+ contentSize?: 'extra-small' | 'small' | 'medium' | 'large' | 'extra-large' | 'accessibility-medium' | 'accessibility-large' | 'accessibility-extra-large';
494
+ }): Promise<CommandResult> {
495
+ const args = ['ui', udid, 'appearance'];
496
+
497
+ if (options.appearance) args.push('--appearance', options.appearance);
498
+ if (options.contentSize) args.push('--content-size', options.contentSize);
499
+
500
+ return execSimCtl(args, this.options);
501
+ }
502
+ }