@theholocron/cli 5.0.0-alpha.121 → 5.0.0-alpha.123
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 +25 -5
- package/dist/cli.mjs +330 -89
- package/dist/cli.mjs.map +1 -1
- package/package.json +5 -5
package/dist/cli.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir, hostname, tmpdir, userInfo } from "node:os";
|
|
4
5
|
import path, { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
5
6
|
import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
6
7
|
import { KNOWN_WORKFLOWS, createAstromech, deployOnRelease, extractPreviewConfig, readWorkspacePackages, reusableTemplates } from "@theholocron/astromech";
|
|
7
8
|
import { loadTasksConfig } from "@theholocron/astromech/config";
|
|
9
|
+
import { LOG_LEVELS, NoopAnalyticsSink, NoopErrorSink, redactObject } from "@theholocron/observability/core";
|
|
8
10
|
import yargs from "yargs";
|
|
9
11
|
import { hideBin } from "yargs/helpers";
|
|
10
12
|
import { AuthError, ProviderApiError, ProviderApiError as ProviderApiError$1, createRestClient } from "@theholocron/http-client";
|
|
@@ -12,18 +14,16 @@ import { createEnvLookup } from "@theholocron/env-utils";
|
|
|
12
14
|
import { Entry, findCredentials } from "@napi-rs/keyring";
|
|
13
15
|
import { pathToFileURL } from "node:url";
|
|
14
16
|
import { errorMessage } from "@theholocron/misc-utils";
|
|
15
|
-
import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/observability/logger";
|
|
16
|
-
import ora from "ora";
|
|
17
|
+
import { createLogger, isCI, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/observability/logger";
|
|
17
18
|
import chalk from "chalk";
|
|
19
|
+
import ora from "ora";
|
|
18
20
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
19
|
-
import { homedir, hostname, userInfo } from "node:os";
|
|
20
21
|
import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
21
22
|
import { createHash } from "node:crypto";
|
|
22
23
|
import { generateReadme } from "@theholocron/components-doc/markdown";
|
|
23
24
|
import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
|
|
24
25
|
import { createGitHubClient } from "@theholocron/github-client";
|
|
25
26
|
import { PostHogSink } from "@theholocron/observability/analytics";
|
|
26
|
-
import { NoopAnalyticsSink, NoopErrorSink, redactObject } from "@theholocron/observability/core";
|
|
27
27
|
import { SentrySink } from "@theholocron/observability/errors";
|
|
28
28
|
import load from "@commitlint/load";
|
|
29
29
|
import lint from "@commitlint/lint";
|
|
@@ -303,23 +303,54 @@ function resolveConfig(raw) {
|
|
|
303
303
|
};
|
|
304
304
|
}
|
|
305
305
|
//#endregion
|
|
306
|
+
//#region src/ui/style.ts
|
|
307
|
+
const style = {
|
|
308
|
+
success: (msg) => `${chalk.green("✓")} ${msg}`,
|
|
309
|
+
warn: (msg) => `${chalk.yellow("⚠")} ${msg}`,
|
|
310
|
+
fail: (msg) => `${chalk.red("✗")} ${msg}`,
|
|
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}`),
|
|
314
|
+
hint: (msg) => chalk.dim(msg),
|
|
315
|
+
dim: (msg) => chalk.dim(msg),
|
|
316
|
+
header: (msg) => chalk.bold(msg)
|
|
317
|
+
};
|
|
318
|
+
//#endregion
|
|
306
319
|
//#region src/logger.ts
|
|
307
320
|
/**
|
|
308
321
|
* Resolve the explicit level to hand to `createLogger`, in priority order:
|
|
309
322
|
*
|
|
310
|
-
* 1. `--verbose` → `"debug"`
|
|
311
|
-
*
|
|
312
|
-
*
|
|
323
|
+
* 1. `--log <level>` 2. `--verbose` → `"debug"` 3. `--quiet` → `"error"`
|
|
324
|
+
* 4. `HOLOCRON_LOG_LEVEL` env var
|
|
325
|
+
* 5. `holocron.config` `log.level` (`configLevel`)
|
|
313
326
|
*
|
|
314
327
|
* Returns `undefined` when nothing applies — `createLogger` then defaults
|
|
315
328
|
* to `"info"`. Resolving the full chain here (rather than passing
|
|
316
329
|
* `configLevel` straight through) keeps config below the env var.
|
|
317
330
|
*/
|
|
318
331
|
function resolveLogLevel(argv, configLevel) {
|
|
332
|
+
if (argv.log) return argv.log;
|
|
319
333
|
if (argv.verbose) return "debug";
|
|
320
334
|
if (argv.quiet) return "error";
|
|
321
335
|
return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
|
|
322
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Resolve whether human-visible console output (pretty or NDJSON) should run
|
|
339
|
+
* at all, independent of level — the `@theholocron/observability`
|
|
340
|
+
* `consoleOutput` override.
|
|
341
|
+
*
|
|
342
|
+
* Local/interactive runs are quiet by default: any raw structured logger
|
|
343
|
+
* output (colorized or not) stays off regardless of the resolved level,
|
|
344
|
+
* unless the user explicitly asked to see it via `--log <level>`,
|
|
345
|
+
* `--verbose`, or `--quiet`. CI is untouched either way — returning
|
|
346
|
+
* `undefined` there preserves `createLogger`'s own `ci`/`tty`
|
|
347
|
+
* auto-detection exactly, so CI's existing NDJSON-to-stdout behavior is
|
|
348
|
+
* unaffected by this flag.
|
|
349
|
+
*/
|
|
350
|
+
function resolveConsoleOutput(argv) {
|
|
351
|
+
if (argv.log || argv.verbose || argv.quiet) return true;
|
|
352
|
+
return isCI() ? void 0 : false;
|
|
353
|
+
}
|
|
323
354
|
let root;
|
|
324
355
|
let rootLevel;
|
|
325
356
|
let rootCommand;
|
|
@@ -358,13 +389,15 @@ function buildCliLogger(argv, opts = {}) {
|
|
|
358
389
|
const { command, configLevel } = opts;
|
|
359
390
|
if (command) rootCommand = command;
|
|
360
391
|
const level = resolveLogLevel(argv, configLevel);
|
|
392
|
+
const consoleOutput = resolveConsoleOutput(argv);
|
|
361
393
|
const axiom = resolveCliAxiom(opts);
|
|
362
394
|
const axiomKey = axiom?.dataset;
|
|
363
|
-
const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
|
|
395
|
+
const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet && !argv.log;
|
|
364
396
|
if (!root || rebuildForConfig || axiomKey !== void 0 && axiomKey !== rootAxiomKey) {
|
|
365
397
|
const built = createLogger({
|
|
366
398
|
...level ? { level } : {},
|
|
367
|
-
...axiom ? { axiom } : {}
|
|
399
|
+
...axiom ? { axiom } : {},
|
|
400
|
+
...consoleOutput !== void 0 ? { consoleOutput } : {}
|
|
368
401
|
});
|
|
369
402
|
root = {
|
|
370
403
|
logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
|
|
@@ -375,9 +408,35 @@ function buildCliLogger(argv, opts = {}) {
|
|
|
375
408
|
}
|
|
376
409
|
return root;
|
|
377
410
|
}
|
|
378
|
-
/**
|
|
411
|
+
/**
|
|
412
|
+
* Lazily-memoized `Logger` for module-level call sites with no `argv` in
|
|
413
|
+
* scope — notably yargs' own `.fail()` handler, which fires on a
|
|
414
|
+
* pre-middleware validation failure (a missing required argument, an
|
|
415
|
+
* unknown command) and therefore runs before `buildCliLogger` has ever
|
|
416
|
+
* built the real root. Without an explicit `consoleOutput` here, that
|
|
417
|
+
* fallback would use `createLogger`'s bare auto-detection (pretty on any
|
|
418
|
+
* local TTY) and leak a structured block even though no flag asked for one
|
|
419
|
+
* — so it resolves the same "quiet unless CI" default `buildCliLogger`
|
|
420
|
+
* would, as if no flags were passed.
|
|
421
|
+
*/
|
|
379
422
|
function getLogger() {
|
|
380
|
-
return (root ??= createLogger()).logger;
|
|
423
|
+
return (root ??= createLogger({ consoleOutput: resolveConsoleOutput({}) })).logger;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Report a fatal, command-ending CLI error: always printed to the console
|
|
427
|
+
* (regardless of `consoleOutput` suppression) via `style.fail`, and also
|
|
428
|
+
* sent through the structured logger for Axiom/telemetry visibility.
|
|
429
|
+
*
|
|
430
|
+
* Use this when a handler is returning or exiting right here with nothing
|
|
431
|
+
* else for the user to see — a resolved `--token` failure, a yargs
|
|
432
|
+
* validation failure, a thrown domain error. Don't use it for a per-step
|
|
433
|
+
* warning inside a multi-step orchestrator that already prints its own
|
|
434
|
+
* `ok / fail / skip` summary — that summary is the user-facing signal;
|
|
435
|
+
* the per-step reason belongs in the structured log alone.
|
|
436
|
+
*/
|
|
437
|
+
function reportError(message, log = getLogger()) {
|
|
438
|
+
console.error(style.fail(message));
|
|
439
|
+
log.error(message);
|
|
381
440
|
}
|
|
382
441
|
/** The current root logger's correlation id, if a root has been built. */
|
|
383
442
|
function getRunId() {
|
|
@@ -637,17 +696,6 @@ async function withSpinner(label, fn) {
|
|
|
637
696
|
}
|
|
638
697
|
}
|
|
639
698
|
//#endregion
|
|
640
|
-
//#region src/ui/style.ts
|
|
641
|
-
const style = {
|
|
642
|
-
success: (msg) => `${chalk.green("✓")} ${msg}`,
|
|
643
|
-
warn: (msg) => `${chalk.yellow("⚠")} ${msg}`,
|
|
644
|
-
fail: (msg) => `${chalk.red("✗")} ${msg}`,
|
|
645
|
-
step: (msg) => `${chalk.cyan("→")} ${msg}`,
|
|
646
|
-
hint: (msg) => chalk.dim(msg),
|
|
647
|
-
dim: (msg) => chalk.dim(msg),
|
|
648
|
-
header: (msg) => chalk.bold(msg)
|
|
649
|
-
};
|
|
650
|
-
//#endregion
|
|
651
699
|
//#region src/commands/auth.ts
|
|
652
700
|
/**
|
|
653
701
|
* `holocron auth <subcommand>` — manage bootstrap credentials in the
|
|
@@ -1071,6 +1119,27 @@ async function runCleanupPreview(input) {
|
|
|
1071
1119
|
}
|
|
1072
1120
|
}
|
|
1073
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
|
|
1074
1143
|
//#region src/commands/clone.ts
|
|
1075
1144
|
/**
|
|
1076
1145
|
* Accepts a bare org (`"theholocron"`) — clones every repo in it — or an
|
|
@@ -1132,7 +1201,6 @@ async function runClone(input) {
|
|
|
1132
1201
|
const fetchFn = input.fetch ?? globalThis.fetch;
|
|
1133
1202
|
const dryRun = input.dryRun ?? false;
|
|
1134
1203
|
const targetDir = resolve(input.dir ?? join(homedir(), "Code", input.org));
|
|
1135
|
-
const label = input.repo ? `${input.org}/${input.repo}` : `org=${input.org}`;
|
|
1136
1204
|
logger.info({
|
|
1137
1205
|
org: input.org,
|
|
1138
1206
|
repo: input.repo,
|
|
@@ -1145,7 +1213,7 @@ async function runClone(input) {
|
|
|
1145
1213
|
stdio: "inherit"
|
|
1146
1214
|
}).status };
|
|
1147
1215
|
});
|
|
1148
|
-
print(
|
|
1216
|
+
print("");
|
|
1149
1217
|
if (!existsSync(targetDir)) {
|
|
1150
1218
|
if (dryRun) print(style.dim(` would create ${targetDir}`));
|
|
1151
1219
|
else mkdirSync(targetDir, { recursive: true });
|
|
@@ -1196,7 +1264,11 @@ async function runClone(input) {
|
|
|
1196
1264
|
for (const repo of repos) {
|
|
1197
1265
|
const dest = join(targetDir, repoDirName(repo));
|
|
1198
1266
|
if (existsSync(dest)) {
|
|
1199
|
-
print(
|
|
1267
|
+
print(statusRow("skip", [
|
|
1268
|
+
repo.full_name,
|
|
1269
|
+
dest,
|
|
1270
|
+
"already exists"
|
|
1271
|
+
]));
|
|
1200
1272
|
skipped++;
|
|
1201
1273
|
continue;
|
|
1202
1274
|
}
|
|
@@ -1205,10 +1277,14 @@ async function runClone(input) {
|
|
|
1205
1277
|
cloned++;
|
|
1206
1278
|
continue;
|
|
1207
1279
|
}
|
|
1208
|
-
print(style.step(` clone ${repo.full_name}`));
|
|
1280
|
+
print(style.step(` clone ${repo.full_name} → ${dest}`));
|
|
1209
1281
|
const { clone_url } = repo;
|
|
1210
1282
|
if (!clone_url.startsWith("https://github.com/")) {
|
|
1211
|
-
print(
|
|
1283
|
+
print(statusRow("fail", [
|
|
1284
|
+
repo.full_name,
|
|
1285
|
+
dest,
|
|
1286
|
+
`unexpected clone URL: ${clone_url}`
|
|
1287
|
+
]));
|
|
1212
1288
|
failed++;
|
|
1213
1289
|
continue;
|
|
1214
1290
|
}
|
|
@@ -1216,7 +1292,11 @@ async function runClone(input) {
|
|
|
1216
1292
|
try {
|
|
1217
1293
|
encodedToken = encodeTokenForGitHttpAuth(input.token);
|
|
1218
1294
|
} catch (err) {
|
|
1219
|
-
print(
|
|
1295
|
+
print(statusRow("fail", [
|
|
1296
|
+
repo.full_name,
|
|
1297
|
+
dest,
|
|
1298
|
+
`invalid token format: ${errorMessage(err)}`
|
|
1299
|
+
]));
|
|
1220
1300
|
failed++;
|
|
1221
1301
|
continue;
|
|
1222
1302
|
}
|
|
@@ -1226,15 +1306,16 @@ async function runClone(input) {
|
|
|
1226
1306
|
`https://x-access-token:${encodedToken}@github.com/${clone_url.slice(19)}`,
|
|
1227
1307
|
dest
|
|
1228
1308
|
], { cwd: targetDir }).status !== 0) {
|
|
1229
|
-
print(
|
|
1309
|
+
print(statusRow("fail", [repo.full_name, dest]));
|
|
1230
1310
|
failed++;
|
|
1231
1311
|
} else {
|
|
1232
|
-
print(
|
|
1312
|
+
print(statusRow("ok", [repo.full_name, dest]));
|
|
1233
1313
|
cloned++;
|
|
1234
1314
|
}
|
|
1235
1315
|
}
|
|
1236
|
-
const summary = `${cloned} cloned, ${
|
|
1237
|
-
print(
|
|
1316
|
+
const summary = `${cloned} cloned, ${failed} failed, ${skipped} skipped`;
|
|
1317
|
+
print("");
|
|
1318
|
+
print(dryRun ? style.dim(` dry-run: ${summary}`) : failed > 0 ? style.fail(` ${summary}`) : style.success(` ${summary}`));
|
|
1238
1319
|
const status = dryRun ? "dry-run" : failed > 0 ? "fail" : "ok";
|
|
1239
1320
|
logger[failed > 0 ? "warn" : "info"]({
|
|
1240
1321
|
org: input.org,
|
|
@@ -1244,6 +1325,10 @@ async function runClone(input) {
|
|
|
1244
1325
|
skipped,
|
|
1245
1326
|
failed
|
|
1246
1327
|
}, "clone: done");
|
|
1328
|
+
if (input.confirmOpen) {
|
|
1329
|
+
const openTarget = input.repo ? join(targetDir, repoDirName(repos[0])) : targetDir;
|
|
1330
|
+
if (existsSync(openTarget)) await input.confirmOpen(openTarget, dryRun && cloned > 0);
|
|
1331
|
+
}
|
|
1247
1332
|
return {
|
|
1248
1333
|
status,
|
|
1249
1334
|
cloned,
|
|
@@ -1574,7 +1659,7 @@ async function runDoctor(input) {
|
|
|
1574
1659
|
assertPluginsResolvable(loader, "doctor");
|
|
1575
1660
|
const rows = [];
|
|
1576
1661
|
const config = input.loaded.resolved;
|
|
1577
|
-
print(
|
|
1662
|
+
print("");
|
|
1578
1663
|
print(style.dim(` config: ${input.loaded.filepath}`));
|
|
1579
1664
|
print("");
|
|
1580
1665
|
for (const failure of loader.loadFailures()) rows.push({
|
|
@@ -1609,10 +1694,7 @@ async function runDoctor(input) {
|
|
|
1609
1694
|
status: row.status,
|
|
1610
1695
|
detail: row.message
|
|
1611
1696
|
}, `doctor: ${row.capability}`);
|
|
1612
|
-
|
|
1613
|
-
if (row.status === "ok") print(` ${style.success(label)}`);
|
|
1614
|
-
else if (row.status === "fail") print(` ${style.fail(label)}`);
|
|
1615
|
-
else print(` ${style.dim(`· ${label}`)}`);
|
|
1697
|
+
print(statusRow(row.status, [`${pad(row.capability, 14)} via ${pad(row.provider, 14)}`, row.message]));
|
|
1616
1698
|
}
|
|
1617
1699
|
const summary = rows.reduce((acc, r) => {
|
|
1618
1700
|
acc[r.status] += 1;
|
|
@@ -1624,7 +1706,7 @@ async function runDoctor(input) {
|
|
|
1624
1706
|
});
|
|
1625
1707
|
const summaryLine = `${summary.ok} ok, ${summary.fail} fail, ${summary.skip} skipped`;
|
|
1626
1708
|
print("");
|
|
1627
|
-
print(
|
|
1709
|
+
print(style.header(summaryLine));
|
|
1628
1710
|
return {
|
|
1629
1711
|
rows,
|
|
1630
1712
|
summary
|
|
@@ -1678,9 +1760,6 @@ function mk(capability, provider, status, message) {
|
|
|
1678
1760
|
message
|
|
1679
1761
|
};
|
|
1680
1762
|
}
|
|
1681
|
-
function pad(s, width) {
|
|
1682
|
-
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
1683
|
-
}
|
|
1684
1763
|
//#endregion
|
|
1685
1764
|
//#region src/commands/new.ts
|
|
1686
1765
|
/**
|
|
@@ -8043,6 +8122,15 @@ function parseGitRemoteUrl(url) {
|
|
|
8043
8122
|
* {@link promptForPositionals} for just that one command — no spawn needed,
|
|
8044
8123
|
* the handler already has everything else it needs.
|
|
8045
8124
|
*
|
|
8125
|
+
* For a command whose handler honours `--dry-run` ({@link
|
|
8126
|
+
* CommandEntry.supportsDryRun}), `launchMenu` always previews it first — a
|
|
8127
|
+
* dry run doesn't mutate anything, so there's nothing to ask permission
|
|
8128
|
+
* for — then, once that preview comes back clean and found something to
|
|
8129
|
+
* do, asks the one real decision: "proceed with the real run?". A preview
|
|
8130
|
+
* that finds nothing to do skips that question too. `--skip-dry-run` opts
|
|
8131
|
+
* a top-level invocation out of the preview entirely, straight to the
|
|
8132
|
+
* real run, for when you already know what you want.
|
|
8133
|
+
*
|
|
8046
8134
|
* Every prompt is preceded by a `process.stdin.isTTY` check. A non-TTY run
|
|
8047
8135
|
* (CI, a script, a pipe) throws {@link NonInteractiveError} instead of
|
|
8048
8136
|
* hanging on `@inquirer/prompts` (which otherwise throws its own
|
|
@@ -8072,17 +8160,20 @@ const COMMAND_REGISTRY = [
|
|
|
8072
8160
|
message: "org/repo to clone (or a bare org to clone every repo in it):",
|
|
8073
8161
|
type: "input",
|
|
8074
8162
|
validate: (v) => v.trim().length > 0 || "org/repo or org is required"
|
|
8075
|
-
}]
|
|
8163
|
+
}],
|
|
8164
|
+
supportsDryRun: true
|
|
8076
8165
|
},
|
|
8077
8166
|
{
|
|
8078
8167
|
name: "doctor",
|
|
8079
8168
|
description: "Load the config and run a smoke check against every provider",
|
|
8080
|
-
positionals: []
|
|
8169
|
+
positionals: [],
|
|
8170
|
+
supportsDryRun: true
|
|
8081
8171
|
},
|
|
8082
8172
|
{
|
|
8083
8173
|
name: "setup",
|
|
8084
8174
|
description: "Apply infra setup actions across every configured capability",
|
|
8085
|
-
positionals: []
|
|
8175
|
+
positionals: [],
|
|
8176
|
+
supportsDryRun: true
|
|
8086
8177
|
},
|
|
8087
8178
|
{
|
|
8088
8179
|
name: "secret set",
|
|
@@ -8091,7 +8182,8 @@ const COMMAND_REGISTRY = [
|
|
|
8091
8182
|
key: "name",
|
|
8092
8183
|
message: "Secret name:",
|
|
8093
8184
|
type: "input"
|
|
8094
|
-
}]
|
|
8185
|
+
}],
|
|
8186
|
+
supportsDryRun: true
|
|
8095
8187
|
},
|
|
8096
8188
|
{
|
|
8097
8189
|
name: "secrets sync",
|
|
@@ -8100,7 +8192,8 @@ const COMMAND_REGISTRY = [
|
|
|
8100
8192
|
key: "environmentId",
|
|
8101
8193
|
message: "Vault environment id:",
|
|
8102
8194
|
type: "input"
|
|
8103
|
-
}]
|
|
8195
|
+
}],
|
|
8196
|
+
supportsDryRun: true
|
|
8104
8197
|
},
|
|
8105
8198
|
{
|
|
8106
8199
|
name: "deploy",
|
|
@@ -8109,7 +8202,8 @@ const COMMAND_REGISTRY = [
|
|
|
8109
8202
|
key: "branch",
|
|
8110
8203
|
message: "Branch to deploy:",
|
|
8111
8204
|
type: "input"
|
|
8112
|
-
}]
|
|
8205
|
+
}],
|
|
8206
|
+
supportsDryRun: true
|
|
8113
8207
|
},
|
|
8114
8208
|
{
|
|
8115
8209
|
name: "cleanup-preview",
|
|
@@ -8119,7 +8213,8 @@ const COMMAND_REGISTRY = [
|
|
|
8119
8213
|
message: "PR number:",
|
|
8120
8214
|
type: "input",
|
|
8121
8215
|
validate: numeric
|
|
8122
|
-
}]
|
|
8216
|
+
}],
|
|
8217
|
+
supportsDryRun: true
|
|
8123
8218
|
},
|
|
8124
8219
|
{
|
|
8125
8220
|
name: "bump-versions",
|
|
@@ -8129,32 +8224,38 @@ const COMMAND_REGISTRY = [
|
|
|
8129
8224
|
cliArg: "new-version",
|
|
8130
8225
|
message: "New version:",
|
|
8131
8226
|
type: "input"
|
|
8132
|
-
}]
|
|
8227
|
+
}],
|
|
8228
|
+
supportsDryRun: true
|
|
8133
8229
|
},
|
|
8134
8230
|
{
|
|
8135
8231
|
name: "publish",
|
|
8136
8232
|
description: "Publish @theholocron/* packages to npm",
|
|
8137
|
-
positionals: []
|
|
8233
|
+
positionals: [],
|
|
8234
|
+
supportsDryRun: true
|
|
8138
8235
|
},
|
|
8139
8236
|
{
|
|
8140
8237
|
name: "sync",
|
|
8141
8238
|
description: "Sync state from config to the provider and local files (labels, properties, teams, topics, keywords, description, homepage, readme, workflows, scripts, wiki)",
|
|
8142
|
-
positionals: []
|
|
8239
|
+
positionals: [],
|
|
8240
|
+
supportsDryRun: true
|
|
8143
8241
|
},
|
|
8144
8242
|
{
|
|
8145
8243
|
name: "ci",
|
|
8146
8244
|
description: "Run the merge-gating checks locally, in CI order — 'will CI pass?'",
|
|
8147
|
-
positionals: []
|
|
8245
|
+
positionals: [],
|
|
8246
|
+
supportsDryRun: true
|
|
8148
8247
|
},
|
|
8149
8248
|
{
|
|
8150
8249
|
name: "sync-github",
|
|
8151
8250
|
description: "Sync workflow templates and composite actions to theholocron/.github",
|
|
8152
|
-
positionals: []
|
|
8251
|
+
positionals: [],
|
|
8252
|
+
supportsDryRun: true
|
|
8153
8253
|
},
|
|
8154
8254
|
{
|
|
8155
8255
|
name: "sync-readme",
|
|
8156
8256
|
description: "Sync the Installation + Usage block in README.md from package.json",
|
|
8157
|
-
positionals: []
|
|
8257
|
+
positionals: [],
|
|
8258
|
+
supportsDryRun: true
|
|
8158
8259
|
},
|
|
8159
8260
|
{
|
|
8160
8261
|
name: "config show",
|
|
@@ -8164,7 +8265,8 @@ const COMMAND_REGISTRY = [
|
|
|
8164
8265
|
{
|
|
8165
8266
|
name: "new",
|
|
8166
8267
|
description: "Scaffold a new repo from a GitHub template (e.g. cli, react, nextjs, node, monorepo, base)",
|
|
8167
|
-
positionals: []
|
|
8268
|
+
positionals: [],
|
|
8269
|
+
supportsDryRun: true
|
|
8168
8270
|
},
|
|
8169
8271
|
{
|
|
8170
8272
|
name: "plugin create",
|
|
@@ -8177,25 +8279,29 @@ const COMMAND_REGISTRY = [
|
|
|
8177
8279
|
key: "vendor",
|
|
8178
8280
|
message: "Vendor display name (PascalCase):",
|
|
8179
8281
|
type: "input"
|
|
8180
|
-
}]
|
|
8282
|
+
}],
|
|
8283
|
+
supportsDryRun: true
|
|
8181
8284
|
},
|
|
8182
8285
|
{
|
|
8183
8286
|
name: "skills install",
|
|
8184
8287
|
description: "Copy skills from @theholocron/skills into .agents/ with agent symlinks",
|
|
8185
8288
|
positionals: [],
|
|
8186
|
-
group: "skills"
|
|
8289
|
+
group: "skills",
|
|
8290
|
+
supportsDryRun: true
|
|
8187
8291
|
},
|
|
8188
8292
|
{
|
|
8189
8293
|
name: "skills remove",
|
|
8190
8294
|
description: "Remove installed skills via npx skills remove",
|
|
8191
8295
|
positionals: [],
|
|
8192
|
-
group: "skills"
|
|
8296
|
+
group: "skills",
|
|
8297
|
+
supportsDryRun: true
|
|
8193
8298
|
},
|
|
8194
8299
|
{
|
|
8195
8300
|
name: "skills update",
|
|
8196
8301
|
description: "Update installed skills to their latest upstream versions via npx skills update",
|
|
8197
8302
|
positionals: [],
|
|
8198
|
-
group: "skills"
|
|
8303
|
+
group: "skills",
|
|
8304
|
+
supportsDryRun: true
|
|
8199
8305
|
},
|
|
8200
8306
|
{
|
|
8201
8307
|
name: "upgrade node",
|
|
@@ -8206,13 +8312,15 @@ const COMMAND_REGISTRY = [
|
|
|
8206
8312
|
type: "input",
|
|
8207
8313
|
validate: numeric
|
|
8208
8314
|
}],
|
|
8209
|
-
group: "upgrade"
|
|
8315
|
+
group: "upgrade",
|
|
8316
|
+
supportsDryRun: true
|
|
8210
8317
|
},
|
|
8211
8318
|
{
|
|
8212
8319
|
name: "upgrade deps",
|
|
8213
8320
|
description: "Bump every @theholocron/* pin to latest and migrate holocron.config.ts to the current preset API",
|
|
8214
8321
|
positionals: [],
|
|
8215
|
-
group: "upgrade"
|
|
8322
|
+
group: "upgrade",
|
|
8323
|
+
supportsDryRun: true
|
|
8216
8324
|
},
|
|
8217
8325
|
{
|
|
8218
8326
|
name: "auth set",
|
|
@@ -8327,25 +8435,41 @@ async function promptForPositionals(entry, argv) {
|
|
|
8327
8435
|
* menu-launched child should inherit from the parent invocation. Everything
|
|
8328
8436
|
* else (command-specific options) was never captured by the top-level `$0`
|
|
8329
8437
|
* handler in the first place, so there's nothing else to forward.
|
|
8438
|
+
*
|
|
8439
|
+
* `dryRunOverride`, when passed, wins over `argv.dryRun` — `launchMenu`'s
|
|
8440
|
+
* dry-run offer needs to force `--dry-run` on for the preview spawn and
|
|
8441
|
+
* force it back off for the real-run spawn that follows, regardless of
|
|
8442
|
+
* what was on the original invocation.
|
|
8330
8443
|
*/
|
|
8331
|
-
function forwardedFlags(argv) {
|
|
8444
|
+
function forwardedFlags(argv, dryRunOverride) {
|
|
8332
8445
|
const out = [];
|
|
8333
8446
|
const tokens = argv.token;
|
|
8334
8447
|
for (const t of tokens ?? []) out.push("--token", t);
|
|
8335
8448
|
if (typeof argv.org === "string") out.push("--org", argv.org);
|
|
8336
8449
|
if (typeof argv.cwd === "string") out.push("--cwd", argv.cwd);
|
|
8337
|
-
if (argv.dryRun === true) out.push("--dry-run");
|
|
8450
|
+
if (dryRunOverride ?? argv.dryRun === true) out.push("--dry-run");
|
|
8338
8451
|
return out;
|
|
8339
8452
|
}
|
|
8340
8453
|
/** `entry.name` split into tokens, resolved positionals appended, then forwarded flags. */
|
|
8341
|
-
function buildChildArgv(entry, positionals, parentArgv) {
|
|
8454
|
+
function buildChildArgv(entry, positionals, parentArgv, dryRunOverride) {
|
|
8342
8455
|
return [
|
|
8343
8456
|
...entry.name.split(" "),
|
|
8344
8457
|
...positionals,
|
|
8345
|
-
...forwardedFlags(parentArgv)
|
|
8458
|
+
...forwardedFlags(parentArgv, dryRunOverride)
|
|
8346
8459
|
];
|
|
8347
8460
|
}
|
|
8348
8461
|
/**
|
|
8462
|
+
* Set on a dry-run preview child's env ({@link spawnChild}'s `extraEnv`) so
|
|
8463
|
+
* its handler knows this specific `--dry-run` came from `launchMenu`'s own
|
|
8464
|
+
* preview-then-ask flow, not a direct `holocron <cmd> --dry-run` invocation.
|
|
8465
|
+
* A handler that supports it uses this — never bare `argv.dryRun` — to
|
|
8466
|
+
* decide whether to report {@link DRY_RUN_NOOP_EXIT_CODE}: that exit code
|
|
8467
|
+
* only has special meaning to `launchMenu` itself, so a direct/scripted
|
|
8468
|
+
* `--dry-run` call must never produce it — doing so unconditionally would
|
|
8469
|
+
* silently change the public exit-code contract for a successful dry run.
|
|
8470
|
+
*/
|
|
8471
|
+
const DRY_RUN_PREVIEW_ENV_VAR = "HOLOCRON_DRY_RUN_PREVIEW";
|
|
8472
|
+
/**
|
|
8349
8473
|
* Spawn `holocron <...args>` inheriting stdio; resolve with its exit code.
|
|
8350
8474
|
*
|
|
8351
8475
|
* `NO_UPDATE_NOTIFIER` is forced on in the child's env — the parent's own
|
|
@@ -8353,15 +8477,17 @@ function buildChildArgv(entry, positionals, parentArgv) {
|
|
|
8353
8477
|
* docstring), so without this the freshly-spawned child independently runs
|
|
8354
8478
|
* its own `checkForUpdates()` too and the notice prints twice: once from
|
|
8355
8479
|
* the child right after its command output, once more from the parent
|
|
8356
|
-
* once the child exits.
|
|
8480
|
+
* once the child exits. `extraEnv` layers on top — used only for {@link
|
|
8481
|
+
* DRY_RUN_PREVIEW_ENV_VAR} on the dry-run preview spawn.
|
|
8357
8482
|
*/
|
|
8358
|
-
function spawnChild(args) {
|
|
8483
|
+
function spawnChild(args, extraEnv) {
|
|
8359
8484
|
return new Promise((resolve) => {
|
|
8360
8485
|
const child = spawn(process.execPath, [process.argv[1], ...args], {
|
|
8361
8486
|
stdio: "inherit",
|
|
8362
8487
|
env: {
|
|
8363
8488
|
...process.env,
|
|
8364
|
-
NO_UPDATE_NOTIFIER: "1"
|
|
8489
|
+
NO_UPDATE_NOTIFIER: "1",
|
|
8490
|
+
...extraEnv
|
|
8365
8491
|
}
|
|
8366
8492
|
});
|
|
8367
8493
|
child.on("exit", (code) => resolve(code ?? 0));
|
|
@@ -8369,6 +8495,17 @@ function spawnChild(args) {
|
|
|
8369
8495
|
});
|
|
8370
8496
|
}
|
|
8371
8497
|
/**
|
|
8498
|
+
* Whether `launchMenu` should auto-preview a picked command before running
|
|
8499
|
+
* it for real — only when the command's handler actually honours
|
|
8500
|
+
* `--dry-run` ({@link CommandEntry.supportsDryRun}) and the top-level
|
|
8501
|
+
* invocation didn't pass `--skip-dry-run` to opt out of the preview
|
|
8502
|
+
* entirely (an escape hatch for "I already know what I want, skip
|
|
8503
|
+
* straight to the real run").
|
|
8504
|
+
*/
|
|
8505
|
+
function shouldPreviewDryRun(entry, parentArgv) {
|
|
8506
|
+
return Boolean(entry.supportsDryRun) && parentArgv.skipDryRun !== true;
|
|
8507
|
+
}
|
|
8508
|
+
/**
|
|
8372
8509
|
* Layers 1 and 2: pick a command from `entries`, prompt for its required
|
|
8373
8510
|
* positionals, spawn it as a fresh `holocron` invocation, and set
|
|
8374
8511
|
* `process.exitCode` from the child — the caller (a `$0` handler in
|
|
@@ -8377,12 +8514,79 @@ function spawnChild(args) {
|
|
|
8377
8514
|
* `command_completed` event fires with command name "unknown" (the
|
|
8378
8515
|
* middleware ran before any command was picked) — left as-is rather than
|
|
8379
8516
|
* suppressed; it's a real, useful signal ("the menu got used").
|
|
8517
|
+
*
|
|
8518
|
+
* For a command that honours `--dry-run` ({@link CommandEntry.supportsDryRun})
|
|
8519
|
+
* and wasn't opted out via `--skip-dry-run` ({@link shouldPreviewDryRun}),
|
|
8520
|
+
* this always previews first — a dry run doesn't mutate anything, so
|
|
8521
|
+
* there's nothing to ask permission for. A preview that exits non-zero
|
|
8522
|
+
* stops here; one that reports {@link DRY_RUN_NOOP_EXIT_CODE} (nothing
|
|
8523
|
+
* would change) stops here too, skipping the follow-up question since the
|
|
8524
|
+
* real run would have nothing to do either. Otherwise it asks "proceed
|
|
8525
|
+
* with the real run?" before the real spawn — the one actual decision
|
|
8526
|
+
* left to make, since this one does mutate something.
|
|
8380
8527
|
*/
|
|
8381
8528
|
async function launchMenu(entries, parentArgv, pickMessage, nonInteractiveMessage = "Run `holocron --help` to see available commands.") {
|
|
8382
8529
|
if (!process.stdin.isTTY) throw new NonInteractiveError(nonInteractiveMessage);
|
|
8383
8530
|
const picked = await pickCommand(entries, pickMessage);
|
|
8384
|
-
const
|
|
8385
|
-
|
|
8531
|
+
const positionals = await promptForPositionals(picked, {});
|
|
8532
|
+
if (shouldPreviewDryRun(picked, parentArgv)) {
|
|
8533
|
+
const dryExit = await spawnChild(buildChildArgv(picked, positionals, parentArgv, true), { [DRY_RUN_PREVIEW_ENV_VAR]: "1" });
|
|
8534
|
+
if (dryExit === 2) {
|
|
8535
|
+
process.exitCode = 0;
|
|
8536
|
+
return;
|
|
8537
|
+
}
|
|
8538
|
+
if (dryExit !== 0) {
|
|
8539
|
+
process.exitCode = dryExit;
|
|
8540
|
+
return;
|
|
8541
|
+
}
|
|
8542
|
+
if (!await confirm({
|
|
8543
|
+
message: "Dry run complete — proceed with the real run?",
|
|
8544
|
+
default: false
|
|
8545
|
+
})) {
|
|
8546
|
+
process.exitCode = 0;
|
|
8547
|
+
return;
|
|
8548
|
+
}
|
|
8549
|
+
process.exitCode = await spawnChild(buildChildArgv(picked, positionals, parentArgv, false));
|
|
8550
|
+
return;
|
|
8551
|
+
}
|
|
8552
|
+
process.exitCode = await spawnChild(buildChildArgv(picked, positionals, parentArgv));
|
|
8553
|
+
}
|
|
8554
|
+
//#endregion
|
|
8555
|
+
//#region src/ui/open-path.ts
|
|
8556
|
+
/** Platform-appropriate command to open a path in the OS's file browser. */
|
|
8557
|
+
function openCommand(path) {
|
|
8558
|
+
if (process.platform === "darwin") return {
|
|
8559
|
+
cmd: "open",
|
|
8560
|
+
args: [path]
|
|
8561
|
+
};
|
|
8562
|
+
if (process.platform === "win32") return {
|
|
8563
|
+
cmd: "cmd",
|
|
8564
|
+
args: [
|
|
8565
|
+
"/c",
|
|
8566
|
+
"start",
|
|
8567
|
+
"",
|
|
8568
|
+
path
|
|
8569
|
+
]
|
|
8570
|
+
};
|
|
8571
|
+
return {
|
|
8572
|
+
cmd: "xdg-open",
|
|
8573
|
+
args: [path]
|
|
8574
|
+
};
|
|
8575
|
+
}
|
|
8576
|
+
/**
|
|
8577
|
+
* Open `path` in the OS's default file browser (Finder / Explorer /
|
|
8578
|
+
* whatever `xdg-open` resolves to on Linux). Fire-and-forget — detached so
|
|
8579
|
+
* it outlives this process, and never throws when the opener binary isn't
|
|
8580
|
+
* on PATH (e.g. no `xdg-open` in a minimal container).
|
|
8581
|
+
*/
|
|
8582
|
+
function openPath(path) {
|
|
8583
|
+
const { cmd, args } = openCommand(path);
|
|
8584
|
+
const child = spawn(cmd, args, {
|
|
8585
|
+
stdio: "ignore",
|
|
8586
|
+
detached: true
|
|
8587
|
+
});
|
|
8588
|
+
child.on("error", () => {});
|
|
8589
|
+
child.unref();
|
|
8386
8590
|
}
|
|
8387
8591
|
//#endregion
|
|
8388
8592
|
//#region src/update-notifier.ts
|
|
@@ -8528,6 +8732,19 @@ function resolveOrg(argv, config) {
|
|
|
8528
8732
|
return argv.org ?? env.get("HOLOCRON_ORG") ?? config.org;
|
|
8529
8733
|
}
|
|
8530
8734
|
/**
|
|
8735
|
+
* Print the dim version line once per terminal session — keyed by the
|
|
8736
|
+
* parent shell's PID, since that's stable across repeated bare `holocron`
|
|
8737
|
+
* invocations in the same shell but changes in a new tab/window. A marker
|
|
8738
|
+
* file in the OS tmpdir tracks whether this shell has already seen it, so
|
|
8739
|
+
* re-running `holocron` doesn't repeat the line every time.
|
|
8740
|
+
*/
|
|
8741
|
+
function printVersionHeaderOnce() {
|
|
8742
|
+
const marker = join(tmpdir(), `.holocron-version-shown-${process.ppid}`);
|
|
8743
|
+
if (existsSync(marker)) return;
|
|
8744
|
+
console.log(style.dim(`v${CLI_VERSION}`));
|
|
8745
|
+
writeFileSync(marker, "");
|
|
8746
|
+
}
|
|
8747
|
+
/**
|
|
8531
8748
|
* `buildCliLogger` options derived from a loaded `holocron.config` — the
|
|
8532
8749
|
* level, the Axiom dataset, and the resolved org for a keyring-backed
|
|
8533
8750
|
* Axiom token. Used by every handler that has already called `loadConfig`.
|
|
@@ -8557,7 +8774,7 @@ function tokenContext(rawTokens) {
|
|
|
8557
8774
|
return parseTokenArgs(rawTokens);
|
|
8558
8775
|
} catch (err) {
|
|
8559
8776
|
if (err instanceof TokenParseError) {
|
|
8560
|
-
|
|
8777
|
+
reportError(`--token: ${err.message}`);
|
|
8561
8778
|
process.exitCode = 1;
|
|
8562
8779
|
return null;
|
|
8563
8780
|
}
|
|
@@ -8574,6 +8791,10 @@ try {
|
|
|
8574
8791
|
type: "boolean",
|
|
8575
8792
|
default: false,
|
|
8576
8793
|
describe: "Print what would be mutated without calling capability mutators. Commands branch on this; read-only commands ignore it."
|
|
8794
|
+
}).option("skip-dry-run", {
|
|
8795
|
+
type: "boolean",
|
|
8796
|
+
default: false,
|
|
8797
|
+
describe: "Interactive mode only: skip the automatic dry-run preview for a dry-run-capable command and run for real immediately."
|
|
8577
8798
|
}).option("token", {
|
|
8578
8799
|
type: "string",
|
|
8579
8800
|
array: true,
|
|
@@ -8597,6 +8818,10 @@ try {
|
|
|
8597
8818
|
type: "boolean",
|
|
8598
8819
|
default: false,
|
|
8599
8820
|
describe: "Set the log level to error — suppress info and warn."
|
|
8821
|
+
}).option("log", {
|
|
8822
|
+
type: "string",
|
|
8823
|
+
choices: LOG_LEVELS,
|
|
8824
|
+
describe: "Show structured logger output at this level. Local/interactive runs are quiet by default (no console output regardless of level) — this opts back in."
|
|
8600
8825
|
}).middleware((argv) => {
|
|
8601
8826
|
const name = argv._.slice(0, 2).join(" ") || "unknown";
|
|
8602
8827
|
printRunId = Boolean(argv.debug || argv.verbose);
|
|
@@ -8618,13 +8843,13 @@ try {
|
|
|
8618
8843
|
try {
|
|
8619
8844
|
token = resolveCloneToken({ cliToken: tokens.cliTokens?.["github"] ?? tokens.cliToken });
|
|
8620
8845
|
} catch (err) {
|
|
8621
|
-
|
|
8846
|
+
reportError(`clone: ${err instanceof AuthError ? err.message : String(err)}`);
|
|
8622
8847
|
process.exitCode = 1;
|
|
8623
8848
|
return;
|
|
8624
8849
|
}
|
|
8625
8850
|
const [targetInput] = await promptForPositionals(getEntry("clone"), argv);
|
|
8626
8851
|
const { org, repo } = parseCloneTarget(targetInput);
|
|
8627
|
-
|
|
8852
|
+
const report = await runClone({
|
|
8628
8853
|
org,
|
|
8629
8854
|
...repo ? { repo } : {},
|
|
8630
8855
|
token,
|
|
@@ -8637,8 +8862,20 @@ try {
|
|
|
8637
8862
|
message: `${org} has ${total} repos — ${alreadyCloned} already cloned locally, ${toClone} new. Clone them all?`,
|
|
8638
8863
|
default: false
|
|
8639
8864
|
});
|
|
8640
|
-
} } : {}
|
|
8641
|
-
|
|
8865
|
+
} } : {},
|
|
8866
|
+
confirmOpen: async (openTarget, moreToCome) => {
|
|
8867
|
+
if (!process.stdin.isTTY) return;
|
|
8868
|
+
if (moreToCome && env.get("HOLOCRON_DRY_RUN_PREVIEW") === "1") return;
|
|
8869
|
+
if (await confirm({
|
|
8870
|
+
message: `Open ${openTarget}?`,
|
|
8871
|
+
default: false
|
|
8872
|
+
})) openPath(openTarget);
|
|
8873
|
+
}
|
|
8874
|
+
});
|
|
8875
|
+
if (report.status === "fail") {
|
|
8876
|
+
if (report.message) reportError(`clone: ${report.message}`);
|
|
8877
|
+
process.exitCode = 1;
|
|
8878
|
+
} else if (report.status === "dry-run" && report.cloned === 0 && env.get("HOLOCRON_DRY_RUN_PREVIEW") === "1") process.exitCode = 2;
|
|
8642
8879
|
}).command("doctor", "Load the config and run a smoke check against every provider", (y) => y.option("repo", {
|
|
8643
8880
|
type: "string",
|
|
8644
8881
|
describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
|
|
@@ -8898,7 +9135,7 @@ try {
|
|
|
8898
9135
|
if (argv.syncTrust) {
|
|
8899
9136
|
const [oldFile, newFile] = argv.syncTrust;
|
|
8900
9137
|
if (!oldFile || !newFile) {
|
|
8901
|
-
|
|
9138
|
+
reportError("publish --sync-trust requires exactly two values: <old-file> <new-file>");
|
|
8902
9139
|
process.exitCode = 1;
|
|
8903
9140
|
return;
|
|
8904
9141
|
}
|
|
@@ -9058,7 +9295,7 @@ try {
|
|
|
9058
9295
|
else try {
|
|
9059
9296
|
token = resolveSyncToken({ cliToken: parsed.cliTokens?.["github"] ?? parsed.cliToken });
|
|
9060
9297
|
} catch (err) {
|
|
9061
|
-
|
|
9298
|
+
reportError(`sync-github: ${err instanceof AuthError ? err.message : String(err)}`);
|
|
9062
9299
|
process.exitCode = 1;
|
|
9063
9300
|
return;
|
|
9064
9301
|
}
|
|
@@ -9078,13 +9315,17 @@ try {
|
|
|
9078
9315
|
}), async (argv) => {
|
|
9079
9316
|
const loaded = await loadConfig(argv.cwd);
|
|
9080
9317
|
applyResolvedConfig(argv, loaded.resolved);
|
|
9081
|
-
|
|
9318
|
+
const report = await runSyncReadme({
|
|
9082
9319
|
loaded,
|
|
9083
9320
|
context: {
|
|
9084
9321
|
repoRoot: argv.cwd,
|
|
9085
9322
|
dryRun: argv.dryRun
|
|
9086
9323
|
}
|
|
9087
|
-
})
|
|
9324
|
+
});
|
|
9325
|
+
if (report.status === "fail") {
|
|
9326
|
+
if (report.message) reportError(`sync-readme: ${report.message}`);
|
|
9327
|
+
process.exitCode = 1;
|
|
9328
|
+
}
|
|
9088
9329
|
}).command("config show", "Print the resolved holocron config", () => {}, async (argv) => {
|
|
9089
9330
|
const loaded = await loadConfig(argv.cwd);
|
|
9090
9331
|
console.log(JSON.stringify(loaded.resolved, null, 2));
|
|
@@ -9313,12 +9554,12 @@ try {
|
|
|
9313
9554
|
}) === "yes";
|
|
9314
9555
|
if (skills.length === 0) skills = parseTopics(await input({ message: "Agent skills (comma-separated, optional):" }));
|
|
9315
9556
|
if (!type) {
|
|
9316
|
-
|
|
9557
|
+
reportError("new: template type is required");
|
|
9317
9558
|
process.exitCode = 1;
|
|
9318
9559
|
return;
|
|
9319
9560
|
}
|
|
9320
9561
|
if (!name) {
|
|
9321
|
-
|
|
9562
|
+
reportError("new: repo name is required");
|
|
9322
9563
|
process.exitCode = 1;
|
|
9323
9564
|
return;
|
|
9324
9565
|
}
|
|
@@ -9347,7 +9588,7 @@ try {
|
|
|
9347
9588
|
})).status === "fail") process.exitCode = 1;
|
|
9348
9589
|
} catch (err) {
|
|
9349
9590
|
if (err instanceof NewError) {
|
|
9350
|
-
|
|
9591
|
+
reportError(`new: ${err.message}`);
|
|
9351
9592
|
process.exitCode = 1;
|
|
9352
9593
|
return;
|
|
9353
9594
|
}
|
|
@@ -9406,7 +9647,7 @@ try {
|
|
|
9406
9647
|
}).status === "fail") process.exitCode = 1;
|
|
9407
9648
|
} catch (err) {
|
|
9408
9649
|
if (err instanceof PluginCreateError) {
|
|
9409
|
-
|
|
9650
|
+
reportError(`plugin create: ${err.message}`);
|
|
9410
9651
|
process.exitCode = 1;
|
|
9411
9652
|
return;
|
|
9412
9653
|
}
|
|
@@ -9434,7 +9675,7 @@ try {
|
|
|
9434
9675
|
extra
|
|
9435
9676
|
});
|
|
9436
9677
|
if (report.status === "fail") {
|
|
9437
|
-
if (report.message)
|
|
9678
|
+
if (report.message) reportError(`upgrade node: ${report.message}`);
|
|
9438
9679
|
process.exitCode = 1;
|
|
9439
9680
|
}
|
|
9440
9681
|
}).command("deps", "Bump every @theholocron/* pin to latest and migrate holocron.config.ts to the current preset API", (yy) => yy.option("pins-only", {
|
|
@@ -9448,7 +9689,7 @@ try {
|
|
|
9448
9689
|
pinsOnly: argv.pinsOnly
|
|
9449
9690
|
});
|
|
9450
9691
|
if (report.status === "fail") {
|
|
9451
|
-
if (report.message)
|
|
9692
|
+
if (report.message) reportError(`upgrade deps: ${report.message}`);
|
|
9452
9693
|
process.exitCode = 1;
|
|
9453
9694
|
}
|
|
9454
9695
|
}).command("$0", false, () => {}, async (argv) => {
|
|
@@ -9474,25 +9715,25 @@ try {
|
|
|
9474
9715
|
}).command("$0", false, () => {}, async (argv) => {
|
|
9475
9716
|
await launchMenu(COMMAND_REGISTRY.filter((e) => e.group === "auth"), argv, "auth — choose a subcommand:", "Run `holocron auth --help` to see available auth subcommands.");
|
|
9476
9717
|
}), () => {}).command("$0", false, () => {}, async (argv) => {
|
|
9477
|
-
|
|
9718
|
+
printVersionHeaderOnce();
|
|
9478
9719
|
await launchMenu(COMMAND_REGISTRY.filter((e) => !e.group), argv);
|
|
9479
9720
|
}).strict().help().epilogue(`Execution contexts:
|
|
9480
9721
|
global works from a bare 'npm i -g': ${commandsInContext("global").join(", ")}\n repo-aware needs ./holocron.config in cwd: ${commandsInContext("repo-aware").join(", ")}\n workspace also needs the plugin packages: ${commandsInContext("workspace").join(", ")}\n https://theholocron.github.io/holocron/execution-contexts`).fail((msg, err) => {
|
|
9481
9722
|
if (err instanceof Error && USER_FACING_ERRORS.has(err.name)) {
|
|
9482
9723
|
captureException(err);
|
|
9483
|
-
|
|
9724
|
+
reportError(err.message);
|
|
9484
9725
|
errorReported = true;
|
|
9485
9726
|
process.exitCode = 1;
|
|
9486
9727
|
return;
|
|
9487
9728
|
}
|
|
9488
9729
|
if (err) throw err;
|
|
9489
|
-
|
|
9730
|
+
reportError(msg);
|
|
9490
9731
|
errorReported = true;
|
|
9491
9732
|
process.exitCode = 1;
|
|
9492
9733
|
}).parse();
|
|
9493
9734
|
} catch (err) {
|
|
9494
9735
|
captureException(err);
|
|
9495
|
-
if (!errorReported && err instanceof Error && USER_FACING_ERRORS.has(err.name))
|
|
9736
|
+
if (!errorReported && err instanceof Error && USER_FACING_ERRORS.has(err.name)) reportError(err.message);
|
|
9496
9737
|
if (!process.exitCode) process.exitCode = 1;
|
|
9497
9738
|
}
|
|
9498
9739
|
finishCommand(!process.exitCode);
|