@theholocron/astromech 4.1.0 → 4.2.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 CHANGED
@@ -79,9 +79,30 @@ export default defineConfig({
79
79
  | `with` | per-repo overrides on the reusable-workflow channel |
80
80
  | `linters` (`lint` only) | explicit linter list; omitted → auto-detect |
81
81
 
82
- Nothing in `holocron run` reads the config yet — the manifest drives
83
- `holocron ci`, workflow generation, and script sync in later phases
84
- (epic #581).
82
+ Top-level keys: `syncScripts: false` disables the `package.json` script
83
+ writes entirely; `holocronScript` sets the command the synced `"holocron"`
84
+ script runs (default `"holocron"`).
85
+
86
+ ### Generated surfaces
87
+
88
+ ```ts
89
+ const astromech = createAstromech({ cwd, config, orgContext: { org, domain } });
90
+
91
+ astromech.thinCallers(); // Map<"<name>.yml", yaml> — one per templated, ci-enabled task
92
+ astromech.packageScripts(); // { holocron: "holocron", lint: "holocron run lint", … }
93
+ ```
94
+
95
+ `thinCallers()` returns the raw `.github/workflows/*.yml` content (no
96
+ generated-by header — the caller prefixes its own). `deploy` with
97
+ `preview:` shorthand produces the combined push-to-Pages / PR-to-preview
98
+ workflow. `packageScripts()` emits the `holocron` entry
99
+ (`holocronScript ?? "holocron"`) plus one `"<task>": "holocron run <task>"`
100
+ per runnable task; it skips `local: false` entries and tasks with no local
101
+ runner (`codeql`, `deploy`), and returns `{}` when `syncScripts: false` or
102
+ there is no config.
103
+
104
+ `holocron run` itself does not read the config yet — that (and
105
+ `holocron ci`) come in later phases (epic #581).
85
106
 
86
107
  ## Development
87
108
 
@@ -1,4 +1,4 @@
1
- import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskConfigItem } from "../schema-4kyr9ILV.mjs";
1
+ import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskConfigItem } from "../schema-DbpiBZCP.mjs";
2
2
  //#region src/config/define.d.ts
3
3
  /**
4
4
  * Typed identity helper for `astromech.config.ts`:
@@ -1,3 +1,4 @@
1
+ import { t as normalizeTaskEntry } from "../schema-Cf5dfaDO.mjs";
1
2
  import { createDefineConfig, loadConfigFile, mergeConfig } from "@theholocron/datapad";
2
3
  //#region src/config/define.ts
3
4
  /**
@@ -35,14 +36,4 @@ function coerce(value) {
35
36
  return Array.isArray(value) ? { tasks: value } : value;
36
37
  }
37
38
  //#endregion
38
- //#region src/config/schema.ts
39
- /** Normalise a `TaskConfigItem` to a full {@link TaskEntry} with defaults applied. */
40
- function normalizeTaskEntry(item) {
41
- return {
42
- ci: true,
43
- local: true,
44
- ...typeof item === "string" ? { name: item } : item
45
- };
46
- }
47
- //#endregion
48
39
  export { defineConfig, loadTasksConfig, normalizeTaskEntry };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as TasksConfig } from "./schema-4kyr9ILV.mjs";
1
+ import { r as TasksConfig } from "./schema-DbpiBZCP.mjs";
2
2
  //#region src/run.d.ts
3
3
  /**
4
4
  * `holocron run <task> [-- <passthrough>]` — run a registry task locally.
@@ -56,83 +56,6 @@ interface RunTaskReport {
56
56
  }
57
57
  declare function runTask(input: RunTaskInput): RunTaskReport;
58
58
  //#endregion
59
- //#region src/astromech.d.ts
60
- interface AstromechOptions {
61
- /** Repo root. */
62
- cwd: string;
63
- /**
64
- * The resolved task manifest. Optional for `run` (which is
65
- * filesystem-driven); later methods (`ci`, workflow generation) need it.
66
- * Load it with `loadTasksConfig` from `@theholocron/astromech/config`.
67
- */
68
- config?: TasksConfig;
69
- /** Structured-logging sink. Defaults to a no-op. */
70
- logger?: RunLogger;
71
- /** User-facing line printer. Defaults to `console.log`. */
72
- print?: (line: string) => void;
73
- /** Injectable subprocess runner (tests). Defaults to `spawnSync` (stdio inherit). */
74
- exec?: ExecFn;
75
- /** Injectable fs (tests). Default to `node:fs`. */
76
- readFile?: (path: string) => string;
77
- fileExists?: (path: string) => boolean;
78
- listDir?: (path: string) => string[];
79
- }
80
- interface RunOptions {
81
- /** Args after `--`, forwarded to the tool / turbo / script. */
82
- passthrough?: string[];
83
- /** Print the resolved command without running it. */
84
- dryRun?: boolean;
85
- /** Fail (exit 1) instead of skipping when the repo has no such task. */
86
- required?: boolean;
87
- }
88
- interface Astromech {
89
- /** Run one task locally. */
90
- run(task: string, opts?: RunOptions): RunTaskReport;
91
- }
92
- declare function createAstromech(options: AstromechOptions): Astromech;
93
- //#endregion
94
- //#region src/registry.d.ts
95
- /**
96
- * The task registry — how each task runs *locally*, without GitHub
97
- * Actions. `holocron run <task>` and `holocron ci` resolve against this;
98
- * adding a task here gives every repo that task.
99
- *
100
- * Keyed identically to the workflow templates — a task IS a workflow.
101
- *
102
- * Spec: `.notes/tech-astromech-task-runner.spec.md` (epic #581).
103
- */
104
- /** How to run one task (or job) locally. */
105
- interface LocalRunner {
106
- /** Binary to invoke — resolved from `node_modules/.bin` then PATH. */
107
- tool?: string;
108
- /** Args appended after the tool. */
109
- args?: string[];
110
- /** First entry whose `when` filename matches a repo-root file wins. */
111
- detect?: Array<{
112
- when: RegExp;
113
- tool: string;
114
- args?: string[];
115
- }>;
116
- /** The task is already a holocron subcommand (`sync`, `sync-wiki`). */
117
- command?: string;
118
- }
119
- interface TaskDef {
120
- /**
121
- * `null` — no local equivalent (CodeQL, deploys). `holocron ci` reports
122
- * it as skipped; `holocron run` treats it as "nothing to do".
123
- */
124
- local: LocalRunner | null;
125
- /** Sub-jobs, keyed by slug — `holocron run audit performance`. */
126
- jobs?: Record<string, {
127
- local: LocalRunner | null;
128
- }>;
129
- /** Org-default flags injected by tool name. Removed by a repo override. */
130
- flags?: Record<string, string[]>;
131
- }
132
- declare const TASKS: Record<string, TaskDef>;
133
- /** Every task name the registry knows. */
134
- declare const KNOWN_TASKS: Set<string>;
135
- //#endregion
136
59
  //#region src/thin-callers.d.ts
