@theholocron/cli 5.0.0-alpha.127 → 5.0.0-alpha.129
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 -10
- package/dist/cli.mjs +285 -54
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -54,11 +54,11 @@ entirely, straight to the real run, for when you already know what you want.
|
|
|
54
54
|
|
|
55
55
|
Every command is tagged with how much of a repo it needs:
|
|
56
56
|
|
|
57
|
-
| Context | Needs | Examples
|
|
58
|
-
| ------------ | -------------------------------------------------------- |
|
|
59
|
-
| `global` | the CLI binary alone | `version`, `clone`, `new`, `upgrade node`, `auth set` / `check`, `plugin create`, `skills …` |
|
|
60
|
-
| `repo-aware` | `./holocron.config`, walking up from cwd, no plugins | `run`, `ci`, `config show`, `sync-readme`
|
|
61
|
-
| `workspace` | the `@theholocron/holocron-plugin-*` packages resolvable | `doctor`, `setup`, `sync`, `secrets sync`, `deploy`
|
|
57
|
+
| Context | Needs | Examples |
|
|
58
|
+
| ------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `global` | the CLI binary alone | `version`, `clone`, `new`, `upgrade node`, `auth set` / `check`, `plugin create`, `run lint commit-msg`, `skills …` |
|
|
60
|
+
| `repo-aware` | `./holocron.config`, walking up from cwd, no plugins | `run`, `run ci`, `config show`, `sync-readme` |
|
|
61
|
+
| `workspace` | the `@theholocron/holocron-plugin-*` packages resolvable | `doctor`, `setup`, `sync`, `secrets sync`, `deploy` |
|
|
62
62
|
|
|
63
63
|
A `workspace` command run from a bare global install (no plugins next to the
|
|
64
64
|
CLI) fails with one actionable line — install the plugins as devDependencies or
|
|
@@ -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
|
|
@@ -237,7 +252,7 @@ export default defineConfig({
|
|
|
237
252
|
});
|
|
238
253
|
```
|
|
239
254
|
|
|
240
|
-
`holocron setup` writes `.husky/pre-push` (runs `holocron ci` before every
|
|
255
|
+
`holocron setup` writes `.husky/pre-push` (runs `holocron run ci` before every
|
|
241
256
|
push) and `.husky/pre-commit` (runs `gitleaks protect --staged` + `lint-staged`
|
|
242
257
|
before every commit — scoped to staged changes, not the whole history, so a
|
|
243
258
|
past false positive can't permanently block future commits) and sets
|
|
@@ -283,7 +298,7 @@ export default defineConfig({
|
|
|
283
298
|
`@theholocron/astromech`'s `loadTasksConfig` merges it over
|
|
284
299
|
`holocron.config`'s `tasks` — **task arrays concatenate**
|
|
285
300
|
(`holocron.config` first), the dedicated file **wins on scalars**,
|
|
286
|
-
`extraRequiredChecks` concatenate. `holocron run` / `holocron ci` /
|
|
301
|
+
`extraRequiredChecks` concatenate. `holocron run` / `holocron run ci` /
|
|
287
302
|
`holocron setup` / `holocron sync` all read the merged result, so the
|
|
288
303
|
split is transparent. Useful for keeping the capability/provider config
|
|
289
304
|
and the "what this repo runs" manifest in separate files (this repo does).
|
|
@@ -328,10 +343,10 @@ delivery.deploy`. A package that doesn't apply is skipped; a failed deploy
|
|
|
328
343
|
exits non-zero. The shared `delivery.publish` workflow runs it as its own
|
|
329
344
|
`deploy` job after the release job (opt in with `deploy-on-release: true`).
|
|
330
345
|
|
|
331
|
-
### `holocron ci`
|
|
346
|
+
### `holocron run ci`
|
|
332
347
|
|
|
333
348
|
```bash
|
|
334
|
-
holocron ci [--all] [--filter <pkg>] [--dry-run]
|
|
349
|
+
holocron run ci [--all] [--filter <pkg>] [--dry-run]
|
|
335
350
|
```
|
|
336
351
|
|
|
337
352
|
Runs the merge-gating checks locally, in CI order, exiting non-zero on the
|
|
@@ -451,7 +466,7 @@ export default acmeConfig;
|
|
|
451
466
|
shape (`const { repo, workflows } = node()` + `repo.requiredChecks` +
|
|
452
467
|
`workflows:`) to the current composed-preset API (`const preset = node()` +
|
|
453
468
|
`...preset` + `tasks:` + `extraRequiredChecks:`). Mechanical transforms only —
|
|
454
|
-
review the result, then `pnpm install && holocron ci`. `--pins-only` skips
|
|
469
|
+
review the result, then `pnpm install && holocron run ci`. `--pins-only` skips
|
|
455
470
|
the config migration; `--dry-run` prints without writing.
|
|
456
471
|
|
|
457
472
|
## Auth — fine-grained tokens
|
package/dist/cli.mjs
CHANGED
|
@@ -330,9 +330,9 @@ const COMMAND_CONTEXTS = {
|
|
|
330
330
|
"skills install": "global",
|
|
331
331
|
"skills remove": "global",
|
|
332
332
|
"skills update": "global",
|
|
333
|
-
"lint commit-msg": "global",
|
|
333
|
+
"run lint commit-msg": "global",
|
|
334
334
|
run: "repo-aware",
|
|
335
|
-
ci: "repo-aware",
|
|
335
|
+
"run ci": "repo-aware",
|
|
336
336
|
"deploy-on-release": "repo-aware",
|
|
337
337
|
"config show": "repo-aware",
|
|
338
338
|
"sync-readme": "repo-aware",
|
|
@@ -4521,12 +4521,12 @@ function createIgnoreConfig$1() {
|
|
|
4521
4521
|
}
|
|
4522
4522
|
//#endregion
|
|
4523
4523
|
//#region src/templates/configs/commit-msg/commit-msg
|
|
4524
|
-
var commit_msg_default = "__HOLOCRON_SCRIPT__ lint commit-msg \"$1\"\n";
|
|
4524
|
+
var commit_msg_default = "__HOLOCRON_SCRIPT__ run lint commit-msg \"$1\"\n";
|
|
4525
4525
|
//#endregion
|
|
4526
4526
|
//#region src/templates/configs/commit-msg/create-config.ts
|
|
4527
4527
|
const { workflowHeader: workflowHeader$8 } = createHeader({ source: "packages/cli/src/templates/configs/commit-msg/create-config.ts" });
|
|
4528
4528
|
/**
|
|
4529
|
-
* `.husky/commit-msg` — lints the commit message via `holocron lint
|
|
4529
|
+
* `.husky/commit-msg` — lints the commit message via `holocron run lint
|
|
4530
4530
|
* commit-msg` (holocron#789), the real programmatic replacement for
|
|
4531
4531
|
* shelling out to `commitlint --edit`. Installed by `holocron setup` when
|
|
4532
4532
|
* hooks are enabled. Same `__HOLOCRON_SCRIPT__` substitution as `pre-push`.
|
|
@@ -4623,12 +4623,12 @@ function createConfig$3() {
|
|
|
4623
4623
|
}
|
|
4624
4624
|
//#endregion
|
|
4625
4625
|
//#region src/templates/configs/pre-push/pre-push
|
|
4626
|
-
var pre_push_default = "# Run the merge-gating checks locally before pushing — the same set CI runs.\n# Bypass once with: git push --no-verify\n\n# CI runners (semantic-release, super-linter auto-commit, bots) push without a\n# built dev environment — the hook has nothing to add there.\n[ -n \"$CI\" ] && exit 0\n\n__HOLOCRON_SCRIPT__ ci\n";
|
|
4626
|
+
var pre_push_default = "# Run the merge-gating checks locally before pushing — the same set CI runs.\n# Bypass once with: git push --no-verify\n\n# CI runners (semantic-release, super-linter auto-commit, bots) push without a\n# built dev environment — the hook has nothing to add there.\n[ -n \"$CI\" ] && exit 0\n\n__HOLOCRON_SCRIPT__ run ci\n";
|
|
4627
4627
|
//#endregion
|
|
4628
4628
|
//#region src/templates/configs/pre-push/create-config.ts
|
|
4629
4629
|
const { workflowHeader: workflowHeader$4 } = createHeader({ source: "packages/cli/src/templates/configs/pre-push/create-config.ts" });
|
|
4630
4630
|
/**
|
|
4631
|
-
* `.husky/pre-push` — runs `holocron ci` (the merge-gating checks, in CI order)
|
|
4631
|
+
* `.husky/pre-push` — runs `holocron run ci` (the merge-gating checks, in CI order)
|
|
4632
4632
|
* before every push. Installed by `holocron setup` when hooks are enabled
|
|
4633
4633
|
* (default for `protection: "strict"`). `git push --no-verify` bypasses it.
|
|
4634
4634
|
*
|
|
@@ -5647,7 +5647,7 @@ async function runSetup(input) {
|
|
|
5647
5647
|
if (hooksEnabled) {
|
|
5648
5648
|
steps.push(await runStep("source", "write .husky/pre-push", dryRun, async () => {
|
|
5649
5649
|
await source.writeRepoFile(".husky/pre-push", createConfig$2(config.holocronScript));
|
|
5650
|
-
return "runs `holocron ci` before push — bypass with `git push --no-verify`";
|
|
5650
|
+
return "runs `holocron run ci` before push — bypass with `git push --no-verify`";
|
|
5651
5651
|
}));
|
|
5652
5652
|
print(formatStep(steps[steps.length - 1]));
|
|
5653
5653
|
steps.push(await runStep("source", "write .husky/pre-commit", dryRun, async () => {
|
|
@@ -7614,7 +7614,7 @@ async function runSyncGithub(input) {
|
|
|
7614
7614
|
* This applies the mechanical transforms and flags anything that needs a
|
|
7615
7615
|
* human eye.
|
|
7616
7616
|
*
|
|
7617
|
-
* After running, `pnpm install && holocron ci`.
|
|
7617
|
+
* After running, `pnpm install && holocron run ci`.
|
|
7618
7618
|
*/
|
|
7619
7619
|
const FETCH_TIMEOUT_MS$1 = 4e3;
|
|
7620
7620
|
/** Resolve a package's `latest` dist-tag from the npm registry. */
|
|
@@ -7681,7 +7681,7 @@ function isLegacyConfig(content) {
|
|
|
7681
7681
|
}
|
|
7682
7682
|
/**
|
|
7683
7683
|
* Apply the mechanical 7.x → 8.x transforms to a `holocron.config.ts`. Best
|
|
7684
|
-
* effort — the result should always be reviewed and run through `holocron ci`.
|
|
7684
|
+
* effort — the result should always be reviewed and run through `holocron run ci`.
|
|
7685
7685
|
*/
|
|
7686
7686
|
function migrateConfig(content) {
|
|
7687
7687
|
const transforms = [];
|
|
@@ -7807,7 +7807,7 @@ async function runUpgradeDeps(input = {}) {
|
|
|
7807
7807
|
print("");
|
|
7808
7808
|
if (bumps.length === 0 && !configMigrated) print("Nothing to do — already up to date.");
|
|
7809
7809
|
else {
|
|
7810
|
-
print(`Next: ${dryRun ? "run without --dry-run, then " : ""}\`pnpm install && holocron ci\``);
|
|
7810
|
+
print(`Next: ${dryRun ? "run without --dry-run, then " : ""}\`pnpm install && holocron run ci\``);
|
|
7811
7811
|
if (configMigrated) print(" review holocron.config.ts before committing.");
|
|
7812
7812
|
}
|
|
7813
7813
|
const status = dryRun ? "dry-run" : "ok";
|
|
@@ -8050,7 +8050,7 @@ async function lintCommitMessage(message, loaded) {
|
|
|
8050
8050
|
//#endregion
|
|
8051
8051
|
//#region src/commit-lint/lint-commit-msg-file.ts
|
|
8052
8052
|
/**
|
|
8053
|
-
* `holocron lint commit-msg <file>` — a real programmatic replacement for
|
|
8053
|
+
* `holocron run lint commit-msg <file>` — a real programmatic replacement for
|
|
8054
8054
|
* shelling out to `commitlint --edit "$1"` from `.husky/commit-msg`
|
|
8055
8055
|
* (holocron#789). Matches that CLI's own contract exactly: `file` is a
|
|
8056
8056
|
* path to the commit message being written (the arg git's `commit-msg`
|
|
@@ -8194,9 +8194,10 @@ const numeric = (value) => /^\d+$/.test(value) ? true : "enter a number";
|
|
|
8194
8194
|
* The full command surface, hand-maintained in parallel with `cli.ts`'s
|
|
8195
8195
|
* yargs registrations (this module does not introspect yargs at runtime).
|
|
8196
8196
|
* `run <task>` is deliberately excluded — it's CI/scripting-oriented, and an
|
|
8197
|
-
* interactive prompt in front of it would work against that. `
|
|
8198
|
-
*
|
|
8199
|
-
*
|
|
8197
|
+
* interactive prompt in front of it would work against that. (`run ci` is a
|
|
8198
|
+
* concrete, prompt-free entry, so it stays.) `run lint commit-msg <file>` is
|
|
8199
|
+
* excluded for the same reason: it's invoked exclusively from
|
|
8200
|
+
* `.husky/commit-msg "$1"`, never typed by a human.
|
|
8200
8201
|
*/
|
|
8201
8202
|
const COMMAND_REGISTRY = [
|
|
8202
8203
|
{
|
|
@@ -8287,7 +8288,7 @@ const COMMAND_REGISTRY = [
|
|
|
8287
8288
|
supportsDryRun: true
|
|
8288
8289
|
},
|
|
8289
8290
|
{
|
|
8290
|
-
name: "ci",
|
|
8291
|
+
name: "run ci",
|
|
8291
8292
|
description: "Run the merge-gating checks locally, in CI order — 'will CI pass?'",
|
|
8292
8293
|
positionals: [],
|
|
8293
8294
|
supportsDryRun: true
|
|
@@ -8599,6 +8600,225 @@ async function launchMenu(entries, parentArgv, pickMessage, nonInteractiveMessag
|
|
|
8599
8600
|
process.exitCode = await spawnChild(buildChildArgv(picked, positionals, parentArgv));
|
|
8600
8601
|
}
|
|
8601
8602
|
//#endregion
|
|
8603
|
+
//#region src/ui/json-view.ts
|
|
8604
|
+
function isContainer(value) {
|
|
8605
|
+
return typeof value === "object" && value !== null;
|
|
8606
|
+
}
|
|
8607
|
+
function childrenOf(value) {
|
|
8608
|
+
return Array.isArray(value) ? value.map((v, i) => [String(i), v]) : Object.entries(value);
|
|
8609
|
+
}
|
|
8610
|
+
function scalar(value) {
|
|
8611
|
+
if (typeof value === "string") return chalk.green(JSON.stringify(value));
|
|
8612
|
+
if (typeof value === "number") return chalk.yellow(String(value));
|
|
8613
|
+
if (typeof value === "boolean") return chalk.magenta(String(value));
|
|
8614
|
+
return chalk.dim(String(value === void 0 ? null : value));
|
|
8615
|
+
}
|
|
8616
|
+
function summary(value) {
|
|
8617
|
+
const n = childrenOf(value).length;
|
|
8618
|
+
const unit = Array.isArray(value) ? n === 1 ? "item" : "items" : n === 1 ? "key" : "keys";
|
|
8619
|
+
return chalk.dim(Array.isArray(value) ? `[… ${n} ${unit}]` : `{… ${n} ${unit}}`);
|
|
8620
|
+
}
|
|
8621
|
+
/**
|
|
8622
|
+
* Paths of every container deeper than `depth` levels are left out, so those
|
|
8623
|
+
* render collapsed. `depth: 0` collapses the root; `Infinity` expands all.
|
|
8624
|
+
*/
|
|
8625
|
+
function expandToDepth(value, depth) {
|
|
8626
|
+
const out = /* @__PURE__ */ new Set();
|
|
8627
|
+
const walk = (v, path, level) => {
|
|
8628
|
+
if (!isContainer(v) || level >= depth) return;
|
|
8629
|
+
out.add(path);
|
|
8630
|
+
for (const [k, child] of childrenOf(v)) walk(child, `${path}/${k}`, level + 1);
|
|
8631
|
+
};
|
|
8632
|
+
walk(value, "", 0);
|
|
8633
|
+
return out;
|
|
8634
|
+
}
|
|
8635
|
+
/** Flatten `value` into the lines visible under `expanded`. Pure — no I/O. */
|
|
8636
|
+
function renderTree(value, { expanded }) {
|
|
8637
|
+
const lines = [];
|
|
8638
|
+
const walk = (v, path, level, label, trailing) => {
|
|
8639
|
+
const indent = " ".repeat(level);
|
|
8640
|
+
const prefix = `${indent}${label}`;
|
|
8641
|
+
if (!isContainer(v)) {
|
|
8642
|
+
lines.push({
|
|
8643
|
+
path,
|
|
8644
|
+
text: `${prefix}${scalar(v)}${trailing}`,
|
|
8645
|
+
foldable: false,
|
|
8646
|
+
expanded: false,
|
|
8647
|
+
level
|
|
8648
|
+
});
|
|
8649
|
+
return;
|
|
8650
|
+
}
|
|
8651
|
+
const kids = childrenOf(v);
|
|
8652
|
+
const [open, close] = Array.isArray(v) ? ["[", "]"] : ["{", "}"];
|
|
8653
|
+
if (kids.length === 0) {
|
|
8654
|
+
lines.push({
|
|
8655
|
+
path,
|
|
8656
|
+
text: `${prefix}${open}${close}${trailing}`,
|
|
8657
|
+
foldable: false,
|
|
8658
|
+
expanded: false,
|
|
8659
|
+
level
|
|
8660
|
+
});
|
|
8661
|
+
return;
|
|
8662
|
+
}
|
|
8663
|
+
if (!expanded.has(path)) {
|
|
8664
|
+
lines.push({
|
|
8665
|
+
path,
|
|
8666
|
+
text: `${prefix}${summary(v)}${trailing}`,
|
|
8667
|
+
foldable: true,
|
|
8668
|
+
expanded: false,
|
|
8669
|
+
level
|
|
8670
|
+
});
|
|
8671
|
+
return;
|
|
8672
|
+
}
|
|
8673
|
+
lines.push({
|
|
8674
|
+
path,
|
|
8675
|
+
text: `${prefix}${open}`,
|
|
8676
|
+
foldable: true,
|
|
8677
|
+
expanded: true,
|
|
8678
|
+
level
|
|
8679
|
+
});
|
|
8680
|
+
kids.forEach(([k, child], i) => {
|
|
8681
|
+
const comma = i < kids.length - 1 ? "," : "";
|
|
8682
|
+
const childLabel = Array.isArray(v) ? "" : `${chalk.cyan(JSON.stringify(k))}: `;
|
|
8683
|
+
walk(child, `${path}/${k}`, level + 1, childLabel, comma);
|
|
8684
|
+
});
|
|
8685
|
+
lines.push({
|
|
8686
|
+
path: `${path}#close`,
|
|
8687
|
+
text: `${indent}${close}${trailing}`,
|
|
8688
|
+
foldable: false,
|
|
8689
|
+
expanded: false,
|
|
8690
|
+
level
|
|
8691
|
+
});
|
|
8692
|
+
};
|
|
8693
|
+
walk(value, "", 0, "", "");
|
|
8694
|
+
return lines;
|
|
8695
|
+
}
|
|
8696
|
+
/** Pure key reducer: `(state, key) -> state`. `rows` is the viewport height. */
|
|
8697
|
+
function reduceKey(state, key, value, rows) {
|
|
8698
|
+
const lines = renderTree(value, { expanded: state.expanded });
|
|
8699
|
+
const cur = lines[state.cursor];
|
|
8700
|
+
let { cursor } = state;
|
|
8701
|
+
const expanded = new Set(state.expanded);
|
|
8702
|
+
switch (key) {
|
|
8703
|
+
case "up":
|
|
8704
|
+
cursor = Math.max(0, cursor - 1);
|
|
8705
|
+
break;
|
|
8706
|
+
case "down":
|
|
8707
|
+
cursor = Math.min(lines.length - 1, cursor + 1);
|
|
8708
|
+
break;
|
|
8709
|
+
case "right":
|
|
8710
|
+
if (cur?.foldable) expanded.add(cur.path);
|
|
8711
|
+
break;
|
|
8712
|
+
case "left":
|
|
8713
|
+
if (cur?.foldable && cur.expanded) expanded.delete(cur.path);
|
|
8714
|
+
else if (cur) {
|
|
8715
|
+
const parent = lines.findLastIndex((l, i) => i < cursor && l.foldable && l.expanded && l.level < cur.level);
|
|
8716
|
+
if (parent >= 0) cursor = parent;
|
|
8717
|
+
}
|
|
8718
|
+
break;
|
|
8719
|
+
case "expandAll": return {
|
|
8720
|
+
cursor,
|
|
8721
|
+
top: state.top,
|
|
8722
|
+
expanded: expandToDepth(value, Infinity)
|
|
8723
|
+
};
|
|
8724
|
+
case "collapseAll": return {
|
|
8725
|
+
cursor: 0,
|
|
8726
|
+
top: 0,
|
|
8727
|
+
expanded: /* @__PURE__ */ new Set()
|
|
8728
|
+
};
|
|
8729
|
+
}
|
|
8730
|
+
const count = renderTree(value, { expanded }).length;
|
|
8731
|
+
cursor = Math.min(cursor, count - 1);
|
|
8732
|
+
let top = state.top;
|
|
8733
|
+
if (cursor < top) top = cursor;
|
|
8734
|
+
if (cursor >= top + rows) top = cursor - rows + 1;
|
|
8735
|
+
return {
|
|
8736
|
+
cursor,
|
|
8737
|
+
top: Math.max(0, top),
|
|
8738
|
+
expanded
|
|
8739
|
+
};
|
|
8740
|
+
}
|
|
8741
|
+
const KEYMAP = {
|
|
8742
|
+
"\x1B[A": "up",
|
|
8743
|
+
k: "up",
|
|
8744
|
+
"\x1B[B": "down",
|
|
8745
|
+
j: "down",
|
|
8746
|
+
"\x1B[C": "right",
|
|
8747
|
+
l: "right",
|
|
8748
|
+
"\r": "right",
|
|
8749
|
+
" ": "right",
|
|
8750
|
+
"\x1B[D": "left",
|
|
8751
|
+
h: "left",
|
|
8752
|
+
E: "expandAll",
|
|
8753
|
+
C: "collapseAll",
|
|
8754
|
+
q: "quit",
|
|
8755
|
+
"\x1B": "quit",
|
|
8756
|
+
"": "quit"
|
|
8757
|
+
};
|
|
8758
|
+
function draw(out, value, state, rows) {
|
|
8759
|
+
const lines = renderTree(value, { expanded: state.expanded });
|
|
8760
|
+
const view = lines.slice(state.top, state.top + rows).map((l, i) => {
|
|
8761
|
+
const row = `${l.foldable ? l.expanded ? "▾ " : "▸ " : " "}${l.text}`;
|
|
8762
|
+
return state.top + i === state.cursor ? chalk.inverse(row) : row;
|
|
8763
|
+
});
|
|
8764
|
+
const footer = chalk.dim(`${state.cursor + 1}/${lines.length} ↑↓ move →/⏎ expand ← collapse E/C all q quit`);
|
|
8765
|
+
out.write(`\u001b[H\u001b[J${view.join("\n")}\n${footer}`);
|
|
8766
|
+
}
|
|
8767
|
+
async function runInteractive(value, depth) {
|
|
8768
|
+
const { stdin, stdout } = process;
|
|
8769
|
+
if (!stdin.isTTY || !stdout.isTTY) throw new NonInteractiveError("--interactive needs a TTY on stdin and stdout.");
|
|
8770
|
+
const rows = () => Math.max(3, (stdout.rows ?? 24) - 1);
|
|
8771
|
+
let state = {
|
|
8772
|
+
cursor: 0,
|
|
8773
|
+
top: 0,
|
|
8774
|
+
expanded: expandToDepth(value, depth)
|
|
8775
|
+
};
|
|
8776
|
+
const wasRaw = stdin.isRaw;
|
|
8777
|
+
stdin.setRawMode(true);
|
|
8778
|
+
stdin.resume();
|
|
8779
|
+
stdout.write("\x1B[?1049h\x1B[?25l");
|
|
8780
|
+
const redraw = () => draw(stdout, value, state, rows());
|
|
8781
|
+
stdout.on("resize", redraw);
|
|
8782
|
+
try {
|
|
8783
|
+
redraw();
|
|
8784
|
+
await new Promise((resolve) => {
|
|
8785
|
+
const onData = (buf) => {
|
|
8786
|
+
const key = KEYMAP[buf.toString()];
|
|
8787
|
+
if (key === "quit") {
|
|
8788
|
+
stdin.off("data", onData);
|
|
8789
|
+
resolve();
|
|
8790
|
+
return;
|
|
8791
|
+
}
|
|
8792
|
+
if (key) {
|
|
8793
|
+
state = reduceKey(state, key, value, rows());
|
|
8794
|
+
redraw();
|
|
8795
|
+
}
|
|
8796
|
+
};
|
|
8797
|
+
stdin.on("data", onData);
|
|
8798
|
+
});
|
|
8799
|
+
} finally {
|
|
8800
|
+
stdout.off("resize", redraw);
|
|
8801
|
+
stdout.write("\x1B[?25h\x1B[?1049l");
|
|
8802
|
+
stdin.setRawMode(wasRaw);
|
|
8803
|
+
stdin.pause();
|
|
8804
|
+
}
|
|
8805
|
+
}
|
|
8806
|
+
/**
|
|
8807
|
+
* Print `value` as JSON. Non-TTY stdout always gets plain, valid
|
|
8808
|
+
* `JSON.stringify` output (so `| jq` and redirects keep working); a TTY gets a
|
|
8809
|
+
* coloured tree, collapsed past `depth`. `interactive` opens a folding viewer (the default on a TTY).
|
|
8810
|
+
*/
|
|
8811
|
+
async function viewJson(value, options = {}) {
|
|
8812
|
+
const depth = options.depth ?? Infinity;
|
|
8813
|
+
if (options.interactive ?? (options.depth === void 0 && Boolean(process.stdin.isTTY && process.stdout.isTTY))) return runInteractive(value, options.depth ?? 1);
|
|
8814
|
+
if (!process.stdout.isTTY) {
|
|
8815
|
+
console.log(JSON.stringify(value, null, 2));
|
|
8816
|
+
return;
|
|
8817
|
+
}
|
|
8818
|
+
const lines = renderTree(value, { expanded: expandToDepth(value, depth) });
|
|
8819
|
+
console.log(lines.map((l) => l.text).join("\n"));
|
|
8820
|
+
}
|
|
8821
|
+
//#endregion
|
|
8602
8822
|
//#region src/ui/open-path.ts
|
|
8603
8823
|
/** Platform-appropriate command to open a path in the OS's file browser. */
|
|
8604
8824
|
function openCommand(path) {
|
|
@@ -8946,7 +9166,7 @@ try {
|
|
|
8946
9166
|
describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
|
|
8947
9167
|
}).option("hooks", {
|
|
8948
9168
|
type: "boolean",
|
|
8949
|
-
describe: "Install git hooks (.husky/pre-push → holocron ci). Default: on for protection:'strict'. --no-hooks to skip."
|
|
9169
|
+
describe: "Install git hooks (.husky/pre-push → holocron run ci). Default: on for protection:'strict'. --no-hooks to skip."
|
|
8950
9170
|
}), async (argv) => {
|
|
8951
9171
|
const tokens = tokenContext(argv.token);
|
|
8952
9172
|
if (!tokens) return;
|
|
@@ -9235,13 +9455,13 @@ try {
|
|
|
9235
9455
|
},
|
|
9236
9456
|
...argv.steps && argv.steps.length > 0 ? { steps: argv.steps } : {}
|
|
9237
9457
|
})).summary.fail > 0) process.exitCode = 1;
|
|
9238
|
-
}).command("run <task> [job] [passthrough..]", "Run a task locally (test, typecheck, lint, build, audit) — figures out turbo / the tool / the package manager", (y) => y.positional("task", {
|
|
9458
|
+
}).command("run <task> [job] [passthrough..]", "Run a task locally (test, typecheck, lint, build, audit) — figures out turbo / the tool / the package manager. `run ci` runs every merge-gating check; `run lint commit-msg <file>` lints a commit message.", (y) => y.positional("task", {
|
|
9239
9459
|
type: "string",
|
|
9240
9460
|
demandOption: true,
|
|
9241
|
-
describe: "Task name (test, typecheck, lint, build, audit). See `holocron run --help`."
|
|
9461
|
+
describe: "Task name (test, typecheck, lint, build, audit, or `ci` for the merge-gating suite). See `holocron run --help`."
|
|
9242
9462
|
}).positional("job", {
|
|
9243
9463
|
type: "string",
|
|
9244
|
-
describe: "Sub-job within the task (e.g. `holocron run audit performance`). Omit to run every job.
|
|
9464
|
+
describe: "Sub-job within the task (e.g. `holocron run audit performance`, `holocron run lint commit-msg <file>`). Omit to run every job."
|
|
9245
9465
|
}).positional("passthrough", {
|
|
9246
9466
|
type: "string",
|
|
9247
9467
|
array: true,
|
|
@@ -9250,11 +9470,43 @@ try {
|
|
|
9250
9470
|
type: "boolean",
|
|
9251
9471
|
default: false,
|
|
9252
9472
|
describe: "Fail (exit 1) if this repo has no such task, instead of skipping."
|
|
9473
|
+
}).option("all", {
|
|
9474
|
+
type: "boolean",
|
|
9475
|
+
default: false,
|
|
9476
|
+
describe: "With `run ci`: run every `ci: true` task, not just the required ones."
|
|
9253
9477
|
}).option("filter", {
|
|
9254
9478
|
type: "string",
|
|
9255
9479
|
describe: "turbo --filter=<pkg> passthrough (monorepo)."
|
|
9256
9480
|
}), async (argv) => {
|
|
9257
|
-
const { logger } = buildCliLogger(argv, { command: "run" });
|
|
9481
|
+
const { logger } = buildCliLogger(argv, { command: argv.task === "ci" ? "run ci" : argv.task === "lint" && argv.job === "commit-msg" ? "run lint commit-msg" : "run" });
|
|
9482
|
+
const passthrough = [...argv.passthrough ?? [], ...argv["--"] ?? []];
|
|
9483
|
+
if (argv.task === "lint" && argv.job === "commit-msg") {
|
|
9484
|
+
const file = passthrough[0];
|
|
9485
|
+
if (!file) {
|
|
9486
|
+
reportError("run lint commit-msg: missing <file> (the commit message file git passes as $1).");
|
|
9487
|
+
process.exitCode = 1;
|
|
9488
|
+
return;
|
|
9489
|
+
}
|
|
9490
|
+
const result = await lintCommitMsgFile(file, argv.cwd);
|
|
9491
|
+
if (!result.valid) {
|
|
9492
|
+
for (const violation of result.violations) console.error(`✖ ${violation.rule}: ${violation.message}`);
|
|
9493
|
+
process.exitCode = 1;
|
|
9494
|
+
}
|
|
9495
|
+
return;
|
|
9496
|
+
}
|
|
9497
|
+
if (argv.task === "ci") {
|
|
9498
|
+
const ciConfig = await loadTasksConfig(argv.cwd).catch(() => void 0);
|
|
9499
|
+
if (createAstromech({
|
|
9500
|
+
cwd: argv.cwd,
|
|
9501
|
+
logger,
|
|
9502
|
+
config: ciConfig
|
|
9503
|
+
}).ci({
|
|
9504
|
+
dryRun: argv.dryRun,
|
|
9505
|
+
scope: argv.all ? "all" : "required",
|
|
9506
|
+
...argv.filter ? { filter: argv.filter } : {}
|
|
9507
|
+
}).status === "fail") process.exitCode = 1;
|
|
9508
|
+
return;
|
|
9509
|
+
}
|
|
9258
9510
|
const config = await loadTasksConfig(argv.cwd).catch(() => void 0);
|
|
9259
9511
|
const report = createAstromech({
|
|
9260
9512
|
cwd: argv.cwd,
|
|
@@ -9262,31 +9514,12 @@ try {
|
|
|
9262
9514
|
config
|
|
9263
9515
|
}).run(argv.task, {
|
|
9264
9516
|
...argv.job !== void 0 ? { job: argv.job } : {},
|
|
9265
|
-
passthrough
|
|
9517
|
+
passthrough,
|
|
9266
9518
|
dryRun: argv.dryRun,
|
|
9267
9519
|
required: argv.required,
|
|
9268
9520
|
...argv.filter ? { filter: argv.filter } : {}
|
|
9269
9521
|
});
|
|
9270
9522
|
if (report.status === "fail" || report.status === "unknown") process.exitCode = 1;
|
|
9271
|
-
}).command("ci", "Run the merge-gating checks locally, in CI order — 'will CI pass?'", (y) => y.option("all", {
|
|
9272
|
-
type: "boolean",
|
|
9273
|
-
default: false,
|
|
9274
|
-
describe: "Run every `ci: true` task, not just the required ones."
|
|
9275
|
-
}).option("filter", {
|
|
9276
|
-
type: "string",
|
|
9277
|
-
describe: "turbo --filter=<pkg> passthrough (monorepo)."
|
|
9278
|
-
}), async (argv) => {
|
|
9279
|
-
const { logger } = buildCliLogger(argv, { command: "ci" });
|
|
9280
|
-
const config = await loadTasksConfig(argv.cwd).catch(() => void 0);
|
|
9281
|
-
if (createAstromech({
|
|
9282
|
-
cwd: argv.cwd,
|
|
9283
|
-
logger,
|
|
9284
|
-
config
|
|
9285
|
-
}).ci({
|
|
9286
|
-
dryRun: argv.dryRun,
|
|
9287
|
-
scope: argv.all ? "all" : "required",
|
|
9288
|
-
...argv.filter ? { filter: argv.filter } : {}
|
|
9289
|
-
}).status === "fail") process.exitCode = 1;
|
|
9290
9523
|
}).command("deploy-on-release", "Deploy every workspace package whose delivery.deploy task is `on: release` — run by the delivery.publish workflow's deploy job", (y) => y.option("channel", {
|
|
9291
9524
|
type: "string",
|
|
9292
9525
|
default: "",
|
|
@@ -9306,17 +9539,7 @@ try {
|
|
|
9306
9539
|
dryRun: argv.dryRun,
|
|
9307
9540
|
print: (line) => console.log(line)
|
|
9308
9541
|
})).status === "fail") process.exitCode = 1;
|
|
9309
|
-
}).command("
|
|
9310
|
-
type: "string",
|
|
9311
|
-
demandOption: true,
|
|
9312
|
-
describe: "Path to the commit message file (git's commit-msg hook passes this as $1)."
|
|
9313
|
-
}), async (argv) => {
|
|
9314
|
-
const result = await lintCommitMsgFile(argv.file, argv.cwd);
|
|
9315
|
-
if (!result.valid) {
|
|
9316
|
-
for (const violation of result.violations) console.error(`✖ ${violation.rule}: ${violation.message}`);
|
|
9317
|
-
process.exitCode = 1;
|
|
9318
|
-
}
|
|
9319
|
-
}), () => {}).command("sync-github", "Sync workflow templates and composite actions to theholocron/.github", (y) => y.option("repo", {
|
|
9542
|
+
}).command("sync-github", "Sync workflow templates and composite actions to theholocron/.github", (y) => y.option("repo", {
|
|
9320
9543
|
type: "string",
|
|
9321
9544
|
default: "theholocron/.github",
|
|
9322
9545
|
describe: "Target org/repo (default: theholocron/.github)"
|
|
@@ -9373,9 +9596,17 @@ try {
|
|
|
9373
9596
|
if (report.message) reportError(`sync-readme: ${report.message}`);
|
|
9374
9597
|
process.exitCode = 1;
|
|
9375
9598
|
}
|
|
9376
|
-
}).command("config show", "Print the resolved holocron config", () =>
|
|
9377
|
-
|
|
9378
|
-
|
|
9599
|
+
}).command("config show", "Print the resolved holocron config", (y) => y.option("depth", {
|
|
9600
|
+
type: "number",
|
|
9601
|
+
describe: "Collapse objects/arrays nested deeper than N levels (TTY output only)"
|
|
9602
|
+
}).option("interactive", {
|
|
9603
|
+
type: "boolean",
|
|
9604
|
+
describe: "Browse the config in a foldable tree (default on a TTY; --no-interactive for static output)"
|
|
9605
|
+
}), async (argv) => {
|
|
9606
|
+
await viewJson((await loadConfig(argv.cwd)).resolved, {
|
|
9607
|
+
depth: argv.depth,
|
|
9608
|
+
interactive: argv.interactive
|
|
9609
|
+
});
|
|
9379
9610
|
}).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
9611
|
type: "string",
|
|
9381
9612
|
describe: "Template type — maps to theholocron/<type>-template (e.g. cli, react, nextjs, node, monorepo, base)"
|