@theholocron/astromech 4.6.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -90,6 +90,7 @@ const astromech = createAstromech({ cwd, config, orgContext: { org, domain } });
90
90
 
91
91
  astromech.thinCallers(); // Map<"<name>.yml", yaml> — one per templated, ci-enabled task
92
92
  astromech.packageScripts(); // { holocron: "holocron", lint: "holocron run lint", … }
93
+ astromech.requiredChecks(); // ["Lint / Conclusion", "codecov/patch", …] — branch-protection contexts
93
94
  ```
94
95
 
95
96
  `thinCallers()` returns the raw `.github/workflows/*.yml` content (no
@@ -101,6 +102,16 @@ per runnable task; it skips `local: false` entries and tasks with no local
101
102
  runner (`codeql`, `deploy`), and returns `{}` when `syncScripts: false` or
102
103
  there is no config.
103
104
 
105
+ ### Required checks
106
+
107
+ `requiredChecks()` derives the branch-protection required-status-check list
108
+ from the manifest: every `{ required: true }` task's check context (the
109
+ `… / Conclusion` aggregate job, from `WORKFLOW_CHECK_CONTEXTS`), ordered by
110
+ `CI_ORDER`, then `config.extraRequiredChecks` (codecov gates, the
111
+ bundle-build check, …), de-duplicated. `holocron setup` prepends `"DCO"` and
112
+ applies the list for `protection: "strict"` repos. Policy-free — manifest
113
+ only.
114
+
104
115
  `holocron run` itself does not read the config yet — that (and
105
116
  `holocron ci`) come in later phases (epic #581).
106
117
 
@@ -1,4 +1,4 @@
1
- import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskConfigItem } from "../schema-DbpiBZCP.mjs";
1
+ import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskConfigItem } from "../schema-uPSn69gZ.mjs";
2
2
  //#region src/config/define.d.ts
3
3
  /**
4
4
  * Typed identity helper for `astromech.config.ts`:
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as TasksConfig } from "./schema-DbpiBZCP.mjs";
1
+ import { r as TasksConfig } from "./schema-uPSn69gZ.mjs";
2
2
  //#region src/run.d.ts
3
3
  /**
4
4
  * `holocron run <task> [-- <passthrough>]` — run a registry task locally.
@@ -135,12 +135,13 @@ declare function lintThinCallerWith(opts: {
135
135
  declare const WORKFLOW_TEMPLATES: Record<string, string>;
136
136
  declare const KNOWN_WORKFLOWS: Set<string>;
137
137
  /**
138
- * GitHub check context name each CI workflow produces on a PR.
138
+ * The GitHub status-check context a `required` task contributes to branch
139
+ * protection. Format: `"{workflow name} / {job name}"`.
139
140
  *
140
- * The format is "{caller-workflow-name} / {reusable-job-name}". The caller
141
- * job's own `name:` field does NOT appear in the external check name — only
142
- * the calling workflow's top-level `name:` and the inner reusable-workflow
143
- * job name matter. Only workflows that gate merges are listed here.
141
+ * These name the **aggregate `Conclusion` job** (fan-in, `if: always()`), not
142
+ * an individual inner job — `test` has several conditionally-run sub-jobs, so
143
+ * `"Test / Conclusion"` is the only stable gate. Only merge-gating workflows
144
+ * are listed. `astro.requiredChecks()` reads this for every `required` task.
144
145
  */
145
146
  declare const WORKFLOW_CHECK_CONTEXTS: Partial<Record<string, string>>;
146
147
  /**
@@ -290,6 +291,13 @@ interface Astromech {
290
291
  * `linters` list, else auto-detection from the repo's config files.
291
292
  */
292
293
  superLinterConfig(): SuperLinterConfig;
294
+ /**
295
+ * The branch-protection required-status-check contexts for this repo —
296
+ * every `required: true` task's check context plus `extraRequiredChecks`,
297
+ * ordered and de-duplicated. `holocron setup` prepends `"DCO"` and applies
298
+ * the list; this method is policy-free (manifest only).
299
+ */
300
+ requiredChecks(): string[];
293
301
  }
294
302
  declare function createAstromech(options: AstromechOptions): Astromech;
295
303
  //#endregion
@@ -407,5 +415,16 @@ interface TaskDef {
407
415
  declare const TASKS: Record<string, TaskDef>;
408
416
  /** Every task name the registry knows. */
409
417
  declare const KNOWN_TASKS: Set<string>;
418
+ /**
419
+ * The order `holocron ci` runs tasks in — cheapest / fastest signal first, so
420
+ * an agent or a `pre-push` hook fails early. Tasks not listed here run last, in
421
+ * manifest order. (The generated thin callers carry no `needs:` — cross-workflow
422
+ * ordering lives in `theholocron/.github` — so `holocron ci` declares its own.)
423
+ */
424
+ declare const CI_ORDER: string[];
425
+ //#endregion
426
+ //#region src/required-checks.d.ts
427
+ /** Ordered (task contexts in {@link CI_ORDER}, then extras), de-duplicated. */
428
+ declare function requiredChecks(config: TasksConfig): string[];
410
429
  //#endregion
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 };
430
+ export { type Astromech, type AstromechOptions, CI_ORDER, 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, requiredChecks, resolveLinters, runTask, superLinterConfig };
package/dist/index.mjs CHANGED
@@ -45,11 +45,318 @@ const TASKS = {
45
45
  ] } },
46
46
  sync: { local: { command: "sync" } },
47
47
  wiki: { local: { command: "sync-wiki" } },
48
+ audit: { local: null },
48
49
  codeql: { local: null },
49
50
  deploy: { local: null }
50
51
  };
51
52
  /** Every task name the registry knows. */
52
53
  const KNOWN_TASKS = new Set(Object.keys(TASKS));