137
60
  /**
138
61
  * Workflow templates + thin-caller generation.
@@ -231,4 +154,101 @@ declare function normalizeWorkflowWith(raw: Record<string, unknown>): Record<str
231
154
  */
232
155
  declare function deriveDeployPaths(raw: Record<string, unknown>): string[];
233
156
  //#endregion
157
+ //#region src/astromech.d.ts
158
+ interface AstromechOptions {
159
+ /** Repo root. */
160
+ cwd: string;
161
+ /**
162
+ * The resolved task manifest. Optional for `run` (which is
163
+ * filesystem-driven); `thinCallers` / `packageScripts` / `ci` need it.
164
+ * Load it with `loadTasksConfig` from `@theholocron/astromech/config`.
165
+ */
166
+ config?: TasksConfig;
167
+ /**
168
+ * Org context for the `deploy` workflow's `preview:` shorthand — used to
169
+ * derive the Cloudflare Pages project / domain when they are not spelt out.
170
+ */
171
+ orgContext?: OrgContext;
172
+ /** Structured-logging sink. Defaults to a no-op. */
173
+ logger?: RunLogger;
174
+ /** User-facing line printer. Defaults to `console.log`. */
175
+ print?: (line: string) => void;
176
+ /** Injectable subprocess runner (tests). Defaults to `spawnSync` (stdio inherit). */
177
+ exec?: ExecFn;
178
+ /** Injectable fs (tests). Default to `node:fs`. */
179
+ readFile?: (path: string) => string;
180
+ fileExists?: (path: string) => boolean;
181
+ listDir?: (path: string) => string[];
182
+ }
183
+ interface RunOptions {
184
+ /** Args after `--`, forwarded to the tool / turbo / script. */
185
+ passthrough?: string[];
186
+ /** Print the resolved command without running it. */
187
+ dryRun?: boolean;
188
+ /** Fail (exit 1) instead of skipping when the repo has no such task. */
189
+ required?: boolean;
190
+ }
191
+ interface Astromech {
192
+ /** Run one task locally. */
193
+ run(task: string, opts?: RunOptions): RunTaskReport;
194
+ /**
195
+ * The `.github/workflows/*.yml` thin callers for this repo's manifest —
196
+ * `filename` → YAML content (no generated-by header; the caller adds it).
197
+ * One entry per `config.tasks` item that has a workflow template and is
198
+ * not `ci: false`.
199
+ */
200
+ thinCallers(): Map<string, string>;
201
+ /**
202
+ * `package.json` scripts for this repo's manifest — the `"holocron"` entry
203
+ * (`config.holocronScript ?? "holocron"`) plus `"<task>": "holocron run
204
+ * <task>"` for every `config.tasks` item that is a runnable registry task
205
+ * and not `local: false`. Merge into `package.json`; never clobber. Empty
206
+ * when there is no config or `syncScripts: false`.
207
+ */
208
+ packageScripts(): Record<string, string>;
209
+ }
210
+ declare function createAstromech(options: AstromechOptions): Astromech;
211
+ //#endregion
212
+ //#region src/registry.d.ts
213
+ /**
214
+ * The task registry — how each task runs *locally*, without GitHub
215
+ * Actions. `holocron run <task>` and `holocron ci` resolve against this;
216
+ * adding a task here gives every repo that task.
217
+ *
218
+ * Keyed identically to the workflow templates — a task IS a workflow.
219
+ *
220
+ * Spec: `.notes/tech-astromech-task-runner.spec.md` (epic #581).
221
+ */
222
+ /** How to run one task (or job) locally. */
223
+ interface LocalRunner {
224
+ /** Binary to invoke — resolved from `node_modules/.bin` then PATH. */
225
+ tool?: string;
226
+ /** Args appended after the tool. */
227
+ args?: string[];
228
+ /** First entry whose `when` filename matches a repo-root file wins. */
229
+ detect?: Array<{
230
+ when: RegExp;
231
+ tool: string;
232
+ args?: string[];
233
+ }>;
234
+ /** The task is already a holocron subcommand (`sync`, `sync-wiki`). */
235
+ command?: string;
236
+ }
237
+ interface TaskDef {
238
+ /**
239
+ * `null` — no local equivalent (CodeQL, deploys). `holocron ci` reports
240
+ * it as skipped; `holocron run` treats it as "nothing to do".
241
+ */
242
+ local: LocalRunner | null;
243
+ /** Sub-jobs, keyed by slug — `holocron run audit performance`. */
244
+ jobs?: Record<string, {
245
+ local: LocalRunner | null;
246
+ }>;
247
+ /** Org-default flags injected by tool name. Removed by a repo override. */
248
+ flags?: Record<string, string[]>;
249
+ }
250
+ declare const TASKS: Record<string, TaskDef>;
251
+ /** Every task name the registry knows. */
252
+ declare const KNOWN_TASKS: Set<string>;
253
+ //#endregion
234
254
  export { type Astromech, type AstromechOptions, type ExecFn, KNOWN_TASKS, KNOWN_WORKFLOWS, type LocalRunner, type OrgContext, type PreviewConfig, type RunLogger, type RunOptions, type RunTaskInput, type RunTaskReport, TASKS, type TaskDef, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, normalizeWorkflowWith, runTask };
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { t as normalizeTaskEntry } from "./schema-Cf5dfaDO.mjs";
1
2
  import { spawnSync } from "node:child_process";
