@theholocron/astromech 4.0.1 → 4.1.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 +99 -1
- package/dist/index.mjs +257 -1
- package/package.json +4 -3
package/dist/index.d.mts
CHANGED
|
@@ -133,4 +133,102 @@ declare const TASKS: Record<string, TaskDef>;
|
|
|
133
133
|
/** Every task name the registry knows. */
|
|
134
134
|
declare const KNOWN_TASKS: Set<string>;
|
|
135
135
|
//#endregion
|
|
136
|
-
|
|
136
|
+
//#region src/thin-callers.d.ts
|
|
137
|
+
/**
|
|
138
|
+
* Workflow templates + thin-caller generation.
|
|
139
|
+
*
|
|
140
|
+
* `WORKFLOW_TEMPLATES` holds each reusable `.github/workflows/<name>.yml`
|
|
141
|
+
* (synced to `theholocron/.github`); `generateThinCallerContent` wraps one
|
|
142
|
+
* into the thin caller `holocron setup` / `holocron sync` write locally.
|
|
143
|
+
*/
|
|
144
|
+
declare const WORKFLOW_TEMPLATES: Record<string, string>;
|
|
145
|
+
declare const KNOWN_WORKFLOWS: Set<string>;
|
|
146
|
+
/**
|
|
147
|
+
* GitHub check context name each CI workflow produces on a PR.
|
|
148
|
+
*
|
|
149
|
+
* The format is "{caller-workflow-name} / {reusable-job-name}". The caller
|
|
150
|
+
* job's own `name:` field does NOT appear in the external check name — only
|
|
151
|
+
* the calling workflow's top-level `name:` and the inner reusable-workflow
|
|
152
|
+
* job name matter. Only workflows that gate merges are listed here.
|
|
153
|
+
*/
|
|
154
|
+
declare const WORKFLOW_CHECK_CONTEXTS: Partial<Record<string, string>>;
|
|
155
|
+
/**
|
|
156
|
+
* Generate the thin caller content for a workflow, optionally injecting or
|
|
157
|
+
* merging `with:` overrides into the jobs block.
|
|
158
|
+
*
|
|
159
|
+
* Two strategies are used depending on the template:
|
|
160
|
+
* - Templates that already have a `with:` block (e.g. lint):
|
|
161
|
+
* the override entries are merged in, replacing existing keys and appending
|
|
162
|
+
* new ones.
|
|
163
|
+
* - Templates that end with ` secrets: inherit`: a new `with:` block is
|
|
164
|
+
* injected immediately before `secrets: inherit`.
|
|
165
|
+
* If neither pattern matches the template, a warning is emitted and the
|
|
166
|
+
* base template is returned unchanged.
|
|
167
|
+
*/
|
|
168
|
+
declare function generateThinCallerContent(name: string, withOverrides?: Record<string, unknown>, additionalPaths?: string[],
|
|
169
|
+
/** Optional sink for the "could not inject `with:`" warning. */
|
|
170
|
+
logger?: {
|
|
171
|
+
warn(obj: Record<string, unknown>, msg: string): void;
|
|
172
|
+
}): string;
|
|
173
|
+
interface PreviewConfig {
|
|
174
|
+
/** Cloudflare Pages project name shared across all repos for previews. */
|
|
175
|
+
project: string;
|
|
176
|
+
/**
|
|
177
|
+
* Base domain for preview URLs (e.g. `"preview.theholocron.dev"`).
|
|
178
|
+
* When set, `holocron setup` automatically provisions:
|
|
179
|
+
* - Cloudflare Pages custom domain `*.<domain>` on the project
|
|
180
|
+
* - DNS wildcard CNAME `*.<domain>` → `<project>.pages.dev`
|
|
181
|
+
*
|
|
182
|
+
* Preview URLs resolve as `<repo>-pr-<n>.<domain>`.
|
|
183
|
+
*/
|
|
184
|
+
domain?: string;
|
|
185
|
+
}
|
|
186
|
+
/** Org-level context used to derive preview defaults from `preview: true`. */
|
|
187
|
+
interface OrgContext {
|
|
188
|
+
/** GitHub org name — becomes the prefix of the default project: `<org>-preview`. */
|
|
189
|
+
org?: string;
|
|
190
|
+
/** Org canonical domain — becomes `preview.<domain>` for the default preview domain. */
|
|
191
|
+
domain?: string;
|
|
192
|
+
/** Project name — injected as the `name` input to the deploy reusable when `docs: true`. */
|
|
193
|
+
repoName?: string;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Extract the Cloudflare Pages preview config from a deploy workflow's `with:` object.
|
|
197
|
+
*
|
|
198
|
+
* Accepts three forms:
|
|
199
|
+
* - `preview: true` — derive both project and domain from org context
|
|
200
|
+
* - `preview: { project: "..." }` — explicit project; domain derived from context if omitted
|
|
201
|
+
* - `preview: { project: "...", domain: "..." }` — fully explicit
|
|
202
|
+
*
|
|
203
|
+
* Returns null when `preview:` is absent, false, or can't be resolved.
|
|
204
|
+
*/
|
|
205
|
+
declare function extractPreviewConfig(raw: Record<string, unknown>, ctx?: OrgContext): PreviewConfig | null;
|
|
206
|
+
/**
|
|
207
|
+
* Generate the full thin-caller YAML for a `deploy.yml` that handles both
|
|
208
|
+
* production (push to main → GitHub Pages) and preview (pull_request →
|
|
209
|
+
* Cloudflare Pages) in a single file.
|
|
210
|
+
*
|
|
211
|
+
* Both jobs receive the same docs/storybook `with:` inputs. If the per-repo
|
|
212
|
+
* config supplies `cloudflare-project` it is forwarded; otherwise the reusable
|
|
213
|
+
* falls back to the `CLOUDFLARE_PAGES_PROJECT` org variable — set that once and
|
|
214
|
+
* all repos with a `deploy` workflow get previews without per-repo config.
|
|
215
|
+
*/
|
|
216
|
+
declare function generateCombinedDeployContent(deployWith: Record<string, unknown>, paths: string[], preview: Pick<PreviewConfig, "project">): string;
|
|
217
|
+
/**
|
|
218
|
+
* Expand structured with-values to flat GitHub Actions inputs before
|
|
219
|
+
* generating the thin caller. Handles:
|
|
220
|
+
* - deploy shorthand: docs/storybook → type + storybook-projects
|
|
221
|
+
* - preview: stripped (handled separately via extractPreviewConfig)
|
|
222
|
+
* - run-chromatic object → run-chromatic: true + chromatic-projects
|
|
223
|
+
* - plain arrays → JSON-stringified for YAML scalar quoting
|
|
224
|
+
*
|
|
225
|
+
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
226
|
+
*/
|
|
227
|
+
declare function normalizeWorkflowWith(raw: Record<string, unknown>): Record<string, unknown>;
|
|
228
|
+
/**
|
|
229
|
+
* Derive on.push.paths entries from the deploy with: shorthand.
|
|
230
|
+
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
231
|
+
*/
|
|
232
|
+
declare function deriveDeployPaths(raw: Record<string, unknown>): string[];
|
|
233
|
+
//#endregion
|
|
234
|
+
export { type Astromech, type AstromechOptions, type ExecFn, KNOWN_TASKS, KNOWN_WORKFLOWS, type LocalRunner, type OrgContext, type PreviewConfig, type RunLogger, type RunOptions, type RunTaskInput, type RunTaskReport, TASKS, type TaskDef, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, normalizeWorkflowWith, runTask };
|
package/dist/index.mjs
CHANGED
|
@@ -234,4 +234,260 @@ function createAstromech(options) {
|
|
|
234
234
|
}) };
|
|
235
235
|
}
|
|
236
236
|
//#endregion
|
|
237
|
-
|
|
237
|
+
//#region src/thin-callers.ts
|
|
238
|
+
/**
|
|
239
|
+
* Workflow templates + thin-caller generation.
|
|
240
|
+
*
|
|
241
|
+
* `WORKFLOW_TEMPLATES` holds each reusable `.github/workflows/<name>.yml`
|
|
242
|
+
* (synced to `theholocron/.github`); `generateThinCallerContent` wraps one
|
|
243
|
+
* into the thin caller `holocron setup` / `holocron sync` write locally.
|
|
244
|
+
*/
|
|
245
|
+
const WORKFLOW_TEMPLATES = {
|
|
246
|
+
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",
|
|
247
|
+
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",
|
|
248
|
+
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",
|
|
249
|
+
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",
|
|
250
|
+
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",
|
|
251
|
+
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",
|
|
252
|
+
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",
|
|
253
|
+
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",
|
|
254
|
+
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",
|
|
255
|
+
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",
|
|
256
|
+
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",
|
|
257
|
+
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",
|
|
258
|
+
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",
|
|
259
|
+
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",
|
|
260
|
+
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"
|
|
261
|
+
};
|
|
262
|
+
const KNOWN_WORKFLOWS = new Set(Object.keys(WORKFLOW_TEMPLATES));
|
|
263
|
+
/**
|
|
264
|
+
* GitHub check context name each CI workflow produces on a PR.
|
|
265
|
+
*
|
|
266
|
+
* The format is "{caller-workflow-name} / {reusable-job-name}". The caller
|
|
267
|
+
* job's own `name:` field does NOT appear in the external check name — only
|
|
268
|
+
* the calling workflow's top-level `name:` and the inner reusable-workflow
|
|
269
|
+
* job name matter. Only workflows that gate merges are listed here.
|
|
270
|
+
*/
|
|
271
|
+
const WORKFLOW_CHECK_CONTEXTS = {
|
|
272
|
+
lint: "Lint / Lint entire codebase",
|
|
273
|
+
test: "Test / Run tests and collect coverage",
|
|
274
|
+
typecheck: "Typecheck / tsc --noEmit"
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* Generate the thin caller content for a workflow, optionally injecting or
|
|
278
|
+
* merging `with:` overrides into the jobs block.
|
|
279
|
+
*
|
|
280
|
+
* Two strategies are used depending on the template:
|
|
281
|
+
* - Templates that already have a `with:` block (e.g. lint):
|
|
282
|
+
* the override entries are merged in, replacing existing keys and appending
|
|
283
|
+
* new ones.
|
|
284
|
+
* - Templates that end with ` secrets: inherit`: a new `with:` block is
|
|
285
|
+
* injected immediately before `secrets: inherit`.
|
|
286
|
+
* If neither pattern matches the template, a warning is emitted and the
|
|
287
|
+
* base template is returned unchanged.
|
|
288
|
+
*/
|
|
289
|
+
function generateThinCallerContent(name, withOverrides, additionalPaths, logger) {
|
|
290
|
+
const base = WORKFLOW_TEMPLATES[name];
|
|
291
|
+
if (!base) return "";
|
|
292
|
+
const yamlScalar = (v) => {
|
|
293
|
+
if (v === true) return "true";
|
|
294
|
+
if (v === false) return "false";
|
|
295
|
+
const s = String(v);
|
|
296
|
+
return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
|
|
297
|
+
};
|
|
298
|
+
const fmt = (k, v) => ` ${k}: ${yamlScalar(v)}`;
|
|
299
|
+
let result = base;
|
|
300
|
+
if (additionalPaths && additionalPaths.length > 0) {
|
|
301
|
+
const pathsBlockRe = /( {4}paths:\n)((?:[ ]{6}- [^\n]+\n)+)/;
|
|
302
|
+
if (pathsBlockRe.test(result)) result = result.replace(pathsBlockRe, (_, header, existing) => {
|
|
303
|
+
const existingPaths = new Set([...existing.matchAll(/- (.+)/g)].map((m) => m[1]));
|
|
304
|
+
const newEntries = additionalPaths.filter((p) => !existingPaths.has(p)).map((p) => ` - ${p}\n`).join("");
|
|
305
|
+
return header + existing + newEntries;
|
|
306
|
+
});
|
|
307
|
+
else {
|
|
308
|
+
const pathsBlock = ` paths:\n${additionalPaths.map((p) => ` - ${p}\n`).join("")}`;
|
|
309
|
+
result = result.replace(/( {4}branches: \[main\]\n)/, `$1${pathsBlock}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (!withOverrides || Object.keys(withOverrides).length === 0) return result;
|
|
313
|
+
const withBlockRe = /( {4}with:\n)((?:[ ]{6}[^\n]+\n)*)/;
|
|
314
|
+
const existingMatch = result.match(withBlockRe);
|
|
315
|
+
if (existingMatch) {
|
|
316
|
+
const existingEntries = new Map(existingMatch[2].split("\n").filter(Boolean).map((line) => {
|
|
317
|
+
const m = line.match(/^ {6}([^:]+):\s*(.*)/);
|
|
318
|
+
return m ? [m[1].trim(), m[2].trim()] : null;
|
|
319
|
+
}).filter((e) => e !== null));
|
|
320
|
+
for (const [k, v] of Object.entries(withOverrides)) existingEntries.set(k, yamlScalar(v));
|
|
321
|
+
const merged = [...existingEntries.entries()].map(([k, v]) => ` ${k}: ${v}`).join("\n");
|
|
322
|
+
return result.replace(withBlockRe, ` with:\n${merged}\n`);
|
|
323
|
+
}
|
|
324
|
+
const withBlock = Object.entries(withOverrides).map(([k, v]) => fmt(k, v)).join("\n");
|
|
325
|
+
const injected = result.replace(/ {4}secrets: inherit\n$/, ` with:\n${withBlock}\n secrets: inherit\n`);
|
|
326
|
+
if (injected === result) logger?.warn({ template: name }, "generateThinCallerContent: could not inject `with:` overrides");
|
|
327
|
+
return injected;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Extract the Cloudflare Pages preview config from a deploy workflow's `with:` object.
|
|
331
|
+
*
|
|
332
|
+
* Accepts three forms:
|
|
333
|
+
* - `preview: true` — derive both project and domain from org context
|
|
334
|
+
* - `preview: { project: "..." }` — explicit project; domain derived from context if omitted
|
|
335
|
+
* - `preview: { project: "...", domain: "..." }` — fully explicit
|
|
336
|
+
*
|
|
337
|
+
* Returns null when `preview:` is absent, false, or can't be resolved.
|
|
338
|
+
*/
|
|
339
|
+
function extractPreviewConfig(raw, ctx = {}) {
|
|
340
|
+
const preview = raw["preview"];
|
|
341
|
+
if (!preview) return null;
|
|
342
|
+
if (preview === true) {
|
|
343
|
+
const project = ctx.org ? `${ctx.org}-preview` : null;
|
|
344
|
+
const domain = ctx.domain ? `preview.${ctx.domain}` : void 0;
|
|
345
|
+
if (!project) return null;
|
|
346
|
+
return {
|
|
347
|
+
project,
|
|
348
|
+
...domain ? { domain } : {}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (typeof preview !== "object") return null;
|
|
352
|
+
const p = preview;
|
|
353
|
+
const project = typeof p["project"] === "string" && p["project"] ? p["project"] : ctx.org ? `${ctx.org}-preview` : null;
|
|
354
|
+
if (!project) return null;
|
|
355
|
+
const domain = typeof p["domain"] === "string" && p["domain"] ? p["domain"] : ctx.domain ? `preview.${ctx.domain}` : void 0;
|
|
356
|
+
return {
|
|
357
|
+
project,
|
|
358
|
+
...domain ? { domain } : {}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Generate the full thin-caller YAML for a `deploy.yml` that handles both
|
|
363
|
+
* production (push to main → GitHub Pages) and preview (pull_request →
|
|
364
|
+
* Cloudflare Pages) in a single file.
|
|
365
|
+
*
|
|
366
|
+
* Both jobs receive the same docs/storybook `with:` inputs. If the per-repo
|
|
367
|
+
* config supplies `cloudflare-project` it is forwarded; otherwise the reusable
|
|
368
|
+
* falls back to the `CLOUDFLARE_PAGES_PROJECT` org variable — set that once and
|
|
369
|
+
* all repos with a `deploy` workflow get previews without per-repo config.
|
|
370
|
+
*/
|
|
371
|
+
function generateCombinedDeployContent(deployWith, paths, preview) {
|
|
372
|
+
const yamlScalar = (v) => {
|
|
373
|
+
if (v === true) return "true";
|
|
374
|
+
if (v === false) return "false";
|
|
375
|
+
const s = String(v);
|
|
376
|
+
return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
|
|
377
|
+
};
|
|
378
|
+
const withLines = (entries) => Object.entries(entries).map(([k, v]) => ` ${k}: ${yamlScalar(v)}`).join("\n");
|
|
379
|
+
const pathsBlock = paths.length > 0 ? ` paths:\n${paths.map((p) => ` - ${p}\n`).join("")}` : "";
|
|
380
|
+
const previewWith = {
|
|
381
|
+
...deployWith,
|
|
382
|
+
"cloudflare-project": preview.project
|
|
383
|
+
};
|
|
384
|
+
const deployWithBlock = Object.keys(deployWith).length > 0 ? ` with:\n${withLines(deployWith)}\n` : "";
|
|
385
|
+
const previewWithBlock = ` with:\n${withLines(previewWith)}\n`;
|
|
386
|
+
return [
|
|
387
|
+
`name: Deploy`,
|
|
388
|
+
``,
|
|
389
|
+
`on: # yamllint disable-line rule:truthy`,
|
|
390
|
+
` push:`,
|
|
391
|
+
` branches: [main]`,
|
|
392
|
+
...pathsBlock ? [`${pathsBlock}`] : [],
|
|
393
|
+
` pull_request:`,
|
|
394
|
+
` branches: [main]`,
|
|
395
|
+
` types: [opened, synchronize, reopened, closed]`,
|
|
396
|
+
...pathsBlock ? [`${pathsBlock}`] : [],
|
|
397
|
+
` workflow_dispatch:`,
|
|
398
|
+
``,
|
|
399
|
+
`concurrency:`,
|
|
400
|
+
` group: $\{{ github.event_name == 'pull_request' && format('preview-{0}', github.event.pull_request.number) || 'pages' }}`,
|
|
401
|
+
` cancel-in-progress: $\{{ github.event_name == 'pull_request' && github.event.action != 'closed' }}`,
|
|
402
|
+
``,
|
|
403
|
+
`permissions:`,
|
|
404
|
+
` contents: read`,
|
|
405
|
+
` deployments: write`,
|
|
406
|
+
` pages: write`,
|
|
407
|
+
` id-token: write`,
|
|
408
|
+
` pull-requests: write`,
|
|
409
|
+
``,
|
|
410
|
+
`jobs:`,
|
|
411
|
+
` deploy:`,
|
|
412
|
+
` name: Deploy`,
|
|
413
|
+
` if: \${{ github.event_name != 'pull_request' }}`,
|
|
414
|
+
` uses: theholocron/.github/.github/workflows/deploy.yml@main`,
|
|
415
|
+
...deployWithBlock ? [deployWithBlock.trimEnd()] : [],
|
|
416
|
+
` secrets: inherit`,
|
|
417
|
+
``,
|
|
418
|
+
` preview:`,
|
|
419
|
+
` name: Preview`,
|
|
420
|
+
` if: \${{ github.event_name == 'pull_request' }}`,
|
|
421
|
+
` uses: theholocron/.github/.github/workflows/preview.yml@main`,
|
|
422
|
+
previewWithBlock.trimEnd(),
|
|
423
|
+
` secrets: inherit`,
|
|
424
|
+
``
|
|
425
|
+
].join("\n");
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Expand structured with-values to flat GitHub Actions inputs before
|
|
429
|
+
* generating the thin caller. Handles:
|
|
430
|
+
* - deploy shorthand: docs/storybook → type + storybook-projects
|
|
431
|
+
* - preview: stripped (handled separately via extractPreviewConfig)
|
|
432
|
+
* - run-chromatic object → run-chromatic: true + chromatic-projects
|
|
433
|
+
* - plain arrays → JSON-stringified for YAML scalar quoting
|
|
434
|
+
*
|
|
435
|
+
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
436
|
+
*/
|
|
437
|
+
function normalizeWorkflowWith(raw) {
|
|
438
|
+
const result = { ...raw };
|
|
439
|
+
delete result["preview"];
|
|
440
|
+
const hasDocs = raw["docs"] === true || raw["docs"] !== null && typeof raw["docs"] === "object";
|
|
441
|
+
const storybookProjects = raw["storybook"];
|
|
442
|
+
if (hasDocs) {
|
|
443
|
+
result["type"] = "docs";
|
|
444
|
+
delete result["docs"];
|
|
445
|
+
}
|
|
446
|
+
if (Array.isArray(storybookProjects)) {
|
|
447
|
+
if (!hasDocs) result["type"] = "storybook";
|
|
448
|
+
result["storybook-projects"] = JSON.stringify(storybookProjects.map(({ name, path = "." }) => ({
|
|
449
|
+
name,
|
|
450
|
+
workingDir: path
|
|
451
|
+
})));
|
|
452
|
+
delete result["storybook"];
|
|
453
|
+
}
|
|
454
|
+
const runChromatic = raw["run-chromatic"];
|
|
455
|
+
if (runChromatic !== null && typeof runChromatic === "object" && "projects" in runChromatic) {
|
|
456
|
+
result["run-chromatic"] = true;
|
|
457
|
+
const projects = runChromatic.projects.map((p) => ({
|
|
458
|
+
...p,
|
|
459
|
+
...Array.isArray(p.untraced) ? { untraced: p.untraced.join("\n") } : {}
|
|
460
|
+
}));
|
|
461
|
+
result["chromatic-projects"] = JSON.stringify(projects);
|
|
462
|
+
}
|
|
463
|
+
for (const [k, v] of Object.entries(result)) if (Array.isArray(v)) result[k] = JSON.stringify(v);
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Derive on.push.paths entries from the deploy with: shorthand.
|
|
468
|
+
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
469
|
+
*/
|
|
470
|
+
function deriveDeployPaths(raw) {
|
|
471
|
+
const paths = [];
|
|
472
|
+
const docs = raw["docs"];
|
|
473
|
+
if (docs === true) {
|
|
474
|
+
paths.push("docs/**");
|
|
475
|
+
paths.push("astro.config.ts");
|
|
476
|
+
paths.push("pnpm-workspace.yaml");
|
|
477
|
+
paths.push("pnpm-lock.yaml");
|
|
478
|
+
} else if (docs !== null && typeof docs === "object" && "path" in docs) {
|
|
479
|
+
const p = docs.path;
|
|
480
|
+
if (p && p !== ".") paths.push(`${p}/**`);
|
|
481
|
+
}
|
|
482
|
+
const storybookProjects = raw["storybook"];
|
|
483
|
+
if (Array.isArray(storybookProjects)) for (const s of storybookProjects) {
|
|
484
|
+
const p = s.path || ".";
|
|
485
|
+
if (p === ".") {
|
|
486
|
+
paths.push("src/**");
|
|
487
|
+
paths.push(".storybook/**");
|
|
488
|
+
} else paths.push(`${p}/**`);
|
|
489
|
+
}
|
|
490
|
+
return paths;
|
|
491
|
+
}
|
|
492
|
+
//#endregion
|
|
493
|
+
export { KNOWN_TASKS, KNOWN_WORKFLOWS, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, normalizeWorkflowWith, runTask };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/astromech",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.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.0
|
|
40
|
+
"@theholocron/datapad": "4.1.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@theholocron/eslint-config": "^8.0.0",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"globals": "^17.11.0",
|
|
53
53
|
"tsdown": "^0.22.14",
|
|
54
54
|
"typescript": "^5.9.3",
|
|
55
|
-
"vitest": "^4.1.11"
|
|
55
|
+
"vitest": "^4.1.11",
|
|
56
|
+
"@theholocron/rollup-plugin-transform-template": "4.1.0"
|
|
56
57
|
},
|
|
57
58
|
"engines": {
|
|
58
59
|
"node": ">=22"
|