@computesdk/cli 1.0.9 → 1.0.11
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 +413 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -587,7 +587,7 @@ import "dotenv/config";
|
|
|
587
587
|
import { Command } from "commander";
|
|
588
588
|
import * as p4 from "@clack/prompts";
|
|
589
589
|
import pc6 from "picocolors";
|
|
590
|
-
import { readFileSync as
|
|
590
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
591
591
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
592
592
|
import { dirname as dirname2, join as join5 } from "path";
|
|
593
593
|
import { compute as compute4 } from "computesdk";
|
|
@@ -694,7 +694,7 @@ function buildProviderConfig(provider) {
|
|
|
694
694
|
// src/actions.ts
|
|
695
695
|
init_esm_shims();
|
|
696
696
|
import pc from "picocolors";
|
|
697
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
697
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
698
698
|
import { basename, join } from "path";
|
|
699
699
|
|
|
700
700
|
// src/actions-client.ts
|
|
@@ -739,6 +739,29 @@ var ActionsClient = class {
|
|
|
739
739
|
});
|
|
740
740
|
return this.parse(res);
|
|
741
741
|
}
|
|
742
|
+
async put(path5, body = {}) {
|
|
743
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
|
|
744
|
+
method: "PUT",
|
|
745
|
+
headers: { ...this.headers(), "content-type": "application/json" },
|
|
746
|
+
body: JSON.stringify(body)
|
|
747
|
+
});
|
|
748
|
+
return this.parse(res);
|
|
749
|
+
}
|
|
750
|
+
async patch(path5, body = {}) {
|
|
751
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
|
|
752
|
+
method: "PATCH",
|
|
753
|
+
headers: { ...this.headers(), "content-type": "application/json" },
|
|
754
|
+
body: JSON.stringify(body)
|
|
755
|
+
});
|
|
756
|
+
return this.parse(res);
|
|
757
|
+
}
|
|
758
|
+
async del(path5) {
|
|
759
|
+
const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
|
|
760
|
+
method: "DELETE",
|
|
761
|
+
headers: this.headers()
|
|
762
|
+
});
|
|
763
|
+
return this.parse(res);
|
|
764
|
+
}
|
|
742
765
|
/**
|
|
743
766
|
* One `data:` JSON payload per SSE event. `next()` resolves to the payload,
|
|
744
767
|
* or null when the stream ends. Call `close()` to abort.
|
|
@@ -877,6 +900,18 @@ async function listRunJobs(c, runId) {
|
|
|
877
900
|
steps: []
|
|
878
901
|
}));
|
|
879
902
|
}
|
|
903
|
+
async function fetchRunSummary(c, runId) {
|
|
904
|
+
try {
|
|
905
|
+
return await c.get(`/api/v1/actions/runs/${runId}/summary`);
|
|
906
|
+
} catch (e) {
|
|
907
|
+
if (!(e instanceof ActionsApiError && e.status === 404)) {
|
|
908
|
+
console.error(
|
|
909
|
+
pc.dim(`summary unavailable: ${e instanceof Error ? e.message : String(e)}`)
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
880
915
|
function fail(error) {
|
|
881
916
|
if (error instanceof ActionsApiError) {
|
|
882
917
|
console.error(pc.red(`Error (${error.status}): ${error.message}`));
|
|
@@ -935,6 +970,7 @@ function formatRunDetail(run, runUrl) {
|
|
|
935
970
|
lines.push(`title: ${run.title}`);
|
|
936
971
|
lines.push(`started: ${run.startedAt} duration: ${formatDuration(run.durationMs)}`);
|
|
937
972
|
if (run.cancellationReason) lines.push(`cancelled: ${run.cancellationReason}`);
|
|
973
|
+
if (run.providerOverride) lines.push(`dispatched to: ${run.providerOverride}`);
|
|
938
974
|
if (run.supersededByRunId) lines.push(`superseded by: ${run.supersededByRunId}`);
|
|
939
975
|
if (runUrl) lines.push(`url: ${runUrl}`);
|
|
940
976
|
if (run.jobs.length > 0) {
|
|
@@ -954,6 +990,96 @@ function formatRunDetail(run, runUrl) {
|
|
|
954
990
|
}
|
|
955
991
|
return lines.join("\n");
|
|
956
992
|
}
|
|
993
|
+
function formatRunInspection(run) {
|
|
994
|
+
const lines = [];
|
|
995
|
+
lines.push(`${pc.bold("run")} ${conclusionLabel(run.conclusion)}${run.runNumber === null ? "" : ` #${run.runNumber}`}`);
|
|
996
|
+
lines.push(pc.dim(run.id));
|
|
997
|
+
lines.push(`${safeTerm(run.ref)} ${shortSha(run.headSha)} ${safeTerm(run.event)}`);
|
|
998
|
+
lines.push(`queued: ${run.queuedAt} started: ${run.startedAt} finished: ${run.finishedAt}`);
|
|
999
|
+
if (run.cancellationReason) lines.push(`cancelled: ${run.cancellationReason}`);
|
|
1000
|
+
if (run.supersededByRunId) lines.push(`superseded by: ${run.supersededByRunId}`);
|
|
1001
|
+
if (run.blockedReason) lines.push(pc.red(`blocked: ${safeTerm(run.blockedReason)}`));
|
|
1002
|
+
if (run.concurrencyGroup) lines.push(`concurrency: ${safeTerm(run.concurrencyGroup)}`);
|
|
1003
|
+
if (run.providerOverride) lines.push(`dispatched to: ${safeTerm(run.providerOverride)}`);
|
|
1004
|
+
if (run.definitionStale) {
|
|
1005
|
+
const parsed = run.workflowParsedFromSha ? shortSha(run.workflowParsedFromSha) : "unknown";
|
|
1006
|
+
lines.push(pc.yellow(`definition: stored parse is from ${parsed}, not this head \u2014 context may be newer than the run`));
|
|
1007
|
+
}
|
|
1008
|
+
if (run.dispatchInputs && Object.keys(run.dispatchInputs).length > 0) {
|
|
1009
|
+
lines.push(`inputs: ${Object.entries(run.dispatchInputs).map(([k, v]) => `${safeTerm(k)}=${safeTerm(v)}`).join(" ")}`);
|
|
1010
|
+
}
|
|
1011
|
+
if (run.secrets) {
|
|
1012
|
+
const names = run.secrets.names.length > 0 ? run.secrets.names.map(safeTerm).join(", ") : "none";
|
|
1013
|
+
lines.push(`secrets (${run.secrets.access}${run.secrets.env ? ", exported to env" : ""}): ${names}`);
|
|
1014
|
+
}
|
|
1015
|
+
if (run.caches.length > 0) {
|
|
1016
|
+
lines.push("");
|
|
1017
|
+
lines.push(pc.bold("caches"));
|
|
1018
|
+
for (const cache of run.caches) {
|
|
1019
|
+
const action = [cache.restoredAt ? "restored" : null, cache.savedAt ? "saved" : null].filter(Boolean).join("+");
|
|
1020
|
+
lines.push(` ${safeTerm(cache.key)} ${pc.dim(`${action} ${safeTerm(cache.scopeRef)} ${cache.sizeBytes}B`)}`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (run.jobs.length > 0) {
|
|
1024
|
+
lines.push("");
|
|
1025
|
+
lines.push(pc.bold("jobs"));
|
|
1026
|
+
for (const job of run.jobs) {
|
|
1027
|
+
const placement = job.provider ? `${safeTerm(job.provider)}${job.region ? `:${safeTerm(job.region)}` : ""}` : "-";
|
|
1028
|
+
lines.push(` ${safeTerm(job.name)} ${conclusionLabel(job.state)} ${placement}`);
|
|
1029
|
+
lines.push(pc.dim(` ${job.id}`));
|
|
1030
|
+
if (job.runsOn.length > 0 || job.resolvedRunsOn !== null) {
|
|
1031
|
+
const resolved = job.resolvedRunsOn !== null && job.resolvedRunsOn.join(",") !== job.runsOn.join(",") ? ` \u2192 ${job.resolvedRunsOn.map(safeTerm).join(", ")}` : "";
|
|
1032
|
+
lines.push(` runs-on: ${job.runsOn.map(safeTerm).join(", ") || "(none)"}${resolved}`);
|
|
1033
|
+
}
|
|
1034
|
+
if (job.runsOnHasExpression) lines.push(pc.dim(" runs-on resolves at runtime (expression)"));
|
|
1035
|
+
if (job.container) lines.push(` container: ${safeTerm(job.container)}`);
|
|
1036
|
+
if (job.runnerImage) lines.push(` image: ${safeTerm(job.runnerImage)}`);
|
|
1037
|
+
if (job.placementHint) {
|
|
1038
|
+
lines.push(` pinned: ${safeTerm(job.placementHint.label)}`);
|
|
1039
|
+
}
|
|
1040
|
+
if (job.concurrencyGroup) {
|
|
1041
|
+
lines.push(` concurrency: ${safeTerm(job.concurrencyGroup)}${job.concurrencyCancelInProgress ? " (cancel-in-progress)" : ""}`);
|
|
1042
|
+
}
|
|
1043
|
+
const overrides = [
|
|
1044
|
+
job.timeoutMinutes !== null ? `timeout ${job.timeoutMinutes}m` : null,
|
|
1045
|
+
job.fetchDepth !== null ? `fetch-depth ${job.fetchDepth}` : null
|
|
1046
|
+
].filter(Boolean);
|
|
1047
|
+
if (overrides.length > 0) lines.push(` ${overrides.join(" ")}`);
|
|
1048
|
+
if (job.matrix) {
|
|
1049
|
+
lines.push(` matrix: ${Object.entries(job.matrix).map(([k, v]) => `${safeTerm(k)}=${safeTerm(v)}`).join(" ")}`);
|
|
1050
|
+
}
|
|
1051
|
+
if (job.failureReason) lines.push(pc.red(` failed: ${safeTerm(job.failureReason)}`));
|
|
1052
|
+
for (const attempt of job.placementAttempts) {
|
|
1053
|
+
lines.push(pc.dim(` placement failed on ${safeTerm(attempt.provider)}${attempt.region ? `:${safeTerm(attempt.region)}` : ""}: ${safeTerm(attempt.error)}`));
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return lines.join("\n");
|
|
1058
|
+
}
|
|
1059
|
+
function formatRunSummary(summary) {
|
|
1060
|
+
const lines = [];
|
|
1061
|
+
lines.push(pc.bold(`failures \u2014 run ${summary.runId} ${conclusionLabel(summary.conclusion)}`));
|
|
1062
|
+
if (summary.failures.length === 0) {
|
|
1063
|
+
lines.push(" no failed jobs");
|
|
1064
|
+
return lines.join("\n");
|
|
1065
|
+
}
|
|
1066
|
+
for (const job of summary.failures) {
|
|
1067
|
+
const placement = job.provider ? `${safeTerm(job.provider)}${job.region ? `:${safeTerm(job.region)}` : ""}` : "-";
|
|
1068
|
+
lines.push(` ${job.id} ${safeTerm(job.name)} ${conclusionLabel(job.conclusion)} ${placement}`);
|
|
1069
|
+
if (job.failureReason) lines.push(pc.dim(` ${safeTerm(job.failureReason)}`));
|
|
1070
|
+
for (const step of job.failedSteps) {
|
|
1071
|
+
const exit = step.exitCode === null ? "" : ` (exit ${step.exitCode})`;
|
|
1072
|
+
lines.push(` step ${step.ordinal} ${safeTerm(step.name)}${exit}`);
|
|
1073
|
+
}
|
|
1074
|
+
if (job.excerpt) {
|
|
1075
|
+
const scope = job.excerpt.stepOrdinal === null ? "job log" : `step ${job.excerpt.stepOrdinal}`;
|
|
1076
|
+
const cut = job.excerpt.truncated ? ", truncated" : "";
|
|
1077
|
+
lines.push(pc.dim(` \u2500\u2500 ${scope} tail${cut} \u2500\u2500`));
|
|
1078
|
+
for (const line of job.excerpt.text.split("\n")) lines.push(` ${safeTerm(line)}`);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return lines.join("\n");
|
|
1082
|
+
}
|
|
957
1083
|
function parseInputs(pairs) {
|
|
958
1084
|
const inputs = {};
|
|
959
1085
|
for (const pair of pairs ?? []) {
|
|
@@ -1041,11 +1167,77 @@ async function* followRunLogs(client2, runId, jobs, step) {
|
|
|
1041
1167
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
1042
1168
|
}
|
|
1043
1169
|
}
|
|
1170
|
+
function safeTerm(s) {
|
|
1171
|
+
return s.replace(/[\u0000-\u001F\u007F-\u009F]/g, "\uFFFD");
|
|
1172
|
+
}
|
|
1173
|
+
function formatRunHistory(history) {
|
|
1174
|
+
const lines = [];
|
|
1175
|
+
const parts = Object.entries(history.conclusions).map(([c, n]) => `${n} ${c}`).join(", ");
|
|
1176
|
+
lines.push(`last ${history.runCount} runs: ${parts || "none"}`);
|
|
1177
|
+
if (history.jobs.length > 0) {
|
|
1178
|
+
lines.push("");
|
|
1179
|
+
lines.push(pc.bold("jobs"));
|
|
1180
|
+
for (const job of history.jobs) {
|
|
1181
|
+
const rate = job.runs > 0 ? Math.round(job.failedRuns / job.runs * 100) : 0;
|
|
1182
|
+
const platform = job.failedBeforeSteps > 0 ? pc.dim(` (${job.failedBeforeSteps} failed before steps)`) : "";
|
|
1183
|
+
const count = `${job.failedRuns}/${job.runs} runs failed`;
|
|
1184
|
+
const label = job.failedRuns > 0 ? pc.red(count) : pc.green(count);
|
|
1185
|
+
lines.push(` ${safeTerm(job.job)} ${label} ${rate}%${platform}`);
|
|
1186
|
+
for (const step of job.steps) {
|
|
1187
|
+
lines.push(pc.dim(` step "${safeTerm(step.step)}" failed in ${step.failedRuns} run${step.failedRuns === 1 ? "" : "s"}`));
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
const failed = history.runs.filter(
|
|
1192
|
+
(r) => r.conclusion === "failed" || r.failedJobs.length > 0
|
|
1193
|
+
);
|
|
1194
|
+
if (failed.length > 0) {
|
|
1195
|
+
lines.push("");
|
|
1196
|
+
lines.push(pc.bold("failed runs"));
|
|
1197
|
+
for (const run of failed) {
|
|
1198
|
+
const blame = run.failedJobs.length > 0 ? run.failedJobs.map((j) => safeTerm(j.step === null ? j.job : `${j.job}:${j.step}`)).join(", ") : pc.dim("(no failed job recorded)");
|
|
1199
|
+
lines.push(` ${shortSha(run.headSha)} ${pc.dim(run.startedAt)} ${blame}`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return lines.join("\n");
|
|
1203
|
+
}
|
|
1204
|
+
function formatProviderRow(p5) {
|
|
1205
|
+
const icon = p5.usable ? pc.green("\u25CF") : pc.gray("\u25CB");
|
|
1206
|
+
const name = p5.usable ? pc.white(p5.provider) : pc.gray(p5.provider);
|
|
1207
|
+
const flags = [
|
|
1208
|
+
p5.credential,
|
|
1209
|
+
p5.actCapable ? pc.green("act") : pc.gray("no-act"),
|
|
1210
|
+
p5.regions.length > 0 ? `regions: ${p5.regions.join(",")}` : "no region choice",
|
|
1211
|
+
p5.position === null ? pc.gray("not in provider order") : `order #${p5.position}`
|
|
1212
|
+
].join(" ");
|
|
1213
|
+
return ` ${icon} ${name} ${flags}`;
|
|
1214
|
+
}
|
|
1215
|
+
function formatVerifyResult(r) {
|
|
1216
|
+
const ok = r.verified === true;
|
|
1217
|
+
const head = ok ? pc.green("verified") : pc.red("not verified");
|
|
1218
|
+
const detail = r.key.statusDetail ? pc.dim(` ${r.key.statusDetail}`) : "";
|
|
1219
|
+
const act = r.key.actCapable ? pc.green(" act-capable") : "";
|
|
1220
|
+
return `${head} ${r.key.provider}${act}${detail}`;
|
|
1221
|
+
}
|
|
1222
|
+
function formatRepoRow(r) {
|
|
1223
|
+
const head = r.enabled ? pc.green("enabled") : pc.dim("disabled");
|
|
1224
|
+
const via = r.authType === "github_app" ? "github-app" : r.authType;
|
|
1225
|
+
const workflows = r.workflowPaths.length > 0 ? `${r.workflowPaths.length} workflow${r.workflowPaths.length === 1 ? "" : "s"}` : "no workflows yet";
|
|
1226
|
+
const error = r.lastPollError ? pc.red(` poll error: ${safeTerm(r.lastPollError)}`) : "";
|
|
1227
|
+
return `${head} ${safeTerm(r.fullName)} ${pc.dim(`${safeTerm(via)} \xB7 ${safeTerm(r.defaultBranch)} \xB7 ${workflows}`)}${error}`;
|
|
1228
|
+
}
|
|
1229
|
+
function printDiscovery(d) {
|
|
1230
|
+
if (d.error) {
|
|
1231
|
+
console.log(pc.yellow(`discovery: ${safeTerm(d.error)} (the poll cron retries)`));
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
console.log(pc.dim(`discovery: ${d.workflowsFound ? "workflows found" : "no workflows yet"}${d.seeded ? ", cursors seeded" : ""}`));
|
|
1235
|
+
}
|
|
1044
1236
|
function registerActionsCommands(program2) {
|
|
1045
1237
|
const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API");
|
|
1046
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 the API key to a non-computesdk, non-localhost --base-url").option("--json", "print machine-readable JSON");
|
|
1047
1239
|
common(
|
|
1048
|
-
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")
|
|
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>)")
|
|
1049
1241
|
).action(async (repo, opts) => {
|
|
1050
1242
|
try {
|
|
1051
1243
|
const c = client(opts);
|
|
@@ -1062,9 +1254,20 @@ function registerActionsCommands(program2) {
|
|
|
1062
1254
|
}
|
|
1063
1255
|
const ref = opts.ref ?? workflow.refs[0];
|
|
1064
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
|
+
}
|
|
1065
1260
|
const result = await c.post(
|
|
1066
1261
|
"/api/v1/actions/dispatch",
|
|
1067
|
-
{
|
|
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
|
+
}
|
|
1068
1271
|
);
|
|
1069
1272
|
const org = await c.org();
|
|
1070
1273
|
const url = `${c.baseUrl}/${org.slug}/actions/runs/${result.runId}`;
|
|
@@ -1096,6 +1299,29 @@ function registerActionsCommands(program2) {
|
|
|
1096
1299
|
fail(e);
|
|
1097
1300
|
}
|
|
1098
1301
|
});
|
|
1302
|
+
common(
|
|
1303
|
+
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
|
+
).action(async (repo, opts) => {
|
|
1305
|
+
try {
|
|
1306
|
+
const c = client(opts);
|
|
1307
|
+
const { workflows } = await c.get(
|
|
1308
|
+
"/api/v1/actions/workflows",
|
|
1309
|
+
{ repo }
|
|
1310
|
+
);
|
|
1311
|
+
const workflow = matchWorkflow(workflows, opts.workflow) ?? workflows.find((w) => w.path === opts.workflow);
|
|
1312
|
+
const workflowPath = workflow?.path ?? opts.workflow;
|
|
1313
|
+
const history = await c.get("/api/v1/actions/history", {
|
|
1314
|
+
repo,
|
|
1315
|
+
workflow: workflowPath,
|
|
1316
|
+
branch: opts.branch,
|
|
1317
|
+
job: opts.job,
|
|
1318
|
+
limit: opts.limit
|
|
1319
|
+
});
|
|
1320
|
+
output(opts, history, (h) => console.log(formatRunHistory(h)));
|
|
1321
|
+
} catch (e) {
|
|
1322
|
+
fail(e);
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1099
1325
|
common(
|
|
1100
1326
|
actions.command("run").description("Show a run: jobs, provider:region placement, placement attempts").argument("<run-id>", "run ID")
|
|
1101
1327
|
).action(async (runId, opts) => {
|
|
@@ -1104,7 +1330,37 @@ function registerActionsCommands(program2) {
|
|
|
1104
1330
|
const run = await c.get(`/api/v1/actions/runs/${runId}`);
|
|
1105
1331
|
const org = await c.org();
|
|
1106
1332
|
const url = `${c.baseUrl}/${org.slug}/actions/runs/${run.id}`;
|
|
1107
|
-
|
|
1333
|
+
const summary = run.conclusion === "failed" ? await fetchRunSummary(c, runId) : null;
|
|
1334
|
+
output(opts, { ...run, url, summary }, (r) => {
|
|
1335
|
+
console.log(formatRunDetail(r, r.url));
|
|
1336
|
+
if (summary && summary.failures.length > 0) {
|
|
1337
|
+
console.log("");
|
|
1338
|
+
console.log(formatRunSummary(summary));
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
} catch (e) {
|
|
1342
|
+
fail(e);
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
common(
|
|
1346
|
+
actions.command("summary").description("Show a run's failure digest: failed jobs, failed steps, bounded log tails").argument("<run-id>", "run ID")
|
|
1347
|
+
).action(async (runId, opts) => {
|
|
1348
|
+
try {
|
|
1349
|
+
const summary = await client(opts).get(`/api/v1/actions/runs/${runId}/summary`);
|
|
1350
|
+
output(opts, summary, (s) => console.log(formatRunSummary(s)));
|
|
1351
|
+
} catch (e) {
|
|
1352
|
+
fail(e);
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
common(
|
|
1356
|
+
actions.command("inspect").description("Inspect a run: resolved runner image, caches, secret names, concurrency, placement context").argument("<run-id>", "run ID")
|
|
1357
|
+
).action(async (runId, opts) => {
|
|
1358
|
+
try {
|
|
1359
|
+
const c = client(opts);
|
|
1360
|
+
const inspection = await c.get(
|
|
1361
|
+
`/api/v1/actions/runs/${runId}/state`
|
|
1362
|
+
);
|
|
1363
|
+
output(opts, inspection, (r) => console.log(formatRunInspection(r)));
|
|
1108
1364
|
} catch (e) {
|
|
1109
1365
|
fail(e);
|
|
1110
1366
|
}
|
|
@@ -1167,6 +1423,157 @@ function registerActionsCommands(program2) {
|
|
|
1167
1423
|
fail(e);
|
|
1168
1424
|
}
|
|
1169
1425
|
});
|
|
1426
|
+
const providers = actions.command("providers").description("List the org's registered compute providers (regions, act/usable status)");
|
|
1427
|
+
common(providers).action(async (opts) => {
|
|
1428
|
+
try {
|
|
1429
|
+
const data = await client(opts).get("/api/v1/actions/providers");
|
|
1430
|
+
output(opts, data, (d) => {
|
|
1431
|
+
for (const p5 of d.providers) console.log(formatProviderRow(p5));
|
|
1432
|
+
});
|
|
1433
|
+
} catch (e) {
|
|
1434
|
+
fail(e);
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
common(
|
|
1438
|
+
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
|
+
).action(async (provider, opts) => {
|
|
1440
|
+
try {
|
|
1441
|
+
const c = client(opts);
|
|
1442
|
+
let body;
|
|
1443
|
+
if (opts.field !== void 0) {
|
|
1444
|
+
if (opts.key !== void 0) throw new Error("Pass either --key or --field, not both.");
|
|
1445
|
+
body = { fields: parseInputs(opts.field) };
|
|
1446
|
+
} else if (opts.key !== void 0) {
|
|
1447
|
+
body = { key: opts.key };
|
|
1448
|
+
} else {
|
|
1449
|
+
throw new Error("Pass --key <key> or --field name=value.");
|
|
1450
|
+
}
|
|
1451
|
+
const saved = await c.put(
|
|
1452
|
+
`/api/v1/actions/providers/${provider}/key`,
|
|
1453
|
+
body
|
|
1454
|
+
);
|
|
1455
|
+
const verification = opts.verify ? await c.post(
|
|
1456
|
+
`/api/v1/actions/providers/${provider}/verify`
|
|
1457
|
+
) : void 0;
|
|
1458
|
+
output(opts, verification ? { key: saved.key, verification } : saved, () => {
|
|
1459
|
+
console.log(`saved ${saved.key.provider} ${pc.dim(saved.key.keyHint)} ${saved.key.status}`);
|
|
1460
|
+
if (verification) console.log(formatVerifyResult(verification));
|
|
1461
|
+
});
|
|
1462
|
+
if (verification?.verified === false) process.exitCode = 1;
|
|
1463
|
+
} catch (e) {
|
|
1464
|
+
fail(e);
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
common(
|
|
1468
|
+
providers.command("verify").description('Probe the stored provider key with a real sandbox ("Test" in Settings)').argument("<provider>", "provider id")
|
|
1469
|
+
).action(async (provider, opts) => {
|
|
1470
|
+
try {
|
|
1471
|
+
const result = await client(opts).post(
|
|
1472
|
+
`/api/v1/actions/providers/${provider}/verify`
|
|
1473
|
+
);
|
|
1474
|
+
output(opts, result, (r) => console.log(formatVerifyResult(r)));
|
|
1475
|
+
if (result.verified === false) process.exitCode = 1;
|
|
1476
|
+
} catch (e) {
|
|
1477
|
+
fail(e);
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
common(
|
|
1481
|
+
providers.command("remove").description("Delete the org's stored credential for a provider").argument("<provider>", "provider id")
|
|
1482
|
+
).action(async (provider, opts) => {
|
|
1483
|
+
try {
|
|
1484
|
+
const result = await client(opts).del(
|
|
1485
|
+
`/api/v1/actions/providers/${provider}/key`
|
|
1486
|
+
);
|
|
1487
|
+
output(opts, result, (r) => {
|
|
1488
|
+
console.log(r.deleted ? `deleted ${r.provider}` : `no key stored for ${r.provider}`);
|
|
1489
|
+
});
|
|
1490
|
+
} catch (e) {
|
|
1491
|
+
fail(e);
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
const repos = actions.command("repos").description("List the org's connected repositories");
|
|
1495
|
+
common(repos).action(async (opts) => {
|
|
1496
|
+
try {
|
|
1497
|
+
const data = await client(opts).get("/api/v1/actions/repos");
|
|
1498
|
+
output(opts, data, (d) => {
|
|
1499
|
+
for (const r of d.repos) console.log(formatRepoRow(r));
|
|
1500
|
+
});
|
|
1501
|
+
} catch (e) {
|
|
1502
|
+
fail(e);
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
common(
|
|
1506
|
+
repos.command("connect").description("Connect a git remote (validated by a real ls-remote; lands enabled)").argument("<cloneUrl>", "http(s) or ssh git remote URL").option("--name <owner/repo>", "repo name override (default: derived from cloneUrl)").option("--branch <branch>", "default branch (default: main, else the remote's first)").option("--token <token>", "credential for private http(s) remotes").option("--username <user>", 'username \u2014 with --token, makes auth "basic" instead of "token"').option("--ssh-key-file <path>", "OpenSSH/PEM private key file for ssh remotes").option("--known-hosts-file <path>", "known_hosts pin for ssh remotes")
|
|
1507
|
+
).action(async (cloneUrl, opts) => {
|
|
1508
|
+
try {
|
|
1509
|
+
let body;
|
|
1510
|
+
if (opts.sshKeyFile !== void 0) {
|
|
1511
|
+
if (opts.token !== void 0 || opts.username !== void 0) {
|
|
1512
|
+
throw new Error("Pass either --ssh-key-file or --token/--username, not both.");
|
|
1513
|
+
}
|
|
1514
|
+
body = {
|
|
1515
|
+
authType: "ssh",
|
|
1516
|
+
credential: readFileSync(opts.sshKeyFile, "utf8"),
|
|
1517
|
+
...opts.knownHostsFile !== void 0 && {
|
|
1518
|
+
knownHosts: readFileSync(opts.knownHostsFile, "utf8")
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
} else if (opts.username !== void 0) {
|
|
1522
|
+
if (opts.token === void 0) throw new Error("--username requires --token.");
|
|
1523
|
+
body = { authType: "basic", username: opts.username, credential: opts.token };
|
|
1524
|
+
} else if (opts.token !== void 0) {
|
|
1525
|
+
body = { authType: "token", credential: opts.token };
|
|
1526
|
+
} else {
|
|
1527
|
+
body = { authType: "none" };
|
|
1528
|
+
}
|
|
1529
|
+
if (opts.knownHostsFile !== void 0 && opts.sshKeyFile === void 0) {
|
|
1530
|
+
throw new Error("--known-hosts-file requires --ssh-key-file.");
|
|
1531
|
+
}
|
|
1532
|
+
const result = await client(opts).post("/api/v1/actions/repos", {
|
|
1533
|
+
cloneUrl,
|
|
1534
|
+
...body,
|
|
1535
|
+
...opts.name !== void 0 && { name: opts.name },
|
|
1536
|
+
...opts.branch !== void 0 && { defaultBranch: opts.branch }
|
|
1537
|
+
});
|
|
1538
|
+
output(opts, result, (r) => {
|
|
1539
|
+
console.log(`connected ${pc.cyan(safeTerm(r.fullName))} ${pc.dim(`(${safeTerm(r.authType)}, ${safeTerm(r.defaultBranch)})`)}`);
|
|
1540
|
+
printDiscovery(r.discovery);
|
|
1541
|
+
});
|
|
1542
|
+
} catch (e) {
|
|
1543
|
+
fail(e);
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
common(
|
|
1547
|
+
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
|
+
).action(async (repo, opts) => {
|
|
1549
|
+
try {
|
|
1550
|
+
const result = await client(opts).patch("/api/v1/actions/repos", {
|
|
1551
|
+
fullName: repo,
|
|
1552
|
+
enabled: true,
|
|
1553
|
+
...opts.repoId !== void 0 && { repoId: opts.repoId }
|
|
1554
|
+
});
|
|
1555
|
+
output(opts, result, (r) => {
|
|
1556
|
+
console.log(`enabled ${pc.cyan(safeTerm(r.fullName))}`);
|
|
1557
|
+
if (r.discovery) printDiscovery(r.discovery);
|
|
1558
|
+
});
|
|
1559
|
+
} catch (e) {
|
|
1560
|
+
fail(e);
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
common(
|
|
1564
|
+
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
|
+
).action(async (repo, opts) => {
|
|
1566
|
+
try {
|
|
1567
|
+
const result = await client(opts).patch("/api/v1/actions/repos", {
|
|
1568
|
+
fullName: repo,
|
|
1569
|
+
enabled: false,
|
|
1570
|
+
...opts.repoId !== void 0 && { repoId: opts.repoId }
|
|
1571
|
+
});
|
|
1572
|
+
output(opts, result, (r) => console.log(`disabled ${pc.cyan(safeTerm(r.fullName))}`));
|
|
1573
|
+
} catch (e) {
|
|
1574
|
+
fail(e);
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1170
1577
|
common(
|
|
1171
1578
|
actions.command("cancel").description("Cancel a run").argument("<run-id>", "run ID")
|
|
1172
1579
|
).action(async (runId, opts) => {
|
|
@@ -1500,7 +1907,7 @@ init_workspace_cli();
|
|
|
1500
1907
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
1501
1908
|
var __dirname2 = dirname2(__filename2);
|
|
1502
1909
|
var packageJson = JSON.parse(
|
|
1503
|
-
|
|
1910
|
+
readFileSync5(join5(__dirname2, "..", "package.json"), "utf-8")
|
|
1504
1911
|
);
|
|
1505
1912
|
var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includes("/packages/cli/");
|
|
1506
1913
|
var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;
|