@limrun/api 0.46.14 → 0.47.1

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 (63) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/exec-client.d.mts +13 -1
  3. package/exec-client.d.mts.map +1 -1
  4. package/exec-client.d.ts +13 -1
  5. package/exec-client.d.ts.map +1 -1
  6. package/exec-client.js.map +1 -1
  7. package/exec-client.mjs.map +1 -1
  8. package/folder-sync-ignore.d.mts +4 -3
  9. package/folder-sync-ignore.d.mts.map +1 -1
  10. package/folder-sync-ignore.d.ts +4 -3
  11. package/folder-sync-ignore.d.ts.map +1 -1
  12. package/folder-sync-ignore.js +13 -7
  13. package/folder-sync-ignore.js.map +1 -1
  14. package/folder-sync-ignore.mjs +13 -7
  15. package/folder-sync-ignore.mjs.map +1 -1
  16. package/instance-client.d.mts +107 -1
  17. package/instance-client.d.mts.map +1 -1
  18. package/instance-client.d.ts +107 -1
  19. package/instance-client.d.ts.map +1 -1
  20. package/instance-client.js +75 -0
  21. package/instance-client.js.map +1 -1
  22. package/instance-client.mjs +75 -0
  23. package/instance-client.mjs.map +1 -1
  24. package/package.json +1 -1
  25. package/resources/daemon-client-shared.d.mts +9 -0
  26. package/resources/daemon-client-shared.d.mts.map +1 -1
  27. package/resources/daemon-client-shared.d.ts +9 -0
  28. package/resources/daemon-client-shared.d.ts.map +1 -1
  29. package/resources/daemon-client-shared.js +11 -1
  30. package/resources/daemon-client-shared.js.map +1 -1
  31. package/resources/daemon-client-shared.mjs +10 -1
  32. package/resources/daemon-client-shared.mjs.map +1 -1
  33. package/resources/gradle-instances-helpers.d.mts +8 -0
  34. package/resources/gradle-instances-helpers.d.mts.map +1 -1
  35. package/resources/gradle-instances-helpers.d.ts +8 -0
  36. package/resources/gradle-instances-helpers.d.ts.map +1 -1
  37. package/resources/gradle-instances-helpers.js +1 -1
  38. package/resources/gradle-instances-helpers.js.map +1 -1
  39. package/resources/gradle-instances-helpers.mjs +1 -1
  40. package/resources/gradle-instances-helpers.mjs.map +1 -1
  41. package/resources/xcode-instances-helpers.d.mts +46 -3
  42. package/resources/xcode-instances-helpers.d.mts.map +1 -1
  43. package/resources/xcode-instances-helpers.d.ts +46 -3
  44. package/resources/xcode-instances-helpers.d.ts.map +1 -1
  45. package/resources/xcode-instances-helpers.js +2 -1
  46. package/resources/xcode-instances-helpers.js.map +1 -1
  47. package/resources/xcode-instances-helpers.mjs +2 -1
  48. package/resources/xcode-instances-helpers.mjs.map +1 -1
  49. package/src/exec-client.ts +13 -1
  50. package/src/folder-sync-ignore.ts +12 -7
  51. package/src/instance-client.ts +227 -1
  52. package/src/resources/daemon-client-shared.ts +11 -1
  53. package/src/resources/gradle-instances-helpers.ts +22 -8
  54. package/src/resources/xcode-instances-helpers.ts +48 -4
  55. package/src/version.ts +1 -1
  56. package/version.d.mts +1 -1
  57. package/version.d.mts.map +1 -1
  58. package/version.d.ts +1 -1
  59. package/version.d.ts.map +1 -1
  60. package/version.js +1 -1
  61. package/version.js.map +1 -1
  62. package/version.mjs +1 -1
  63. package/version.mjs.map +1 -1
