@limrun/api 0.46.8 → 0.46.10
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 +46 -1
- package/exec-client.d.mts.map +1 -1
- package/exec-client.d.ts +46 -1
- package/exec-client.d.ts.map +1 -1
- package/exec-client.js +215 -69
- package/exec-client.js.map +1 -1
- package/exec-client.mjs +212 -68
- package/exec-client.mjs.map +1 -1
- package/index.d.mts +1 -0
- package/index.d.mts.map +1 -1
- package/index.d.ts +1 -0
- 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/assets-helpers.d.mts +7 -0
- package/resources/assets-helpers.d.mts.map +1 -1
- package/resources/assets-helpers.d.ts +7 -0
- package/resources/assets-helpers.d.ts.map +1 -1
- package/resources/assets-helpers.js +1 -0
- package/resources/assets-helpers.js.map +1 -1
- package/resources/assets-helpers.mjs +1 -0
- package/resources/assets-helpers.mjs.map +1 -1
- package/resources/assets.d.mts +26 -0
- package/resources/assets.d.mts.map +1 -1
- package/resources/assets.d.ts +26 -0
- package/resources/assets.d.ts.map +1 -1
- package/resources/daemon-client-shared.d.mts +20 -2
- package/resources/daemon-client-shared.d.mts.map +1 -1
- package/resources/daemon-client-shared.d.ts +20 -2
- package/resources/daemon-client-shared.d.ts.map +1 -1
- package/resources/daemon-client-shared.js +2 -2
- package/resources/daemon-client-shared.js.map +1 -1
- package/resources/daemon-client-shared.mjs +2 -2
- package/resources/daemon-client-shared.mjs.map +1 -1
- package/resources/xcode-instances-helpers.d.mts +8 -1
- package/resources/xcode-instances-helpers.d.mts.map +1 -1
- package/resources/xcode-instances-helpers.d.ts +8 -1
- package/resources/xcode-instances-helpers.d.ts.map +1 -1
- package/resources/xcode-instances-helpers.js +3 -1
- package/resources/xcode-instances-helpers.js.map +1 -1
- package/resources/xcode-instances-helpers.mjs +3 -1
- package/resources/xcode-instances-helpers.mjs.map +1 -1
- package/src/exec-client.ts +231 -65
- package/src/index.ts +1 -0
- package/src/resources/assets-helpers.ts +9 -0
- package/src/resources/assets.ts +31 -0
- package/src/resources/daemon-client-shared.ts +24 -3
- package/src/resources/xcode-instances-helpers.ts +25 -8
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.mts.map +1 -1
- package/version.d.ts +1 -1
- package/version.d.ts.map +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
- package/version.mjs +1 -1
- package/version.mjs.map +1 -1
package/src/exec-client.ts
CHANGED
|
@@ -7,6 +7,34 @@
|
|
|
7
7
|
|
|
8
8
|
import { createEventSource, type EventSourceClient, type EventSourceMessage } from 'eventsource-client';
|
|
9
9
|
import { nodeProxyTransport } from './internal/proxy-transport';
|
|
10
|
+
import { sseFetch } from './internal/sse-fetch';
|
|
11
|
+
import type { Fetch } from './internal/builtin-types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Give-up policy for the exec event stream. The EventSource reconnects
|
|
15
|
+
* forever on its own, so a dead exec (deleted instance, expired record)
|
|
16
|
+
* would otherwise retry until the one-hour completion timeout. Proof of
|
|
17
|
+
* life resets the clock: a delivered event, or a clean connection that
|
|
18
|
+
* stays open healthyConnectionMs (long compiles stream nothing for
|
|
19
|
+
* minutes while intermediaries kill idle connections). The window is
|
|
20
|
+
* wide enough to ride out real transient outages (an ingress redeploy, a
|
|
21
|
+
* VPN reconnect); tightening it trades outage tolerance for faster
|
|
22
|
+
* dead-exec detection. It is measured in time, not attempts: the server
|
|
23
|
+
* steers the retry delay through SSE `retry:` frames, so an attempt
|
|
24
|
+
* count would put the give-up horizon under remote control.
|
|
25
|
+
*
|
|
26
|
+
* @internal Mutable for tests only.
|
|
27
|
+
*/
|
|
28
|
+
export const sseStreamPolicy = {
|
|
29
|
+
healthyConnectionMs: 15_000,
|
|
30
|
+
giveUpAfterMs: 300_000,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** The event stream kept failing past the give-up window; remote state unknown. */
|
|
34
|
+
export class ExecStreamLostError extends Error {}
|
|
35
|
+
|
|
36
|
+
/** The server closed the event stream for good (HTTP 204). */
|
|
37
|
+
export class ExecStreamClosedError extends Error {}
|
|
10
38
|
|
|
11
39
|
// =============================================================================
|
|
12
40
|
// Types
|
|
@@ -50,6 +78,13 @@ export type XcodeBuildExecRequest = {
|
|
|
50
78
|
buildSettings?: Record<string, string>;
|
|
51
79
|
gitInit?: boolean;
|
|
52
80
|
signedUploadUrl?: string;
|
|
81
|
+
/**
|
|
82
|
+
* ID of the Limrun asset signedUploadUrl targets, when it was minted from
|
|
83
|
+
* one. Lets limbuild record the built app's metadata (title, bundle
|
|
84
|
+
* identifier, versions, deep link scheme, icon) on the asset, which the
|
|
85
|
+
* registry's OTA install flow reads. Older limbuild servers ignore it.
|
|
86
|
+
*/
|
|
87
|
+
assetId?: string;
|
|
53
88
|
webhook?: WebhookConfig;
|
|
54
89
|
additionalMetadata?: {
|
|
55
90
|
signedDownloadUrl?: string;
|
|
@@ -211,6 +246,14 @@ export type ExecResult = {
|
|
|
211
246
|
* running and may yet succeed.
|
|
212
247
|
*/
|
|
213
248
|
timedOut?: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* Why the client gave up, present exactly when timedOut is true.
|
|
251
|
+
* 'timeout' is the completion budget expiring on a live stream;
|
|
252
|
+
* 'stream-lost' means the event stream kept failing (the execution may
|
|
253
|
+
* no longer exist); 'stream-closed' means the server ended the stream
|
|
254
|
+
* for good.
|
|
255
|
+
*/
|
|
256
|
+
incomplete?: { reason: 'timeout' | 'stream-lost' | 'stream-closed'; message: string };
|
|
214
257
|
};
|
|
215
258
|
|
|
216
259
|
export type AppStoreEvent = {
|
|
@@ -327,6 +370,10 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
327
370
|
private abortController = new AbortController();
|
|
328
371
|
private sseConnection: EventSourceClient | null = null;
|
|
329
372
|
private killed = false;
|
|
373
|
+
// Start of the stream's current unbroken failure streak, 0 while
|
|
374
|
+
// healthy. run() reads it to classify a completion timeout that
|
|
375
|
+
// expired while the stream was already dead.
|
|
376
|
+
private streamDeadSince = 0;
|
|
330
377
|
private detached = false;
|
|
331
378
|
private appStoreEvent: AppStoreEvent | null = null;
|
|
332
379
|
private playstoreEvent: PlaystoreEvent | null = null;
|
|
@@ -501,22 +548,41 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
501
548
|
}
|
|
502
549
|
let exitCode: number;
|
|
503
550
|
let timedOut = false;
|
|
551
|
+
let incomplete: ExecResult['incomplete'];
|
|
504
552
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
505
553
|
try {
|
|
506
554
|
exitCode = await Promise.race([
|
|
507
555
|
this.connectSSE(eventsUrl),
|
|
508
556
|
new Promise<never>((_, reject) => {
|
|
509
|
-
timeoutId = setTimeout(() => reject(new Error('SSE timeout')), timeoutMs);
|
|
557
|
+
timeoutId = setTimeout(() => reject(new Error('SSE completion timeout')), timeoutMs);
|
|
510
558
|
}),
|
|
511
559
|
]);
|
|
512
|
-
} catch {
|
|
560
|
+
} catch (err) {
|
|
513
561
|
if (this.killed) {
|
|
514
562
|
log('debug', 'Execution killed');
|
|
515
563
|
exitCode = -1;
|
|
516
564
|
} else {
|
|
517
|
-
// The client stopped waiting; the
|
|
518
|
-
//
|
|
519
|
-
|
|
565
|
+
// The client stopped waiting; the fabricated exit code must not
|
|
566
|
+
// read as a build failure. The structured reason lets callers give
|
|
567
|
+
// the right advice: a dead stream means the execution may be gone,
|
|
568
|
+
// while a genuine timeout means it may still be running. A budget
|
|
569
|
+
// that expires mid-failure-streak (short run timeouts undercut the
|
|
570
|
+
// stream's own give-up window) counts as a stream problem too.
|
|
571
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
572
|
+
const streakMs = this.streamDeadSince > 0 ? Date.now() - this.streamDeadSince : 0;
|
|
573
|
+
let reason: NonNullable<ExecResult['incomplete']>['reason'] =
|
|
574
|
+
err instanceof ExecStreamClosedError ? 'stream-closed'
|
|
575
|
+
: err instanceof ExecStreamLostError ? 'stream-lost'
|
|
576
|
+
: 'timeout';
|
|
577
|
+
if (reason === 'timeout' && streakMs > 0) {
|
|
578
|
+
reason = 'stream-lost';
|
|
579
|
+
message =
|
|
580
|
+
`the completion budget expired while the event stream had already been failing ` +
|
|
581
|
+
`for ${Math.round(streakMs / 1000)}s; the execution may no longer exist ` +
|
|
582
|
+
`(instance deleted or record expired)`;
|
|
583
|
+
}
|
|
584
|
+
incomplete = { reason, message };
|
|
585
|
+
log('warn', message);
|
|
520
586
|
exitCode = 1;
|
|
521
587
|
timedOut = true;
|
|
522
588
|
}
|
|
@@ -552,6 +618,7 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
552
618
|
...(this.appStoreEvent ? { appstore: this.appStoreEvent } : {}),
|
|
553
619
|
...(this.playstoreEvent ? { playstore: this.playstoreEvent } : {}),
|
|
554
620
|
...(timedOut ? { timedOut } : {}),
|
|
621
|
+
...(incomplete ? { incomplete } : {}),
|
|
555
622
|
};
|
|
556
623
|
|
|
557
624
|
this.log('debug', `Execution finished: ${result.status} (exit ${result.exitCode})`);
|
|
@@ -561,75 +628,174 @@ export class ExecChildProcess implements PromiseLike<ExecResult> {
|
|
|
561
628
|
/**
|
|
562
629
|
* Opens an SSE connection and routes streamed events to the exposed command/stdout/stderr streams.
|
|
563
630
|
* Resolves with the exit code when an 'exitCode' event arrives.
|
|
564
|
-
* Rejects when the abort signal fires (kill or cleanup)
|
|
631
|
+
* Rejects when the abort signal fires (kill or cleanup), when the server
|
|
632
|
+
* closes the stream for good, or when the give-up clock runs out.
|
|
565
633
|
*/
|
|
566
634
|
private connectSSE(eventsUrl: string): Promise<number> {
|
|
567
635
|
return new Promise<number>((resolve, reject) => {
|
|
568
|
-
|
|
636
|
+
const signal = this.abortController.signal;
|
|
637
|
+
if (signal.aborted) {
|
|
569
638
|
reject(new Error('killed'));
|
|
570
639
|
return;
|
|
571
640
|
}
|
|
572
641
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
642
|
+
// Settle-once pair. Both paths close the source: the EventSource
|
|
643
|
+
// reconnects on its own otherwise, even after the promise is done.
|
|
644
|
+
let settled = false;
|
|
645
|
+
let connectedAt = 0;
|
|
646
|
+
let lastCycleAt = 0;
|
|
647
|
+
let lastCycleMono = 0;
|
|
648
|
+
let proofOfLifeThisCycle = false;
|
|
649
|
+
let cleanResponseThisCycle = false;
|
|
650
|
+
let lastStreamError: Error | undefined;
|
|
651
|
+
const cleanup = () => {
|
|
652
|
+
eventSource.close();
|
|
653
|
+
signal.removeEventListener('abort', onAbort);
|
|
654
|
+
};
|
|
655
|
+
const succeed = (exitCode: number) => {
|
|
656
|
+
if (settled) return;
|
|
657
|
+
settled = true;
|
|
658
|
+
cleanup();
|
|
659
|
+
resolve(exitCode);
|
|
660
|
+
};
|
|
661
|
+
const fail = (error: Error) => {
|
|
662
|
+
if (settled) return;
|
|
663
|
+
settled = true;
|
|
664
|
+
cleanup();
|
|
665
|
+
reject(error);
|
|
666
|
+
};
|
|
667
|
+
const onAbort = () => fail(new Error('killed'));
|
|
668
|
+
|
|
669
|
+
// Stream health is judged at the fetch surface, where the response
|
|
670
|
+
// status is visible; the library fires onConnect for error responses
|
|
671
|
+
// too, so an error page held open must not read as a healthy
|
|
672
|
+
// connection. An HTTP 204 makes the EventSource close for good
|
|
673
|
+
// without reporting anything, so it must fail here. A rejected fetch
|
|
674
|
+
// never reaches onDisconnect (the hazard sseFetch exists for):
|
|
675
|
+
// capture it for the give-up message and let the clock decide, so
|
|
676
|
+
// one transient refusal does not kill a live build.
|
|
677
|
+
const fetchWithStreamPolicy: Fetch = async (input, init) => {
|
|
678
|
+
const response = await nodeProxyTransport.fetch(input, init);
|
|
679
|
+
if (response.status === 204) {
|
|
680
|
+
fail(new ExecStreamClosedError(`event stream to ${eventsUrl} was closed by the server (HTTP 204)`));
|
|
681
|
+
} else if (!response.ok) {
|
|
682
|
+
lastStreamError = new Error(`server answered HTTP ${response.status}`);
|
|
683
|
+
}
|
|
684
|
+
cleanResponseThisCycle = response.ok;
|
|
685
|
+
return response;
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
const eventSource = createEventSource({
|
|
689
|
+
url: eventsUrl,
|
|
690
|
+
fetch: sseFetch(fetchWithStreamPolicy, (err) => {
|
|
691
|
+
lastStreamError = err instanceof Error ? err : new Error(String(err));
|
|
692
|
+
}),
|
|
693
|
+
headers: { Authorization: `Bearer ${this.options.token}` },
|
|
694
|
+
onConnect: () => {
|
|
695
|
+
connectedAt = Date.now();
|
|
696
|
+
},
|
|
697
|
+
// Comments count as proof of life, so a server-side keepalive
|
|
698
|
+
// works without a client change (onMessage never sees them).
|
|
699
|
+
onComment: () => {
|
|
700
|
+
proofOfLifeThisCycle = true;
|
|
701
|
+
},
|
|
702
|
+
// Fires once per broken cycle, before the retry timer is armed, on
|
|
703
|
+
// both failure paths (request rejected, stream ended). This is
|
|
704
|
+
// where the give-up clock runs.
|
|
705
|
+
onScheduleReconnect: () => {
|
|
706
|
+
if (settled || this.killed) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const now = Date.now();
|
|
710
|
+
const mono = performance.now();
|
|
711
|
+
const livedMs = connectedAt > 0 ? now - connectedAt : 0;
|
|
712
|
+
connectedAt = 0;
|
|
713
|
+
// Date.now() is wall clock: a laptop waking from sleep (or a
|
|
714
|
+
// clock step) would arrive with the whole window already
|
|
715
|
+
// "elapsed" and fail on its first attempt. Sleep is the wall
|
|
716
|
+
// clock advancing while the monotonic clock stands still; a
|
|
717
|
+
// large drift between the two restarts the streak. Long server
|
|
718
|
+
// retry delays and hanging connects advance both clocks equally
|
|
719
|
+
// and keep counting.
|
|
720
|
+
if (this.streamDeadSince > 0 && lastCycleAt > 0) {
|
|
721
|
+
const wallGapMs = now - lastCycleAt;
|
|
722
|
+
const monoGapMs = mono - lastCycleMono;
|
|
723
|
+
if (wallGapMs - monoGapMs > 30_000) {
|
|
724
|
+
this.streamDeadSince = now;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
lastCycleAt = now;
|
|
728
|
+
lastCycleMono = mono;
|
|
729
|
+
const healthy =
|
|
730
|
+
proofOfLifeThisCycle ||
|
|
731
|
+
(cleanResponseThisCycle && livedMs >= sseStreamPolicy.healthyConnectionMs);
|
|
732
|
+
proofOfLifeThisCycle = false;
|
|
733
|
+
cleanResponseThisCycle = false;
|
|
734
|
+
if (healthy) {
|
|
735
|
+
this.streamDeadSince = 0;
|
|
736
|
+
lastStreamError = undefined;
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (this.streamDeadSince === 0) {
|
|
740
|
+
this.streamDeadSince = now;
|
|
741
|
+
this.log('warn', 'SSE disconnected; reconnecting');
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (now - this.streamDeadSince >= sseStreamPolicy.giveUpAfterMs) {
|
|
745
|
+
const seconds = Math.round((now - this.streamDeadSince) / 1000);
|
|
746
|
+
const cause = lastStreamError ? `; last error: ${lastStreamError.message}` : '';
|
|
747
|
+
fail(
|
|
748
|
+
new ExecStreamLostError(
|
|
749
|
+
`event stream to ${eventsUrl} kept failing for ${seconds}s without delivering events${cause}; ` +
|
|
750
|
+
'the execution may no longer exist (instance deleted or record expired)',
|
|
751
|
+
),
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
onMessage: (message: EventSourceMessage) => {
|
|
756
|
+
this.streamDeadSince = 0;
|
|
757
|
+
proofOfLifeThisCycle = true;
|
|
758
|
+
lastStreamError = undefined;
|
|
759
|
+
const data = typeof message.data === 'string' ? message.data : String(message.data ?? '');
|
|
760
|
+
const eventType = message.event;
|
|
761
|
+
if (eventType === 'command') {
|
|
762
|
+
this.command.emit('data', data);
|
|
763
|
+
} else if (eventType === 'stdout') {
|
|
764
|
+
this.stdout.emit('data', data);
|
|
765
|
+
} else if (eventType === 'stderr') {
|
|
766
|
+
this.stderr.emit('data', data);
|
|
767
|
+
} else if (eventType === 'testflight') {
|
|
768
|
+
try {
|
|
769
|
+
this.appStoreEvent = JSON.parse(data) as AppStoreEvent;
|
|
770
|
+
} catch {
|
|
771
|
+
// The wire event itself proves the server ran the App Store upload,
|
|
772
|
+
// so never let a payload glitch look like a missing feature.
|
|
773
|
+
this.appStoreEvent = { state: 'unknown' };
|
|
774
|
+
this.log('warn', `SSE testflight event has invalid data: ${data}`);
|
|
614
775
|
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
776
|
+
} else if (eventType === 'playstore') {
|
|
777
|
+
try {
|
|
778
|
+
this.playstoreEvent = JSON.parse(data) as PlaystoreEvent;
|
|
779
|
+
} catch {
|
|
780
|
+
// Same contract as the App Store upload event: its presence proves the server
|
|
781
|
+
// ran the Play Store step, so a payload glitch must not
|
|
782
|
+
// read as a missing feature.
|
|
783
|
+
this.playstoreEvent = { state: 'unknown' };
|
|
784
|
+
this.log('warn', `SSE playstore event has invalid data: ${data}`);
|
|
619
785
|
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}
|
|
786
|
+
} else if (eventType === 'exitCode') {
|
|
787
|
+
const exitCode = parseInt(data, 10);
|
|
788
|
+
if (Number.isNaN(exitCode)) {
|
|
789
|
+
this.log('warn', `SSE exitCode event has invalid data: ${data}`);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
this.log('debug', `Execution completed via SSE: exitCode=${exitCode}`);
|
|
793
|
+
succeed(exitCode);
|
|
794
|
+
}
|
|
795
|
+
},
|
|
796
|
+
});
|
|
797
|
+
this.sseConnection = eventSource;
|
|
798
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
633
799
|
});
|
|
634
800
|
}
|
|
635
801
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { promises as fs } from 'fs';
|
|
|
5
5
|
import { RequestOptions } from '../internal/request-options';
|
|
6
6
|
import { nodeProxyTransport } from '../internal/proxy-transport';
|
|
7
7
|
import { Assets as GeneratedAssets, type AssetKind, type AssetPlatform } from './assets';
|
|
8
|
+
import type { AssetUploadOptions } from './daemon-client-shared';
|
|
8
9
|
|
|
9
10
|
export interface AssetGetOrUploadParams {
|
|
10
11
|
/**
|
|
@@ -40,6 +41,13 @@ export interface AssetGetOrUploadParams {
|
|
|
40
41
|
* and the upload is skipped.
|
|
41
42
|
*/
|
|
42
43
|
onUploadProgress?: (uploadedBytes: number, totalBytes: number) => void;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* App metadata to record on the asset (title, bundle identifier, versions,
|
|
47
|
+
* deep link scheme), for bundles that don't go through limbuild. The
|
|
48
|
+
* registry's OTA install flow reads the manifest identity from it.
|
|
49
|
+
*/
|
|
50
|
+
uploadOptions?: AssetUploadOptions;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
export interface AssetGetOrUploadResponse {
|
|
@@ -94,6 +102,7 @@ export class Assets extends GeneratedAssets {
|
|
|
94
102
|
kind: body.kind ?? 'App',
|
|
95
103
|
...(body.platform && { platform: body.platform }),
|
|
96
104
|
...(body.ttl && { ttl: body.ttl }),
|
|
105
|
+
...body.uploadOptions,
|
|
97
106
|
},
|
|
98
107
|
options,
|
|
99
108
|
);
|
package/src/resources/assets.ts
CHANGED
|
@@ -188,6 +188,37 @@ export interface AssetGetOrCreateParams {
|
|
|
188
188
|
* current expiry unchanged.
|
|
189
189
|
*/
|
|
190
190
|
ttl?: string;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Human-readable app title for the uploaded bundle, as users see it on iOS.
|
|
194
|
+
* Builds through limbuild record it automatically from the bundle's
|
|
195
|
+
* Info.plist; set it here for hand-uploaded bundles.
|
|
196
|
+
*/
|
|
197
|
+
displayName?: string;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* CFBundleIdentifier of the uploaded app bundle. Used by over-the-air
|
|
201
|
+
* installation when present.
|
|
202
|
+
*/
|
|
203
|
+
bundleIdentifier?: string;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* CFBundleShortVersionString of the uploaded app bundle, e.g. 1.2.3.
|
|
207
|
+
*/
|
|
208
|
+
shortVersion?: string;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* CFBundleVersion of the uploaded app bundle, e.g. 42. Used by
|
|
212
|
+
* over-the-air installation when present.
|
|
213
|
+
*/
|
|
214
|
+
buildVersion?: string;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The app's primary URL scheme, e.g. "myapp" for myapp:// deep links.
|
|
218
|
+
* Consumers such as the registry's OTA install page use it to offer an Open
|
|
219
|
+
* button after installation.
|
|
220
|
+
*/
|
|
221
|
+
deeplink?: string;
|
|
191
222
|
}
|
|
192
223
|
|
|
193
224
|
export type AssetKind = 'App' | 'Keychain';
|
|
@@ -68,18 +68,39 @@ export type SyncResult = {
|
|
|
68
68
|
stopWatching?: () => Promise<void>;
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
export type AssetUploadUrls = { signedUploadUrl: string; signedDownloadUrl: string };
|
|
71
|
+
export type AssetUploadUrls = { id: string; signedUploadUrl: string; signedDownloadUrl: string };
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* App metadata recorded on the asset at upload time, for bundles that don't
|
|
75
|
+
* go through limbuild (which extracts and records it automatically). The
|
|
76
|
+
* registry's OTA install flow reads the manifest identity from these fields.
|
|
77
|
+
*/
|
|
78
|
+
export type AssetUploadOptions = {
|
|
79
|
+
/** Human-readable app title users see on iOS. */
|
|
80
|
+
displayName?: string;
|
|
81
|
+
/** CFBundleIdentifier of the uploaded app bundle. */
|
|
82
|
+
bundleIdentifier?: string;
|
|
83
|
+
/** CFBundleShortVersionString, e.g. 1.2.3. */
|
|
84
|
+
shortVersion?: string;
|
|
85
|
+
/** CFBundleVersion, e.g. 42. */
|
|
86
|
+
buildVersion?: string;
|
|
87
|
+
/** The app's primary URL scheme, e.g. "myapp" for myapp:// deep links. */
|
|
88
|
+
deeplink?: string;
|
|
89
|
+
};
|
|
72
90
|
|
|
73
91
|
/**
|
|
74
92
|
* Mints presigned upload/download URLs for a named asset via assets.getOrCreate,
|
|
75
93
|
* wrapping failures with the asset name (and the original error as cause).
|
|
76
94
|
*/
|
|
77
95
|
export function mintAssetUploadUrls(
|
|
78
|
-
assets: {
|
|
96
|
+
assets: {
|
|
97
|
+
getOrCreate: (body: { name: string; ttl?: string } & AssetUploadOptions) => Promise<AssetUploadUrls>;
|
|
98
|
+
},
|
|
79
99
|
name: string,
|
|
80
100
|
ttl?: string,
|
|
101
|
+
uploadOptions?: AssetUploadOptions,
|
|
81
102
|
): Promise<AssetUploadUrls> {
|
|
82
|
-
return assets.getOrCreate({ name, ...(ttl && { ttl }) }).catch((err) => {
|
|
103
|
+
return assets.getOrCreate({ name, ...(ttl && { ttl }), ...uploadOptions }).catch((err) => {
|
|
83
104
|
const message = `Failed to create upload URL for asset '${name}': ${
|
|
84
105
|
err instanceof Error ? err.message : err
|
|
85
106
|
}`;
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
createDaemonLogger,
|
|
34
34
|
deriveBasisCache,
|
|
35
35
|
mintAssetUploadUrls,
|
|
36
|
+
type AssetUploadOptions,
|
|
36
37
|
type BuildLog,
|
|
37
38
|
type LogLevel,
|
|
38
39
|
type SyncResult,
|
|
@@ -175,7 +176,18 @@ export type ReactNativeBuildConfig = {
|
|
|
175
176
|
};
|
|
176
177
|
|
|
177
178
|
export type XcodeBuildOptions = {
|
|
178
|
-
upload?:
|
|
179
|
+
upload?:
|
|
180
|
+
| {
|
|
181
|
+
assetName: string;
|
|
182
|
+
/**
|
|
183
|
+
* App metadata to record on the asset up front. limbuild extracts
|
|
184
|
+
* the same metadata from the built bundle after the build and
|
|
185
|
+
* overwrites whatever it found; values set here survive only for
|
|
186
|
+
* fields the bundle doesn't declare (e.g. a deep link scheme).
|
|
187
|
+
*/
|
|
188
|
+
uploadOptions?: AssetUploadOptions;
|
|
189
|
+
}
|
|
190
|
+
| { signedUploadUrl: string };
|
|
179
191
|
signing?: XcodeSigningConfig;
|
|
180
192
|
cloudSigning?: XcodeCloudSigningConfig;
|
|
181
193
|
/**
|
|
@@ -806,13 +818,18 @@ export class XcodeInstances extends GeneratedXcodeInstances {
|
|
|
806
818
|
};
|
|
807
819
|
|
|
808
820
|
if (options?.upload && 'assetName' in options.upload) {
|
|
809
|
-
const requestPromise = mintAssetUploadUrls(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
821
|
+
const requestPromise = mintAssetUploadUrls(
|
|
822
|
+
client.assets,
|
|
823
|
+
options.upload.assetName,
|
|
824
|
+
undefined,
|
|
825
|
+
options.upload.uploadOptions,
|
|
826
|
+
).then((asset) => {
|
|
827
|
+
request.signedUploadUrl = asset.signedUploadUrl;
|
|
828
|
+
// Lets limbuild record the built app's metadata on the asset.
|
|
829
|
+
request.assetId = asset.id;
|
|
830
|
+
request.additionalMetadata = { signedDownloadUrl: asset.signedDownloadUrl };
|
|
831
|
+
return request;
|
|
832
|
+
});
|
|
816
833
|
return exec(requestPromise, { apiUrl, token, log });
|
|
817
834
|
}
|
|
818
835
|
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.46.
|
|
1
|
+
export const VERSION = '0.46.10'; // x-release-please-version
|
package/version.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.46.
|
|
1
|
+
export declare const VERSION = "0.46.10";
|
|
2
2
|
//# sourceMappingURL=version.d.mts.map
|
package/version.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.mts","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"version.d.mts","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,YAAY,CAAC"}
|
package/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.46.
|
|
1
|
+
export declare const VERSION = "0.46.10";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,YAAY,CAAC"}
|
package/version.js
CHANGED
package/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":";;;AAAa,QAAA,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":";;;AAAa,QAAA,OAAO,GAAG,SAAS,CAAC,CAAC,2BAA2B"}
|
package/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '0.46.
|
|
1
|
+
export const VERSION = '0.46.10'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.mjs.map
|
package/version.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.mjs","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"version.mjs","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,2BAA2B"}
|