@theholocron/cli 5.0.0-alpha.126 → 5.0.0-alpha.128

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 CHANGED
@@ -66,6 +66,21 @@ use `pnpm exec holocron <cmd>`. `auth check` degrades to "token present,
66
66
  verification skipped" instead. `holocron --help` prints the grouping; see
67
67
  [Execution Contexts](https://theholocron.github.io/holocron/execution-contexts).
68
68
 
69
+ ### Viewing the resolved config
70
+
71
+ `holocron config show` prints the resolved config. On a TTY it opens a foldable
72
+ tree by default; piped or redirected, it always emits plain, valid JSON
73
+ (`holocron config show | jq`).
74
+
75
+ ```console
76
+ holocron config show # TTY: foldable tree (↑↓ move, →/⏎ expand, ← collapse, E/C all, q quit)
77
+ holocron config show --depth 2 # static coloured tree, anything nested deeper than 2 levels collapsed
78
+ holocron config show --no-interactive # static coloured tree, fully expanded
79
+ ```
80
+
81
+ Passing `--interactive` explicitly needs a TTY on stdin and stdout and fails
82
+ with a one-line message otherwise; the default falls back to static output. It has no extra dependencies — nothing to install.
83
+
69
84
  ## Config file
70
85
 
71
86
  Holocron reads `holocron.config.{json,js,ts}` from the project root
package/dist/cli.mjs CHANGED
@@ -8599,6 +8599,225 @@ async function launchMenu(entries, parentArgv, pickMessage, nonInteractiveMessag
8599
8599
  process.exitCode = await spawnChild(buildChildArgv(picked, positionals, parentArgv));
8600
8600
  }
8601
8601
  //#endregion
8602
+ //#region src/ui/json-view.ts
8603
+ function isContainer(value) {
8604
+ return typeof value === "object" && value !== null;
8605
+ }
8606
+ function childrenOf(value) {
8607
+ return Array.isArray(value) ? value.map((v, i) => [String(i), v]) : Object.entries(value);
8608
+ }
8609
+ function scalar(value) {
8610
+ if (typeof value === "string") return chalk.green(JSON.stringify(value));
8611
+ if (typeof value === "number") return chalk.yellow(String(value));
8612
+ if (typeof value === "boolean") return chalk.magenta(String(value));
8613
+ return chalk.dim(String(value === void 0 ? null : value));
8614
+ }
8615
+ function summary(value) {
8616
+ const n = childrenOf(value).length;
8617
+ const unit = Array.isArray(value) ? n === 1 ? "item" : "items" : n === 1 ? "key" : "keys";
8618
+ return chalk.dim(Array.isArray(value) ? `[… ${n} ${unit}]` : `{… ${n} ${unit}}`);
8619
+ }
8620
+ /**
8621
+ * Paths of every container deeper than `depth` levels are left out, so those
8622
+ * render collapsed. `depth: 0` collapses the root; `Infinity` expands all.
8623
+ */
8624
+ function expandToDepth(value, depth) {
8625
+ const out = /* @__PURE__ */ new Set();
8626
+ const walk = (v, path, level) => {
8627
+ if (!isContainer(v) || level >= depth) return;
8628
+ out.add(path);
8629
+ for (const [k, child] of childrenOf(v)) walk(child, `${path}/${k}`, level + 1);
8630
+ };
8631
+ walk(value, "", 0);
8632
+ return out;
8633
+ }
8634
+ /** Flatten `value` into the lines visible under `expanded`. Pure — no I/O. */
8635
+ function renderTree(value, { expanded }) {
8636
+ const lines = [];
8637
+ const walk = (v, path, level, label, trailing) => {
8638
+ const indent = " ".repeat(level);
8639
+ const prefix = `${indent}${label}`;
8640
+ if (!isContainer(v)) {
8641
+ lines.push({
8642
+ path,
8643
+ text: `${prefix}${scalar(v)}${trailing}`,
8644
+ foldable: false,
8645
+ expanded: false,
8646
+ level
8647
+ });
8648
+ return;
8649
+ }
8650
+ const kids = childrenOf(v);
8651
+ const [open, close] = Array.isArray(v) ? ["[", "]"] : ["{", "}"];
8652
+ if (kids.length === 0) {
8653
+ lines.push({
8654
+ path,
8655
+ text: `${prefix}${open}${close}${trailing}`,
8656
+ foldable: false,
8657
+ expanded: false,
8658
+ level
8659
+ });
8660
+ return;
8661
+ }
8662
+ if (!expanded.has(path)) {
8663
+ lines.push({
8664
+ path,
8665
+ text: `${prefix}${summary(v)}${trailing}`,
8666
+ foldable: true,
8667
+ expanded: false,
8668
+ level
8669
+ });
8670
+ return;
8671
+ }
8672
+ lines.push({
8673
+ path,
8674
+ text: `${prefix}${open}`,
8675
+ foldable: true,
8676
+ expanded: true,
8677
+ level
8678
+ });
8679
+ kids.forEach(([k, child], i) => {
8680
+ const comma = i < kids.length - 1 ? "," : "";
8681
+ const childLabel = Array.isArray(v) ? "" : `${chalk.cyan(JSON.stringify(k))}: `;
8682
+ walk(child, `${path}/${k}`, level + 1, childLabel, comma);
8683
+ });
8684
+ lines.push({
8685
+ path: `${path}#close`,
8686
+ text: `${indent}${close}${trailing}`,
8687
+ foldable: false,
8688
+ expanded: false,
8689
+ level
8690
+ });
8691
+ };
8692
+ walk(value, "", 0, "", "");
8693
+ return lines;
8694
+ }
8695
+ /** Pure key reducer: `(state, key) -> state`. `rows` is the viewport height. */
8696
+ function reduceKey(state, key, value, rows) {
8697
+ const lines = renderTree(value, { expanded: state.expanded });
8698
+ const cur = lines[state.cursor];
8699
+ let { cursor } = state;
8700
+ const expanded = new Set(state.expanded);
8701
+ switch (key) {
8702
+ case "up":
8703
+ cursor = Math.max(0, cursor - 1);
8704
+ break;
8705
+ case "down":
8706
+ cursor = Math.min(lines.length - 1, cursor + 1);
8707
+ break;
8708
+ case "right":
8709
+ if (cur?.foldable) expanded.add(cur.path);
8710
+ break;
8711
+ case "left":
8712
+ if (cur?.foldable && cur.expanded) expanded.delete(cur.path);
8713
+ else if (cur) {
8714
+ const parent = lines.findLastIndex((l, i) => i < cursor && l.foldable && l.expanded && l.level < cur.level);
8715
+ if (parent >= 0) cursor = parent;
8716
+ }
8717
+ break;
8718
+ case "expandAll": return {
8719
+ cursor,
8720
+ top: state.top,
8721
+ expanded: expandToDepth(value, Infinity)
8722
+ };
8723
+ case "collapseAll": return {
8724
+ cursor: 0,
8725
+ top: 0,
8726
+ expanded: /* @__PURE__ */ new Set()
8727
+ };
8728
+ }
8729
+ const count = renderTree(value, { expanded }).length;
8730
+ cursor = Math.min(cursor, count - 1);
8731
+ let top = state.top;
8732
+ if (cursor < top) top = cursor;
8733
+ if (cursor >= top + rows) top = cursor - rows + 1;
8734
+ return {
8735
+ cursor,
8736
+ top: Math.max(0, top),
8737
+ expanded
8738
+ };
8739
+ }
8740
+ const KEYMAP = {
8741
+ "\x1B[A": "up",
8742
+ k: "up",
8743
+ "\x1B[B": "down",
8744
+ j: "down",
8745
+ "\x1B[C": "right",
8746
+ l: "right",
8747
+ "\r": "right",
8748
+ " ": "right",
8749
+ "\x1B[D": "left",
8750
+ h: "left",
8751
+ E: "expandAll",
8752
+ C: "collapseAll",
8753
+ q: "quit",
8754
+ "\x1B": "quit",
8755
+ "": "quit"
8756
+ };
8757
+ function draw(out, value, state, rows) {
8758
+ const lines = renderTree(value, { expanded: state.expanded });
8759
+ const view = lines.slice(state.top, state.top + rows).map((l, i) => {
8760
+ const row = `${l.foldable ? l.expanded ? "▾ " : "▸ " : " "}${l.text}`;
8761
+ return state.top + i === state.cursor ? chalk.inverse(row) : row;
8762
+ });
8763
+ const footer = chalk.dim(`${state.cursor + 1}/${lines.length} ↑↓ move →/⏎ expand ← collapse E/C all q quit`);
8764
+ out.write(`\u001b[H\u001b[J${view.join("\n")}\n${footer}`);
8765
+ }
8766
+ async function runInteractive(value, depth) {
8767
+ const { stdin, stdout } = process;
8768
+ if (!stdin.isTTY || !stdout.isTTY) throw new NonInteractiveError("--interactive needs a TTY on stdin and stdout.");
8769
+ const rows = () => Math.max(3, (stdout.rows ?? 24) - 1);
8770
+ let state = {
8771
+ cursor: 0,
8772
+ top: 0,
8773
+ expanded: expandToDepth(value, depth)
8774
+ };
8775
+ const wasRaw = stdin.isRaw;
8776
+ stdin.setRawMode(true);
8777
+ stdin.resume();
8778
+ stdout.write("\x1B[?1049h\x1B[?25l");
8779
+ const redraw = () => draw(stdout, value, state, rows());
8780
+ stdout.on("resize", redraw);
8781
+ try {
8782
+ redraw();
8783
+ await new Promise((resolve) => {
8784
+ const onData = (buf) => {
8785
+ const key = KEYMAP[buf.toString()];
8786
+ if (key === "quit") {
8787
+ stdin.off("data", onData);
8788
+ resolve();
8789
+ return;
8790
+ }
8791
+ if (key) {
8792
+ state = reduceKey(state, key, value, rows());
8793
+ redraw();
8794
+ }
8795
+ };
8796
+ stdin.on("data", onData);
8797
+ });
8798
+ } finally {
8799
+ stdout.off("resize", redraw);
8800
+ stdout.write("\x1B[?25h\x1B[?1049l");
8801
+ stdin.setRawMode(wasRaw);
8802
+ stdin.pause();
8803
+ }
8804
+ }
8805
+ /**
8806
+ * Print `value` as JSON. Non-TTY stdout always gets plain, valid
8807
+ * `JSON.stringify` output (so `| jq` and redirects keep working); a TTY gets a
8808
+ * coloured tree, collapsed past `depth`. `interactive` opens a folding viewer (the default on a TTY).
8809
+ */
8810
+ async function viewJson(value, options = {}) {
8811
+ const depth = options.depth ?? Infinity;
8812
+ if (options.interactive ?? (options.depth === void 0 && Boolean(process.stdin.isTTY && process.stdout.isTTY))) return runInteractive(value, options.depth ?? 1);
8813
+ if (!process.stdout.isTTY) {
8814
+ console.log(JSON.stringify(value, null, 2));
8815
+ return;
8816
+ }
8817
+ const lines = renderTree(value, { expanded: expandToDepth(value, depth) });
8818
+ console.log(lines.map((l) => l.text).join("\n"));
8819
+ }
8820
+ //#endregion
8602
8821
  //#region src/ui/open-path.ts
8603
8822
  /** Platform-appropriate command to open a path in the OS's file browser. */
8604
8823
  function openCommand(path) {
@@ -9373,9 +9592,17 @@ try {
9373
9592
  if (report.message) reportError(`sync-readme: ${report.message}`);
9374
9593
  process.exitCode = 1;
9375
9594
  }
9376
- }).command("config show", "Print the resolved holocron config", () => {}, async (argv) => {
9377
- const loaded = await loadConfig(argv.cwd);
9378
- console.log(JSON.stringify(loaded.resolved, null, 2));
9595
+ }).command("config show", "Print the resolved holocron config", (y) => y.option("depth", {
9596
+ type: "number",
9597
+ describe: "Collapse objects/arrays nested deeper than N levels (TTY output only)"
9598
+ }).option("interactive", {
9599
+ type: "boolean",
9600
+ describe: "Browse the config in a foldable tree (default on a TTY; --no-interactive for static output)"
9601
+ }), async (argv) => {
9602
+ await viewJson((await loadConfig(argv.cwd)).resolved, {
9603
+ depth: argv.depth,
9604
+ interactive: argv.interactive
9605
+ });
9379
9606
  }).command("new [type] [name]", "Scaffold a new repo from a GitHub template (e.g. cli, react, nextjs, node, monorepo, base)", (y) => y.positional("type", {
9380
9607
  type: "string",
9381
9608
  describe: "Template type — maps to theholocron/<type>-template (e.g. cli, react, nextjs, node, monorepo, base)"