2
3
  import { existsSync, readFileSync, readdirSync } from "node:fs";
3
4
  import { join } from "node:path";
@@ -199,41 +200,6 @@ function packageJsonScript(cwd, task, readFile, fileExists) {
199
200
  }
200
201
  }
201
202
  //#endregion
202
- //#region src/astromech.ts
203
- /**
204
- * `createAstromech(options)` — the self-contained task runner.
205
- * `@theholocron/cli` instantiates it once and delegates the `run` /
206
- * `ci` / workflow-generation commands to it (like `@theholocron/logger`).
207
- */
208
- const noopLogger = {
209
- debug() {},
210
- warn() {}
211
- };
212
- const realExec = (cmd, args, opts) => {
213
- return { exitCode: spawnSync(cmd, args, {
214
- cwd: opts.cwd,
215
- stdio: "inherit"
216
- }).status ?? -1 };
217
- };
218
- function createAstromech(options) {
219
- const deps = {
220
- print: options.print ?? ((line) => console.log(line)),
221
- logger: options.logger ?? noopLogger,
222
- exec: options.exec ?? realExec,
223
- readFile: options.readFile ?? ((path) => readFileSync(path, "utf8")),
224
- fileExists: options.fileExists ?? ((path) => existsSync(path)),
225
- listDir: options.listDir ?? ((path) => readdirSync(path))
226
- };
227
- return { run: (task, opts = {}) => runTask({
228
- ...deps,
229
- task,
230
- cwd: options.cwd,
231
- passthrough: opts.passthrough ?? [],
232
- dryRun: opts.dryRun ?? false,
233
- required: opts.required ?? false
234
- }) };
235
- }
236
- //#endregion
237
203
  //#region src/thin-callers.ts
