@theholocron/astromech 4.4.0 → 4.5.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/dist/index.d.mts +73 -42
- package/dist/index.mjs +230 -185
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -56,6 +56,63 @@ interface RunTaskReport {
|
|
|
56
56
|
}
|
|
57
57
|
declare function runTask(input: RunTaskInput): RunTaskReport;
|
|
58
58
|
//#endregion
|
|
59
|
+
//#region src/super-linter.d.ts
|
|
60
|
+
/**
|
|
61
|
+
* `superLinterConfig()` — turn the resolved linter set into the exact
|
|
62
|
+
* super-linter `VALIDATE_*` / `FIX_*` env the CI `lint` job needs. The CLI
|
|
63
|
+
* serializes {@link SuperLinterConfig.env} as the `super-linter-env` input
|
|
64
|
+
* on each repo's generated `lint` thin caller; the reusable workflow
|
|
65
|
+
* expands it verbatim. This is the CI half of "lint parity" — the local
|
|
66
|
+
* half is the `holocron run lint` aggregate, driven by the same
|
|
67
|
+
* {@link resolveLinters}.
|
|
68
|
+
*/
|
|
69
|
+
interface SuperLinterConfig {
|
|
70
|
+
/**
|
|
71
|
+
* Enabled `VALIDATE_*` / `FIX_*` keys → `"true"`. Only enabled keys are
|
|
72
|
+
* present (super-linter allow-list mode). Ready for `JSON.stringify`.
|
|
73
|
+
*/
|
|
74
|
+
env: Record<string, string>;
|
|
75
|
+
/** Resolved linter names in execution order — for the human-readable comment. */
|
|
76
|
+
linters: string[];
|
|
77
|
+
/** Config-file inputs the resolved set honors (`eslint-config`, …). */
|
|
78
|
+
configInputs: Partial<Record<"eslint-config" | "prettier-config" | "yaml-config", true>>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Resolve the super-linter env for a repo's `lint` task.
|
|
82
|
+
*
|
|
83
|
+
* @param opts.explicit the task's `linters` list, if any (else auto-detect)
|
|
84
|
+
* @param opts.rootFiles repo-root filenames (from `listDir(cwd)`)
|
|
85
|
+
* @param opts.includeFix emit `FIX_*` keys too (default `true`)
|
|
86
|
+
*/
|
|
87
|
+
declare function superLinterConfig(opts: {
|
|
88
|
+
explicit?: string[];
|
|
89
|
+
rootFiles: string[];
|
|
90
|
+
includeFix?: boolean;
|
|
91
|
+
}): SuperLinterConfig;
|
|
92
|
+
/**
|
|
93
|
+
* The always-on baseline env — every `always` linter, no detection. This is
|
|
94
|
+
* what the reusable `lint.yml`'s `super-linter-env` input defaults to, so a
|
|
95
|
+
* repo whose thin caller has not been re-synced yet behaves exactly as before.
|
|
96
|
+
*/
|
|
97
|
+
declare function baselineSuperLinterEnv(): Record<string, string>;
|
|
98
|
+
/**
|
|
99
|
+
* The `lint` thin caller's `with:` overrides + the `# linters: …` comment,
|
|
100
|
+
* from the resolved linter set. Shared by `createAstromech().thinCallers()`
|
|
101
|
+
* and the CLI's `sync` / `setup` workflow writers so the three stay in step.
|
|
102
|
+
*
|
|
103
|
+
* @param opts.explicit the `lint` task's `linters` list, if any
|
|
104
|
+
* @param opts.rootFiles repo-root filenames (auto-detect fallback)
|
|
105
|
+
* @param opts.extra per-repo `with:` overrides that win over the defaults
|
|
106
|
+
*/
|
|
107
|
+
declare function lintThinCallerWith(opts: {
|
|
108
|
+
explicit?: string[];
|
|
109
|
+
rootFiles: string[];
|
|
110
|
+
extra?: Record<string, unknown>;
|
|
111
|
+
}): {
|
|
112
|
+
withOverrides: Record<string, unknown>;
|
|
113
|
+
comments: Record<string, string>;
|
|
114
|
+
};
|
|
115
|
+
//#endregion
|
|
59
116
|
//#region src/thin-callers.d.ts
|
|
60
117
|
/**
|
|
61
118
|
* Workflow templates + thin-caller generation.
|
|
@@ -92,7 +149,13 @@ declare function generateThinCallerContent(name: string, withOverrides?: Record<
|
|
|
92
149
|
/** Optional sink for the "could not inject `with:`" warning. */
|
|
93
150
|
logger?: {
|
|
94
151
|
warn(obj: Record<string, unknown>, msg: string): void;
|
|
95
|
-
}
|
|
152
|
+
},
|
|
153
|
+
/**
|
|
154
|
+
* `with:` key → a `# comment` line rendered immediately above that entry.
|
|
155
|
+
* Only honored when injecting a fresh `with:` block (no template today
|
|
156
|
+
* ships one), which is the path `lint` takes.
|
|
157
|
+
*/
|
|
158
|
+
comments?: Record<string, string>): string;
|
|
96
159
|
interface PreviewConfig {
|
|
97
160
|
/** Cloudflare Pages project name shared across all repos for previews. */
|
|
98
161
|
project: string;
|
|
@@ -206,6 +269,14 @@ interface Astromech {
|
|
|
206
269
|
* when there is no config or `syncScripts: false`.
|
|
207
270
|
*/
|
|
208
271
|
packageScripts(): Record<string, string>;
|
|
272
|
+
/**
|
|
273
|
+
* The resolved super-linter env for this repo's `lint` task — the CI half
|
|
274
|
+
* of lint parity. `thinCallers()` already bakes `env` into the `lint` thin
|
|
275
|
+
* caller's `super-linter-env` input; this method exposes the full result
|
|
276
|
+
* for `holocron doctor` / diagnostics. Driven by the `lint` entry's
|
|
277
|
+
* `linters` list, else auto-detection from the repo's config files.
|
|
278
|
+
*/
|
|
279
|
+
superLinterConfig(): SuperLinterConfig;
|
|
209
280
|
}
|
|
210
281
|
declare function createAstromech(options: AstromechOptions): Astromech;
|
|
211
282
|
//#endregion
|
|
@@ -324,44 +395,4 @@ declare const TASKS: Record<string, TaskDef>;
|
|
|
324
395
|
/** Every task name the registry knows. */
|
|
325
396
|
declare const KNOWN_TASKS: Set<string>;
|
|
326
397
|
//#endregion
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* `superLinterConfig()` — turn the resolved linter set into the exact
|
|
330
|
-
* super-linter `VALIDATE_*` / `FIX_*` env the CI `lint` job needs. The CLI
|
|
331
|
-
* serializes {@link SuperLinterConfig.env} as the `super-linter-env` input
|
|
332
|
-
* on each repo's generated `lint` thin caller; the reusable workflow
|
|
333
|
-
* expands it verbatim. This is the CI half of "lint parity" — the local
|
|
334
|
-
* half is the `holocron run lint` aggregate, driven by the same
|
|
335
|
-
* {@link resolveLinters}.
|
|
336
|
-
*/
|
|
337
|
-
interface SuperLinterConfig {
|
|
338
|
-
/**
|
|
339
|
-
* Enabled `VALIDATE_*` / `FIX_*` keys → `"true"`. Only enabled keys are
|
|
340
|
-
* present (super-linter allow-list mode). Ready for `JSON.stringify`.
|
|
341
|
-
*/
|
|
342
|
-
env: Record<string, string>;
|
|
343
|
-
/** Resolved linter names in execution order — for the human-readable comment. */
|
|
344
|
-
linters: string[];
|
|
345
|
-
/** Config-file inputs the resolved set honors (`eslint-config`, …). */
|
|
346
|
-
configInputs: Partial<Record<"eslint-config" | "prettier-config" | "yaml-config", true>>;
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Resolve the super-linter env for a repo's `lint` task.
|
|
350
|
-
*
|
|
351
|
-
* @param opts.explicit the task's `linters` list, if any (else auto-detect)
|
|
352
|
-
* @param opts.rootFiles repo-root filenames (from `listDir(cwd)`)
|
|
353
|
-
* @param opts.includeFix emit `FIX_*` keys too (default `true`)
|
|
354
|
-
*/
|
|
355
|
-
declare function superLinterConfig(opts: {
|
|
356
|
-
explicit?: string[];
|
|
357
|
-
rootFiles: string[];
|
|
358
|
-
includeFix?: boolean;
|
|
359
|
-
}): SuperLinterConfig;
|
|
360
|
-
/**
|
|
361
|
-
* The always-on baseline env — every `always` linter, no detection. This is
|
|
362
|
-
* what the reusable `lint.yml`'s `super-linter-env` input defaults to, so a
|
|
363
|
-
* repo whose thin caller has not been re-synced yet behaves exactly as before.
|
|
364
|
-
*/
|
|
365
|
-
declare function baselineSuperLinterEnv(): Record<string, string>;
|
|
366
|
-
//#endregion
|
|
367
|
-
export { type Astromech, type AstromechOptions, type ExecFn, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, type LinterDef, type LocalRunner, type OrgContext, type PreviewConfig, type RunLogger, type RunOptions, type RunTaskInput, type RunTaskReport, type SuperLinterConfig, TASKS, type TaskDef, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, baselineSuperLinterEnv, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, normalizeWorkflowWith, resolveLinters, runTask, superLinterConfig };
|
|
398
|
+
export { type Astromech, type AstromechOptions, type ExecFn, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, type LinterDef, type LocalRunner, type OrgContext, type PreviewConfig, type RunLogger, type RunOptions, type RunTaskInput, type RunTaskReport, type SuperLinterConfig, TASKS, type TaskDef, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, baselineSuperLinterEnv, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, lintThinCallerWith, normalizeWorkflowWith, resolveLinters, runTask, superLinterConfig };
|
package/dist/index.mjs
CHANGED
|
@@ -203,6 +203,204 @@ function packageJsonScript(cwd, task, readFile, fileExists) {
|
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
//#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
|
+
//#region src/super-linter.ts
|
|
337
|
+
/**
|
|
338
|
+
* `superLinterConfig()` — turn the resolved linter set into the exact
|
|
339
|
+
* super-linter `VALIDATE_*` / `FIX_*` env the CI `lint` job needs. The CLI
|
|
340
|
+
* serializes {@link SuperLinterConfig.env} as the `super-linter-env` input
|
|
341
|
+
* on each repo's generated `lint` thin caller; the reusable workflow
|
|
342
|
+
* expands it verbatim. This is the CI half of "lint parity" — the local
|
|
343
|
+
* half is the `holocron run lint` aggregate, driven by the same
|
|
344
|
+
* {@link resolveLinters}.
|
|
345
|
+
*/
|
|
346
|
+
/**
|
|
347
|
+
* Resolve the super-linter env for a repo's `lint` task.
|
|
348
|
+
*
|
|
349
|
+
* @param opts.explicit the task's `linters` list, if any (else auto-detect)
|
|
350
|
+
* @param opts.rootFiles repo-root filenames (from `listDir(cwd)`)
|
|
351
|
+
* @param opts.includeFix emit `FIX_*` keys too (default `true`)
|
|
352
|
+
*/
|
|
353
|
+
function superLinterConfig(opts) {
|
|
354
|
+
const includeFix = opts.includeFix ?? true;
|
|
355
|
+
const resolved = resolveLinters({
|
|
356
|
+
explicit: opts.explicit,
|
|
357
|
+
rootFiles: opts.rootFiles
|
|
358
|
+
});
|
|
359
|
+
const env = {};
|
|
360
|
+
const configInputs = {};
|
|
361
|
+
for (const { def } of resolved) {
|
|
362
|
+
for (const key of def.validate) env[key] = "true";
|
|
363
|
+
if (includeFix) for (const key of def.fix ?? []) env[key] = "true";
|
|
364
|
+
if (def.configInput) configInputs[def.configInput] = true;
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
env,
|
|
368
|
+
linters: resolved.map((r) => r.name),
|
|
369
|
+
configInputs
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The always-on baseline env — every `always` linter, no detection. This is
|
|
374
|
+
* what the reusable `lint.yml`'s `super-linter-env` input defaults to, so a
|
|
375
|
+
* repo whose thin caller has not been re-synced yet behaves exactly as before.
|
|
376
|
+
*/
|
|
377
|
+
function baselineSuperLinterEnv() {
|
|
378
|
+
return superLinterConfig({ rootFiles: [] }).env;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* The `lint` thin caller's `with:` overrides + the `# linters: …` comment,
|
|
382
|
+
* from the resolved linter set. Shared by `createAstromech().thinCallers()`
|
|
383
|
+
* and the CLI's `sync` / `setup` workflow writers so the three stay in step.
|
|
384
|
+
*
|
|
385
|
+
* @param opts.explicit the `lint` task's `linters` list, if any
|
|
386
|
+
* @param opts.rootFiles repo-root filenames (auto-detect fallback)
|
|
387
|
+
* @param opts.extra per-repo `with:` overrides that win over the defaults
|
|
388
|
+
*/
|
|
389
|
+
function lintThinCallerWith(opts) {
|
|
390
|
+
const sl = superLinterConfig({
|
|
391
|
+
explicit: opts.explicit,
|
|
392
|
+
rootFiles: opts.rootFiles
|
|
393
|
+
});
|
|
394
|
+
return {
|
|
395
|
+
withOverrides: {
|
|
396
|
+
"enable-auto-commit": true,
|
|
397
|
+
"super-linter-env": JSON.stringify(sl.env),
|
|
398
|
+
...opts.extra ?? {}
|
|
399
|
+
},
|
|
400
|
+
comments: { "super-linter-env": `linters: ${sl.linters.join(", ")}` }
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
206
404
|
//#region src/thin-callers.ts
|
|
207
405
|
/**
|
|
208
406
|
* Workflow templates + thin-caller generation.
|
|
@@ -255,7 +453,7 @@ const WORKFLOW_CHECK_CONTEXTS = {
|
|
|
255
453
|
* If neither pattern matches the template, a warning is emitted and the
|
|
256
454
|
* base template is returned unchanged.
|
|
257
455
|
*/
|
|
258
|
-
function generateThinCallerContent(name, withOverrides, additionalPaths, logger) {
|
|
456
|
+
function generateThinCallerContent(name, withOverrides, additionalPaths, logger, comments) {
|
|
259
457
|
const base = WORKFLOW_TEMPLATES[name];
|
|
260
458
|
if (!base) return "";
|
|
261
459
|
const yamlScalar = (v) => {
|
|
@@ -264,7 +462,10 @@ function generateThinCallerContent(name, withOverrides, additionalPaths, logger)
|
|
|
264
462
|
const s = String(v);
|
|
265
463
|
return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
|
|
266
464
|
};
|
|
267
|
-
const fmt = (k, v) =>
|
|
465
|
+
const fmt = (k, v) => {
|
|
466
|
+
const line = ` ${k}: ${yamlScalar(v)}`;
|
|
467
|
+
return comments?.[k] ? ` # ${comments[k]}\n${line}` : line;
|
|
468
|
+
};
|
|
268
469
|
let result = base;
|
|
269
470
|
if (additionalPaths && additionalPaths.length > 0) {
|
|
270
471
|
const pathsBlockRe = /( {4}paths:\n)((?:[ ]{6}- [^\n]+\n)+)/;
|
|
@@ -282,7 +483,7 @@ function generateThinCallerContent(name, withOverrides, additionalPaths, logger)
|
|
|
282
483
|
const withBlockRe = /( {4}with:\n)((?:[ ]{6}[^\n]+\n)*)/;
|
|
283
484
|
const existingMatch = result.match(withBlockRe);
|
|
284
485
|
if (existingMatch) {
|
|
285
|
-
const existingEntries = new Map(existingMatch[2].split("\n").filter(Boolean).map((line) => {
|
|
486
|
+
const existingEntries = new Map(existingMatch[2].split("\n").filter(Boolean).filter((line) => !line.trim().startsWith("#")).map((line) => {
|
|
286
487
|
const m = line.match(/^ {6}([^:]+):\s*(.*)/);
|
|
287
488
|
return m ? [m[1].trim(), m[2].trim()] : null;
|
|
288
489
|
}).filter((e) => e !== null));
|
|
@@ -485,6 +686,14 @@ function createAstromech(options) {
|
|
|
485
686
|
listDir: options.listDir ?? ((path) => readdirSync(path))
|
|
486
687
|
};
|
|
487
688
|
const items = () => (options.config?.tasks ?? []).map((i) => normalizeTaskEntry(i));
|
|
689
|
+
const rootFiles = () => {
|
|
690
|
+
try {
|
|
691
|
+
return deps.listDir(options.cwd);
|
|
692
|
+
} catch {
|
|
693
|
+
return [];
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
const lintEntry = () => items().filter((e) => e.name === "lint").at(-1);
|
|
488
697
|
return {
|
|
489
698
|
run: (task, opts = {}) => runTask({
|
|
490
699
|
...deps,
|
|
@@ -501,10 +710,17 @@ function createAstromech(options) {
|
|
|
501
710
|
if (entry.ci === false || !KNOWN_WORKFLOWS.has(entry.name)) continue;
|
|
502
711
|
const rawWith = entry.with;
|
|
503
712
|
const normalized = rawWith ? normalizeWorkflowWith(rawWith) : void 0;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
713
|
+
let withOverrides = normalized;
|
|
714
|
+
let comments;
|
|
715
|
+
if (entry.name === "lint") {
|
|
716
|
+
const lint = lintThinCallerWith({
|
|
717
|
+
explicit: entry.linters,
|
|
718
|
+
rootFiles: rootFiles(),
|
|
719
|
+
extra: normalized
|
|
720
|
+
});
|
|
721
|
+
withOverrides = lint.withOverrides;
|
|
722
|
+
comments = lint.comments;
|
|
723
|
+
}
|
|
508
724
|
const additionalPaths = entry.paths ?? (entry.name === "deploy" && rawWith ? deriveDeployPaths(rawWith) : void 0);
|
|
509
725
|
if (entry.name === "deploy" && rawWith) {
|
|
510
726
|
const preview = extractPreviewConfig(rawWith, orgCtx);
|
|
@@ -515,7 +731,7 @@ function createAstromech(options) {
|
|
|
515
731
|
continue;
|
|
516
732
|
}
|
|
517
733
|
}
|
|
518
|
-
out.set(`${entry.name}.yml`, generateThinCallerContent(entry.name, withOverrides, additionalPaths, deps.logger));
|
|
734
|
+
out.set(`${entry.name}.yml`, generateThinCallerContent(entry.name, withOverrides, additionalPaths, deps.logger, comments));
|
|
519
735
|
}
|
|
520
736
|
return out;
|
|
521
737
|
},
|
|
@@ -528,183 +744,12 @@ function createAstromech(options) {
|
|
|
528
744
|
out[entry.name] = `holocron run ${entry.name}`;
|
|
529
745
|
}
|
|
530
746
|
return out;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Known linters, in execution order. `always` entries are the current
|
|
538
|
-
* hard-coded super-linter baseline; `prettier` is always-on because the org
|
|
539
|
-
* applies it universally (super-linter only lints files that exist).
|
|
540
|
-
*/
|
|
541
|
-
const LINTERS = {
|
|
542
|
-
eslint: {
|
|
543
|
-
validate: ["VALIDATE_JAVASCRIPT_ES", "VALIDATE_TYPESCRIPT_ES"],
|
|
544
|
-
localBin: "eslint",
|
|
545
|
-
localArgs: ["."],
|
|
546
|
-
detect: [
|
|
547
|
-
"eslint.config.ts",
|
|
548
|
-
"eslint.config.js",
|
|
549
|
-
"eslint.config.mjs",
|
|
550
|
-
"eslint.config.cjs",
|
|
551
|
-
".eslintrc",
|
|
552
|
-
".eslintrc.json",
|
|
553
|
-
".eslintrc.yml",
|
|
554
|
-
".eslintrc.yaml",
|
|
555
|
-
".eslintrc.cjs"
|
|
556
|
-
],
|
|
557
|
-
configInput: "eslint-config"
|
|
558
|
-
},
|
|
559
|
-
prettier: {
|
|
560
|
-
validate: [
|
|
561
|
-
"VALIDATE_JAVASCRIPT_PRETTIER",
|
|
562
|
-
"VALIDATE_JSX_PRETTIER",
|
|
563
|
-
"VALIDATE_TYPESCRIPT_PRETTIER",
|
|
564
|
-
"VALIDATE_TSX",
|
|
565
|
-
"VALIDATE_MARKDOWN_PRETTIER"
|
|
566
|
-
],
|
|
567
|
-
fix: [
|
|
568
|
-
"FIX_JAVASCRIPT_PRETTIER",
|
|
569
|
-
"FIX_JSX_PRETTIER",
|
|
570
|
-
"FIX_TYPESCRIPT_PRETTIER",
|
|
571
|
-
"FIX_TSX",
|
|
572
|
-
"FIX_MARKDOWN_PRETTIER"
|
|
573
|
-
],
|
|
574
|
-
always: true,
|
|
575
|
-
localBin: "prettier",
|
|
576
|
-
localArgs: ["--check", "."],
|
|
577
|
-
configInput: "prettier-config"
|
|
578
|
-
},
|
|
579
|
-
yamllint: {
|
|
580
|
-
validate: ["VALIDATE_YAML"],
|
|
581
|
-
always: true,
|
|
582
|
-
localBin: "yamllint",
|
|
583
|
-
localArgs: ["."],
|
|
584
|
-
installHint: "brew install yamllint",
|
|
585
|
-
configInput: "yaml-config"
|
|
586
|
-
},
|
|
587
|
-
actionlint: {
|
|
588
|
-
validate: ["VALIDATE_GITHUB_ACTIONS"],
|
|
589
|
-
always: true,
|
|
590
|
-
localBin: "actionlint",
|
|
591
|
-
localArgs: [],
|
|
592
|
-
installHint: "brew install actionlint"
|
|
593
|
-
},
|
|
594
|
-
gitleaks: {
|
|
595
|
-
validate: ["VALIDATE_GITLEAKS"],
|
|
596
|
-
always: true,
|
|
597
|
-
localBin: "gitleaks",
|
|
598
|
-
localArgs: ["dir", "--no-banner"],
|
|
599
|
-
installHint: "brew install gitleaks"
|
|
600
|
-
},
|
|
601
|
-
editorconfig: {
|
|
602
|
-
validate: ["VALIDATE_EDITORCONFIG"],
|
|
603
|
-
always: true,
|
|
604
|
-
localBin: "editorconfig-checker",
|
|
605
|
-
localArgs: [],
|
|
606
|
-
installHint: "brew install editorconfig-checker"
|
|
607
|
-
},
|
|
608
|
-
commitlint: {
|
|
609
|
-
validate: ["VALIDATE_GIT_COMMITLINT"],
|
|
610
|
-
always: true,
|
|
611
|
-
localBin: "commitlint",
|
|
612
|
-
localArgs: ["--last"]
|
|
613
|
-
},
|
|
614
|
-
"git-merge-conflict-markers": {
|
|
615
|
-
validate: ["VALIDATE_GIT_MERGE_CONFLICT_MARKERS"],
|
|
616
|
-
always: true
|
|
617
|
-
},
|
|
618
|
-
markdownlint: {
|
|
619
|
-
validate: ["VALIDATE_MARKDOWN"],
|
|
620
|
-
localBin: "markdownlint-cli2",
|
|
621
|
-
localArgs: ["**/*.md"],
|
|
622
|
-
detect: [
|
|
623
|
-
".markdownlint.json",
|
|
624
|
-
".markdownlint.jsonc",
|
|
625
|
-
".markdownlint.yaml",
|
|
626
|
-
".markdownlint.yml",
|
|
627
|
-
".markdownlint-cli2.jsonc",
|
|
628
|
-
".markdownlint-cli2.yaml",
|
|
629
|
-
".markdownlint-cli2.mjs"
|
|
630
|
-
]
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
/** Every linter name the registry knows. */
|
|
634
|
-
const LINTER_NAMES = new Set(Object.keys(LINTERS));
|
|
635
|
-
/**
|
|
636
|
-
* Resolve the linter set for a repo. An `explicit` list (from
|
|
637
|
-
* `config.tasks`) wins verbatim; otherwise every `always` linter plus every
|
|
638
|
-
* linter whose `detect` filenames are present at the repo root. Result is
|
|
639
|
-
* ordered by {@link LINTERS} declaration order.
|
|
640
|
-
*
|
|
641
|
-
* @throws when an `explicit` name is not in the registry — a typo is a
|
|
642
|
-
* config bug, not a linter to silently skip.
|
|
643
|
-
*/
|
|
644
|
-
function resolveLinters(opts) {
|
|
645
|
-
const order = Object.keys(LINTERS);
|
|
646
|
-
if (opts.explicit && opts.explicit.length > 0) {
|
|
647
|
-
const unknown = opts.explicit.filter((n) => !LINTER_NAMES.has(n));
|
|
648
|
-
if (unknown.length > 0) throw new Error(`unknown linter${unknown.length > 1 ? "s" : ""} ${unknown.map((n) => `"${n}"`).join(", ")} — known: ${order.join(", ")}`);
|
|
649
|
-
const wanted = new Set(opts.explicit);
|
|
650
|
-
return order.filter((n) => wanted.has(n)).map((name) => ({
|
|
651
|
-
name,
|
|
652
|
-
def: LINTERS[name]
|
|
653
|
-
}));
|
|
654
|
-
}
|
|
655
|
-
const present = new Set(opts.rootFiles);
|
|
656
|
-
return order.filter((name) => {
|
|
657
|
-
const def = LINTERS[name];
|
|
658
|
-
return def.always === true || def.detect.some((f) => present.has(f));
|
|
659
|
-
}).map((name) => ({
|
|
660
|
-
name,
|
|
661
|
-
def: LINTERS[name]
|
|
662
|
-
}));
|
|
663
|
-
}
|
|
664
|
-
//#endregion
|
|
665
|
-
//#region src/super-linter.ts
|
|
666
|
-
/**
|
|
667
|
-
* `superLinterConfig()` — turn the resolved linter set into the exact
|
|
668
|
-
* super-linter `VALIDATE_*` / `FIX_*` env the CI `lint` job needs. The CLI
|
|
669
|
-
* serializes {@link SuperLinterConfig.env} as the `super-linter-env` input
|
|
670
|
-
* on each repo's generated `lint` thin caller; the reusable workflow
|
|
671
|
-
* expands it verbatim. This is the CI half of "lint parity" — the local
|
|
672
|
-
* half is the `holocron run lint` aggregate, driven by the same
|
|
673
|
-
* {@link resolveLinters}.
|
|
674
|
-
*/
|
|
675
|
-
/**
|
|
676
|
-
* Resolve the super-linter env for a repo's `lint` task.
|
|
677
|
-
*
|
|
678
|
-
* @param opts.explicit the task's `linters` list, if any (else auto-detect)
|
|
679
|
-
* @param opts.rootFiles repo-root filenames (from `listDir(cwd)`)
|
|
680
|
-
* @param opts.includeFix emit `FIX_*` keys too (default `true`)
|
|
681
|
-
*/
|
|
682
|
-
function superLinterConfig(opts) {
|
|
683
|
-
const includeFix = opts.includeFix ?? true;
|
|
684
|
-
const resolved = resolveLinters({
|
|
685
|
-
explicit: opts.explicit,
|
|
686
|
-
rootFiles: opts.rootFiles
|
|
687
|
-
});
|
|
688
|
-
const env = {};
|
|
689
|
-
const configInputs = {};
|
|
690
|
-
for (const { def } of resolved) {
|
|
691
|
-
for (const key of def.validate) env[key] = "true";
|
|
692
|
-
if (includeFix) for (const key of def.fix ?? []) env[key] = "true";
|
|
693
|
-
if (def.configInput) configInputs[def.configInput] = true;
|
|
694
|
-
}
|
|
695
|
-
return {
|
|
696
|
-
env,
|
|
697
|
-
linters: resolved.map((r) => r.name),
|
|
698
|
-
configInputs
|
|
747
|
+
},
|
|
748
|
+
superLinterConfig: () => superLinterConfig({
|
|
749
|
+
explicit: lintEntry()?.linters,
|
|
750
|
+
rootFiles: rootFiles()
|
|
751
|
+
})
|
|
699
752
|
};
|
|
700
753
|
}
|
|
701
|
-
/**
|
|
702
|
-
* The always-on baseline env — every `always` linter, no detection. This is
|
|
703
|
-
* what the reusable `lint.yml`'s `super-linter-env` input defaults to, so a
|
|
704
|
-
* repo whose thin caller has not been re-synced yet behaves exactly as before.
|
|
705
|
-
*/
|
|
706
|
-
function baselineSuperLinterEnv() {
|
|
707
|
-
return superLinterConfig({ rootFiles: [] }).env;
|
|
708
|
-
}
|
|
709
754
|
//#endregion
|
|
710
|
-
export { KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, baselineSuperLinterEnv, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, normalizeWorkflowWith, resolveLinters, runTask, superLinterConfig };
|
|
755
|
+
export { KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, baselineSuperLinterEnv, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, lintThinCallerWith, normalizeWorkflowWith, resolveLinters, runTask, superLinterConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/astromech",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.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.5.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.5.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=22"
|