@theholocron/astromech 4.19.0 → 5.0.0-alpha.100
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 +99 -84
- package/dist/config/index.d.mts +17 -2
- package/dist/config/index.mjs +13 -3
- package/dist/index.d.mts +254 -106
- package/dist/index.mjs +708 -251
- package/dist/{schema-BegK4MsY.d.mts → schema-D01VYg43.d.mts} +11 -7
- package/package.json +11 -12
package/README.md
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
The Holocron task runner. One **task manifest** per repo, and every
|
|
4
4
|
derived surface comes from it: `holocron run` (local), `holocron ci` (the
|
|
5
5
|
CI suite run locally), the generated GitHub Actions workflows, the
|
|
6
|
-
`package.json` scripts,
|
|
7
|
-
required-checks list.
|
|
6
|
+
`package.json` scripts, and the branch-protection required-checks list.
|
|
8
7
|
|
|
9
8
|
> An astromech droid runs a starfighter's maintenance, diagnostics and
|
|
10
9
|
> system wiring while the pilot flies. This does that for a repo.
|
|
@@ -25,27 +24,77 @@ import { createAstromech } from "@theholocron/astromech";
|
|
|
25
24
|
|
|
26
25
|
const astromech = createAstromech({ cwd });
|
|
27
26
|
|
|
28
|
-
const report = astromech.run("
|
|
27
|
+
const report = astromech.run("verification.unitTests", { passthrough: ["--watch"] });
|
|
29
28
|
// → { status: "ok" | "fail" | "skip" | "dry-run" | "unknown", command?, message? }
|
|
30
29
|
```
|
|
31
30
|
|
|
32
31
|
### `holocron run <task>` resolution
|
|
33
32
|
|
|
34
|
-
`holocron run
|
|
35
|
-
npm, or which runner:
|
|
33
|
+
`holocron run verification.unitTests` runs your tests — you don't tell it
|
|
34
|
+
turbo vs pnpm vs npm, or which runner:
|
|
36
35
|
|
|
37
|
-
```
|
|
36
|
+
```text
|
|
38
37
|
1. turbo.json defines the task → turbo run <task>
|
|
39
38
|
2. package.json has a <task> script → <detected pm> run <task>
|
|
40
39
|
(a "holocron run …" thin caller is skipped — no recursion)
|
|
41
40
|
3. the registry has a local runner → <tool> <args> <org-flags> (e.g. --coverage)
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
3b. the task is a linterGroup → each resolved linter, run natively
|
|
42
|
+
4. the task is a container of jobs → each job, in declared order
|
|
43
|
+
5. known task, nothing to run → "no <task> task", exit 0 (exit 1 with --required)
|
|
44
|
+
6. unknown task → error, exit 1
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
## Intent → technology
|
|
48
|
+
|
|
49
|
+
Tasks are named by **intent**, not by tool — a repo declares
|
|
50
|
+
`verification.unitTests`, not `vitest`. The table below is the full
|
|
51
|
+
registry (`TASKS` in `src/registry.ts`): every task name astromech knows,
|
|
52
|
+
what it actually runs, and why. Tool names never appear in
|
|
53
|
+
`holocron.config.ts` — they're an implementation detail this table
|
|
54
|
+
documents, not a naming convention repos need to follow.
|
|
55
|
+
|
|
56
|
+
| Task | Runs | Notes |
|
|
57
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `verification.unitTests` | vitest | carries the `--coverage` org default |
|
|
59
|
+
| `verification.typeSafety` | tsc | `tsc --noEmit` |
|
|
60
|
+
| `verification.performance` | Lighthouse CI | only runs with a `lighthouse.config.*` present |
|
|
61
|
+
| `sourceQuality.staticAnalysis` | eslint, actionlint, git-merge-conflict-markers | a `linterGroup` — see below |
|
|
62
|
+
| `sourceQuality.formatting` | prettier, editorconfig-checker, markdownlint-cli2 | a `linterGroup` |
|
|
63
|
+
| `sourceQuality.structuredDataValidation` | yamllint | |
|
|
64
|
+
| `sourceQuality.deadCodeAnalysis` | knip | |
|
|
65
|
+
| `security.secretDetection` | gitleaks | |
|
|
66
|
+
| `security.codeScanning` | CodeQL | no local equivalent — CI only |
|
|
67
|
+
| `security.dependencyReview` | GitHub's native Dependabot alerts/graph | a capability-model method (`Source.enableVulnerabilityAlerts()`), not a task |
|
|
68
|
+
| `delivery.build` | tsdown / vite / rollup / tsc, detected from the repo's own config file, or `package.json`'s own `dependencies`/`devDependencies` if that config file isn't there | |
|
|
69
|
+
| `delivery.publish` | semantic-release | no local equivalent — CI only; carries `preview` (npm dist-tags) |
|
|
70
|
+
| `delivery.deploy` | Cloudflare Pages / Vercel | no local equivalent — CI only; carries `preview` |
|
|
71
|
+
| `delivery.bundleSize` | bundle-size upload to Codecov | no local equivalent — CI only |
|
|
72
|
+
| `platform.repoSync` | `holocron sync` | keeps generated files current |
|
|
73
|
+
| `platform.commitStandards` | commitlint | no local equivalent — enforced by the `commit-msg` hook locally, over the PR's commit range in CI |
|
|
74
|
+
| `platform.repoValidation` | `scripts/validate-adrs.mjs`, `scripts/validate-registry.mjs`, `scripts/validate-docs-presence.mjs` | a job-bearing task — spec/ADR frontmatter, registry-doc completeness, and docs-presence, not linters |
|
|
75
|
+
| `knowledge.wiki` | Fern | publishes `docs/wiki/*.md` — not a sync, a publish; carries `preview` |
|
|
76
|
+
| `knowledge.docs` | Astro build | the docs site itself; carries `preview` (defaults on) |
|
|
77
|
+
| `knowledge.components` | Storybook build | a browsable component catalog — "Storybook" is the tool, not the intent; carries `preview` (defaults on) |
|
|
78
|
+
|
|
79
|
+
**`preview` is a cross-cutting feature, not a namespace.** npm has staging
|
|
80
|
+
dist-tags, Cloudflare/Vercel do per-PR preview deploys, Fern previews
|
|
81
|
+
docs — none of these earn their own task or verb; it's one capability any
|
|
82
|
+
publish/deploy-shaped task can carry, toggled via that task's `with:`.
|
|
83
|
+
|
|
84
|
+
**`linterGroup` tasks** (`sourceQuality.staticAnalysis`,
|
|
85
|
+
`sourceQuality.formatting`) bundle more than one tool under a single
|
|
86
|
+
required check. Each tool is still gated by its own detection rule — e.g.
|
|
87
|
+
`eslint` runs when either a local `eslint.config.*` is present, or the
|
|
88
|
+
package has no local file but `@theholocron/eslint-config` resolves
|
|
89
|
+
(the same shared-config splice the `tool`/`detect` runner path uses) — the
|
|
90
|
+
`tool name → detection rule → local binary` mapping lives in one place,
|
|
91
|
+
`src/linters.ts`. A repo's choice of which of these run is based on which
|
|
92
|
+
tasks it includes in `tasks: [...]`, same as any other task.
|
|
93
|
+
|
|
94
|
+
**No super-linter.** Each task above runs its own tool directly, through
|
|
95
|
+
the same `holocron` composite action that resolves it locally — CI runs
|
|
96
|
+
the identical command a developer runs, not a third-party action bundling
|
|
97
|
+
a tool version this org doesn't control.
|
|
49
98
|
|
|
50
99
|
## Config — `@theholocron/astromech/config`
|
|
51
100
|
|
|
@@ -59,9 +108,9 @@ import { defineConfig } from "@theholocron/astromech/config";
|
|
|
59
108
|
|
|
60
109
|
export default defineConfig({
|
|
61
110
|
tasks: [
|
|
62
|
-
"
|
|
63
|
-
{ name: "
|
|
64
|
-
{ name: "
|
|
111
|
+
"verification.typeSafety",
|
|
112
|
+
{ name: "verification.unitTests", required: true, with: { "run-coverage": true } },
|
|
113
|
+
{ name: "security.codeScanning", ci: true, local: false }, // CI-only
|
|
65
114
|
],
|
|
66
115
|
});
|
|
67
116
|
```
|
|
@@ -69,6 +118,9 @@ export default defineConfig({
|
|
|
69
118
|
`loadTasksConfig(cwd)` resolves the manifest: the `tasks` key of
|
|
70
119
|
`holocron.config.*` (a bare item array), then a dedicated
|
|
71
120
|
`astromech.config.*` merged on top (dedicated wins; arrays concatenate).
|
|
121
|
+
`mergeTasksLayers(parentTasks, dedicated)` is that merge step on its own,
|
|
122
|
+
for callers that load the two sources some other way (Sentinel reads them
|
|
123
|
+
through the GitHub API).
|
|
72
124
|
|
|
73
125
|
| `TaskEntry` field | Effect |
|
|
74
126
|
| ------------------------ | ----------------------------------------------------------- |
|
|
@@ -77,7 +129,6 @@ export default defineConfig({
|
|
|
77
129
|
| `local: false` | `holocron run <name>` → "CI-only task", exit 0 |
|
|
78
130
|
| `required` | the task's check context is a required status check |
|
|
79
131
|
| `with` | per-repo overrides on the reusable-workflow channel |
|
|
80
|
-
| `linters` (`lint` only) | explicit linter list; omitted → auto-detect |
|
|
81
132
|
|
|
82
133
|
Top-level keys: `syncScripts: false` disables the `package.json` script
|
|
83
134
|
writes entirely; `holocronScript` sets the command the synced `"holocron"`
|
|
@@ -89,39 +140,43 @@ script runs (default `"holocron"`).
|
|
|
89
140
|
const astromech = createAstromech({ cwd, config, orgContext: { org, domain } });
|
|
90
141
|
|
|
91
142
|
astromech.thinCallers(); // Map<"<name>.yml", yaml> — one per templated, ci-enabled task
|
|
92
|
-
astromech.packageScripts(); // { holocron: "holocron",
|
|
93
|
-
astromech.requiredChecks(); // ["
|
|
143
|
+
astromech.packageScripts(); // { holocron: "holocron", "verification.unitTests": "holocron run verification.unitTests", … }
|
|
144
|
+
astromech.requiredChecks(); // ["Typecheck / tsc --noEmit", "codecov/patch", …] — branch-protection contexts
|
|
94
145
|
astromech.codecovConfig(existing); // codecov.yml content — merges into `existing`, or scaffolds fresh when null
|
|
95
146
|
astromech.ci({ scope: "required" }); // CiReport — run the gating checks locally, in CI order
|
|
96
147
|
```
|
|
97
148
|
|
|
98
|
-
`ci()` runs every `required: true` task (else every `ci: true` task) through
|
|
99
|
-
same resolution as `run()`, in `CI_ORDER`, and returns `{ status, jobs }`.
|
|
100
|
-
`required` task whose local runner can't run is a failure
|
|
101
|
-
|
|
149
|
+
`ci()` runs every `required: true` task (else every `ci: true` task) through
|
|
150
|
+
the same resolution as `run()`, in `CI_ORDER`, and returns `{ status, jobs }`.
|
|
151
|
+
A `required` task whose local runner can't run is a failure — **except** a
|
|
152
|
+
task that's genuinely CI-only (`local: null`, or a `linterGroup` whose every
|
|
153
|
+
member lacks a local binary entirely, like `platform.commitStandards`),
|
|
154
|
+
which is reported skipped even when required. `holocron ci` sets the
|
|
102
155
|
process exit code from `status`.
|
|
103
156
|
|
|
104
157
|
`thinCallers()` returns the raw `.github/workflows/*.yml` content (no
|
|
105
|
-
generated-by header — the caller prefixes its own). `deploy`
|
|
106
|
-
`
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
158
|
+
generated-by header — the caller prefixes its own). `delivery.deploy` /
|
|
159
|
+
`knowledge.docs` / `knowledge.components` with `preview:` produce the
|
|
160
|
+
combined push-to-Pages / PR-to-preview workflow —
|
|
161
|
+
`knowledge.docs`/`knowledge.components` default `preview` on, since
|
|
162
|
+
neither has a plain-production-only fallback template.
|
|
163
|
+
`packageScripts()` emits the `holocron` entry (`holocronScript ?? "holocron"`)
|
|
164
|
+
plus one `"<task>": "holocron run <task>"` per runnable task; it skips
|
|
165
|
+
`local: false` entries and tasks with no local runner at all, and returns
|
|
166
|
+
`{}` when `syncScripts: false` or there is no config.
|
|
112
167
|
|
|
113
168
|
### Required checks
|
|
114
169
|
|
|
115
170
|
`requiredChecks()` derives the branch-protection required-status-check list
|
|
116
|
-
from the manifest: every `{ required: true }` task's check context
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
171
|
+
from the manifest: every `{ required: true }` task's check context, ordered
|
|
172
|
+
by `CI_ORDER`, then `config.extraRequiredChecks` (codecov gates, …),
|
|
173
|
+
de-duplicated. Most tasks are single always-run jobs now, so their context
|
|
174
|
+
names that job directly (`"Typecheck / tsc --noEmit"`) — the `… /
|
|
175
|
+
Conclusion` fan-in aggregate is only used where a task genuinely has
|
|
176
|
+
several conditionally-run jobs feeding one check
|
|
177
|
+
(`verification.unitTests`, `platform.repoValidation`). `holocron setup`
|
|
178
|
+
prepends `"DCO"` and applies the list for `protection: "strict"` repos.
|
|
179
|
+
Policy-free — manifest only.
|
|
125
180
|
|
|
126
181
|
### `codecov.yml`
|
|
127
182
|
|
|
@@ -135,56 +190,16 @@ current file's content (or `null`) and it either merges the
|
|
|
135
190
|
thresholds, flags, custom rules) or scaffolds a fresh file from the base
|
|
136
191
|
template. `holocron setup` writes the result via the `source` capability;
|
|
137
192
|
this method never touches the filesystem beyond reading `packages/*` and
|
|
138
|
-
`apps/*` under `cwd`.
|
|
139
|
-
coverage setup tracks the `test` task, the same way required checks track
|
|
140
|
-
`tasks`.
|
|
141
|
-
|
|
142
|
-
## Lint parity
|
|
143
|
-
|
|
144
|
-
One linter list drives both CI and local — no asymmetry. Source: the
|
|
145
|
-
`lint` task's `linters` array, or auto-detection from the config files
|
|
146
|
-
present. The `linter name → super-linter VALIDATE_* keys` mapping lives in
|
|
147
|
-
one place, `src/linters.ts`.
|
|
148
|
-
|
|
149
|
-
| linter | `VALIDATE_*` | always-on | local binary |
|
|
150
|
-
| ---------------------------- | ----------------------------------------- | ----------------------------------- | ---------------------------------------- |
|
|
151
|
-
| `eslint` | `JAVASCRIPT_ES`, `TYPESCRIPT_ES` | on `eslint.config.*` / `.eslintrc*` | `eslint .` |
|
|
152
|
-
| `prettier` | `*_PRETTIER` (JS/JSX/TS/TSX/MD) + `FIX_*` | yes | `prettier --check .` |
|
|
153
|
-
| `yamllint` | `YAML` | yes | `yamllint .` (usually CI-only) |
|
|
154
|
-
| `actionlint` | `GITHUB_ACTIONS` | yes | `actionlint` (usually CI-only) |
|
|
155
|
-
| `gitleaks` | `GITLEAKS` | yes | `gitleaks dir` (usually CI-only) |
|
|
156
|
-
| `editorconfig` | `EDITORCONFIG` | yes | `editorconfig-checker` (usually CI-only) |
|
|
157
|
-
| `commitlint` | `GIT_COMMITLINT` | yes | — (commit-msg hook + CI) |
|
|
158
|
-
| `git-merge-conflict-markers` | `GIT_MERGE_CONFLICT_MARKERS` | yes | — (CI only) |
|
|
159
|
-
| `markdownlint` | `MARKDOWN` | on `.markdownlint*` | `markdownlint-cli2` |
|
|
160
|
-
|
|
161
|
-
```ts
|
|
162
|
-
import { superLinterConfig, resolveLinters } from "@theholocron/astromech";
|
|
163
|
-
|
|
164
|
-
superLinterConfig({ explicit: ["eslint", "prettier"], rootFiles: fs.readdirSync(cwd) });
|
|
165
|
-
// → { env: { VALIDATE_JAVASCRIPT_ES: "true", … }, linters: ["eslint","prettier"], configInputs: { … } }
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
`superLinterConfig().env` is the exact `VALIDATE_*`/`FIX_*` map the CI
|
|
169
|
-
`lint` job needs — the CLI serializes it as the `super-linter-env` input on
|
|
170
|
-
each repo's generated `lint` thin caller. Setting any `VALIDATE_*` puts
|
|
171
|
-
super-linter in allow-list mode, so emitting only the enabled keys makes it
|
|
172
|
-
run exactly the resolved set.
|
|
173
|
-
|
|
174
|
-
`holocron run lint` (later phase) runs the same set natively: `turbo run
|
|
175
|
-
lint` for the eslint portion (cached), then each other linter whose binary
|
|
176
|
-
resolves; linters with a binary that is not on `PATH` are flagged with an
|
|
177
|
-
install hint; the rest print "CI only".
|
|
193
|
+
`apps/*` under `cwd`.
|
|
178
194
|
|
|
179
195
|
## Development
|
|
180
196
|
|
|
181
|
-
| Script
|
|
182
|
-
|
|
|
183
|
-
| `pnpm build`
|
|
184
|
-
| `pnpm
|
|
185
|
-
| `pnpm
|
|
186
|
-
| `pnpm
|
|
187
|
-
| `pnpm lint` | ESLint |
|
|
197
|
+
| Script | Description |
|
|
198
|
+
| --------------------------------------- | ------------------------------------------- |
|
|
199
|
+
| `pnpm run delivery.build` | Bundle with tsdown |
|
|
200
|
+
| `pnpm run verification.unitTests` | Run the vitest suite (always with coverage) |
|
|
201
|
+
| `pnpm run verification.typeSafety` | `tsc --noEmit` |
|
|
202
|
+
| `pnpm run sourceQuality.staticAnalysis` | ESLint |
|
|
188
203
|
|
|
189
204
|
## Releases
|
|
190
205
|
|
package/dist/config/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskConfigItem } from "../schema-
|
|
1
|
+
import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskConfigItem } from "../schema-D01VYg43.mjs";
|
|
2
2
|
//#region src/config/define.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Typed identity helper for `astromech.config.ts`:
|
|
@@ -14,6 +14,13 @@ import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskCon
|
|
|
14
14
|
declare const defineConfig: <C extends TasksConfig>(config: C) => C;
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/config/load.d.ts
|
|
17
|
+
/**
|
|
18
|
+
* The `tasks` value in each source. A dedicated `astromech.config.*`
|
|
19
|
+
* default-exports the full {@link TasksConfig} object; the `tasks` key of
|
|
20
|
+
* `holocron.config.*` is the bare item array (it mirrors the old
|
|
21
|
+
* `workflows` key). Either form is accepted from either source.
|
|
22
|
+
*/
|
|
23
|
+
type TasksInput = TasksConfig | TaskConfigItem[];
|
|
17
24
|
/**
|
|
18
25
|
* Resolve the task manifest for a repo: the `tasks` key of
|
|
19
26
|
* `holocron.config.*`, then a dedicated `astromech.config.*` merged on
|
|
@@ -21,5 +28,13 @@ declare const defineConfig: <C extends TasksConfig>(config: C) => C;
|
|
|
21
28
|
* neither source is present.
|
|
22
29
|
*/
|
|
23
30
|
declare function loadTasksConfig(cwd: string): Promise<TasksConfig>;
|
|
31
|
+
/**
|
|
32
|
+
* The merge step of {@link loadTasksConfig}, for callers that load the two
|
|
33
|
+
* sources some other way (Sentinel reads them from the GitHub API, not
|
|
34
|
+
* from disk, holocron#916): `holocron.config.*`'s `tasks` value first,
|
|
35
|
+
* then a dedicated `astromech.config.*`'s default export merged on top.
|
|
36
|
+
* Either may be `undefined` (source absent); returns `{}` when both are.
|
|
37
|
+
*/
|
|
38
|
+
declare function mergeTasksLayers(parentTasks: TasksInput | undefined, dedicated: TasksInput | undefined): TasksConfig;
|
|
24
39
|
//#endregion
|
|
25
|
-
export { type TaskConfigItem, type TaskEntry, type TasksConfig, defineConfig, loadTasksConfig, normalizeTaskEntry };
|
|
40
|
+
export { type TaskConfigItem, type TaskEntry, type TasksConfig, defineConfig, loadTasksConfig, mergeTasksLayers, normalizeTaskEntry };
|
package/dist/config/index.mjs
CHANGED
|
@@ -26,14 +26,24 @@ async function loadTasksConfig(cwd) {
|
|
|
26
26
|
cwd,
|
|
27
27
|
name: "astromech"
|
|
28
28
|
});
|
|
29
|
-
return
|
|
29
|
+
return mergeTasksLayers((await loadConfigFile({
|
|
30
30
|
cwd,
|
|
31
31
|
name: "holocron"
|
|
32
|
-
}))?.config.tasks
|
|
32
|
+
}))?.config.tasks, dedicated?.config);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The merge step of {@link loadTasksConfig}, for callers that load the two
|
|
36
|
+
* sources some other way (Sentinel reads them from the GitHub API, not
|
|
37
|
+
* from disk, holocron#916): `holocron.config.*`'s `tasks` value first,
|
|
38
|
+
* then a dedicated `astromech.config.*`'s default export merged on top.
|
|
39
|
+
* Either may be `undefined` (source absent); returns `{}` when both are.
|
|
40
|
+
*/
|
|
41
|
+
function mergeTasksLayers(parentTasks, dedicated) {
|
|
42
|
+
return [coerce(parentTasks), coerce(dedicated)].filter((layer) => layer !== void 0).reduce((acc, layer) => mergeConfig(acc, layer), {});
|
|
33
43
|
}
|
|
34
44
|
function coerce(value) {
|
|
35
45
|
if (value == null) return void 0;
|
|
36
46
|
return Array.isArray(value) ? { tasks: value } : value;
|
|
37
47
|
}
|
|
38
48
|
//#endregion
|
|
39
|
-
export { defineConfig, loadTasksConfig, normalizeTaskEntry };
|
|
49
|
+
export { defineConfig, loadTasksConfig, mergeTasksLayers, normalizeTaskEntry };
|