54
+ /**
55
+ * The order `holocron ci` runs tasks in — cheapest / fastest signal first, so
56
+ * an agent or a `pre-push` hook fails early. Tasks not listed here run last, in
57
+ * manifest order. (The generated thin callers carry no `needs:` — cross-workflow
58
+ * ordering lives in `theholocron/.github` — so `holocron ci` declares its own.)
59
+ */
60
+ const CI_ORDER = [
61
+ "typecheck",
62
+ "lint",
63
+ "test",
64
+ "build",
65
+ "audit",
66
+ "codeql",
67
+ "deploy"
68
+ ];
69
+ //#endregion
70
+ //#region src/thin-callers.ts
71
+ /**
72
+ * Workflow templates + thin-caller generation.
73
+ *
74
+ * `WORKFLOW_TEMPLATES` holds each reusable `.github/workflows/<name>.yml`
75
+ * (synced to `theholocron/.github`); `generateThinCallerContent` wraps one
76
+ * into the thin caller `holocron setup` / `holocron sync` write locally.
77
+ */
78
+ const WORKFLOW_TEMPLATES = {
79
+ lint: "name: Lint\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: lint-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write\n issues: write\n statuses: write\n\njobs:\n lint:\n name: Lint\n uses: theholocron/.github/.github/workflows/lint.yml@main\n secrets: inherit\n",
80
+ test: "name: Test\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: test-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n id-token: write\n statuses: write\n\njobs:\n test:\n name: Test\n uses: theholocron/.github/.github/workflows/test.yml@main\n with:\n run-unit: true\n secrets: inherit\n",
81
+ typecheck: "name: Typecheck\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: typecheck-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n\njobs:\n typecheck:\n name: Typecheck\n uses: theholocron/.github/.github/workflows/typecheck.yml@main\n secrets: inherit\n",
82
+ security: "name: Security\n\non: # yamllint disable-line rule:truthy\n push:\n branches:\n - main\n pull_request:\n branches:\n - main\n schedule:\n - cron: \"0 0 * * 1\"\n\npermissions:\n actions: read\n contents: read\n security-events: write\n\njobs:\n security:\n uses: theholocron/.github/.github/workflows/security.yml@main\n secrets: inherit\n",
83
+ preview: "name: Preview\n\non: # yamllint disable-line rule:truthy\n pull_request:\n branches: [main]\n\nconcurrency:\n group: preview-${{ github.event.pull_request.number }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n deployments: write\n pull-requests: write\n\njobs:\n preview:\n name: Preview\n uses: theholocron/.github/.github/workflows/preview.yml@main\n secrets: inherit\n",
84
+ review: "name: Review\n\non: # yamllint disable-line rule:truthy\n pull_request:\n\nconcurrency:\n group: review-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n checks: write\n pull-requests: write\n\njobs:\n review:\n name: Review\n uses: theholocron/.github/.github/workflows/review.yml@main\n secrets: inherit\n",
85
+ release: "name: Release\n\non: # yamllint disable-line rule:truthy\n push:\n branches:\n - main\n - alpha\n workflow_dispatch:\n inputs:\n dry_run:\n description: >\n Dry run — analyze commits and preview the release without git writes\n or publish. Push-triggered runs always run fully; this only applies\n to manual workflow_dispatch triggers.\n required: false\n default: true\n type: boolean\n\npermissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n\nconcurrency:\n group: ${{ github.workflow }}-${{ github.ref }}\n cancel-in-progress: false\n\njobs:\n release:\n uses: theholocron/.github/.github/workflows/release.yml@main\n with:\n dry-run: ${{ inputs.dry_run == true }}\n secrets: inherit\n",
86
+ stale: "name: Stale\n\non: # yamllint disable-line rule:truthy\n schedule:\n - cron: \"30 1 * * *\"\n\npermissions:\n contents: write\n issues: write\n pull-requests: write\n\njobs:\n stale:\n uses: theholocron/.github/.github/workflows/stale.yml@main\n with:\n exempt-issue-labels: \"in-progress,wip\"\n exempt-all-issue-milestones: true\n exempt-all-issue-projects: true\n exempt-all-pr-projects: true\n secrets: inherit\n",
87
+ sync: "name: Sync\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n paths:\n - holocron.config.ts\n - package.json\n - pnpm-workspace.yaml\n workflow_dispatch:\n inputs:\n steps:\n description: \"Sync steps to run (default: all)\"\n type: string\n required: false\n\nconcurrency:\n group: sync-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write\n pull-requests: write\n\njobs:\n sync:\n name: Sync\n uses: theholocron/.github/.github/workflows/sync.yml@main\n with:\n steps: ${{ inputs.steps }}\n secrets: inherit\n",
88
+ greetings: "name: Greetings\n\non: # yamllint disable-line rule:truthy\n pull_request:\n issues:\n\npermissions:\n issues: write\n pull-requests: write\n\njobs:\n greetings:\n uses: theholocron/.github/.github/workflows/greetings.yml@main\n secrets: inherit\n",
89
+ dependencies: "name: Dependencies\n\non: # yamllint disable-line rule:truthy\n pull_request:\n\npermissions:\n contents: write\n pull-requests: write\n\njobs:\n dependencies:\n uses: theholocron/.github/.github/workflows/dependencies.yml@main\n secrets: inherit\n",
90
+ bookkeeping: "name: Bookkeeping\n\non: # yamllint disable-line rule:truthy\n pull_request:\n types:\n - opened\n - edited\n\npermissions:\n contents: read\n issues: write\n pull-requests: write\n\njobs:\n bookkeeping:\n uses: theholocron/.github/.github/workflows/bookkeeping.yml@main\n secrets: inherit\n",
91
+ audit: "name: Audit\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n audit:\n uses: theholocron/.github/.github/workflows/audit.yml@main\n secrets: inherit\n",
92
+ deploy: "name: Deploy\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n workflow_dispatch:\n\nconcurrency:\n group: pages\n cancel-in-progress: false\n\npermissions:\n contents: read\n pages: write\n id-token: write\n\njobs:\n deploy:\n name: Deploy\n uses: theholocron/.github/.github/workflows/deploy.yml@main\n secrets: inherit\n",
93
+ wiki: "name: Wiki\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\nconcurrency:\n group: ${{ github.event_name == 'pull_request' && format('wiki-preview-{0}', github.event.pull_request.number) || 'wiki' }}\n cancel-in-progress: ${{ github.event_name == 'pull_request' }}\n\npermissions:\n contents: read\n deployments: write\n\njobs:\n publish:\n name: Publish\n if: ${{ github.event_name != 'pull_request' }}\n uses: theholocron/.github/.github/workflows/wiki.yml@main\n secrets: inherit\n\n preview:\n name: Preview\n if: ${{ github.event_name == 'pull_request' }}\n uses: theholocron/.github/.github/workflows/wiki.yml@main\n with:\n preview: true\n preview-id: pr-${{ github.event.pull_request.number }}\n secrets: inherit\n"
94
+ };
95
+ const KNOWN_WORKFLOWS = new Set(Object.keys(WORKFLOW_TEMPLATES));
96
+ /**
97
+ * The GitHub status-check context a `required` task contributes to branch
98
+ * protection. Format: `"{workflow name} / {job name}"`.
99
+ *
100
+ * These name the **aggregate `Conclusion` job** (fan-in, `if: always()`), not
101
+ * an individual inner job — `test` has several conditionally-run sub-jobs, so
102
+ * `"Test / Conclusion"` is the only stable gate. Only merge-gating workflows
103
+ * are listed. `astro.requiredChecks()` reads this for every `required` task.
104
+ */
105
+ const WORKFLOW_CHECK_CONTEXTS = {
106
+ lint: "Lint / Conclusion",
107
+ test: "Test / Conclusion",
108
+ typecheck: "Typecheck / Conclusion",
109
+ audit: "audit / Conclusion"
110
+ };
111
+ /**
112
+ * Generate the thin caller content for a workflow, optionally injecting or
113
+ * merging `with:` overrides into the jobs block.
114
+ *
115
+ * Two strategies are used depending on the template:
116
+ * - Templates that already have a `with:` block (e.g. lint):
117
+ * the override entries are merged in, replacing existing keys and appending
118
+ * new ones.
119
+ * - Templates that end with ` secrets: inherit`: a new `with:` block is
120
+ * injected immediately before `secrets: inherit`.
121
+ * If neither pattern matches the template, a warning is emitted and the
122
+ * base template is returned unchanged.
123
+ */
124
+ function generateThinCallerContent(name, withOverrides, additionalPaths, logger, comments) {
125
+ const base = WORKFLOW_TEMPLATES[name];
126
+ if (!base) return "";
127
+ const yamlScalar = (v) => {
128
+ if (v === true) return "true";
129
+ if (v === false) return "false";
130
+ const s = String(v);
131
+ return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
132
+ };
133
+ const fmt = (k, v) => {
134
+ const line = ` ${k}: ${yamlScalar(v)}`;
135
+ return comments?.[k] ? ` # ${comments[k]}\n${line}` : line;
136
+ };
137
+ let result = base;
138
+ if (additionalPaths && additionalPaths.length > 0) {
139
+ const pathsBlockRe = /( {4}paths:\n)((?:[ ]{6}- [^\n]+\n)+)/;
140
+ if (pathsBlockRe.test(result)) result = result.replace(pathsBlockRe, (_, header, existing) => {
141
+ const existingPaths = new Set([...existing.matchAll(/- (.+)/g)].map((m) => m[1]));
142
+ const newEntries = additionalPaths.filter((p) => !existingPaths.has(p)).map((p) => ` - ${p}\n`).join("");
143
+ return header + existing + newEntries;
144
+ });
145
+ else {
146
+ const pathsBlock = ` paths:\n${additionalPaths.map((p) => ` - ${p}\n`).join("")}`;
147
+ result = result.replace(/( {4}branches: \[main\]\n)/, `$1${pathsBlock}`);
148
+ }
149
+ }
150
+ if (!withOverrides || Object.keys(withOverrides).length === 0) return result;
151
+ const withBlockRe = /( {4}with:\n)((?:[ ]{6}[^\n]+\n)*)/;
152
+ const existingMatch = result.match(withBlockRe);
153
+ if (existingMatch) {
154
+ const existingEntries = new Map(existingMatch[2].split("\n").filter(Boolean).filter((line) => !line.trim().startsWith("#")).map((line) => {
155
+ const m = line.match(/^ {6}([^:]+):\s*(.*)/);
156
+ return m ? [m[1].trim(), m[2].trim()] : null;
157
+ }).filter((e) => e !== null));
158
+ for (const [k, v] of Object.entries(withOverrides)) existingEntries.set(k, yamlScalar(v));
159
+ const merged = [...existingEntries.entries()].map(([k, v]) => ` ${k}: ${v}`).join("\n");
160
+ return result.replace(withBlockRe, ` with:\n${merged}\n`);
161
+ }
162
+ const withBlock = Object.entries(withOverrides).map(([k, v]) => fmt(k, v)).join("\n");
163
+ const injected = result.replace(/ {4}secrets: inherit\n$/, ` with:\n${withBlock}\n secrets: inherit\n`);
164
+ if (injected === result) logger?.warn({ template: name }, "generateThinCallerContent: could not inject `with:` overrides");
165
+ return injected;
166
+ }
167
+ /**
168
+ * Extract the Cloudflare Pages preview config from a deploy workflow's `with:` object.
169
+ *
170
+ * Accepts three forms:
171
+ * - `preview: true` — derive both project and domain from org context
172
+ * - `preview: { project: "..." }` — explicit project; domain derived from context if omitted
173
+ * - `preview: { project: "...", domain: "..." }` — fully explicit
174
+ *
175
+ * Returns null when `preview:` is absent, false, or can't be resolved.
176
+ */
177
+ function extractPreviewConfig(raw, ctx = {}) {
178
+ const preview = raw["preview"];
179
+ if (!preview) return null;
180
+ if (preview === true) {
181
+ const project = ctx.org ? `${ctx.org}-preview` : null;
182
+ const domain = ctx.domain ? `preview.${ctx.domain}` : void 0;
183
+ if (!project) return null;
184
+ return {
185
+ project,
186
+ ...domain ? { domain } : {}
187
+ };
188
+ }
189
+ if (typeof preview !== "object") return null;
190
+ const p = preview;
191
+ const project = typeof p["project"] === "string" && p["project"] ? p["project"] : ctx.org ? `${ctx.org}-preview` : null;
192
+ if (!project) return null;
193
+ const domain = typeof p["domain"] === "string" && p["domain"] ? p["domain"] : ctx.domain ? `preview.${ctx.domain}` : void 0;
194
+ return {
195
+ project,
196
+ ...domain ? { domain } : {}
197
+ };
198
+ }
199
+ /**
200
+ * Generate the full thin-caller YAML for a `deploy.yml` that handles both
201
+ * production (push to main → GitHub Pages) and preview (pull_request →
202
+ * Cloudflare Pages) in a single file.
203
+ *
204
+ * Both jobs receive the same docs/storybook `with:` inputs. If the per-repo
205
+ * config supplies `cloudflare-project` it is forwarded; otherwise the reusable
206
+ * falls back to the `CLOUDFLARE_PAGES_PROJECT` org variable — set that once and
207
+ * all repos with a `deploy` workflow get previews without per-repo config.
208
+ */
209
+ function generateCombinedDeployContent(deployWith, paths, preview) {
210
+ const yamlScalar = (v) => {
211
+ if (v === true) return "true";
212
+ if (v === false) return "false";
213
+ const s = String(v);
214
+ return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
215
+ };
216
+ const withLines = (entries) => Object.entries(entries).map(([k, v]) => ` ${k}: ${yamlScalar(v)}`).join("\n");
217
+ const pathsBlock = paths.length > 0 ? ` paths:\n${paths.map((p) => ` - ${p}\n`).join("")}` : "";
218
+ const previewWith = {
219
+ ...deployWith,
220
+ "cloudflare-project": preview.project
221
+ };
222
+ const deployWithBlock = Object.keys(deployWith).length > 0 ? ` with:\n${withLines(deployWith)}\n` : "";
223
+ const previewWithBlock = ` with:\n${withLines(previewWith)}\n`;
224
+ return [
225
+ `name: Deploy`,
226
+ ``,
227
+ `on: # yamllint disable-line rule:truthy`,
228
+ ` push:`,
229
+ ` branches: [main]`,
230
+ ...pathsBlock ? [`${pathsBlock}`] : [],
231
+ ` pull_request:`,
232
+ ` branches: [main]`,
233
+ ` types: [opened, synchronize, reopened, closed]`,
234
+ ...pathsBlock ? [`${pathsBlock}`] : [],
235
+ ` workflow_dispatch:`,
236
+ ``,
237
+ `concurrency:`,
238
+ ` group: $\{{ github.event_name == 'pull_request' && format('preview-{0}', github.event.pull_request.number) || 'pages' }}`,
239
+ ` cancel-in-progress: $\{{ github.event_name == 'pull_request' && github.event.action != 'closed' }}`,
240
+ ``,
241
+ `permissions:`,
242
+ ` contents: read`,
243
+ ` deployments: write`,
244
+ ` pages: write`,
245
+ ` id-token: write`,
246
+ ` pull-requests: write`,
247
+ ``,
248
+ `jobs:`,
249
+ ` deploy:`,
250
+ ` name: Deploy`,
251
+ ` if: \${{ github.event_name != 'pull_request' }}`,
252
+ ` uses: theholocron/.github/.github/workflows/deploy.yml@main`,
253
+ ...deployWithBlock ? [deployWithBlock.trimEnd()] : [],
254
+ ` secrets: inherit`,
255
+ ``,
256
+ ` preview:`,
257
+ ` name: Preview`,
258
+ ` if: \${{ github.event_name == 'pull_request' }}`,
259
+ ` uses: theholocron/.github/.github/workflows/preview.yml@main`,
260
+ previewWithBlock.trimEnd(),
261
+ ` secrets: inherit`,
262
+ ``
263
+ ].join("\n");
264
+ }
265
+ /**
266
+ * Expand structured with-values to flat GitHub Actions inputs before
267
+ * generating the thin caller. Handles:
268
+ * - deploy shorthand: docs/storybook → type + storybook-projects
269
+ * - preview: stripped (handled separately via extractPreviewConfig)
270
+ * - run-chromatic object → run-chromatic: true + chromatic-projects
271
+ * - plain arrays → JSON-stringified for YAML scalar quoting
272
+ *
273
+ * Used by both `holocron setup` and `sync-workflow-templates`.
274
+ */
275
+ function normalizeWorkflowWith(raw) {
276
+ const result = { ...raw };
277
+ delete result["preview"];
278
+ const hasDocs = raw["docs"] === true || raw["docs"] !== null && typeof raw["docs"] === "object";
279
+ const storybookProjects = raw["storybook"];
280
+ if (hasDocs) {
281
+ result["type"] = "docs";
282
+ delete result["docs"];
283
+ }
284
+ if (Array.isArray(storybookProjects)) {
285
+ if (!hasDocs) result["type"] = "storybook";
286
+ result["storybook-projects"] = JSON.stringify(storybookProjects.map(({ name, path = "." }) => ({
287
+ name,
288
+ workingDir: path
289
+ })));
290
+ delete result["storybook"];
291
+ }
292
+ const runChromatic = raw["run-chromatic"];
293
+ if (runChromatic !== null && typeof runChromatic === "object" && "projects" in runChromatic) {
294
+ result["run-chromatic"] = true;
295
+ const projects = runChromatic.projects.map((p) => ({
296
+ ...p,
297
+ ...Array.isArray(p.untraced) ? { untraced: p.untraced.join("\n") } : {}
298
+ }));
299
+ result["chromatic-projects"] = JSON.stringify(projects);
300
+ }
301
+ for (const [k, v] of Object.entries(result)) if (Array.isArray(v)) result[k] = JSON.stringify(v);
302
+ return result;
303
+ }
304
+ /**
305
+ * Derive on.push.paths entries from the deploy with: shorthand.
306
+ * Used by both `holocron setup` and `sync-workflow-templates`.
307
+ */
308
+ function deriveDeployPaths(raw) {
309
+ const paths = [];
310
+ const docs = raw["docs"];
311
+ if (docs === true) {
312
+ paths.push("docs/**");
313
+ paths.push("astro.config.ts");
314
+ paths.push("pnpm-workspace.yaml");
315
+ paths.push("pnpm-lock.yaml");
316
+ } else if (docs !== null && typeof docs === "object" && "path" in docs) {
317
+ const p = docs.path;
318
+ if (p && p !== ".") paths.push(`${p}/**`);
319
+ }
320
+ const storybookProjects = raw["storybook"];
321
+ if (Array.isArray(storybookProjects)) for (const s of storybookProjects) {
322
+ const p = s.path || ".";
323
+ if (p === ".") {
324
+ paths.push("src/**");
325
+ paths.push(".storybook/**");
326
+ } else paths.push(`${p}/**`);
327
+ }
328
+ return paths;
329
+ }
330
+ //#endregion
331
+ //#region src/required-checks.ts
332
+ /**
333
+ * `requiredChecks(config)` — the branch-protection required-status-check list,
334
+ * derived from the task manifest. Every `required: true` task contributes its
335
+ * {@link WORKFLOW_CHECK_CONTEXTS} entry; `config.extraRequiredChecks` adds
336
+ * contexts not backed by a task (codecov, DCO is prepended by the caller).
337
+ *
338
+ * `@theholocron/cli`'s `holocron setup` calls this instead of the old
339
+ * hand-maintained `repo.requiredChecks` array. Policy-free — it only knows the
340
+ * manifest.
341
+ */
342
+ /** Ordered (task contexts in {@link CI_ORDER}, then extras), de-duplicated. */
343
+ function requiredChecks(config) {
344
+ const entries = (config.tasks ?? []).map(normalizeTaskEntry);
345
+ const seen = /* @__PURE__ */ new Set();
346
+ const out = [];
347
+ for (const name of CI_ORDER) {
348
+ const context = entries.some((e) => e.name === name && e.required === true) ? WORKFLOW_CHECK_CONTEXTS[name] : void 0;
349
+ if (context && !seen.has(context)) {
350
+ seen.add(context);
351
+ out.push(context);
352
+ }
353
+ }
354
+ for (const context of config.extraRequiredChecks ?? []) if (!seen.has(context)) {
355
+ seen.add(context);
356
+ out.push(context);
357
+ }
358
+ return out;
359
+ }
53
360
  //#endregion
