@theholocron/cli 3.55.1 → 3.57.0

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
@@ -13,8 +13,8 @@ import ora from "ora";
13
13
  import chalk from "chalk";
14
14
  import { execFile, execFileSync, spawnSync } from "node:child_process";
15
15
  import { homedir } from "node:os";
16
- import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
17
16
  import { createLogger, parseLogLevel } from "@theholocron/logger";
17
+ import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
18
18
  import { createHash } from "node:crypto";
19
19
  import { generateReadme } from "@theholocron/components-doc/markdown";
20
20
  import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
@@ -896,6 +896,66 @@ async function runDeploy(input) {
896
896
  }
897
897
  }
898
898
  //#endregion
899
+ //#region src/logger.ts
900
+ /**
901
+ * CLI-side wiring for `@theholocron/logger`.
902
+ *
903
+ * `logger` is the operational-output channel — internal state, debug
904
+ * traces, errors, structured context that routes to Axiom. It runs in
905
+ * parallel to `print` (user-facing UX output) and does not replace it.
906
+ */
907
+ /**
908
+ * Resolve the explicit level to hand to `createLogger`, in priority order:
909
+ *
910
+ * 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
911
+ * 3. `HOLOCRON_LOG_LEVEL` env var
912
+ * 4. `holocron.config` `log.level` (`configLevel`)
913
+ *
914
+ * Returns `undefined` when nothing applies — `createLogger` then defaults
915
+ * to `"info"`. Resolving the full chain here (rather than passing
916
+ * `configLevel` straight through) keeps config below the env var.
917
+ */
918
+ function resolveLogLevel(argv, configLevel) {
919
+ if (argv.verbose) return "debug";
920
+ if (argv.quiet) return "error";
921
+ return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
922
+ }
923
+ let root;
924
+ let rootLevel;
925
+ let rootCommand;
926
+ /**
927
+ * The process-wide root logger. Built once (from `cli.ts`'s middleware,
928
+ * with the command name + flags + env). Rebuilt at most once more when a
929
+ * command's handler supplies its `holocron.config` `log.level` — a case
930
+ * the flag/env-only first pass could not have known — as long as no
931
+ * higher-priority `--verbose` / `--quiet` already fixed the level. That
932
+ * rebuild generates a fresh `runId`, which is harmless: nothing logs
933
+ * between the middleware and the handler.
934
+ */
935
+ function buildCliLogger(argv, opts = {}) {
936
+ const { command, configLevel } = opts;
937
+ if (command) rootCommand = command;
938
+ const level = resolveLogLevel(argv, configLevel);
939
+ const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
940
+ if (!root || rebuildForConfig) {
941
+ const built = createLogger(level ? { level } : {});
942
+ root = {
943
+ logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
944
+ runId: built.runId
945
+ };
946
+ rootLevel = level;
947
+ }
948
+ return root;
949
+ }
950
+ /** Lazily-memoized `Logger` for module-level call sites with no `argv` in scope. */
951
+ function getLogger() {
952
+ return (root ??= createLogger()).logger;
953
+ }
954
+ /** The current root logger's correlation id, if a root has been built. */
955
+ function getRunId() {
956
+ return root?.runId;
957
+ }
958
+ //#endregion
899
959
  //#region src/commands/doctor.ts
900
960
  async function runDoctor(input) {
901
961
  const print = input.print ?? ((line) => console.log(line));
@@ -924,7 +984,14 @@ async function runDoctor(input) {
924
984
  rows.push(row);
925
985
  }
926
986
  }
