@computesdk/cli 1.0.10 → 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 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 readFileSync4 } from "fs";
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
@@ -747,6 +747,14 @@ var ActionsClient = class {
747
747
  });
748
748
  return this.parse(res);
749
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
+ }
750
758
  async del(path5) {
751
759
  const res = await this.fetchImpl(`${this.auth.baseUrl}${path5}`, {
752
760
  method: "DELETE",
@@ -892,6 +900,18 @@ async function listRunJobs(c, runId) {
892
900
  steps: []
893
901
  }));
894
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
+ }
895
915
  function fail(error) {
896
916
  if (error instanceof ActionsApiError) {
897
917
  console.error(pc.red(`Error (${error.status}): ${error.message}`));
@@ -970,6 +990,96 @@ function formatRunDetail(run, runUrl) {
970
990
  }
971
991
  return lines.join("\n");
972
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
+ }
973
1083
  function parseInputs(pairs) {
974
1084
  const inputs = {};
975
1085
  for (const pair of pairs ?? []) {
@@ -1109,6 +1219,20 @@ function formatVerifyResult(r) {
1109
1219
  const act = r.key.actCapable ? pc.green(" act-capable") : "";
1110
1220
  return `${head} ${r.key.provider}${act}${detail}`;
1111
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
+ }
1112
1236
  function registerActionsCommands(program2) {
1113
1237
  const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API");
1114
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");
@@ -1206,7 +1330,37 @@ function registerActionsCommands(program2) {
1206
1330
  const run = await c.get(`/api/v1/actions/runs/${runId}`);
1207
1331
  const org = await c.org();
1208
1332
  const url = `${c.baseUrl}/${org.slug}/actions/runs/${run.id}`;
1209
- output(opts, { ...run, url }, (r) => console.log(formatRunDetail(r, r.url)));
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)));
1210
1364
  } catch (e) {
1211
1365
  fail(e);
1212
1366
  }
@@ -1337,6 +1491,89 @@ function registerActionsCommands(program2) {
1337
1491
  fail(e);
1338
1492
  }
1339
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
+ });
1340
1577
  common(
1341
1578
  actions.command("cancel").description("Cancel a run").argument("<run-id>", "run ID")
1342
1579
  ).action(async (runId, opts) => {
@@ -1670,7 +1907,7 @@ init_workspace_cli();
1670
1907
  var __filename2 = fileURLToPath2(import.meta.url);
1671
1908
  var __dirname2 = dirname2(__filename2);
1672
1909
  var packageJson = JSON.parse(
1673
- readFileSync4(join5(__dirname2, "..", "package.json"), "utf-8")
1910
+ readFileSync5(join5(__dirname2, "..", "package.json"), "utf-8")
1674
1911
  );
1675
1912
  var isDevBuild = import.meta.url.includes("worktrees") || import.meta.url.includes("/packages/cli/");
1676
1913
  var VERSION = isDevBuild ? `${packageJson.version}-dev` : packageJson.version;