238
204
  /**
239
205
  * Workflow templates + thin-caller generation.
@@ -490,4 +456,77 @@ function deriveDeployPaths(raw) {
490
456
  return paths;
491
457
  }
492
458
  //#endregion
459
+ //#region src/astromech.ts
460
+ /**
461
+ * `createAstromech(options)` — the self-contained task runner.
462
+ * `@theholocron/cli` instantiates it once and delegates the `run` /
463
+ * `ci` / workflow-generation commands to it (like `@theholocron/logger`).
464
+ */
465
+ const noopLogger = {
466
+ debug() {},
467
+ warn() {}
468
+ };
469
+ const realExec = (cmd, args, opts) => {
470
+ return { exitCode: spawnSync(cmd, args, {
471
+ cwd: opts.cwd,
472
+ stdio: "inherit"
473
+ }).status ?? -1 };
474
+ };
475
+ function createAstromech(options) {
476
+ const deps = {
477
+ print: options.print ?? ((line) => console.log(line)),
478
+ logger: options.logger ?? noopLogger,
479
+ exec: options.exec ?? realExec,
480
+ readFile: options.readFile ?? ((path) => readFileSync(path, "utf8")),
481
+ fileExists: options.fileExists ?? ((path) => existsSync(path)),
482
+ listDir: options.listDir ?? ((path) => readdirSync(path))
483
+ };
484
+ const items = () => (options.config?.tasks ?? []).map((i) => normalizeTaskEntry(i));
485
+ return {
486
+ run: (task, opts = {}) => runTask({
487
+ ...deps,
488
+ task,
489
+ cwd: options.cwd,
490
+ passthrough: opts.passthrough ?? [],
491
+ dryRun: opts.dryRun ?? false,
492
+ required: opts.required ?? false
493
+ }),
494
+ thinCallers: () => {
495
+ const orgCtx = options.orgContext ?? {};
496
+ const out = /* @__PURE__ */ new Map();
497
+ for (const entry of items()) {
498
+ if (entry.ci === false || !KNOWN_WORKFLOWS.has(entry.name)) continue;
499
+ const rawWith = entry.with;
500
+ const normalized = rawWith ? normalizeWorkflowWith(rawWith) : void 0;
501
+ const withOverrides = entry.name === "lint" ? {
502
+ "enable-auto-commit": true,
503
+ ...normalized ?? {}
504
+ } : normalized;
505
+ const additionalPaths = entry.paths ?? (entry.name === "deploy" && rawWith ? deriveDeployPaths(rawWith) : void 0);
506
+ if (entry.name === "deploy" && rawWith) {
507
+ const preview = extractPreviewConfig(rawWith, orgCtx);
508
+ if (preview) {
509
+ const deployWith = normalizeWorkflowWith(rawWith);
510
+ const deployPaths = entry.paths ?? deriveDeployPaths(rawWith);
511
+ out.set("deploy.yml", generateCombinedDeployContent(deployWith, deployPaths, preview));
512
+ continue;
513
+ }
514
+ }
515
+ out.set(`${entry.name}.yml`, generateThinCallerContent(entry.name, withOverrides, additionalPaths, deps.logger));
516
+ }
517
+ return out;
518
+ },
519
+ packageScripts: () => {
520
+ const out = {};
521
+ if (!options.config || options.config.syncScripts === false) return out;
522
+ out.holocron = options.config.holocronScript ?? "holocron";
523
+ for (const entry of items()) {
524
+ if (entry.local === false || !KNOWN_TASKS.has(entry.name) || TASKS[entry.name]?.local === null) continue;
525
+ out[entry.name] = `holocron run ${entry.name}`;
526
+ }
527
+ return out;
528
+ }
529
+ };
530
+ }
531
+ //#endregion
493
532
  export { KNOWN_TASKS, KNOWN_WORKFLOWS, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, normalizeWorkflowWith, runTask };