987
+ const log = getLogger();
927
988
  for (const row of rows) {
989
+ log[row.status === "fail" ? "warn" : "info"]({
990
+ capability: row.capability,
991
+ provider: row.provider,
992
+ status: row.status,
993
+ detail: row.message
994
+ }, `doctor: ${row.capability}`);
928
995
  const label = `${pad(row.capability, 14)} via ${pad(row.provider, 14)} ${row.message}`;
929
996
  if (row.status === "ok") print(` ${style.success(label)}`);
930
997
  else if (row.status === "fail") print(` ${style.fail(label)}`);
@@ -3112,59 +3179,6 @@ var dependabot_default = "version: 2\nupdates:\n - package-ecosystem: npm\n
3112
3179
  //#region src/templates/labeler.yml
3113
3180
  var labeler_default = "bug:\n - '^fix'\n\nchore:\n - '^chore(?!\\(deps)'\n\nci:\n - '^ci'\n\ndependencies:\n - '^chore\\(deps'\n\ndocumentation:\n - '^docs'\n\nenhancement:\n - '^feat'\n\nperformance:\n - '^perf'\n\nrefactor:\n - '^refactor'\n\ntest:\n - '^test'\n";
3114
3181
  //#endregion
3115
- //#region src/logger.ts
3116
- /**
3117
- * CLI-side wiring for `@theholocron/logger`.
3118
- *
3119
- * `logger` is the operational-output channel — internal state, debug
3120
- * traces, errors, structured context that routes to Axiom. It runs in
3121
- * parallel to `print` (user-facing UX output) and does not replace it.
3122
- */
3123
- /**
3124
- * Resolve the explicit level to hand to `createLogger`, in priority order:
3125
- *
3126
- * 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
3127
- * 3. `HOLOCRON_LOG_LEVEL` env var
3128
- * 4. `holocron.config` `log.level` (`configLevel`)
3129
- *
3130
- * Returns `undefined` when nothing applies — `createLogger` then defaults
3131
- * to `"info"`. Resolving the full chain here (rather than passing
3132
- * `configLevel` straight through) keeps config below the env var.
3133
- */
3134
- function resolveLogLevel(argv, configLevel) {
3135
- if (argv.verbose) return "debug";
3136
- if (argv.quiet) return "error";
3137
- return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
3138
- }
3139
- let root;
3140
- let rootLevel;
3141
- /**
3142
- * The process-wide root logger. Built once (from `cli.ts`'s middleware,
3143
- * with flags + env only). Rebuilt at most once more when a command's
3144
- * handler supplies its `holocron.config` `log.level` — a case the
3145
- * flag/env-only first pass could not have known — as long as no
3146
- * higher-priority `--verbose` / `--quiet` already fixed the level. That
3147
- * rebuild generates a fresh `runId`, which is harmless: nothing logs
3148
- * between the middleware and the handler.
3149
- */
3150
- function buildCliLogger(argv, configLevel) {
3151
- const level = resolveLogLevel(argv, configLevel);
3152
- const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
3153
- if (!root || rebuildForConfig) {
3154
- root = createLogger(level ? { level } : {});
3155
- rootLevel = level;
3156
- }
3157
- return root;
3158
- }
3159
- /** Lazily-memoized `Logger` for module-level call sites with no `argv` in scope. */
3160
- function getLogger() {
3161
- return (root ??= createLogger()).logger;
3162
- }
3163
- /** The current root logger's correlation id, if a root has been built. */
3164
- function getRunId() {
3165
- return root?.runId;
3166
- }
3167
- //#endregion
3168
3182
  //#region src/commands/setup-workflows/index.ts
3169
3183
  /**
3170
3184
  * Thin workflow wrapper templates for `holocron setup`.
@@ -3989,6 +4003,18 @@ const BALANCED_REPO_SETTINGS = {
3989
4003
  //#endregion
3990
4004
  //#region src/commands/setup/run-step.ts
3991
4005
  async function runStep(capability, step, dryRun, body, opts = {}) {
4006
+ const result = await execStep(capability, step, dryRun, body, opts);
4007
+ const { status, message, reason } = result;
4008
+ getLogger()[status === "fail" ? "warn" : "info"]({
4009
+ capability,
4010
+ step,
4011
+ status,
4012
+ ...message ? { detail: message } : {},
4013
+ ...reason ? { reason } : {}
4014
+ }, `${capability}.${step}`);
4015
+ return result;
4016
+ }
4017
+ async function execStep(capability, step, dryRun, body, opts = {}) {
3992
4018
  if (dryRun) return {
3993
4019
  capability,
3994
4020
  step,
@@ -4629,6 +4655,29 @@ async function runSetup(input) {
4629
4655
  }
4630
4656
  print(formatStep(steps[steps.length - 1]));
4631
4657
  }
4658
+ if (loader.has("errors")) {
4659
+ const errors = loader.get("errors");
4660
+ print(style.step("errors"));
4661
+ if (errors.ensureProject) {
4662
+ let dsn;
4663
+ steps.push(await runStep("errors", `ensureProject ${config.name}`, dryRun, async () => {
4664
+ const result = await errors.ensureProject({ name: config.name });
4665
+ dsn = result.dsn;
4666
+ return `project ${result.alreadyExists ? "exists" : "created"}`;
4667
+ }));
4668
+ print(formatStep(steps[steps.length - 1]));
4669
+ if (dsn && loader.has("secrets")) {
4670
+ const secrets = loader.get("secrets");
4671
+ const { envKeys } = await errors.describe();
4672
+ for (const key of envKeys) {
4673
+ steps.push(await runStep("errors", `secrets set ${key}`, dryRun, async () => {
4674
+ await secrets.setSecret({ kind: "repo" }, key, dsn);
4675
+ }));
4676
+ print(formatStep(steps[steps.length - 1]));
4677
+ }
4678
+ }
4679
+ }
4680
+ }
4632
4681
  if (loader.has("logs")) {
4633
4682
  const logs = loader.get("logs");
4634
4683
  print(style.step("logs"));
@@ -6164,14 +6213,26 @@ async function fileExists(path) {
6164
6213
  }
6165
6214
  //#endregion
6166
6215
  //#region src/telemetry.ts
6167
- const DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
6216
+ const FALLBACK_DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
6217
+ /**
6218
+ * Resolve the Sentry DSN — the `errors` capability's self-contained
6219
+ * activation, per ADR-0007:
6220
+ *
6221
+ * HOLOCRON_SENTRY_DSN → SENTRY_DSN → built-in fallback
6222
+ *
6223
+ * A consumer repo with `SENTRY_DSN` set gets CLI errors routed to its own
6224
+ * project with no config. Opt out entirely with `HOLOCRON_TELEMETRY=false`.
6225
+ */
6226
+ function resolveDsn() {
6227
+ return env.get("HOLOCRON_SENTRY_DSN") ?? env.get("SENTRY_DSN") ?? FALLBACK_DSN;
6228
+ }
6168
6229
  function isEnabled() {
6169
- return !(env.get("HOLOCRON_TELEMETRY") === "false" || Boolean(env.get("NO_HOLOCRON_TELEMETRY"))) && true;
6230
+ return !(env.get("HOLOCRON_TELEMETRY") === "false" || Boolean(env.get("NO_HOLOCRON_TELEMETRY"))) && resolveDsn() !== "";
6170
6231
  }
6171
6232
  function init(version) {
6172
6233
  if (!isEnabled()) return;
6173
6234
  Sentry.init({
6174
- dsn: DSN,
6235
+ dsn: resolveDsn(),
6175
6236
  release: `holocron@${version}`,
6176
6237
  environment: env.get("CI") ? "ci" : "local",
6177
6238
  tracesSampleRate: 1,
@@ -6394,9 +6455,10 @@ try {
6394
6455
  default: false,
6395
6456
  describe: "Set the log level to error — suppress info and warn."
6396
6457
  }).middleware((argv) => {
6397
- finishCommand = startCommand(argv._.slice(0, 2).join(" ") || "unknown");
6458
+ const name = argv._.slice(0, 2).join(" ") || "unknown";
6459
+ finishCommand = startCommand(name);
6398
6460
  printRunId = Boolean(argv.debug || argv.verbose);
6399
- buildCliLogger(argv);
6461
+ buildCliLogger(argv, { command: name });
6400
6462
  }).command("version", "Print the CLI version", () => {}, () => {
6401
6463
  console.log(`holocron ${CLI_VERSION}`);
6402
6464
  }).command("clone", "Clone all repos in a GitHub org as siblings under a single directory", (y) => y.option("org", {
@@ -6430,7 +6492,7 @@ try {
6430
6492
  const tokens = tokenContext(argv.token);
6431
6493
  if (!tokens) return;
6432
6494
  const loaded = await loadConfig(argv.cwd);
6433
- buildCliLogger(argv, loaded.resolved.log?.level);
6495
+ buildCliLogger(argv, { configLevel: loaded.resolved.log?.level });
6434
6496
  if ((await runDoctor({
6435
6497
  loaded,
6436
6498
  context: {
@@ -6448,7 +6510,7 @@ try {
6448
6510
  const tokens = tokenContext(argv.token);
6449
6511
  if (!tokens) return;
6450
6512
  const loaded = await loadConfig(argv.cwd);
6451
- buildCliLogger(argv, loaded.resolved.log?.level);
6513
+ buildCliLogger(argv, { configLevel: loaded.resolved.log?.level });
6452
6514
  if ((await runSetup({
6453
6515
  loaded,
6454
6516
  context: {
@@ -6514,7 +6576,7 @@ try {
6514
6576
  const scopeArg = argv.scope;
6515
6577
  const scope = parseScope(scopeArg);
6516
6578
  const loaded = await loadConfig(argv.cwd);
6517
- buildCliLogger(argv, loaded.resolved.log?.level);
6579
+ buildCliLogger(argv, { configLevel: loaded.resolved.log?.level });
6518
6580
  if ((await runSecretSet({
6519
6581
  loaded,
6520
6582
  context: {
@@ -6544,7 +6606,7 @@ try {
6544
6606
  const tokens = tokenContext(argv.token);
6545
6607
  if (!tokens) return;
6546
6608
  const loaded = await loadConfig(argv.cwd);
6547
- buildCliLogger(argv, loaded.resolved.log?.level);
6609
+ buildCliLogger(argv, { configLevel: loaded.resolved.log?.level });
6548
6610
  if ((await runSecretsSync({
6549
6611
  loaded,
6550
6612
  context: {
@@ -6573,7 +6635,7 @@ try {
6573
6635
  const tokens = tokenContext(argv.token);
6574
6636
  if (!tokens) return;
6575
6637
  const loaded = await loadConfig(argv.cwd);
6576
- buildCliLogger(argv, loaded.resolved.log?.level);
6638
+ buildCliLogger(argv, { configLevel: loaded.resolved.log?.level });
6577
6639
  if ((await runDeploy({
6578
6640
  loaded,
6579
6641
  context: {
@@ -6601,7 +6663,7 @@ try {
6601
6663
  const tokens = tokenContext(argv.token);
6602
6664
  if (!tokens) return;
6603
6665
  const loaded = await loadConfig(argv.cwd);
6604
- buildCliLogger(argv, loaded.resolved.log?.level);
6666
+ buildCliLogger(argv, { configLevel: loaded.resolved.log?.level });
6605
6667
  if ((await runCleanupPreview({
6606
6668
  loaded,
6607
6669
  context: {
@@ -6649,7 +6711,7 @@ try {
6649
6711
  const tokens = tokenContext(argv.token);
6650
6712
  if (!tokens) return;
6651
6713
  const loaded = await loadConfig(argv.cwd);
6652
- buildCliLogger(argv, loaded.resolved.log?.level);
6714
+ buildCliLogger(argv, { configLevel: loaded.resolved.log?.level });
6653
6715
  if ((await runSync({
6654
6716
  loaded,
6655
6717
  context: {
@@ -6706,7 +6768,7 @@ try {
6706
6768
  default: false
6707
6769
  }), async (argv) => {
6708
6770
  const loaded = await loadConfig(argv.cwd);
6709
- buildCliLogger(argv, loaded.resolved.log?.level);
6771
+ buildCliLogger(argv, { configLevel: loaded.resolved.log?.level });
6710
6772
  if ((await runSyncReadme({
6711
6773
  loaded,
6712
6774
  context: {