@limrun/api 0.46.0 → 0.46.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 +13 -0
- package/client.d.mts +2 -3
- package/client.d.mts.map +1 -1
- package/client.d.ts +2 -3
- package/client.d.ts.map +1 -1
- package/client.js +2 -2
- package/client.js.map +1 -1
- package/client.mjs +1 -1
- package/client.mjs.map +1 -1
- package/exec-client.d.mts +12 -0
- package/exec-client.d.mts.map +1 -1
- package/exec-client.d.ts +12 -0
- package/exec-client.d.ts.map +1 -1
- package/exec-client.js.map +1 -1
- package/exec-client.mjs.map +1 -1
- package/index.d.mts +2 -1
- package/index.d.mts.map +1 -1
- package/index.d.ts +2 -1
- package/index.d.ts.map +1 -1
- package/index.js +10 -1
- package/index.js.map +1 -1
- package/index.mjs +1 -0
- package/index.mjs.map +1 -1
- package/package.json +11 -1
- package/resources/gradle-instances-helpers.d.mts +9 -7
- package/resources/gradle-instances-helpers.d.mts.map +1 -1
- package/resources/gradle-instances-helpers.d.ts +9 -7
- package/resources/gradle-instances-helpers.d.ts.map +1 -1
- package/resources/gradle-instances-helpers.js +7 -3
- package/resources/gradle-instances-helpers.js.map +1 -1
- package/resources/gradle-instances-helpers.mjs +8 -2
- package/resources/gradle-instances-helpers.mjs.map +1 -1
- package/resources/gradle-instances.d.mts +9 -0
- package/resources/gradle-instances.d.mts.map +1 -1
- package/resources/gradle-instances.d.ts +9 -0
- package/resources/gradle-instances.d.ts.map +1 -1
- package/resources/index.d.mts +4 -3
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +4 -3
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js +7 -3
- package/resources/index.js.map +1 -1
- package/resources/index.mjs +5 -1
- package/resources/index.mjs.map +1 -1
- package/resources/xcode-instances-helpers.d.mts +45 -2
- package/resources/xcode-instances-helpers.d.mts.map +1 -1
- package/resources/xcode-instances-helpers.d.ts +45 -2
- package/resources/xcode-instances-helpers.d.ts.map +1 -1
- package/resources/xcode-instances-helpers.js +39 -0
- package/resources/xcode-instances-helpers.js.map +1 -1
- package/resources/xcode-instances-helpers.mjs +40 -1
- package/resources/xcode-instances-helpers.mjs.map +1 -1
- package/src/client.ts +7 -19
- package/src/exec-client.ts +12 -0
- package/src/index.ts +23 -0
- package/src/resources/gradle-instances-helpers.ts +21 -4
- package/src/resources/gradle-instances.ts +10 -0
- package/src/resources/index.ts +14 -8
- package/src/resources/xcode-instances-helpers.ts +79 -2
- package/src/version.ts +1 -1
- package/src/xcode-cache.ts +327 -0
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
- package/xcode-cache.d.mts +142 -0
- package/xcode-cache.d.mts.map +1 -0
- package/xcode-cache.d.ts +142 -0
- package/xcode-cache.d.ts.map +1 -0
- package/xcode-cache.js +175 -0
- package/xcode-cache.js.map +1 -0
- package/xcode-cache.mjs +165 -0
- package/xcode-cache.mjs.map +1 -0
package/src/resources/index.ts
CHANGED
|
@@ -24,6 +24,13 @@ export {
|
|
|
24
24
|
type AssetGetParams,
|
|
25
25
|
type AssetGetOrCreateParams,
|
|
26
26
|
} from './assets';
|
|
27
|
+
export {
|
|
28
|
+
GradleInstances,
|
|
29
|
+
type GradleInstance,
|
|
30
|
+
type GradleInstanceCreateParams,
|
|
31
|
+
type GradleInstanceListParams,
|
|
32
|
+
type GradleInstancesItems,
|
|
33
|
+
} from './gradle-instances';
|
|
27
34
|
export {
|
|
28
35
|
IosInstances,
|
|
29
36
|
type IosInstance,
|
|
@@ -38,13 +45,6 @@ export {
|
|
|
38
45
|
type XcodeInstanceListParams,
|
|
39
46
|
type XcodeInstancesItems,
|
|
40
47
|
} from './xcode-instances';
|
|
41
|
-
export {
|
|
42
|
-
type GradleInstance,
|
|
43
|
-
type GradleInstanceCreateParams,
|
|
44
|
-
type GradleInstanceListParams,
|
|
45
|
-
type GradleInstancesItems,
|
|
46
|
-
} from './gradle-instances';
|
|
47
|
-
|
|
48
48
|
export { Assets, AssetGetOrUploadParams, AssetGetOrUploadResponse } from './assets-helpers';
|
|
49
49
|
export {
|
|
50
50
|
XcodeInstances,
|
|
@@ -57,6 +57,8 @@ export {
|
|
|
57
57
|
type XcodeRunOptions,
|
|
58
58
|
type XcodeGenConfig,
|
|
59
59
|
type XcodeSigningConfig,
|
|
60
|
+
type XcodeCloudSigningConfig,
|
|
61
|
+
type XcodeCloudSigningMethod,
|
|
60
62
|
type ReactNativeBuildConfig,
|
|
61
63
|
type SimulatorAttachResult,
|
|
62
64
|
type SimulatorStatus,
|
|
@@ -68,9 +70,13 @@ export {
|
|
|
68
70
|
type XcodeBuildLog,
|
|
69
71
|
type BazelBuildLog,
|
|
70
72
|
type WebhookConfig,
|
|
73
|
+
type XcodeInstanceCreateParamsWithCache,
|
|
71
74
|
} from './xcode-instances-helpers';
|
|
75
|
+
// Side-effect import: grafts the createClient/listBuildLogs helpers onto the
|
|
76
|
+
// generated GradleInstances class. The type-only re-export below is erased at
|
|
77
|
+
// runtime, so it would not load the module on its own.
|
|
78
|
+
import './gradle-instances-helpers';
|
|
72
79
|
export {
|
|
73
|
-
GradleInstances,
|
|
74
80
|
type GradleCreateClientParams,
|
|
75
81
|
type GradleClient,
|
|
76
82
|
type GradleSyncOptions,
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
XcodeInstances as GeneratedXcodeInstances,
|
|
3
|
+
type XcodeInstance,
|
|
4
|
+
type XcodeInstanceCreateParams,
|
|
5
|
+
} from './xcode-instances';
|
|
2
6
|
import { type IosInstance } from './ios-instances';
|
|
3
7
|
import { APIPromise } from '../core/api-promise';
|
|
4
8
|
import { RequestOptions } from '../internal/request-options';
|
|
5
9
|
import { path } from '../internal/utils/path';
|
|
10
|
+
import {
|
|
11
|
+
followXcodeCache,
|
|
12
|
+
type XcodeCacheConfig,
|
|
13
|
+
type XcodeCacheFollowOptions,
|
|
14
|
+
type XcodeCacheFollowResult,
|
|
15
|
+
type XcodeInstanceCache,
|
|
16
|
+
} from '../xcode-cache';
|
|
6
17
|
import {
|
|
7
18
|
exec,
|
|
8
19
|
type AppStoreUploadConfig,
|
|
@@ -130,6 +141,21 @@ export type XcodeSigningConfig = {
|
|
|
130
141
|
provisioningProfilesBase64?: string[];
|
|
131
142
|
};
|
|
132
143
|
|
|
144
|
+
export type XcodeCloudSigningMethod = 'app-store-connect' | 'release-testing' | 'debugging';
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Apple-managed signing credentials used while exporting an unsigned device
|
|
148
|
+
* archive. Apple retains the signing certificate; callers provide only the
|
|
149
|
+
* App Store Connect team key.
|
|
150
|
+
*/
|
|
151
|
+
export type XcodeCloudSigningConfig = {
|
|
152
|
+
method: XcodeCloudSigningMethod;
|
|
153
|
+
teamId: string;
|
|
154
|
+
apiKeyId: string;
|
|
155
|
+
apiIssuerId: string;
|
|
156
|
+
apiPrivateKeyBase64: string;
|
|
157
|
+
};
|
|
158
|
+
|
|
133
159
|
export type ReactNativeBuildConfig = {
|
|
134
160
|
/**
|
|
135
161
|
* Relative path from the synced workspace root to the Expo app directory.
|
|
@@ -151,9 +177,10 @@ export type ReactNativeBuildConfig = {
|
|
|
151
177
|
export type XcodeBuildOptions = {
|
|
152
178
|
upload?: { assetName: string } | { signedUploadUrl: string };
|
|
153
179
|
signing?: XcodeSigningConfig;
|
|
180
|
+
cloudSigning?: XcodeCloudSigningConfig;
|
|
154
181
|
/**
|
|
155
182
|
* Upload the signed device IPA to App Store Connect after the build.
|
|
156
|
-
* Requires signing and sdk=iphoneos. Check ExecResult.appstore on
|
|
183
|
+
* Requires manual or cloud signing and sdk=iphoneos. Check ExecResult.appstore on
|
|
157
184
|
* completion; it is absent when the instance's limbuild predates the
|
|
158
185
|
* feature (old servers silently ignore this option).
|
|
159
186
|
*/
|
|
@@ -442,6 +469,11 @@ export type XcodeCreateClientParams =
|
|
|
442
469
|
| { instance: XcodeInstance; logLevel?: LogLevel }
|
|
443
470
|
| { apiUrl: string; token: string; logLevel?: LogLevel };
|
|
444
471
|
|
|
472
|
+
/** Generated create params plus the build cache the generator does not know about yet. */
|
|
473
|
+
export type XcodeInstanceCreateParamsWithCache = Omit<XcodeInstanceCreateParams, 'spec'> & {
|
|
474
|
+
spec?: XcodeInstanceCreateParams['spec'] & { cache?: XcodeCacheConfig };
|
|
475
|
+
};
|
|
476
|
+
|
|
445
477
|
function normalizeWorkspaceRelativePath(remotePath: string): string {
|
|
446
478
|
if (
|
|
447
479
|
remotePath === '' ||
|
|
@@ -584,6 +616,50 @@ export class XcodeInstances extends GeneratedXcodeInstances {
|
|
|
584
616
|
return this._client.get(path`/v1/xcode_instances/${id}/bazel_build_logs`, options);
|
|
585
617
|
}
|
|
586
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Create an Xcode instance, optionally with a build cache.
|
|
621
|
+
*
|
|
622
|
+
* Widens the generated signature with `spec.cache`; drop this override once the generator
|
|
623
|
+
* knows about the field. The request body passes through untouched either way.
|
|
624
|
+
*/
|
|
625
|
+
override create(
|
|
626
|
+
params: XcodeInstanceCreateParamsWithCache,
|
|
627
|
+
options?: RequestOptions,
|
|
628
|
+
): APIPromise<XcodeInstance> {
|
|
629
|
+
return super.create(params as XcodeInstanceCreateParams, options);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** Current cache configuration and status of an instance. */
|
|
633
|
+
getCache(id: string, options?: RequestOptions): APIPromise<XcodeInstanceCache> {
|
|
634
|
+
return this._client.get<XcodeInstanceCache>(path`/v1/xcode_instances/${id}/cache`, options);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Bind the destination key this instance publishes under at termination. Set once: binding
|
|
639
|
+
* the same key again is accepted, a different one is rejected.
|
|
640
|
+
*
|
|
641
|
+
* The instance needs a stable directory to publish from, which only a create that asked for
|
|
642
|
+
* caching allocates, so bind on an instance created with cache paths or a cache key.
|
|
643
|
+
*/
|
|
644
|
+
bindCacheKey(id: string, key: string, options?: RequestOptions): APIPromise<XcodeInstanceCache> {
|
|
645
|
+
return this._client.put<XcodeInstanceCache>(path`/v1/xcode_instances/${id}/cache`, {
|
|
646
|
+
body: { key },
|
|
647
|
+
...options,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Follow an instance's cache status until the chosen side reaches a terminal phase. Reports
|
|
653
|
+
* every phase change through `onUpdate`, so a caller can show a restore as it happens.
|
|
654
|
+
*/
|
|
655
|
+
followCache(id: string, options?: XcodeCacheFollowOptions): Promise<XcodeCacheFollowResult> {
|
|
656
|
+
const apiKey = this._client.apiKey;
|
|
657
|
+
if (!apiKey) {
|
|
658
|
+
throw new Error('Following cache status needs an API key on the client');
|
|
659
|
+
}
|
|
660
|
+
return followXcodeCache({ baseURL: this._client.baseURL, apiKey, instanceId: id }, options);
|
|
661
|
+
}
|
|
662
|
+
|
|
587
663
|
async createClient(params: XcodeCreateClientParams): Promise<XcodeClient> {
|
|
588
664
|
let apiUrl: string;
|
|
589
665
|
let token: string;
|
|
@@ -720,6 +796,7 @@ export class XcodeInstances extends GeneratedXcodeInstances {
|
|
|
720
796
|
...(options?.xcodegen && { xcodegen: options.xcodegen }),
|
|
721
797
|
...(options?.reactNative && { reactNative: options.reactNative }),
|
|
722
798
|
...(options?.signing && { signing: options.signing }),
|
|
799
|
+
...(options?.cloudSigning && { cloudSigning: options.cloudSigning }),
|
|
723
800
|
// The SDK calls this App Store; limbuild's existing wire field remains
|
|
724
801
|
// testflight until the exec API is revised separately.
|
|
725
802
|
...(options?.appstore && { testflight: options.appstore }),
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.46.
|
|
1
|
+
export const VERSION = '0.46.2'; // x-release-please-version
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { createEventSource, type EventSourceMessage } from 'eventsource-client';
|
|
2
|
+
|
|
3
|
+
import { nodeProxyTransport } from './internal/proxy-transport';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build cache configuration for an Xcode instance. Omitted means an ordinary uncached
|
|
7
|
+
* instance, which resolves nothing, adopts no stable directory, and publishes nothing.
|
|
8
|
+
*/
|
|
9
|
+
export type XcodeCacheConfig = {
|
|
10
|
+
/**
|
|
11
|
+
* Where this instance's workspace is published at termination. Organization-scoped and set
|
|
12
|
+
* once, either here or later through `bindCacheKey`. Reusing a key is normal operation:
|
|
13
|
+
* termination replaces its archive.
|
|
14
|
+
*
|
|
15
|
+
* With no restoreKeys, a key given here also serves as the single restore key.
|
|
16
|
+
*/
|
|
17
|
+
key?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Keys to restore from, tried in exactly this order: exact match first, then literal prefix,
|
|
20
|
+
* newest archive first within a prefix. Immutable after create.
|
|
21
|
+
*/
|
|
22
|
+
restoreKeys?: string[];
|
|
23
|
+
/**
|
|
24
|
+
* Project-root-relative paths to store. Create-only, and it controls what is STORED: the
|
|
25
|
+
* first publication under a key fixes its path set and restores materialize that set.
|
|
26
|
+
* Omitted means the whole deterministic workspace.
|
|
27
|
+
*/
|
|
28
|
+
paths?: string[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* How far a restore got. `disabled`, `unsupported` and `skipped` are not failures: they
|
|
33
|
+
* describe an ordinary cold instance, which is perfectly usable. Only `failed` means the
|
|
34
|
+
* workspace was supposed to be warm and something actually broke.
|
|
35
|
+
*/
|
|
36
|
+
export type XcodeCacheRestorePhase =
|
|
37
|
+
| 'disabled'
|
|
38
|
+
| 'unsupported'
|
|
39
|
+
| 'placing'
|
|
40
|
+
| 'downloading'
|
|
41
|
+
| 'materializing'
|
|
42
|
+
| 'restored'
|
|
43
|
+
| 'skipped'
|
|
44
|
+
| 'failed';
|
|
45
|
+
|
|
46
|
+
/** How far publication got. */
|
|
47
|
+
export type XcodeCacheSavePhase =
|
|
48
|
+
| 'disabled'
|
|
49
|
+
| 'idle'
|
|
50
|
+
| 'quiescing'
|
|
51
|
+
| 'archiving'
|
|
52
|
+
| 'uploading'
|
|
53
|
+
| 'verifying'
|
|
54
|
+
| 'published'
|
|
55
|
+
| 'skipped'
|
|
56
|
+
| 'failed'
|
|
57
|
+
| 'timed_out';
|
|
58
|
+
|
|
59
|
+
/** A restore key resolution passed over, so a miss is explainable rather than just a miss. */
|
|
60
|
+
export type XcodeCacheSkippedKey = { key: string; reason: string };
|
|
61
|
+
|
|
62
|
+
export type XcodeCacheRestoreStatus = {
|
|
63
|
+
phase: XcodeCacheRestorePhase;
|
|
64
|
+
reason?: string;
|
|
65
|
+
message?: string;
|
|
66
|
+
/** Restore key whose archive was used. */
|
|
67
|
+
matchedKey?: string;
|
|
68
|
+
matchKind?: 'exact_hit' | 'prefix_hit';
|
|
69
|
+
skippedKeys?: XcodeCacheSkippedKey[];
|
|
70
|
+
/** Path set actually materialized, which is the set the archive stores. */
|
|
71
|
+
paths?: string[];
|
|
72
|
+
/** Whether the bytes came through the regional accelerator or direct from object storage. */
|
|
73
|
+
source?: 'tag' | 'tigris';
|
|
74
|
+
bytes?: number;
|
|
75
|
+
uncompressedBytes?: number;
|
|
76
|
+
startedAt?: string;
|
|
77
|
+
finishedAt?: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type XcodeCacheSaveStatus = {
|
|
81
|
+
phase: XcodeCacheSavePhase;
|
|
82
|
+
reason?: string;
|
|
83
|
+
message?: string;
|
|
84
|
+
/** Destination key the archive is published under. */
|
|
85
|
+
cacheKey?: string;
|
|
86
|
+
paths?: string[];
|
|
87
|
+
bytes?: number;
|
|
88
|
+
uncompressedBytes?: number;
|
|
89
|
+
startedAt?: string;
|
|
90
|
+
finishedAt?: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type XcodeInstanceCache = {
|
|
94
|
+
config?: XcodeCacheConfig;
|
|
95
|
+
restore: XcodeCacheRestoreStatus;
|
|
96
|
+
save: XcodeCacheSaveStatus;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** Which half of the cache lifecycle a follower waits on. */
|
|
100
|
+
export type XcodeCacheSide = 'restore' | 'save';
|
|
101
|
+
|
|
102
|
+
export type XcodeCacheFollowOptions = {
|
|
103
|
+
/** Defaults to 'restore'. */
|
|
104
|
+
side?: XcodeCacheSide;
|
|
105
|
+
/**
|
|
106
|
+
* Called once per distinct state, including the snapshot the stream opens with. Phases that
|
|
107
|
+
* repeat, which a reconnect produces, are not reported again.
|
|
108
|
+
*/
|
|
109
|
+
onUpdate?: (cache: XcodeInstanceCache) => void;
|
|
110
|
+
/**
|
|
111
|
+
* Called when the stream is open, and again after each reconnect. A caller that is about to
|
|
112
|
+
* cause the very transitions it wants to watch waits for this first, so it cannot race its
|
|
113
|
+
* own subscription.
|
|
114
|
+
*/
|
|
115
|
+
onOpen?: () => void;
|
|
116
|
+
/**
|
|
117
|
+
* How long to wait for a terminal phase. Restores move multi-gigabyte archives and
|
|
118
|
+
* publication is capped server-side at ten minutes, so these are generous by design.
|
|
119
|
+
*/
|
|
120
|
+
timeoutMs?: number;
|
|
121
|
+
signal?: AbortSignal;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type XcodeCacheFollowResult = {
|
|
125
|
+
/** The last state seen, terminal unless the stream ended first. */
|
|
126
|
+
cache: XcodeInstanceCache;
|
|
127
|
+
/**
|
|
128
|
+
* True when the instance was collected before the phase went terminal. The last snapshot is
|
|
129
|
+
* still the summary: the control plane writes the terminal phase and deletes moments later.
|
|
130
|
+
*/
|
|
131
|
+
gone: boolean;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The instance was gone before it said anything about its cache, so there is no last state to
|
|
136
|
+
* summarise. Distinct from a follow that ends with `gone: true`, which at least saw one.
|
|
137
|
+
*/
|
|
138
|
+
export class XcodeCacheGoneError extends Error {
|
|
139
|
+
constructor(readonly instanceId: string) {
|
|
140
|
+
super(`Instance ${instanceId} was gone before it reported any cache status`);
|
|
141
|
+
this.name = 'XcodeCacheGoneError';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class XcodeCacheTimeoutError extends Error {
|
|
146
|
+
constructor(
|
|
147
|
+
readonly side: XcodeCacheSide,
|
|
148
|
+
readonly timeoutMs: number,
|
|
149
|
+
readonly cache: XcodeInstanceCache | undefined,
|
|
150
|
+
) {
|
|
151
|
+
super(
|
|
152
|
+
`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for the cache ${side} to finish` +
|
|
153
|
+
(cache ? ` (last phase: ${cache[side].phase})` : ''),
|
|
154
|
+
);
|
|
155
|
+
this.name = 'XcodeCacheTimeoutError';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const DEFAULT_TIMEOUT_MS: Record<XcodeCacheSide, number> = {
|
|
160
|
+
restore: 20 * 60 * 1000,
|
|
161
|
+
save: 12 * 60 * 1000,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** Phases past which nothing more will happen. */
|
|
165
|
+
export function isRestoreTerminal(phase: XcodeCacheRestorePhase): boolean {
|
|
166
|
+
return (
|
|
167
|
+
phase === 'disabled' ||
|
|
168
|
+
phase === 'unsupported' ||
|
|
169
|
+
phase === 'restored' ||
|
|
170
|
+
phase === 'skipped' ||
|
|
171
|
+
phase === 'failed'
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Separates the one outcome worth reacting to from the terminal phases that just mean "cold,
|
|
177
|
+
* carry on". A caller that created an instance for a warm workspace deletes it on a failure,
|
|
178
|
+
* but a fallback to a cold build leaves a perfectly usable instance behind.
|
|
179
|
+
*/
|
|
180
|
+
export function isRestoreFailure(phase: XcodeCacheRestorePhase): boolean {
|
|
181
|
+
return phase === 'failed';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function isSaveTerminal(phase: XcodeCacheSavePhase): boolean {
|
|
185
|
+
return (
|
|
186
|
+
phase === 'disabled' ||
|
|
187
|
+
phase === 'published' ||
|
|
188
|
+
phase === 'skipped' ||
|
|
189
|
+
phase === 'failed' ||
|
|
190
|
+
phase === 'timed_out'
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function isCacheTerminal(cache: XcodeInstanceCache, side: XcodeCacheSide): boolean {
|
|
195
|
+
return side === 'restore' ? isRestoreTerminal(cache.restore.phase) : isSaveTerminal(cache.save.phase);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export type XcodeCacheFollowTarget = {
|
|
199
|
+
baseURL: string;
|
|
200
|
+
apiKey: string;
|
|
201
|
+
instanceId: string;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Follows an instance's cache status until the chosen side reaches a terminal phase, the
|
|
206
|
+
* instance is collected, or the wait times out.
|
|
207
|
+
*
|
|
208
|
+
* The transport is the same endpoint that serves a JSON snapshot, asked for as an event
|
|
209
|
+
* stream. The server emits on phase change only, so every callback is a real transition.
|
|
210
|
+
*/
|
|
211
|
+
export function followXcodeCache(
|
|
212
|
+
target: XcodeCacheFollowTarget,
|
|
213
|
+
options: XcodeCacheFollowOptions = {},
|
|
214
|
+
): Promise<XcodeCacheFollowResult> {
|
|
215
|
+
const side = options.side ?? 'restore';
|
|
216
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS[side];
|
|
217
|
+
let base = target.baseURL;
|
|
218
|
+
while (base.endsWith('/')) base = base.slice(0, -1);
|
|
219
|
+
const url = `${base}/v1/xcode_instances/${encodeURIComponent(target.instanceId)}/cache`;
|
|
220
|
+
|
|
221
|
+
return new Promise<XcodeCacheFollowResult>((resolve, reject) => {
|
|
222
|
+
let last: XcodeInstanceCache | undefined;
|
|
223
|
+
let lastKey = '';
|
|
224
|
+
let settled = false;
|
|
225
|
+
|
|
226
|
+
const finish = (fn: () => void) => {
|
|
227
|
+
if (settled) return;
|
|
228
|
+
settled = true;
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
231
|
+
try {
|
|
232
|
+
source.close();
|
|
233
|
+
} catch {
|
|
234
|
+
// A stream that never opened has nothing to close.
|
|
235
|
+
}
|
|
236
|
+
fn();
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const timer = setTimeout(
|
|
240
|
+
() => finish(() => reject(new XcodeCacheTimeoutError(side, timeoutMs, last))),
|
|
241
|
+
timeoutMs,
|
|
242
|
+
);
|
|
243
|
+
const onAbort = () => finish(() => reject(options.signal?.reason ?? new Error('aborted')));
|
|
244
|
+
|
|
245
|
+
let confirming = false;
|
|
246
|
+
const confirmGone = () => {
|
|
247
|
+
if (confirming || settled) return;
|
|
248
|
+
confirming = true;
|
|
249
|
+
nodeProxyTransport
|
|
250
|
+
.fetch(url, {
|
|
251
|
+
headers: { Authorization: `Bearer ${target.apiKey}`, Accept: 'application/json' },
|
|
252
|
+
})
|
|
253
|
+
.then(async (res) => {
|
|
254
|
+
confirming = false;
|
|
255
|
+
if (res.status !== 404) {
|
|
256
|
+
await res.text().catch(() => undefined);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
finish(() =>
|
|
260
|
+
last ? resolve({ cache: last, gone: true }) : reject(new XcodeCacheGoneError(target.instanceId)),
|
|
261
|
+
);
|
|
262
|
+
})
|
|
263
|
+
.catch(() => {
|
|
264
|
+
confirming = false;
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// The endpoint answers from the region, which keeps the instance while it terminates and
|
|
269
|
+
// drops it once it is collected, so a 404 is the instance being over rather than a fault.
|
|
270
|
+
// Without this the client treats it as a connection to retry and follows a gone instance
|
|
271
|
+
// until the timeout, which is minutes of a command that looks hung and says nothing.
|
|
272
|
+
const fetchOrGone: typeof nodeProxyTransport.fetch = async (input, init) => {
|
|
273
|
+
const res = await nodeProxyTransport.fetch(input, init);
|
|
274
|
+
if (res.status === 404) {
|
|
275
|
+
finish(() =>
|
|
276
|
+
last ? resolve({ cache: last, gone: true }) : reject(new XcodeCacheGoneError(target.instanceId)),
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
return res;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const source = createEventSource({
|
|
283
|
+
url,
|
|
284
|
+
fetch: fetchOrGone,
|
|
285
|
+
onConnect: () => options.onOpen?.(),
|
|
286
|
+
headers: {
|
|
287
|
+
Authorization: `Bearer ${target.apiKey}`,
|
|
288
|
+
Accept: 'text/event-stream',
|
|
289
|
+
},
|
|
290
|
+
onMessage: (message: EventSourceMessage) => {
|
|
291
|
+
if (message.event !== 'cache' && message.event !== 'gone') return;
|
|
292
|
+
let cache: XcodeInstanceCache;
|
|
293
|
+
try {
|
|
294
|
+
cache = JSON.parse(typeof message.data === 'string' ? message.data : String(message.data));
|
|
295
|
+
} catch (err) {
|
|
296
|
+
finish(() => reject(new Error(`Cache stream sent unreadable data: ${err}`)));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
last = cache;
|
|
300
|
+
if (message.event === 'gone') {
|
|
301
|
+
// The server also ends the stream this way when its own watch is cut short, which
|
|
302
|
+
// Kubernetes does routinely on a long one, so a snapshot decides which it was. The
|
|
303
|
+
// client library reconnects on its own when the instance is in fact still there.
|
|
304
|
+
confirmGone();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
// A reconnect replays the current state, which is not a transition.
|
|
308
|
+
const key = `${cache.restore.phase}/${cache.restore.reason ?? ''}|${cache.save.phase}/${
|
|
309
|
+
cache.save.reason ?? ''
|
|
310
|
+
}`;
|
|
311
|
+
if (key !== lastKey) {
|
|
312
|
+
lastKey = key;
|
|
313
|
+
options.onUpdate?.(cache);
|
|
314
|
+
}
|
|
315
|
+
if (isCacheTerminal(cache, side)) {
|
|
316
|
+
finish(() => resolve({ cache, gone: false }));
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (options.signal?.aborted) {
|
|
322
|
+
onAbort();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
326
|
+
});
|
|
327
|
+
}
|
package/version.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.46.
|
|
1
|
+
export declare const VERSION = "0.46.2";
|
|
2
2
|
//# sourceMappingURL=version.d.mts.map
|
package/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.46.
|
|
1
|
+
export declare const VERSION = "0.46.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.46.
|
|
1
|
+
export const VERSION = '0.46.2'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.mjs.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build cache configuration for an Xcode instance. Omitted means an ordinary uncached
|
|
3
|
+
* instance, which resolves nothing, adopts no stable directory, and publishes nothing.
|
|
4
|
+
*/
|
|
5
|
+
export type XcodeCacheConfig = {
|
|
6
|
+
/**
|
|
7
|
+
* Where this instance's workspace is published at termination. Organization-scoped and set
|
|
8
|
+
* once, either here or later through `bindCacheKey`. Reusing a key is normal operation:
|
|
9
|
+
* termination replaces its archive.
|
|
10
|
+
*
|
|
11
|
+
* With no restoreKeys, a key given here also serves as the single restore key.
|
|
12
|
+
*/
|
|
13
|
+
key?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Keys to restore from, tried in exactly this order: exact match first, then literal prefix,
|
|
16
|
+
* newest archive first within a prefix. Immutable after create.
|
|
17
|
+
*/
|
|
18
|
+
restoreKeys?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Project-root-relative paths to store. Create-only, and it controls what is STORED: the
|
|
21
|
+
* first publication under a key fixes its path set and restores materialize that set.
|
|
22
|
+
* Omitted means the whole deterministic workspace.
|
|
23
|
+
*/
|
|
24
|
+
paths?: string[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* How far a restore got. `disabled`, `unsupported` and `skipped` are not failures: they
|
|
28
|
+
* describe an ordinary cold instance, which is perfectly usable. Only `failed` means the
|
|
29
|
+
* workspace was supposed to be warm and something actually broke.
|
|
30
|
+
*/
|
|
31
|
+
export type XcodeCacheRestorePhase = 'disabled' | 'unsupported' | 'placing' | 'downloading' | 'materializing' | 'restored' | 'skipped' | 'failed';
|
|
32
|
+
/** How far publication got. */
|
|
33
|
+
export type XcodeCacheSavePhase = 'disabled' | 'idle' | 'quiescing' | 'archiving' | 'uploading' | 'verifying' | 'published' | 'skipped' | 'failed' | 'timed_out';
|
|
34
|
+
/** A restore key resolution passed over, so a miss is explainable rather than just a miss. */
|
|
35
|
+
export type XcodeCacheSkippedKey = {
|
|
36
|
+
key: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
};
|
|
39
|
+
export type XcodeCacheRestoreStatus = {
|
|
40
|
+
phase: XcodeCacheRestorePhase;
|
|
41
|
+
reason?: string;
|
|
42
|
+
message?: string;
|
|
43
|
+
/** Restore key whose archive was used. */
|
|
44
|
+
matchedKey?: string;
|
|
45
|
+
matchKind?: 'exact_hit' | 'prefix_hit';
|
|
46
|
+
skippedKeys?: XcodeCacheSkippedKey[];
|
|
47
|
+
/** Path set actually materialized, which is the set the archive stores. */
|
|
48
|
+
paths?: string[];
|
|
49
|
+
/** Whether the bytes came through the regional accelerator or direct from object storage. */
|
|
50
|
+
source?: 'tag' | 'tigris';
|
|
51
|
+
bytes?: number;
|
|
52
|
+
uncompressedBytes?: number;
|
|
53
|
+
startedAt?: string;
|
|
54
|
+
finishedAt?: string;
|
|
55
|
+
};
|
|
56
|
+
export type XcodeCacheSaveStatus = {
|
|
57
|
+
phase: XcodeCacheSavePhase;
|
|
58
|
+
reason?: string;
|
|
59
|
+
message?: string;
|
|
60
|
+
/** Destination key the archive is published under. */
|
|
61
|
+
cacheKey?: string;
|
|
62
|
+
paths?: string[];
|
|
63
|
+
bytes?: number;
|
|
64
|
+
uncompressedBytes?: number;
|
|
65
|
+
startedAt?: string;
|
|
66
|
+
finishedAt?: string;
|
|
67
|
+
};
|
|
68
|
+
export type XcodeInstanceCache = {
|
|
69
|
+
config?: XcodeCacheConfig;
|
|
70
|
+
restore: XcodeCacheRestoreStatus;
|
|
71
|
+
save: XcodeCacheSaveStatus;
|
|
72
|
+
};
|
|
73
|
+
/** Which half of the cache lifecycle a follower waits on. */
|
|
74
|
+
export type XcodeCacheSide = 'restore' | 'save';
|
|
75
|
+
export type XcodeCacheFollowOptions = {
|
|
76
|
+
/** Defaults to 'restore'. */
|
|
77
|
+
side?: XcodeCacheSide;
|
|
78
|
+
/**
|
|
79
|
+
* Called once per distinct state, including the snapshot the stream opens with. Phases that
|
|
80
|
+
* repeat, which a reconnect produces, are not reported again.
|
|
81
|
+
*/
|
|
82
|
+
onUpdate?: (cache: XcodeInstanceCache) => void;
|
|
83
|
+
/**
|
|
84
|
+
* Called when the stream is open, and again after each reconnect. A caller that is about to
|
|
85
|
+
* cause the very transitions it wants to watch waits for this first, so it cannot race its
|
|
86
|
+
* own subscription.
|
|
87
|
+
*/
|
|
88
|
+
onOpen?: () => void;
|
|
89
|
+
/**
|
|
90
|
+
* How long to wait for a terminal phase. Restores move multi-gigabyte archives and
|
|
91
|
+
* publication is capped server-side at ten minutes, so these are generous by design.
|
|
92
|
+
*/
|
|
93
|
+
timeoutMs?: number;
|
|
94
|
+
signal?: AbortSignal;
|
|
95
|
+
};
|
|
96
|
+
export type XcodeCacheFollowResult = {
|
|
97
|
+
/** The last state seen, terminal unless the stream ended first. */
|
|
98
|
+
cache: XcodeInstanceCache;
|
|
99
|
+
/**
|
|
100
|
+
* True when the instance was collected before the phase went terminal. The last snapshot is
|
|
101
|
+
* still the summary: the control plane writes the terminal phase and deletes moments later.
|
|
102
|
+
*/
|
|
103
|
+
gone: boolean;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* The instance was gone before it said anything about its cache, so there is no last state to
|
|
107
|
+
* summarise. Distinct from a follow that ends with `gone: true`, which at least saw one.
|
|
108
|
+
*/
|
|
109
|
+
export declare class XcodeCacheGoneError extends Error {
|
|
110
|
+
readonly instanceId: string;
|
|
111
|
+
constructor(instanceId: string);
|
|
112
|
+
}
|
|
113
|
+
export declare class XcodeCacheTimeoutError extends Error {
|
|
114
|
+
readonly side: XcodeCacheSide;
|
|
115
|
+
readonly timeoutMs: number;
|
|
116
|
+
readonly cache: XcodeInstanceCache | undefined;
|
|
117
|
+
constructor(side: XcodeCacheSide, timeoutMs: number, cache: XcodeInstanceCache | undefined);
|
|
118
|
+
}
|
|
119
|
+
/** Phases past which nothing more will happen. */
|
|
120
|
+
export declare function isRestoreTerminal(phase: XcodeCacheRestorePhase): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Separates the one outcome worth reacting to from the terminal phases that just mean "cold,
|
|
123
|
+
* carry on". A caller that created an instance for a warm workspace deletes it on a failure,
|
|
124
|
+
* but a fallback to a cold build leaves a perfectly usable instance behind.
|
|
125
|
+
*/
|
|
126
|
+
export declare function isRestoreFailure(phase: XcodeCacheRestorePhase): boolean;
|
|
127
|
+
export declare function isSaveTerminal(phase: XcodeCacheSavePhase): boolean;
|
|
128
|
+
export declare function isCacheTerminal(cache: XcodeInstanceCache, side: XcodeCacheSide): boolean;
|
|
129
|
+
export type XcodeCacheFollowTarget = {
|
|
130
|
+
baseURL: string;
|
|
131
|
+
apiKey: string;
|
|
132
|
+
instanceId: string;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Follows an instance's cache status until the chosen side reaches a terminal phase, the
|
|
136
|
+
* instance is collected, or the wait times out.
|
|
137
|
+
*
|
|
138
|
+
* The transport is the same endpoint that serves a JSON snapshot, asked for as an event
|
|
139
|
+
* stream. The server emits on phase change only, so every callback is a real transition.
|
|
140
|
+
*/
|
|
141
|
+
export declare function followXcodeCache(target: XcodeCacheFollowTarget, options?: XcodeCacheFollowOptions): Promise<XcodeCacheFollowResult>;
|
|
142
|
+
//# sourceMappingURL=xcode-cache.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xcode-cache.d.mts","sourceRoot":"","sources":["src/xcode-cache.ts"],"names":[],"mappings":"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"}
|