@theholocron/astromech 3.65.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Newton Koumantzelis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # `@theholocron/astromech`
2
+
3
+ The Holocron task runner. One **task manifest** per repo, and every
4
+ derived surface comes from it: `holocron run` (local), `holocron ci` (the
5
+ CI suite run locally), the generated GitHub Actions workflows, the
6
+ `package.json` scripts, the linter set, and the branch-protection
7
+ required-checks list.
8
+
9
+ > An astromech droid runs a starfighter's maintenance, diagnostics and
10
+ > system wiring while the pilot flies. This does that for a repo.
11
+
12
+ A plain library — **not** a capability plugin. `@theholocron/cli` depends
13
+ on it and instantiates it once.
14
+
15
+ ## Installation
16
+
17
+ ```sh
18
+ pnpm add @theholocron/astromech
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { createAstromech } from "@theholocron/astromech";
25
+
26
+ const astromech = createAstromech({ cwd });
27
+
28
+ const report = astromech.run("test", { passthrough: ["--watch"] });
29
+ // → { status: "ok" | "fail" | "skip" | "dry-run" | "unknown", command?, message? }
30
+ ```
31
+
32
+ ### `holocron run <task>` resolution
33
+
34
+ `holocron run test` runs your tests — you don't tell it turbo vs pnpm vs
35
+ npm, or which runner:
36
+
37
+ ```
38
+ 1. turbo.json defines the task → turbo run <task>
39
+ 2. package.json has a <task> script → <detected pm> run <task>
40
+ (a "holocron run …" thin caller is skipped — no recursion)
41
+ 3. the registry has a local runner → <tool> <args> <org-flags> (e.g. --coverage)
42
+ 4. known task, nothing to run → "no <task> task", exit 0 (exit 1 with --required)
43
+ 5. unknown task → error, exit 1
44
+ ```
45
+
46
+ The registry (`TASKS`) covers `test` / `typecheck` / `lint` / `build` /
47
+ `sync` / `wiki`; `codeql` / `deploy` have no local equivalent. Adding a
48
+ task here gives every repo that task.
49
+
50
+ ## Config — `@theholocron/astromech/config`
51
+
52
+ The config surface lives in a zero-runtime-dep subpath so
53
+ `astromech.config.ts` and `@theholocron/cli` import it without pulling
54
+ the runner.
55
+
56
+ ```ts
57
+ // astromech.config.ts
58
+ import { defineConfig } from "@theholocron/astromech/config";
59
+
60
+ export default defineConfig({
61
+ tasks: [
62
+ "typecheck",
63
+ { name: "test", required: true, with: { "run-coverage": true } },
64
+ { name: "audit", ci: true, local: false }, // CI-only
65
+ ],
66
+ });
67
+ ```
68
+
69
+ `loadTasksConfig(cwd)` resolves the manifest: the `tasks` key of
70
+ `holocron.config.*` (a bare item array), then a dedicated
71
+ `astromech.config.*` merged on top (dedicated wins; arrays concatenate).
72
+
73
+ | `TaskEntry` field | Effect |
74
+ | ------------------------ | ----------------------------------------------------------- |
75
+ | `ci` (default `true`) | emit the CI workflow; include in `holocron ci` |
76
+ | `local` (default `true`) | write the `package.json` script; `holocron run` resolves it |
77
+ | `local: false` | `holocron run <name>` → "CI-only task", exit 0 |
78
+ | `required` | the task's check context is a required status check |
79
+ | `with` | per-repo overrides on the reusable-workflow channel |
80
+ | `linters` (`lint` only) | explicit linter list; omitted → auto-detect |
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).
85
+
86
+ ## Development
87
+
88
+ | Script | Description |
89
+ | -------------------- | ----------------------- |
90
+ | `pnpm build` | Bundle with tsdown |
91
+ | `pnpm test` | Run the vitest suite |
92
+ | `pnpm test:coverage` | Run tests with coverage |
93
+ | `pnpm typecheck` | `tsc --noEmit` |
94
+ | `pnpm lint` | ESLint |
95
+
96
+ ## Releases
97
+
98
+ Automated via semantic-release. See [CHANGELOG.md](../../CHANGELOG.md).
99
+
100
+ ## Documentation
101
+
102
+ <https://theholocron.github.io/holocron/>
@@ -0,0 +1,25 @@
1
+ import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskConfigItem } from "../schema-4kyr9ILV.mjs";
2
+ //#region src/config/define.d.ts
3
+ /**
4
+ * Typed identity helper for `astromech.config.ts`:
5
+ *
6
+ * ```ts
7
+ * import { defineConfig } from "@theholocron/astromech/config";
8
+ *
9
+ * export default defineConfig({
10
+ * tasks: ["typecheck", { name: "test", required: true }],
11
+ * });
12
+ * ```
13
+ */
14
+ declare const defineConfig: <C extends TasksConfig>(config: C) => C;
15
+ //#endregion
16
+ //#region src/config/load.d.ts
17
+ /**
18
+ * Resolve the task manifest for a repo: the `tasks` key of
19
+ * `holocron.config.*`, then a dedicated `astromech.config.*` merged on
20
+ * top (dedicated wins; item arrays concatenate). Returns `{}` when
21
+ * neither source is present.
22
+ */
23
+ declare function loadTasksConfig(cwd: string): Promise<TasksConfig>;
24
+ //#endregion
25
+ export { type TaskConfigItem, type TaskEntry, type TasksConfig, defineConfig, loadTasksConfig, normalizeTaskEntry };
@@ -0,0 +1,48 @@
1
+ import { createDefineConfig, loadConfigFile, mergeConfig } from "@theholocron/datapad";
2
+ //#region src/config/define.ts
3
+ /**
4
+ * Typed identity helper for `astromech.config.ts`:
5
+ *
6
+ * ```ts
7
+ * import { defineConfig } from "@theholocron/astromech/config";
8
+ *
9
+ * export default defineConfig({
10
+ * tasks: ["typecheck", { name: "test", required: true }],
11
+ * });
12
+ * ```
13
+ */
14
+ const defineConfig = createDefineConfig();
15
+ //#endregion
16
+ //#region src/config/load.ts
17
+ /**
18
+ * Resolve the task manifest for a repo: the `tasks` key of
19
+ * `holocron.config.*`, then a dedicated `astromech.config.*` merged on
20
+ * top (dedicated wins; item arrays concatenate). Returns `{}` when
21
+ * neither source is present.
22
+ */
23
+ async function loadTasksConfig(cwd) {
24
+ const dedicated = await loadConfigFile({
25
+ cwd,
26
+ name: "astromech"
27
+ });
28
+ return [coerce((await loadConfigFile({
29
+ cwd,
30
+ name: "holocron"
31
+ }))?.config.tasks), coerce(dedicated?.config)].filter((layer) => layer !== void 0).reduce((acc, layer) => mergeConfig(acc, layer), {});
32
+ }
33
+ function coerce(value) {
34
+ if (value == null) return void 0;
35
+ return Array.isArray(value) ? { tasks: value } : value;
36
+ }
37
+ //#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
+ export { defineConfig, loadTasksConfig, normalizeTaskEntry };
@@ -0,0 +1,136 @@
1
+ import { r as TasksConfig } from "./schema-4kyr9ILV.mjs";
2
+ //#region src/run.d.ts
3
+ /**
4
+ * `holocron run <task> [-- <passthrough>]` — run a registry task locally.
5
+ *
6
+ * Resolution:
7
+ *
8
+ * 1. turbo.json defines the task → `turbo run <task>`
9
+ * 2. package.json has a `<task>` script → `<pm> run <task>`
10
+ * (unless it's the `holocron run …` thin caller — that recurses)
11
+ * 3. TASKS[task].local resolves → `<tool> <args> <org-flags> <passthrough>`
12
+ * 4. known task, nothing to run → "no <task> task" (exit 0, or 1 with --required)
13
+ * 5. unknown task → "unknown task" (exit 1)
14
+ */
15
+ /** Minimal structural logger — `@theholocron/logger`'s `Logger` satisfies it. */
16
+ interface RunLogger {
17
+ debug(obj: Record<string, unknown>, msg?: string): void;
18
+ warn(obj: Record<string, unknown>, msg?: string): void;
19
+ }
20
+ type ExecFn = (cmd: string, args: string[], opts: {
21
+ cwd: string;
22
+ }) => {
23
+ exitCode: number;
24
+ };
25
+ /**
26
+ * Everything `runTask` needs from the outside world. `createAstromech`
27
+ * fills these with real implementations (or the caller's injected ones);
28
+ * `runTask` never reaches for a global itself, so it has no default
29
+ * branches to leave untested.
30
+ */
31
+ interface RunDeps {
32
+ print: (line: string) => void;
33
+ logger: RunLogger;
34
+ exec: ExecFn;
35
+ readFile: (path: string) => string;
36
+ fileExists: (path: string) => boolean;
37
+ listDir: (path: string) => string[];
38
+ }
39
+ interface RunTaskInput extends RunDeps {
40
+ /** Registry task name, e.g. `"test"`. */
41
+ task: string;
42
+ /** Directory to run in. */
43
+ cwd: string;
44
+ /** Args after `--` on the command line, forwarded to the tool / turbo / script. */
45
+ passthrough?: string[];
46
+ /** Print the resolved command without running it. */
47
+ dryRun?: boolean;
48
+ /** Turn "no such task for this repo" (normally exit 0) into a failure. */
49
+ required?: boolean;
50
+ }
51
+ interface RunTaskReport {
52
+ status: "ok" | "fail" | "skip" | "dry-run" | "unknown";
53
+ /** Resolved command, for the caller / tests. */
54
+ command?: string;
55
+ message?: string;
56
+ }
57
+ declare function runTask(input: RunTaskInput): RunTaskReport;
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
+ export { type Astromech, type AstromechOptions, type ExecFn, KNOWN_TASKS, type LocalRunner, type RunLogger, type RunOptions, type RunTaskInput, type RunTaskReport, TASKS, type TaskDef, createAstromech, runTask };
package/dist/index.mjs ADDED
@@ -0,0 +1,237 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ //#region src/registry.ts
5
+ const TASKS = {
6
+ test: {
7
+ local: {
8
+ tool: "vitest",
9
+ args: ["run"]
10
+ },
11
+ flags: { vitest: ["--coverage"] }
12
+ },
13
+ typecheck: { local: {
14
+ tool: "tsc",
15
+ args: ["--noEmit"]
16
+ } },
17
+ lint: { local: {
18
+ tool: "eslint",
19
+ args: ["."]
20
+ } },
21
+ build: { local: { detect: [
22
+ {
23
+ when: /^tsdown\.config\.(ts|js|mjs|cjs)$/,
24
+ tool: "tsdown"
25
+ },
26
+ {
27
+ when: /^vite\.config\.(ts|js|mjs|cjs)$/,
28
+ tool: "vite",
29
+ args: ["build"]
30
+ },
31
+ {
32
+ when: /^rollup\.config\.(ts|js|mjs|cjs)$/,
33
+ tool: "rollup",
34
+ args: ["-c"]
35
+ },
36
+ {
37
+ when: /^tsconfig\.json$/,
38
+ tool: "tsc",
39
+ args: ["-b"]
40
+ }
41
+ ] } },
42
+ sync: { local: { command: "sync" } },
43
+ wiki: { local: { command: "sync-wiki" } },
44
+ codeql: { local: null },
45
+ deploy: { local: null }
46
+ };
47
+ /** Every task name the registry knows. */
48
+ const KNOWN_TASKS = new Set(Object.keys(TASKS));
49
+ //#endregion
50
+ //#region src/run.ts
51
+ /**
52
+ * `holocron run <task> [-- <passthrough>]` — run a registry task locally.
53
+ *
54
+ * Resolution:
55
+ *
56
+ * 1. turbo.json defines the task → `turbo run <task>`
57
+ * 2. package.json has a `<task>` script → `<pm> run <task>`
58
+ * (unless it's the `holocron run …` thin caller — that recurses)
59
+ * 3. TASKS[task].local resolves → `<tool> <args> <org-flags> <passthrough>`
60
+ * 4. known task, nothing to run → "no <task> task" (exit 0, or 1 with --required)
61
+ * 5. unknown task → "unknown task" (exit 1)
62
+ */
63
+ function runTask(input) {
64
+ const { print, logger, exec, readFile, fileExists, listDir, task, cwd } = input;
65
+ const passthrough = input.passthrough ?? [];
66
+ const dryRun = input.dryRun ?? false;
67
+ const run = (cmd, args) => {
68
+ const command = [cmd, ...args].join(" ");
69
+ if (dryRun) {
70
+ print(`would run: ${command}`);
71
+ logger.debug({
72
+ task,
73
+ command,
74
+ status: "dry-run"
75
+ }, `run: ${task}`);
76
+ return {
77
+ status: "dry-run",
78
+ command
79
+ };
80
+ }
81
+ print(`→ ${command}`);
82
+ const { exitCode } = exec(cmd, args, { cwd });
83
+ const status = exitCode === 0 ? "ok" : "fail";
84
+ logger[status === "fail" ? "warn" : "debug"]({
85
+ task,
86
+ command,
87
+ exitCode,
88
+ status
89
+ }, `run: ${task}`);
90
+ return {
91
+ status,
92
+ command,
93
+ ...status === "fail" ? { message: `\`${command}\` exited ${exitCode}` } : {}
94
+ };
95
+ };
96
+ if (turboDefinesTask(cwd, task, readFile, fileExists)) {
97
+ const args = [
98
+ "run",
99
+ task,
100
+ ...passthrough.length ? ["--", ...passthrough] : []
101
+ ];
102
+ return run(resolveBin(cwd, "turbo", fileExists), args);
103
+ }
104
+ const def = TASKS[task];
105
+ const script = packageJsonScript(cwd, task, readFile, fileExists);
106
+ if (script && !/^holocron run\b/.test(script.trim())) return run(packageManager(cwd, readFile, fileExists), [
107
+ "run",
108
+ task,
109
+ ...passthrough.length ? ["--", ...passthrough] : []
110
+ ]);
111
+ if (def?.local) {
112
+ if (def.local.command) return run(process.execPath, [
113
+ process.argv[1],
114
+ def.local.command,
115
+ ...passthrough
116
+ ]);
117
+ const runner = resolveRunner(def.local, cwd, listDir);
118
+ if (runner) {
119
+ const flags = def.flags?.[runner.tool] ?? [];
120
+ return run(resolveBin(cwd, runner.tool, fileExists), [
121
+ ...runner.args,
122
+ ...flags,
123
+ ...passthrough
124
+ ]);
125
+ }
126
+ }
127
+ if (KNOWN_TASKS.has(task) || def?.local === null) {
128
+ const msg = `no ${task} task for this repo`;
129
+ print(input.required ? `✗ ${msg} (required)` : `· ${msg}`);
130
+ logger[input.required ? "warn" : "debug"]({
131
+ task,
132
+ status: input.required ? "fail" : "skip"
133
+ }, `run: ${task}`);
134
+ return {
135
+ status: input.required ? "fail" : "skip",
136
+ message: msg
137
+ };
138
+ }
139
+ print(`✗ unknown task "${task}"`);
140
+ logger.warn({
141
+ task,
142
+ status: "unknown"
143
+ }, `run: ${task}`);
144
+ return {
145
+ status: "unknown",
146
+ message: `unknown task "${task}"`
147
+ };
148
+ }
149
+ const mkRunner = (tool, args = []) => ({
150
+ tool,
151
+ args
152
+ });
153
+ /**
154
+ * Resolve `{ tool, args }` for a runner, applying `detect[]` against repo
155
+ * files. Only called for `tool` / `detect` runners — the caller handles
156
+ * `command` runners itself, so `local.detect` is present whenever
157
+ * `local.tool` is not.
158
+ */
159
+ function resolveRunner(local, cwd, listDir) {
160
+ if (local.tool) return mkRunner(local.tool, local.args);
161
+ let files;
162
+ try {
163
+ files = listDir(cwd);
164
+ } catch {
165
+ return;
166
+ }
167
+ for (const candidate of local.detect) if (files.some((f) => candidate.when.test(f))) return mkRunner(candidate.tool, candidate.args);
168
+ }
169
+ function resolveBin(cwd, tool, fileExists) {
170
+ const local = join(cwd, "node_modules", ".bin", tool);
171
+ return fileExists(local) ? local : tool;
172
+ }
173
+ function packageManager(cwd, readFile, fileExists) {
174
+ try {
175
+ const pkg = JSON.parse(readFile(join(cwd, "package.json")));
176
+ if (typeof pkg.packageManager === "string") return pkg.packageManager.split("@")[0];
177
+ } catch {}
178
+ if (fileExists(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
179
+ if (fileExists(join(cwd, "bun.lockb"))) return "bun";
180
+ if (fileExists(join(cwd, "yarn.lock"))) return "yarn";
181
+ if (fileExists(join(cwd, "package-lock.json"))) return "npm";
182
+ return "pnpm";
183
+ }
184
+ function turboDefinesTask(cwd, task, readFile, fileExists) {
185
+ if (!fileExists(join(cwd, "turbo.json"))) return false;
186
+ try {
187
+ const turbo = JSON.parse(readFile(join(cwd, "turbo.json")));
188
+ return Boolean(turbo.tasks?.[task] ?? turbo.pipeline?.[task]);
189
+ } catch {
190
+ return false;
191
+ }
192
+ }
193
+ function packageJsonScript(cwd, task, readFile, fileExists) {
194
+ if (!fileExists(join(cwd, "package.json"))) return void 0;
195
+ try {
196
+ return JSON.parse(readFile(join(cwd, "package.json"))).scripts?.[task];
197
+ } catch {
198
+ return;
199
+ }
200
+ }
201
+ //#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
+ export { KNOWN_TASKS, TASKS, createAstromech, runTask };
@@ -0,0 +1,54 @@
1
+ //#region src/config/schema.d.ts
2
+ /**
3
+ * The `tasks` config shape — the single manifest of what a repo runs.
4
+ * Read from a dedicated `astromech.config.*` file or the `tasks` key of
5
+ * `holocron.config.*` (see {@link loadTasksConfig}).
6
+ *
7
+ * Runtime validation is the loader's job, not this type's — the type only
8
+ * describes what an author writes.
9
+ */
10
+ interface TaskEntry {
11
+ /** Registry task name — `test`, `lint`, `build`, `audit`, … */
12
+ name: string;
13
+ /**
14
+ * Emit a `.github/workflows/<name>.yml` thin caller and include the task
15
+ * in `holocron ci`. Default `true`.
16
+ */
17
+ ci?: boolean;
18
+ /**
19
+ * Write a `"<name>": "holocron run <name>"` script to `package.json` and
20
+ * let `holocron run <name>` resolve the task. Default `true`.
21
+ * `false` → `holocron run <name>` prints "CI-only task" and exits 0.
22
+ */
23
+ local?: boolean;
24
+ /**
25
+ * The task's CI check context is a required status check (branch
26
+ * protection) and part of `holocron ci`'s default run.
27
+ */
28
+ required?: boolean;
29
+ /** Per-repo overrides on the same channel the reusable workflow reads. */
30
+ with?: Record<string, unknown>;
31
+ /**
32
+ * `lint` only — the explicit linter list driving both super-linter's
33
+ * `VALIDATE_*` env (CI) and the native local run. Omitted → auto-detect
34
+ * from the config files present.
35
+ */
36
+ linters?: string[];
37
+ }
38
+ /** A task is either its bare name (all defaults) or an entry object. */
39
+ type TaskConfigItem = string | TaskEntry;
40
+ interface TasksConfig {
41
+ /** The manifest. */
42
+ tasks?: TaskConfigItem[];
43
+ /** Opt out of the `package.json` script writes. Default `true`. */
44
+ syncScripts?: boolean;
45
+ /**
46
+ * Required status-check contexts not backed by a task — DCO, semantic
47
+ * PR title, …
48
+ */
49
+ extraRequiredChecks?: string[];
50
+ }
51
+ /** Normalise a `TaskConfigItem` to a full {@link TaskEntry} with defaults applied. */
52
+ declare function normalizeTaskEntry(item: TaskConfigItem): Required<Pick<TaskEntry, "name" | "ci" | "local">> & TaskEntry;
53
+ //#endregion
54
+ export { normalizeTaskEntry as i, TaskEntry as n, TasksConfig as r, TaskConfigItem as t };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@theholocron/astromech",
3
+ "version": "3.65.0",
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
+ "keywords": [
6
+ "ci",
7
+ "holocron",
8
+ "monorepo",
9
+ "task-runner",
10
+ "theholocron",
11
+ "turbo"
12
+ ],
13
+ "homepage": "https://github.com/theholocron/holocron/tree/main/packages/astromech#readme",
14
+ "bugs": "https://github.com/theholocron/holocron/issues",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/theholocron/holocron.git",
18
+ "directory": "packages/astromech"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Newton Koumantzelis",
22
+ "sideEffects": false,
23
+ "type": "module",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.mts",
27
+ "import": "./dist/index.mjs",
28
+ "default": "./dist/index.mjs"
29
+ },
30
+ "./config": {
31
+ "types": "./dist/config/index.d.mts",
32
+ "import": "./dist/config/index.mjs",
33
+ "default": "./dist/config/index.mjs"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "dependencies": {
40
+ "@theholocron/datapad": "3.65.0"
41
+ },
42
+ "devDependencies": {
43
+ "@theholocron/eslint-config": "^7.32.1",
44
+ "@theholocron/tsconfig": "^7.32.1",
45
+ "@theholocron/tsdown-config": "^7.32.1",
46
+ "@theholocron/vitest-config": "^7.32.1",
47
+ "@types/node": "^26",
48
+ "@vitest/coverage-v8": "^4.1.11",
49
+ "@vitest/eslint-plugin": "^1.6.27",
50
+ "eslint": "^10.8.1",
51
+ "eslint-plugin-n": "^18.3.0",
52
+ "globals": "^17.11.0",
53
+ "tsdown": "^0.22.14",
54
+ "typescript": "^5.9.3",
55
+ "vitest": "^4.1.11"
56
+ },
57
+ "engines": {
58
+ "node": ">=22"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "scripts": {
64
+ "build": "tsdown",
65
+ "lint": "eslint .",
66
+ "typecheck": "tsc --noEmit",
67
+ "test": "vitest run",
68
+ "test:watch": "vitest",
69
+ "test:coverage": "vitest run --coverage"
70
+ }
71
+ }