@limrun/api 0.39.1 → 0.39.3
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/CHANGELOG.md +8 -0
- package/folder-sync.d.mts +6 -0
- package/folder-sync.d.mts.map +1 -1
- package/folder-sync.d.ts +6 -0
- package/folder-sync.d.ts.map +1 -1
- package/folder-sync.js +2 -1
- package/folder-sync.js.map +1 -1
- package/folder-sync.mjs +2 -1
- package/folder-sync.mjs.map +1 -1
- package/ios-client.d.mts +10 -2
- package/ios-client.d.mts.map +1 -1
- package/ios-client.d.ts +10 -2
- package/ios-client.d.ts.map +1 -1
- package/ios-client.js +108 -0
- package/ios-client.js.map +1 -1
- package/ios-client.mjs +108 -0
- package/ios-client.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/xcode-instances-helpers.d.mts +6 -0
- package/resources/xcode-instances-helpers.d.mts.map +1 -1
- package/resources/xcode-instances-helpers.d.ts +6 -0
- package/resources/xcode-instances-helpers.d.ts.map +1 -1
- package/resources/xcode-instances-helpers.js +6 -2
- package/resources/xcode-instances-helpers.js.map +1 -1
- package/resources/xcode-instances-helpers.mjs +6 -2
- package/resources/xcode-instances-helpers.mjs.map +1 -1
- package/src/folder-sync.ts +8 -1
- package/src/ios-client.ts +154 -2
- package/src/resources/xcode-instances-helpers.ts +12 -2
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/src/ios-client.ts
CHANGED
|
@@ -270,7 +270,17 @@ export type DetoxLaunchRuntime = {
|
|
|
270
270
|
|
|
271
271
|
export type LaunchAppRuntime = DetoxLaunchRuntime;
|
|
272
272
|
|
|
273
|
-
type
|
|
273
|
+
export type LaunchAppShutdownCallback = (exitCode: number, logs: string[]) => Promise<void>;
|
|
274
|
+
|
|
275
|
+
type LaunchAppShutdownOptions = {
|
|
276
|
+
/**
|
|
277
|
+
* Called when the launched app exits. The logs array contains one entry per
|
|
278
|
+
* stdout/stderr line produced by that execution, as reported by limulator.
|
|
279
|
+
*/
|
|
280
|
+
onShutdown?: LaunchAppShutdownCallback;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
type StandardLaunchAppOptions = LaunchAppShutdownOptions & {
|
|
274
284
|
/**
|
|
275
285
|
* Launch behavior when the app may already be running.
|
|
276
286
|
* Defaults to `ForegroundIfRunning` server-side.
|
|
@@ -279,7 +289,7 @@ type StandardLaunchAppOptions = {
|
|
|
279
289
|
runtime?: undefined;
|
|
280
290
|
};
|
|
281
291
|
|
|
282
|
-
type RuntimeLaunchAppOptions = {
|
|
292
|
+
type RuntimeLaunchAppOptions = LaunchAppShutdownOptions & {
|
|
283
293
|
/** Runtime launches must relaunch so runtime injection is applied. */
|
|
284
294
|
mode?: Extract<LaunchAppMode, 'RelaunchIfRunning'>;
|
|
285
295
|
/** Optional app runtime to attach during launch. */
|
|
@@ -960,6 +970,9 @@ type ServerResponse = {
|
|
|
960
970
|
exitCode?: number;
|
|
961
971
|
// Log tail fields
|
|
962
972
|
logs?: string;
|
|
973
|
+
// App shutdown fields
|
|
974
|
+
execId?: string;
|
|
975
|
+
logLineCount?: number;
|
|
963
976
|
// App log streaming fields
|
|
964
977
|
lines?: string[];
|
|
965
978
|
// performActions batch result fields
|
|
@@ -1295,6 +1308,12 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1295
1308
|
// Simctl uses streaming, so it needs separate handling
|
|
1296
1309
|
const simctlExecutions: Map<string, SimctlExecution> = new Map();
|
|
1297
1310
|
|
|
1311
|
+
// Server notifications are keyed by notification type and that type's
|
|
1312
|
+
// identifier field. They intentionally survive transient WebSocket reconnects
|
|
1313
|
+
// and are one-shot once a matching notification is processed.
|
|
1314
|
+
type ServerNotificationHandler = (message: ServerResponse) => Promise<void>;
|
|
1315
|
+
const serverNotifications: Map<string, Map<string, ServerNotificationHandler>> = new Map();
|
|
1316
|
+
|
|
1298
1317
|
const xcrunShimCleanups: Array<() => Promise<void>> = [];
|
|
1299
1318
|
const httpProxyCleanups: Array<() => Promise<void>> = [];
|
|
1300
1319
|
|
|
@@ -1375,6 +1394,52 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1375
1394
|
cleanupClientResources();
|
|
1376
1395
|
};
|
|
1377
1396
|
|
|
1397
|
+
const registerServerNotification = (
|
|
1398
|
+
type: string,
|
|
1399
|
+
notificationId: string,
|
|
1400
|
+
handler: ServerNotificationHandler,
|
|
1401
|
+
): void => {
|
|
1402
|
+
const handlers = serverNotifications.get(type) ?? new Map<string, ServerNotificationHandler>();
|
|
1403
|
+
handlers.set(notificationId, handler);
|
|
1404
|
+
serverNotifications.set(type, handlers);
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
const takeServerNotification = (
|
|
1408
|
+
type: string,
|
|
1409
|
+
notificationId: string,
|
|
1410
|
+
): ServerNotificationHandler | undefined => {
|
|
1411
|
+
const handlers = serverNotifications.get(type);
|
|
1412
|
+
if (!handlers) {
|
|
1413
|
+
return undefined;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const handler = handlers.get(notificationId);
|
|
1417
|
+
if (!handler) {
|
|
1418
|
+
return undefined;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
handlers.delete(notificationId);
|
|
1422
|
+
if (handlers.size === 0) {
|
|
1423
|
+
serverNotifications.delete(type);
|
|
1424
|
+
}
|
|
1425
|
+
return handler;
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
const deleteServerNotification = (type: string, notificationId: string): void => {
|
|
1429
|
+
const handlers = serverNotifications.get(type);
|
|
1430
|
+
if (!handlers) {
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
handlers.delete(notificationId);
|
|
1434
|
+
if (handlers.size === 0) {
|
|
1435
|
+
serverNotifications.delete(type);
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
const clearServerNotifications = (): void => {
|
|
1440
|
+
serverNotifications.clear();
|
|
1441
|
+
};
|
|
1442
|
+
|
|
1378
1443
|
let pingInterval: NodeJS.Timeout | undefined;
|
|
1379
1444
|
const keepAliveSessionId =
|
|
1380
1445
|
Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
@@ -1392,12 +1457,14 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1392
1457
|
if (isNonRetryableError(lastError ?? '')) {
|
|
1393
1458
|
logger.error(`Skipping reconnection (non-retryable error): ${lastError}`);
|
|
1394
1459
|
updateConnectionState('disconnected');
|
|
1460
|
+
clearServerNotifications();
|
|
1395
1461
|
return;
|
|
1396
1462
|
}
|
|
1397
1463
|
|
|
1398
1464
|
if (reconnectAttempts >= maxReconnectAttempts) {
|
|
1399
1465
|
logger.error(`Max reconnection attempts (${maxReconnectAttempts}) reached. Giving up.`);
|
|
1400
1466
|
updateConnectionState('disconnected');
|
|
1467
|
+
clearServerNotifications();
|
|
1401
1468
|
return;
|
|
1402
1469
|
}
|
|
1403
1470
|
|
|
@@ -1427,6 +1494,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1427
1494
|
return {
|
|
1428
1495
|
type: request['type'],
|
|
1429
1496
|
id: request['id'],
|
|
1497
|
+
execId: request['execId'],
|
|
1430
1498
|
bundleId: request['bundleId'],
|
|
1431
1499
|
mode: request['mode'],
|
|
1432
1500
|
runtime: runtime ? { kind: runtime.kind, version: runtime.version } : undefined,
|
|
@@ -1471,6 +1539,45 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1471
1539
|
});
|
|
1472
1540
|
};
|
|
1473
1541
|
|
|
1542
|
+
const splitAppLogTail = (logs: string): string[] => {
|
|
1543
|
+
if (logs.length === 0) {
|
|
1544
|
+
return [];
|
|
1545
|
+
}
|
|
1546
|
+
return logs.split(/\r?\n/);
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
const handleAppShutdown = (message: ServerResponse): boolean => {
|
|
1550
|
+
if (message.type !== 'appShutdown') {
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
const { execId, bundleId, exitCode, logLineCount } = message;
|
|
1555
|
+
if (
|
|
1556
|
+
typeof execId !== 'string' ||
|
|
1557
|
+
typeof bundleId !== 'string' ||
|
|
1558
|
+
typeof exitCode !== 'number' ||
|
|
1559
|
+
!Number.isInteger(exitCode) ||
|
|
1560
|
+
typeof logLineCount !== 'number' ||
|
|
1561
|
+
!Number.isInteger(logLineCount) ||
|
|
1562
|
+
logLineCount < 0
|
|
1563
|
+
) {
|
|
1564
|
+
logger.warn('Received malformed appShutdown message:', message);
|
|
1565
|
+
return true;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
const notification = takeServerNotification('appShutdown', execId);
|
|
1569
|
+
if (!notification) {
|
|
1570
|
+
logger.debug(`Received appShutdown for unknown or already handled execId: ${execId}`);
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
void notification(message).catch((error) => {
|
|
1575
|
+
logger.error(`Error processing appShutdown notification for execId ${execId}:`, error);
|
|
1576
|
+
});
|
|
1577
|
+
|
|
1578
|
+
return true;
|
|
1579
|
+
};
|
|
1580
|
+
|
|
1474
1581
|
// Response handlers - transform raw responses to typed results
|
|
1475
1582
|
const responseHandlers: Record<string, (msg: ServerResponse) => unknown> = {
|
|
1476
1583
|
screenshotResult: (msg) => ({
|
|
@@ -1545,6 +1652,10 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1545
1652
|
return;
|
|
1546
1653
|
}
|
|
1547
1654
|
|
|
1655
|
+
if (handleAppShutdown(message)) {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1548
1659
|
// Handle simctl streaming separately (it uses multiple messages per request)
|
|
1549
1660
|
if (message.type === 'simctlStream') {
|
|
1550
1661
|
const execution = simctlExecutions.get(message.id);
|
|
@@ -1638,6 +1749,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1638
1749
|
cleanup();
|
|
1639
1750
|
updateConnectionState('disconnected');
|
|
1640
1751
|
failPendingRequests('Non-retryable error');
|
|
1752
|
+
clearServerNotifications();
|
|
1641
1753
|
logger.debug('Non-retryable error. Closing connection.');
|
|
1642
1754
|
}
|
|
1643
1755
|
});
|
|
@@ -1800,10 +1912,49 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1800
1912
|
);
|
|
1801
1913
|
}
|
|
1802
1914
|
const mode = launchOptions.runtime ? 'RelaunchIfRunning' : launchOptions.mode;
|
|
1915
|
+
const onShutdown = launchOptions.onShutdown;
|
|
1916
|
+
const execId = onShutdown ? generateId() : undefined;
|
|
1917
|
+
if (execId && onShutdown) {
|
|
1918
|
+
registerServerNotification('appShutdown', execId, async (message) => {
|
|
1919
|
+
const { bundleId, exitCode, logLineCount } = message;
|
|
1920
|
+
if (
|
|
1921
|
+
typeof bundleId !== 'string' ||
|
|
1922
|
+
typeof exitCode !== 'number' ||
|
|
1923
|
+
!Number.isInteger(exitCode) ||
|
|
1924
|
+
typeof logLineCount !== 'number' ||
|
|
1925
|
+
!Number.isInteger(logLineCount) ||
|
|
1926
|
+
logLineCount < 0
|
|
1927
|
+
) {
|
|
1928
|
+
logger.warn(`Received malformed appShutdown payload for execId ${execId}:`, message);
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
let logs: string[] = [];
|
|
1933
|
+
try {
|
|
1934
|
+
logs = splitAppLogTail(
|
|
1935
|
+
await sendRequest<string>('appLogTail', { bundleId, lines: logLineCount }),
|
|
1936
|
+
);
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
logger.error(`Failed to fetch app logs for shutdown execId ${execId}:`, error);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
try {
|
|
1942
|
+
await onShutdown(exitCode, logs);
|
|
1943
|
+
} catch (error) {
|
|
1944
|
+
logger.error(`Error in onShutdown callback for execId ${execId}:`, error);
|
|
1945
|
+
}
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1803
1948
|
return sendRequest<void>('launchApp', {
|
|
1804
1949
|
bundleId,
|
|
1805
1950
|
mode,
|
|
1806
1951
|
runtime: launchOptions.runtime,
|
|
1952
|
+
execId,
|
|
1953
|
+
}).catch((error) => {
|
|
1954
|
+
if (execId) {
|
|
1955
|
+
deleteServerNotification('appShutdown', execId);
|
|
1956
|
+
}
|
|
1957
|
+
throw error;
|
|
1807
1958
|
});
|
|
1808
1959
|
};
|
|
1809
1960
|
|
|
@@ -2268,6 +2419,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
2268
2419
|
cleanup();
|
|
2269
2420
|
updateConnectionState('disconnected');
|
|
2270
2421
|
failPendingRequests('Intentional disconnect');
|
|
2422
|
+
clearServerNotifications();
|
|
2271
2423
|
logger.debug('Intentionally disconnected from WebSocket.');
|
|
2272
2424
|
};
|
|
2273
2425
|
|
|
@@ -52,6 +52,12 @@ export type SyncOptions = {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
export type SyncResult = {
|
|
55
|
+
/**
|
|
56
|
+
* Number of bytes transmitted to the server for this sync (full uploads plus
|
|
57
|
+
* delta patches, before transport compression). In watch mode, this reflects
|
|
58
|
+
* the initial sync only.
|
|
59
|
+
*/
|
|
60
|
+
bytesSent?: number;
|
|
55
61
|
/** Present only when watch=true; call to stop watching */
|
|
56
62
|
stopWatching?: () => Promise<void>;
|
|
57
63
|
};
|
|
@@ -628,10 +634,14 @@ export class XcodeInstances extends GeneratedXcodeInstances {
|
|
|
628
634
|
};
|
|
629
635
|
|
|
630
636
|
const result = await syncFolderImpl(localCodePath, codeSyncOpts);
|
|
637
|
+
const out: SyncResult = {};
|
|
638
|
+
if (result.bytesSent !== undefined) {
|
|
639
|
+
out.bytesSent = result.bytesSent;
|
|
640
|
+
}
|
|
631
641
|
if (result.stopWatching) {
|
|
632
|
-
|
|
642
|
+
out.stopWatching = result.stopWatching;
|
|
633
643
|
}
|
|
634
|
-
return
|
|
644
|
+
return out;
|
|
635
645
|
},
|
|
636
646
|
|
|
637
647
|
xcodebuild(settings?: XcodeProjectConfig, options?: XcodeBuildOptions): ExecChildProcess {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.39.
|
|
1
|
+
export const VERSION = '0.39.3'; // x-release-please-version
|
package/version.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.39.
|
|
1
|
+
export declare const VERSION = "0.39.3";
|
|
2
2
|
//# sourceMappingURL=version.d.mts.map
|
package/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.39.
|
|
1
|
+
export declare const VERSION = "0.39.3";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/version.js
CHANGED
package/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '0.39.
|
|
1
|
+
export const VERSION = '0.39.3'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.mjs.map
|