@theholocron/astromech 4.5.0 → 4.6.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 +1 -1
- package/dist/index.d.mts +13 -0
- package/dist/index.mjs +235 -136
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ one place, `src/linters.ts`.
|
|
|
119
119
|
| `actionlint` | `GITHUB_ACTIONS` | yes | `actionlint` (usually CI-only) |
|
|
120
120
|
| `gitleaks` | `GITLEAKS` | yes | `gitleaks dir` (usually CI-only) |
|
|
121
121
|
| `editorconfig` | `EDITORCONFIG` | yes | `editorconfig-checker` (usually CI-only) |
|
|
122
|
-
| `commitlint` | `GIT_COMMITLINT` | yes |
|
|
122
|
+
| `commitlint` | `GIT_COMMITLINT` | yes | — (commit-msg hook + CI) |
|
|
123
123
|
| `git-merge-conflict-markers` | `GIT_MERGE_CONFLICT_MARKERS` | yes | — (CI only) |
|
|
124
124
|
| `markdownlint` | `MARKDOWN` | on `.markdownlint*` | `markdownlint-cli2` |
|
|
125
125
|
|
package/dist/index.d.mts
CHANGED
|
@@ -5,12 +5,19 @@ import { r as TasksConfig } from "./schema-DbpiBZCP.mjs";
|
|
|
5
5
|
*
|
|
6
6
|
* Resolution:
|
|
7
7
|
*
|
|
8
|
+
* 0. task === "lint" → the linter aggregate (see below)
|
|
8
9
|
* 1. turbo.json defines the task → `turbo run <task>`
|
|
9
10
|
* 2. package.json has a `<task>` script → `<pm> run <task>`
|
|
10
11
|
* (unless it's the `holocron run …` thin caller — that recurses)
|
|
11
12
|
* 3. TASKS[task].local resolves → `<tool> <args> <org-flags> <passthrough>`
|
|
12
13
|
* 4. known task, nothing to run → "no <task> task" (exit 0, or 1 with --required)
|
|
13
14
|
* 5. unknown task → "unknown task" (exit 1)
|
|
15
|
+
*
|
|
16
|
+
* `lint` runs the resolved linter set (`config.tasks` `linters`, else
|
|
17
|
+
* auto-detected): the eslint slot goes through the standard turbo / script /
|
|
18
|
+
* `eslint .` resolution (so turbo caching is kept); every other linter runs
|
|
19
|
+
* its `localBin` when found on PATH. Missing tools are flagged; the exit code
|
|
20
|
+
* is the worst of the lot.
|
|
14
21
|
*/
|
|
15
22
|
/** Minimal structural logger — `@theholocron/logger`'s `Logger` satisfies it. */
|
|
16
23
|
interface RunLogger {
|
|
@@ -35,6 +42,8 @@ interface RunDeps {
|
|
|
35
42
|
readFile: (path: string) => string;
|
|
36
43
|
fileExists: (path: string) => boolean;
|
|
37
44
|
listDir: (path: string) => string[];
|
|
45
|
+
/** `node_modules/.bin/<bin>` or a PATH entry; `null` when not runnable. */
|
|
46
|
+
lookPath: (cwd: string, bin: string) => string | null;
|
|
38
47
|
}
|
|
39
48
|
interface RunTaskInput extends RunDeps {
|
|
40
49
|
/** Registry task name, e.g. `"test"`. */
|
|
@@ -47,6 +56,8 @@ interface RunTaskInput extends RunDeps {
|
|
|
47
56
|
dryRun?: boolean;
|
|
48
57
|
/** Turn "no such task for this repo" (normally exit 0) into a failure. */
|
|
49
58
|
required?: boolean;
|
|
59
|
+
/** The `lint` task's explicit linter list from `config.tasks`, if any. */
|
|
60
|
+
linters?: string[];
|
|
50
61
|
}
|
|
51
62
|
interface RunTaskReport {
|
|
52
63
|
status: "ok" | "fail" | "skip" | "dry-run" | "unknown";
|
|
@@ -242,6 +253,8 @@ interface AstromechOptions {
|
|
|
242
253
|
readFile?: (path: string) => string;
|
|
243
254
|
fileExists?: (path: string) => boolean;
|
|
244
255
|
listDir?: (path: string) => string[];
|
|
256
|
+
/** Injectable binary lookup (tests). Default checks `node_modules/.bin` then `PATH`. */
|
|
257
|
+
lookPath?: (cwd: string, bin: string) => string | null;
|
|
245
258
|
}
|
|
246
259
|
interface RunOptions {
|
|
247
260
|
/** Args after `--`, forwarded to the tool / turbo / script. */
|
package/dist/index.mjs
CHANGED
|
@@ -51,24 +51,159 @@ const TASKS = {
|
|
|
51
51
|
/** Every task name the registry knows. */
|
|
52
52
|
const KNOWN_TASKS = new Set(Object.keys(TASKS));
|
|
53
53
|
//#endregion
|
|
54
|
+
//#region src/linters.ts
|
|
55
|
+
/**
|
|
56
|
+
* Known linters, in execution order. `always` entries are the current
|
|
57
|
+
* hard-coded super-linter baseline; `prettier` is always-on because the org
|
|
58
|
+
* applies it universally (super-linter only lints files that exist).
|
|
59
|
+
*/
|
|
60
|
+
const LINTERS = {
|
|
61
|
+
eslint: {
|
|
62
|
+
validate: ["VALIDATE_JAVASCRIPT_ES", "VALIDATE_TYPESCRIPT_ES"],
|
|
63
|
+
localBin: "eslint",
|
|
64
|
+
localArgs: ["."],
|
|
65
|
+
detect: [
|
|
66
|
+
"eslint.config.ts",
|
|
67
|
+
"eslint.config.js",
|
|
68
|
+
"eslint.config.mjs",
|
|
69
|
+
"eslint.config.cjs",
|
|
70
|
+
".eslintrc",
|
|
71
|
+
".eslintrc.json",
|
|
72
|
+
".eslintrc.yml",
|
|
73
|
+
".eslintrc.yaml",
|
|
74
|
+
".eslintrc.cjs"
|
|
75
|
+
],
|
|
76
|
+
configInput: "eslint-config"
|
|
77
|
+
},
|
|
78
|
+
prettier: {
|
|
79
|
+
validate: [
|
|
80
|
+
"VALIDATE_JAVASCRIPT_PRETTIER",
|
|
81
|
+
"VALIDATE_JSX_PRETTIER",
|
|
82
|
+
"VALIDATE_TYPESCRIPT_PRETTIER",
|
|
83
|
+
"VALIDATE_TSX",
|
|
84
|
+
"VALIDATE_MARKDOWN_PRETTIER"
|
|
85
|
+
],
|
|
86
|
+
fix: [
|
|
87
|
+
"FIX_JAVASCRIPT_PRETTIER",
|
|
88
|
+
"FIX_JSX_PRETTIER",
|
|
89
|
+
"FIX_TYPESCRIPT_PRETTIER",
|
|
90
|
+
"FIX_TSX",
|
|
91
|
+
"FIX_MARKDOWN_PRETTIER"
|
|
92
|
+
],
|
|
93
|
+
always: true,
|
|
94
|
+
localBin: "prettier",
|
|
95
|
+
localArgs: ["--check", "."],
|
|
96
|
+
configInput: "prettier-config"
|
|
97
|
+
},
|
|
98
|
+
yamllint: {
|
|
99
|
+
validate: ["VALIDATE_YAML"],
|
|
100
|
+
always: true,
|
|
101
|
+
localBin: "yamllint",
|
|
102
|
+
localArgs: ["."],
|
|
103
|
+
installHint: "brew install yamllint",
|
|
104
|
+
configInput: "yaml-config"
|
|
105
|
+
},
|
|
106
|
+
actionlint: {
|
|
107
|
+
validate: ["VALIDATE_GITHUB_ACTIONS"],
|
|
108
|
+
always: true,
|
|
109
|
+
localBin: "actionlint",
|
|
110
|
+
localArgs: [],
|
|
111
|
+
installHint: "brew install actionlint"
|
|
112
|
+
},
|
|
113
|
+
gitleaks: {
|
|
114
|
+
validate: ["VALIDATE_GITLEAKS"],
|
|
115
|
+
always: true,
|
|
116
|
+
localBin: "gitleaks",
|
|
117
|
+
localArgs: ["dir", "--no-banner"],
|
|
118
|
+
installHint: "brew install gitleaks"
|
|
119
|
+
},
|
|
120
|
+
editorconfig: {
|
|
121
|
+
validate: ["VALIDATE_EDITORCONFIG"],
|
|
122
|
+
always: true,
|
|
123
|
+
localBin: "editorconfig-checker",
|
|
124
|
+
localArgs: [],
|
|
125
|
+
installHint: "brew install editorconfig-checker"
|
|
126
|
+
},
|
|
127
|
+
commitlint: {
|
|
128
|
+
validate: ["VALIDATE_GIT_COMMITLINT"],
|
|
129
|
+
always: true
|
|
130
|
+
},
|
|
131
|
+
"git-merge-conflict-markers": {
|
|
132
|
+
validate: ["VALIDATE_GIT_MERGE_CONFLICT_MARKERS"],
|
|
133
|
+
always: true
|
|
134
|
+
},
|
|
135
|
+
markdownlint: {
|
|
136
|
+
validate: ["VALIDATE_MARKDOWN"],
|
|
137
|
+
localBin: "markdownlint-cli2",
|
|
138
|
+
localArgs: ["**/*.md"],
|
|
139
|
+
detect: [
|
|
140
|
+
".markdownlint.json",
|
|
141
|
+
".markdownlint.jsonc",
|
|
142
|
+
".markdownlint.yaml",
|
|
143
|
+
".markdownlint.yml",
|
|
144
|
+
".markdownlint-cli2.jsonc",
|
|
145
|
+
".markdownlint-cli2.yaml",
|
|
146
|
+
".markdownlint-cli2.mjs"
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
/** Every linter name the registry knows. */
|
|
151
|
+
const LINTER_NAMES = new Set(Object.keys(LINTERS));
|
|
152
|
+
/**
|
|
153
|
+
* Resolve the linter set for a repo. An `explicit` list (from
|
|
154
|
+
* `config.tasks`) wins verbatim; otherwise every `always` linter plus every
|
|
155
|
+
* linter whose `detect` filenames are present at the repo root. Result is
|
|
156
|
+
* ordered by {@link LINTERS} declaration order.
|
|
157
|
+
*
|
|
158
|
+
* @throws when an `explicit` name is not in the registry — a typo is a
|
|
159
|
+
* config bug, not a linter to silently skip.
|
|
160
|
+
*/
|
|
161
|
+
function resolveLinters(opts) {
|
|
162
|
+
const order = Object.keys(LINTERS);
|
|
163
|
+
if (opts.explicit && opts.explicit.length > 0) {
|
|
164
|
+
const unknown = opts.explicit.filter((n) => !LINTER_NAMES.has(n));
|
|
165
|
+
if (unknown.length > 0) throw new Error(`unknown linter${unknown.length > 1 ? "s" : ""} ${unknown.map((n) => `"${n}"`).join(", ")} — known: ${order.join(", ")}`);
|
|
166
|
+
const wanted = new Set(opts.explicit);
|
|
167
|
+
return order.filter((n) => wanted.has(n)).map((name) => ({
|
|
168
|
+
name,
|
|
169
|
+
def: LINTERS[name]
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
const present = new Set(opts.rootFiles);
|
|
173
|
+
return order.filter((name) => {
|
|
174
|
+
const def = LINTERS[name];
|
|
175
|
+
return def.always === true || def.detect.some((f) => present.has(f));
|
|
176
|
+
}).map((name) => ({
|
|
177
|
+
name,
|
|
178
|
+
def: LINTERS[name]
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
54
182
|
//#region src/run.ts
|
|
55
183
|
/**
|
|
56
184
|
* `holocron run <task> [-- <passthrough>]` — run a registry task locally.
|
|
57
185
|
*
|
|
58
186
|
* Resolution:
|
|
59
187
|
*
|
|
188
|
+
* 0. task === "lint" → the linter aggregate (see below)
|
|
60
189
|
* 1. turbo.json defines the task → `turbo run <task>`
|
|
61
190
|
* 2. package.json has a `<task>` script → `<pm> run <task>`
|
|
62
191
|
* (unless it's the `holocron run …` thin caller — that recurses)
|
|
63
192
|
* 3. TASKS[task].local resolves → `<tool> <args> <org-flags> <passthrough>`
|
|
64
193
|
* 4. known task, nothing to run → "no <task> task" (exit 0, or 1 with --required)
|
|
65
194
|
* 5. unknown task → "unknown task" (exit 1)
|
|
195
|
+
*
|
|
196
|
+
* `lint` runs the resolved linter set (`config.tasks` `linters`, else
|
|
197
|
+
* auto-detected): the eslint slot goes through the standard turbo / script /
|
|
198
|
+
* `eslint .` resolution (so turbo caching is kept); every other linter runs
|
|
199
|
+
* its `localBin` when found on PATH. Missing tools are flagged; the exit code
|
|
200
|
+
* is the worst of the lot.
|
|
66
201
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
202
|
+
/** The per-command runner — `runTask` and `runLintAggregate` share it. */
|
|
203
|
+
function makeRunOne(input) {
|
|
204
|
+
const { print, logger, exec, task, cwd } = input;
|
|
70
205
|
const dryRun = input.dryRun ?? false;
|
|
71
|
-
|
|
206
|
+
return (cmd, args) => {
|
|
72
207
|
const command = [cmd, ...args].join(" ");
|
|
73
208
|
if (dryRun) {
|
|
74
209
|
print(`would run: ${command}`);
|
|
@@ -97,6 +232,12 @@ function runTask(input) {
|
|
|
97
232
|
...status === "fail" ? { message: `\`${command}\` exited ${exitCode}` } : {}
|
|
98
233
|
};
|
|
99
234
|
};
|
|
235
|
+
}
|
|
236
|
+
function runTask(input) {
|
|
237
|
+
const { print, logger, readFile, fileExists, listDir, task, cwd } = input;
|
|
238
|
+
const passthrough = input.passthrough ?? [];
|
|
239
|
+
const run = makeRunOne(input);
|
|
240
|
+
if (task === "lint") return runLintAggregate(input);
|
|
100
241
|
if (turboDefinesTask(cwd, task, readFile, fileExists)) {
|
|
101
242
|
const args = [
|
|
102
243
|
"run",
|
|
@@ -150,6 +291,85 @@ function runTask(input) {
|
|
|
150
291
|
message: `unknown task "${task}"`
|
|
151
292
|
};
|
|
152
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* `holocron run lint` — the resolved linter set, run natively. The eslint
|
|
296
|
+
* slot reuses turbo / the `lint` script / `eslint .`; the rest run their
|
|
297
|
+
* `localBin` when it resolves on PATH. Worst exit code wins.
|
|
298
|
+
*/
|
|
299
|
+
function runLintAggregate(input) {
|
|
300
|
+
const { print, logger, readFile, fileExists, listDir, lookPath, cwd } = input;
|
|
301
|
+
const passthrough = input.passthrough ?? [];
|
|
302
|
+
const dryRun = input.dryRun ?? false;
|
|
303
|
+
const runOne = makeRunOne(input);
|
|
304
|
+
const pass = passthrough.length ? ["--", ...passthrough] : [];
|
|
305
|
+
let rootFiles;
|
|
306
|
+
try {
|
|
307
|
+
rootFiles = listDir(cwd);
|
|
308
|
+
} catch {
|
|
309
|
+
rootFiles = [];
|
|
310
|
+
}
|
|
311
|
+
const resolved = resolveLinters({
|
|
312
|
+
explicit: input.linters,
|
|
313
|
+
rootFiles
|
|
314
|
+
});
|
|
315
|
+
const reports = [];
|
|
316
|
+
/** Run one linter's `localBin` natively, or flag it. */
|
|
317
|
+
const runLinter = (name, bin, args, hint) => {
|
|
318
|
+
if (!bin) {
|
|
319
|
+
print(`· ${name} (CI only)`);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const found = lookPath(cwd, bin);
|
|
323
|
+
if (!found) {
|
|
324
|
+
print(`! ${name} — ${bin} not on PATH${hint ? `. ${hint}` : ""} (enforced in CI)`);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
reports.push(runOne(found, [...args, ...passthrough]));
|
|
328
|
+
};
|
|
329
|
+
if (resolved.some((r) => r.name === "eslint")) {
|
|
330
|
+
const script = packageJsonScript(cwd, "lint", readFile, fileExists);
|
|
331
|
+
if (turboDefinesTask(cwd, "lint", readFile, fileExists)) reports.push(runOne(resolveBin(cwd, "turbo", fileExists), [
|
|
332
|
+
"run",
|
|
333
|
+
"lint",
|
|
334
|
+
...pass
|
|
335
|
+
]));
|
|
336
|
+
else if (script && !/^holocron run\b/.test(script.trim())) reports.push(runOne(packageManager(cwd, readFile, fileExists), [
|
|
337
|
+
"run",
|
|
338
|
+
"lint",
|
|
339
|
+
...pass
|
|
340
|
+
]));
|
|
341
|
+
else runLinter("eslint", "eslint", ["."]);
|
|
342
|
+
}
|
|
343
|
+
for (const { name, def } of resolved) {
|
|
344
|
+
if (name === "eslint") continue;
|
|
345
|
+
runLinter(name, def.localBin, def.localArgs ?? [], def.installHint);
|
|
346
|
+
}
|
|
347
|
+
if (reports.length === 0) {
|
|
348
|
+
const msg = "no lint tooling available locally — every resolved linter is CI-only here";
|
|
349
|
+
print(input.required ? `✗ ${msg} (required)` : `· ${msg}`);
|
|
350
|
+
logger[input.required ? "warn" : "debug"]({
|
|
351
|
+
task: "lint",
|
|
352
|
+
status: input.required ? "fail" : "skip"
|
|
353
|
+
}, "run: lint");
|
|
354
|
+
return {
|
|
355
|
+
status: input.required ? "fail" : "skip",
|
|
356
|
+
message: msg
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const command = reports.map((r) => r.command).filter((c) => Boolean(c)).join(" && ");
|
|
360
|
+
if (dryRun) return {
|
|
361
|
+
status: "dry-run",
|
|
362
|
+
command
|
|
363
|
+
};
|
|
364
|
+
return reports.some((r) => r.status === "fail") ? {
|
|
365
|
+
status: "fail",
|
|
366
|
+
command,
|
|
367
|
+
message: "one or more linters failed"
|
|
368
|
+
} : {
|
|
369
|
+
status: "ok",
|
|
370
|
+
command
|
|
371
|
+
};
|
|
372
|
+
}
|
|
153
373
|
const mkRunner = (tool, args = []) => ({
|
|
154
374
|
tool,
|
|
155
375
|
args
|
|
@@ -203,136 +423,6 @@ function packageJsonScript(cwd, task, readFile, fileExists) {
|
|
|
203
423
|
}
|
|
204
424
|
}
|
|
205
425
|
//#endregion
|
|
206
|
-
//#region src/linters.ts
|
|
207
|
-
/**
|
|
208
|
-
* Known linters, in execution order. `always` entries are the current
|
|
209
|
-
* hard-coded super-linter baseline; `prettier` is always-on because the org
|
|
210
|
-
* applies it universally (super-linter only lints files that exist).
|
|
211
|
-
*/
|
|
212
|
-
const LINTERS = {
|
|
213
|
-
eslint: {
|
|
214
|
-
validate: ["VALIDATE_JAVASCRIPT_ES", "VALIDATE_TYPESCRIPT_ES"],
|
|
215
|
-
localBin: "eslint",
|
|
216
|
-
localArgs: ["."],
|
|
217
|
-
detect: [
|
|
218
|
-
"eslint.config.ts",
|
|
219
|
-
"eslint.config.js",
|
|
220
|
-
"eslint.config.mjs",
|
|
221
|
-
"eslint.config.cjs",
|
|
222
|
-
".eslintrc",
|
|
223
|
-
".eslintrc.json",
|
|
224
|
-
".eslintrc.yml",
|
|
225
|
-
".eslintrc.yaml",
|
|
226
|
-
".eslintrc.cjs"
|
|
227
|
-
],
|
|
228
|
-
configInput: "eslint-config"
|
|
229
|
-
},
|
|
230
|
-
prettier: {
|
|
231
|
-
validate: [
|
|
232
|
-
"VALIDATE_JAVASCRIPT_PRETTIER",
|
|
233
|
-
"VALIDATE_JSX_PRETTIER",
|
|
234
|
-
"VALIDATE_TYPESCRIPT_PRETTIER",
|
|
235
|
-
"VALIDATE_TSX",
|
|
236
|
-
"VALIDATE_MARKDOWN_PRETTIER"
|
|
237
|
-
],
|
|
238
|
-
fix: [
|
|
239
|
-
"FIX_JAVASCRIPT_PRETTIER",
|
|
240
|
-
"FIX_JSX_PRETTIER",
|
|
241
|
-
"FIX_TYPESCRIPT_PRETTIER",
|
|
242
|
-
"FIX_TSX",
|
|
243
|
-
"FIX_MARKDOWN_PRETTIER"
|
|
244
|
-
],
|
|
245
|
-
always: true,
|
|
246
|
-
localBin: "prettier",
|
|
247
|
-
localArgs: ["--check", "."],
|
|
248
|
-
configInput: "prettier-config"
|
|
249
|
-
},
|
|
250
|
-
yamllint: {
|
|
251
|
-
validate: ["VALIDATE_YAML"],
|
|
252
|
-
always: true,
|
|
253
|
-
localBin: "yamllint",
|
|
254
|
-
localArgs: ["."],
|
|
255
|
-
installHint: "brew install yamllint",
|
|
256
|
-
configInput: "yaml-config"
|
|
257
|
-
},
|
|
258
|
-
actionlint: {
|
|
259
|
-
validate: ["VALIDATE_GITHUB_ACTIONS"],
|
|
260
|
-
always: true,
|
|
261
|
-
localBin: "actionlint",
|
|
262
|
-
localArgs: [],
|
|
263
|
-
installHint: "brew install actionlint"
|
|
264
|
-
},
|
|
265
|
-
gitleaks: {
|
|
266
|
-
validate: ["VALIDATE_GITLEAKS"],
|
|
267
|
-
always: true,
|
|
268
|
-
localBin: "gitleaks",
|
|
269
|
-
localArgs: ["dir", "--no-banner"],
|
|
270
|
-
installHint: "brew install gitleaks"
|
|
271
|
-
},
|
|
272
|
-
editorconfig: {
|
|
273
|
-
validate: ["VALIDATE_EDITORCONFIG"],
|
|
274
|
-
always: true,
|
|
275
|
-
localBin: "editorconfig-checker",
|
|
276
|
-
localArgs: [],
|
|
277
|
-
installHint: "brew install editorconfig-checker"
|
|
278
|
-
},
|
|
279
|
-
commitlint: {
|
|
280
|
-
validate: ["VALIDATE_GIT_COMMITLINT"],
|
|
281
|
-
always: true,
|
|
282
|
-
localBin: "commitlint",
|
|
283
|
-
localArgs: ["--last"]
|
|
284
|
-
},
|
|
285
|
-
"git-merge-conflict-markers": {
|
|
286
|
-
validate: ["VALIDATE_GIT_MERGE_CONFLICT_MARKERS"],
|
|
287
|
-
always: true
|
|
288
|
-
},
|
|
289
|
-
markdownlint: {
|
|
290
|
-
validate: ["VALIDATE_MARKDOWN"],
|
|
291
|
-
localBin: "markdownlint-cli2",
|
|
292
|
-
localArgs: ["**/*.md"],
|
|
293
|
-
detect: [
|
|
294
|
-
".markdownlint.json",
|
|
295
|
-
".markdownlint.jsonc",
|
|
296
|
-
".markdownlint.yaml",
|
|
297
|
-
".markdownlint.yml",
|
|
298
|
-
".markdownlint-cli2.jsonc",
|
|
299
|
-
".markdownlint-cli2.yaml",
|
|
300
|
-
".markdownlint-cli2.mjs"
|
|
301
|
-
]
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
/** Every linter name the registry knows. */
|
|
305
|
-
const LINTER_NAMES = new Set(Object.keys(LINTERS));
|
|
306
|
-
/**
|
|
307
|
-
* Resolve the linter set for a repo. An `explicit` list (from
|
|
308
|
-
* `config.tasks`) wins verbatim; otherwise every `always` linter plus every
|
|
309
|
-
* linter whose `detect` filenames are present at the repo root. Result is
|
|
310
|
-
* ordered by {@link LINTERS} declaration order.
|
|
311
|
-
*
|
|
312
|
-
* @throws when an `explicit` name is not in the registry — a typo is a
|
|
313
|
-
* config bug, not a linter to silently skip.
|
|
314
|
-
*/
|
|
315
|
-
function resolveLinters(opts) {
|
|
316
|
-
const order = Object.keys(LINTERS);
|
|
317
|
-
if (opts.explicit && opts.explicit.length > 0) {
|
|
318
|
-
const unknown = opts.explicit.filter((n) => !LINTER_NAMES.has(n));
|
|
319
|
-
if (unknown.length > 0) throw new Error(`unknown linter${unknown.length > 1 ? "s" : ""} ${unknown.map((n) => `"${n}"`).join(", ")} — known: ${order.join(", ")}`);
|
|
320
|
-
const wanted = new Set(opts.explicit);
|
|
321
|
-
return order.filter((n) => wanted.has(n)).map((name) => ({
|
|
322
|
-
name,
|
|
323
|
-
def: LINTERS[name]
|
|
324
|
-
}));
|
|
325
|
-
}
|
|
326
|
-
const present = new Set(opts.rootFiles);
|
|
327
|
-
return order.filter((name) => {
|
|
328
|
-
const def = LINTERS[name];
|
|
329
|
-
return def.always === true || def.detect.some((f) => present.has(f));
|
|
330
|
-
}).map((name) => ({
|
|
331
|
-
name,
|
|
332
|
-
def: LINTERS[name]
|
|
333
|
-
}));
|
|
334
|
-
}
|
|
335
|
-
//#endregion
|
|
336
426
|
//#region src/super-linter.ts
|
|
337
427
|
/**
|
|
338
428
|
* `superLinterConfig()` — turn the resolved linter set into the exact
|
|
@@ -676,6 +766,13 @@ const realExec = (cmd, args, opts) => {
|
|
|
676
766
|
stdio: "inherit"
|
|
677
767
|
}).status ?? -1 };
|
|
678
768
|
};
|
|
769
|
+
/** `node_modules/.bin/<bin>`, else the first `PATH` entry that has it, else `null`. */
|
|
770
|
+
const realLookPath = (cwd, bin) => {
|
|
771
|
+
const local = join(cwd, "node_modules", ".bin", bin);
|
|
772
|
+
if (existsSync(local)) return local;
|
|
773
|
+
for (const dir of (process.env["PATH"] ?? "").split(":")) if (dir && existsSync(join(dir, bin))) return join(dir, bin);
|
|
774
|
+
return null;
|
|
775
|
+
};
|
|
679
776
|
function createAstromech(options) {
|
|
680
777
|
const deps = {
|
|
681
778
|
print: options.print ?? ((line) => console.log(line)),
|
|
@@ -683,7 +780,8 @@ function createAstromech(options) {
|
|
|
683
780
|
exec: options.exec ?? realExec,
|
|
684
781
|
readFile: options.readFile ?? ((path) => readFileSync(path, "utf8")),
|
|
685
782
|
fileExists: options.fileExists ?? ((path) => existsSync(path)),
|
|
686
|
-
listDir: options.listDir ?? ((path) => readdirSync(path))
|
|
783
|
+
listDir: options.listDir ?? ((path) => readdirSync(path)),
|
|
784
|
+
lookPath: options.lookPath ?? realLookPath
|
|
687
785
|
};
|
|
688
786
|
const items = () => (options.config?.tasks ?? []).map((i) => normalizeTaskEntry(i));
|
|
689
787
|
const rootFiles = () => {
|
|
@@ -701,7 +799,8 @@ function createAstromech(options) {
|
|
|
701
799
|
cwd: options.cwd,
|
|
702
800
|
passthrough: opts.passthrough ?? [],
|
|
703
801
|
dryRun: opts.dryRun ?? false,
|
|
704
|
-
required: opts.required ?? false
|
|
802
|
+
required: opts.required ?? false,
|
|
803
|
+
...task === "lint" ? { linters: lintEntry()?.linters } : {}
|
|
705
804
|
}),
|
|
706
805
|
thinCallers: () => {
|
|
707
806
|
const orgCtx = options.orgContext ?? {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/astromech",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.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.
|
|
40
|
+
"@theholocron/datapad": "4.6.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.
|
|
56
|
+
"@theholocron/rollup-plugin-transform-template": "4.6.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=22"
|