@limrun/api 0.39.0 → 0.39.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.
@@ -30,6 +30,7 @@ export type ExecRequest = {
30
30
  certificatePassword?: string;
31
31
  provisioningProfileBase64?: string;
32
32
  };
33
+ testflight?: TestflightUploadConfig;
33
34
  buildSettings?: Record<string, string>;
34
35
  signedUploadUrl?: string;
35
36
  additionalMetadata?: {
@@ -48,6 +49,40 @@ export type ExecResult = {
48
49
  execId: string;
49
50
  status: 'SUCCEEDED' | 'FAILED' | 'CANCELLED';
50
51
  signedDownloadUrl?: string;
52
+ /**
53
+ * Last TestFlight state streamed by the server. Absent when the build ran
54
+ * without a testflight request, or when the server predates the feature.
55
+ */
56
+ testflight?: TestflightEvent;
57
+ /**
58
+ * True when the client gave up waiting for the build's event stream. The
59
+ * exit code is fabricated in that case; the remote build may still be
60
+ * running and may yet succeed.
61
+ */
62
+ timedOut?: boolean;
63
+ };
64
+
65
+ export type TestflightEvent = {
66
+ /** 'unknown' means a testflight event arrived but its payload was unreadable. */
67
+ state: 'uploading' | 'processing' | 'accepted' | 'failed' | 'unknown';
68
+ uploadId?: string;
69
+ buildId?: string;
70
+ };
71
+
72
+ export type TestflightUploadConfig = {
73
+ /** App Store Connect API key ID, e.g. 2X9R4HXF34. */
74
+ apiKeyId: string;
75
+ /** Issuer ID for team API keys. Omit for individual API keys. */
76
+ apiIssuerId?: string;
77
+ /** Base64-encoded content of the .p8 private key file. */
78
+ apiPrivateKeyBase64: string;
79
+ /**
80
+ * How long the server watches for App Store Connect's processing verdict
81
+ * after the upload. A FAILED verdict within the window fails the build;
82
+ * expiry without a verdict succeeds with the build still processing on
83
+ * Apple's side. 0 skips the watch. Defaults to 120.
84
+ */
85
+ waitTimeoutSeconds?: number;
51
86
  };
52
87
 
53
88
  type DataListener = (chunk: string) => void;
@@ -121,6 +156,7 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
121
156
  private abortController = new AbortController();
122
157
  private sseConnection: EventSourceClient | null = null;
123
158
  private killed = false;
159
+ private testflightEvent: TestflightEvent | null = null;
124
160
  private readonly options: ExecOptions;
125
161
  private readonly log: (level: 'debug' | 'info' | 'warn' | 'error', msg: string) => void;
126
162
 
@@ -228,8 +264,15 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
228
264
  // 2. Stream logs via SSE and wait for exit code
229
265
  const eventsUrl = `${apiUrl}/exec/${this.execId}/events`;
230
266
 
231
- const timeoutMs = 3600 * 1000; // 1 hour max
267
+ // 1 hour max for the build itself; a TestFlight request extends the
268
+ // budget by its server-side verdict watch plus upload headroom so a long
269
+ // build is not force-failed client-side while the server still succeeds.
270
+ let timeoutMs = 3600 * 1000;
271
+ if (request.testflight) {
272
+ timeoutMs += (Math.max(0, request.testflight.waitTimeoutSeconds ?? 120) + 900) * 1000;
273
+ }
232
274
  let exitCode: number;
275
+ let timedOut = false;
233
276
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
234
277
  try {
235
278
  exitCode = await Promise.race([
@@ -243,8 +286,11 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
243
286
  log('debug', 'Build killed');
244
287
  exitCode = -1;
245
288
  } else {
289
+ // The client stopped waiting; the remote build may still be running.
290
+ // The fabricated exit code must not read as a build failure.
246
291
  log('warn', 'SSE completion timeout');
247
292
  exitCode = 1;
293
+ timedOut = true;
248
294
  }
249
295
  } finally {
250
296
  clearTimeout(timeoutId);
@@ -275,6 +321,8 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
275
321
  execId: this.execId!,
276
322
  status,
277
323
  ...(request.additionalMetadata ?? {}),
324
+ ...(this.testflightEvent ? { testflight: this.testflightEvent } : {}),
325
+ ...(timedOut ? { timedOut } : {}),
278
326
  };
279
327
 
280
328
  this.log('debug', `Build finished: ${result.status} (exit ${result.exitCode})`);
@@ -307,6 +355,15 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
307
355
  this.stdout.emit('data', data);
308
356
  } else if (eventType === 'stderr') {
309
357
  this.stderr.emit('data', data);
358
+ } else if (eventType === 'testflight') {
359
+ try {
360
+ this.testflightEvent = JSON.parse(data) as TestflightEvent;
361
+ } catch {
362
+ // The event itself proves the server ran the TestFlight step,
363
+ // so never let a payload glitch look like a missing feature.
364
+ this.testflightEvent = { state: 'unknown' };
365
+ this.log('warn', `SSE testflight event has invalid data: ${data}`);
366
+ }
310
367
  } else if (eventType === 'exitCode') {
311
368
  const exitCode = parseInt(data, 10);
312
369
  if (Number.isNaN(exitCode)) {
@@ -59,6 +59,12 @@ export type FolderSyncOptions = {
59
59
  export type SyncFolderResult = {
60
60
  installedAppPath?: string;
61
61
  installedBundleId?: string;
62
+ /**
63
+ * Number of bytes transmitted to the server for this sync (full uploads plus
64
+ * delta patches, before transport compression). In watch mode, this reflects
65
+ * the initial sync only.
66
+ */
67
+ bytesSent?: number;
62
68
  /** Present only when watch=true; call to stop watching. */
63
69
  stopWatching?: () => Promise<void>;
64
70
  };
@@ -703,6 +709,7 @@ async function syncFolderOnce(
703
709
  },
704
710
  filePath: entry.absPath,
705
711
  });
712
+ bytesSentFull += entry.size;
706
713
  }
707
714
  if (retryPayloads.length > 0) {
708
715
  slog('debug', `server requested full for ${retryPayloads.length} files; retrying once`);
@@ -747,7 +754,7 @@ async function syncFolderOnce(
747
754
  syncWorkMs,
748
755
  )} total=${fmtMs(tookMs)}`,
749
756
  );
750
- const out: SyncFolderResult = {};
757
+ const out: SyncFolderResult = { bytesSent: totalBytes };
751
758
  if (resp.installedAppPath) {
752
759
  out.installedAppPath = resp.installedAppPath;
753
760
  }
package/src/index.ts CHANGED
@@ -16,6 +16,8 @@ export {
16
16
  type ExecOptions,
17
17
  type ExecResult,
18
18
  type ExecChildProcess,
19
+ type TestflightEvent,
20
+ type TestflightUploadConfig,
19
21
  } from './exec-client';
20
22
  export {
21
23
  type XcodeCreateClientParams,
@@ -4,7 +4,9 @@ import crypto from 'crypto';
4
4
 
5
5
  import { XcodeInstances as GeneratedXcodeInstances, type XcodeInstance } from './xcode-instances';
6
6
  import { type IosInstance } from './ios-instances';
7
- import { exec, type ExecChildProcess, type ExecRequest } from '../exec-client';
7
+ import { exec, type ExecChildProcess, type ExecRequest, type TestflightUploadConfig } from '../exec-client';
8
+
9
+ export type { TestflightUploadConfig } from '../exec-client';
8
10
  import {
9
11
  syncFolder as syncFolderImpl,
10
12
  type AdditionalFileSyncEntry,
@@ -50,6 +52,12 @@ export type SyncOptions = {
50
52
  };
51
53
 
52
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;
53
61
  /** Present only when watch=true; call to stop watching */
54
62
  stopWatching?: () => Promise<void>;
55
63
  };
@@ -93,6 +101,13 @@ export type ReactNativeBuildConfig = {
93
101
  export type XcodeBuildOptions = {
94
102
  upload?: { assetName: string } | { signedUploadUrl: string };
95
103
  signing?: XcodeSigningConfig;
104
+ /**
105
+ * Upload the signed device IPA to TestFlight after the build. Requires
106
+ * signing and sdk=iphoneos. Check ExecResult.testflight on completion: it is
107
+ * absent when the instance's limbuild predates the feature (old servers
108
+ * silently ignore this option).
109
+ */
110
+ testflight?: TestflightUploadConfig;
96
111
  reactNative?: ReactNativeBuildConfig;
97
112
  buildSettings?: Record<string, string>;
98
113
  };
@@ -619,10 +634,14 @@ export class XcodeInstances extends GeneratedXcodeInstances {
619
634
  };
620
635
 
621
636
  const result = await syncFolderImpl(localCodePath, codeSyncOpts);
637
+ const out: SyncResult = {};
638
+ if (result.bytesSent !== undefined) {
639
+ out.bytesSent = result.bytesSent;
640
+ }
622
641
  if (result.stopWatching) {
623
- return { stopWatching: result.stopWatching };
642
+ out.stopWatching = result.stopWatching;
624
643
  }
625
- return {};
644
+ return out;
626
645
  },
627
646
 
628
647
  xcodebuild(settings?: XcodeProjectConfig, options?: XcodeBuildOptions): ExecChildProcess {
@@ -637,6 +656,7 @@ export class XcodeInstances extends GeneratedXcodeInstances {
637
656
  ...(settings && { xcodebuild: settings }),
638
657
  ...(options?.reactNative && { reactNative: options.reactNative }),
639
658
  ...(options?.signing && { signing: options.signing }),
659
+ ...(options?.testflight && { testflight: options.testflight }),
640
660
  ...(options?.buildSettings && { buildSettings: options.buildSettings }),
641
661
  };
642
662
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.39.0'; // x-release-please-version
1
+ export const VERSION = '0.39.2'; // x-release-please-version
package/version.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.39.0";
1
+ export declare const VERSION = "0.39.2";
2
2
  //# sourceMappingURL=version.d.mts.map
package/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.39.0";
1
+ export declare const VERSION = "0.39.2";
2
2
  //# sourceMappingURL=version.d.ts.map
package/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = '0.39.0'; // x-release-please-version
4
+ exports.VERSION = '0.39.2'; // x-release-please-version
5
5
  //# sourceMappingURL=version.js.map
package/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.39.0'; // x-release-please-version
1
+ export const VERSION = '0.39.2'; // x-release-please-version
2
2
  //# sourceMappingURL=version.mjs.map