@limrun/api 0.48.0 → 0.48.2
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/exec-client.d.mts +64 -0
- package/exec-client.d.mts.map +1 -1
- package/exec-client.d.ts +64 -0
- package/exec-client.d.ts.map +1 -1
- package/exec-client.js +67 -1
- package/exec-client.js.map +1 -1
- package/exec-client.mjs +67 -1
- package/exec-client.mjs.map +1 -1
- package/index.d.mts +1 -1
- package/index.d.mts.map +1 -1
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js.map +1 -1
- package/index.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/xcode-instances-helpers.d.mts +36 -2
- package/resources/xcode-instances-helpers.d.mts.map +1 -1
- package/resources/xcode-instances-helpers.d.ts +36 -2
- package/resources/xcode-instances-helpers.d.ts.map +1 -1
- package/resources/xcode-instances-helpers.js +26 -2
- package/resources/xcode-instances-helpers.js.map +1 -1
- package/resources/xcode-instances-helpers.mjs +26 -2
- package/resources/xcode-instances-helpers.mjs.map +1 -1
- package/src/exec-client.ts +124 -1
- package/src/index.ts +3 -0
- package/src/resources/xcode-instances-helpers.ts +67 -3
- 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/exec-client.ts
CHANGED
|
@@ -51,6 +51,28 @@ export type XcodeBuildExecRequest = {
|
|
|
51
51
|
sdk?: 'iphonesimulator' | 'iphoneos' | 'watchsimulator' | 'watchos';
|
|
52
52
|
configuration?: 'Debug' | 'Release';
|
|
53
53
|
artifactName?: string;
|
|
54
|
+
/**
|
|
55
|
+
* xcodebuild action. 'build' (default) produces one .app; with a
|
|
56
|
+
* simulator attached it is installed there. 'build-for-testing' compiles
|
|
57
|
+
* the scheme's test targets and, with a simulator attached, runs them
|
|
58
|
+
* there, streaming per-case results as 'xctest' events; the exec exits
|
|
59
|
+
* non-zero when tests fail. Requires the iphonesimulator sdk.
|
|
60
|
+
*/
|
|
61
|
+
action?: 'build' | 'build-for-testing';
|
|
62
|
+
/**
|
|
63
|
+
* Run only these tests, in xcodebuild's -only-testing format:
|
|
64
|
+
* Target[/Class[/method]]. Mutually exclusive with skipTesting; only
|
|
65
|
+
* valid with action 'build-for-testing'.
|
|
66
|
+
*/
|
|
67
|
+
onlyTesting?: string[];
|
|
68
|
+
/** Skip these tests, in xcodebuild's -skip-testing format. */
|
|
69
|
+
skipTesting?: string[];
|
|
70
|
+
/**
|
|
71
|
+
* When false, 'build-for-testing' compiles the test targets but does not
|
|
72
|
+
* run them even when a simulator is attached. Servers without support
|
|
73
|
+
* ignore the field and still run the tests.
|
|
74
|
+
*/
|
|
75
|
+
runTests?: boolean;
|
|
54
76
|
};
|
|
55
77
|
xcodegen?: {
|
|
56
78
|
spec?: string;
|
|
@@ -234,6 +256,12 @@ export type ExecOptions = {
|
|
|
234
256
|
apiUrl: string;
|
|
235
257
|
token: string;
|
|
236
258
|
log?: (level: 'debug' | 'info' | 'warn' | 'error', msg: string) => void;
|
|
259
|
+
/**
|
|
260
|
+
* Called for each XCTest event as it streams from a build-for-testing run:
|
|
261
|
+
* one per finished test case, then the terminal summary. The same data
|
|
262
|
+
* lands accumulated on ExecResult.xctest.
|
|
263
|
+
*/
|
|
264
|
+
onXctestEvent?: (event: XctestEvent) => void;
|
|
237
265
|
};
|
|
238
266
|
|
|
239
267
|
export type ExecResult = {
|
|
@@ -252,6 +280,13 @@ export type ExecResult = {
|
|
|
252
280
|
* without a playstore request, or when the server predates the feature.
|
|
253
281
|
*/
|
|
254
282
|
playstore?: PlaystoreEvent;
|
|
283
|
+
/**
|
|
284
|
+
* XCTest results streamed by a build-for-testing run: every finished case
|
|
285
|
+
* plus the terminal summary. Absent for plain builds and servers that
|
|
286
|
+
* predate the feature. A missing summary means the run died partway; the
|
|
287
|
+
* cases already streamed still stand.
|
|
288
|
+
*/
|
|
289
|
+
xctest?: { cases: XctestCaseEvent[]; summary?: XctestSummaryEvent };
|
|
255
290
|
/**
|
|
256
291
|
* True when the client gave up waiting for the build's event stream. The
|
|
257
292
|
* exit code is fabricated in that case; the remote build may still be
|
|
@@ -268,6 +303,32 @@ export type ExecResult = {
|
|
|
268
303
|
incomplete?: { reason: 'timeout' | 'stream-lost' | 'stream-closed'; message: string };
|
|
269
304
|
};
|
|
270
305
|
|
|
306
|
+
export type XctestCaseEvent = {
|
|
307
|
+
type: 'case';
|
|
308
|
+
/**
|
|
309
|
+
* The test class as XCTest reports it: module-qualified for Swift
|
|
310
|
+
* ('AppTests.LoginTests'), bare for Objective-C.
|
|
311
|
+
*/
|
|
312
|
+
testClass: string;
|
|
313
|
+
method: string;
|
|
314
|
+
passed: boolean;
|
|
315
|
+
durationMs: number;
|
|
316
|
+
/** First failure message of a failed case. */
|
|
317
|
+
failureMessage?: string;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
export type XctestSummaryEvent = {
|
|
321
|
+
type: 'summary';
|
|
322
|
+
passed: number;
|
|
323
|
+
failed: number;
|
|
324
|
+
/** True only once every test in scope ran to completion. */
|
|
325
|
+
planFinished: boolean;
|
|
326
|
+
/** Crash or per-target failure detail when the run degraded. */
|
|
327
|
+
error?: string;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
export type XctestEvent = XctestCaseEvent | XctestSummaryEvent;
|
|
331
|
+
|
|
271
332
|
export type AppStoreEvent = {
|
|
272
333
|
/** 'unknown' means an App Store upload event arrived but its payload was unreadable. */
|
|
273
334
|
state: 'uploading' | 'processing' | 'accepted' | 'failed' | 'unknown';
|
|
@@ -389,6 +450,13 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
389
450
|
private detached = false;
|
|
390
451
|
private appStoreEvent: AppStoreEvent | null = null;
|
|
391
452
|
private playstoreEvent: PlaystoreEvent | null = null;
|
|
453
|
+
private xctestCases: XctestCaseEvent[] = [];
|
|
454
|
+
private xctestSummary: XctestSummaryEvent | null = null;
|
|
455
|
+
// Highest server-assigned event id already dispatched. The server numbers
|
|
456
|
+
// every frame and replays the stream from the start on reconnect; skipping
|
|
457
|
+
// ids at or below this mark drops the replayed prefix for every event type
|
|
458
|
+
// at once, and stays correct if the server ever honors Last-Event-ID.
|
|
459
|
+
private lastSeenEventId = -1;
|
|
392
460
|
private readonly options: ExecOptions;
|
|
393
461
|
private readonly log: (level: 'debug' | 'info' | 'warn' | 'error', msg: string) => void;
|
|
394
462
|
|
|
@@ -555,7 +623,18 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
555
623
|
let timeoutMs = 3600 * 1000;
|
|
556
624
|
if (request.command === 'xcodebuild' && request.testflight) {
|
|
557
625
|
timeoutMs += (Math.max(0, request.testflight.waitTimeoutSeconds ?? 0) + 900) * 1000;
|
|
558
|
-
}
|
|
626
|
+
}
|
|
627
|
+
if (request.command === 'xcodebuild' && request.xcodebuild?.action === 'build-for-testing') {
|
|
628
|
+
// The test run happens after the build: products sync plus the suite,
|
|
629
|
+
// which the server caps at an hour per target. Two hours cover the
|
|
630
|
+
// common unit-plus-UI shape; the client cannot know the target count, so
|
|
631
|
+
// schemes with more targets may need a liveness-based deadline instead.
|
|
632
|
+
// Kept even with runTests: false: servers without run suppression still
|
|
633
|
+
// execute the suite, and the cap is an upper bound so a pure build
|
|
634
|
+
// finishes early regardless.
|
|
635
|
+
timeoutMs += 2 * 3600 * 1000;
|
|
636
|
+
}
|
|
637
|
+
if (request.command === 'run') {
|
|
559
638
|
timeoutMs = (Math.max(1, request.timeoutSeconds ?? 3600) + 60) * 1000;
|
|
560
639
|
}
|
|
561
640
|
let exitCode: number;
|
|
@@ -629,6 +708,14 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
629
708
|
...('additionalMetadata' in request ? request.additionalMetadata ?? {} : {}),
|
|
630
709
|
...(this.appStoreEvent ? { appstore: this.appStoreEvent } : {}),
|
|
631
710
|
...(this.playstoreEvent ? { playstore: this.playstoreEvent } : {}),
|
|
711
|
+
...(this.xctestCases.length > 0 || this.xctestSummary ?
|
|
712
|
+
{
|
|
713
|
+
xctest: {
|
|
714
|
+
cases: this.xctestCases,
|
|
715
|
+
...(this.xctestSummary ? { summary: this.xctestSummary } : {}),
|
|
716
|
+
},
|
|
717
|
+
}
|
|
718
|
+
: {}),
|
|
632
719
|
...(timedOut ? { timedOut } : {}),
|
|
633
720
|
...(incomplete ? { incomplete } : {}),
|
|
634
721
|
};
|
|
@@ -768,6 +855,15 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
768
855
|
this.streamDeadSince = 0;
|
|
769
856
|
proofOfLifeThisCycle = true;
|
|
770
857
|
lastStreamError = undefined;
|
|
858
|
+
const eventId = message.id !== undefined ? parseInt(message.id, 10) : NaN;
|
|
859
|
+
if (!Number.isNaN(eventId)) {
|
|
860
|
+
if (eventId <= this.lastSeenEventId) {
|
|
861
|
+
// A reconnect replays the stream from the start; this frame was
|
|
862
|
+
// already dispatched on an earlier connection.
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
this.lastSeenEventId = eventId;
|
|
866
|
+
}
|
|
771
867
|
const data = typeof message.data === 'string' ? message.data : String(message.data ?? '');
|
|
772
868
|
const eventType = message.event;
|
|
773
869
|
if (eventType === 'command') {
|
|
@@ -795,6 +891,33 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
795
891
|
this.playstoreEvent = { state: 'unknown' };
|
|
796
892
|
this.log('warn', `SSE playstore event has invalid data: ${data}`);
|
|
797
893
|
}
|
|
894
|
+
} else if (eventType === 'xctest') {
|
|
895
|
+
let event: XctestEvent | undefined;
|
|
896
|
+
try {
|
|
897
|
+
const parsed = JSON.parse(data) as XctestEvent;
|
|
898
|
+
if (parsed?.type === 'case') {
|
|
899
|
+
this.xctestCases.push(parsed);
|
|
900
|
+
event = parsed;
|
|
901
|
+
} else if (parsed?.type === 'summary') {
|
|
902
|
+
this.xctestSummary = parsed;
|
|
903
|
+
event = parsed;
|
|
904
|
+
} else {
|
|
905
|
+
// The simulator owns the frame vocabulary and may grow it; the
|
|
906
|
+
// callback's contract is cases and summaries only.
|
|
907
|
+
this.log('debug', `SSE xctest event of unknown type ignored: ${data}`);
|
|
908
|
+
}
|
|
909
|
+
} catch {
|
|
910
|
+
this.log('warn', `SSE xctest event has invalid data: ${data}`);
|
|
911
|
+
}
|
|
912
|
+
if (event) {
|
|
913
|
+
try {
|
|
914
|
+
this.options.onXctestEvent?.(event);
|
|
915
|
+
} catch (err) {
|
|
916
|
+
// The consumer's callback, not the frame: the run must not be
|
|
917
|
+
// derailed and the log must not blame the payload.
|
|
918
|
+
this.log('warn', `onXctestEvent callback threw: ${String(err)}`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
798
921
|
} else if (eventType === 'exitCode') {
|
|
799
922
|
const exitCode = parseInt(data, 10);
|
|
800
923
|
if (Number.isNaN(exitCode)) {
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type ExecChildProcess,
|
|
21
21
|
type ExecRequest,
|
|
22
22
|
type WebhookConfig,
|
|
23
|
+
type XctestEvent,
|
|
23
24
|
} from '../exec-client';
|
|
24
25
|
|
|
25
26
|
export type { AppStoreUploadConfig, WebhookConfig } from '../exec-client';
|
|
@@ -117,6 +118,33 @@ export type XcodeProjectConfig = {
|
|
|
117
118
|
* discovery picks the wrong bundle.
|
|
118
119
|
*/
|
|
119
120
|
artifactName?: string;
|
|
121
|
+
/**
|
|
122
|
+
* xcodebuild action. 'build' (default) produces one .app; with a simulator
|
|
123
|
+
* attached it is installed there. 'build-for-testing' compiles the scheme's
|
|
124
|
+
* test targets and, with a simulator attached, runs them there: per-case
|
|
125
|
+
* results stream through XcodeBuildOptions.onXctestEvent and accumulate on
|
|
126
|
+
* ExecResult.xctest, and the exec exits non-zero when tests fail. Requires
|
|
127
|
+
* the iphonesimulator sdk. Older limbuild servers reject the field.
|
|
128
|
+
*/
|
|
129
|
+
action?: 'build' | 'build-for-testing';
|
|
130
|
+
/**
|
|
131
|
+
* Run only these tests, in xcodebuild's -only-testing format:
|
|
132
|
+
* Target[/Class[/method]]. A bare target runs that target unfiltered;
|
|
133
|
+
* targets named by no entry are skipped. Mutually exclusive with
|
|
134
|
+
* skipTesting; only valid with action 'build-for-testing'.
|
|
135
|
+
*/
|
|
136
|
+
onlyTesting?: string[];
|
|
137
|
+
/**
|
|
138
|
+
* Skip these tests, in xcodebuild's -skip-testing format. A bare target
|
|
139
|
+
* skips that whole target.
|
|
140
|
+
*/
|
|
141
|
+
skipTesting?: string[];
|
|
142
|
+
/**
|
|
143
|
+
* When false, 'build-for-testing' compiles the test targets but does not
|
|
144
|
+
* run them even when a simulator is attached. Servers without support
|
|
145
|
+
* ignore the field and still run the tests.
|
|
146
|
+
*/
|
|
147
|
+
runTests?: boolean;
|
|
120
148
|
};
|
|
121
149
|
|
|
122
150
|
export type XcodeGenConfig = {
|
|
@@ -220,7 +248,8 @@ export type XcodeBuildOptions = {
|
|
|
220
248
|
cloudSigning?: XcodeCloudSigningConfig;
|
|
221
249
|
/**
|
|
222
250
|
* Upload the signed device IPA to App Store Connect after the build.
|
|
223
|
-
* Requires manual or cloud signing
|
|
251
|
+
* Requires manual or cloud signing; the upload itself supports only
|
|
252
|
+
* sdk=iphoneos builds. Check ExecResult.appstore on
|
|
224
253
|
* completion; it is absent when the instance's limbuild predates the
|
|
225
254
|
* feature (old servers silently ignore this option).
|
|
226
255
|
*/
|
|
@@ -259,6 +288,12 @@ export type XcodeBuildOptions = {
|
|
|
259
288
|
* build. Older limbuild servers silently ignore this option.
|
|
260
289
|
*/
|
|
261
290
|
webhook?: WebhookConfig;
|
|
291
|
+
/**
|
|
292
|
+
* Called for each XCTest event streamed by a build-for-testing run: one per
|
|
293
|
+
* finished test case, then the terminal summary. The same data accumulates
|
|
294
|
+
* on ExecResult.xctest.
|
|
295
|
+
*/
|
|
296
|
+
onXctestEvent?: (event: XctestEvent) => void;
|
|
262
297
|
};
|
|
263
298
|
|
|
264
299
|
export type SimulatorInstallState =
|
|
@@ -842,6 +877,25 @@ export class XcodeInstances extends GeneratedXcodeInstances {
|
|
|
842
877
|
if (options?.reactNative?.devServerURL && settings?.configuration === 'Release') {
|
|
843
878
|
throw new Error('reactNative.devServerURL is only supported for Debug builds');
|
|
844
879
|
}
|
|
880
|
+
if (settings?.onlyTesting?.length && settings?.skipTesting?.length) {
|
|
881
|
+
throw new Error('onlyTesting and skipTesting are mutually exclusive; pass one');
|
|
882
|
+
}
|
|
883
|
+
if (
|
|
884
|
+
(settings?.onlyTesting?.length || settings?.skipTesting?.length) &&
|
|
885
|
+
settings?.action !== 'build-for-testing'
|
|
886
|
+
) {
|
|
887
|
+
throw new Error("onlyTesting/skipTesting require action: 'build-for-testing'");
|
|
888
|
+
}
|
|
889
|
+
if (settings?.runTests === false && settings?.action !== 'build-for-testing') {
|
|
890
|
+
throw new Error("runTests: false requires action: 'build-for-testing'");
|
|
891
|
+
}
|
|
892
|
+
if (
|
|
893
|
+
settings?.action === 'build-for-testing' &&
|
|
894
|
+
settings.sdk !== undefined &&
|
|
895
|
+
settings.sdk !== 'iphonesimulator'
|
|
896
|
+
) {
|
|
897
|
+
throw new Error("action: 'build-for-testing' requires the iphonesimulator sdk");
|
|
898
|
+
}
|
|
845
899
|
if (options?.buildSettings) {
|
|
846
900
|
validateBuildSettings(options.buildSettings);
|
|
847
901
|
}
|
|
@@ -861,6 +915,16 @@ export class XcodeInstances extends GeneratedXcodeInstances {
|
|
|
861
915
|
...(options?.webhook && { webhook: options.webhook }),
|
|
862
916
|
};
|
|
863
917
|
|
|
918
|
+
// One options object for both exec call sites, so a future per-exec
|
|
919
|
+
// option cannot be added to one branch and silently dropped from the
|
|
920
|
+
// other.
|
|
921
|
+
const execOptions = {
|
|
922
|
+
apiUrl,
|
|
923
|
+
token,
|
|
924
|
+
log,
|
|
925
|
+
...(options?.onXctestEvent && { onXctestEvent: options.onXctestEvent }),
|
|
926
|
+
};
|
|
927
|
+
|
|
864
928
|
if (options?.upload && 'assetName' in options.upload) {
|
|
865
929
|
const requestPromise = mintAssetUploadUrls(
|
|
866
930
|
client.assets,
|
|
@@ -874,14 +938,14 @@ export class XcodeInstances extends GeneratedXcodeInstances {
|
|
|
874
938
|
request.additionalMetadata = { signedDownloadUrl: asset.signedDownloadUrl };
|
|
875
939
|
return request;
|
|
876
940
|
});
|
|
877
|
-
return exec(requestPromise,
|
|
941
|
+
return exec(requestPromise, execOptions);
|
|
878
942
|
}
|
|
879
943
|
|
|
880
944
|
if (options?.upload && 'signedUploadUrl' in options.upload) {
|
|
881
945
|
request.signedUploadUrl = options.upload.signedUploadUrl;
|
|
882
946
|
}
|
|
883
947
|
|
|
884
|
-
return exec(request,
|
|
948
|
+
return exec(request, execOptions);
|
|
885
949
|
},
|
|
886
950
|
|
|
887
951
|
run(commandLine: string, options?: XcodeRunOptions): ExecChildProcess {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.48.
|
|
1
|
+
export const VERSION = '0.48.2'; // x-release-please-version
|
package/version.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.48.
|
|
1
|
+
export declare const VERSION = "0.48.2";
|
|
2
2
|
//# sourceMappingURL=version.d.mts.map
|
package/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.48.
|
|
1
|
+
export declare const VERSION = "0.48.2";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/version.js
CHANGED
package/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '0.48.
|
|
1
|
+
export const VERSION = '0.48.2'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.mjs.map
|