@theholocron/cli 4.10.2 → 4.11.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/README.md +23 -4
- package/dist/cli.mjs +47 -91
- package/dist/cli.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -133,6 +133,21 @@ Writes `"holocron": "<holocronScript ?? 'holocron'>"` plus one
|
|
|
133
133
|
`"<task>": "holocron run <task>"` per runnable task. `syncScripts: false`
|
|
134
134
|
opts out entirely.
|
|
135
135
|
|
|
136
|
+
### `holocron run`
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
holocron run <task> [job] [--required] [--filter <pkg>] [--dry-run] [-- <passthrough>]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Runs one task locally — resolving turbo vs the package manager vs the tool
|
|
143
|
+
itself. A `job` argument targets a sub-job of tasks that have them: `holocron
|
|
144
|
+
run audit performance` runs Lighthouse CI, `holocron run audit knip` runs Knip,
|
|
145
|
+
`holocron run audit` (no job) runs every audit sub-job in declared order. A
|
|
146
|
+
sub-job whose tool isn't installed, or that has no local equivalent
|
|
147
|
+
(`bundle-size`), is skipped with a note — `--required` makes that a failure. For
|
|
148
|
+
a task with no sub-jobs the `job` slot is the first passthrough arg
|
|
149
|
+
(`holocron run build src/`).
|
|
150
|
+
|
|
136
151
|
### `holocron ci`
|
|
137
152
|
|
|
138
153
|
```bash
|
|
@@ -146,7 +161,9 @@ first failure — the "will my PR be green?" pre-flight, and what the
|
|
|
146
161
|
`--all` forces the full set; `--filter` is a `turbo --filter=` passthrough.
|
|
147
162
|
Order comes from the `CI_ORDER` constant in `@theholocron/astromech`
|
|
148
163
|
(`typecheck → lint → test → build → …`). Tasks with no local equivalent
|
|
149
|
-
(`
|
|
164
|
+
(`codeql`, `deploy`) are reported as enforced-in-CI, never failures; `audit`
|
|
165
|
+
expands into its sub-jobs (each under its own `audit / …` check context)
|
|
166
|
+
unless the repo ships its own `"audit"` script.
|
|
150
167
|
|
|
151
168
|
### Lint parity
|
|
152
169
|
|
|
@@ -306,9 +323,11 @@ all three. See the [telemetry guide](https://docs.theholocron.dev/holocron/telem
|
|
|
306
323
|
capability config packages, builds the capability registry
|
|
307
324
|
- `src/cli.ts` — yargs entry, dispatches subcommands. `holocron run` / `ci` are
|
|
308
325
|
delegated to [`@theholocron/astromech`](../astromech) (`createAstromech`).
|
|
309
|
-
`holocron sync
|
|
310
|
-
|
|
311
|
-
|
|
326
|
+
`holocron sync` / `holocron setup` write each repo's `.github/workflows/*.yml`
|
|
327
|
+
thin callers from `astromech.thinCallers()`; `holocron sync-github` pushes the
|
|
328
|
+
reusable `workflow_call` implementations + composite actions from
|
|
329
|
+
`astromech.reusableTemplates()` to `theholocron/.github` (a pure sync target —
|
|
330
|
+
never hand-edit its `.github/workflows/*`).
|
|
312
331
|
- `src/commands/` — `setup`, `sync`, `doctor`, `deploy`, `secret set`,
|
|
313
332
|
`secrets sync`, `npm publish-initial`, `sync-github`, `upgrade node`,
|
|
314
333
|
`plugin create`, `auth`
|
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import path, { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { checkbox, input, select } from "@inquirer/prompts";
|
|
6
|
-
import { KNOWN_WORKFLOWS, createAstromech,
|
|
6
|
+
import { KNOWN_WORKFLOWS, createAstromech, extractPreviewConfig, reusableTemplates } from "@theholocron/astromech";
|
|
7
7
|
import { loadTasksConfig } from "@theholocron/astromech/config";
|
|
8
8
|
import yargs from "yargs";
|
|
9
9
|
import { hideBin } from "yargs/helpers";
|
|
@@ -4385,31 +4385,16 @@ async function runSetup(input) {
|
|
|
4385
4385
|
if (loader.has("source") && tasks && tasks.length > 0) {
|
|
4386
4386
|
const source = loader.get("source");
|
|
4387
4387
|
print(style.step("workflows"));
|
|
4388
|
+
const files = createAstromech({
|
|
4389
|
+
cwd: input.context.repoRoot,
|
|
4390
|
+
config: { tasks },
|
|
4391
|
+
orgContext: {
|
|
4392
|
+
org: config.org,
|
|
4393
|
+
domain: config.domain
|
|
4394
|
+
}
|
|
4395
|
+
}).thinCallers();
|
|
4388
4396
|
for (const entry of tasks) {
|
|
4389
4397
|
const name = typeof entry === "string" ? entry : entry.name;
|
|
4390
|
-
const rawWith = typeof entry === "object" ? entry.with : void 0;
|
|
4391
|
-
const normalized = rawWith ? normalizeWorkflowWith(rawWith) : void 0;
|
|
4392
|
-
let withOverrides = normalized;
|
|
4393
|
-
let comments;
|
|
4394
|
-
if (name === "lint") {
|
|
4395
|
-
let rootFiles = [];
|
|
4396
|
-
try {
|
|
4397
|
-
rootFiles = readdirSync(input.context.repoRoot);
|
|
4398
|
-
} catch {}
|
|
4399
|
-
const lint = lintThinCallerWith({
|
|
4400
|
-
explicit: typeof entry === "object" ? entry.linters : void 0,
|
|
4401
|
-
rootFiles,
|
|
4402
|
-
extra: normalized
|
|
4403
|
-
});
|
|
4404
|
-
withOverrides = lint.withOverrides;
|
|
4405
|
-
comments = lint.comments;
|
|
4406
|
-
}
|
|
4407
|
-
const additionalPaths = (typeof entry === "object" ? entry.paths : void 0) ?? (name === "deploy" && rawWith ? deriveDeployPaths(rawWith) : void 0);
|
|
4408
|
-
if (name === "test" && withOverrides) {
|
|
4409
|
-
const runUnit = withOverrides["run-unit"];
|
|
4410
|
-
const runStorybook = withOverrides["run-storybook"];
|
|
4411
|
-
if (runUnit === false && runStorybook === false) throw new ConfigError("test workflow: at least one of \"run-unit\" or \"run-storybook\" must be true. Library repos use run-unit: true; UI/Storybook repos use run-storybook: true.");
|
|
4412
|
-
}
|
|
4413
4398
|
if (!KNOWN_WORKFLOWS.has(name)) {
|
|
4414
4399
|
steps.push({
|
|
4415
4400
|
capability: "source",
|
|
@@ -4420,22 +4405,12 @@ async function runSetup(input) {
|
|
|
4420
4405
|
print(formatStep(steps[steps.length - 1]));
|
|
4421
4406
|
continue;
|
|
4422
4407
|
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
const paths = additionalPaths;
|
|
4430
|
-
steps.push(await runStep("source", "write workflow deploy (with preview)", dryRun, async () => {
|
|
4431
|
-
await source.writeWorkflowFile("deploy.yml", `${workflowHeader$1()}${generateCombinedDeployContent(withOverrides, paths, previewCfg)}`);
|
|
4432
|
-
}));
|
|
4433
|
-
print(formatStep(steps[steps.length - 1]));
|
|
4434
|
-
continue;
|
|
4435
|
-
}
|
|
4436
|
-
}
|
|
4437
|
-
steps.push(await runStep("source", `write workflow ${name}`, dryRun, async () => {
|
|
4438
|
-
await source.writeWorkflowFile(`${name}.yml`, `${workflowHeader$1()}${generateThinCallerContent(name, withOverrides, additionalPaths, void 0, comments)}`);
|
|
4408
|
+
const filename = name === "deploy" ? "deploy.yml" : `${name}.yml`;
|
|
4409
|
+
const content = files.get(filename);
|
|
4410
|
+
if (content === void 0) continue;
|
|
4411
|
+
const step = name === "deploy" && content.includes("workflows/preview.yml@main") ? "write workflow deploy (with preview)" : `write workflow ${name}`;
|
|
4412
|
+
steps.push(await runStep("source", step, dryRun, async () => {
|
|
4413
|
+
await source.writeWorkflowFile(filename, `${workflowHeader$1()}${content}`);
|
|
4439
4414
|
}));
|
|
4440
4415
|
print(formatStep(steps[steps.length - 1]));
|
|
4441
4416
|
}
|
|
@@ -5647,51 +5622,36 @@ async function runSync(input) {
|
|
|
5647
5622
|
message: "no tasks configured"
|
|
5648
5623
|
});
|
|
5649
5624
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
5650
|
-
} else
|
|
5651
|
-
const
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
let comments;
|
|
5656
|
-
if (name === "lint") {
|
|
5657
|
-
const lint = lintThinCallerWith({
|
|
5658
|
-
explicit: typeof entry === "object" ? entry.linters : void 0,
|
|
5659
|
-
rootFiles: safeReaddir(input.context.repoRoot),
|
|
5660
|
-
extra: normalized
|
|
5661
|
-
});
|
|
5662
|
-
withOverrides = lint.withOverrides;
|
|
5663
|
-
comments = lint.comments;
|
|
5664
|
-
}
|
|
5665
|
-
const additionalPaths = (typeof entry === "object" ? entry.paths : void 0) ?? (name === "deploy" && rawWith ? deriveDeployPaths(rawWith) : void 0);
|
|
5666
|
-
if (!KNOWN_WORKFLOWS.has(name)) {
|
|
5667
|
-
steps.push({
|
|
5668
|
-
capability: "local",
|
|
5669
|
-
step: `sync workflow ${name}`,
|
|
5670
|
-
status: "skip",
|
|
5671
|
-
message: `unknown workflow "${name}" — no template available`
|
|
5672
|
-
});
|
|
5673
|
-
print(formatSyncStep(steps[steps.length - 1]));
|
|
5674
|
-
continue;
|
|
5675
|
-
}
|
|
5676
|
-
if (name === "deploy" && rawWith) {
|
|
5677
|
-
const previewCfg = extractPreviewConfig(rawWith, {
|
|
5625
|
+
} else {
|
|
5626
|
+
const files = createAstromech({
|
|
5627
|
+
cwd: input.context.repoRoot,
|
|
5628
|
+
config: { tasks: config.tasks },
|
|
5629
|
+
orgContext: {
|
|
5678
5630
|
org: config.org,
|
|
5679
5631
|
domain: config.domain
|
|
5680
|
-
}
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5632
|
+
}
|
|
5633
|
+
}).thinCallers();
|
|
5634
|
+
for (const entry of taskEntries) {
|
|
5635
|
+
const name = typeof entry === "string" ? entry : entry.name;
|
|
5636
|
+
if (!KNOWN_WORKFLOWS.has(name)) {
|
|
5637
|
+
steps.push({
|
|
5638
|
+
capability: "local",
|
|
5639
|
+
step: `sync workflow ${name}`,
|
|
5640
|
+
status: "skip",
|
|
5641
|
+
message: `unknown workflow "${name}" — no template available`
|
|
5642
|
+
});
|
|
5686
5643
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
5687
5644
|
continue;
|
|
5688
5645
|
}
|
|
5646
|
+
const filename = name === "deploy" ? "deploy.yml" : `${name}.yml`;
|
|
5647
|
+
const content = files.get(filename);
|
|
5648
|
+
if (content === void 0) continue;
|
|
5649
|
+
const step = `sync workflow ${name}${name === "deploy" && content.includes("workflows/preview.yml@main") ? " (with preview)" : ""}`;
|
|
5650
|
+
steps.push(await runSyncStep("local", step, dryRun, async () => {
|
|
5651
|
+
await writeWorkflowFile(input.context.repoRoot, filename, `${workflowHeader()}${content}`);
|
|
5652
|
+
}));
|
|
5653
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
5689
5654
|
}
|
|
5690
|
-
steps.push(await runSyncStep("local", `sync workflow ${name}`, dryRun, async () => {
|
|
5691
|
-
const content = `${workflowHeader()}${generateThinCallerContent(name, withOverrides, additionalPaths, void 0, comments)}`;
|
|
5692
|
-
await writeWorkflowFile(input.context.repoRoot, `${name}.yml`, content);
|
|
5693
|
-
}));
|
|
5694
|
-
print(formatSyncStep(steps[steps.length - 1]));
|
|
5695
5655
|
}
|
|
5696
5656
|
}
|
|
5697
5657
|
if (stepName === "scripts") if (config.syncScripts === false) {
|
|
@@ -5781,14 +5741,6 @@ function formatSyncStep(step) {
|
|
|
5781
5741
|
const detail = step.message ? ` (${step.message})` : "";
|
|
5782
5742
|
return ` ${icon} ${step.step}${detail}`;
|
|
5783
5743
|
}
|
|
5784
|
-
/** Repo-root filenames for linter auto-detection; `[]` when the dir is unreadable. */
|
|
5785
|
-
function safeReaddir(dir) {
|
|
5786
|
-
try {
|
|
5787
|
-
return readdirSync(dir);
|
|
5788
|
-
} catch {
|
|
5789
|
-
return [];
|
|
5790
|
-
}
|
|
5791
|
-
}
|
|
5792
5744
|
async function writePackageJsonField(repoRoot, field, value) {
|
|
5793
5745
|
const pkgPath = join(repoRoot, "package.json");
|
|
5794
5746
|
let content;
|
|
@@ -6983,10 +6935,13 @@ try {
|
|
|
6983
6935
|
},
|
|
6984
6936
|
...argv.steps && argv.steps.length > 0 ? { steps: argv.steps } : {}
|
|
6985
6937
|
})).summary.fail > 0) process.exitCode = 1;
|
|
6986
|
-
}).command("run <task> [passthrough..]", "Run a task locally (test, typecheck, lint, build) — figures out turbo / the tool / the package manager", (y) => y.positional("task", {
|
|
6938
|
+
}).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", {
|
|
6987
6939
|
type: "string",
|
|
6988
6940
|
demandOption: true,
|
|
6989
|
-
describe: "Task name (test, typecheck, lint, build). See `holocron run --help`."
|
|
6941
|
+
describe: "Task name (test, typecheck, lint, build, audit). See `holocron run --help`."
|
|
6942
|
+
}).positional("job", {
|
|
6943
|
+
type: "string",
|
|
6944
|
+
describe: "Sub-job within the task (e.g. `holocron run audit performance`). Omit to run every job. Only `audit` has jobs today."
|
|
6990
6945
|
}).positional("passthrough", {
|
|
6991
6946
|
type: "string",
|
|
6992
6947
|
array: true,
|
|
@@ -7006,6 +6961,7 @@ try {
|
|
|
7006
6961
|
logger,
|
|
7007
6962
|
config
|
|
7008
6963
|
}).run(argv.task, {
|
|
6964
|
+
...argv.job !== void 0 ? { job: argv.job } : {},
|
|
7009
6965
|
passthrough: argv.passthrough ?? [],
|
|
7010
6966
|
dryRun: argv.dryRun,
|
|
7011
6967
|
required: argv.required,
|
|
@@ -7424,8 +7380,8 @@ try {
|
|
|
7424
7380
|
let extra = [];
|
|
7425
7381
|
try {
|
|
7426
7382
|
const raw = readFileSync(join(argv.cwd, "holocron.config.json"), "utf8");
|
|
7427
|
-
const
|
|
7428
|
-
if (Array.isArray(
|
|
7383
|
+
const extraRaw = JSON.parse(raw).upgrade?.node?.extra;
|
|
7384
|
+
if (Array.isArray(extraRaw)) extra = extraRaw;
|
|
7429
7385
|
} catch {}
|
|
7430
7386
|
const report = await runUpgradeNode({
|
|
7431
7387
|
to: argv.to,
|