@@ -0,0 +1,11 @@
1
+ //#region src/config/schema.ts
2
+ /** Normalise a `TaskConfigItem` to a full {@link TaskEntry} with defaults applied. */
3
+ function normalizeTaskEntry(item) {
4
+ return {
5
+ ci: true,
6
+ local: true,
7
+ ...typeof item === "string" ? { name: item } : item
8
+ };
9
+ }
10
+ //#endregion
11
+ export { normalizeTaskEntry as t };
@@ -34,6 +34,8 @@ interface TaskEntry {
34
34
  * from the config files present.
35
35
  */
36
36
  linters?: string[];
37
+ /** Extra `on.push.paths` entries for the generated CI workflow. */
38
+ paths?: string[];
37
39
  }
38
40
  /** A task is either its bare name (all defaults) or an entry object. */
39
41
  type TaskConfigItem = string | TaskEntry;
@@ -42,6 +44,12 @@ interface TasksConfig {
42
44
  tasks?: TaskConfigItem[];
43
45
  /** Opt out of the `package.json` script writes. Default `true`. */
44
46
  syncScripts?: boolean;
47
+ /**
48
+ * The command the synced `"holocron"` `package.json` script runs. Default
49
+ * `"holocron"` (the installed bin). The source repo overrides it to run
50
+ * its own build, e.g. `"node packages/cli/dist/cli.mjs"`.
51
+ */
52
+ holocronScript?: string;
45
53
  /**
46
54
  * Required status-check contexts not backed by a task — DCO, semantic
47
55
  * PR title, …
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/astromech",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "The Holocron task runner — one task manifest drives `holocron run`, `holocron ci`, the CI workflows, package.json scripts, linters, and required checks.",
5
5
  "keywords": [
6
6
  "ci",
@@ -37,7 +37,7 @@
37
37
  "dist"
38
38
  ],
39
39
  "dependencies": {
40
- "@theholocron/datapad": "4.1.0"
40
+ "@theholocron/datapad": "4.2.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@theholocron/eslint-config": "^8.0.0",
@@ -53,7 +53,7 @@
53
53
  "tsdown": "^0.22.14",
54
54
  "typescript": "^5.9.3",
55
55
  "vitest": "^4.1.11",
56
- "@theholocron/rollup-plugin-transform-template": "4.1.0"
56
+ "@theholocron/rollup-plugin-transform-template": "4.2.0"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=22"