@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 +15 -0
- package/dist/cli.mjs +230 -3
- package/dist/cli.mjs.map +1 -1
- package/package.json +7 -7
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", () =>
|
|
9377
|
-
|
|
9378
|
-
|
|
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)"
|