54
361
  //#region src/linters.ts
55
362
  /**
@@ -491,265 +798,6 @@ function lintThinCallerWith(opts) {
491
798
  };
492
799
  }
493
800
  //#endregion
494
- //#region src/thin-callers.ts
495
- /**
496
- * Workflow templates + thin-caller generation.
497
- *
498
- * `WORKFLOW_TEMPLATES` holds each reusable `.github/workflows/<name>.yml`
499
- * (synced to `theholocron/.github`); `generateThinCallerContent` wraps one
500
- * into the thin caller `holocron setup` / `holocron sync` write locally.
501
- */
502
- const WORKFLOW_TEMPLATES = {
503
- lint: "name: Lint\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: lint-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write\n issues: write\n statuses: write\n\njobs:\n lint:\n name: Lint\n uses: theholocron/.github/.github/workflows/lint.yml@main\n secrets: inherit\n",
504
- test: "name: Test\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: test-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n id-token: write\n statuses: write\n\njobs:\n test:\n name: Test\n uses: theholocron/.github/.github/workflows/test.yml@main\n with:\n run-unit: true\n secrets: inherit\n",
505
- typecheck: "name: Typecheck\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: typecheck-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n\njobs:\n typecheck:\n name: Typecheck\n uses: theholocron/.github/.github/workflows/typecheck.yml@main\n secrets: inherit\n",
506
- security: "name: Security\n\non: # yamllint disable-line rule:truthy\n push:\n branches:\n - main\n pull_request:\n branches:\n - main\n schedule:\n - cron: \"0 0 * * 1\"\n\npermissions:\n actions: read\n contents: read\n security-events: write\n\njobs:\n security:\n uses: theholocron/.github/.github/workflows/security.yml@main\n secrets: inherit\n",
507
- preview: "name: Preview\n\non: # yamllint disable-line rule:truthy\n pull_request:\n branches: [main]\n\nconcurrency:\n group: preview-${{ github.event.pull_request.number }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n deployments: write\n pull-requests: write\n\njobs:\n preview:\n name: Preview\n uses: theholocron/.github/.github/workflows/preview.yml@main\n secrets: inherit\n",
508
- review: "name: Review\n\non: # yamllint disable-line rule:truthy\n pull_request:\n\nconcurrency:\n group: review-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n checks: write\n pull-requests: write\n\njobs:\n review:\n name: Review\n uses: theholocron/.github/.github/workflows/review.yml@main\n secrets: inherit\n",
509
- release: "name: Release\n\non: # yamllint disable-line rule:truthy\n push:\n branches:\n - main\n - alpha\n workflow_dispatch:\n inputs:\n dry_run:\n description: >\n Dry run — analyze commits and preview the release without git writes\n or publish. Push-triggered runs always run fully; this only applies\n to manual workflow_dispatch triggers.\n required: false\n default: true\n type: boolean\n\npermissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n\nconcurrency:\n group: ${{ github.workflow }}-${{ github.ref }}\n cancel-in-progress: false\n\njobs:\n release:\n uses: theholocron/.github/.github/workflows/release.yml@main\n with:\n dry-run: ${{ inputs.dry_run == true }}\n secrets: inherit\n",
510
- stale: "name: Stale\n\non: # yamllint disable-line rule:truthy\n schedule:\n - cron: \"30 1 * * *\"\n\npermissions:\n contents: write\n issues: write\n pull-requests: write\n\njobs:\n stale:\n uses: theholocron/.github/.github/workflows/stale.yml@main\n with:\n exempt-issue-labels: \"in-progress,wip\"\n exempt-all-issue-milestones: true\n exempt-all-issue-projects: true\n exempt-all-pr-projects: true\n secrets: inherit\n",
511
- sync: "name: Sync\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n paths:\n - holocron.config.ts\n - package.json\n - pnpm-workspace.yaml\n workflow_dispatch:\n inputs:\n steps:\n description: \"Sync steps to run (default: all)\"\n type: string\n required: false\n\nconcurrency:\n group: sync-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write\n pull-requests: write\n\njobs:\n sync:\n name: Sync\n uses: theholocron/.github/.github/workflows/sync.yml@main\n with:\n steps: ${{ inputs.steps }}\n secrets: inherit\n",
512
- greetings: "name: Greetings\n\non: # yamllint disable-line rule:truthy\n pull_request:\n issues:\n\npermissions:\n issues: write\n pull-requests: write\n\njobs:\n greetings:\n uses: theholocron/.github/.github/workflows/greetings.yml@main\n secrets: inherit\n",
513
- dependencies: "name: Dependencies\n\non: # yamllint disable-line rule:truthy\n pull_request:\n\npermissions:\n contents: write\n pull-requests: write\n\njobs:\n dependencies:\n uses: theholocron/.github/.github/workflows/dependencies.yml@main\n secrets: inherit\n",
514
- bookkeeping: "name: Bookkeeping\n\non: # yamllint disable-line rule:truthy\n pull_request:\n types:\n - opened\n - edited\n\npermissions:\n contents: read\n issues: write\n pull-requests: write\n\njobs:\n bookkeeping:\n uses: theholocron/.github/.github/workflows/bookkeeping.yml@main\n secrets: inherit\n",
515
- audit: "name: Audit\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n audit:\n uses: theholocron/.github/.github/workflows/audit.yml@main\n secrets: inherit\n",
516
- deploy: "name: Deploy\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n workflow_dispatch:\n\nconcurrency:\n group: pages\n cancel-in-progress: false\n\npermissions:\n contents: read\n pages: write\n id-token: write\n\njobs:\n deploy:\n name: Deploy\n uses: theholocron/.github/.github/workflows/deploy.yml@main\n secrets: inherit\n",
517
- wiki: "name: Wiki\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\nconcurrency:\n group: ${{ github.event_name == 'pull_request' && format('wiki-preview-{0}', github.event.pull_request.number) || 'wiki' }}\n cancel-in-progress: ${{ github.event_name == 'pull_request' }}\n\npermissions:\n contents: read\n deployments: write\n\njobs:\n publish:\n name: Publish\n if: ${{ github.event_name != 'pull_request' }}\n uses: theholocron/.github/.github/workflows/wiki.yml@main\n secrets: inherit\n\n preview:\n name: Preview\n if: ${{ github.event_name == 'pull_request' }}\n uses: theholocron/.github/.github/workflows/wiki.yml@main\n with:\n preview: true\n preview-id: pr-${{ github.event.pull_request.number }}\n secrets: inherit\n"
518
- };
519
- const KNOWN_WORKFLOWS = new Set(Object.keys(WORKFLOW_TEMPLATES));
520
- /**
521
- * GitHub check context name each CI workflow produces on a PR.
522
- *
523
- * The format is "{caller-workflow-name} / {reusable-job-name}". The caller
524
- * job's own `name:` field does NOT appear in the external check name — only
525
- * the calling workflow's top-level `name:` and the inner reusable-workflow
526
- * job name matter. Only workflows that gate merges are listed here.
527
- */
528
- const WORKFLOW_CHECK_CONTEXTS = {
529
- lint: "Lint / Lint entire codebase",
530
- test: "Test / Run tests and collect coverage",
531
- typecheck: "Typecheck / tsc --noEmit"
532
- };
533
- /**
534
- * Generate the thin caller content for a workflow, optionally injecting or
535
- * merging `with:` overrides into the jobs block.
536
- *
537
- * Two strategies are used depending on the template:
538
- * - Templates that already have a `with:` block (e.g. lint):
539
- * the override entries are merged in, replacing existing keys and appending
540
- * new ones.
541
- * - Templates that end with ` secrets: inherit`: a new `with:` block is
542
- * injected immediately before `secrets: inherit`.
543
- * If neither pattern matches the template, a warning is emitted and the
544
- * base template is returned unchanged.
545
- */
546
- function generateThinCallerContent(name, withOverrides, additionalPaths, logger, comments) {
547
- const base = WORKFLOW_TEMPLATES[name];
548
- if (!base) return "";
549
- const yamlScalar = (v) => {
550
- if (v === true) return "true";
551
- if (v === false) return "false";
552
- const s = String(v);
553
- return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
554
- };
555
- const fmt = (k, v) => {
556
- const line = ` ${k}: ${yamlScalar(v)}`;
557
- return comments?.[k] ? ` # ${comments[k]}\n${line}` : line;
558
- };
559
- let result = base;
560
- if (additionalPaths && additionalPaths.length > 0) {
561
- const pathsBlockRe = /( {4}paths:\n)((?:[ ]{6}- [^\n]+\n)+)/;
562
- if (pathsBlockRe.test(result)) result = result.replace(pathsBlockRe, (_, header, existing) => {
563
- const existingPaths = new Set([...existing.matchAll(/- (.+)/g)].map((m) => m[1]));
564
- const newEntries = additionalPaths.filter((p) => !existingPaths.has(p)).map((p) => ` - ${p}\n`).join("");
565
- return header + existing + newEntries;
566
- });
567
- else {
568
- const pathsBlock = ` paths:\n${additionalPaths.map((p) => ` - ${p}\n`).join("")}`;
569
- result = result.replace(/( {4}branches: \[main\]\n)/, `$1${pathsBlock}`);
570
- }
571
- }
572
- if (!withOverrides || Object.keys(withOverrides).length === 0) return result;
573
- const withBlockRe = /( {4}with:\n)((?:[ ]{6}[^\n]+\n)*)/;
574
- const existingMatch = result.match(withBlockRe);
575
- if (existingMatch) {
576
- const existingEntries = new Map(existingMatch[2].split("\n").filter(Boolean).filter((line) => !line.trim().startsWith("#")).map((line) => {
577
- const m = line.match(/^ {6}([^:]+):\s*(.*)/);
578
- return m ? [m[1].trim(), m[2].trim()] : null;
579
- }).filter((e) => e !== null));
580
- for (const [k, v] of Object.entries(withOverrides)) existingEntries.set(k, yamlScalar(v));
581
- const merged = [...existingEntries.entries()].map(([k, v]) => ` ${k}: ${v}`).join("\n");
582
- return result.replace(withBlockRe, ` with:\n${merged}\n`);
583
- }
584
- const withBlock = Object.entries(withOverrides).map(([k, v]) => fmt(k, v)).join("\n");
585
- const injected = result.replace(/ {4}secrets: inherit\n$/, ` with:\n${withBlock}\n secrets: inherit\n`);
586
- if (injected === result) logger?.warn({ template: name }, "generateThinCallerContent: could not inject `with:` overrides");
587
- return injected;
588
- }
589
- /**
590
- * Extract the Cloudflare Pages preview config from a deploy workflow's `with:` object.
591
- *
592
- * Accepts three forms:
593
- * - `preview: true` — derive both project and domain from org context
594
- * - `preview: { project: "..." }` — explicit project; domain derived from context if omitted
595
- * - `preview: { project: "...", domain: "..." }` — fully explicit
596
- *
597
- * Returns null when `preview:` is absent, false, or can't be resolved.
598
- */
599
- function extractPreviewConfig(raw, ctx = {}) {
600
- const preview = raw["preview"];
601
- if (!preview) return null;
602
- if (preview === true) {
603
- const project = ctx.org ? `${ctx.org}-preview` : null;
604
- const domain = ctx.domain ? `preview.${ctx.domain}` : void 0;
605
- if (!project) return null;
606
- return {
607
- project,
608
- ...domain ? { domain } : {}
609
- };
610
- }
611
- if (typeof preview !== "object") return null;
612
- const p = preview;
613
- const project = typeof p["project"] === "string" && p["project"] ? p["project"] : ctx.org ? `${ctx.org}-preview` : null;
614
- if (!project) return null;
615
- const domain = typeof p["domain"] === "string" && p["domain"] ? p["domain"] : ctx.domain ? `preview.${ctx.domain}` : void 0;
616
- return {
617
- project,
618
- ...domain ? { domain } : {}
619
- };
620
- }
621
- /**
622
- * Generate the full thin-caller YAML for a `deploy.yml` that handles both
623
- * production (push to main → GitHub Pages) and preview (pull_request →
624
- * Cloudflare Pages) in a single file.
625
- *
626
- * Both jobs receive the same docs/storybook `with:` inputs. If the per-repo
627
- * config supplies `cloudflare-project` it is forwarded; otherwise the reusable
628
- * falls back to the `CLOUDFLARE_PAGES_PROJECT` org variable — set that once and
629
- * all repos with a `deploy` workflow get previews without per-repo config.
630
- */
631
- function generateCombinedDeployContent(deployWith, paths, preview) {
632
- const yamlScalar = (v) => {
633
- if (v === true) return "true";
634
- if (v === false) return "false";
635
- const s = String(v);
636
- return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
637
- };
638
- const withLines = (entries) => Object.entries(entries).map(([k, v]) => ` ${k}: ${yamlScalar(v)}`).join("\n");
639
- const pathsBlock = paths.length > 0 ? ` paths:\n${paths.map((p) => ` - ${p}\n`).join("")}` : "";
640
- const previewWith = {
641
- ...deployWith,
642
- "cloudflare-project": preview.project
643
- };
644
- const deployWithBlock = Object.keys(deployWith).length > 0 ? ` with:\n${withLines(deployWith)}\n` : "";
645
- const previewWithBlock = ` with:\n${withLines(previewWith)}\n`;
646
- return [
647
- `name: Deploy`,
648
- ``,
649
- `on: # yamllint disable-line rule:truthy`,
650
- ` push:`,
651
- ` branches: [main]`,
652
- ...pathsBlock ? [`${pathsBlock}`] : [],
653
- ` pull_request:`,
654
- ` branches: [main]`,
655
- ` types: [opened, synchronize, reopened, closed]`,
656
- ...pathsBlock ? [`${pathsBlock}`] : [],
657
- ` workflow_dispatch:`,
658
- ``,
659
- `concurrency:`,
660
- ` group: $\{{ github.event_name == 'pull_request' && format('preview-{0}', github.event.pull_request.number) || 'pages' }}`,
661
- ` cancel-in-progress: $\{{ github.event_name == 'pull_request' && github.event.action != 'closed' }}`,
662
- ``,
663
- `permissions:`,
664
- ` contents: read`,
665
- ` deployments: write`,
666
- ` pages: write`,
667
- ` id-token: write`,
668
- ` pull-requests: write`,
669
- ``,
670
- `jobs:`,
671
- ` deploy:`,
672
- ` name: Deploy`,
673
- ` if: \${{ github.event_name != 'pull_request' }}`,
674
- ` uses: theholocron/.github/.github/workflows/deploy.yml@main`,
675
- ...deployWithBlock ? [deployWithBlock.trimEnd()] : [],
676
- ` secrets: inherit`,
677
- ``,
678
- ` preview:`,
679
- ` name: Preview`,
680
- ` if: \${{ github.event_name == 'pull_request' }}`,
681
- ` uses: theholocron/.github/.github/workflows/preview.yml@main`,
682
- previewWithBlock.trimEnd(),
683
- ` secrets: inherit`,
684
- ``
685
- ].join("\n");
686
- }
687
- /**
688
- * Expand structured with-values to flat GitHub Actions inputs before
689
- * generating the thin caller. Handles:
690
- * - deploy shorthand: docs/storybook → type + storybook-projects
691
- * - preview: stripped (handled separately via extractPreviewConfig)
692
- * - run-chromatic object → run-chromatic: true + chromatic-projects
693
- * - plain arrays → JSON-stringified for YAML scalar quoting
694
- *
695
- * Used by both `holocron setup` and `sync-workflow-templates`.
696
- */
697
- function normalizeWorkflowWith(raw) {
698
- const result = { ...raw };
699
- delete result["preview"];
700
- const hasDocs = raw["docs"] === true || raw["docs"] !== null && typeof raw["docs"] === "object";
701
- const storybookProjects = raw["storybook"];
702
- if (hasDocs) {
703
- result["type"] = "docs";
704
- delete result["docs"];
705
- }
706
- if (Array.isArray(storybookProjects)) {
707
- if (!hasDocs) result["type"] = "storybook";
708
- result["storybook-projects"] = JSON.stringify(storybookProjects.map(({ name, path = "." }) => ({
709
- name,
710
- workingDir: path
711
- })));
712
- delete result["storybook"];
713
- }
714
- const runChromatic = raw["run-chromatic"];
715
- if (runChromatic !== null && typeof runChromatic === "object" && "projects" in runChromatic) {
716
- result["run-chromatic"] = true;
717
- const projects = runChromatic.projects.map((p) => ({
718
- ...p,
719
- ...Array.isArray(p.untraced) ? { untraced: p.untraced.join("\n") } : {}
720
- }));
721
- result["chromatic-projects"] = JSON.stringify(projects);
722
- }
723
- for (const [k, v] of Object.entries(result)) if (Array.isArray(v)) result[k] = JSON.stringify(v);
724
- return result;
725
- }
726
- /**
727
- * Derive on.push.paths entries from the deploy with: shorthand.
728
- * Used by both `holocron setup` and `sync-workflow-templates`.
729
- */
730
- function deriveDeployPaths(raw) {
731
- const paths = [];
732
- const docs = raw["docs"];
733
- if (docs === true) {
734
- paths.push("docs/**");
735
- paths.push("astro.config.ts");
736
- paths.push("pnpm-workspace.yaml");
737
- paths.push("pnpm-lock.yaml");
738
- } else if (docs !== null && typeof docs === "object" && "path" in docs) {
739
- const p = docs.path;
740
- if (p && p !== ".") paths.push(`${p}/**`);
741
- }
742
- const storybookProjects = raw["storybook"];
743
- if (Array.isArray(storybookProjects)) for (const s of storybookProjects) {
744
- const p = s.path || ".";
745
- if (p === ".") {
746
- paths.push("src/**");
747
- paths.push(".storybook/**");
748
- } else paths.push(`${p}/**`);
749
- }
750
- return paths;
751
- }
752
- //#endregion
753
801
  //#region src/astromech.ts
