@limrun/api 0.33.4 → 0.34.0

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/client.d.mts +3 -0
  3. package/client.d.mts.map +1 -1
  4. package/client.d.ts +3 -0
  5. package/client.d.ts.map +1 -1
  6. package/client.js +3 -0
  7. package/client.js.map +1 -1
  8. package/client.mjs +3 -0
  9. package/client.mjs.map +1 -1
  10. package/index.d.mts +0 -1
  11. package/index.d.mts.map +1 -1
  12. package/index.d.ts +0 -1
  13. package/index.d.ts.map +1 -1
  14. package/index.js +1 -5
  15. package/index.js.map +1 -1
  16. package/index.mjs +0 -1
  17. package/index.mjs.map +1 -1
  18. package/package.json +1 -11
  19. package/resources/analytics.d.mts +650 -0
  20. package/resources/analytics.d.mts.map +1 -0
  21. package/resources/analytics.d.ts +650 -0
  22. package/resources/analytics.d.ts.map +1 -0
  23. package/resources/analytics.js +22 -0
  24. package/resources/analytics.js.map +1 -0
  25. package/resources/analytics.mjs +18 -0
  26. package/resources/analytics.mjs.map +1 -0
  27. package/resources/index.d.mts +1 -0
  28. package/resources/index.d.mts.map +1 -1
  29. package/resources/index.d.ts +1 -0
  30. package/resources/index.d.ts.map +1 -1
  31. package/resources/index.js +3 -1
  32. package/resources/index.js.map +1 -1
  33. package/resources/index.mjs +1 -0
  34. package/resources/index.mjs.map +1 -1
  35. package/resources/xcode-instances-helpers.d.mts +0 -18
  36. package/resources/xcode-instances-helpers.d.mts.map +1 -1
  37. package/resources/xcode-instances-helpers.d.ts +0 -18
  38. package/resources/xcode-instances-helpers.d.ts.map +1 -1
  39. package/resources/xcode-instances-helpers.js +0 -18
  40. package/resources/xcode-instances-helpers.js.map +1 -1
  41. package/resources/xcode-instances-helpers.mjs +0 -18
  42. package/resources/xcode-instances-helpers.mjs.map +1 -1
  43. package/src/client.ts +17 -0
  44. package/src/index.ts +0 -1
  45. package/src/resources/analytics.ts +743 -0
  46. package/src/resources/index.ts +7 -0
  47. package/src/resources/xcode-instances-helpers.ts +0 -53
  48. package/src/version.ts +1 -1
  49. package/version.d.mts +1 -1
  50. package/version.d.ts +1 -1
  51. package/version.js +1 -1
  52. package/version.mjs +1 -1
  53. package/rbe-bep.d.mts +0 -77
  54. package/rbe-bep.d.mts.map +0 -1
  55. package/rbe-bep.d.ts +0 -77
  56. package/rbe-bep.d.ts.map +0 -1
  57. package/rbe-bep.js +0 -225
  58. package/rbe-bep.js.map +0 -1
  59. package/rbe-bep.mjs +0 -219
  60. package/rbe-bep.mjs.map +0 -1
  61. package/src/rbe-bep.ts +0 -292
