@theholocron/astromech 4.4.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 +86 -42
- package/dist/index.mjs +335 -191
- 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";
|
|
@@ -56,6 +67,63 @@ interface RunTaskReport {
|
|
|
56
67
|
}
|
|
57
68
|
declare function runTask(input: RunTaskInput): RunTaskReport;
|
|
58
69
|
//#endregion
|
|
70
|
+
//#region src/super-linter.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* `superLinterConfig()` — turn the resolved linter set into the exact
|
|
73
|
+
* super-linter `VALIDATE_*` / `FIX_*` env the CI `lint` job needs. The CLI
|
|
74
|
+
* serializes {@link SuperLinterConfig.env} as the `super-linter-env` input
|
|
75
|
+
* on each repo's generated `lint` thin caller; the reusable workflow
|
|
76
|
+
* expands it verbatim. This is the CI half of "lint parity" — the local
|
|
77
|
+
* half is the `holocron run lint` aggregate, driven by the same
|
|
78
|
+
* {@link resolveLinters}.
|
|
79
|
+
*/
|
|
80
|
+
interface SuperLinterConfig {
|
|
81
|
+
/**
|
|
82
|
+
* Enabled `VALIDATE_*` / `FIX_*` keys → `"true"`. Only enabled keys are
|
|
83
|
+
* present (super-linter allow-list mode). Ready for `JSON.stringify`.
|
|
84
|
+
*/
|
|
85
|
+
env: Record<string, string>;
|
|
86
|
+
/** Resolved linter names in execution order — for the human-readable comment. */
|
|
87
|
+
linters: string[];
|
|
88
|
+
/** Config-file inputs the resolved set honors (`eslint-config`, …). */
|
|
89
|
+
configInputs: Partial<Record<"eslint-config" | "prettier-config" | "yaml-config", true>>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve the super-linter env for a repo's `lint` task.
|
|
93
|
+
*
|
|
94
|
+
* @param opts.explicit the task's `linters` list, if any (else auto-detect)
|
|
95
|
+
* @param opts.rootFiles repo-root filenames (from `listDir(cwd)`)
|
|
96
|
+
* @param opts.includeFix emit `FIX_*` keys too (default `true`)
|
|
97
|
+
*/
|
|
98
|
+
declare function superLinterConfig(opts: {
|
|
99
|
+
explicit?: string[];
|
|
100
|
+
rootFiles: string[];
|
|
101
|
+
includeFix?: boolean;
|
|
102
|
+
}): SuperLinterConfig;
|
|
103
|
+
/**
|
|
104
|
+
* The always-on baseline env — every `always` linter, no detection. This is
|
|
105
|
+
* what the reusable `lint.yml`'s `super-linter-env` input defaults to, so a
|
|
106
|
+
* repo whose thin caller has not been re-synced yet behaves exactly as before.
|
|
107
|
+
*/
|
|
108
|
+
declare function baselineSuperLinterEnv(): Record<string, string>;
|
|
109
|
+
/**
|
|
110
|
+
* The `lint` thin caller's `with:` overrides + the `# linters: …` comment,
|
|
111
|
+
* from the resolved linter set. Shared by `createAstromech().thinCallers()`
|
|
112
|
+
* and the CLI's `sync` / `setup` workflow writers so the three stay in step.
|
|
113
|
+
*
|
|
114
|
+
* @param opts.explicit the `lint` task's `linters` list, if any
|
|
115
|
+
* @param opts.rootFiles repo-root filenames (auto-detect fallback)
|
|
116
|
+
* @param opts.extra per-repo `with:` overrides that win over the defaults
|
|
117
|
+
*/
|
|
118
|
+
declare function lintThinCallerWith(opts: {
|
|
119
|
+
explicit?: string[];
|
|
120
|
+
rootFiles: string[];
|
|
121
|
+
extra?: Record<string, unknown>;
|
|
122
|
+
}): {
|
|
123
|
+
withOverrides: Record<string, unknown>;
|
|
124
|
+
comments: Record<string, string>;
|
|
125
|
+
};
|
|
126
|
+
//#endregion
|
|
59
127
|
//#region src/thin-callers.d.ts
|
|
60
128
|
/**
|
|
61
129
|
* Workflow templates + thin-caller generation.
|
|
@@ -92,7 +160,13 @@ declare function generateThinCallerContent(name: string, withOverrides?: Record<
|
|
|
92
160
|
/** Optional sink for the "could not inject `with:`" warning. */
|
|
93
161
|
logger?: {
|
|
94
162
|
warn(obj: Record<string, unknown>, msg: string): void;
|
|
95
|
-
}
|
|
163
|
+
},
|
|
164
|
+
/**
|
|
165
|
+
* `with:` key → a `# comment` line rendered immediately above that entry.
|
|
166
|
+
* Only honored when injecting a fresh `with:` block (no template today
|
|
167
|
+
* ships one), which is the path `lint` takes.
|
|
168
|
+
*/
|
|
169
|
+
comments?: Record<string, string>): string;
|
|
96
170
|
interface PreviewConfig {
|
|
97
171
|
/** Cloudflare Pages project name shared across all repos for previews. */
|
|
98
172
|
project: string;
|
|
@@ -179,6 +253,8 @@ interface AstromechOptions {
|
|
|
179
253
|
readFile?: (path: string) => string;
|
|
180
254
|
fileExists?: (path: string) => boolean;
|
|
181
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;
|
|
182
258
|
}
|
|
183
259
|
interface RunOptions {
|
|
184
260
|
/** Args after `--`, forwarded to the tool / turbo / script. */
|
|
@@ -206,6 +282,14 @@ interface Astromech {
|
|
|
206
282
|
* when there is no config or `syncScripts: false`.
|
|
207
283
|
*/
|
|
208
284
|
packageScripts(): Record<string, string>;
|
|
285
|
+
/**
|
|
286
|
+
* The resolved super-linter env for this repo's `lint` task — the CI half
|
|
287
|
+
* of lint parity. `thinCallers()` already bakes `env` into the `lint` thin
|
|
288
|
+
* caller's `super-linter-env` input; this method exposes the full result
|
|
289
|
+
* for `holocron doctor` / diagnostics. Driven by the `lint` entry's
|
|
290
|
+
* `linters` list, else auto-detection from the repo's config files.
|
|
291
|
+
*/
|
|
292
|
+
superLinterConfig(): SuperLinterConfig;
|
|
209
293
|
}
|
|
210
294
|
declare function createAstromech(options: AstromechOptions): Astromech;
|
|
211
295
|
//#endregion
|
|
@@ -324,44 +408,4 @@ declare const TASKS: Record<string, TaskDef>;
|
|
|
324
408
|
/** Every task name the registry knows. */
|
|
325
409
|
declare const KNOWN_TASKS: Set<string>;
|
|
326
410
|
//#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 };
|
|
411
|
+
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
|
@@ -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,6 +423,74 @@ function packageJsonScript(cwd, task, readFile, fileExists) {
|
|
|
203
423
|
}
|
|
204
424
|
}
|
|
205
425
|
//#endregion
|
|
426
|
+
//#region src/super-linter.ts
|
|
427
|
+
/**
|
|
428
|
+
* `superLinterConfig()` — turn the resolved linter set into the exact
|
|
429
|
+
* super-linter `VALIDATE_*` / `FIX_*` env the CI `lint` job needs. The CLI
|
|
430
|
+
* serializes {@link SuperLinterConfig.env} as the `super-linter-env` input
|
|
431
|
+
* on each repo's generated `lint` thin caller; the reusable workflow
|
|
432
|
+
* expands it verbatim. This is the CI half of "lint parity" — the local
|
|
433
|
+
* half is the `holocron run lint` aggregate, driven by the same
|
|
434
|
+
* {@link resolveLinters}.
|
|
435
|
+
*/
|
|
436
|
+
/**
|
|
437
|
+
* Resolve the super-linter env for a repo's `lint` task.
|
|
438
|
+
*
|
|
439
|
+
* @param opts.explicit the task's `linters` list, if any (else auto-detect)
|
|
440
|
+
* @param opts.rootFiles repo-root filenames (from `listDir(cwd)`)
|
|
441
|
+
* @param opts.includeFix emit `FIX_*` keys too (default `true`)
|
|
442
|
+
*/
|
|
443
|
+
function superLinterConfig(opts) {
|
|
444
|
+
const includeFix = opts.includeFix ?? true;
|
|
445
|
+
const resolved = resolveLinters({
|
|
446
|
+
explicit: opts.explicit,
|
|
447
|
+
rootFiles: opts.rootFiles
|
|
448
|
+
});
|
|
449
|
+
const env = {};
|
|
450
|
+
const configInputs = {};
|
|
451
|
+
for (const { def } of resolved) {
|
|
452
|
+
for (const key of def.validate) env[key] = "true";
|
|
453
|
+
if (includeFix) for (const key of def.fix ?? []) env[key] = "true";
|
|
454
|
+
if (def.configInput) configInputs[def.configInput] = true;
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
env,
|
|
458
|
+
linters: resolved.map((r) => r.name),
|
|
459
|
+
configInputs
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* The always-on baseline env — every `always` linter, no detection. This is
|
|
464
|
+
* what the reusable `lint.yml`'s `super-linter-env` input defaults to, so a
|
|
465
|
+
* repo whose thin caller has not been re-synced yet behaves exactly as before.
|
|
466
|
+
*/
|
|
467
|
+
function baselineSuperLinterEnv() {
|
|
468
|
+
return superLinterConfig({ rootFiles: [] }).env;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* The `lint` thin caller's `with:` overrides + the `# linters: …` comment,
|
|
472
|
+
* from the resolved linter set. Shared by `createAstromech().thinCallers()`
|
|
473
|
+
* and the CLI's `sync` / `setup` workflow writers so the three stay in step.
|
|
474
|
+
*
|
|
475
|
+
* @param opts.explicit the `lint` task's `linters` list, if any
|
|
476
|
+
* @param opts.rootFiles repo-root filenames (auto-detect fallback)
|
|
477
|
+
* @param opts.extra per-repo `with:` overrides that win over the defaults
|
|
478
|
+
*/
|
|
479
|
+
function lintThinCallerWith(opts) {
|
|
480
|
+
const sl = superLinterConfig({
|
|
481
|
+
explicit: opts.explicit,
|
|
482
|
+
rootFiles: opts.rootFiles
|
|
483
|
+
});
|
|
484
|
+
return {
|
|
485
|
+
withOverrides: {
|
|
486
|
+
"enable-auto-commit": true,
|
|
487
|
+
"super-linter-env": JSON.stringify(sl.env),
|
|
488
|
+
...opts.extra ?? {}
|
|
489
|
+
},
|
|
490
|
+
comments: { "super-linter-env": `linters: ${sl.linters.join(", ")}` }
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
//#endregion
|
|
206
494
|
//#region src/thin-callers.ts
|
|
207
495
|
/**
|
|
208
496
|
* Workflow templates + thin-caller generation.
|
|
@@ -255,7 +543,7 @@ const WORKFLOW_CHECK_CONTEXTS = {
|
|
|
255
543
|
* If neither pattern matches the template, a warning is emitted and the
|
|
256
544
|
* base template is returned unchanged.
|
|
257
545
|
*/
|
|
258
|
-
function generateThinCallerContent(name, withOverrides, additionalPaths, logger) {
|
|
546
|
+
function generateThinCallerContent(name, withOverrides, additionalPaths, logger, comments) {
|
|
259
547
|
const base = WORKFLOW_TEMPLATES[name];
|
|
260
548
|
if (!base) return "";
|
|
261
549
|
const yamlScalar = (v) => {
|
|
@@ -264,7 +552,10 @@ function generateThinCallerContent(name, withOverrides, additionalPaths, logger)
|
|
|
264
552
|
const s = String(v);
|
|
265
553
|
return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
|
|
266
554
|
};
|
|
267
|
-
const fmt = (k, v) =>
|
|
555
|
+
const fmt = (k, v) => {
|
|
556
|
+
const line = ` ${k}: ${yamlScalar(v)}`;
|
|
557
|
+
return comments?.[k] ? ` # ${comments[k]}\n${line}` : line;
|
|
558
|
+
};
|
|
268
559
|
let result = base;
|
|
269
560
|
if (additionalPaths && additionalPaths.length > 0) {
|
|
270
561
|
const pathsBlockRe = /( {4}paths:\n)((?:[ ]{6}- [^\n]+\n)+)/;
|
|
@@ -282,7 +573,7 @@ function generateThinCallerContent(name, withOverrides, additionalPaths, logger)
|
|
|
282
573
|
const withBlockRe = /( {4}with:\n)((?:[ ]{6}[^\n]+\n)*)/;
|
|
283
574
|
const existingMatch = result.match(withBlockRe);
|
|
284
575
|
if (existingMatch) {
|
|
285
|
-
const existingEntries = new Map(existingMatch[2].split("\n").filter(Boolean).map((line) => {
|
|
576
|
+
const existingEntries = new Map(existingMatch[2].split("\n").filter(Boolean).filter((line) => !line.trim().startsWith("#")).map((line) => {
|
|
286
577
|
const m = line.match(/^ {6}([^:]+):\s*(.*)/);
|
|
287
578
|
return m ? [m[1].trim(), m[2].trim()] : null;
|
|
288
579
|
}).filter((e) => e !== null));
|
|
@@ -475,6 +766,13 @@ const realExec = (cmd, args, opts) => {
|
|
|
475
766
|
stdio: "inherit"
|
|
476
767
|
}).status ?? -1 };
|
|
477
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
|
+
};
|
|
478
776
|
function createAstromech(options) {
|
|
479
777
|
const deps = {
|
|
480
778
|
print: options.print ?? ((line) => console.log(line)),
|
|
@@ -482,9 +780,18 @@ function createAstromech(options) {
|
|
|
482
780
|
exec: options.exec ?? realExec,
|
|
483
781
|
readFile: options.readFile ?? ((path) => readFileSync(path, "utf8")),
|
|
484
782
|
fileExists: options.fileExists ?? ((path) => existsSync(path)),
|
|
485
|
-
listDir: options.listDir ?? ((path) => readdirSync(path))
|
|
783
|
+
listDir: options.listDir ?? ((path) => readdirSync(path)),
|
|
784
|
+
lookPath: options.lookPath ?? realLookPath
|
|
486
785
|
};
|
|
487
786
|
const items = () => (options.config?.tasks ?? []).map((i) => normalizeTaskEntry(i));
|
|
787
|
+
const rootFiles = () => {
|
|
788
|
+
try {
|
|
789
|
+
return deps.listDir(options.cwd);
|
|
790
|
+
} catch {
|
|
791
|
+
return [];
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
const lintEntry = () => items().filter((e) => e.name === "lint").at(-1);
|
|
488
795
|
return {
|
|
489
796
|
run: (task, opts = {}) => runTask({
|
|
490
797
|
...deps,
|
|
@@ -492,7 +799,8 @@ function createAstromech(options) {
|
|
|
492
799
|
cwd: options.cwd,
|
|
493
800
|
passthrough: opts.passthrough ?? [],
|
|
494
801
|
dryRun: opts.dryRun ?? false,
|
|
495
|
-
required: opts.required ?? false
|
|
802
|
+
required: opts.required ?? false,
|
|
803
|
+
...task === "lint" ? { linters: lintEntry()?.linters } : {}
|
|
496
804
|
}),
|
|
497
805
|
thinCallers: () => {
|
|
498
806
|
const orgCtx = options.orgContext ?? {};
|
|
@@ -501,10 +809,17 @@ function createAstromech(options) {
|
|
|
501
809
|
if (entry.ci === false || !KNOWN_WORKFLOWS.has(entry.name)) continue;
|
|
502
810
|
const rawWith = entry.with;
|
|
503
811
|
const normalized = rawWith ? normalizeWorkflowWith(rawWith) : void 0;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
812
|
+
let withOverrides = normalized;
|
|
813
|
+
let comments;
|
|
814
|
+
if (entry.name === "lint") {
|
|
815
|
+
const lint = lintThinCallerWith({
|
|
816
|
+
explicit: entry.linters,
|
|
817
|
+
rootFiles: rootFiles(),
|
|
818
|
+
extra: normalized
|
|
819
|
+
});
|
|
820
|
+
withOverrides = lint.withOverrides;
|
|
821
|
+
comments = lint.comments;
|
|
822
|
+
}
|
|
508
823
|
const additionalPaths = entry.paths ?? (entry.name === "deploy" && rawWith ? deriveDeployPaths(rawWith) : void 0);
|
|
509
824
|
if (entry.name === "deploy" && rawWith) {
|
|
510
825
|
const preview = extractPreviewConfig(rawWith, orgCtx);
|
|
@@ -515,7 +830,7 @@ function createAstromech(options) {
|
|
|
515
830
|
continue;
|
|
516
831
|
}
|
|
517
832
|
}
|
|
518
|
-
out.set(`${entry.name}.yml`, generateThinCallerContent(entry.name, withOverrides, additionalPaths, deps.logger));
|
|
833
|
+
out.set(`${entry.name}.yml`, generateThinCallerContent(entry.name, withOverrides, additionalPaths, deps.logger, comments));
|
|
519
834
|
}
|
|
520
835
|
return out;
|
|
521
836
|
},
|
|
@@ -528,183 +843,12 @@ function createAstromech(options) {
|
|
|
528
843
|
out[entry.name] = `holocron run ${entry.name}`;
|
|
529
844
|
}
|
|
530
845
|
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
|
|
846
|
+
},
|
|
847
|
+
superLinterConfig: () => superLinterConfig({
|
|
848
|
+
explicit: lintEntry()?.linters,
|
|
849
|
+
rootFiles: rootFiles()
|
|
850
|
+
})
|
|
699
851
|
};
|
|
700
852
|
}
|
|
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
853
|
//#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 };
|
|
854
|
+
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.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"
|