754
802
  /**
755
803
  * `createAstromech(options)` — the self-contained task runner.
@@ -847,8 +895,9 @@ function createAstromech(options) {
847
895
  superLinterConfig: () => superLinterConfig({
848
896
  explicit: lintEntry()?.linters,
849
897
  rootFiles: rootFiles()
850
- })
898
+ }),
899
+ requiredChecks: () => requiredChecks(options.config ?? {})
851
900
  };
852
901
  }
853
902
  //#endregion
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 };
903
+ export { CI_ORDER, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, baselineSuperLinterEnv, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, lintThinCallerWith, normalizeWorkflowWith, requiredChecks, resolveLinters, runTask, superLinterConfig };
@@ -22,8 +22,9 @@ interface TaskEntry {
22
22
  */
23
23
  local?: boolean;
24
24
  /**
25
- * The task's CI check context is a required status check (branch
26
- * protection) and part of `holocron ci`'s default run.
25
+ * The task's CI check context (from `WORKFLOW_CHECK_CONTEXTS`) is a
26
+ * required status check in branch protection (`astro.requiredChecks()`)
27
+ * and part of `holocron ci`'s default run.
27
28
  */
28
29
  required?: boolean;
29
30
  /** Per-repo overrides on the same channel the reusable workflow reads. */
@@ -51,8 +52,9 @@ interface TasksConfig {
51
52
  */
52
53
  holocronScript?: string;
53
54
  /**
54
- * Required status-check contexts not backed by a task — DCO, semantic
55
- * PR title, …
55
+ * Required status-check contexts not backed by a task — codecov gates,
56
+ * a bundle-build check, … Appended to `astro.requiredChecks()` after the
57
+ * `required`-task contexts. (`DCO` is prepended by `holocron setup` itself.)
56
58
  */
57
59
  extraRequiredChecks?: string[];
58
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/astromech",
3
- "version": "4.6.0",
3
+ "version": "4.7.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.6.0"
40
+ "@theholocron/datapad": "4.7.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.6.0"
56
+ "@theholocron/rollup-plugin-transform-template": "4.7.0"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=22"