@theholocron/cli 5.0.0-alpha.122 → 5.0.0-alpha.124

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/cli.mjs CHANGED
@@ -309,6 +309,8 @@ const style = {
309
309
  warn: (msg) => `${chalk.yellow("⚠")} ${msg}`,
310
310
  fail: (msg) => `${chalk.red("✗")} ${msg}`,
311
311
  step: (msg) => `${chalk.cyan("→")} ${msg}`,
312
+ /** A dim `·` marker for a row that's neither a pass nor a failure — loaded but inactive, or skipped. */
313
+ skip: (msg) => chalk.dim(`· ${msg}`),
312
314
  hint: (msg) => chalk.dim(msg),
313
315
  dim: (msg) => chalk.dim(msg),
314
316
  header: (msg) => chalk.bold(msg)
@@ -1117,6 +1119,27 @@ async function runCleanupPreview(input) {
1117
1119
  }
1118
1120
  }
1119
1121
  //#endregion
1122
+ //#region src/ui/status-row.ts
1123
+ /** Right-pads `s` to `width` with spaces — never truncates. */
1124
+ function pad(s, width) {
1125
+ return s.length >= width ? s : s + " ".repeat(width - s.length);
1126
+ }
1127
+ /**
1128
+ * One row of a status report: a 2-space indent, the status icon (✓ / ✗ / ·),
1129
+ * then `columns` joined with a two-space gap (blank columns dropped, so a
1130
+ * row that has nothing for its last column doesn't trail whitespace).
1131
+ * Column padding (`pad`) is the caller's job — `doctor` pads capability and
1132
+ * provider to a fixed width; `clone`'s repo names and paths don't, since
1133
+ * they vary too widely for fixed-width alignment to read cleanly.
1134
+ *
1135
+ * Shared by `doctor` (capability / provider / message) and `clone` (repo /
1136
+ * destination / reason) so every status table in the CLI reads the same way.
1137
+ */
1138
+ function statusRow(status, columns) {
1139
+ const joined = columns.filter((c) => c.length > 0).join(" ");
1140
+ return ` ${status === "ok" ? style.success(joined) : status === "fail" ? style.fail(joined) : style.skip(joined)}`;
1141
+ }
1142
+ //#endregion
1120
1143
  //#region src/commands/clone.ts
