@run-cloud/sdk 0.26.0 → 0.28.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.
package/dist/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import WebSocket from "ws";
2
2
  import { context, propagation } from "@opentelemetry/api";
3
3
  import { createHash, randomBytes, randomUUID } from "node:crypto";
4
+ import { cp, copyFile, lstat, mkdir, mkdtemp, readFile, rm, stat } from "node:fs/promises";
5
+ import { tmpdir } from "node:os";
6
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
7
+ import ignore from "ignore";
8
+ import { create as createTar } from "tar";
4
9
  import { RemotionClient } from "./remotion.js";
5
10
  export * from "./remotion.js";
6
11
  function randomW3cId(bytes) {
@@ -711,7 +716,7 @@ async function md5Hex(blob) {
711
716
  }
712
717
  return hash.digest("hex");
713
718
  }
714
- function isRetryableUploadError(error) {
719
+ function isRetryableRequestError(error) {
715
720
  if (!(error instanceof RunCloudError))
716
721
  return true;
717
722
  return error.status === 408 || error.status === 429 || error.status >= 500;
@@ -719,6 +724,207 @@ function isRetryableUploadError(error) {
719
724
  async function retryDelay(attempt) {
720
725
  await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));
721
726
  }
727
+ async function retryXcodeOperation(operation) {
728
+ let lastError;
729
+ for (let attempt = 0; attempt < 4; attempt += 1) {
730
+ try {
731
+ return await operation();
732
+ }
733
+ catch (error) {
734
+ lastError = error;
735
+ if (!isRetryableRequestError(error) || attempt === 3)
736
+ throw error;
737
+ await retryDelay(attempt);
738
+ }
739
+ }
740
+ throw lastError;
741
+ }
742
+ const MAX_XCODE_SOURCE_ARCHIVE_BYTES = 100 * 1024 * 1024;
743
+ function xcodeMatchers(value) {
744
+ return value === undefined ? [] : Array.isArray(value) ? value : [value];
745
+ }
746
+ function xcodePathMatches(matcher, relativePath) {
747
+ if (typeof matcher === "function")
748
+ return matcher(relativePath);
749
+ const expression = typeof matcher === "string" ? new RegExp(matcher) : matcher;
750
+ expression.lastIndex = 0;
751
+ return expression.test(relativePath);
752
+ }
753
+ function defaultXcodeSourceExclude(relativePath) {
754
+ const normalized = relativePath.replace(/\/$/, "");
755
+ const parts = normalized.split("/").filter(Boolean);
756
+ const name = parts.at(-1) ?? "";
757
+ if (parts.length === 0)
758
+ return false;
759
+ if (name === ".DS_Store" || name.endsWith(".dSYM"))
760
+ return true;
761
+ if (parts.includes(".git") || parts.includes(".runcloud-home"))
762
+ return true;
763
+ if (parts.includes("xcuserdata"))
764
+ return true;
765
+ if (["build", ".build", "DerivedData", "Index.noindex", "ModuleCache.noindex", ".index-build", ".swiftpm", "Pods"].some((item) => parts.includes(item))) {
766
+ return true;
767
+ }
768
+ return parts.some((part, index) => part === "Carthage" && parts[index + 1] === "Build");
769
+ }
770
+ function safeRemoteAdditionalPath(value) {
771
+ const normalized = value.replaceAll("\\", "/");
772
+ const home = normalized.startsWith("~/");
773
+ const path = home ? normalized.slice(2) : normalized.replace(/^\.\//, "");
774
+ if (!path
775
+ || path.startsWith("/")
776
+ || /^[A-Za-z]:\//.test(path)
777
+ || path.split("/").some((part) => part === ".." || part === "")
778
+ || (!home && path.split("/")[0] === ".runcloud-home")) {
779
+ throw new Error(`additional file remotePath must stay inside the workspace or ~/ directory: ${value}`);
780
+ }
781
+ return { home, path };
782
+ }
783
+ /** Create the source archive accepted by the run.cloud Xcode build endpoint. */
784
+ export async function packXcodeSource(sourcePath, options = {}) {
785
+ const sourceRoot = resolve(sourcePath);
786
+ const sourceStat = await stat(sourceRoot).catch(() => null);
787
+ if (!sourceStat?.isDirectory())
788
+ throw new Error(`Xcode source path is not a directory: ${sourcePath}`);
789
+ const temporary = await mkdtemp(join(tmpdir(), "runcloud-xcode-"));
790
+ const workspace = join(temporary, "workspace");
791
+ const archivePath = join(temporary, "source.tar.gz");
792
+ try {
793
+ const gitignorePath = join(sourceRoot, ".gitignore");
794
+ const gitignoreText = await readFile(gitignorePath, "utf8").catch(() => "");
795
+ const ignoredByGit = ignore();
796
+ if (gitignoreText)
797
+ ignoredByGit.add(gitignoreText);
798
+ const ignored = xcodeMatchers(options.ignore);
799
+ const included = xcodeMatchers(options.include);
800
+ await cp(sourceRoot, workspace, {
801
+ recursive: true,
802
+ dereference: false,
803
+ preserveTimestamps: true,
804
+ filter: async (source) => {
805
+ if (source === sourceRoot)
806
+ return true;
807
+ const relativeNative = relative(sourceRoot, source);
808
+ const relativePath = relativeNative.split(sep).join("/");
809
+ const sourceInfo = await lstat(source);
810
+ const candidate = sourceInfo.isDirectory() ? `${relativePath}/` : relativePath;
811
+ const forced = relativePath.endsWith(".xcconfig")
812
+ || included.some((matcher) => xcodePathMatches(matcher, candidate));
813
+ if (!forced) {
814
+ if (defaultXcodeSourceExclude(candidate))
815
+ return false;
816
+ if (ignoredByGit.ignores(candidate))
817
+ return false;
818
+ if (ignored.some((matcher) => xcodePathMatches(matcher, candidate)))
819
+ return false;
820
+ }
821
+ if (sourceInfo.isSymbolicLink()) {
822
+ throw new Error(`Xcode source archives do not follow symbolic links: ${relativePath}`);
823
+ }
824
+ return true;
825
+ },
826
+ });
827
+ for (const additional of options.additionalFiles ?? []) {
828
+ const localPath = resolve(additional.localPath);
829
+ const info = await stat(localPath).catch(() => null);
830
+ if (!info?.isFile())
831
+ throw new Error(`additional file is not a regular file: ${additional.localPath}`);
832
+ const remote = safeRemoteAdditionalPath(additional.remotePath);
833
+ const target = resolve(workspace, remote.home ? ".runcloud-home" : ".", remote.path);
834
+ const targetRoot = resolve(workspace, remote.home ? ".runcloud-home" : ".");
835
+ if (target !== targetRoot && !target.startsWith(`${targetRoot}${sep}`)) {
836
+ throw new Error(`additional file escaped its destination: ${additional.remotePath}`);
837
+ }
838
+ await mkdir(dirname(target), { recursive: true });
839
+ await copyFile(localPath, target);
840
+ }
841
+ await createTar({ cwd: workspace, file: archivePath, gzip: true, portable: true, noMtime: true }, ["."]);
842
+ const archive = await readFile(archivePath);
843
+ if (archive.length > MAX_XCODE_SOURCE_ARCHIVE_BYTES) {
844
+ throw new Error("Xcode source archive exceeds the 100 MiB compressed limit");
845
+ }
846
+ return { archive: new Uint8Array(archive), filename: `${basename(sourceRoot) || "source"}.tar.gz` };
847
+ }
848
+ finally {
849
+ await rm(temporary, { recursive: true, force: true });
850
+ }
851
+ }
852
+ function xcodeBuildRequest(options) {
853
+ const rest = { ...options };
854
+ for (const key of ["source", "idempotencyKey", "appstore", "appStore", "reactNative", "signing"]) {
855
+ delete rest[key];
856
+ }
857
+ const { appstore, appStore, reactNative, signing } = options;
858
+ const profiles = signing
859
+ ? signing.provisioningProfilesBase64
860
+ ?? (signing.provisioningProfileBase64 ? [signing.provisioningProfileBase64] : undefined)
861
+ : undefined;
862
+ return {
863
+ ...rest,
864
+ ...(reactNative ? {
865
+ reactNative: {
866
+ ...reactNative,
867
+ ...(reactNative.devServerURL && !reactNative.devServerUrl
868
+ ? { devServerUrl: reactNative.devServerURL }
869
+ : {}),
870
+ devServerURL: undefined,
871
+ },
872
+ } : {}),
873
+ ...(signing ? {
874
+ signing: {
875
+ certificateP12Base64: signing.certificateP12Base64,
876
+ certificatePassword: signing.certificatePassword,
877
+ ...(profiles ? { provisioningProfilesBase64: profiles } : {}),
878
+ },
879
+ } : {}),
880
+ ...((appStore ?? appstore) ? { appStore: appStore ?? appstore } : {}),
881
+ };
882
+ }
883
+ function validateXcodeBuildOptions(options) {
884
+ if (options.workspace && options.project)
885
+ throw new Error("Xcode build accepts either workspace or project, not both");
886
+ if (options.xcodegen && options.reactNative)
887
+ throw new Error("XcodeGen options cannot be combined with Expo or React Native options");
888
+ if (options.reuse) {
889
+ if (!/^xcb_[A-Za-z0-9]+$/.test(options.reuse.basisBuildId)) {
890
+ throw new Error("reuse.basisBuildId must be a run.cloud Xcode build ID");
891
+ }
892
+ if (!options.reactNative)
893
+ throw new Error("reuse requires reactNative options");
894
+ if (options.workspace || options.project || options.scheme || options.xcodegen) {
895
+ throw new Error("reuse cannot select a workspace, project, scheme, or XcodeGen spec");
896
+ }
897
+ if (options.sdk && options.sdk !== "iphonesimulator") {
898
+ throw new Error("reuse requires sdk iphonesimulator");
899
+ }
900
+ if (options.configuration && options.configuration !== "Release") {
901
+ throw new Error("reuse requires configuration Release");
902
+ }
903
+ if (options.signing || options.cloudSigning || options.appStore || options.appstore) {
904
+ throw new Error("reuse cannot be combined with signing or App Store upload");
905
+ }
906
+ }
907
+ const devServerUrl = options.reactNative?.devServerUrl ?? options.reactNative?.devServerURL;
908
+ if (devServerUrl && options.configuration === "Release")
909
+ throw new Error("reactNative.devServerUrl is only supported for Debug builds");
910
+ if (options.timeoutSeconds !== undefined && (!Number.isInteger(options.timeoutSeconds) || options.timeoutSeconds < 1 || options.timeoutSeconds > 21_600)) {
911
+ throw new Error("timeoutSeconds must be an integer from 1 to 21600");
912
+ }
913
+ if (options.signing && options.cloudSigning)
914
+ throw new Error("manual signing and cloudSigning are mutually exclusive");
915
+ if (options.cloudSigning && options.sdk && options.sdk !== "iphoneos")
916
+ throw new Error("cloudSigning requires sdk iphoneos");
917
+ if (options.signing && options.sdk && !["iphoneos", "watchos"].includes(options.sdk))
918
+ throw new Error("manual signing requires sdk iphoneos or watchos");
919
+ const appStore = options.appStore ?? options.appstore;
920
+ if (appStore?.enabled !== false && appStore && (!options.signing && !options.cloudSigning))
921
+ throw new Error("App Store upload requires manual or cloud signing");
922
+ if (appStore?.enabled !== false && appStore && options.sdk && options.sdk !== "iphoneos")
923
+ throw new Error("App Store upload requires sdk iphoneos");
924
+ if (appStore?.waitTimeoutSeconds !== undefined && (!Number.isInteger(appStore.waitTimeoutSeconds) || appStore.waitTimeoutSeconds < 0 || appStore.waitTimeoutSeconds > 1_800)) {
925
+ throw new Error("appStore.waitTimeoutSeconds must be an integer from 0 to 1800");
926
+ }
927
+ }
722
928
  export class Client {
723
929
  ios;
724
930
  android;
@@ -731,6 +937,9 @@ export class Client {
731
937
  ci;
732
938
  images;
733
939
  remotion;
940
+ xcode;
941
+ capabilities;
942
+ mobile;
734
943
  apiKey;
735
944
  apiUrl;
736
945
  fetchImpl;
@@ -757,6 +966,9 @@ export class Client {
757
966
  this.ci = new CiClient(this);
758
967
  this.images = new ImageClient(this);
759
968
  this.remotion = new RemotionClient(this.sandboxes, this.snapshots);
969
+ this.xcode = new XcodeBuildsClient(this);
970
+ this.capabilities = new CapabilitiesClient(this);
971
+ this.mobile = new MobileClient(this);
760
972
  }
761
973
  async usage(options = {}) {
762
974
  const query = options.orgId ? `?orgId=${encodeURIComponent(options.orgId)}` : "";
@@ -848,11 +1060,12 @@ export class Client {
848
1060
  operation.cleanup();
849
1061
  }
850
1062
  }
851
- async uploadForm(path, form) {
1063
+ async uploadForm(path, form, headers = {}) {
852
1064
  const response = await this.fetchImpl(`${this.apiUrl}${path}`, {
853
1065
  method: "POST",
854
1066
  headers: {
855
1067
  Authorization: `Bearer ${this.apiKey}`,
1068
+ ...headers,
856
1069
  ...outboundTraceHeaders(),
857
1070
  },
858
1071
  body: form,
@@ -1061,6 +1274,115 @@ export class Client {
1061
1274
  });
1062
1275
  }
1063
1276
  }
1277
+ function waitDelay(milliseconds, signal) {
1278
+ if (signal?.aborted)
1279
+ return Promise.reject(new DOMException("The operation was aborted", "AbortError"));
1280
+ return new Promise((resolvePromise, reject) => {
1281
+ const onAbort = () => {
1282
+ clearTimeout(timer);
1283
+ reject(new DOMException("The operation was aborted", "AbortError"));
1284
+ };
1285
+ const timer = setTimeout(() => {
1286
+ signal?.removeEventListener("abort", onAbort);
1287
+ resolvePromise();
1288
+ }, milliseconds);
1289
+ signal?.addEventListener("abort", onAbort, { once: true });
1290
+ });
1291
+ }
1292
+ export class XcodeBuildsClient {
1293
+ client;
1294
+ constructor(client) {
1295
+ this.client = client;
1296
+ }
1297
+ /** Package a local source tree and submit one idempotent remote Xcode build. */
1298
+ async build(sourcePath = ".", options = {}) {
1299
+ validateXcodeBuildOptions(options);
1300
+ const packed = await packXcodeSource(sourcePath, options.source);
1301
+ const archiveBuffer = new ArrayBuffer(packed.archive.byteLength);
1302
+ new Uint8Array(archiveBuffer).set(packed.archive);
1303
+ const form = new FormData();
1304
+ form.append("request", JSON.stringify(xcodeBuildRequest(options)));
1305
+ form.append("source", new Blob([archiveBuffer], { type: "application/gzip" }), packed.filename);
1306
+ const idempotencyKey = options.idempotencyKey ?? `xcode_${randomUUID()}`;
1307
+ return await retryXcodeOperation(async () => await this.client.uploadForm("/run-cloud/xcode/builds", form, { "Idempotency-Key": idempotencyKey }));
1308
+ }
1309
+ /** Reuse a compatible simulator native shell and replace only its embedded JavaScript bundle. */
1310
+ async rebundle(sourcePath, basisBuildId, options = {}) {
1311
+ return await this.build(sourcePath, {
1312
+ ...options,
1313
+ sdk: "iphonesimulator",
1314
+ configuration: "Release",
1315
+ reactNative: options.reactNative ?? {},
1316
+ reuse: { basisBuildId },
1317
+ });
1318
+ }
1319
+ async get(id) {
1320
+ return await retryXcodeOperation(async () => await this.client.request("GET", `/run-cloud/xcode/builds/${encodeURIComponent(id)}`));
1321
+ }
1322
+ async list(options = {}) {
1323
+ const query = new URLSearchParams();
1324
+ if (options.orgId)
1325
+ query.set("orgId", options.orgId);
1326
+ if (options.limit !== undefined)
1327
+ query.set("limit", String(options.limit));
1328
+ const result = await retryXcodeOperation(async () => await this.client.request("GET", `/run-cloud/xcode/builds${query.size ? `?${query}` : ""}`));
1329
+ return result.items;
1330
+ }
1331
+ /** Read an ordered, replayable event page. Persist nextCursor after processing the page. */
1332
+ async events(options = {}) {
1333
+ const query = new URLSearchParams();
1334
+ if (options.orgId)
1335
+ query.set("orgId", options.orgId);
1336
+ if (options.after !== undefined)
1337
+ query.set("after", String(options.after));
1338
+ if (options.buildId)
1339
+ query.set("buildId", options.buildId);
1340
+ if (options.limit !== undefined)
1341
+ query.set("limit", String(options.limit));
1342
+ return await retryXcodeOperation(async () => await this.client.request("GET", `/run-cloud/xcode/events${query.size ? `?${query}` : ""}`));
1343
+ }
1344
+ async logs(id, offset = 0, source) {
1345
+ return await retryXcodeOperation(async () => await this.client.request("GET", `/run-cloud/xcode/builds/${encodeURIComponent(id)}/logs?offset=${offset}${source ? `&source=${source}` : ""}`));
1346
+ }
1347
+ /** Poll state and emit sanitized log chunks until the build is terminal. */
1348
+ async wait(id, options = {}) {
1349
+ const timeoutSeconds = options.timeoutSeconds ?? 6 * 60 * 60 + 15 * 60;
1350
+ const deadline = Date.now() + timeoutSeconds * 1_000;
1351
+ const interval = options.pollIntervalMs ?? 1_000;
1352
+ let offset = 0;
1353
+ let source;
1354
+ for (;;) {
1355
+ if (options.signal?.aborted)
1356
+ throw new DOMException("The operation was aborted", "AbortError");
1357
+ let build = await this.get(id);
1358
+ if (build.logs.available || build.status === "running" || build.status === "queued") {
1359
+ const chunk = await this.logs(id, offset, source);
1360
+ if (chunk.text)
1361
+ options.onLog?.(chunk.text);
1362
+ offset = chunk.nextOffset;
1363
+ source = chunk.source ?? source;
1364
+ if (["succeeded", "failed", "cancelled"].includes(build.status) && chunk.complete)
1365
+ return build;
1366
+ }
1367
+ else if (["succeeded", "failed", "cancelled"].includes(build.status)) {
1368
+ return build;
1369
+ }
1370
+ if (Date.now() >= deadline) {
1371
+ build = await this.get(id);
1372
+ if (["succeeded", "failed", "cancelled"].includes(build.status))
1373
+ return build;
1374
+ throw new Error(`Xcode build ${id} did not finish within ${timeoutSeconds} seconds`);
1375
+ }
1376
+ await waitDelay(interval, options.signal);
1377
+ }
1378
+ }
1379
+ async cancel(id) {
1380
+ return await retryXcodeOperation(async () => await this.client.request("POST", `/run-cloud/xcode/builds/${encodeURIComponent(id)}/cancel`, {}));
1381
+ }
1382
+ async downloadArtifact(id) {
1383
+ return await retryXcodeOperation(async () => await this.client.requestBytes(`/run-cloud/xcode/builds/${encodeURIComponent(id)}/artifact`));
1384
+ }
1385
+ }
1064
1386
  export class SimulatorClient {
1065
1387
  client;
1066
1388
  platform;
@@ -1088,8 +1410,12 @@ export class SimulatorClient {
1088
1410
  return await this.client.request("PATCH", `/run-cloud/${this.platform}/${encodeURIComponent(id)}/tags`, { tags });
1089
1411
  }
1090
1412
  async list(options = {}) {
1091
- const query = options.all ? "?all=1" : "";
1092
- return await this.client.request("GET", `/run-cloud/${this.platform}${query}`);
1413
+ const query = new URLSearchParams();
1414
+ if (options.all)
1415
+ query.set("all", "1");
1416
+ if (options.orgId)
1417
+ query.set("orgId", options.orgId);
1418
+ return await this.client.request("GET", `/run-cloud/${this.platform}${query.size ? `?${query}` : ""}`);
1093
1419
  }
1094
1420
  async get(id) {
1095
1421
  return await this.client.request("GET", `/run-cloud/${this.platform}/${encodeURIComponent(id)}`);
@@ -1496,11 +1822,15 @@ export class SimulatorsClient {
1496
1822
  return await new SimulatorClient(this.client, platform).create(rest);
1497
1823
  }
1498
1824
  async list(options = {}) {
1499
- const { platform, all } = options;
1825
+ const { platform, all, orgId } = options;
1500
1826
  if (platform)
1501
- return await new SimulatorClient(this.client, platform).list({ all });
1502
- const query = all ? "?all=1" : "";
1503
- return await this.client.request("GET", `/run-cloud/simulators${query}`);
1827
+ return await new SimulatorClient(this.client, platform).list({ all, orgId });
1828
+ const query = new URLSearchParams();
1829
+ if (all)
1830
+ query.set("all", "1");
1831
+ if (orgId)
1832
+ query.set("orgId", orgId);
1833
+ return await this.client.request("GET", `/run-cloud/simulators${query.size ? `?${query}` : ""}`);
1504
1834
  }
1505
1835
  async get(id, options = {}) {
1506
1836
  return await new SimulatorClient(this.client, options.platform ?? "ios").get(id);
@@ -2143,8 +2473,9 @@ export class AssetsClient {
2143
2473
  constructor(client) {
2144
2474
  this.client = client;
2145
2475
  }
2146
- async list() {
2147
- return await this.client.request("GET", "/run-cloud/assets");
2476
+ async list(options = {}) {
2477
+ const query = options.orgId ? `?orgId=${encodeURIComponent(options.orgId)}` : "";
2478
+ return await this.client.request("GET", `/run-cloud/assets${query}`);
2148
2479
  }
2149
2480
  /** Upload through the buffered multipart endpoint instead of signed storage URLs. */
2150
2481
  async uploadDirect(file, options = {}) {
@@ -2166,6 +2497,7 @@ export class AssetsClient {
2166
2497
  const uploadRunId = options.uploadRunId ?? this.defaultUploadRunId;
2167
2498
  const prepared = await this.client.request("POST", "/run-cloud/assets/uploads", {
2168
2499
  filename,
2500
+ ...(options.orgId ? { orgId: options.orgId } : {}),
2169
2501
  ...(options.name ? { name: options.name } : {}),
2170
2502
  contentType: file.type || "application/octet-stream",
2171
2503
  byteSize: file.size,
@@ -2189,7 +2521,7 @@ export class AssetsClient {
2189
2521
  catch (error) {
2190
2522
  transferError = error;
2191
2523
  retries = attempt;
2192
- if (!isRetryableUploadError(error) || attempt === MAX_ASSET_UPLOAD_ATTEMPTS - 1)
2524
+ if (!isRetryableRequestError(error) || attempt === MAX_ASSET_UPLOAD_ATTEMPTS - 1)
2193
2525
  break;
2194
2526
  await retryDelay(attempt);
2195
2527
  }
@@ -2251,4 +2583,50 @@ export class AssetsClient {
2251
2583
  return await this.client.request("DELETE", `/run-cloud/assets/${encodeURIComponent(id)}`);
2252
2584
  }
2253
2585
  }
2586
+ export class CapabilitiesClient {
2587
+ client;
2588
+ constructor(client) {
2589
+ this.client = client;
2590
+ }
2591
+ /** Mint a short-lived bearer token. The token is returned only by this call. */
2592
+ async create(options) {
2593
+ return await this.client.request("POST", "/run-cloud/capabilities", options);
2594
+ }
2595
+ async list(options = {}) {
2596
+ const query = new URLSearchParams();
2597
+ if (options.orgId)
2598
+ query.set("orgId", options.orgId);
2599
+ if (options.active === false)
2600
+ query.set("active", "0");
2601
+ const result = await this.client.request("GET", `/run-cloud/capabilities${query.size ? `?${query}` : ""}`);
2602
+ return result.items;
2603
+ }
2604
+ async revoke(id) {
2605
+ return await this.client.request("DELETE", `/run-cloud/capabilities/${encodeURIComponent(id)}`);
2606
+ }
2607
+ }
2608
+ export class MobileClient {
2609
+ client;
2610
+ constructor(client) {
2611
+ this.client = client;
2612
+ }
2613
+ /** Idempotently create a mobile session and install one existing Asset or Xcode build output. */
2614
+ async launch(source, options) {
2615
+ if (!options.idempotencyKey?.trim()) {
2616
+ throw new Error("mobile.launch requires a stable idempotencyKey");
2617
+ }
2618
+ if (Boolean(source.assetId) === Boolean(source.buildId)) {
2619
+ throw new Error("mobile.launch requires exactly one assetId or buildId");
2620
+ }
2621
+ return await this.client.request("POST", "/run-cloud/mobile/launch", {
2622
+ ...(source.assetId ? { assetId: source.assetId } : { buildId: source.buildId }),
2623
+ ...(options.orgId ? { orgId: options.orgId } : {}),
2624
+ ...(options.platform ? { platform: options.platform } : {}),
2625
+ ...(options.session ? { session: options.session } : {}),
2626
+ }, { "Idempotency-Key": options.idempotencyKey });
2627
+ }
2628
+ async getLaunch(id) {
2629
+ return await this.client.request("GET", `/run-cloud/mobile/launches/${encodeURIComponent(id)}`);
2630
+ }
2631
+ }
2254
2632
  //# sourceMappingURL=index.js.map