@@ -152,9 +152,76 @@ export type InstanceClient = {
152
152
  /**
153
153
  * Play an on-device WAV/MP3 file as microphone input.
154
154
  *
155
- * The file must already exist on the Android instance, for example after pushing it with ADB.
155
+ * The file must already exist on the Android instance, for example after
156
+ * uploading it with {@link pushFile} or pushing it with ADB.
156
157
  */
157
158
  playOnMicrophone: (path: string, options?: PlayOnMicrophoneOptions) => Promise<PlayOnMicrophoneResult>;
159
+ /**
160
+ * Upload a local file to the instance, without needing an ADB connection.
161
+ *
162
+ * With no destination, the instance stores the file under an internal name
163
+ * and returns its path. With a destination, behaves like `adb push`: the
164
+ * write is performed with the same permissions the ADB shell user has, so
165
+ * writable locations (e.g. `/data/local/tmp`, `/sdcard`) succeed and
166
+ * protected ones are rejected, exactly as they would be for `adb push`.
167
+ *
168
+ * @param path The path of the local file to upload.
169
+ * @param destination Optional absolute path on the instance to write to.
170
+ * @returns A promise that resolves to the absolute path of the file on the
171
+ * instance, usable with {@link playOnMicrophone}.
172
+ */
173
+ pushFile: (path: string, destination?: string) => Promise<string>;
174
+ /**
175
+ * Play a local video file as the camera feed.
176
+ *
177
+ * Uploads the file to the instance and switches the virtual camera source
178
+ * to it, replacing the live WebRTC camera until {@link clearCameraVideo} is
179
+ * called. Apps see the video through their regular Camera2/CameraX
180
+ * pipeline, and the file's audio track (if any) plays through the
181
+ * microphone in sync.
182
+ *
183
+ * By default the video loops; with `loop: false` it plays once and the
184
+ * feed freezes on the last frame.
185
+ *
186
+ * @param path The path of the local video file to play as the camera.
187
+ * @param opts Playback options (currently just `loop`).
188
+ * @throws If the file has no decodable video track or the instance does not
189
+ * support camera injection (Android 14 and older).
190
+ */
191
+ setCameraVideo: (path: string, opts?: CameraVideoOptions) => Promise<void>;
192
+ /**
193
+ * Stop video-file camera playback and restore the default WebRTC camera
194
+ * source. Safe to call when no video is playing.
195
+ */
196
+ clearCameraVideo: () => Promise<void>;
197
+ /**
198
+ * Run a shell command on the instance, without needing `adb` installed
199
+ * locally. The command runs with the same permissions the ADB shell user
200
+ * has (exactly like `adb shell`), and its stdout, stderr, and exit code are
201
+ * returned separately.
202
+ *
203
+ * The command and each argument are safely quoted before being sent, so
204
+ * argument values cannot be interpreted as extra shell syntax (no injection
205
+ * from untrusted args), mirroring `child_process.execFile`:
206
+ *
207
+ * ```ts
208
+ * await client.adbShell('pm', ['list', 'packages', '-3']);
209
+ * await client.adbShell('getprop', ['ro.build.version.sdk']);
210
+ * ```
211
+ *
212
+ * To use shell features such as pipes or redirection, invoke a shell
213
+ * explicitly and pass the script as an argument:
214
+ *
215
+ * ```ts
216
+ * await client.adbShell('sh', ['-c', 'dumpsys battery | grep level']);
217
+ * ```
218
+ *
219
+ * @param command The executable or shell builtin to run.
220
+ * @param args Arguments passed to the command; each is individually quoted.
221
+ * @throws If the command times out or the ADB transport is unavailable. A
222
+ * non-zero `exitCode` is returned in the result, not thrown.
223
+ */
224
+ adbShell: (command: string, args?: string[], options?: AdbShellOptions) => Promise<AdbShellResult>;
158
225
  /**
159
226
  * Set Android Wi-Fi bandwidth limits in Kbps. Omit a direction to leave it unchanged;
160
227
  * pass `0` to clear that direction's limit.
@@ -443,6 +510,18 @@ export type PlayOnMicrophoneOptions = {
443
510
  once?: boolean;
444
511
  };
445
512
 
513
+ /**
514
+ * Options for playing a video file as the virtual camera feed.
515
+ */
516
+ export type CameraVideoOptions = {
517
+ /**
518
+ * Whether to restart the video when it ends. When false the video
519
+ * plays once and the camera feed freezes on its last frame.
520
+ * @default true
521
+ */
522
+ loop?: boolean;
523
+ };
524
+
446
525
  export type PlayOnMicrophoneResult = {
447
526
  /** Duration of the decoded audio in microseconds. */
448
527
  duration: number;
@@ -455,6 +534,46 @@ export type WifiBandwidthOptions = {
455
534
  upKbps?: number;
456
535
  };
457
536
 
537
+ /**
538
+ * Quotes each token so the remote `sh -c` receives them as literal,
539
+ * separate arguments — values cannot inject extra shell syntax. Uses
540
+ * POSIX single-quoting: wrap in single quotes and escape embedded single
541
+ * quotes as `'\''`.
542
+ */
543
+ function quoteShellArgs(tokens: string[]): string {
544
+ return tokens.map((token) => `'${token.replace(/'/g, `'\\''`)}'`).join(' ');
545
+ }
546
+
547
+ /**
548
+ * Options for {@link InstanceClient.adbShell}.
549
+ */
550
+ export type AdbShellOptions = {
551
+ /**
552
+ * Maximum time to wait for the command to finish, in milliseconds.
553
+ * @default 30000
554
+ */
555
+ timeoutMs?: number;
556
+ /**
557
+ * Encoding used to decode stdout/stderr into strings.
558
+ * @default 'utf8'
559
+ */
560
+ encoding?: BufferEncoding;
561
+ };
562
+
563
+ /**
564
+ * Result of a shell command run via {@link InstanceClient.adbShell}.
565
+ */
566
+ export type AdbShellResult = {
567
+ /** Standard output, decoded with the requested encoding. */
568
+ stdout: string;
569
+ /** Standard error, decoded with the requested encoding. */
570
+ stderr: string;
571
+ /** Process exit code (`-1` if the device reported no exit status). */
572
+ exitCode: number;
573
+ /** True if output exceeded the capture limit and was truncated. */
574
+ truncated: boolean;
575
+ };
576
+
458
577
  type EmptyCommandResult = Record<string, never>;
459
578
 
460
579
  type ScreenshotErrorResponse = {
@@ -589,6 +708,15 @@ type PlayOnMicrophoneResultMessage = {
589
708
  error?: CommandError;
590
709
  };
591
710
 
711
+ // The server sends camera control results "flat": fields at the top level and
712
+ // `error` as a plain string, with no payload envelope.
713
+ type CameraControlResultMessage = {
714
+ type: 'cameraControlResult';
715
+ id: string;
716
+ payload?: EmptyCommandResult;
717
+ error?: string;
718
+ };
719
+
592
720
  type SetWifiBandwidthResultMessage = {
593
721
  type: 'setWifiBandwidthResult';
594
722
  id: string;
@@ -596,6 +724,19 @@ type SetWifiBandwidthResultMessage = {
596
724
  error?: CommandError;
597
725
  };
598
726
 
727
+ // The server sends adb shell results "flat": stdout/stderr (base64) and
728
+ // exitCode at the top level, with `error` as a plain string.
729
+ type AdbShellResultMessage = {
730
+ type: 'adbShellResult';
731
+ id: string;
732
+ stdout?: string;
733
+ stderr?: string;
734
+ exitCode?: number;
735
+ truncated?: boolean;
736
+ payload?: EmptyCommandResult;
737
+ error?: string;
738
+ };
739
+
599
740
  type StartVideoRecordingResultMessage = {
600
741
  type: 'startRecordingResult';
601
742
  id: string;
@@ -625,6 +766,8 @@ type KnownCommandResultMessage =
625
766
  | WatchAppResultMessage
626
767
  | UnwatchAppResultMessage
627
768
  | PlayOnMicrophoneResultMessage
769
+ | CameraControlResultMessage
770
+ | AdbShellResultMessage
628
771
  | SetWifiBandwidthResultMessage
629
772
  | StartVideoRecordingResultMessage
630
773
  | StopVideoRecordingResultMessage;
@@ -651,6 +794,8 @@ type CommandRequestMap = {
651
794
  watchApp: { packageName: string; execId: string };
652
795
  unwatchApp: { execId: string };
653
796
  playOnMicrophone: { path: string; once?: boolean };
797
+ cameraControl: { action: 'setSource'; source: 'video'; arg: string; loop?: boolean } | { action: 'reset' };
798
+ adbShell: { command: string; timeoutMs?: number };
654
799
  setWifiBandwidth: WifiBandwidthOptions;
655
800
  startRecording: { quality?: RecordingQuality };
656
801
  stopRecording: { upload?: { presignedUrl: string } };
@@ -671,6 +816,8 @@ type CommandResultMap = {
671
816
  watchApp: { packageName?: string };
672
817
  unwatchApp: EmptyCommandResult;
673
818
  playOnMicrophone: PlayOnMicrophoneResult;
819
+ cameraControl: EmptyCommandResult;
820
+ adbShell: AdbShellResultMessage;
674
821
  setWifiBandwidth: EmptyCommandResult;
675
822
  startRecording: EmptyCommandResult;
676
823
  stopRecording: EmptyCommandResult;
@@ -812,6 +959,11 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
812
959
  if ('message' in message && typeof message.message === 'string') {
813
960
  return message.message;
814
961
  }
962
+ // "Flat" results (e.g. cameraControlResult, playOnMicrophoneResult)
963
+ // carry the error as a plain string instead of a CommandError object.
964
+ if ('error' in message && typeof message.error === 'string' && message.error) {
965
+ return message.error;
966
+ }
815
967
  if ('error' in message && message.error && typeof message.error === 'object') {
816
968
  const obj = message.error as CommandError;
817
969
  if (typeof obj.message === 'string' && obj.message) {
@@ -845,6 +997,8 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
845
997
  case 'watchAppResult':
846
998
  case 'unwatchAppResult':
847
999
  case 'playOnMicrophoneResult':
1000
+ case 'cameraControlResult':
1001
+ case 'adbShellResult':
848
1002
  case 'setWifiBandwidthResult':
849
1003
  case 'startRecordingResult':
850
1004
  case 'stopRecordingResult':
@@ -1116,6 +1270,10 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1116
1270
  terminateApp,
1117
1271
  watchApp,
1118
1272
  playOnMicrophone,
1273
+ pushFile,
1274
+ setCameraVideo,
1275
+ clearCameraVideo,
1276
+ adbShell,
1119
1277
  setWifiBandwidth,
1120
1278
  startRecording,
1121
1279
  stopRecording,
@@ -1279,6 +1437,74 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1279
1437
  });
1280
1438
  };
1281
1439
 
1440
+ const pushFile = async (filePath: string, destination?: string): Promise<string> => {
1441
+ const uploadUrl =
1442
+ destination === undefined ?
1443
+ `${options.apiUrl}/files`
1444
+ : `${options.apiUrl}/files?path=${encodeURIComponent(destination)}`;
1445
+ const fileStream = fs.createReadStream(filePath);
1446
+ // Node's fetch (undici) supports streaming request bodies but TS DOM types may not include
1447
+ // `duplex` and may not accept Node ReadStreams as BodyInit in some configs.
1448
+ const response = await nodeProxyTransport.fetch(uploadUrl, {
1449
+ method: 'PUT',
1450
+ headers: {
1451
+ 'Content-Type': 'application/octet-stream',
1452
+ 'Content-Length': fs.statSync(filePath).size.toString(),
1453
+ Authorization: `Bearer ${options.token}`,
1454
+ },
1455
+ body: fileStream as any,
1456
+ duplex: 'half' as any,
1457
+ } as any);
1458
+ if (!response.ok) {
1459
+ const errorBody = await response.text();
1460
+ logger.debug(`Upload failed: ${response.status} ${errorBody}`);
1461
+ throw new Error(`Upload failed: ${response.status} ${errorBody}`);
1462
+ }
1463
+ const result = (await response.json()) as { path: string };
1464
+ return result.path;
1465
+ };
1466
+
1467
+ const setCameraVideo = async (filePath: string, cameraOptions?: CameraVideoOptions): Promise<void> => {
1468
+ const remotePath = await pushFile(filePath);
1469
+ await sendRequest('cameraControl', {
1470
+ action: 'setSource',
1471
+ source: 'video',
1472
+ arg: remotePath,
1473
+ loop: cameraOptions?.loop ?? true,
1474
+ });
1475
+ };
1476
+
1477
+ const clearCameraVideo = async (): Promise<void> => {
1478
+ await sendRequest('cameraControl', { action: 'reset' });
1479
+ };
1480
+
1481
+ const adbShell = async (
1482
+ command: string,
1483
+ args: string[] = [],
1484
+ adbOptions?: AdbShellOptions,
1485
+ ): Promise<AdbShellResult> => {
1486
+ if (!command) {
1487
+ throw new Error('command must be a non-empty string');
1488
+ }
1489
+ const commandLine = quoteShellArgs([command, ...args]);
1490
+ const timeoutMs = adbOptions?.timeoutMs;
1491
+ // Wait a little longer than the device-side timeout so the server's own
1492
+ // error surfaces instead of a generic client timeout.
1493
+ const requestTimeoutMs = (timeoutMs ?? 30000) + 15000;
1494
+ const result = await sendRequest(
1495
+ 'adbShell',
1496
+ { command: commandLine, ...(timeoutMs === undefined ? {} : { timeoutMs }) },
1497
+ requestTimeoutMs,
1498
+ );
1499
+ const encoding = adbOptions?.encoding ?? 'utf8';
1500
+ return {
1501
+ stdout: Buffer.from(result.stdout ?? '', 'base64').toString(encoding),
1502
+ stderr: Buffer.from(result.stderr ?? '', 'base64').toString(encoding),
1503
+ exitCode: typeof result.exitCode === 'number' ? result.exitCode : -1,
1504
+ truncated: result.truncated === true,
1505
+ };
1506
+ };
1507
+
1282
1508
  const setWifiBandwidth = async (bandwidthOptions: WifiBandwidthOptions): Promise<void> => {
1283
1509
  const request: CommandRequestMap['setWifiBandwidth'] = {};
1284
1510
  if (bandwidthOptions.downKbps !== undefined) {
@@ -88,9 +88,19 @@ export type AssetUploadOptions = {
88
88
  deeplink?: string;
89
89
  };
90
90
 
91
+ /**
92
+ * Default TTL for build-product uploads. Every build upload without an
93
+ * explicit ttl pushes the asset's expiry to 14 days from that upload, so
94
+ * abandoned build artifacts don't accumulate in Asset Storage forever.
95
+ * Assets uploaded through the general asset API keep no expiry by default.
96
+ */
97
+ export const DEFAULT_BUILD_ASSET_TTL = '336h';
98
+
91
99
  /**
92
100
  * Mints presigned upload/download URLs for a named asset via assets.getOrCreate,
93
101
  * wrapping failures with the asset name (and the original error as cause).
102
+ * All build-product upload paths (xcodebuild, gradlebuild, RBE) go through
103
+ * here, which is what scopes DEFAULT_BUILD_ASSET_TTL to build uploads.
94
104
  */
95
105
  export function mintAssetUploadUrls(
96
106
  assets: {
@@ -100,7 +110,7 @@ export function mintAssetUploadUrls(
100
110
  ttl?: string,
101
111
  uploadOptions?: AssetUploadOptions,
102
112
  ): Promise<AssetUploadUrls> {
103
- return assets.getOrCreate({ name, ...(ttl && { ttl }), ...uploadOptions }).catch((err) => {
113
+ return assets.getOrCreate({ name, ttl: ttl || DEFAULT_BUILD_ASSET_TTL, ...uploadOptions }).catch((err) => {
104
114
  const message = `Failed to create upload URL for asset '${name}': ${
105
115
  err instanceof Error ? err.message : err
106
116
  }`;
@@ -56,7 +56,19 @@ export type GradleBuildOptions = {
56
56
  /** Relative path to the Gradle root when auto-discovery is ambiguous. */
57
57
  projectPath?: string;
58
58
  /** Upload the built artifact as a named org asset, or to a presigned URL. */
59
- upload?: { assetName: string; signedUploadUrl?: never } | { signedUploadUrl: string; assetName?: never };
59
+ upload?:
60
+ | {
61
+ assetName: string;
62
+ /**
63
+ * Asset TTL as a Go duration (e.g. "24h", "30m"; "1d" is invalid),
64
+ * forwarded to assets.getOrCreate. Defaults to 336h (14 days): each
65
+ * build upload without an explicit ttl pushes the asset's expiry to
66
+ * 14 days from that upload.
67
+ */
68
+ ttl?: string;
69
+ signedUploadUrl?: never;
70
+ }
71
+ | { signedUploadUrl: string; assetName?: never; ttl?: never };
60
72
  /** React Native / Expo tuning. */
61
73
  reactNative?: GradleReactNativeConfig;
62
74
  /** Release signing config; presence makes bundleRelease the default task. */
@@ -183,13 +195,15 @@ class GradleInstancesHelpers extends GradleInstances {
183
195
  };
184
196
 
185
197
  if (options?.upload && 'assetName' in options.upload) {
186
- const requestPromise = mintAssetUploadUrls(client.assets, options.upload.assetName).then(
187
- (asset) => {
188
- request.signedUploadUrl = asset.signedUploadUrl;
189
- request.additionalMetadata = { signedDownloadUrl: asset.signedDownloadUrl };
190
- return request;
191
- },
192
- );
198
+ const requestPromise = mintAssetUploadUrls(
199
+ client.assets,
200
+ options.upload.assetName,
201
+ options.upload.ttl,
202
+ ).then((asset) => {
203
+ request.signedUploadUrl = asset.signedUploadUrl;
204
+ request.additionalMetadata = { signedDownloadUrl: asset.signedDownloadUrl };
205
+ return request;
206
+ });
193
207
  return exec(requestPromise, { apiUrl, token, log });
194
208
  }
195
209
 
@@ -90,8 +90,12 @@ export type SyncOptions = {
90
90
  export type XcodeRunOptions = {
91
91
  /** Working directory relative to the synced workspace root. Defaults to ".". */
92
92
  cwd?: string;
93
- /** Environment variables to add to limbuild's curated sandbox environment. */
94
- env?: Record<string, string>;
93
+ /**
94
+ * Ordered KEY=VALUE entries added to limbuild's curated sandbox
95
+ * environment. Server-managed variables (PATH, HOME, ...) cannot be
96
+ * overridden; when a key repeats, the last entry wins.
97
+ */
98
+ env?: string[];
95
99
  /** Server-side timeout in seconds. Defaults to 3600; maximum 21600. */
96
100
  timeoutSeconds?: number;
97
101
  };
@@ -105,6 +109,14 @@ export type XcodeProjectConfig = {
105
109
  * Debug for native Xcode builds and Release for React Native / Expo builds.
106
110
  */
107
111
  configuration?: 'Debug' | 'Release';
112
+ /**
113
+ * Full filename of the built app bundle, including the .app extension
114
+ * (e.g. "MyApp-dev.app"). When set, limbuild takes the artifact from
115
+ * this name in the built products directory verbatim, skipping artifact
116
+ * discovery. Use when the scheme name differs from the product name and
117
+ * discovery picks the wrong bundle.
118
+ */
119
+ artifactName?: string;
108
120
  };
109
121
 
110
122
  export type XcodeGenConfig = {
@@ -173,12 +185,28 @@ export type ReactNativeBuildConfig = {
173
185
  * server URL you want opened.
174
186
  */
175
187
  devServerURL?: string;
188
+ /**
189
+ * Run `expo prebuild` even when the synced workspace already contains the
190
+ * native ios/ tree, regenerating it in place. Use when the tree holds
191
+ * client-synced files the server treats as user-owned (for example
192
+ * committed Podfile customizations) while the Xcode project itself is
193
+ * generated and gitignored. Requires a detectable Expo project. Older
194
+ * limbuild servers silently ignore this option.
195
+ */
196
+ expoForcePrebuild?: boolean;
176
197
  };
177
198
 
178
199
  export type XcodeBuildOptions = {
179
200
  upload?:
180
201
  | {
181
202
  assetName: string;
203
+ /**
204
+ * Asset TTL as a Go duration (e.g. "24h", "30m"; "1d" is invalid),
205
+ * forwarded to assets.getOrCreate. Defaults to 336h (14 days): each
206
+ * build upload without an explicit ttl pushes the asset's expiry to
207
+ * 14 days from that upload.
208
+ */
209
+ ttl?: string;
182
210
  /**
183
211
  * App metadata to record on the asset up front. limbuild extracts
184
212
  * the same metadata from the built bundle after the build and
@@ -207,6 +235,16 @@ export type XcodeBuildOptions = {
207
235
  */
208
236
  xcodegen?: XcodeGenConfig;
209
237
  buildSettings?: Record<string, string>;
238
+ /**
239
+ * Ordered KEY=VALUE entries added to the environment of every build
240
+ * pipeline command (dependency installs, expo prebuild, pod install,
241
+ * xcodebuild and its script phases). Server-managed variables (PATH,
242
+ * HOME, ...) cannot be overridden; when a key repeats, the last entry
243
+ * wins. Dependency installs always include devDependencies, so
244
+ * NODE_ENV=production cannot break them. Older limbuild servers
245
+ * silently ignore this option.
246
+ */
247
+ env?: string[];
210
248
  /**
211
249
  * Run `git init` in the synced workspace before project generation,
212
250
  * dependency resolution, and xcodebuild.
@@ -323,7 +361,12 @@ export type RbeUploadOptions =
323
361
  | {
324
362
  /** Upload as a named asset: the SDK mints the presigned upload URL via assets.getOrCreate. */
325
363
  assetName: string;
326
- /** Optional asset TTL as a Go duration (e.g. "24h", "30m"; "1d" is invalid), forwarded to assets.getOrCreate. */
364
+ /**
365
+ * Optional asset TTL as a Go duration (e.g. "24h", "30m"; "1d" is
366
+ * invalid), forwarded to assets.getOrCreate. Defaults to 336h (14
367
+ * days): each upload without an explicit ttl pushes the asset's
368
+ * expiry to 14 days from that upload.
369
+ */
327
370
  ttl?: string;
328
371
  signedUploadUrl?: never;
329
372
  }
@@ -813,6 +856,7 @@ export class XcodeInstances extends GeneratedXcodeInstances {
813
856
  // testflight until the exec API is revised separately.
814
857
  ...(options?.appstore && { testflight: options.appstore }),
815
858
  ...(options?.buildSettings && { buildSettings: options.buildSettings }),
859
+ ...(options?.env?.length && { env: options.env }),
816
860
  ...(options?.gitInit !== undefined && { gitInit: options.gitInit }),
817
861
  ...(options?.webhook && { webhook: options.webhook }),
818
862
  };
@@ -821,7 +865,7 @@ export class XcodeInstances extends GeneratedXcodeInstances {
821
865
  const requestPromise = mintAssetUploadUrls(
822
866
  client.assets,
823
867
  options.upload.assetName,
824
- undefined,
868
+ options.upload.ttl,
825
869
  options.upload.uploadOptions,
826
870
  ).then((asset) => {
827
871
  request.signedUploadUrl = asset.signedUploadUrl;
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.46.14'; // x-release-please-version
1
+ export const VERSION = '0.47.1'; // x-release-please-version
package/version.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.46.14";
1
+ export declare const VERSION = "0.47.1";
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,YAAY,CAAC"}
1
+ {"version":3,"file":"version.d.mts","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,WAAW,CAAC"}
package/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.46.14";
1
+ export declare const VERSION = "0.47.1";
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,YAAY,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,WAAW,CAAC"}
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.46.14'; // x-release-please-version
4
+ exports.VERSION = '0.47.1'; // x-release-please-version
5
5
  //# sourceMappingURL=version.js.map
package/version.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":";;;AAAa,QAAA,OAAO,GAAG,SAAS,CAAC,CAAC,2BAA2B"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":";;;AAAa,QAAA,OAAO,GAAG,QAAQ,CAAC,CAAC,2BAA2B"}
package/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.46.14'; // x-release-please-version
1
+ export const VERSION = '0.47.1'; // 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,SAAS,CAAC,CAAC,2BAA2B"}
1
+ {"version":3,"file":"version.mjs","sourceRoot":"","sources":["src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,2BAA2B"}