@theholocron/cli 5.0.0-alpha.121 → 5.0.0-alpha.122
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 +289 -78
- 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,52 @@ 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
|
+
hint: (msg) => chalk.dim(msg),
|
|
313
|
+
dim: (msg) => chalk.dim(msg),
|
|
314
|
+
header: (msg) => chalk.bold(msg)
|
|
315
|
+
};
|
|
316
|
+
//#endregion
|
|
306
317
|
//#region src/logger.ts
|
|
307
318
|
/**
|
|
308
319
|
* Resolve the explicit level to hand to `createLogger`, in priority order:
|
|
309
320
|
*
|
|
310
|
-
* 1. `--verbose` → `"debug"`
|
|
311
|
-
*
|
|
312
|
-
*
|
|
321
|
+
* 1. `--log <level>` 2. `--verbose` → `"debug"` 3. `--quiet` → `"error"`
|
|
322
|
+
* 4. `HOLOCRON_LOG_LEVEL` env var
|
|
323
|
+
* 5. `holocron.config` `log.level` (`configLevel`)
|
|
313
324
|
*
|
|
314
325
|
* Returns `undefined` when nothing applies — `createLogger` then defaults
|
|
315
326
|
* to `"info"`. Resolving the full chain here (rather than passing
|
|
316
327
|
* `configLevel` straight through) keeps config below the env var.
|
|
317
328
|
*/
|
|
318
329
|
function resolveLogLevel(argv, configLevel) {
|
|
330
|
+
if (argv.log) return argv.log;
|
|
319
331
|
if (argv.verbose) return "debug";
|
|
320
332
|
if (argv.quiet) return "error";
|
|
321
333
|
return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
|
|
322
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Resolve whether human-visible console output (pretty or NDJSON) should run
|
|
337
|
+
* at all, independent of level — the `@theholocron/observability`
|
|
338
|
+
* `consoleOutput` override.
|
|
339
|
+
*
|
|
340
|
+
* Local/interactive runs are quiet by default: any raw structured logger
|
|
341
|
+
* output (colorized or not) stays off regardless of the resolved level,
|
|
342
|
+
* unless the user explicitly asked to see it via `--log <level>`,
|
|
343
|
+
* `--verbose`, or `--quiet`. CI is untouched either way — returning
|
|
344
|
+
* `undefined` there preserves `createLogger`'s own `ci`/`tty`
|
|
345
|
+
* auto-detection exactly, so CI's existing NDJSON-to-stdout behavior is
|
|
346
|
+
* unaffected by this flag.
|
|
347
|
+
*/
|
|
348
|
+
function resolveConsoleOutput(argv) {
|
|
349
|
+
if (argv.log || argv.verbose || argv.quiet) return true;
|
|
350
|
+
return isCI() ? void 0 : false;
|
|
351
|
+
}
|
|
323
352
|
let root;
|
|
324
353
|
let rootLevel;
|
|
325
354
|
let rootCommand;
|
|
@@ -358,13 +387,15 @@ function buildCliLogger(argv, opts = {}) {
|
|
|
358
387
|
const { command, configLevel } = opts;
|
|
359
388
|
if (command) rootCommand = command;
|
|
360
389
|
const level = resolveLogLevel(argv, configLevel);
|
|
390
|
+
const consoleOutput = resolveConsoleOutput(argv);
|
|
361
391
|
const axiom = resolveCliAxiom(opts);
|
|
362
392
|
const axiomKey = axiom?.dataset;
|
|
363
|
-
const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
|
|
393
|
+
const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet && !argv.log;
|
|
364
394
|
if (!root || rebuildForConfig || axiomKey !== void 0 && axiomKey !== rootAxiomKey) {
|
|
365
395
|
const built = createLogger({
|
|
366
396
|
...level ? { level } : {},
|
|
367
|
-
...axiom ? { axiom } : {}
|
|
397
|
+
...axiom ? { axiom } : {},
|
|
398
|
+
...consoleOutput !== void 0 ? { consoleOutput } : {}
|
|
368
399
|
});
|
|
369
400
|
root = {
|
|
370
401
|
logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
|
|
@@ -375,9 +406,35 @@ function buildCliLogger(argv, opts = {}) {
|
|
|
375
406
|
}
|
|
376
407
|
return root;
|
|
377
408
|
}
|
|
378
|
-
/**
|
|
409
|
+
/**
|
|
410
|
+
* Lazily-memoized `Logger` for module-level call sites with no `argv` in
|
|
411
|
+
* scope — notably yargs' own `.fail()` handler, which fires on a
|
|
412
|
+
* pre-middleware validation failure (a missing required argument, an
|
|
413
|
+
* unknown command) and therefore runs before `buildCliLogger` has ever
|
|
414
|
+
* built the real root. Without an explicit `consoleOutput` here, that
|
|
415
|
+
* fallback would use `createLogger`'s bare auto-detection (pretty on any
|
|
416
|
+
* local TTY) and leak a structured block even though no flag asked for one
|
|
417
|
+
* — so it resolves the same "quiet unless CI" default `buildCliLogger`
|
|
418
|
+
* would, as if no flags were passed.
|
|
419
|
+
*/
|
|
379
420
|
function getLogger() {
|
|
380
|
-
return (root ??= createLogger()).logger;
|
|
421
|
+
return (root ??= createLogger({ consoleOutput: resolveConsoleOutput({}) })).logger;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Report a fatal, command-ending CLI error: always printed to the console
|
|
425
|
+
* (regardless of `consoleOutput` suppression) via `style.fail`, and also
|
|
426
|
+
* sent through the structured logger for Axiom/telemetry visibility.
|
|
427
|
+
*
|
|
428
|
+
* Use this when a handler is returning or exiting right here with nothing
|
|
429
|
+
* else for the user to see — a resolved `--token` failure, a yargs
|
|
430
|
+
* validation failure, a thrown domain error. Don't use it for a per-step
|
|
431
|
+
* warning inside a multi-step orchestrator that already prints its own
|
|
432
|
+
* `ok / fail / skip` summary — that summary is the user-facing signal;
|
|
433
|
+
* the per-step reason belongs in the structured log alone.
|
|
434
|
+
*/
|
|
435
|
+
function reportError(message, log = getLogger()) {
|
|
436
|
+
console.error(style.fail(message));
|
|
437
|
+
log.error(message);
|
|
381
438
|
}
|
|
382
439
|
/** The current root logger's correlation id, if a root has been built. */
|
|
383
440
|
function getRunId() {
|
|
@@ -637,17 +694,6 @@ async function withSpinner(label, fn) {
|
|
|
637
694
|
}
|
|
638
695
|
}
|
|
639
696
|
//#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
697
|
//#region src/commands/auth.ts
|
|
652
698
|
/**
|
|
653
699
|
* `holocron auth <subcommand>` — manage bootstrap credentials in the
|
|
@@ -1132,7 +1178,6 @@ async function runClone(input) {
|
|
|
1132
1178
|
const fetchFn = input.fetch ?? globalThis.fetch;
|
|
1133
1179
|
const dryRun = input.dryRun ?? false;
|
|
1134
1180
|
const targetDir = resolve(input.dir ?? join(homedir(), "Code", input.org));
|
|
1135
|
-
const label = input.repo ? `${input.org}/${input.repo}` : `org=${input.org}`;
|
|
1136
1181
|
logger.info({
|
|
1137
1182
|
org: input.org,
|
|
1138
1183
|
repo: input.repo,
|
|
@@ -1145,7 +1190,6 @@ async function runClone(input) {
|
|
|
1145
1190
|
stdio: "inherit"
|
|
1146
1191
|
}).status };
|
|
1147
1192
|
});
|
|
1148
|
-
print(style.header(`Holocron clone — ${label} → ${targetDir}${dryRun ? " (dry-run)" : ""}`));
|
|
1149
1193
|
if (!existsSync(targetDir)) {
|
|
1150
1194
|
if (dryRun) print(style.dim(` would create ${targetDir}`));
|
|
1151
1195
|
else mkdirSync(targetDir, { recursive: true });
|
|
@@ -1196,7 +1240,7 @@ async function runClone(input) {
|
|
|
1196
1240
|
for (const repo of repos) {
|
|
1197
1241
|
const dest = join(targetDir, repoDirName(repo));
|
|
1198
1242
|
if (existsSync(dest)) {
|
|
1199
|
-
print(style.dim(` skip ${repo.full_name}`));
|
|
1243
|
+
print(style.dim(` skip ${repo.full_name} → ${dest} (already exists)`));
|
|
1200
1244
|
skipped++;
|
|
1201
1245
|
continue;
|
|
1202
1246
|
}
|
|
@@ -1205,10 +1249,10 @@ async function runClone(input) {
|
|
|
1205
1249
|
cloned++;
|
|
1206
1250
|
continue;
|
|
1207
1251
|
}
|
|
1208
|
-
print(style.step(` clone ${repo.full_name}`));
|
|
1252
|
+
print(style.step(` clone ${repo.full_name} → ${dest}`));
|
|
1209
1253
|
const { clone_url } = repo;
|
|
1210
1254
|
if (!clone_url.startsWith("https://github.com/")) {
|
|
1211
|
-
print(style.fail(` failed ${repo.full_name}
|
|
1255
|
+
print(style.fail(` failed ${repo.full_name} → unexpected clone URL: ${clone_url}`));
|
|
1212
1256
|
failed++;
|
|
1213
1257
|
continue;
|
|
1214
1258
|
}
|
|
@@ -1216,7 +1260,7 @@ async function runClone(input) {
|
|
|
1216
1260
|
try {
|
|
1217
1261
|
encodedToken = encodeTokenForGitHttpAuth(input.token);
|
|
1218
1262
|
} catch (err) {
|
|
1219
|
-
print(style.fail(` failed ${repo.full_name}
|
|
1263
|
+
print(style.fail(` failed ${repo.full_name} → invalid token format: ${errorMessage(err)}`));
|
|
1220
1264
|
failed++;
|
|
1221
1265
|
continue;
|
|
1222
1266
|
}
|
|
@@ -1229,12 +1273,13 @@ async function runClone(input) {
|
|
|
1229
1273
|
print(style.fail(` failed ${repo.full_name}`));
|
|
1230
1274
|
failed++;
|
|
1231
1275
|
} else {
|
|
1232
|
-
print(style.success(` cloned ${repo.name}`));
|
|
1276
|
+
print(style.success(` cloned ${repo.name} → ${dest}`));
|
|
1233
1277
|
cloned++;
|
|
1234
1278
|
}
|
|
1235
1279
|
}
|
|
1236
1280
|
const summary = `${cloned} cloned, ${skipped} skipped, ${failed} failed`;
|
|
1237
|
-
print(
|
|
1281
|
+
print("");
|
|
1282
|
+
print(dryRun ? style.dim(` dry-run: ${summary}`) : failed > 0 ? style.fail(` ${summary}`) : style.success(` ${summary}`));
|
|
1238
1283
|
const status = dryRun ? "dry-run" : failed > 0 ? "fail" : "ok";
|
|
1239
1284
|
logger[failed > 0 ? "warn" : "info"]({
|
|
1240
1285
|
org: input.org,
|
|
@@ -1244,6 +1289,10 @@ async function runClone(input) {
|
|
|
1244
1289
|
skipped,
|
|
1245
1290
|
failed
|
|
1246
1291
|
}, "clone: done");
|
|
1292
|
+
if (input.confirmOpen) {
|
|
1293
|
+
const openTarget = input.repo ? join(targetDir, repoDirName(repos[0])) : targetDir;
|
|
1294
|
+
if (existsSync(openTarget)) await input.confirmOpen(openTarget, dryRun && cloned > 0);
|
|
1295
|
+
}
|
|
1247
1296
|
return {
|
|
1248
1297
|
status,
|
|
1249
1298
|
cloned,
|
|
@@ -8043,6 +8092,15 @@ function parseGitRemoteUrl(url) {
|
|
|
8043
8092
|
* {@link promptForPositionals} for just that one command — no spawn needed,
|
|
8044
8093
|
* the handler already has everything else it needs.
|
|
8045
8094
|
*
|
|
8095
|
+
* For a command whose handler honours `--dry-run` ({@link
|
|
8096
|
+
* CommandEntry.supportsDryRun}), `launchMenu` always previews it first — a
|
|
8097
|
+
* dry run doesn't mutate anything, so there's nothing to ask permission
|
|
8098
|
+
* for — then, once that preview comes back clean and found something to
|
|
8099
|
+
* do, asks the one real decision: "proceed with the real run?". A preview
|
|
8100
|
+
* that finds nothing to do skips that question too. `--skip-dry-run` opts
|
|
8101
|
+
* a top-level invocation out of the preview entirely, straight to the
|
|
8102
|
+
* real run, for when you already know what you want.
|
|
8103
|
+
*
|
|
8046
8104
|
* Every prompt is preceded by a `process.stdin.isTTY` check. A non-TTY run
|
|
8047
8105
|
* (CI, a script, a pipe) throws {@link NonInteractiveError} instead of
|
|
8048
8106
|
* hanging on `@inquirer/prompts` (which otherwise throws its own
|
|
@@ -8072,17 +8130,20 @@ const COMMAND_REGISTRY = [
|
|
|
8072
8130
|
message: "org/repo to clone (or a bare org to clone every repo in it):",
|
|
8073
8131
|
type: "input",
|
|
8074
8132
|
validate: (v) => v.trim().length > 0 || "org/repo or org is required"
|
|
8075
|
-
}]
|
|
8133
|
+
}],
|
|
8134
|
+
supportsDryRun: true
|
|
8076
8135
|
},
|
|
8077
8136
|
{
|
|
8078
8137
|
name: "doctor",
|
|
8079
8138
|
description: "Load the config and run a smoke check against every provider",
|
|
8080
|
-
positionals: []
|
|
8139
|
+
positionals: [],
|
|
8140
|
+
supportsDryRun: true
|
|
8081
8141
|
},
|
|
8082
8142
|
{
|
|
8083
8143
|
name: "setup",
|
|
8084
8144
|
description: "Apply infra setup actions across every configured capability",
|
|
8085
|
-
positionals: []
|
|
8145
|
+
positionals: [],
|
|
8146
|
+
supportsDryRun: true
|
|
8086
8147
|
},
|
|
8087
8148
|
{
|
|
8088
8149
|
name: "secret set",
|
|
@@ -8091,7 +8152,8 @@ const COMMAND_REGISTRY = [
|
|
|
8091
8152
|
key: "name",
|
|
8092
8153
|
message: "Secret name:",
|
|
8093
8154
|
type: "input"
|
|
8094
|
-
}]
|
|
8155
|
+
}],
|
|
8156
|
+
supportsDryRun: true
|
|
8095
8157
|
},
|
|
8096
8158
|
{
|
|
8097
8159
|
name: "secrets sync",
|
|
@@ -8100,7 +8162,8 @@ const COMMAND_REGISTRY = [
|
|
|
8100
8162
|
key: "environmentId",
|
|
8101
8163
|
message: "Vault environment id:",
|
|
8102
8164
|
type: "input"
|
|
8103
|
-
}]
|
|
8165
|
+
}],
|
|
8166
|
+
supportsDryRun: true
|
|
8104
8167
|
},
|
|
8105
8168
|
{
|
|
8106
8169
|
name: "deploy",
|
|
@@ -8109,7 +8172,8 @@ const COMMAND_REGISTRY = [
|
|
|
8109
8172
|
key: "branch",
|
|
8110
8173
|
message: "Branch to deploy:",
|
|
8111
8174
|
type: "input"
|
|
8112
|
-
}]
|
|
8175
|
+
}],
|
|
8176
|
+
supportsDryRun: true
|
|
8113
8177
|
},
|
|
8114
8178
|
{
|
|
8115
8179
|
name: "cleanup-preview",
|
|
@@ -8119,7 +8183,8 @@ const COMMAND_REGISTRY = [
|
|
|
8119
8183
|
message: "PR number:",
|
|
8120
8184
|
type: "input",
|
|
8121
8185
|
validate: numeric
|
|
8122
|
-
}]
|
|
8186
|
+
}],
|
|
8187
|
+
supportsDryRun: true
|
|
8123
8188
|
},
|
|
8124
8189
|
{
|
|
8125
8190
|
name: "bump-versions",
|
|
@@ -8129,32 +8194,38 @@ const COMMAND_REGISTRY = [
|
|
|
8129
8194
|
cliArg: "new-version",
|
|
8130
8195
|
message: "New version:",
|
|
8131
8196
|
type: "input"
|
|
8132
|
-
}]
|
|
8197
|
+
}],
|
|
8198
|
+
supportsDryRun: true
|
|
8133
8199
|
},
|
|
8134
8200
|
{
|
|
8135
8201
|
name: "publish",
|
|
8136
8202
|
description: "Publish @theholocron/* packages to npm",
|
|
8137
|
-
positionals: []
|
|
8203
|
+
positionals: [],
|
|
8204
|
+
supportsDryRun: true
|
|
8138
8205
|
},
|
|
8139
8206
|
{
|
|
8140
8207
|
name: "sync",
|
|
8141
8208
|
description: "Sync state from config to the provider and local files (labels, properties, teams, topics, keywords, description, homepage, readme, workflows, scripts, wiki)",
|
|
8142
|
-
positionals: []
|
|
8209
|
+
positionals: [],
|
|
8210
|
+
supportsDryRun: true
|
|
8143
8211
|
},
|
|
8144
8212
|
{
|
|
8145
8213
|
name: "ci",
|
|
8146
8214
|
description: "Run the merge-gating checks locally, in CI order — 'will CI pass?'",
|
|
8147
|
-
positionals: []
|
|
8215
|
+
positionals: [],
|
|
8216
|
+
supportsDryRun: true
|
|
8148
8217
|
},
|
|
8149
8218
|
{
|
|
8150
8219
|
name: "sync-github",
|
|
8151
8220
|
description: "Sync workflow templates and composite actions to theholocron/.github",
|
|
8152
|
-
positionals: []
|
|
8221
|
+
positionals: [],
|
|
8222
|
+
supportsDryRun: true
|
|
8153
8223
|
},
|
|
8154
8224
|
{
|
|
8155
8225
|
name: "sync-readme",
|
|
8156
8226
|
description: "Sync the Installation + Usage block in README.md from package.json",
|
|
8157
|
-
positionals: []
|
|
8227
|
+
positionals: [],
|
|
8228
|
+
supportsDryRun: true
|
|
8158
8229
|
},
|
|
8159
8230
|
{
|
|
8160
8231
|
name: "config show",
|
|
@@ -8164,7 +8235,8 @@ const COMMAND_REGISTRY = [
|
|
|
8164
8235
|
{
|
|
8165
8236
|
name: "new",
|
|
8166
8237
|
description: "Scaffold a new repo from a GitHub template (e.g. cli, react, nextjs, node, monorepo, base)",
|
|
8167
|
-
positionals: []
|
|
8238
|
+
positionals: [],
|
|
8239
|
+
supportsDryRun: true
|
|
8168
8240
|
},
|
|
8169
8241
|
{
|
|
8170
8242
|
name: "plugin create",
|
|
@@ -8177,25 +8249,29 @@ const COMMAND_REGISTRY = [
|
|
|
8177
8249
|
key: "vendor",
|
|
8178
8250
|
message: "Vendor display name (PascalCase):",
|
|
8179
8251
|
type: "input"
|
|
8180
|
-
}]
|
|
8252
|
+
}],
|
|
8253
|
+
supportsDryRun: true
|
|
8181
8254
|
},
|
|
8182
8255
|
{
|
|
8183
8256
|
name: "skills install",
|
|
8184
8257
|
description: "Copy skills from @theholocron/skills into .agents/ with agent symlinks",
|
|
8185
8258
|
positionals: [],
|
|
8186
|
-
group: "skills"
|
|
8259
|
+
group: "skills",
|
|
8260
|
+
supportsDryRun: true
|
|
8187
8261
|
},
|
|
8188
8262
|
{
|
|
8189
8263
|
name: "skills remove",
|
|
8190
8264
|
description: "Remove installed skills via npx skills remove",
|
|
8191
8265
|
positionals: [],
|
|
8192
|
-
group: "skills"
|
|
8266
|
+
group: "skills",
|
|
8267
|
+
supportsDryRun: true
|
|
8193
8268
|
},
|
|
8194
8269
|
{
|
|
8195
8270
|
name: "skills update",
|
|
8196
8271
|
description: "Update installed skills to their latest upstream versions via npx skills update",
|
|
8197
8272
|
positionals: [],
|
|
8198
|
-
group: "skills"
|
|
8273
|
+
group: "skills",
|
|
8274
|
+
supportsDryRun: true
|
|
8199
8275
|
},
|
|
8200
8276
|
{
|
|
8201
8277
|
name: "upgrade node",
|
|
@@ -8206,13 +8282,15 @@ const COMMAND_REGISTRY = [
|
|
|
8206
8282
|
type: "input",
|
|
8207
8283
|
validate: numeric
|
|
8208
8284
|
}],
|
|
8209
|
-
group: "upgrade"
|
|
8285
|
+
group: "upgrade",
|
|
8286
|
+
supportsDryRun: true
|
|
8210
8287
|
},
|
|
8211
8288
|
{
|
|
8212
8289
|
name: "upgrade deps",
|
|
8213
8290
|
description: "Bump every @theholocron/* pin to latest and migrate holocron.config.ts to the current preset API",
|
|
8214
8291
|
positionals: [],
|
|
8215
|
-
group: "upgrade"
|
|
8292
|
+
group: "upgrade",
|
|
8293
|
+
supportsDryRun: true
|
|
8216
8294
|
},
|
|
8217
8295
|
{
|
|
8218
8296
|
name: "auth set",
|
|
@@ -8327,25 +8405,41 @@ async function promptForPositionals(entry, argv) {
|
|
|
8327
8405
|
* menu-launched child should inherit from the parent invocation. Everything
|
|
8328
8406
|
* else (command-specific options) was never captured by the top-level `$0`
|
|
8329
8407
|
* handler in the first place, so there's nothing else to forward.
|
|
8408
|
+
*
|
|
8409
|
+
* `dryRunOverride`, when passed, wins over `argv.dryRun` — `launchMenu`'s
|
|
8410
|
+
* dry-run offer needs to force `--dry-run` on for the preview spawn and
|
|
8411
|
+
* force it back off for the real-run spawn that follows, regardless of
|
|
8412
|
+
* what was on the original invocation.
|
|
8330
8413
|
*/
|
|
8331
|
-
function forwardedFlags(argv) {
|
|
8414
|
+
function forwardedFlags(argv, dryRunOverride) {
|
|
8332
8415
|
const out = [];
|
|
8333
8416
|
const tokens = argv.token;
|
|
8334
8417
|
for (const t of tokens ?? []) out.push("--token", t);
|
|
8335
8418
|
if (typeof argv.org === "string") out.push("--org", argv.org);
|
|
8336
8419
|
if (typeof argv.cwd === "string") out.push("--cwd", argv.cwd);
|
|
8337
|
-
if (argv.dryRun === true) out.push("--dry-run");
|
|
8420
|
+
if (dryRunOverride ?? argv.dryRun === true) out.push("--dry-run");
|
|
8338
8421
|
return out;
|
|
8339
8422
|
}
|
|
8340
8423
|
/** `entry.name` split into tokens, resolved positionals appended, then forwarded flags. */
|
|
8341
|
-
function buildChildArgv(entry, positionals, parentArgv) {
|
|
8424
|
+
function buildChildArgv(entry, positionals, parentArgv, dryRunOverride) {
|
|
8342
8425
|
return [
|
|
8343
8426
|
...entry.name.split(" "),
|
|
8344
8427
|
...positionals,
|
|
8345
|
-
...forwardedFlags(parentArgv)
|
|
8428
|
+
...forwardedFlags(parentArgv, dryRunOverride)
|
|
8346
8429
|
];
|
|
8347
8430
|
}
|
|
8348
8431
|
/**
|
|
8432
|
+
* Set on a dry-run preview child's env ({@link spawnChild}'s `extraEnv`) so
|
|
8433
|
+
* its handler knows this specific `--dry-run` came from `launchMenu`'s own
|
|
8434
|
+
* preview-then-ask flow, not a direct `holocron <cmd> --dry-run` invocation.
|
|
8435
|
+
* A handler that supports it uses this — never bare `argv.dryRun` — to
|
|
8436
|
+
* decide whether to report {@link DRY_RUN_NOOP_EXIT_CODE}: that exit code
|
|
8437
|
+
* only has special meaning to `launchMenu` itself, so a direct/scripted
|
|
8438
|
+
* `--dry-run` call must never produce it — doing so unconditionally would
|
|
8439
|
+
* silently change the public exit-code contract for a successful dry run.
|
|
8440
|
+
*/
|
|
8441
|
+
const DRY_RUN_PREVIEW_ENV_VAR = "HOLOCRON_DRY_RUN_PREVIEW";
|
|
8442
|
+
/**
|
|
8349
8443
|
* Spawn `holocron <...args>` inheriting stdio; resolve with its exit code.
|
|
8350
8444
|
*
|
|
8351
8445
|
* `NO_UPDATE_NOTIFIER` is forced on in the child's env — the parent's own
|
|
@@ -8353,15 +8447,17 @@ function buildChildArgv(entry, positionals, parentArgv) {
|
|
|
8353
8447
|
* docstring), so without this the freshly-spawned child independently runs
|
|
8354
8448
|
* its own `checkForUpdates()` too and the notice prints twice: once from
|
|
8355
8449
|
* the child right after its command output, once more from the parent
|
|
8356
|
-
* once the child exits.
|
|
8450
|
+
* once the child exits. `extraEnv` layers on top — used only for {@link
|
|
8451
|
+
* DRY_RUN_PREVIEW_ENV_VAR} on the dry-run preview spawn.
|
|
8357
8452
|
*/
|
|
8358
|
-
function spawnChild(args) {
|
|
8453
|
+
function spawnChild(args, extraEnv) {
|
|
8359
8454
|
return new Promise((resolve) => {
|
|
8360
8455
|
const child = spawn(process.execPath, [process.argv[1], ...args], {
|
|
8361
8456
|
stdio: "inherit",
|
|
8362
8457
|
env: {
|
|
8363
8458
|
...process.env,
|
|
8364
|
-
NO_UPDATE_NOTIFIER: "1"
|
|
8459
|
+
NO_UPDATE_NOTIFIER: "1",
|
|
8460
|
+
...extraEnv
|
|
8365
8461
|
}
|
|
8366
8462
|
});
|
|
8367
8463
|
child.on("exit", (code) => resolve(code ?? 0));
|
|
@@ -8369,6 +8465,17 @@ function spawnChild(args) {
|
|
|
8369
8465
|
});
|
|
8370
8466
|
}
|
|
8371
8467
|
/**
|
|
8468
|
+
* Whether `launchMenu` should auto-preview a picked command before running
|
|
8469
|
+
* it for real — only when the command's handler actually honours
|
|
8470
|
+
* `--dry-run` ({@link CommandEntry.supportsDryRun}) and the top-level
|
|
8471
|
+
* invocation didn't pass `--skip-dry-run` to opt out of the preview
|
|
8472
|
+
* entirely (an escape hatch for "I already know what I want, skip
|
|
8473
|
+
* straight to the real run").
|
|
8474
|
+
*/
|
|
8475
|
+
function shouldPreviewDryRun(entry, parentArgv) {
|
|
8476
|
+
return Boolean(entry.supportsDryRun) && parentArgv.skipDryRun !== true;
|
|
8477
|
+
}
|
|
8478
|
+
/**
|
|
8372
8479
|
* Layers 1 and 2: pick a command from `entries`, prompt for its required
|
|
8373
8480
|
* positionals, spawn it as a fresh `holocron` invocation, and set
|
|
8374
8481
|
* `process.exitCode` from the child — the caller (a `$0` handler in
|
|
@@ -8377,12 +8484,79 @@ function spawnChild(args) {
|
|
|
8377
8484
|
* `command_completed` event fires with command name "unknown" (the
|
|
8378
8485
|
* middleware ran before any command was picked) — left as-is rather than
|
|
8379
8486
|
* suppressed; it's a real, useful signal ("the menu got used").
|
|
8487
|
+
*
|
|
8488
|
+
* For a command that honours `--dry-run` ({@link CommandEntry.supportsDryRun})
|
|
8489
|
+
* and wasn't opted out via `--skip-dry-run` ({@link shouldPreviewDryRun}),
|
|
8490
|
+
* this always previews first — a dry run doesn't mutate anything, so
|
|
8491
|
+
* there's nothing to ask permission for. A preview that exits non-zero
|
|
8492
|
+
* stops here; one that reports {@link DRY_RUN_NOOP_EXIT_CODE} (nothing
|
|
8493
|
+
* would change) stops here too, skipping the follow-up question since the
|
|
8494
|
+
* real run would have nothing to do either. Otherwise it asks "proceed
|
|
8495
|
+
* with the real run?" before the real spawn — the one actual decision
|
|
8496
|
+
* left to make, since this one does mutate something.
|
|
8380
8497
|
*/
|
|
8381
8498
|
async function launchMenu(entries, parentArgv, pickMessage, nonInteractiveMessage = "Run `holocron --help` to see available commands.") {
|
|
8382
8499
|
if (!process.stdin.isTTY) throw new NonInteractiveError(nonInteractiveMessage);
|
|
8383
8500
|
const picked = await pickCommand(entries, pickMessage);
|
|
8384
|
-
const
|
|
8385
|
-
|
|
8501
|
+
const positionals = await promptForPositionals(picked, {});
|
|
8502
|
+
if (shouldPreviewDryRun(picked, parentArgv)) {
|
|
8503
|
+
const dryExit = await spawnChild(buildChildArgv(picked, positionals, parentArgv, true), { [DRY_RUN_PREVIEW_ENV_VAR]: "1" });
|
|
8504
|
+
if (dryExit === 2) {
|
|
8505
|
+
process.exitCode = 0;
|
|
8506
|
+
return;
|
|
8507
|
+
}
|
|
8508
|
+
if (dryExit !== 0) {
|
|
8509
|
+
process.exitCode = dryExit;
|
|
8510
|
+
return;
|
|
8511
|
+
}
|
|
8512
|
+
if (!await confirm({
|
|
8513
|
+
message: "Dry run complete — proceed with the real run?",
|
|
8514
|
+
default: false
|
|
8515
|
+
})) {
|
|
8516
|
+
process.exitCode = 0;
|
|
8517
|
+
return;
|
|
8518
|
+
}
|
|
8519
|
+
process.exitCode = await spawnChild(buildChildArgv(picked, positionals, parentArgv, false));
|
|
8520
|
+
return;
|
|
8521
|
+
}
|
|
8522
|
+
process.exitCode = await spawnChild(buildChildArgv(picked, positionals, parentArgv));
|
|
8523
|
+
}
|
|
8524
|
+
//#endregion
|
|
8525
|
+
//#region src/ui/open-path.ts
|
|
8526
|
+
/** Platform-appropriate command to open a path in the OS's file browser. */
|
|
8527
|
+
function openCommand(path) {
|
|
8528
|
+
if (process.platform === "darwin") return {
|
|
8529
|
+
cmd: "open",
|
|
8530
|
+
args: [path]
|
|
8531
|
+
};
|
|
8532
|
+
if (process.platform === "win32") return {
|
|
8533
|
+
cmd: "cmd",
|
|
8534
|
+
args: [
|
|
8535
|
+
"/c",
|
|
8536
|
+
"start",
|
|
8537
|
+
"",
|
|
8538
|
+
path
|
|
8539
|
+
]
|
|
8540
|
+
};
|
|
8541
|
+
return {
|
|
8542
|
+
cmd: "xdg-open",
|
|
8543
|
+
args: [path]
|
|
8544
|
+
};
|
|
8545
|
+
}
|
|
8546
|
+
/**
|
|
8547
|
+
* Open `path` in the OS's default file browser (Finder / Explorer /
|
|
8548
|
+
* whatever `xdg-open` resolves to on Linux). Fire-and-forget — detached so
|
|
8549
|
+
* it outlives this process, and never throws when the opener binary isn't
|
|
8550
|
+
* on PATH (e.g. no `xdg-open` in a minimal container).
|
|
8551
|
+
*/
|
|
8552
|
+
function openPath(path) {
|
|
8553
|
+
const { cmd, args } = openCommand(path);
|
|
8554
|
+
const child = spawn(cmd, args, {
|
|
8555
|
+
stdio: "ignore",
|
|
8556
|
+
detached: true
|
|
8557
|
+
});
|
|
8558
|
+
child.on("error", () => {});
|
|
8559
|
+
child.unref();
|
|
8386
8560
|
}
|
|
8387
8561
|
//#endregion
|
|
8388
8562
|
//#region src/update-notifier.ts
|
|
@@ -8528,6 +8702,19 @@ function resolveOrg(argv, config) {
|
|
|
8528
8702
|
return argv.org ?? env.get("HOLOCRON_ORG") ?? config.org;
|
|
8529
8703
|
}
|
|
8530
8704
|
/**
|
|
8705
|
+
* Print the dim version line once per terminal session — keyed by the
|
|
8706
|
+
* parent shell's PID, since that's stable across repeated bare `holocron`
|
|
8707
|
+
* invocations in the same shell but changes in a new tab/window. A marker
|
|
8708
|
+
* file in the OS tmpdir tracks whether this shell has already seen it, so
|
|
8709
|
+
* re-running `holocron` doesn't repeat the line every time.
|
|
8710
|
+
*/
|
|
8711
|
+
function printVersionHeaderOnce() {
|
|
8712
|
+
const marker = join(tmpdir(), `.holocron-version-shown-${process.ppid}`);
|
|
8713
|
+
if (existsSync(marker)) return;
|
|
8714
|
+
console.log(style.dim(`v${CLI_VERSION}`));
|
|
8715
|
+
writeFileSync(marker, "");
|
|
8716
|
+
}
|
|
8717
|
+
/**
|
|
8531
8718
|
* `buildCliLogger` options derived from a loaded `holocron.config` — the
|
|
8532
8719
|
* level, the Axiom dataset, and the resolved org for a keyring-backed
|
|
8533
8720
|
* Axiom token. Used by every handler that has already called `loadConfig`.
|
|
@@ -8557,7 +8744,7 @@ function tokenContext(rawTokens) {
|
|
|
8557
8744
|
return parseTokenArgs(rawTokens);
|
|
8558
8745
|
} catch (err) {
|
|
8559
8746
|
if (err instanceof TokenParseError) {
|
|
8560
|
-
|
|
8747
|
+
reportError(`--token: ${err.message}`);
|
|
8561
8748
|
process.exitCode = 1;
|
|
8562
8749
|
return null;
|
|
8563
8750
|
}
|
|
@@ -8574,6 +8761,10 @@ try {
|
|
|
8574
8761
|
type: "boolean",
|
|
8575
8762
|
default: false,
|
|
8576
8763
|
describe: "Print what would be mutated without calling capability mutators. Commands branch on this; read-only commands ignore it."
|
|
8764
|
+
}).option("skip-dry-run", {
|
|
8765
|
+
type: "boolean",
|
|
8766
|
+
default: false,
|
|
8767
|
+
describe: "Interactive mode only: skip the automatic dry-run preview for a dry-run-capable command and run for real immediately."
|
|
8577
8768
|
}).option("token", {
|
|
8578
8769
|
type: "string",
|
|
8579
8770
|
array: true,
|
|
@@ -8597,6 +8788,10 @@ try {
|
|
|
8597
8788
|
type: "boolean",
|
|
8598
8789
|
default: false,
|
|
8599
8790
|
describe: "Set the log level to error — suppress info and warn."
|
|
8791
|
+
}).option("log", {
|
|
8792
|
+
type: "string",
|
|
8793
|
+
choices: LOG_LEVELS,
|
|
8794
|
+
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
8795
|
}).middleware((argv) => {
|
|
8601
8796
|
const name = argv._.slice(0, 2).join(" ") || "unknown";
|
|
8602
8797
|
printRunId = Boolean(argv.debug || argv.verbose);
|
|
@@ -8618,13 +8813,13 @@ try {
|
|
|
8618
8813
|
try {
|
|
8619
8814
|
token = resolveCloneToken({ cliToken: tokens.cliTokens?.["github"] ?? tokens.cliToken });
|
|
8620
8815
|
} catch (err) {
|
|
8621
|
-
|
|
8816
|
+
reportError(`clone: ${err instanceof AuthError ? err.message : String(err)}`);
|
|
8622
8817
|
process.exitCode = 1;
|
|
8623
8818
|
return;
|
|
8624
8819
|
}
|
|
8625
8820
|
const [targetInput] = await promptForPositionals(getEntry("clone"), argv);
|
|
8626
8821
|
const { org, repo } = parseCloneTarget(targetInput);
|
|
8627
|
-
|
|
8822
|
+
const report = await runClone({
|
|
8628
8823
|
org,
|
|
8629
8824
|
...repo ? { repo } : {},
|
|
8630
8825
|
token,
|
|
@@ -8637,8 +8832,20 @@ try {
|
|
|
8637
8832
|
message: `${org} has ${total} repos — ${alreadyCloned} already cloned locally, ${toClone} new. Clone them all?`,
|
|
8638
8833
|
default: false
|
|
8639
8834
|
});
|
|
8640
|
-
} } : {}
|
|
8641
|
-
|
|
8835
|
+
} } : {},
|
|
8836
|
+
confirmOpen: async (openTarget, moreToCome) => {
|
|
8837
|
+
if (!process.stdin.isTTY) return;
|
|
8838
|
+
if (moreToCome && env.get("HOLOCRON_DRY_RUN_PREVIEW") === "1") return;
|
|
8839
|
+
if (await confirm({
|
|
8840
|
+
message: `Open ${openTarget}?`,
|
|
8841
|
+
default: false
|
|
8842
|
+
})) openPath(openTarget);
|
|
8843
|
+
}
|
|
8844
|
+
});
|
|
8845
|
+
if (report.status === "fail") {
|
|
8846
|
+
if (report.message) reportError(`clone: ${report.message}`);
|
|
8847
|
+
process.exitCode = 1;
|
|
8848
|
+
} else if (report.status === "dry-run" && report.cloned === 0 && env.get("HOLOCRON_DRY_RUN_PREVIEW") === "1") process.exitCode = 2;
|
|
8642
8849
|
}).command("doctor", "Load the config and run a smoke check against every provider", (y) => y.option("repo", {
|
|
8643
8850
|
type: "string",
|
|
8644
8851
|
describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
|
|
@@ -8898,7 +9105,7 @@ try {
|
|
|
8898
9105
|
if (argv.syncTrust) {
|
|
8899
9106
|
const [oldFile, newFile] = argv.syncTrust;
|
|
8900
9107
|
if (!oldFile || !newFile) {
|
|
8901
|
-
|
|
9108
|
+
reportError("publish --sync-trust requires exactly two values: <old-file> <new-file>");
|
|
8902
9109
|
process.exitCode = 1;
|
|
8903
9110
|
return;
|
|
8904
9111
|
}
|
|
@@ -9058,7 +9265,7 @@ try {
|
|
|
9058
9265
|
else try {
|
|
9059
9266
|
token = resolveSyncToken({ cliToken: parsed.cliTokens?.["github"] ?? parsed.cliToken });
|
|
9060
9267
|
} catch (err) {
|
|
9061
|
-
|
|
9268
|
+
reportError(`sync-github: ${err instanceof AuthError ? err.message : String(err)}`);
|
|
9062
9269
|
process.exitCode = 1;
|
|
9063
9270
|
return;
|
|
9064
9271
|
}
|
|
@@ -9078,13 +9285,17 @@ try {
|
|
|
9078
9285
|
}), async (argv) => {
|
|
9079
9286
|
const loaded = await loadConfig(argv.cwd);
|
|
9080
9287
|
applyResolvedConfig(argv, loaded.resolved);
|
|
9081
|
-
|
|
9288
|
+
const report = await runSyncReadme({
|
|
9082
9289
|
loaded,
|
|
9083
9290
|
context: {
|
|
9084
9291
|
repoRoot: argv.cwd,
|
|
9085
9292
|
dryRun: argv.dryRun
|
|
9086
9293
|
}
|
|
9087
|
-
})
|
|
9294
|
+
});
|
|
9295
|
+
if (report.status === "fail") {
|
|
9296
|
+
if (report.message) reportError(`sync-readme: ${report.message}`);
|
|
9297
|
+
process.exitCode = 1;
|
|
9298
|
+
}
|
|
9088
9299
|
}).command("config show", "Print the resolved holocron config", () => {}, async (argv) => {
|
|
9089
9300
|
const loaded = await loadConfig(argv.cwd);
|
|
9090
9301
|
console.log(JSON.stringify(loaded.resolved, null, 2));
|
|
@@ -9313,12 +9524,12 @@ try {
|
|
|
9313
9524
|
}) === "yes";
|
|
9314
9525
|
if (skills.length === 0) skills = parseTopics(await input({ message: "Agent skills (comma-separated, optional):" }));
|
|
9315
9526
|
if (!type) {
|
|
9316
|
-
|
|
9527
|
+
reportError("new: template type is required");
|
|
9317
9528
|
process.exitCode = 1;
|
|
9318
9529
|
return;
|
|
9319
9530
|
}
|
|
9320
9531
|
if (!name) {
|
|
9321
|
-
|
|
9532
|
+
reportError("new: repo name is required");
|
|
9322
9533
|
process.exitCode = 1;
|
|
9323
9534
|
return;
|
|
9324
9535
|
}
|
|
@@ -9347,7 +9558,7 @@ try {
|
|
|
9347
9558
|
})).status === "fail") process.exitCode = 1;
|
|
9348
9559
|
} catch (err) {
|
|
9349
9560
|
if (err instanceof NewError) {
|
|
9350
|
-
|
|
9561
|
+
reportError(`new: ${err.message}`);
|
|
9351
9562
|
process.exitCode = 1;
|
|
9352
9563
|
return;
|
|
9353
9564
|
}
|
|
@@ -9406,7 +9617,7 @@ try {
|
|
|
9406
9617
|
}).status === "fail") process.exitCode = 1;
|
|
9407
9618
|
} catch (err) {
|
|
9408
9619
|
if (err instanceof PluginCreateError) {
|
|
9409
|
-
|
|
9620
|
+
reportError(`plugin create: ${err.message}`);
|
|
9410
9621
|
process.exitCode = 1;
|
|
9411
9622
|
return;
|
|
9412
9623
|
}
|
|
@@ -9434,7 +9645,7 @@ try {
|
|
|
9434
9645
|
extra
|
|
9435
9646
|
});
|
|
9436
9647
|
if (report.status === "fail") {
|
|
9437
|
-
if (report.message)
|
|
9648
|
+
if (report.message) reportError(`upgrade node: ${report.message}`);
|
|
9438
9649
|
process.exitCode = 1;
|
|
9439
9650
|
}
|
|
9440
9651
|
}).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 +9659,7 @@ try {
|
|
|
9448
9659
|
pinsOnly: argv.pinsOnly
|
|
9449
9660
|
});
|
|
9450
9661
|
if (report.status === "fail") {
|
|
9451
|
-
if (report.message)
|
|
9662
|
+
if (report.message) reportError(`upgrade deps: ${report.message}`);
|
|
9452
9663
|
process.exitCode = 1;
|
|
9453
9664
|
}
|
|
9454
9665
|
}).command("$0", false, () => {}, async (argv) => {
|
|
@@ -9474,25 +9685,25 @@ try {
|
|
|
9474
9685
|
}).command("$0", false, () => {}, async (argv) => {
|
|
9475
9686
|
await launchMenu(COMMAND_REGISTRY.filter((e) => e.group === "auth"), argv, "auth — choose a subcommand:", "Run `holocron auth --help` to see available auth subcommands.");
|
|
9476
9687
|
}), () => {}).command("$0", false, () => {}, async (argv) => {
|
|
9477
|
-
|
|
9688
|
+
printVersionHeaderOnce();
|
|
9478
9689
|
await launchMenu(COMMAND_REGISTRY.filter((e) => !e.group), argv);
|
|
9479
9690
|
}).strict().help().epilogue(`Execution contexts:
|
|
9480
9691
|
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
9692
|
if (err instanceof Error && USER_FACING_ERRORS.has(err.name)) {
|
|
9482
9693
|
captureException(err);
|
|
9483
|
-
|
|
9694
|
+
reportError(err.message);
|
|
9484
9695
|
errorReported = true;
|
|
9485
9696
|
process.exitCode = 1;
|
|
9486
9697
|
return;
|
|
9487
9698
|
}
|
|
9488
9699
|
if (err) throw err;
|
|
9489
|
-
|
|
9700
|
+
reportError(msg);
|
|
9490
9701
|
errorReported = true;
|
|
9491
9702
|
process.exitCode = 1;
|
|
9492
9703
|
}).parse();
|
|
9493
9704
|
} catch (err) {
|
|
9494
9705
|
captureException(err);
|
|
9495
|
-
if (!errorReported && err instanceof Error && USER_FACING_ERRORS.has(err.name))
|
|
9706
|
+
if (!errorReported && err instanceof Error && USER_FACING_ERRORS.has(err.name)) reportError(err.message);
|
|
9496
9707
|
if (!process.exitCode) process.exitCode = 1;
|
|
9497
9708
|
}
|
|
9498
9709
|
finishCommand(!process.exitCode);
|