package/rbe-bep.mjs DELETED
@@ -1,219 +0,0 @@
1
- /**
2
- * Parses Bazel's Build Event Protocol (BEP, the `--build_event_json_file`
3
- * newline-delimited JSON) to find a built target's top-level `.ipa` and its
4
- * content-addressed (CAS) digest.
5
- *
6
- * A remotely-executed output carries a `bytestream://<host>[/<instance>]/blobs/
7
- * <hash>/<size>` URI in its BEP `file` entry regardless of whether Bazel also
8
- * downloads the bytes (`--remote_download_outputs`), because the URI names the
9
- * output's CAS identity, not its local availability. `lim xcode rbe install`
10
- * reads that digest and hands it to the instance, which fetches the blob from
11
- * its own RBE cache and installs it — no round-trip. The generated bazelrc sets
12
- * `--remote_download_outputs=minimal` to skip the (unneeded) local download by
13
- * default; overriding it (e.g. `--remote_download_outputs=toplevel` on the command
14
- * line) to materialize the .ipa locally does not affect this parser.
15
- *
16
- * A fully-cached rebuild (0 actions executed) still emits the bytestream URI and
17
- * reproduces the same digest, so the auto-install watcher can rely on this parser
18
- * after a no-op or revert (e.g. `git stash`) rebuild. Verified empirically.
19
- */
20
- export class RbeBepError extends Error {
21
- constructor(kind, message) {
22
- super(message);
23
- this.name = 'RbeBepError';
24
- this.kind = kind;
25
- }
26
- /**
27
- * True when re-reading the same (completed) BEP cannot help — the build was
28
- * produced with flags the instance cache can't use (local download or a
29
- * non-SHA256 digest). The watcher logs these once instead of retrying.
30
- */
31
- get terminal() {
32
- return this.kind === 'local-only' || this.kind === 'non-sha256';
33
- }
34
- }
35
- /** A SHA-256 digest is exactly 64 lowercase hex chars; the instance CAS is keyed by it. */
36
- const SHA256_HEX = /^[0-9a-f]{64}$/;
37
- /**
38
- * Extracts (digestFunction, hash, size) from a Bazel/REAPI bytestream blob URI:
39
- * `bytestream://…/blobs/[<digest_function>/]<hash>/<size>`. The digest-function
40
- * segment is OMITTED for functions inferable from hash length (SHA-256 among
41
- * them) and PRESENT otherwise — notably BLAKE3, whose 256-bit output is also 64
42
- * hex chars and so cannot be told apart from SHA-256 by length. Capturing the
43
- * function lets the caller reject a non-SHA256 build (e.g. Bazel 9's BLAKE3
44
- * default) with the right guidance instead of misreading the URI.
45
- */
46
- function parseBytestreamDigest(uri) {
47
- // Anchor on the /blobs/[func/]<hash>/<size> tail (tolerating an instance-name
48
- // prefix). The optional function group only matches a real function segment:
49
- // for the SHA-256 form (`/blobs/<hash>/<size>`) it backtracks to absent, and
50
- // for `/blobs/blake3/<hash>/<size>` the non-hex 'l'/'k' force it to be captured
51
- // as the function rather than the hash.
52
- const m = uri.match(/\/blobs\/(?:([a-z0-9_]+)\/)?([0-9a-fA-F]+)\/(\d+)(?:$|\/)/);
53
- if (!m || m[2] === undefined || m[3] === undefined)
54
- return null;
55
- return {
56
- ...(m[1] !== undefined ? { digestFunction: m[1] } : {}),
57
- hash: m[2].toLowerCase(),
58
- sizeBytes: Number(m[3]),
59
- };
60
- }
61
- /**
62
- * Canonicalizes a Bazel target label to the `//pkg:name` form BEP records.
63
- * Bazel expands the `//pkg` shorthand to `//pkg:<basename(pkg)>` in its
64
- * targetCompleted events, so `install //App` must match BEP's `//App:App`.
65
- */
66
- function canonicalizeLabel(label) {
67
- if (label.includes(':'))
68
- return label;
69
- // Strip trailing slashes with a linear scan rather than a regex: `/\/+$/`
70
- // backtracks quadratically on a label of many trailing slashes (CodeQL ReDoS).
71
- let end = label.length;
72
- while (end > 0 && label[end - 1] === '/')
73
- end--;
74
- const pkg = label.slice(0, end);
75
- const name = pkg.split('/').pop() ?? '';
76
- return name ? `${pkg}:${name}` : pkg;
77
- }
78
- /** Parses the BEP stream into events, ignoring partial/garbled lines. */
79
- function parseEvents(bepJson) {
80
- const events = [];
81
- for (const line of bepJson.split('\n')) {
82
- const trimmed = line.trim();
83
- if (!trimmed)
84
- continue;
85
- try {
86
- events.push(JSON.parse(trimmed));
87
- }
88
- catch {
89
- // Ignore partial/garbled lines (BEP is flushed incrementally).
90
- }
91
- }
92
- return events;
93
- }
94
- /**
95
- * Inspects a BEP stream for build completion. The watcher reads the BEP on every
96
- * change but only acts once `complete` is true (the stream is fully flushed) and
97
- * `success` is true (the build did not fail). `invocationId` lets the watcher act
98
- * once per build even when consecutive builds reproduce the same .ipa digest
99
- * (e.g. a `git stash` revert that rebuilds to an earlier digest).
100
- */
101
- export function inspectBuildCompletion(bepJson) {
102
- let complete = false;
103
- let sawFinished = false;
104
- let success = false;
105
- let invocationId;
106
- for (const e of parseEvents(bepJson)) {
107
- if (e.started?.uuid)
108
- invocationId = e.started.uuid;
109
- if (e.id?.buildFinished !== undefined || e.finished !== undefined) {
110
- sawFinished = true;
111
- // proto3 JSON omits zero-valued scalars, so a SUCCESS exit code serializes
112
- // as `{name:"SUCCESS"}` with `code` omitted — checking `code === 0` would be
113
- // a dead branch. Trust overallSuccess (present=true on success, omitted on
114
- // failure) and fall back to the exit code's symbolic name.
115
- success = e.finished?.overallSuccess === true || e.finished?.exitCode?.name === 'SUCCESS';
116
- }
117
- if (e.lastMessage === true)
118
- complete = true;
119
- }
120
- return {
121
- complete,
122
- success: sawFinished && success,
123
- ...(invocationId !== undefined ? { invocationId } : {}),
124
- };
125
- }
126
- /** Indexes namedSetOfFiles events by id for transitive fileset resolution. */
127
- function indexNamedSets(events) {
128
- const namedSets = new Map();
129
- for (const e of events) {
130
- const id = e.id?.namedSet?.id;
131
- if (id && e.namedSetOfFiles)
132
- namedSets.set(id, e.namedSetOfFiles);
133
- }
134
- return namedSets;
135
- }
136
- /**
137
- * Resolves the top-level `.ipa` CAS digest for one successful target's
138
- * `completed` event, walking its default output group's filesets transitively.
139
- * Throws a typed RbeBepError when no .ipa is present, it was materialized
140
- * locally (`file://`), or it carries a non-SHA256 (e.g. BLAKE3) digest.
141
- */
142
- function resolveIpaDigest(label, completed, namedSets) {
143
- // Default output group -> the fileSet (namedSetOfFiles) ids holding its outputs.
144
- const rootFileSetIds = (completed.outputGroup ?? [])
145
- .filter((og) => og.name === 'default' || !og.name)
146
- .flatMap((og) => (og.fileSets ?? []).map((fs) => fs.id))
147
- .filter((id) => !!id);
148
- if (rootFileSetIds.length === 0) {
149
- throw new RbeBepError('no-output', `Build of ${label} reported no default output files.`);
150
- }
151
- const seen = new Set();
152
- const queue = [...rootFileSetIds];
153
- // Set to the name of an .ipa that was seen but had no bytestream digest (its
154
- // output was downloaded locally) — distinguishes that case from "no .ipa at all".
155
- let localOnlyIpa;
156
- while (queue.length > 0) {
157
- const id = queue.shift();
158
- if (seen.has(id))
159
- continue;
160
- seen.add(id);
161
- const set = namedSets.get(id);
162
- if (!set)
163
- continue;
164
- for (const f of set.files ?? []) {
165
- if (!f.name || !f.name.endsWith('.ipa'))
166
- continue;
167
- const digest = f.uri ? parseBytestreamDigest(f.uri) : null;
168
- if (digest) {
169
- // The instance cache is keyed by SHA-256. Reject any other digest
170
- // function — Bazel 9 defaults to BLAKE3, whose URI carries an explicit
171
- // `blake3` function segment (and whose 64-hex hash is indistinguishable
172
- // from SHA-256 by length, so the hash form alone can't catch it). Surface
173
- // it here with the fix rather than letting it fail later as a cryptic
174
- // server cache miss.
175
- const isSha256 = digest.digestFunction === undefined ?
176
- SHA256_HEX.test(digest.hash)
177
- : digest.digestFunction === 'sha256';
178
- if (!isSha256) {
179
- const got = digest.digestFunction ?? `${digest.hash.length} hex chars`;
180
- throw new RbeBepError('non-sha256', `Built ${f.name} with a non-SHA256 digest (${got}; Bazel 9 defaults to BLAKE3). The instance ` +
181
- `cache is keyed by SHA-256 — rebuild with --digest_function=sha256, e.g. ` +
182
- `\`bazelisk --digest_function=sha256 build --config=limrun ${label}\`.`);
183
- }
184
- return { hash: digest.hash, sizeBytes: digest.sizeBytes, ipaName: f.name };
185
- }
186
- localOnlyIpa = f.name;
187
- }
188
- for (const nested of set.fileSets ?? []) {
189
- if (nested.id)
190
- queue.push(nested.id);
191
- }
192
- }
193
- if (localOnlyIpa) {
194
- throw new RbeBepError('local-only', `Found ${label}'s .ipa (${localOnlyIpa}) but it has no remote (bytestream) digest — it was ` +
195
- `built locally, not remotely executed. Ensure you built with --config=limrun so the .ipa is ` +
196
- `produced in the instance's cache.`);
197
- }
198
- throw new RbeBepError('no-ipa', `No .ipa output found for ${label} in the build event log.`);
199
- }
200
- /**
201
- * Finds the `.ipa` produced for `label` in a BEP stream and returns its CAS
202
- * digest. Throws a typed RbeBepError when the target/.ipa is absent, its output
203
- * was materialized locally (a `file://` URI) instead of left in CAS, or it was
204
- * built with a non-SHA256 digest the instance cache cannot resolve.
205
- */
206
- export function parseTopLevelIpaDigest(bepJson, label) {
207
- const canonical = canonicalizeLabel(label);
208
- const events = parseEvents(bepJson);
209
- // The successful TargetComplete for the requested label (matched against the
210
- // canonical //pkg:name form Bazel records, so `//App` matches `//App:App`).
211
- const completed = events.find((e) => e.id?.targetCompleted?.label === canonical && e.completed?.success)
212
- ?.completed;
213
- if (!completed) {
214
- throw new RbeBepError('no-build', `No successful build of ${label} found in the build event log. ` +
215
- `Build it first with --config=limrun (e.g. \`bazelisk --digest_function=sha256 build --config=limrun ${label}\`).`);
216
- }
217
- return resolveIpaDigest(label, completed, indexNamedSets(events));
218
- }
219
- //# sourceMappingURL=rbe-bep.mjs.map
package/rbe-bep.mjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"rbe-bep.mjs","sourceRoot":"","sources":["src/rbe-bep.ts"],"names":[],"mappings":"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"}
package/src/rbe-bep.ts DELETED
@@ -1,292 +0,0 @@
1
- /**
2
- * Parses Bazel's Build Event Protocol (BEP, the `--build_event_json_file`
3
- * newline-delimited JSON) to find a built target's top-level `.ipa` and its
4
- * content-addressed (CAS) digest.
5
- *
6
- * A remotely-executed output carries a `bytestream://<host>[/<instance>]/blobs/
7
- * <hash>/<size>` URI in its BEP `file` entry regardless of whether Bazel also
8
- * downloads the bytes (`--remote_download_outputs`), because the URI names the
9
- * output's CAS identity, not its local availability. `lim xcode rbe install`
10
- * reads that digest and hands it to the instance, which fetches the blob from
11
- * its own RBE cache and installs it — no round-trip. The generated bazelrc sets
12
- * `--remote_download_outputs=minimal` to skip the (unneeded) local download by
13
- * default; overriding it (e.g. `--remote_download_outputs=toplevel` on the command
14
- * line) to materialize the .ipa locally does not affect this parser.
15
- *
16
- * A fully-cached rebuild (0 actions executed) still emits the bytestream URI and
17
- * reproduces the same digest, so the auto-install watcher can rely on this parser
18
- * after a no-op or revert (e.g. `git stash`) rebuild. Verified empirically.
19
- */
20
-
21
- export type BepIpaDigest = {
22
- /** Lowercase hex SHA-256 of the .ipa blob. */
23
- hash: string;
24
- /** Size of the .ipa blob in bytes. */
25
- sizeBytes: number;
26
- /** The .ipa file name as Bazel reported it (e.g. "App/App.ipa"). */
27
- ipaName: string;
28
- };
29
-
30
- type BepEvent = {
31
- id?: {
32
- targetCompleted?: { label?: string };
33
- namedSet?: { id?: string };
34
- buildFinished?: unknown;
35
- };
36
- started?: { uuid?: string };
37
- finished?: { overallSuccess?: boolean; exitCode?: { name?: string; code?: number } };
38
- /** Bazel sets this on the final BEP event once the stream is fully flushed. */
39
- lastMessage?: boolean;
40
- completed?: {
41
- success?: boolean;
42
- outputGroup?: Array<{ name?: string; fileSets?: Array<{ id?: string }> }>;
43
- };
44
- namedSetOfFiles?: {
45
- files?: Array<{ name?: string; uri?: string }>;
46
- fileSets?: Array<{ id?: string }>;
47
- };
48
- };
49
-
50
- /**
51
- * Why a BEP could not yield an .ipa digest for a label.
52
- *
53
- * `terminal` failures will not resolve by re-reading the same completed BEP
54
- * (the build used the wrong flags); `no-build` / `no-output` / `no-ipa` are
55
- * transient while the build is still flushing and become terminal only once
56
- * the build is confirmed complete. The watcher uses this to decide whether to
57
- * retry the read or to log-once and give up.
58
- */
59
- export type RbeBepErrorKind = 'no-build' | 'no-output' | 'no-ipa' | 'local-only' | 'non-sha256';
60
-
61
- export class RbeBepError extends Error {
62
- readonly kind: RbeBepErrorKind;
63
- constructor(kind: RbeBepErrorKind, message: string) {
64
- super(message);
65
- this.name = 'RbeBepError';
66
- this.kind = kind;
67
- }
68
-
69
- /**
70
- * True when re-reading the same (completed) BEP cannot help — the build was
71
- * produced with flags the instance cache can't use (local download or a
72
- * non-SHA256 digest). The watcher logs these once instead of retrying.
73
- */
74
- get terminal(): boolean {
75
- return this.kind === 'local-only' || this.kind === 'non-sha256';
76
- }
77
- }
78
-
79
- /** A SHA-256 digest is exactly 64 lowercase hex chars; the instance CAS is keyed by it. */
80
- const SHA256_HEX = /^[0-9a-f]{64}$/;
81
-
82
- /**
83
- * Extracts (digestFunction, hash, size) from a Bazel/REAPI bytestream blob URI:
84
- * `bytestream://…/blobs/[<digest_function>/]<hash>/<size>`. The digest-function
85
- * segment is OMITTED for functions inferable from hash length (SHA-256 among
86
- * them) and PRESENT otherwise — notably BLAKE3, whose 256-bit output is also 64
87
- * hex chars and so cannot be told apart from SHA-256 by length. Capturing the
88
- * function lets the caller reject a non-SHA256 build (e.g. Bazel 9's BLAKE3
89
- * default) with the right guidance instead of misreading the URI.
90
- */
91
- function parseBytestreamDigest(
92
- uri: string,
93
- ): { digestFunction?: string; hash: string; sizeBytes: number } | null {
94
- // Anchor on the /blobs/[func/]<hash>/<size> tail (tolerating an instance-name
95
- // prefix). The optional function group only matches a real function segment:
96
- // for the SHA-256 form (`/blobs/<hash>/<size>`) it backtracks to absent, and
97
- // for `/blobs/blake3/<hash>/<size>` the non-hex 'l'/'k' force it to be captured
98
- // as the function rather than the hash.
99
- const m = uri.match(/\/blobs\/(?:([a-z0-9_]+)\/)?([0-9a-fA-F]+)\/(\d+)(?:$|\/)/);
100
- if (!m || m[2] === undefined || m[3] === undefined) return null;
101
- return {
102
- ...(m[1] !== undefined ? { digestFunction: m[1] } : {}),
103
- hash: m[2].toLowerCase(),
104
- sizeBytes: Number(m[3]),
105
- };
106
- }
107
-
108
- /**
109
- * Canonicalizes a Bazel target label to the `//pkg:name` form BEP records.
110
- * Bazel expands the `//pkg` shorthand to `//pkg:<basename(pkg)>` in its
111
- * targetCompleted events, so `install //App` must match BEP's `//App:App`.
112
- */
113
- function canonicalizeLabel(label: string): string {
114
- if (label.includes(':')) return label;
115
- // Strip trailing slashes with a linear scan rather than a regex: `/\/+$/`
116
- // backtracks quadratically on a label of many trailing slashes (CodeQL ReDoS).
117
- let end = label.length;
118
- while (end > 0 && label[end - 1] === '/') end--;
119
- const pkg = label.slice(0, end);
120
- const name = pkg.split('/').pop() ?? '';
121
- return name ? `${pkg}:${name}` : pkg;
122
- }
123
-
124
- /** Parses the BEP stream into events, ignoring partial/garbled lines. */
125
- function parseEvents(bepJson: string): BepEvent[] {
126
- const events: BepEvent[] = [];
127
- for (const line of bepJson.split('\n')) {
128
- const trimmed = line.trim();
129
- if (!trimmed) continue;
130
- try {
131
- events.push(JSON.parse(trimmed) as BepEvent);
132
- } catch {
133
- // Ignore partial/garbled lines (BEP is flushed incrementally).
134
- }
135
- }
136
- return events;
137
- }
138
-
139
- export type BepCompletion = {
140
- /**
141
- * True once Bazel has flushed the terminal event (`lastMessage`), so every
142
- * earlier event — including the .ipa's namedSetOfFiles — is durably on disk.
143
- * Gate the digest read on this, NOT on the presence of `buildFinished`:
144
- * `buildFinished` is followed by `buildToolLogs` and `buildMetrics`
145
- * (the `lastMessage` carrier), so it is not the last line.
146
- */
147
- complete: boolean;
148
- /** Whether the build itself succeeded (overallSuccess / exit code 0). */
149
- success: boolean;
150
- /** Bazel's invocation UUID — distinguishes one build from the next. */
151
- invocationId?: string;
152
- };
153
-
154
- /**
155
- * Inspects a BEP stream for build completion. The watcher reads the BEP on every
156
- * change but only acts once `complete` is true (the stream is fully flushed) and
157
- * `success` is true (the build did not fail). `invocationId` lets the watcher act
158
- * once per build even when consecutive builds reproduce the same .ipa digest
159
- * (e.g. a `git stash` revert that rebuilds to an earlier digest).
160
- */
161
- export function inspectBuildCompletion(bepJson: string): BepCompletion {
162
- let complete = false;
163
- let sawFinished = false;
164
- let success = false;
165
- let invocationId: string | undefined;
166
- for (const e of parseEvents(bepJson)) {
167
- if (e.started?.uuid) invocationId = e.started.uuid;
168
- if (e.id?.buildFinished !== undefined || e.finished !== undefined) {
169
- sawFinished = true;
170
- // proto3 JSON omits zero-valued scalars, so a SUCCESS exit code serializes
171
- // as `{name:"SUCCESS"}` with `code` omitted — checking `code === 0` would be
172
- // a dead branch. Trust overallSuccess (present=true on success, omitted on
173
- // failure) and fall back to the exit code's symbolic name.
174
- success = e.finished?.overallSuccess === true || e.finished?.exitCode?.name === 'SUCCESS';
175
- }
176
- if (e.lastMessage === true) complete = true;
177
- }
178
- return {
179
- complete,
180
- success: sawFinished && success,
181
- ...(invocationId !== undefined ? { invocationId } : {}),
182
- };
183
- }
184
-
185
- /** Indexes namedSetOfFiles events by id for transitive fileset resolution. */
186
- function indexNamedSets(events: BepEvent[]): Map<string, NonNullable<BepEvent['namedSetOfFiles']>> {
187
- const namedSets = new Map<string, NonNullable<BepEvent['namedSetOfFiles']>>();
188
- for (const e of events) {
189
- const id = e.id?.namedSet?.id;
190
- if (id && e.namedSetOfFiles) namedSets.set(id, e.namedSetOfFiles);
191
- }
192
- return namedSets;
193
- }
194
-
195
- /**
196
- * Resolves the top-level `.ipa` CAS digest for one successful target's
197
- * `completed` event, walking its default output group's filesets transitively.
198
- * Throws a typed RbeBepError when no .ipa is present, it was materialized
199
- * locally (`file://`), or it carries a non-SHA256 (e.g. BLAKE3) digest.
200
- */
201
- function resolveIpaDigest(
202
- label: string,
203
- completed: NonNullable<BepEvent['completed']>,
204
- namedSets: Map<string, NonNullable<BepEvent['namedSetOfFiles']>>,
205
- ): BepIpaDigest {
206
- // Default output group -> the fileSet (namedSetOfFiles) ids holding its outputs.
207
- const rootFileSetIds = (completed.outputGroup ?? [])
208
- .filter((og) => og.name === 'default' || !og.name)
209
- .flatMap((og) => (og.fileSets ?? []).map((fs) => fs.id))
210
- .filter((id): id is string => !!id);
211
- if (rootFileSetIds.length === 0) {
212
- throw new RbeBepError('no-output', `Build of ${label} reported no default output files.`);
213
- }
214
-
215
- const seen = new Set<string>();
216
- const queue = [...rootFileSetIds];
217
- // Set to the name of an .ipa that was seen but had no bytestream digest (its
218
- // output was downloaded locally) — distinguishes that case from "no .ipa at all".
219
- let localOnlyIpa: string | undefined;
220
- while (queue.length > 0) {
221
- const id = queue.shift()!;
222
- if (seen.has(id)) continue;
223
- seen.add(id);
224
- const set = namedSets.get(id);
225
- if (!set) continue;
226
- for (const f of set.files ?? []) {
227
- if (!f.name || !f.name.endsWith('.ipa')) continue;
228
- const digest = f.uri ? parseBytestreamDigest(f.uri) : null;
229
- if (digest) {
230
- // The instance cache is keyed by SHA-256. Reject any other digest
231
- // function — Bazel 9 defaults to BLAKE3, whose URI carries an explicit
232
- // `blake3` function segment (and whose 64-hex hash is indistinguishable
233
- // from SHA-256 by length, so the hash form alone can't catch it). Surface
234
- // it here with the fix rather than letting it fail later as a cryptic
235
- // server cache miss.
236
- const isSha256 =
237
- digest.digestFunction === undefined ?
238
- SHA256_HEX.test(digest.hash)
239
- : digest.digestFunction === 'sha256';
240
- if (!isSha256) {
241
- const got = digest.digestFunction ?? `${digest.hash.length} hex chars`;
242
- throw new RbeBepError(
243
- 'non-sha256',
244
- `Built ${f.name} with a non-SHA256 digest (${got}; Bazel 9 defaults to BLAKE3). The instance ` +
245
- `cache is keyed by SHA-256 — rebuild with --digest_function=sha256, e.g. ` +
246
- `\`bazelisk --digest_function=sha256 build --config=limrun ${label}\`.`,
247
- );
248
- }
249
- return { hash: digest.hash, sizeBytes: digest.sizeBytes, ipaName: f.name };
250
- }
251
- localOnlyIpa = f.name;
252
- }
253
- for (const nested of set.fileSets ?? []) {
254
- if (nested.id) queue.push(nested.id);
255
- }
256
- }
257
-
258
- if (localOnlyIpa) {
259
- throw new RbeBepError(
260
- 'local-only',
261
- `Found ${label}'s .ipa (${localOnlyIpa}) but it has no remote (bytestream) digest — it was ` +
262
- `built locally, not remotely executed. Ensure you built with --config=limrun so the .ipa is ` +
263
- `produced in the instance's cache.`,
264
- );
265
- }
266
- throw new RbeBepError('no-ipa', `No .ipa output found for ${label} in the build event log.`);
267
- }
268
-
269
- /**
270
- * Finds the `.ipa` produced for `label` in a BEP stream and returns its CAS
271
- * digest. Throws a typed RbeBepError when the target/.ipa is absent, its output
272
- * was materialized locally (a `file://` URI) instead of left in CAS, or it was
273
- * built with a non-SHA256 digest the instance cache cannot resolve.
274
- */
275
- export function parseTopLevelIpaDigest(bepJson: string, label: string): BepIpaDigest {
276
- const canonical = canonicalizeLabel(label);
277
- const events = parseEvents(bepJson);
278
-
279
- // The successful TargetComplete for the requested label (matched against the
280
- // canonical //pkg:name form Bazel records, so `//App` matches `//App:App`).
281
- const completed = events.find((e) => e.id?.targetCompleted?.label === canonical && e.completed?.success)
282
- ?.completed;
283
- if (!completed) {
284
- throw new RbeBepError(
285
- 'no-build',
286
- `No successful build of ${label} found in the build event log. ` +
287
- `Build it first with --config=limrun (e.g. \`bazelisk --digest_function=sha256 build --config=limrun ${label}\`).`,
288
- );
289
- }
290
-
291
- return resolveIpaDigest(label, completed, indexNamedSets(events));
292
- }