@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/README.md +37 -0
- package/dist/index.d.ts +302 -2
- package/dist/index.js +389 -11
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
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
|
|
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 =
|
|
1092
|
-
|
|
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 =
|
|
1503
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|