@limrun/api 0.31.0 → 0.31.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.
@@ -45,4 +45,5 @@ export {
45
45
  type SimulatorAttachment,
46
46
  type SimulatorDeviceInfo,
47
47
  type SimulatorInstallState,
48
+ type RbeInstallResult,
48
49
  } from './xcode-instances-helpers';
@@ -4,6 +4,7 @@ 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 { parseTopLevelIpaDigest } from '../rbe-bep';
7
8
  import { exec, type ExecChildProcess, type ExecRequest } from '../exec-client';
8
9
  import {
9
10
  syncFolder as syncFolderImpl,
@@ -179,6 +180,27 @@ export type RbeTunnelOptions = {
179
180
  logLevel?: LogLevel;
180
181
  };
181
182
 
183
+ /** Content-addressed digest of a build artifact in the instance's RBE cache. */
184
+ type RbeArtifactDigest = {
185
+ /** Lowercase hex SHA-256 of the blob (matches --digest_function=sha256). */
186
+ hash: string;
187
+ /** Size of the blob in bytes. */
188
+ sizeBytes: number;
189
+ };
190
+
191
+ export type RbeInstallResult = {
192
+ /** True when the app was synced and installed on the attached simulator. */
193
+ installed: boolean;
194
+ /** CFBundleIdentifier of the installed app, as reported by the simulator. */
195
+ bundleId?: string;
196
+ /** The .app bundle name discovered inside the .ipa (Payload/<appName>.app). */
197
+ appName?: string;
198
+ /** Differential-sync duration in milliseconds. */
199
+ syncDurationMs?: number;
200
+ /** Simulator install duration in milliseconds. */
201
+ installDurationMs?: number;
202
+ };
203
+
182
204
  export type XcodeClient = {
183
205
  /**
184
206
  * Sync source code to the xcode instance. In watch mode, keeps syncing on changes.
@@ -198,7 +220,9 @@ export type XcodeClient = {
198
220
 
199
221
  /**
200
222
  * Attach a simulator to this xcode instance.
201
- * After attaching, builds will auto-install on the simulator.
223
+ * After attaching, the latest xcodebuild build (if any) is installed on the
224
+ * simulator. RBE builds are not auto-installed on attach — install them
225
+ * explicitly with `installRbeBuildFromBep` while a simulator is attached.
202
226
  */
203
227
  attachSimulator: (
204
228
  simulator: IosInstance | { apiUrl: string; token: string },
@@ -221,12 +245,39 @@ export type XcodeClient = {
221
245
  /** Stop the RBE stack. */
222
246
  stopRbe: () => Promise<RbeStatus>;
223
247
 
248
+ /**
249
+ * Install a Bazel RBE build on the attached simulator, server-side, from its
250
+ * build event log (BEP). Parses the top-level `.ipa`'s CAS digest for `target`
251
+ * out of `bep` (the contents of `--build_event_json_file`); the instance fetches
252
+ * the blob from its embedded cache, unpacks the .app, and pushes it to the
253
+ * attached simulator via the differential-sync path — no client round-trip.
254
+ * Requires a running RBE stack and an attached simulator (installed=false when
255
+ * none is attached — attach one and call again). Throws a descriptive error if
256
+ * the target/.ipa is absent, was downloaded locally, or was built with a
257
+ * non-SHA256 digest (e.g. BLAKE3) the instance cache can't resolve. `ipaName` is
258
+ * the `.ipa` file name Bazel reported.
259
+ */
260
+ installRbeBuildFromBep: (opts: {
261
+ bep: string;
262
+ target: string;
263
+ }) => Promise<RbeInstallResult & { ipaName: string }>;
264
+
224
265
  /**
225
266
  * Open a local TCP listener bridged to the instance's RBE gRPC frontend
226
267
  * over a multiplexed websocket. Point bazel at it with
227
268
  * --remote_executor=grpc://127.0.0.1:<port>.
228
269
  */
229
270
  startRbeTunnel: (opts?: RbeTunnelOptions) => Promise<Tunnel>;
271
+
272
+ /**
273
+ * Create a new iOS simulator instance and attach it to this xcode instance.
274
+ * Deletes the simulator if attach fails so it is never leaked. Returns the new
275
+ * simulator's instance id (record it for teardown) and the instance itself.
276
+ */
277
+ attachNewSimulator: () => Promise<{ iosInstanceId: string; simulator: IosInstance }>;
278
+
279
+ /** Best-effort delete of a simulator instance. Never throws; returns success. */
280
+ deleteSimulator: (iosInstanceId: string) => Promise<boolean>;
230
281
  };
231
282
 
232
283
  export type XcodeCreateClientParams =
@@ -366,6 +417,84 @@ export class XcodeInstances extends GeneratedXcodeInstances {
366
417
  return sandboxInfoPromise;
367
418
  };
368
419
 
420
+ // Shared local closures. The methods below live in an object literal over
421
+ // these closures (not `this`), so anything reused across methods is defined
422
+ // here once and called from each method.
423
+ const postRbeInstall = async (
424
+ ipaDigest: RbeArtifactDigest,
425
+ target?: string,
426
+ ): Promise<RbeInstallResult> => {
427
+ const res = await nodeProxyTransport.fetch(`${apiUrl}/rbe/install`, {
428
+ method: 'POST',
429
+ headers: {
430
+ 'Content-Type': 'application/json',
431
+ Authorization: `Bearer ${token}`,
432
+ },
433
+ body: JSON.stringify({ ipaDigest, ...(target ? { target } : {}) }),
434
+ });
435
+ return readRbeResponse<RbeInstallResult>(res, 'POST /rbe/install');
436
+ };
437
+
438
+ const installFromBep = async (
439
+ bep: string,
440
+ target: string,
441
+ ): Promise<RbeInstallResult & { ipaName: string }> => {
442
+ const digest = parseTopLevelIpaDigest(bep, target); // throws BLAKE3 / no-ipa errors
443
+ const result = await postRbeInstall({ hash: digest.hash, sizeBytes: digest.sizeBytes }, target);
444
+ return { ...result, ipaName: digest.ipaName };
445
+ };
446
+
447
+ const attachSimulatorImpl = async (
448
+ simulator: IosInstance | { apiUrl: string; token: string },
449
+ ): Promise<SimulatorAttachResult> => {
450
+ let simApiUrl: string;
451
+ let simToken: string;
452
+ if ('status' in simulator) {
453
+ if (!simulator.status.apiUrl) {
454
+ throw new Error('Simulator instance not ready: apiUrl is not available');
455
+ }
456
+ simApiUrl = simulator.status.apiUrl;
457
+ simToken = simulator.status.token;
458
+ } else {
459
+ simApiUrl = simulator.apiUrl;
460
+ simToken = simulator.token;
461
+ }
462
+ const res = await nodeProxyTransport.fetch(`${apiUrl}/simulator`, {
463
+ method: 'POST',
464
+ headers: {
465
+ 'Content-Type': 'application/json',
466
+ Authorization: `Bearer ${token}`,
467
+ },
468
+ body: JSON.stringify({ apiUrl: simApiUrl, token: simToken }),
469
+ });
470
+ return readJsonResponse<SimulatorAttachResult>(res, 'POST /simulator');
471
+ };
472
+
473
+ const deleteSimulatorImpl = async (iosInstanceId: string): Promise<boolean> => {
474
+ try {
475
+ await client.iosInstances.delete(iosInstanceId);
476
+ return true;
477
+ } catch {
478
+ return false;
479
+ }
480
+ };
481
+
482
+ const attachNewSimulatorImpl = async (): Promise<{
483
+ iosInstanceId: string;
484
+ simulator: IosInstance;
485
+ }> => {
486
+ const simulator = await client.iosInstances.create({ wait: true });
487
+ // The sim exists from here; if attach fails, delete it so we never leak an
488
+ // orphan the caller can't reap.
489
+ try {
490
+ await attachSimulatorImpl(simulator);
491
+ } catch (err) {
492
+ await deleteSimulatorImpl(simulator.metadata.id);
493
+ throw err;
494
+ }
495
+ return { iosInstanceId: simulator.metadata.id, simulator };
496
+ };
497
+
369
498
  return {
370
499
  async sync(localCodePath: string, opts?: SyncOptions): Promise<SyncResult> {
371
500
  const resolvedPath = path.resolve(localCodePath);
@@ -486,32 +615,9 @@ export class XcodeInstances extends GeneratedXcodeInstances {
486
615
  return readJsonResponse<SimulatorStatus>(res, 'GET /simulator');
487
616
  },
488
617
 
489
- async attachSimulator(
490
- simulator: IosInstance | { apiUrl: string; token: string },
491
- ): Promise<SimulatorAttachResult> {
492
- let simApiUrl: string;
493
- let simToken: string;
494
- if ('status' in simulator) {
495
- if (!simulator.status.apiUrl) {
496
- throw new Error('Simulator instance not ready: apiUrl is not available');
497
- }
498
- simApiUrl = simulator.status.apiUrl;
499
- simToken = simulator.status.token;
500
- } else {
501
- simApiUrl = simulator.apiUrl;
502
- simToken = simulator.token;
503
- }
504
-
505
- const res = await nodeProxyTransport.fetch(`${apiUrl}/simulator`, {
506
- method: 'POST',
507
- headers: {
508
- 'Content-Type': 'application/json',
509
- Authorization: `Bearer ${token}`,
510
- },
511
- body: JSON.stringify({ apiUrl: simApiUrl, token: simToken }),
512
- });
513
- return readJsonResponse<SimulatorAttachResult>(res, 'POST /simulator');
514
- },
618
+ attachSimulator: attachSimulatorImpl,
619
+ attachNewSimulator: attachNewSimulatorImpl,
620
+ deleteSimulator: deleteSimulatorImpl,
515
621
 
516
622
  async startRbe(opts?: RbeStartOptions): Promise<RbeStatus> {
517
623
  const res = await nodeProxyTransport.fetch(`${apiUrl}/rbe`, {
@@ -545,6 +651,9 @@ export class XcodeInstances extends GeneratedXcodeInstances {
545
651
  return readRbeResponse<RbeStatus>(res, 'DELETE /rbe');
546
652
  },
547
653
 
654
+ installRbeBuildFromBep: (opts: { bep: string; target: string }) =>
655
+ installFromBep(opts.bep, opts.target),
656
+
548
657
  async startRbeTunnel(opts?: RbeTunnelOptions): Promise<Tunnel> {
549
658
  return startTcpTunnel(
550
659
  deriveRbeTunnelUrl(apiUrl),
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.31.0'; // x-release-please-version
1
+ export const VERSION = '0.31.2'; // x-release-please-version
package/version.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.31.0";
1
+ export declare const VERSION = "0.31.2";
2
2
  //# sourceMappingURL=version.d.mts.map
package/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.31.0";
1
+ export declare const VERSION = "0.31.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.31.0'; // x-release-please-version
4
+ exports.VERSION = '0.31.2'; // x-release-please-version
5
5
  //# sourceMappingURL=version.js.map
package/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.31.0'; // x-release-please-version
1
+ export const VERSION = '0.31.2'; // x-release-please-version
2
2
  //# sourceMappingURL=version.mjs.map