@computesdk/cli 1.0.11 → 1.0.13
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 +328 -71
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -708,6 +708,76 @@ var ActionsApiError = class extends Error {
|
|
|
708
708
|
this.name = "ActionsApiError";
|
|
709
709
|
}
|
|
710
710
|
};
|
|
711
|
+
var ActionsCliError = class extends Error {
|
|
712
|
+
constructor(code, message) {
|
|
713
|
+
super(message);
|
|
714
|
+
this.code = code;
|
|
715
|
+
this.name = "ActionsCliError";
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
function codeForStatus(status) {
|
|
719
|
+
switch (status) {
|
|
720
|
+
case 400:
|
|
721
|
+
return "bad_request";
|
|
722
|
+
case 401:
|
|
723
|
+
return "unauthenticated";
|
|
724
|
+
case 403:
|
|
725
|
+
return "forbidden";
|
|
726
|
+
case 404:
|
|
727
|
+
return "not_found";
|
|
728
|
+
case 409:
|
|
729
|
+
return "conflict";
|
|
730
|
+
case 413:
|
|
731
|
+
return "payload_too_large";
|
|
732
|
+
case 429:
|
|
733
|
+
return "rate_limited";
|
|
734
|
+
default:
|
|
735
|
+
return status >= 500 ? "server_error" : "http_error";
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function errorDetails(body) {
|
|
739
|
+
if (body && typeof body === "object" && "details" in body) {
|
|
740
|
+
const details = body.details;
|
|
741
|
+
if (details && typeof details === "object" && !Array.isArray(details)) {
|
|
742
|
+
return details;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return void 0;
|
|
746
|
+
}
|
|
747
|
+
function toErrorEnvelope(error) {
|
|
748
|
+
if (error instanceof ActionsApiError) {
|
|
749
|
+
const details = errorDetails(error.body);
|
|
750
|
+
return {
|
|
751
|
+
ok: false,
|
|
752
|
+
error: {
|
|
753
|
+
code: codeForStatus(error.status),
|
|
754
|
+
message: error.message,
|
|
755
|
+
httpStatus: error.status,
|
|
756
|
+
retryable: error.status === 429 || error.status === 502 || error.status === 503 || error.status === 504,
|
|
757
|
+
...details !== void 0 && { details }
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
if (error instanceof ActionsCliError) {
|
|
762
|
+
return { ok: false, error: { code: error.code, message: error.message, retryable: false } };
|
|
763
|
+
}
|
|
764
|
+
if (error instanceof TypeError && /fetch failed/i.test(error.message)) {
|
|
765
|
+
const cause = error.cause;
|
|
766
|
+
const detail = cause instanceof Error ? `: ${cause.message}` : "";
|
|
767
|
+
return {
|
|
768
|
+
ok: false,
|
|
769
|
+
error: { code: "network", message: `${error.message}${detail}`, retryable: true }
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
ok: false,
|
|
774
|
+
error: {
|
|
775
|
+
code: "unknown",
|
|
776
|
+
message: error instanceof Error ? error.message : String(error),
|
|
777
|
+
retryable: false
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
711
781
|
var ActionsClient = class {
|
|
712
782
|
constructor(auth, fetchImpl = fetch) {
|
|
713
783
|
this.auth = auth;
|
|
@@ -834,22 +904,64 @@ function encodeWatchCursor(id, jobId, step, offset) {
|
|
|
834
904
|
const stepPart = step === null ? "-" : step === "runner" ? "runner" : String(step);
|
|
835
905
|
return `${id}:${jobId}:${stepPart}:${offset}`;
|
|
836
906
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (!apiKey) {
|
|
840
|
-
throw new Error(
|
|
841
|
-
"No API key. Set COMPUTE_API_KEY or pass --api-key."
|
|
842
|
-
);
|
|
843
|
-
}
|
|
907
|
+
var NO_CREDENTIALS_HINT = "Set COMPUTE_API_KEY, pass --api-key, or run `compute bench auth login`.";
|
|
908
|
+
async function resolveActionsAuth(opts, resolveStored = resolveStoredPlatformAuth) {
|
|
844
909
|
const baseUrl = (opts.baseUrl || process.env.COMPUTE_PLATFORM_URL || process.env.BENCHMARKS_PLATFORM_URL || // legacy name
|
|
845
910
|
DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
846
911
|
if (!opts.allowUntrustedHost && !isTrustedActionsHost(baseUrl)) {
|
|
847
|
-
throw new
|
|
912
|
+
throw new ActionsCliError(
|
|
913
|
+
"untrusted_host",
|
|
848
914
|
`Refusing to send the API key to ${baseUrl} \u2014 it is not a computesdk.com or localhost host. If this is a self-hosted/dev deployment you trust, pass --allow-untrusted-host.`
|
|
849
915
|
);
|
|
850
916
|
}
|
|
917
|
+
if (!isSecureActionsTransport(baseUrl)) {
|
|
918
|
+
throw new ActionsCliError(
|
|
919
|
+
"insecure_transport",
|
|
920
|
+
`Refusing to send the API key over plaintext HTTP to ${baseUrl}. Use an https:// base URL; http:// is only allowed for localhost/loopback.`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
let apiKey = opts.apiKey || process.env.COMPUTE_API_KEY || process.env.BENCHMARKS_PLATFORM_API_KEY || // legacy name
|
|
924
|
+
void 0;
|
|
925
|
+
if (!apiKey) {
|
|
926
|
+
if (!isTrustedActionsHost(baseUrl)) {
|
|
927
|
+
throw new ActionsCliError(
|
|
928
|
+
"untrusted_host_stored_auth",
|
|
929
|
+
`Refusing to use stored platform credentials with untrusted host ${baseUrl}. Pass --api-key or set COMPUTE_API_KEY to use an explicit key with --allow-untrusted-host.`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
let stored;
|
|
933
|
+
try {
|
|
934
|
+
stored = await resolveStored({ baseUrl });
|
|
935
|
+
} catch (error) {
|
|
936
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
937
|
+
const detail = reason.startsWith("No credentials found") ? "No API key." : reason;
|
|
938
|
+
throw new ActionsCliError("no_credentials", `${detail} ${NO_CREDENTIALS_HINT}`);
|
|
939
|
+
}
|
|
940
|
+
apiKey = stored.apiKey || stored.token || void 0;
|
|
941
|
+
}
|
|
942
|
+
if (!apiKey) {
|
|
943
|
+
throw new ActionsCliError("no_credentials", `No API key. ${NO_CREDENTIALS_HINT}`);
|
|
944
|
+
}
|
|
851
945
|
return { apiKey, baseUrl };
|
|
852
946
|
}
|
|
947
|
+
async function resolveStoredPlatformAuth(opts) {
|
|
948
|
+
const { resolveAuth } = await import("@benchsdk/cli");
|
|
949
|
+
const auth = await resolveAuth({ baseUrl: opts.baseUrl });
|
|
950
|
+
return { apiKey: auth.apiKey, token: auth.token };
|
|
951
|
+
}
|
|
952
|
+
function isLoopbackHost(host) {
|
|
953
|
+
return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
|
|
954
|
+
}
|
|
955
|
+
function isSecureActionsTransport(baseUrl) {
|
|
956
|
+
let url;
|
|
957
|
+
try {
|
|
958
|
+
url = new URL(baseUrl);
|
|
959
|
+
} catch {
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
if (url.protocol === "https:") return true;
|
|
963
|
+
return url.protocol === "http:" && isLoopbackHost(url.hostname);
|
|
964
|
+
}
|
|
853
965
|
function isTrustedActionsHost(baseUrl) {
|
|
854
966
|
let host;
|
|
855
967
|
try {
|
|
@@ -857,7 +969,7 @@ function isTrustedActionsHost(baseUrl) {
|
|
|
857
969
|
} catch {
|
|
858
970
|
return false;
|
|
859
971
|
}
|
|
860
|
-
return host === "computesdk.com" || host.endsWith(".computesdk.com") || host
|
|
972
|
+
return host === "computesdk.com" || host.endsWith(".computesdk.com") || isLoopbackHost(host);
|
|
861
973
|
}
|
|
862
974
|
|
|
863
975
|
// src/actions.ts
|
|
@@ -866,8 +978,21 @@ function sanitizePathPart(name) {
|
|
|
866
978
|
if (base === "" || base === "." || base === "..") return "artifact";
|
|
867
979
|
return base;
|
|
868
980
|
}
|
|
869
|
-
function client(opts) {
|
|
870
|
-
return new ActionsClient(resolveActionsAuth(opts));
|
|
981
|
+
async function client(opts) {
|
|
982
|
+
return new ActionsClient(await resolveActionsAuth(opts));
|
|
983
|
+
}
|
|
984
|
+
function assertVaultValueHost(auth) {
|
|
985
|
+
if (!isTrustedActionsHost(auth.baseUrl)) {
|
|
986
|
+
throw new ActionsCliError(
|
|
987
|
+
"untrusted_host",
|
|
988
|
+
`Refusing to send or read vault values at ${auth.baseUrl} \u2014 only computesdk.com and localhost hosts are allowed, even with --allow-untrusted-host.`
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
async function vaultValueClient(opts) {
|
|
993
|
+
const auth = await resolveActionsAuth(opts);
|
|
994
|
+
assertVaultValueHost(auth);
|
|
995
|
+
return new ActionsClient(auth);
|
|
871
996
|
}
|
|
872
997
|
function output(opts, data, render) {
|
|
873
998
|
if (opts.json) {
|
|
@@ -912,14 +1037,24 @@ async function fetchRunSummary(c, runId) {
|
|
|
912
1037
|
return null;
|
|
913
1038
|
}
|
|
914
1039
|
}
|
|
915
|
-
function fail(error) {
|
|
916
|
-
if (
|
|
1040
|
+
function fail(error, opts = {}) {
|
|
1041
|
+
if (opts.json) {
|
|
1042
|
+
process.stderr.write(JSON.stringify(toErrorEnvelope(error)) + "\n");
|
|
1043
|
+
} else if (error instanceof ActionsApiError) {
|
|
917
1044
|
console.error(pc.red(`Error (${error.status}): ${error.message}`));
|
|
918
1045
|
} else {
|
|
919
|
-
console.error(pc.red(`Error: ${error.message}`));
|
|
1046
|
+
console.error(pc.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
920
1047
|
}
|
|
921
1048
|
process.exit(1);
|
|
922
1049
|
}
|
|
1050
|
+
function usageErrorOutput(str, write, argv = process.argv) {
|
|
1051
|
+
if (argv.includes("--json")) {
|
|
1052
|
+
const message = str.replace(/^error:\s*/i, "").trim();
|
|
1053
|
+
write(JSON.stringify(toErrorEnvelope(new ActionsCliError("invalid_argument", message))) + "\n");
|
|
1054
|
+
} else {
|
|
1055
|
+
write(str);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
923
1058
|
function formatDuration(ms) {
|
|
924
1059
|
if (ms === null || ms === void 0) return "-";
|
|
925
1060
|
if (ms < 1e3) return `${ms}ms`;
|
|
@@ -1085,7 +1220,7 @@ function parseInputs(pairs) {
|
|
|
1085
1220
|
for (const pair of pairs ?? []) {
|
|
1086
1221
|
const eq = pair.indexOf("=");
|
|
1087
1222
|
if (eq === -1) {
|
|
1088
|
-
throw new
|
|
1223
|
+
throw new ActionsCliError("invalid_argument", `Invalid --inputs entry "${pair}". Expected key=value.`);
|
|
1089
1224
|
}
|
|
1090
1225
|
inputs[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
1091
1226
|
}
|
|
@@ -1135,7 +1270,7 @@ function parseStep(step) {
|
|
|
1135
1270
|
if (step === "runner") return "runner";
|
|
1136
1271
|
const n = Number(step);
|
|
1137
1272
|
if (!Number.isInteger(n) || n < 0) {
|
|
1138
|
-
throw new
|
|
1273
|
+
throw new ActionsCliError("invalid_argument", `Invalid --step "${step}". Expected a step ordinal or "runner".`);
|
|
1139
1274
|
}
|
|
1140
1275
|
return n;
|
|
1141
1276
|
}
|
|
@@ -1233,14 +1368,79 @@ function printDiscovery(d) {
|
|
|
1233
1368
|
}
|
|
1234
1369
|
console.log(pc.dim(`discovery: ${d.workflowsFound ? "workflows found" : "no workflows yet"}${d.seeded ? ", cursors seeded" : ""}`));
|
|
1235
1370
|
}
|
|
1371
|
+
function dispatchBody(workflow, opts) {
|
|
1372
|
+
if (!workflow.dispatchable && !opts.manual) {
|
|
1373
|
+
throw new ActionsCliError(
|
|
1374
|
+
"invalid_argument",
|
|
1375
|
+
`Workflow "${workflow.path}" does not declare workflow_dispatch. Pass --manual to run it anyway (no inputs).`
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
if (!workflow.dispatchable && opts.inputs !== void 0 && opts.inputs.length > 0) {
|
|
1379
|
+
throw new ActionsCliError(
|
|
1380
|
+
"invalid_argument",
|
|
1381
|
+
`Workflow "${workflow.path}" has no workflow_dispatch inputs; --manual runs of it take no --inputs.`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
const ref = opts.ref ?? workflow.refs[0];
|
|
1385
|
+
if (!ref) throw new ActionsCliError("invalid_argument", "No --ref given and the workflow has no watched refs.");
|
|
1386
|
+
if (opts.providerRegion !== void 0 && opts.provider === void 0) {
|
|
1387
|
+
throw new ActionsCliError("invalid_argument", "--provider-region requires --provider.");
|
|
1388
|
+
}
|
|
1389
|
+
return {
|
|
1390
|
+
workflowId: workflow.id,
|
|
1391
|
+
ref,
|
|
1392
|
+
inputs: parseInputs(opts.inputs),
|
|
1393
|
+
...opts.manual && { manual: true },
|
|
1394
|
+
...opts.provider !== void 0 && {
|
|
1395
|
+
provider: opts.provider,
|
|
1396
|
+
...opts.providerRegion !== void 0 && { providerRegion: opts.providerRegion }
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
function parseVaultKind(kind) {
|
|
1401
|
+
if (kind === void 0 || kind === "secret" || kind === "variable") return kind ?? "secret";
|
|
1402
|
+
throw new ActionsCliError("invalid_argument", `--kind must be secret or variable, got "${kind}".`);
|
|
1403
|
+
}
|
|
1404
|
+
function vaultPath(path5, params) {
|
|
1405
|
+
const search = new URLSearchParams();
|
|
1406
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1407
|
+
if (value !== void 0) search.set(key, value);
|
|
1408
|
+
}
|
|
1409
|
+
const query = search.toString();
|
|
1410
|
+
return query ? `${path5}?${query}` : path5;
|
|
1411
|
+
}
|
|
1412
|
+
function vaultSetBody(name, value, opts) {
|
|
1413
|
+
if (value === "") throw new ActionsCliError("invalid_argument", "The value is empty.");
|
|
1414
|
+
if (opts.revealable && opts.kind === "variable") {
|
|
1415
|
+
throw new ActionsCliError("invalid_argument", "--revealable applies to secrets; variables are always readable.");
|
|
1416
|
+
}
|
|
1417
|
+
return {
|
|
1418
|
+
name,
|
|
1419
|
+
kind: opts.kind,
|
|
1420
|
+
value,
|
|
1421
|
+
...opts.revealable && { revealable: true },
|
|
1422
|
+
...opts.description !== void 0 && { description: opts.description },
|
|
1423
|
+
...opts.labels !== void 0 && opts.labels.length > 0 && { labels: opts.labels }
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function formatVaultRow(item) {
|
|
1427
|
+
const flags = [
|
|
1428
|
+
item.kind,
|
|
1429
|
+
...item.revealable && item.kind === "secret" ? ["revealable"] : [],
|
|
1430
|
+
`v${item.version}`,
|
|
1431
|
+
...item.source === "organization" ? ["inherited"] : [],
|
|
1432
|
+
...item.overridesOrganization ? ["overrides org"] : []
|
|
1433
|
+
];
|
|
1434
|
+
return `${pc.cyan(safeTerm(item.name))} ${pc.dim(flags.join(", "))} ${pc.dim(`updated ${safeTerm(item.updatedAt)}`)}`;
|
|
1435
|
+
}
|
|
1236
1436
|
function registerActionsCommands(program2) {
|
|
1237
|
-
const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API");
|
|
1238
|
-
const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send
|
|
1437
|
+
const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API").configureOutput({ outputError: usageErrorOutput });
|
|
1438
|
+
const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
|
|
1239
1439
|
common(
|
|
1240
|
-
actions.command("dispatch").description("Dispatch a workflow_dispatch run").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--ref <ref>", "git ref to run (default: the workflow's first watched ref)").option("--inputs <pairs...>", "workflow inputs as key=value").option("--provider <id>", "place the run on one provider (e.g. namespace, vercel:sfo1) instead of the org provider order").option("--provider-region <region>", "region for --provider (same as --provider <id>:<region>)")
|
|
1440
|
+
actions.command("dispatch").description("Dispatch a workflow_dispatch run (or any workflow with --manual)").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--ref <ref>", "git ref to run (default: the workflow's first watched ref)").option("--inputs <pairs...>", "workflow inputs as key=value (workflow_dispatch inputs only)").option("--manual", "run a workflow even if it does not declare workflow_dispatch (such runs take no --inputs)").option("--provider <id>", "place the run on one provider (e.g. namespace, vercel:sfo1) instead of the org provider order").option("--provider-region <region>", "region for --provider (same as --provider <id>:<region>)")
|
|
1241
1441
|
).action(async (repo, opts) => {
|
|
1242
1442
|
try {
|
|
1243
|
-
const c = client(opts);
|
|
1443
|
+
const c = await client(opts);
|
|
1244
1444
|
const { workflows } = await c.get(
|
|
1245
1445
|
"/api/v1/actions/workflows",
|
|
1246
1446
|
{ repo }
|
|
@@ -1248,26 +1448,15 @@ function registerActionsCommands(program2) {
|
|
|
1248
1448
|
const workflow = matchWorkflow(workflows, opts.workflow);
|
|
1249
1449
|
if (!workflow) {
|
|
1250
1450
|
const choices = workflows.map((w) => `${w.path} (${w.name})`).join(", ") || "none";
|
|
1251
|
-
throw new
|
|
1252
|
-
|
|
1451
|
+
throw new ActionsCliError(
|
|
1452
|
+
"workflow_not_found",
|
|
1453
|
+
`No workflow "${opts.workflow}" in ${repo}. Available: ${choices}`
|
|
1253
1454
|
);
|
|
1254
1455
|
}
|
|
1255
|
-
const
|
|
1256
|
-
if (!ref) throw new Error("No --ref given and the workflow has no watched refs.");
|
|
1257
|
-
if (opts.providerRegion !== void 0 && opts.provider === void 0) {
|
|
1258
|
-
throw new Error("--provider-region requires --provider.");
|
|
1259
|
-
}
|
|
1456
|
+
const body = dispatchBody(workflow, opts);
|
|
1260
1457
|
const result = await c.post(
|
|
1261
1458
|
"/api/v1/actions/dispatch",
|
|
1262
|
-
|
|
1263
|
-
workflowId: workflow.id,
|
|
1264
|
-
ref,
|
|
1265
|
-
inputs: parseInputs(opts.inputs),
|
|
1266
|
-
...opts.provider !== void 0 && {
|
|
1267
|
-
provider: opts.provider,
|
|
1268
|
-
...opts.providerRegion !== void 0 && { providerRegion: opts.providerRegion }
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1459
|
+
body
|
|
1271
1460
|
);
|
|
1272
1461
|
const org = await c.org();
|
|
1273
1462
|
const url = `${c.baseUrl}/${org.slug}/actions/runs/${result.runId}`;
|
|
@@ -1277,14 +1466,14 @@ function registerActionsCommands(program2) {
|
|
|
1277
1466
|
console.log(`url: ${r.url}`);
|
|
1278
1467
|
});
|
|
1279
1468
|
} catch (e) {
|
|
1280
|
-
fail(e);
|
|
1469
|
+
fail(e, opts);
|
|
1281
1470
|
}
|
|
1282
1471
|
});
|
|
1283
1472
|
common(
|
|
1284
1473
|
actions.command("runs").description("List recent runs for a repo").argument("<repo>", "repository in owner/repo format").option("--status <status...>", "filter by conclusion: passed, failed, running, cancelled, none").option("--branch <branch...>", "filter by branch/ref")
|
|
1285
1474
|
).action(async (repo, opts) => {
|
|
1286
1475
|
try {
|
|
1287
|
-
const { runs } = await client(opts).get(
|
|
1476
|
+
const { runs } = await (await client(opts)).get(
|
|
1288
1477
|
"/api/v1/actions/runs",
|
|
1289
1478
|
{ repo, status: opts.status, branch: opts.branch }
|
|
1290
1479
|
);
|
|
@@ -1296,14 +1485,14 @@ function registerActionsCommands(program2) {
|
|
|
1296
1485
|
for (const run of rs) console.log(formatRunRow(run));
|
|
1297
1486
|
});
|
|
1298
1487
|
} catch (e) {
|
|
1299
|
-
fail(e);
|
|
1488
|
+
fail(e, opts);
|
|
1300
1489
|
}
|
|
1301
1490
|
});
|
|
1302
1491
|
common(
|
|
1303
1492
|
actions.command("history").description("Recent run history for a workflow \u2014 per-job/step failure rates (is this failure flaky?)").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--branch <branch...>", "narrow the window to branches/refs").option("--job <job>", "narrow the rollup to one job name or workflow job id").option("--limit <n>", "runs in the window (default 10, max 50)")
|
|
1304
1493
|
).action(async (repo, opts) => {
|
|
1305
1494
|
try {
|
|
1306
|
-
const c = client(opts);
|
|
1495
|
+
const c = await client(opts);
|
|
1307
1496
|
const { workflows } = await c.get(
|
|
1308
1497
|
"/api/v1/actions/workflows",
|
|
1309
1498
|
{ repo }
|
|
@@ -1319,14 +1508,14 @@ function registerActionsCommands(program2) {
|
|
|
1319
1508
|
});
|
|
1320
1509
|
output(opts, history, (h) => console.log(formatRunHistory(h)));
|
|
1321
1510
|
} catch (e) {
|
|
1322
|
-
fail(e);
|
|
1511
|
+
fail(e, opts);
|
|
1323
1512
|
}
|
|
1324
1513
|
});
|
|
1325
1514
|
common(
|
|
1326
1515
|
actions.command("run").description("Show a run: jobs, provider:region placement, placement attempts").argument("<run-id>", "run ID")
|
|
1327
1516
|
).action(async (runId, opts) => {
|
|
1328
1517
|
try {
|
|
1329
|
-
const c = client(opts);
|
|
1518
|
+
const c = await client(opts);
|
|
1330
1519
|
const run = await c.get(`/api/v1/actions/runs/${runId}`);
|
|
1331
1520
|
const org = await c.org();
|
|
1332
1521
|
const url = `${c.baseUrl}/${org.slug}/actions/runs/${run.id}`;
|
|
@@ -1339,37 +1528,37 @@ function registerActionsCommands(program2) {
|
|
|
1339
1528
|
}
|
|
1340
1529
|
});
|
|
1341
1530
|
} catch (e) {
|
|
1342
|
-
fail(e);
|
|
1531
|
+
fail(e, opts);
|
|
1343
1532
|
}
|
|
1344
1533
|
});
|
|
1345
1534
|
common(
|
|
1346
1535
|
actions.command("summary").description("Show a run's failure digest: failed jobs, failed steps, bounded log tails").argument("<run-id>", "run ID")
|
|
1347
1536
|
).action(async (runId, opts) => {
|
|
1348
1537
|
try {
|
|
1349
|
-
const summary = await client(opts).get(`/api/v1/actions/runs/${runId}/summary`);
|
|
1538
|
+
const summary = await (await client(opts)).get(`/api/v1/actions/runs/${runId}/summary`);
|
|
1350
1539
|
output(opts, summary, (s) => console.log(formatRunSummary(s)));
|
|
1351
1540
|
} catch (e) {
|
|
1352
|
-
fail(e);
|
|
1541
|
+
fail(e, opts);
|
|
1353
1542
|
}
|
|
1354
1543
|
});
|
|
1355
1544
|
common(
|
|
1356
1545
|
actions.command("inspect").description("Inspect a run: resolved runner image, caches, secret names, concurrency, placement context").argument("<run-id>", "run ID")
|
|
1357
1546
|
).action(async (runId, opts) => {
|
|
1358
1547
|
try {
|
|
1359
|
-
const c = client(opts);
|
|
1548
|
+
const c = await client(opts);
|
|
1360
1549
|
const inspection = await c.get(
|
|
1361
1550
|
`/api/v1/actions/runs/${runId}/state`
|
|
1362
1551
|
);
|
|
1363
1552
|
output(opts, inspection, (r) => console.log(formatRunInspection(r)));
|
|
1364
1553
|
} catch (e) {
|
|
1365
|
-
fail(e);
|
|
1554
|
+
fail(e, opts);
|
|
1366
1555
|
}
|
|
1367
1556
|
});
|
|
1368
1557
|
common(
|
|
1369
1558
|
actions.command("logs").description("Print or follow a run's job logs").argument("<run-id>", "run ID").option("--job <job>", "job ID or name (default: all jobs)").option("--step <step>", 'step ordinal or "runner" (default: whole job)').option("--follow", "stream logs while the run is live")
|
|
1370
1559
|
).action(async (runId, opts) => {
|
|
1371
1560
|
try {
|
|
1372
|
-
const c = client(opts);
|
|
1561
|
+
const c = await client(opts);
|
|
1373
1562
|
let jobs = await listRunJobs(c, runId);
|
|
1374
1563
|
if (opts.job) {
|
|
1375
1564
|
const job = matchJob(jobs, opts.job);
|
|
@@ -1420,25 +1609,25 @@ function registerActionsCommands(program2) {
|
|
|
1420
1609
|
}
|
|
1421
1610
|
}
|
|
1422
1611
|
} catch (e) {
|
|
1423
|
-
fail(e);
|
|
1612
|
+
fail(e, opts);
|
|
1424
1613
|
}
|
|
1425
1614
|
});
|
|
1426
1615
|
const providers = actions.command("providers").description("List the org's registered compute providers (regions, act/usable status)");
|
|
1427
1616
|
common(providers).action(async (opts) => {
|
|
1428
1617
|
try {
|
|
1429
|
-
const data = await client(opts).get("/api/v1/actions/providers");
|
|
1618
|
+
const data = await (await client(opts)).get("/api/v1/actions/providers");
|
|
1430
1619
|
output(opts, data, (d) => {
|
|
1431
1620
|
for (const p5 of d.providers) console.log(formatProviderRow(p5));
|
|
1432
1621
|
});
|
|
1433
1622
|
} catch (e) {
|
|
1434
|
-
fail(e);
|
|
1623
|
+
fail(e, opts);
|
|
1435
1624
|
}
|
|
1436
1625
|
});
|
|
1437
1626
|
common(
|
|
1438
1627
|
providers.command("configure").description("Save the org's credential for a provider (owner/admin API key)").argument("<provider>", "provider id (e.g. tensorlake, blaxel)").option("--key <key>", "provider API key (single-field credentials)").option("--field <pair...>", "credential fields as name=value (multi-field providers)").option("--verify", "run the placement probe after saving")
|
|
1439
1628
|
).action(async (provider, opts) => {
|
|
1440
1629
|
try {
|
|
1441
|
-
const c = client(opts);
|
|
1630
|
+
const c = await client(opts);
|
|
1442
1631
|
let body;
|
|
1443
1632
|
if (opts.field !== void 0) {
|
|
1444
1633
|
if (opts.key !== void 0) throw new Error("Pass either --key or --field, not both.");
|
|
@@ -1461,45 +1650,45 @@ function registerActionsCommands(program2) {
|
|
|
1461
1650
|
});
|
|
1462
1651
|
if (verification?.verified === false) process.exitCode = 1;
|
|
1463
1652
|
} catch (e) {
|
|
1464
|
-
fail(e);
|
|
1653
|
+
fail(e, opts);
|
|
1465
1654
|
}
|
|
1466
1655
|
});
|
|
1467
1656
|
common(
|
|
1468
1657
|
providers.command("verify").description('Probe the stored provider key with a real sandbox ("Test" in Settings)').argument("<provider>", "provider id")
|
|
1469
1658
|
).action(async (provider, opts) => {
|
|
1470
1659
|
try {
|
|
1471
|
-
const result = await client(opts).post(
|
|
1660
|
+
const result = await (await client(opts)).post(
|
|
1472
1661
|
`/api/v1/actions/providers/${provider}/verify`
|
|
1473
1662
|
);
|
|
1474
1663
|
output(opts, result, (r) => console.log(formatVerifyResult(r)));
|
|
1475
1664
|
if (result.verified === false) process.exitCode = 1;
|
|
1476
1665
|
} catch (e) {
|
|
1477
|
-
fail(e);
|
|
1666
|
+
fail(e, opts);
|
|
1478
1667
|
}
|
|
1479
1668
|
});
|
|
1480
1669
|
common(
|
|
1481
1670
|
providers.command("remove").description("Delete the org's stored credential for a provider").argument("<provider>", "provider id")
|
|
1482
1671
|
).action(async (provider, opts) => {
|
|
1483
1672
|
try {
|
|
1484
|
-
const result = await client(opts).del(
|
|
1673
|
+
const result = await (await client(opts)).del(
|
|
1485
1674
|
`/api/v1/actions/providers/${provider}/key`
|
|
1486
1675
|
);
|
|
1487
1676
|
output(opts, result, (r) => {
|
|
1488
1677
|
console.log(r.deleted ? `deleted ${r.provider}` : `no key stored for ${r.provider}`);
|
|
1489
1678
|
});
|
|
1490
1679
|
} catch (e) {
|
|
1491
|
-
fail(e);
|
|
1680
|
+
fail(e, opts);
|
|
1492
1681
|
}
|
|
1493
1682
|
});
|
|
1494
1683
|
const repos = actions.command("repos").description("List the org's connected repositories");
|
|
1495
1684
|
common(repos).action(async (opts) => {
|
|
1496
1685
|
try {
|
|
1497
|
-
const data = await client(opts).get("/api/v1/actions/repos");
|
|
1686
|
+
const data = await (await client(opts)).get("/api/v1/actions/repos");
|
|
1498
1687
|
output(opts, data, (d) => {
|
|
1499
1688
|
for (const r of d.repos) console.log(formatRepoRow(r));
|
|
1500
1689
|
});
|
|
1501
1690
|
} catch (e) {
|
|
1502
|
-
fail(e);
|
|
1691
|
+
fail(e, opts);
|
|
1503
1692
|
}
|
|
1504
1693
|
});
|
|
1505
1694
|
common(
|
|
@@ -1529,7 +1718,7 @@ function registerActionsCommands(program2) {
|
|
|
1529
1718
|
if (opts.knownHostsFile !== void 0 && opts.sshKeyFile === void 0) {
|
|
1530
1719
|
throw new Error("--known-hosts-file requires --ssh-key-file.");
|
|
1531
1720
|
}
|
|
1532
|
-
const result = await client(opts).post("/api/v1/actions/repos", {
|
|
1721
|
+
const result = await (await client(opts)).post("/api/v1/actions/repos", {
|
|
1533
1722
|
cloneUrl,
|
|
1534
1723
|
...body,
|
|
1535
1724
|
...opts.name !== void 0 && { name: opts.name },
|
|
@@ -1540,14 +1729,14 @@ function registerActionsCommands(program2) {
|
|
|
1540
1729
|
printDiscovery(r.discovery);
|
|
1541
1730
|
});
|
|
1542
1731
|
} catch (e) {
|
|
1543
|
-
fail(e);
|
|
1732
|
+
fail(e, opts);
|
|
1544
1733
|
}
|
|
1545
1734
|
});
|
|
1546
1735
|
common(
|
|
1547
1736
|
repos.command("enable").description("Enable a repo the org can already see (granted by a GitHub App install, or a connected remote)").argument("<repo>", "repository in owner/repo format").option("--repo-id <id>", "scope to one row when two installations grant the same name")
|
|
1548
1737
|
).action(async (repo, opts) => {
|
|
1549
1738
|
try {
|
|
1550
|
-
const result = await client(opts).patch("/api/v1/actions/repos", {
|
|
1739
|
+
const result = await (await client(opts)).patch("/api/v1/actions/repos", {
|
|
1551
1740
|
fullName: repo,
|
|
1552
1741
|
enabled: true,
|
|
1553
1742
|
...opts.repoId !== void 0 && { repoId: opts.repoId }
|
|
@@ -1557,42 +1746,110 @@ function registerActionsCommands(program2) {
|
|
|
1557
1746
|
if (r.discovery) printDiscovery(r.discovery);
|
|
1558
1747
|
});
|
|
1559
1748
|
} catch (e) {
|
|
1560
|
-
fail(e);
|
|
1749
|
+
fail(e, opts);
|
|
1561
1750
|
}
|
|
1562
1751
|
});
|
|
1563
1752
|
common(
|
|
1564
1753
|
repos.command("disable").description("Disable a repo \u2014 it stays connected but no runs are scheduled").argument("<repo>", "repository in owner/repo format").option("--repo-id <id>", "scope to one row when two installations grant the same name")
|
|
1565
1754
|
).action(async (repo, opts) => {
|
|
1566
1755
|
try {
|
|
1567
|
-
const result = await client(opts).patch("/api/v1/actions/repos", {
|
|
1756
|
+
const result = await (await client(opts)).patch("/api/v1/actions/repos", {
|
|
1568
1757
|
fullName: repo,
|
|
1569
1758
|
enabled: false,
|
|
1570
1759
|
...opts.repoId !== void 0 && { repoId: opts.repoId }
|
|
1571
1760
|
});
|
|
1572
1761
|
output(opts, result, (r) => console.log(`disabled ${pc.cyan(safeTerm(r.fullName))}`));
|
|
1573
1762
|
} catch (e) {
|
|
1574
|
-
fail(e);
|
|
1763
|
+
fail(e, opts);
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
const vault = actions.command("vault").description("The org vault: secrets and variables (owner/admin key)");
|
|
1767
|
+
const vaultScope = (cmd) => common(cmd).option("--repo <owner/repo>", "scope to a repo's own items instead of the org's").option("--kind <kind>", "secret or variable", "secret");
|
|
1768
|
+
vaultScope(
|
|
1769
|
+
vault.command("ls").description("List names and metadata (never values)")
|
|
1770
|
+
).action(async (opts) => {
|
|
1771
|
+
try {
|
|
1772
|
+
const path5 = vaultPath("/api/v1/vault", { repo: opts.repo, kind: parseVaultKind(opts.kind) });
|
|
1773
|
+
const data = await (await client(opts)).get(path5);
|
|
1774
|
+
output(opts, data, (d) => {
|
|
1775
|
+
for (const item of d.items) console.log(formatVaultRow(item));
|
|
1776
|
+
});
|
|
1777
|
+
} catch (e) {
|
|
1778
|
+
fail(e, opts);
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
1781
|
+
vaultScope(
|
|
1782
|
+
vault.command("set").description("Store a value, read from stdin (or --from-file) and sent exactly as read").argument("<name>", "item name, e.g. NPM_TOKEN").option("--from-file <path>", "read the value from a file instead of stdin").option("--revealable", "let the value be read back with `vault get` (secrets; fixed at creation)").option("--description <text>", "what the item is for").option("--labels <labels...>", "labels (fixed at creation)")
|
|
1783
|
+
).action(async (name, opts) => {
|
|
1784
|
+
try {
|
|
1785
|
+
if (opts.fromFile === void 0 && process.stdin.isTTY) {
|
|
1786
|
+
throw new ActionsCliError(
|
|
1787
|
+
"invalid_argument",
|
|
1788
|
+
'Pipe the value on stdin (e.g. printf %s "$VALUE" | compute actions vault set NAME) or pass --from-file.'
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
const value = readFileSync(opts.fromFile ?? 0, "utf8");
|
|
1792
|
+
const kind = parseVaultKind(opts.kind);
|
|
1793
|
+
const body = vaultSetBody(name, value, { ...opts, kind });
|
|
1794
|
+
const result = await (await vaultValueClient(opts)).put(
|
|
1795
|
+
vaultPath("/api/v1/vault", { repo: opts.repo }),
|
|
1796
|
+
body
|
|
1797
|
+
);
|
|
1798
|
+
output(opts, result, (r) => {
|
|
1799
|
+
const where = opts.repo ? safeTerm(opts.repo) : "org";
|
|
1800
|
+
console.log(`saved ${pc.cyan(safeTerm(r.item.name))} ${pc.dim(`(${r.item.kind}, v${r.item.version}, ${where})`)}`);
|
|
1801
|
+
if (/\r?\n$/.test(value)) {
|
|
1802
|
+
console.error(pc.yellow("note: the stored value ends with a newline; use printf %s rather than echo to avoid one."));
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
} catch (e) {
|
|
1806
|
+
fail(e, opts);
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
vaultScope(
|
|
1810
|
+
vault.command("get").description("Print a value: any variable, or a secret created --revealable").argument("<name>", "item name")
|
|
1811
|
+
).action(async (name, opts) => {
|
|
1812
|
+
try {
|
|
1813
|
+
const kind = parseVaultKind(opts.kind);
|
|
1814
|
+
const result = await (await vaultValueClient(opts)).post(
|
|
1815
|
+
vaultPath("/api/v1/vault/reveal", { repo: opts.repo }),
|
|
1816
|
+
{ name, kind }
|
|
1817
|
+
);
|
|
1818
|
+
output(opts, result, (r) => process.stdout.write(r.value));
|
|
1819
|
+
} catch (e) {
|
|
1820
|
+
fail(e, opts);
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
vaultScope(
|
|
1824
|
+
vault.command("rm").description("Delete an item (with --repo, only the repo's override)").argument("<name>", "item name")
|
|
1825
|
+
).action(async (name, opts) => {
|
|
1826
|
+
try {
|
|
1827
|
+
const path5 = vaultPath("/api/v1/vault", { repo: opts.repo, kind: parseVaultKind(opts.kind), name });
|
|
1828
|
+
const result = await (await client(opts)).del(path5);
|
|
1829
|
+
output(opts, result, (r) => console.log(`deleted ${pc.cyan(safeTerm(r.name))} ${pc.dim(`(${r.kind})`)}`));
|
|
1830
|
+
} catch (e) {
|
|
1831
|
+
fail(e, opts);
|
|
1575
1832
|
}
|
|
1576
1833
|
});
|
|
1577
1834
|
common(
|
|
1578
1835
|
actions.command("cancel").description("Cancel a run").argument("<run-id>", "run ID")
|
|
1579
1836
|
).action(async (runId, opts) => {
|
|
1580
1837
|
try {
|
|
1581
|
-
const result = await client(opts).post(
|
|
1838
|
+
const result = await (await client(opts)).post(
|
|
1582
1839
|
`/api/v1/actions/runs/${runId}/cancel`
|
|
1583
1840
|
);
|
|
1584
1841
|
output(opts, result, (r) => {
|
|
1585
1842
|
console.log(r.cancelled ? `Cancelled ${runId}` : `Run ${runId} was already finished`);
|
|
1586
1843
|
});
|
|
1587
1844
|
} catch (e) {
|
|
1588
|
-
fail(e);
|
|
1845
|
+
fail(e, opts);
|
|
1589
1846
|
}
|
|
1590
1847
|
});
|
|
1591
1848
|
common(
|
|
1592
1849
|
actions.command("rerun").description("Re-run a finished run at the same commit").argument("<run-id>", "run ID")
|
|
1593
1850
|
).action(async (runId, opts) => {
|
|
1594
1851
|
try {
|
|
1595
|
-
const c = client(opts);
|
|
1852
|
+
const c = await client(opts);
|
|
1596
1853
|
const result = await c.post(
|
|
1597
1854
|
`/api/v1/actions/runs/${runId}/rerun`
|
|
1598
1855
|
);
|
|
@@ -1603,14 +1860,14 @@ function registerActionsCommands(program2) {
|
|
|
1603
1860
|
console.log(`url: ${r.url}`);
|
|
1604
1861
|
});
|
|
1605
1862
|
} catch (e) {
|
|
1606
|
-
fail(e);
|
|
1863
|
+
fail(e, opts);
|
|
1607
1864
|
}
|
|
1608
1865
|
});
|
|
1609
1866
|
common(
|
|
1610
1867
|
actions.command("artifacts").description("List or download a run's artifacts").argument("<run-id>", "run ID").option("--job <job>", "job ID or name (default: all jobs)").option("--out <dir>", "download artifacts into this directory instead of listing")
|
|
1611
1868
|
).action(async (runId, opts) => {
|
|
1612
1869
|
try {
|
|
1613
|
-
const c = client(opts);
|
|
1870
|
+
const c = await client(opts);
|
|
1614
1871
|
let jobs = await listRunJobs(c, runId);
|
|
1615
1872
|
if (opts.job) {
|
|
1616
1873
|
const job = matchJob(jobs, opts.job);
|
|
@@ -1659,7 +1916,7 @@ function registerActionsCommands(program2) {
|
|
|
1659
1916
|
if (!any) console.log("No artifacts.");
|
|
1660
1917
|
});
|
|
1661
1918
|
} catch (e) {
|
|
1662
|
-
fail(e);
|
|
1919
|
+
fail(e, opts);
|
|
1663
1920
|
}
|
|
1664
1921
|
});
|
|
1665
1922
|
}
|