1121
1144
  /**
1122
1145
  * Accepts a bare org (`"theholocron"`) — clones every repo in it — or an
@@ -1190,6 +1213,7 @@ async function runClone(input) {
1190
1213
  stdio: "inherit"
1191
1214
  }).status };
1192
1215
  });
1216
+ print("");
1193
1217
  if (!existsSync(targetDir)) {
1194
1218
  if (dryRun) print(style.dim(` would create ${targetDir}`));
1195
1219
  else mkdirSync(targetDir, { recursive: true });
@@ -1240,7 +1264,11 @@ async function runClone(input) {
1240
1264
  for (const repo of repos) {
1241
1265
  const dest = join(targetDir, repoDirName(repo));
1242
1266
  if (existsSync(dest)) {
1243
- print(style.dim(` skip ${repo.full_name} → ${dest} (already exists)`));
1267
+ print(statusRow("skip", [
1268
+ repo.full_name,
1269
+ dest,
1270
+ "already exists"
1271
+ ]));
1244
1272
  skipped++;
1245
1273
  continue;
1246
1274
  }
@@ -1252,7 +1280,11 @@ async function runClone(input) {
1252
1280
  print(style.step(` clone ${repo.full_name} → ${dest}`));
1253
1281
  const { clone_url } = repo;
1254
1282
  if (!clone_url.startsWith("https://github.com/")) {
1255
- print(style.fail(` failed ${repo.full_name} → unexpected clone URL: ${clone_url}`));
1283
+ print(statusRow("fail", [
1284
+ repo.full_name,
1285
+ dest,
1286
+ `unexpected clone URL: ${clone_url}`
1287
+ ]));
1256
1288
  failed++;
1257
1289
  continue;
1258
1290
  }
@@ -1260,7 +1292,11 @@ async function runClone(input) {
1260
1292
  try {
1261
1293
  encodedToken = encodeTokenForGitHttpAuth(input.token);
1262
1294
  } catch (err) {
1263
- print(style.fail(` failed ${repo.full_name} → invalid token format: ${errorMessage(err)}`));
1295
+ print(statusRow("fail", [
1296
+ repo.full_name,
1297
+ dest,
1298
+ `invalid token format: ${errorMessage(err)}`
1299
+ ]));
1264
1300
  failed++;
1265
1301
  continue;
1266
1302
  }
@@ -1270,14 +1306,14 @@ async function runClone(input) {
1270
1306
  `https://x-access-token:${encodedToken}@github.com/${clone_url.slice(19)}`,
1271
1307
  dest
1272
1308
  ], { cwd: targetDir }).status !== 0) {
1273
- print(style.fail(` failed ${repo.full_name}`));
1309
+ print(statusRow("fail", [repo.full_name, dest]));
1274
1310
  failed++;
1275
1311
  } else {
1276
- print(style.success(` cloned ${repo.name} → ${dest}`));
1312
+ print(statusRow("ok", [repo.full_name, dest]));
1277
1313
  cloned++;
1278
1314
  }
1279
1315
  }
1280
- const summary = `${cloned} cloned, ${skipped} skipped, ${failed} failed`;
1316
+ const summary = `${cloned} cloned, ${failed} failed, ${skipped} skipped`;
1281
1317
  print("");
1282
1318
  print(dryRun ? style.dim(` dry-run: ${summary}`) : failed > 0 ? style.fail(` ${summary}`) : style.success(` ${summary}`));
1283
1319
  const status = dryRun ? "dry-run" : failed > 0 ? "fail" : "ok";
@@ -1623,7 +1659,7 @@ async function runDoctor(input) {
1623
1659
  assertPluginsResolvable(loader, "doctor");
1624
1660
  const rows = [];
1625
1661
  const config = input.loaded.resolved;
1626
- print(style.header(`Holocron doctor — ${config.name}`));
1662
+ print("");
1627
1663
  print(style.dim(` config: ${input.loaded.filepath}`));
1628
1664
  print("");
1629
1665
  for (const failure of loader.loadFailures()) rows.push({
@@ -1658,10 +1694,7 @@ async function runDoctor(input) {
1658
1694
  status: row.status,
1659
1695
  detail: row.message
1660
1696
  }, `doctor: ${row.capability}`);
1661
- const label = `${pad(row.capability, 14)} via ${pad(row.provider, 14)} ${row.message}`;
1662
- if (row.status === "ok") print(` ${style.success(label)}`);
1663
- else if (row.status === "fail") print(` ${style.fail(label)}`);
1664
- else print(` ${style.dim(`· ${label}`)}`);
1697
+ print(statusRow(row.status, [`${pad(row.capability, 14)} via ${pad(row.provider, 14)}`, row.message]));
1665
1698
  }
1666
1699
  const summary = rows.reduce((acc, r) => {
1667
1700
  acc[r.status] += 1;
@@ -1673,7 +1706,7 @@ async function runDoctor(input) {
1673
1706
  });
1674
1707
  const summaryLine = `${summary.ok} ok, ${summary.fail} fail, ${summary.skip} skipped`;
1675
1708
  print("");
1676
- print(summary.fail > 0 ? style.fail(summaryLine) : style.success(summaryLine));
1709
+ print(style.header(summaryLine));
1677
1710
  return {
1678
1711
  rows,
1679
1712
  summary
@@ -1693,6 +1726,16 @@ async function smokeCheck(key, provider, impl) {
1693
1726
  return mk(key, provider, "ok", runs.length === 0 ? "no recent runs" : `most recent: ${runs[0].workflowName} (${runs[0].status})`);
1694
1727
  }
1695
1728
  case "vault": return mk(key, provider, "ok", `${(await impl.list()).length} keys available`);
1729
+ case "deployment": return mk(key, provider, "ok", `${(await impl.listProjects()).length} project(s) visible`);
1730
+ case "environments": return mk(key, provider, "ok", `${(await impl.listEnvironments()).length} environment(s) configured`);
1731
+ case "dns": {
1732
+ const dns = impl;
1733
+ if (dns.whoami) {
1734
+ const who = await dns.whoami();
1735
+ return mk(key, provider, who.ok ? "ok" : "fail", `${who.zones} zone(s) visible`);
1736
+ }
1737
+ return mk(key, provider, "skip", "loaded (no smoke check defined for this capability)");
1738
+ }
1696
1739
  case "auth": {
1697
1740
  const desc = await impl.describe();
1698
1741
  return mk(key, provider, "ok", `provider: ${desc.provider}; env keys: ${desc.envKeys.join(", ")}`);
@@ -1727,9 +1770,6 @@ function mk(capability, provider, status, message) {
1727
1770
  message
1728
1771
  };
1729
1772
  }
1730
- function pad(s, width) {
1731
- return s.length >= width ? s : s + " ".repeat(width - s.length);
1732
- }
1733
1773
  //#endregion
1734
1774
  //#region src/commands/new.ts
1735
1775
  /**