@theholocron/astromech 5.0.0-alpha.129 → 5.0.0-alpha.133
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 +4 -4
- package/dist/config/index.d.mts +1 -1
- package/dist/index.d.mts +68 -23
- package/dist/index.mjs +146 -45
- package/dist/{schema-CGybbV7_.d.mts → schema-BDld3xq1.d.mts} +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ documents, not a naming convention repos need to follow.
|
|
|
69
69
|
| `delivery.publish` | semantic-release | no local equivalent — CI only; carries `preview` (npm dist-tags) |
|
|
70
70
|
| `delivery.deploy` | Cloudflare Pages / Vercel | no local equivalent — CI only; carries `preview` |
|
|
71
71
|
| `delivery.bundleSize` | bundle-size upload to Codecov | no local equivalent — CI only |
|
|
72
|
-
| `platform.repoSync` | `holocron sync`
|
|
72
|
+
| `platform.repoSync` | `holocron repo sync` | keeps generated files current |
|
|
73
73
|
| `platform.commitStandards` | commitlint | no local equivalent — enforced by the `commit-msg` hook locally, over the PR's commit range in CI |
|
|
74
74
|
| `platform.repoValidation` | `scripts/validate-adrs.mjs`, `scripts/validate-registry.mjs`, `scripts/validate-docs-presence.mjs` | a job-bearing task — spec/ADR frontmatter, registry-doc completeness, and docs-presence, not linters |
|
|
75
75
|
| `knowledge.wiki` | Fern | publishes `docs/wiki/*.md` — not a sync, a publish; carries `preview` |
|
|
@@ -174,7 +174,7 @@ de-duplicated. Most tasks are single always-run jobs now, so their context
|
|
|
174
174
|
names that job directly (`"Typecheck / tsc --noEmit"`) — the `… /
|
|
175
175
|
Conclusion` fan-in aggregate is only used where a task genuinely has
|
|
176
176
|
several conditionally-run jobs feeding one check
|
|
177
|
-
(`verification.unitTests`, `platform.repoValidation`). `holocron setup`
|
|
177
|
+
(`verification.unitTests`, `platform.repoValidation`). `holocron repo setup`
|
|
178
178
|
prepends `"DCO"` and applies the list for `protection: "strict"` repos.
|
|
179
179
|
Policy-free — manifest only.
|
|
180
180
|
|
|
@@ -188,7 +188,7 @@ came from), same manifest-derived category as `thinCallers()`. Pass the
|
|
|
188
188
|
current file's content (or `null`) and it either merges the
|
|
189
189
|
`individual_components` list in (preserving everything else in the file —
|
|
190
190
|
thresholds, flags, custom rules) or scaffolds a fresh file from the base
|
|
191
|
-
template. `holocron setup` writes the result via the `source` capability;
|
|
191
|
+
template. `holocron repo setup` writes the result via the `source` capability;
|
|
192
192
|
this method never touches the filesystem beyond reading `packages/*` and
|
|
193
193
|
`apps/*` under `cwd`.
|
|
194
194
|
|
|
@@ -201,7 +201,7 @@ Deploys the workspace packages whose manifest declares it (holocron#930):
|
|
|
201
201
|
tasks: [{ name: "delivery.deploy", with: { on: "release", channel: "alpha" } }];
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
-
`holocron deploy
|
|
204
|
+
`holocron deploy on-release --channel=<alpha|main> --from=<previous release commit> --to=<release commit>`
|
|
205
205
|
(or `deployOnRelease()` from `@theholocron/astromech`) deploys each such package
|
|
206
206
|
when the release is on its `channel` (default `main`, the stable release;
|
|
207
207
|
set `alpha` for prereleases) and changes the package or one of its `workspace:*` dependencies
|
package/dist/config/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskConfigItem } from "../schema-
|
|
1
|
+
import { i as normalizeTaskEntry, n as TaskEntry, r as TasksConfig, t as TaskConfigItem } from "../schema-BDld3xq1.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,11 +1,11 @@
|
|
|
1
|
-
import { r as TasksConfig } from "./schema-
|
|
1
|
+
import { r as TasksConfig } from "./schema-BDld3xq1.mjs";
|
|
2
2
|
//#region src/generators/github-action-thin-callers.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Workflow templates + thin-caller generation.
|
|
5
5
|
*
|
|
6
6
|
* `WORKFLOW_TEMPLATES` holds each reusable `.github/workflows/<name>.yml`
|
|
7
7
|
* (synced to `theholocron/.github`); `generateThinCallerContent` wraps one
|
|
8
|
-
* into the thin caller `holocron setup` / `holocron sync` write locally.
|
|
8
|
+
* into the thin caller `holocron repo setup` / `holocron repo sync` write locally.
|
|
9
9
|
*/
|
|
10
10
|
declare const WORKFLOW_TEMPLATES: Record<string, string>;
|
|
11
11
|
declare const KNOWN_WORKFLOWS: Set<string>;
|
|
@@ -55,7 +55,7 @@ interface PreviewConfig {
|
|
|
55
55
|
project: string;
|
|
56
56
|
/**
|
|
57
57
|
* Base domain for preview URLs (e.g. `"preview.theholocron.dev"`).
|
|
58
|
-
* When set, `holocron setup` automatically provisions:
|
|
58
|
+
* When set, `holocron repo setup` automatically provisions:
|
|
59
59
|
* - Cloudflare Pages custom domain `*.<domain>` on the project
|
|
60
60
|
* - DNS wildcard CNAME `*.<domain>` → `<project>.pages.dev`
|
|
61
61
|
*
|
|
@@ -102,12 +102,12 @@ declare function generateCombinedDeployContent(deployWith: Record<string, unknow
|
|
|
102
102
|
* - run-chromatic object → run-chromatic: true + chromatic-projects
|
|
103
103
|
* - plain arrays → JSON-stringified for YAML scalar quoting
|
|
104
104
|
*
|
|
105
|
-
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
105
|
+
* Used by both `holocron repo setup` and `sync-workflow-templates`.
|
|
106
106
|
*/
|
|
107
107
|
declare function normalizeWorkflowWith(raw: Record<string, unknown>): Record<string, unknown>;
|
|
108
108
|
/**
|
|
109
109
|
* Derive on.push.paths entries from the deploy with: shorthand.
|
|
110
|
-
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
110
|
+
* Used by both `holocron repo setup` and `sync-workflow-templates`.
|
|
111
111
|
*/
|
|
112
112
|
declare function deriveDeployPaths(raw: Record<string, unknown>): string[];
|
|
113
113
|
//#endregion
|
|
@@ -231,6 +231,34 @@ interface CiInput extends RunDeps, CiOptions {
|
|
|
231
231
|
}
|
|
232
232
|
declare function runCi(input: CiInput): CiReport;
|
|
233
233
|
//#endregion
|
|
234
|
+
//#region src/tasks/group.d.ts
|
|
235
|
+
interface GroupOptions {
|
|
236
|
+
/** Print the resolved commands without running anything. */
|
|
237
|
+
dryRun?: boolean;
|
|
238
|
+
/** `turbo --filter=<pkg>` passthrough (monorepo). */
|
|
239
|
+
filter?: string;
|
|
240
|
+
}
|
|
241
|
+
interface GroupJobReport {
|
|
242
|
+
task: string;
|
|
243
|
+
status: "ok" | "fail" | "skip" | "dry-run";
|
|
244
|
+
command?: string;
|
|
245
|
+
message?: string;
|
|
246
|
+
}
|
|
247
|
+
interface GroupReport {
|
|
248
|
+
/** `"unknown"` when the namespace matches zero configured tasks. */
|
|
249
|
+
status: "ok" | "fail" | "dry-run" | "unknown";
|
|
250
|
+
jobs: GroupJobReport[];
|
|
251
|
+
}
|
|
252
|
+
interface GroupInput extends RunDeps, GroupOptions {
|
|
253
|
+
cwd: string;
|
|
254
|
+
config: TasksConfig;
|
|
255
|
+
/** The bare namespace, e.g. `"sourceQuality"` — matched against `"sourceQuality."`-prefixed task names. */
|
|
256
|
+
namespace: string;
|
|
257
|
+
}
|
|
258
|
+
/** Every configured task whose name starts with `"<namespace>."`, in manifest declaration order. */
|
|
259
|
+
declare function matchNamespace(config: TasksConfig, namespace: string): string[];
|
|
260
|
+
declare function runGroup(input: GroupInput): GroupReport;
|
|
261
|
+
//#endregion
|
|
234
262
|
//#region src/astromech.d.ts
|
|
235
263
|
interface AstromechOptions {
|
|
236
264
|
/** Repo root. */
|
|
@@ -276,8 +304,15 @@ interface RunOptions {
|
|
|
276
304
|
filter?: string;
|
|
277
305
|
}
|
|
278
306
|
interface Astromech {
|
|
279
|
-
/**
|
|
280
|
-
|
|
307
|
+
/**
|
|
308
|
+
* Run one task — or one of its sub-jobs (`opts.job`) — locally. When
|
|
309
|
+
* `task` isn't an exact registry task but matches the prefix of one or
|
|
310
|
+
* more configured tasks (`"sourceQuality"` against `sourceQuality.*`),
|
|
311
|
+
* runs every matching task in sequence instead ({@link GroupReport}) —
|
|
312
|
+
* `opts.job` is ignored for a namespace match, since a job belongs to
|
|
313
|
+
* one task, not a namespace.
|
|
314
|
+
*/
|
|
315
|
+
run(task: string, opts?: RunOptions): RunTaskReport | GroupReport;
|
|
281
316
|
/**
|
|
282
317
|
* Run the merge-gating checks locally, in CI order — "will CI pass?".
|
|
283
318
|
* Default scope: `required: true` tasks (falls back to every `ci: true`
|
|
@@ -292,7 +327,7 @@ interface Astromech {
|
|
|
292
327
|
*/
|
|
293
328
|
thinCallers(): Map<string, string>;
|
|
294
329
|
/**
|
|
295
|
-
* The complete reusable-workflow batch `holocron sync
|
|
330
|
+
* The complete reusable-workflow batch `holocron sync github` pushes to
|
|
296
331
|
* `theholocron/.github` — repo-relative path → content, "do not edit" header
|
|
297
332
|
* already applied to the YAML. Config-independent (the same for every repo).
|
|
298
333
|
*/
|
|
@@ -305,10 +340,19 @@ interface Astromech {
|
|
|
305
340
|
* when there is no config or `syncScripts: false`.
|
|
306
341
|
*/
|
|
307
342
|
packageScripts(): Record<string, string>;
|
|
343
|
+
/**
|
|
344
|
+
* Task names this repo's manifest can actually run locally — the same
|
|
345
|
+
* filter {@link packageScripts} applies (skips `local: false` and tasks
|
|
346
|
+
* with no local runner at all), but independent of `syncScripts`: a repo
|
|
347
|
+
* that opts out of generated `package.json` scripts still has real
|
|
348
|
+
* runnable tasks. Empty when there is no config. Backs `holocron`'s own
|
|
349
|
+
* interactive task picker (`run` with no task given).
|
|
350
|
+
*/
|
|
351
|
+
runnableTasks(): string[];
|
|
308
352
|
/**
|
|
309
353
|
* The branch-protection required-status-check contexts for this repo —
|
|
310
354
|
* every `required: true` task's check context plus `extraRequiredChecks`,
|
|
311
|
-
* ordered and de-duplicated. `holocron setup` prepends `"DCO"` and applies
|
|
355
|
+
* ordered and de-duplicated. `holocron repo setup` prepends `"DCO"` and applies
|
|
312
356
|
* the list; this method is policy-free (manifest only).
|
|
313
357
|
*/
|
|
314
358
|
requiredChecks(): string[];
|
|
@@ -316,7 +360,7 @@ interface Astromech {
|
|
|
316
360
|
* This repo's `codecov.yml` — component `paths` derived from `packages/*`.
|
|
317
361
|
* Pass the current file's content (`null` if none exists yet) to either
|
|
318
362
|
* merge the component list in or scaffold a fresh file. Policy-free same
|
|
319
|
-
* as {@link requiredChecks} — `holocron setup` owns writing the result.
|
|
363
|
+
* as {@link requiredChecks} — `holocron repo setup` owns writing the result.
|
|
320
364
|
*/
|
|
321
365
|
codecovConfig(existing: string | null): string;
|
|
322
366
|
/**
|
|
@@ -359,14 +403,14 @@ declare function createAstromech(options: AstromechOptions): Astromech;
|
|
|
359
403
|
* come from workspace packages; the status targets track the `test` task's
|
|
360
404
|
* coverage setup), the same category as the reusable `test.yml` this package
|
|
361
405
|
* also generates. Moved out of `@theholocron/cli` (theholocron/holocron#650)
|
|
362
|
-
* — `holocron setup`'s codecov step now calls {@link codecovConfig} instead
|
|
406
|
+
* — `holocron repo setup`'s codecov step now calls {@link codecovConfig} instead
|
|
363
407
|
* of importing this logic directly.
|
|
364
408
|
*/
|
|
365
409
|
interface WorkspacePackage {
|
|
366
410
|
slug: string;
|
|
367
411
|
name: string;
|
|
368
412
|
/** Which workspace root the package was discovered under. Defaults to `"packages"`. */
|
|
369
|
-
dir?: "packages" | "apps";
|
|
413
|
+
dir?: "packages" | "apps" | "plugins";
|
|
370
414
|
}
|
|
371
415
|
declare function codecovComponentBlock(packages: WorkspacePackage[]): string;
|
|
372
416
|
/**
|
|
@@ -382,17 +426,18 @@ declare function codecovComponentBlock(packages: WorkspacePackage[]): string;
|
|
|
382
426
|
declare function ensureIfNotFound(yaml: string): string;
|
|
383
427
|
declare function mergeCodecovComponents(existing: string, packages: WorkspacePackage[]): string;
|
|
384
428
|
/**
|
|
385
|
-
* Every public `packages
|
|
386
|
-
* when
|
|
387
|
-
* ship user-facing code under `apps/` alongside library
|
|
388
|
-
* `packages
|
|
389
|
-
*
|
|
429
|
+
* Every public `packages/*`, `apps/*`, and `plugins/*` workspace, sorted by
|
|
430
|
+
* slug. `[]` when none of the three directories exist. Monorepo templates
|
|
431
|
+
* (Next.js, React, …) ship user-facing code under `apps/` alongside library
|
|
432
|
+
* code under `packages/`; `holocron` itself splits vendor plugin packages
|
|
433
|
+
* into their own `plugins/*` root — every recognized root needs a codecov
|
|
434
|
+
* component so `codecov/patch/<slug>` actually posts for each.
|
|
390
435
|
*/
|
|
391
436
|
declare function readWorkspacePackages(repoRoot: string): WorkspacePackage[];
|
|
392
437
|
/** A brand-new `codecov.yml` — the scaffold header + the base template + this repo's component list. */
|
|
393
438
|
declare function createCodecovConfig(packages: WorkspacePackage[]): string;
|
|
394
439
|
/**
|
|
395
|
-
* The full pipeline `holocron setup` needs: read this repo's public
|
|
440
|
+
* The full pipeline `holocron repo setup` needs: read this repo's public
|
|
396
441
|
* `packages/*`, then either merge them into an `existing` `codecov.yml` or
|
|
397
442
|
* scaffold a new one.
|
|
398
443
|
*/
|
|
@@ -401,7 +446,7 @@ declare function codecovConfig(repoRoot: string, existing: string | null): strin
|
|
|
401
446
|
//#region src/generators/github-action-reusable-workflows.d.ts
|
|
402
447
|
/**
|
|
403
448
|
* The **reusable** GitHub Actions surface pushed to `theholocron/.github` by
|
|
404
|
-
* `holocron sync
|
|
449
|
+
* `holocron sync github` — the `workflow_call` implementations and composite
|
|
405
450
|
* actions that every repo's thin callers delegate to.
|
|
406
451
|
*
|
|
407
452
|
* `theholocron/.github` is a **pure sync target**: `.github/workflows/*`,
|
|
@@ -419,7 +464,7 @@ declare const REUSABLE_ACTIONS: Record<string, string>;
|
|
|
419
464
|
/** GitHub starter-workflow metadata → `workflow-templates/<name>.properties.json`. */
|
|
420
465
|
declare const WORKFLOW_TEMPLATE_PROPERTIES: Record<string, string>;
|
|
421
466
|
/**
|
|
422
|
-
* The complete file batch `holocron sync
|
|
467
|
+
* The complete file batch `holocron sync github` pushes to `theholocron/.github`:
|
|
423
468
|
* path → content. YAML gets the "do not edit" header; the starter-workflow
|
|
424
469
|
* `.properties.json` files are left bare (native JSON, not a commented file).
|
|
425
470
|
*/
|
|
@@ -463,7 +508,7 @@ declare function requiredChecks(config: TasksConfig): string[];
|
|
|
463
508
|
* listing which packages to build) is a structurally different, genuinely
|
|
464
509
|
* per-repo document — out of scope here, same as Bucket C content.
|
|
465
510
|
*
|
|
466
|
-
* Not yet wired into `holocron setup`'s per-package write loop (every
|
|
511
|
+
* Not yet wired into `holocron repo setup`'s per-package write loop (every
|
|
467
512
|
* other Bucket B file there is a single repo-root file, safely
|
|
468
513
|
* overwritten every run — `tsconfig.json` is per-package, and most
|
|
469
514
|
* packages' files still carry real hand-authored content today, not yet
|
|
@@ -515,7 +560,7 @@ declare function createTsconfig(options: TsconfigOptions): string;
|
|
|
515
560
|
* `sourceQuality.*`, `security.*`, `delivery.*`, `platform.*`,
|
|
516
561
|
* `knowledge.*`), not tool names — `eslint`/`vitest`/`tsdown`/… stay
|
|
517
562
|
* internal to this file and `theholocron/configs`. See
|
|
518
|
-
*
|
|
563
|
+
* `docs/wiki/specifications/tech-vocabulary-rename.spec.md` (#675) for the full mapping and
|
|
519
564
|
* the reasoning behind each namespace and decomposition.
|
|
520
565
|
*
|
|
521
566
|
* Spec: `docs/wiki/specifications/tech-astromech-task-runner.spec.md` (epic #581).
|
|
@@ -653,4 +698,4 @@ interface DeployOnReleaseReport {
|
|
|
653
698
|
*/
|
|
654
699
|
declare function deployOnRelease(options: DeployOnReleaseOptions, { run, readJson, listDirs, sleep, now, loadTasks }?: DeployOnReleaseDeps): Promise<DeployOnReleaseReport>;
|
|
655
700
|
//#endregion
|
|
656
|
-
export { type Astromech, type AstromechOptions, CI_ORDER, type CiJobReport, type CiOptions, type CiReport, type DeployOnReleaseOptions, type DeployOnReleaseReport, type DeployOnReleaseResult, type ExecFn, type JobDef, KNOWN_TASKS, KNOWN_WORKFLOWS, type LocalRunner, type OrgContext, type PreviewConfig, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, type RunLogger, type RunOptions, type RunTaskInput, type RunTaskReport, TASKS, type TaskDef, type TsconfigOptions, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, type WorkspacePackage, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, createTsconfig, deployOnRelease, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, reusableTemplates, runCi, runTask };
|
|
701
|
+
export { type Astromech, type AstromechOptions, CI_ORDER, type CiJobReport, type CiOptions, type CiReport, type DeployOnReleaseOptions, type DeployOnReleaseReport, type DeployOnReleaseResult, type ExecFn, type GroupJobReport, type GroupOptions, type GroupReport, type JobDef, KNOWN_TASKS, KNOWN_WORKFLOWS, type LocalRunner, type OrgContext, type PreviewConfig, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, type RunLogger, type RunOptions, type RunTaskInput, type RunTaskReport, TASKS, type TaskDef, type TsconfigOptions, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, type WorkspacePackage, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, createTsconfig, deployOnRelease, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, matchNamespace, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, reusableTemplates, runCi, runGroup, runTask };
|
package/dist/index.mjs
CHANGED
|
@@ -11,13 +11,13 @@ var codecov_default = "codecov:\n require_ci_to_pass: true\n\ncoverage:\n prec
|
|
|
11
11
|
* come from workspace packages; the status targets track the `test` task's
|
|
12
12
|
* coverage setup), the same category as the reusable `test.yml` this package
|
|
13
13
|
* also generates. Moved out of `@theholocron/cli` (theholocron/holocron#650)
|
|
14
|
-
* — `holocron setup`'s codecov step now calls {@link codecovConfig} instead
|
|
14
|
+
* — `holocron repo setup`'s codecov step now calls {@link codecovConfig} instead
|
|
15
15
|
* of importing this logic directly.
|
|
16
16
|
*/
|
|
17
17
|
const INDIVIDUAL_COMPONENTS_MARKER = " individual_components:";
|
|
18
18
|
function scaffoldHeader() {
|
|
19
19
|
return [
|
|
20
|
-
`# Scaffolded by holocron setup — edit this file freely.`,
|
|
20
|
+
`# Scaffolded by holocron repo setup — edit this file freely.`,
|
|
21
21
|
`# Source: theholocron/holocron · packages/astromech/src/generators/codecov.ts`,
|
|
22
22
|
``
|
|
23
23
|
].join("\n");
|
|
@@ -91,21 +91,26 @@ function readWorkspaceDir(repoRoot, dir) {
|
|
|
91
91
|
return packages;
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
-
* Every public `packages
|
|
95
|
-
* when
|
|
96
|
-
* ship user-facing code under `apps/` alongside library
|
|
97
|
-
* `packages
|
|
98
|
-
*
|
|
94
|
+
* Every public `packages/*`, `apps/*`, and `plugins/*` workspace, sorted by
|
|
95
|
+
* slug. `[]` when none of the three directories exist. Monorepo templates
|
|
96
|
+
* (Next.js, React, …) ship user-facing code under `apps/` alongside library
|
|
97
|
+
* code under `packages/`; `holocron` itself splits vendor plugin packages
|
|
98
|
+
* into their own `plugins/*` root — every recognized root needs a codecov
|
|
99
|
+
* component so `codecov/patch/<slug>` actually posts for each.
|
|
99
100
|
*/
|
|
100
101
|
function readWorkspacePackages(repoRoot) {
|
|
101
|
-
return [
|
|
102
|
+
return [
|
|
103
|
+
...readWorkspaceDir(repoRoot, "packages"),
|
|
104
|
+
...readWorkspaceDir(repoRoot, "apps"),
|
|
105
|
+
...readWorkspaceDir(repoRoot, "plugins")
|
|
106
|
+
].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
102
107
|
}
|
|
103
108
|
/** A brand-new `codecov.yml` — the scaffold header + the base template + this repo's component list. */
|
|
104
109
|
function createCodecovConfig(packages) {
|
|
105
110
|
return `${scaffoldHeader()}${codecov_default.trimEnd()}${codecovComponentBlock(packages)}`;
|
|
106
111
|
}
|
|
107
112
|
/**
|
|
108
|
-
* The full pipeline `holocron setup` needs: read this repo's public
|
|
113
|
+
* The full pipeline `holocron repo setup` needs: read this repo's public
|
|
109
114
|
* `packages/*`, then either merge them into an `existing` `codecov.yml` or
|
|
110
115
|
* scaffold a new one.
|
|
111
116
|
*/
|
|
@@ -118,7 +123,7 @@ function codecovConfig(repoRoot, existing) {
|
|
|
118
123
|
var auto_commit_default = "name: Auto Commit\ndescription: >\n Commit and push any file changes using stefanzweifel/git-auto-commit-action.\n If a branch name is provided and differs from the current branch, a new branch\n is created automatically.\n\ninputs:\n token:\n description: >\n GitHub token with push access to the target branch. Used to authenticate\n the git remote so the commit can be pushed.\n required: true\n branch:\n description: >\n Branch to commit to. Leave empty to commit to the current branch (useful\n in PR contexts). Provide an explicit name (e.g. chore/auto-sync) to push\n to a separate branch.\n required: false\n default: \"\"\n commit-message:\n description: Commit message.\n required: true\n commit-options:\n description: >\n Additional options passed to git commit (e.g. --no-verify).\n required: false\n default: \"\"\n commit-user-name:\n description: Git user.name for the commit.\n required: false\n default: \"github-actions[bot]\"\n commit-user-email:\n description: Git user.email for the commit.\n required: false\n default: \"41898282+github-actions[bot]@users.noreply.github.com\"\n commit-author:\n description: >\n Git author string in \"Name <email>\" format.\n required: false\n default: \"github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>\"\n\noutputs:\n changes-detected:\n description: \"'true' if any files were committed, 'false' otherwise.\"\n value: ${{ steps.commit.outputs.changes_detected }}\n\nruns:\n using: composite\n steps:\n - name: Configure git remote with token\n shell: bash\n run: |\n REMOTE_URL=$(git remote get-url origin)\n AUTHED_URL=$(echo \"$REMOTE_URL\" | sed \"s|https://|https://x-access-token:${TOKEN}@|\")\n git remote set-url origin \"$AUTHED_URL\"\n env:\n TOKEN: ${{ inputs.token }}\n\n - name: Reset branch to current HEAD\n if: ${{ inputs.branch != '' }}\n shell: bash\n run: git checkout -B \"$BRANCH\"\n env:\n BRANCH: ${{ inputs.branch }}\n\n - name: Build signed commit message\n id: msg\n shell: bash\n run: |\n TRAILER=\"Signed-off-by: ${COMMIT_USER_NAME} <${COMMIT_USER_EMAIL}>\"\n if echo \"$COMMIT_MESSAGE\" | grep -qF \"$TRAILER\"; then\n FINAL=\"$COMMIT_MESSAGE\"\n else\n FINAL=\"${COMMIT_MESSAGE}\"$'\\n\\n'\"${TRAILER}\"\n fi\n {\n echo \"value<<EOF_TRAILER\"\n printf '%s' \"$FINAL\"\n echo \"\"\n echo \"EOF_TRAILER\"\n } >> \"$GITHUB_OUTPUT\"\n env:\n COMMIT_MESSAGE: ${{ inputs.commit-message }}\n COMMIT_USER_NAME: ${{ inputs.commit-user-name }}\n COMMIT_USER_EMAIL: ${{ inputs.commit-user-email }}\n\n - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0\n id: commit\n with:\n branch: ${{ inputs.branch }}\n commit_message: ${{ steps.msg.outputs.value }}\n commit_options: ${{ inputs.commit-options }}\n commit_user_name: ${{ inputs.commit-user-name }}\n commit_user_email: ${{ inputs.commit-user-email }}\n commit_author: ${{ inputs.commit-author }}\n";
|
|
119
124
|
//#endregion
|
|
120
125
|
//#region src/templates/reusable/actions/holocron.yml
|
|
121
|
-
var holocron_default = "name: Holocron\ndescription: >\n Run the Holocron CLI — `holocron run <task> [job]` by default, or any other\n subcommand via `command:` (`sync`, `sync
|
|
126
|
+
var holocron_default = "name: Holocron\ndescription: >\n Run the Holocron CLI — `holocron run <task> [job]` by default, or any other\n subcommand via `command:` (`repo sync`, `sync github`, …). Resolves\n `@theholocron/cli` from `node_modules` (published tarball) or builds it from\n source when it resolves to an unbuilt workspace checkout (the holocron repo\n itself). Requires dependencies to be installed already (run the setup action\n first).\n\ninputs:\n command:\n description: >\n Holocron subcommand. Default `run` — then `task` (and optional `job`)\n name the manifest task. Set to `repo sync` / `sync github` / … to run that\n subcommand directly; `args` becomes everything after it.\n required: false\n default: run\n task:\n description: >\n Manifest task to run when command is run — typecheck, test, build, audit.\n required: false\n default: \"\"\n job:\n description: Optional sub-job within the task (e.g. knip, performance for audit).\n required: false\n default: \"\"\n args:\n description: >\n For `command: run`, extra arguments forwarded to the tool after `--`.\n For any other command, the arguments passed straight to it.\n required: false\n default: \"\"\n\noutputs:\n log-file:\n description: >\n Path to a file holding the full captured stdout+stderr of this\n invocation — the same text the live Actions log shows, saved for a\n caller that wants to ship it elsewhere (e.g. Sentinel's dispatched\n checks shipping it to Axiom, holocron#769/#794).\n value: ${{ steps.run.outputs.log-file }}\n\nruns:\n using: composite\n\n steps:\n - name: Build the workspace when the CLI resolves to an unbuilt checkout\n shell: bash\n # The holocron repo carries @theholocron/cli as `workspace:*`; its bin\n # (dist/cli.mjs) does not exist on a fresh checkout. `pnpm build` is a\n # turbo run — cached and cheap once warm. A no-op everywhere else, where\n # the published tarball already ships dist/.\n #\n # pnpm creates every workspace package's node_modules/.bin/holocron shim\n # once, during the setup action's earlier `pnpm install` — before this\n # build has run, so every one of them is silently skipped (\"Failed to\n # create bin ... ENOENT\"), not just @theholocron/cli's own. That's a\n # one-time decision: a later build doesn't retroactively fix a shim that\n # was never created. Re-running `pnpm install` here (fast — the lockfile\n # hasn't changed, so it only re-links `.bin`) is what actually fixes it,\n # confirmed against a real cold install: `holocron` stays unresolvable\n # from inside any package until this second install runs.\n run: |\n if [ ! -f node_modules/@theholocron/cli/dist/cli.mjs ]; then\n echo \"→ @theholocron/cli dist/ missing — building the workspace from source\" >&2\n pnpm build\n echo \"→ re-linking node_modules/.bin — pnpm skipped it pre-build\" >&2\n pnpm install --frozen-lockfile\n fi\n\n - name: holocron ${{ inputs.command }} ${{ inputs.task }}\n id: run\n shell: bash\n env:\n HOLOCRON_COMMAND: ${{ inputs.command }}\n HOLOCRON_TASK: ${{ inputs.task }}\n HOLOCRON_JOB: ${{ inputs.job }}\n HOLOCRON_ARGS: ${{ inputs.args }}\n run: |\n if [ \"$HOLOCRON_COMMAND\" = \"run\" ]; then\n set -- run \"$HOLOCRON_TASK\"\n [ -n \"$HOLOCRON_JOB\" ] && set -- \"$@\" \"$HOLOCRON_JOB\"\n # shellcheck disable=SC2086\n [ -n \"$HOLOCRON_ARGS\" ] && set -- \"$@\" -- $HOLOCRON_ARGS\n else\n # `command` may be a multi-word path (`repo sync`, `deploy on-release`):\n # word-split it so each token is its own argument.\n # shellcheck disable=SC2086\n set -- $HOLOCRON_COMMAND $HOLOCRON_ARGS\n fi\n # Invoke the built entry directly. `pnpm exec holocron` relies on a\n # node_modules/.bin/holocron shim that pnpm does not create for a\n # workspace package whose dist/ was absent at install time (the holocron\n # repo itself). Resolving the package's main export gives dist/index.mjs;\n # the bin sits next to it as dist/cli.mjs. This is `require.resolve` from\n # node_modules — it works in the holocron monorepo AND in every consumer\n # repo (theholocron/holocron#655), unlike a hard-coded `packages/cli/…`.\n cli=\"$(dirname \"$(node -e 'process.stdout.write(require.resolve(\"@theholocron/cli\"))')\")/cli.mjs\"\n if [ ! -f \"$cli\" ]; then\n echo \"::error::@theholocron/cli entry not found at $cli — is the workspace built?\" >&2\n exit 1\n fi\n echo \"→ $cli $*\" >&2\n # `tee` keeps the live Actions log streaming exactly as before while\n # also capturing it to a file callers can ship elsewhere (log-file\n # output, above). PIPESTATUS[0] -- not $? -- is `node`'s own exit\n # code; $? after a pipe is tee's, which succeeds even when node fails.\n log_file=\"${RUNNER_TEMP}/holocron-output.log\"\n node \"$cli\" \"$@\" 2>&1 | tee \"$log_file\"\n exit_code=${PIPESTATUS[0]}\n echo \"log-file=$log_file\" >> \"$GITHUB_OUTPUT\"\n exit \"$exit_code\"\n";
|
|
122
127
|
//#endregion
|
|
123
128
|
//#region src/templates/reusable/actions/install.yml
|
|
124
129
|
var install_default = "name: Install dependencies\ndescription: Install project dependencies with pnpm frozen lockfile.\n\nruns:\n using: composite\n\n steps:\n - name: Install dependencies\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n shell: bash\n run: pnpm install --frozen-lockfile\n";
|
|
@@ -142,7 +147,7 @@ var delivery_bundleSize_default$1 = "name: Audit the Bundle Size\n\non: # yamlli
|
|
|
142
147
|
var delivery_deploy_default$1 = "name: Deploy\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n type:\n description: \"Type of deployment: docs or storybook\"\n required: true\n type: string\n storybook-projects:\n description: >\n JSON array of { \"name\"?, \"workingDir\", \"outputDir\"? } objects for storybook deploys.\n Each is built via `pnpm -C <workingDir> build:storybook`. If \"name\" is provided the\n output is placed under `sandbox/<name>/`; omit \"name\" for single-repo deploys and the\n output lands directly in `sandbox/`.\n type: string\n required: false\n default: \"[]\"\n build-script:\n description: pnpm script that builds the Storybook static output (single storybook, type:storybook only)\n type: string\n required: false\n default: build:storybook\n output-dir:\n description: Directory where Storybook writes its static output (single storybook, type:storybook only)\n type: string\n required: false\n default: storybook-static\n\njobs:\n deploy:\n name: Deploy\n runs-on: ubuntu-latest\n permissions:\n contents: read\n pages: write\n id-token: write\n environment:\n name: github-pages\n url: ${{ steps.deployment.outputs.page_url }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Build docs site\n if: ${{ inputs.type == 'docs' }}\n run: pnpm -C docs build\n\n - name: Build Storybook projects\n if: ${{ inputs.storybook-projects != '[]' }}\n env:\n PROJECTS: ${{ inputs.storybook-projects }}\n run: |\n echo \"$PROJECTS\" | jq -c '.[]' | while IFS= read -r project; do\n workingDir=$(echo \"$project\" | jq -r '.workingDir')\n pnpm -C \"$workingDir\" build:storybook\n done\n\n - name: Build Storybook\n if: ${{ inputs.type == 'storybook' && inputs.storybook-projects == '[]' }}\n env:\n BUILD_SCRIPT: ${{ inputs.build-script }}\n run: pnpm run \"$BUILD_SCRIPT\"\n\n - name: Assemble site\n env:\n DEPLOY_TYPE: ${{ inputs.type }}\n PROJECTS: ${{ inputs.storybook-projects }}\n STORYBOOK_OUTPUT_DIR: ${{ inputs.output-dir }}\n run: |\n mkdir -p _site\n if [ \"$DEPLOY_TYPE\" = \"docs\" ]; then\n cp -r docs/dist/. _site/\n fi\n if [ \"$PROJECTS\" != \"[]\" ]; then\n echo \"$PROJECTS\" | jq -c '.[]' | while IFS= read -r project; do\n name=$(echo \"$project\" | jq -r '.name // \"\"')\n workingDir=$(echo \"$project\" | jq -r '.workingDir')\n outputDir=$(echo \"$project\" | jq -r '.outputDir // \"storybook-static\"')\n if [ -n \"$name\" ]; then\n target=\"_site/sandbox/${name}\"\n else\n target=\"_site/sandbox\"\n fi\n mkdir -p \"$target\"\n cp -r \"${workingDir}/${outputDir}/.\" \"$target/\"\n done\n elif [ \"$DEPLOY_TYPE\" = \"storybook\" ]; then\n mkdir -p _site/sandbox\n cp -r \"${STORYBOOK_OUTPUT_DIR}/.\" _site/sandbox/\n fi\n\n - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1\n name: Upload pages artifact\n with:\n path: _site\n\n - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5\n id: deployment\n name: Deploy to GitHub Pages\n";
|
|
143
148
|
//#endregion
|
|
144
149
|
//#region src/templates/reusable/delivery.publish.yml
|
|
145
|
-
var delivery_publish_default$1 = "name: Release\n\n# Semantic-release with OIDC Trusted Publishing.\n# actions/setup-node writes a default NODE_AUTH_TOKEN=${{ github.token }}\n# which shadows OIDC auth. We explicitly clear it so npm falls through to\n# the Trusted Publisher OIDC exchange.\n# The calling repo must have a .releaserc.json that configures branches,\n# plugins, and any publish options. npm@11+ is installed to support OIDC.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n dry-run:\n description: >\n When true, runs semantic-release --dry-run: analyzes commits and\n previews the next release without git writes or publish. Only\n meaningful for workflow_dispatch triggers; push-triggered runs\n always run fully.\n type: boolean\n required: false\n default: false\n post-release:\n description: >\n When true, dispatches a readme sync broadcast after a successful\n release. Enable for packages consumed by other repos via the\n registry-doc system so downstream README installation blocks\n stay current.\n type: boolean\n required: false\n default: false\n run-build:\n description: Run `pnpm build` before releasing\n type: boolean\n required: false\n default: true\n deploy-on-release:\n description: >\n When true, runs a separate `deploy` job after a successful release\n (holocron#930): `holocron deploy-on-release` deploys every workspace\n package whose `delivery.deploy` task is `with: { on: release }`, when\n the release is on its channel and changes its paths. The job is its\n own check, re-runnable on its own, and gets VERCEL_TOKEN; the release\n job never sees it. Off by default, so a repo that doesn't deploy from\n its release never exposes the token.\n type: boolean\n required: false\n default: false\n sentry-project:\n description: >\n Sentry project slug for sourcemap upload and release creation after\n publishing. Omit to skip the Sentry release step entirely.\n type: string\n required: false\n default: \"\"\n secrets:\n HOLOCRON_RELEASE_TOKEN:\n description: >\n Fine-grained PAT (Contents + Issues + Pull requests: write) owned by\n an admin. Required when the default branch is protected by a ruleset —\n github.token cannot push through rulesets, but an admin PAT can.\n Takes priority over HOLOCRON_SYNC_TOKEN. Falls back to github.token.\n required: false\n HOLOCRON_SYNC_TOKEN:\n description: >\n Legacy alias for HOLOCRON_RELEASE_TOKEN — kept for backward compatibility.\n Prefer HOLOCRON_RELEASE_TOKEN for new repos.\n required: false\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to github.token.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when\n HOLOCRON_READ_TOKEN is not set.\n required: false\n SENTRY_AUTH_TOKEN:\n description: >\n Sentry auth token for sourcemap upload and release creation.\n Required when sentry-project is set. Use the org-level secret.\n required: false\n TURBO_TOKEN:\n required: false\n VERCEL_TOKEN:\n description: >\n Vercel token for the deploy job. Only used when deploy-on-release is\n true, and only by that job.\n required: false\n\njobs:\n release:\n name: Semantic release\n permissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n outputs:\n # Read by the deploy job (holocron#930). Empty unless semantic-release\n # published: `commit` is the release commit, `previous` the commit of\n # the release before it (to diff against), `channel` the new version's\n # prerelease identifier (`alpha`), empty for a stable release (`main`).\n commit: ${{ steps.release.outputs.commit }}\n previous: ${{ steps.release.outputs.previous }}\n channel: ${{ steps.release.outputs.channel }}\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n # Do not cancel in-progress releases — a partial release is worse than a slow one.\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n persist-credentials: false\n # Use HOLOCRON_RELEASE_TOKEN when available — git push (tags, release commits)\n # uses the checkout credential, not GITHUB_TOKEN env var. The\n # built-in github.token cannot push through branch protection rulesets.\n token: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Configure git identity\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n git config --global user.name \"$GIT_NAME\"\n git config --global user.email \"$GIT_EMAIL\"\n git config --global format.signoff true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Upgrade npm for OIDC support\n run: npm install -g npm@11 sigstore\n # sigstore is required by libnpmpublish/provenance.js at module parse\n # time — before any config takes effect. Some npm 11.x builds stopped\n # bundling it; installing it globally into the same prefix ensures it\n # resolves regardless of npm version. (Discovered 2026-07-09.)\n\n - run: pnpm build\n name: Build\n if: ${{ inputs.run-build == true }}\n\n - name: Release\n id: release\n run: |\n BEFORE=$(git rev-parse HEAD)\n PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true)\n PREVIOUS=\"\"\n if [ -n \"$PREVIOUS_TAG\" ]; then PREVIOUS=$(git rev-list -n 1 \"$PREVIOUS_TAG\"); fi\n if [ \"$DRY_RUN\" = \"true\" ]; then\n npx semantic-release --dry-run\n else\n npx semantic-release\n fi\n AFTER=$(git rev-parse HEAD)\n # semantic-release only commits (`chore(release): … [skip ci]`) when it\n # actually publishes. That commit gets no CI (and so no coverage\n # upload), which makes the next PR's `codecov/project` compare a full\n # report against a stale ancestor — a phantom drop (holocron#644).\n # Emit the new SHA so the steps below upload coverage for it.\n if [ \"$BEFORE\" != \"$AFTER\" ]; then\n echo \"commit=$AFTER\" >> \"$GITHUB_OUTPUT\"\n echo \"previous=$PREVIOUS\" >> \"$GITHUB_OUTPUT\"\n # The new version's channel: the prerelease identifier of its tag\n # (v5.0.0-alpha.108 -> alpha), empty for a stable release.\n VERSION=$(git describe --tags --abbrev=0)\n VERSION=${VERSION#v}\n CHANNEL=\"\"\n case \"$VERSION\" in *-*) PRE=${VERSION#*-}; CHANNEL=${PRE%%.*} ;; esac\n echo \"channel=$CHANNEL\" >> \"$GITHUB_OUTPUT\"\n fi\n env:\n DRY_RUN: ${{ inputs.dry-run }}\n # Prefer HOLOCRON_RELEASE_TOKEN (fine-grained PAT, Contents+Issues+PRs write,\n # owned by an admin with ruleset bypass) so @semantic-release/git can\n # push the version-bump commit through branch protection. Falls back to\n # HOLOCRON_SYNC_TOKEN (legacy) then github.token for unprotected repos.\n GITHUB_TOKEN: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n HUSKY: \"0\"\n NPM_CONFIG_PROVENANCE: true\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Coverage for the release commit\n if: ${{ steps.release.outputs.commit != '' }}\n with:\n task: verification.unitTests\n\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload release-commit coverage to Codecov\n if: ${{ steps.release.outputs.commit != '' }}\n # fail_ci_if_error makes a failed upload fail this step, so it is visible, but\n # continue-on-error keeps it from failing the job: the release has already\n # published, and the Sentry release steps below must still run (holocron#934).\n continue-on-error: true\n with:\n use_oidc: true\n fail_ci_if_error: true\n override_commit: ${{ steps.release.outputs.commit }}\n override_branch: ${{ github.ref_name }}\n\n - name: Get release version\n id: release_version\n if: ${{ inputs.sentry-project != '' && inputs.dry-run != true }}\n env:\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n run: |\n TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo \"\")\n if [ -n \"$TAG\" ]; then\n echo \"release=${SENTRY_PROJECT}@${TAG#v}\" >> \"$GITHUB_OUTPUT\"\n fi\n\n - name: Create Sentry release\n if: ${{ inputs.sentry-project != '' && steps.release_version.outputs.release != '' && inputs.dry-run != true }}\n uses: getsentry/action-release@ff07929a6537bac57790c3451cf4d364aca38528 # v3\n env:\n SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n SENTRY_ORG: theholocron\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n with:\n environment: production\n version: ${{ steps.release_version.outputs.release }}\n sourcemaps: \"**/dist\"\n\n deploy:\n name: Deploy\n needs: release\n # Only after a release that actually published (`commit` is empty on a dry\n # run or when nothing was released). A failed deploy fails only this job:\n # the release is already on npm, and this job re-runs on its own.\n if: ${{ inputs.deploy-on-release == true && inputs.dry-run != true && needs.release.outputs.commit != '' }}\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 30\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout the release commit\n with:\n ref: ${{ needs.release.outputs.commit }}\n fetch-depth: 0\n persist-credentials: false\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Deploy packages that deploy on release\n env:\n VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n with:\n command: deploy-on-release\n args: >-\n --channel=${{ needs.release.outputs.channel }}\n --from=${{ needs.release.outputs.previous }}\n --to=${{ needs.release.outputs.commit }}\n\n sync-readme:\n name: Post-release readme sync\n needs: release\n if: ${{ inputs.post-release == true && inputs.dry-run != true }}\n runs-on: ubuntu-latest\n timeout-minutes: 5\n steps:\n - name: Trigger broadcast readme sync\n run: |\n gh workflow run sync-dispatch.yml \\\n --repo theholocron/.github \\\n --field \"steps=readme\"\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n";
|
|
150
|
+
var delivery_publish_default$1 = "name: Release\n\n# Semantic-release with OIDC Trusted Publishing.\n# actions/setup-node writes a default NODE_AUTH_TOKEN=${{ github.token }}\n# which shadows OIDC auth. We explicitly clear it so npm falls through to\n# the Trusted Publisher OIDC exchange.\n# The calling repo must have a .releaserc.json that configures branches,\n# plugins, and any publish options. npm@11+ is installed to support OIDC.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n dry-run:\n description: >\n When true, runs semantic-release --dry-run: analyzes commits and\n previews the next release without git writes or publish. Only\n meaningful for workflow_dispatch triggers; push-triggered runs\n always run fully.\n type: boolean\n required: false\n default: false\n post-release:\n description: >\n When true, dispatches a readme sync broadcast after a successful\n release. Enable for packages consumed by other repos via the\n registry-doc system so downstream README installation blocks\n stay current.\n type: boolean\n required: false\n default: false\n run-build:\n description: Run `pnpm build` before releasing\n type: boolean\n required: false\n default: true\n deploy-on-release:\n description: >\n When true, runs a separate `deploy` job after a successful release\n (holocron#930): `holocron deploy on-release` deploys every workspace\n package whose `delivery.deploy` task is `with: { on: release }`, when\n the release is on its channel and changes its paths. The job is its\n own check, re-runnable on its own, and gets VERCEL_TOKEN; the release\n job never sees it. Off by default, so a repo that doesn't deploy from\n its release never exposes the token.\n type: boolean\n required: false\n default: false\n sentry-project:\n description: >\n Sentry project slug for sourcemap upload and release creation after\n publishing. Omit to skip the Sentry release step entirely.\n type: string\n required: false\n default: \"\"\n secrets:\n HOLOCRON_RELEASE_TOKEN:\n description: >\n Fine-grained PAT (Contents + Issues + Pull requests: write) owned by\n an admin. Required when the default branch is protected by a ruleset —\n github.token cannot push through rulesets, but an admin PAT can.\n Takes priority over HOLOCRON_SYNC_TOKEN. Falls back to github.token.\n required: false\n HOLOCRON_SYNC_TOKEN:\n description: >\n Legacy alias for HOLOCRON_RELEASE_TOKEN — kept for backward compatibility.\n Prefer HOLOCRON_RELEASE_TOKEN for new repos.\n required: false\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to github.token.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when\n HOLOCRON_READ_TOKEN is not set.\n required: false\n SENTRY_AUTH_TOKEN:\n description: >\n Sentry auth token for sourcemap upload and release creation.\n Required when sentry-project is set. Use the org-level secret.\n required: false\n TURBO_TOKEN:\n required: false\n VERCEL_TOKEN:\n description: >\n Vercel token for the deploy job. Only used when deploy-on-release is\n true, and only by that job.\n required: false\n\njobs:\n release:\n name: Semantic release\n permissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n outputs:\n # Read by the deploy job (holocron#930). Empty unless semantic-release\n # published: `commit` is the release commit, `previous` the commit of\n # the release before it (to diff against), `channel` the new version's\n # prerelease identifier (`alpha`), empty for a stable release (`main`).\n commit: ${{ steps.release.outputs.commit }}\n previous: ${{ steps.release.outputs.previous }}\n channel: ${{ steps.release.outputs.channel }}\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n # Do not cancel in-progress releases — a partial release is worse than a slow one.\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n persist-credentials: false\n # Use HOLOCRON_RELEASE_TOKEN when available — git push (tags, release commits)\n # uses the checkout credential, not GITHUB_TOKEN env var. The\n # built-in github.token cannot push through branch protection rulesets.\n token: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Configure git identity\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n git config --global user.name \"$GIT_NAME\"\n git config --global user.email \"$GIT_EMAIL\"\n git config --global format.signoff true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Upgrade npm for OIDC support\n run: npm install -g npm@11 sigstore\n # sigstore is required by libnpmpublish/provenance.js at module parse\n # time — before any config takes effect. Some npm 11.x builds stopped\n # bundling it; installing it globally into the same prefix ensures it\n # resolves regardless of npm version. (Discovered 2026-07-09.)\n\n - run: pnpm build\n name: Build\n if: ${{ inputs.run-build == true }}\n\n - name: Release\n id: release\n run: |\n BEFORE=$(git rev-parse HEAD)\n PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true)\n PREVIOUS=\"\"\n if [ -n \"$PREVIOUS_TAG\" ]; then PREVIOUS=$(git rev-list -n 1 \"$PREVIOUS_TAG\"); fi\n if [ \"$DRY_RUN\" = \"true\" ]; then\n npx semantic-release --dry-run\n else\n npx semantic-release\n fi\n AFTER=$(git rev-parse HEAD)\n # semantic-release only commits (`chore(release): … [skip ci]`) when it\n # actually publishes. That commit gets no CI (and so no coverage\n # upload), which makes the next PR's `codecov/project` compare a full\n # report against a stale ancestor — a phantom drop (holocron#644).\n # Emit the new SHA so the steps below upload coverage for it.\n if [ \"$BEFORE\" != \"$AFTER\" ]; then\n echo \"commit=$AFTER\" >> \"$GITHUB_OUTPUT\"\n echo \"previous=$PREVIOUS\" >> \"$GITHUB_OUTPUT\"\n # The new version's channel: the prerelease identifier of its tag\n # (v5.0.0-alpha.108 -> alpha), empty for a stable release.\n VERSION=$(git describe --tags --abbrev=0)\n VERSION=${VERSION#v}\n CHANNEL=\"\"\n case \"$VERSION\" in *-*) PRE=${VERSION#*-}; CHANNEL=${PRE%%.*} ;; esac\n echo \"channel=$CHANNEL\" >> \"$GITHUB_OUTPUT\"\n fi\n env:\n DRY_RUN: ${{ inputs.dry-run }}\n # Prefer HOLOCRON_RELEASE_TOKEN (fine-grained PAT, Contents+Issues+PRs write,\n # owned by an admin with ruleset bypass) so @semantic-release/git can\n # push the version-bump commit through branch protection. Falls back to\n # HOLOCRON_SYNC_TOKEN (legacy) then github.token for unprotected repos.\n GITHUB_TOKEN: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n HUSKY: \"0\"\n NPM_CONFIG_PROVENANCE: true\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Coverage for the release commit\n if: ${{ steps.release.outputs.commit != '' }}\n with:\n task: verification.unitTests\n\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload release-commit coverage to Codecov\n if: ${{ steps.release.outputs.commit != '' }}\n # fail_ci_if_error makes a failed upload fail this step, so it is visible, but\n # continue-on-error keeps it from failing the job: the release has already\n # published, and the Sentry release steps below must still run (holocron#934).\n continue-on-error: true\n with:\n use_oidc: true\n fail_ci_if_error: true\n override_commit: ${{ steps.release.outputs.commit }}\n override_branch: ${{ github.ref_name }}\n\n - name: Get release version\n id: release_version\n if: ${{ inputs.sentry-project != '' && inputs.dry-run != true }}\n env:\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n run: |\n TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo \"\")\n if [ -n \"$TAG\" ]; then\n echo \"release=${SENTRY_PROJECT}@${TAG#v}\" >> \"$GITHUB_OUTPUT\"\n fi\n\n - name: Create Sentry release\n if: ${{ inputs.sentry-project != '' && steps.release_version.outputs.release != '' && inputs.dry-run != true }}\n uses: getsentry/action-release@ff07929a6537bac57790c3451cf4d364aca38528 # v3\n env:\n SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n SENTRY_ORG: theholocron\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n with:\n environment: production\n version: ${{ steps.release_version.outputs.release }}\n sourcemaps: \"**/dist\"\n\n deploy:\n name: Deploy\n needs: release\n # Only after a release that actually published (`commit` is empty on a dry\n # run or when nothing was released). A failed deploy fails only this job:\n # the release is already on npm, and this job re-runs on its own.\n if: ${{ inputs.deploy-on-release == true && inputs.dry-run != true && needs.release.outputs.commit != '' }}\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 30\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout the release commit\n with:\n ref: ${{ needs.release.outputs.commit }}\n fetch-depth: 0\n persist-credentials: false\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Deploy packages that deploy on release\n env:\n VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n with:\n command: deploy on-release\n args: >-\n --channel=${{ needs.release.outputs.channel }}\n --from=${{ needs.release.outputs.previous }}\n --to=${{ needs.release.outputs.commit }}\n\n sync-readme:\n name: Post-release readme sync\n needs: release\n if: ${{ inputs.post-release == true && inputs.dry-run != true }}\n runs-on: ubuntu-latest\n timeout-minutes: 5\n steps:\n - name: Trigger broadcast readme sync\n run: |\n gh workflow run sync-dispatch.yml \\\n --repo theholocron/.github \\\n --field \"steps=readme\"\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n";
|
|
146
151
|
//#endregion
|
|
147
152
|
//#region src/templates/reusable/dependencies.yml
|
|
148
153
|
var dependencies_default$1 = "name: Dependencies\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n merge-token:\n description: >\n Optional privileged token for auto-merge. Falls back to GITHUB_TOKEN.\n Required when branch protection enforces required reviews — GITHUB_TOKEN\n cannot approve its own PRs.\n required: false\n\njobs:\n dependabot:\n name: Update the dependencies\n permissions:\n contents: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 5\n if: github.event.pull_request.user.login == 'dependabot[bot]'\n steps:\n - uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0\n name: Fetch Dependabot metadata\n id: metadata\n\n - run: gh pr merge --auto --squash \"$PR_URL\"\n # --squash is intentional: repo protection sets allow_merge_commit: false,\n # so --merge would fail on any repo using the standard preset.\n name: Enable auto-merge for Dependabot PRs\n if: steps.metadata.outputs.update-type == 'version-update:semver-patch'\n env:\n PR_URL: ${{ github.event.pull_request.html_url }}\n GH_TOKEN: ${{ secrets.merge-token || github.token }}\n";
|
|
@@ -160,7 +165,7 @@ var platform_commitStandards_default$1 = "name: Commit Standards\n\non: # yamlli
|
|
|
160
165
|
var platform_dispatchedCheck_default = "name: Dispatched Check\n\non: # yamllint disable-line rule:truthy\n workflow_dispatch:\n inputs:\n repo:\n description: Target repo, \"owner/name\", to check out and run the task against.\n type: string\n required: true\n ref:\n description: Git ref (branch or commit SHA) to check out.\n type: string\n required: true\n task:\n description: The holocron task name to run (e.g. \"verification.typeSafety\").\n type: string\n required: true\n check-run-id:\n description: >\n ID of the check run already posted as \"queued\" on the target repo —\n patched to \"completed\" once the task finishes.\n type: string\n required: true\n\npermissions:\n contents: read\n\njobs:\n run-task:\n name: Run a Sentinel-dispatched task against another repo\n runs-on: ubuntu-latest\n timeout-minutes: 20\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n # actions/create-github-app-token defaults to scoping its token to the\n # *current* repo (.github, where this workflow runs) when owner/\n # repositories are omitted — useless here, since every real use of this\n # token targets inputs.repo instead. Split \"owner/name\" up front so the\n # token step below can scope explicitly to the actual target.\n - name: Parse target repo\n id: target\n run: |\n echo \"owner=${INPUT_REPO%%/*}\" >> \"$GITHUB_OUTPUT\"\n echo \"name=${INPUT_REPO##*/}\" >> \"$GITHUB_OUTPUT\"\n env:\n INPUT_REPO: ${{ inputs.repo }}\n\n # Sentinel's own dispatch call only carries plain, non-secret inputs\n # (repo/ref/task/check-run-id — all visible in the Actions UI/logs).\n # This step mints a fresh, short-lived installation token here instead,\n # scoped by the same App's stored credentials — never a raw token\n # passed through a dispatch input.\n - name: Generate installation token\n id: app-token\n uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0\n with:\n app-id: ${{ secrets.SENTINEL_APP_ID }}\n private-key: ${{ secrets.SENTINEL_APP_PRIVATE_KEY }}\n owner: ${{ steps.target.outputs.owner }}\n repositories: ${{ steps.target.outputs.name }}\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout target repository\n with:\n repository: ${{ inputs.repo }}\n ref: ${{ inputs.ref }}\n token: ${{ steps.app-token.outputs.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Run the task\n id: run-task\n continue-on-error: true\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n task: ${{ inputs.task }}\n\n # A bare .../actions/runs/<id> link lands on the run summary, one click\n # short of the actual step-by-step log a normal check's details_url\n # points at. This job is always alone in its run, so the first (only)\n # job in the run's own job list is unambiguously this one.\n - name: Resolve this job's numeric ID\n id: job-info\n if: always()\n run: |\n job_id=$(gh api \"repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs\" --jq '.jobs[0].id')\n echo \"job_id=$job_id\" >> \"$GITHUB_OUTPUT\"\n env:\n GH_TOKEN: ${{ steps.app-token.outputs.token }}\n\n # Ships the task's real captured output (tsc/vitest/etc — whatever\n # `holocron run` actually printed) to the same Axiom dataset Sentinel's\n # own structured logs already flow into, tagged so it's queryable by\n # this exact run. Reuses the existing dataset rather than provisioning\n # a new one — a prototype-stage call, revisit if CI-log volume ever\n # warrants a dedicated dataset. Builds a permalink filtered to this\n # run's own logs (Axiom's own \"copy query link\" URL shape:\n # /<org>/query?initForm={\"apl\":\"...\"} ) so a viewer lands directly on\n # the relevant lines, not a bare dataset view.\n - name: Ship task output to Axiom\n id: axiom\n if: always()\n run: |\n message=$(tail -c 40000 \"$LOG_FILE\" 2>/dev/null || echo \"(no output captured)\")\n # \"level\" is one of the field names Axiom's Stream/Query tabs\n # recognize for severity-color highlighting (docs: level, @level,\n # severity, @severity, status.code) -- conclusion alone isn't.\n level=$([ \"$CONCLUSION\" = \"success\" ] && echo \"info\" || echo \"error\")\n event=$(jq -cn \\\n --arg time \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --arg repo \"$REPO\" --arg task \"$TASK\" --arg run_id \"$GITHUB_RUN_ID\" \\\n --arg job_id \"$JOB_ID\" --arg check_run_id \"$CHECK_RUN_ID\" \\\n --arg conclusion \"$CONCLUSION\" --arg level \"$level\" --arg message \"$message\" \\\n '{_time: $time, source: \"dispatched-check\", repo: $repo, task: $task,\n run_id: $run_id, job_id: $job_id, check_run_id: $check_run_id,\n conclusion: $conclusion, level: $level, message: $message}')\n curl -sf -X POST \"https://api.axiom.co/v1/datasets/${AXIOM_DATASET}/ingest\" \\\n -H \"Authorization: Bearer ${AXIOM_TOKEN}\" \\\n -H \"Content-Type: application/json\" \\\n -d \"[${event}]\" || echo \"::warning::Axiom ingest failed — continuing, the check itself still reports the real result\"\n\n # The ['...'] is Axiom's own APL array-dataset-reference syntax, not\n # a shell quote boundary — $AXIOM_DATASET/$CHECK_RUN_ID both expand\n # fine here; shellcheck's single-quote heuristic just can't tell.\n # shellcheck disable=SC2016\n apl=\"['${AXIOM_DATASET}'] | where check_run_id == \\\"${CHECK_RUN_ID}\\\"\"\n init_form=$(jq -cn --arg apl \"$apl\" '{apl: $apl}')\n encoded=$(jq -rn --arg s \"$init_form\" '$s | @uri')\n echo \"query-url=https://app.axiom.co/${AXIOM_ORG}/query?initForm=${encoded}\" >> \"$GITHUB_OUTPUT\"\n env:\n LOG_FILE: ${{ steps.run-task.outputs.log-file }}\n REPO: ${{ inputs.repo }}\n TASK: ${{ inputs.task }}\n JOB_ID: ${{ steps.job-info.outputs.job_id }}\n CHECK_RUN_ID: ${{ inputs.check-run-id }}\n CONCLUSION: ${{ steps.run-task.outcome == 'success' && 'success' || 'failure' }}\n AXIOM_ORG: the-holocron-7bbe\n AXIOM_DATASET: holocron-sentinel\n AXIOM_TOKEN: ${{ secrets.SENTINEL_AXIOM_INGEST_TOKEN }}\n\n # Always runs, success or failure — the queued check run on the target\n # repo must be resolved either way, not left hanging. Full JSON body via\n # jq + --input, not -f flags: output.text needs real nesting (gh api -f\n # has no dot-notation for that), and the captured task output can\n # contain characters (quotes, $, backticks) that would break a\n # hand-built -f string.\n - name: Report result to the target repo's check run\n if: always()\n run: |\n excerpt=$(tail -c 3000 \"$LOG_FILE\" 2>/dev/null || echo \"(no output captured)\")\n # The single-quoted format string is intentional — every %s fills\n # via printf's own args below, not shell expansion; nothing in it\n # needs to expand.\n # shellcheck disable=SC2016\n text=$(printf '**Task:** `%s`\\n\\n**Result:** %s\\n\\n[Full logs on Axiom](%s)\\n\\n### Last output\\n\\n```\\n%s\\n```\\n' \\\n \"$TASK\" \"$CONCLUSION\" \"$QUERY_URL\" \"$excerpt\")\n body=$(jq -n \\\n --arg conclusion \"$CONCLUSION\" \\\n --arg details_url \"$RUN_URL\" \\\n --arg title \"$TASK ($CONCLUSION)\" \\\n --arg summary \"Dispatched via theholocron/.github — see the log excerpt below, or the full run/Axiom links.\" \\\n --arg text \"$text\" \\\n '{status: \"completed\", conclusion: $conclusion, details_url: $details_url,\n output: {title: $title, summary: $summary, text: $text}}')\n echo \"$body\" | gh api --method PATCH \"/repos/${REPO}/check-runs/${CHECK_RUN_ID}\" --input -\n env:\n # `GH_TOKEN` is the literal name `gh api` requires to auto-authenticate\n # -- not a stand-in for a shared/blanket credential. Its value is the\n # short-lived, Sentinel-App-scoped token minted above, same as the\n # checkout step uses; never secrets.HOLOCRON_SYNC_TOKEN (the one\n # genuinely shared PAT in this org, used by sync-dispatch.yml).\n GH_TOKEN: ${{ steps.app-token.outputs.token }}\n REPO: ${{ inputs.repo }}\n TASK: ${{ inputs.task }}\n CHECK_RUN_ID: ${{ inputs.check-run-id }}\n CONCLUSION: ${{ steps.run-task.outcome == 'success' && 'success' || 'failure' }}\n LOG_FILE: ${{ steps.run-task.outputs.log-file }}\n QUERY_URL: ${{ steps.axiom.outputs.query-url }}\n RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ steps.job-info.outputs.job_id }}\n";
|
|
161
166
|
//#endregion
|
|
162
167
|
//#region src/templates/reusable/platform.repoSync.yml
|
|
163
|
-
var platform_repoSync_default$1 = "name: Sync\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n steps:\n description: >\n Sync steps to run (default: all). Valid values:\n labels, properties, teams, topics, keywords, description, homepage, readme, workflows, wiki.\n Pass a space-separated list to run a subset, e.g. \"readme\" or \"readme wiki\".\n type: string\n required: false\n secrets:\n HOLOCRON_ADMIN_TOKEN:\n description: Fine-grained PAT with admin scopes (labels, properties, teams).\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n Falls back to the vendor-native AXIOM_TOKEN secret.\n required: false\n AXIOM_TOKEN:\n description: Vendor-native fallback for HOLOCRON_AXIOM_TOKEN.\n required: false\n HOLOCRON_DEPLOY_TOKEN:\n description: Fine-grained PAT for GitHub Pages configuration.\n required: false\n HOLOCRON_ISSUES_TOKEN:\n description: Fine-grained PAT for issue management.\n required: false\n HOLOCRON_ORG_TOKEN:\n description: Org-scoped fine-grained PAT for team sync and org properties.\n required: false\n HOLOCRON_READ_TOKEN:\n description: Fine-grained PAT for read-only GitHub API calls.\n required: false\n HOLOCRON_SYNC_TOKEN:\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for gh CLI calls. Used when\n HOLOCRON_SYNC_TOKEN is not set.\n required: false\n\njobs:\n sync:\n name: Run holocron sync\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: write\n pull-requests: write\n # Job-level so the `holocron` composite action's steps inherit them.\n env:\n HOLOCRON_ADMIN_TOKEN: ${{ secrets.HOLOCRON_ADMIN_TOKEN }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN || secrets.AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET || vars.AXIOM_DATASET }}\n POSTHOG_PROJECT_TOKEN: ${{ vars.POSTHOG_PROJECT_TOKEN }}\n HOLOCRON_DEPLOY_TOKEN: ${{ secrets.HOLOCRON_DEPLOY_TOKEN }}\n HOLOCRON_ISSUES_TOKEN: ${{ secrets.HOLOCRON_ISSUES_TOKEN }}\n HOLOCRON_ORG_TOKEN: ${{ secrets.HOLOCRON_ORG_TOKEN }}\n HOLOCRON_READ_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Run holocron sync\n # The `holocron` action resolves @theholocron/cli from node_modules (or\n # builds the workspace when it's an unbuilt checkout — the holocron repo\n # itself). `inputs.steps` is a space-separated list or empty; the\n # `--steps <list>` arg is omitted when empty.\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n command: sync\n args: ${{ inputs.steps != '' && format('--steps {0}', inputs.steps) || '' }}\n\n - name: Format generated files\n run: |\n pnpm exec prettier --write README.md docs/src/content/docs/index.mdx 2>/dev/null || true\n # `holocron sync` writes package.json fields (keywords, description,\n # homepage) with a plain assignment, which appends new keys at the\n # end — re-apply the canonical order the lint config enforces.\n pnpm exec eslint --fix --no-warn-ignored package.json 2>/dev/null || true\n\n - uses: theholocron/.github/.github/actions/auto-commit@main\n id: auto-commit\n name: Commit sync changes\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n branch: chore/auto-sync\n commit-message: \"chore: sync from holocron.config\"\n commit-options: \"--no-verify\"\n\n - name: Open PR if changes were committed\n if: steps.auto-commit.outputs.changes-detected == 'true'\n run: |\n gh pr create \\\n --title \"chore: sync README and repo metadata\" \\\n --body \"Automated sync triggered by changes to config or package files. Merge to apply.\" \\\n --base main \\\n --head chore/auto-sync \\\n || echo \"PR already open — branch updated.\"\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Broadcast wiki sync if navbar changed\n if: >-\n steps.auto-commit.outputs.changes-detected == 'true' &&\n github.event_name == 'push' &&\n (inputs.steps == '' || contains(inputs.steps, 'wiki'))\n run: |\n if git diff HEAD~1 --name-only | grep -q 'fern/docs.yml'; then\n gh workflow run sync-dispatch.yml \\\n --repo theholocron/.github \\\n --field \"steps=wiki\" \\\n || echo \"skipping broadcast — insufficient permissions\"\n fi\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n";
|
|
168
|
+
var platform_repoSync_default$1 = "name: Sync\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n steps:\n description: >\n Sync steps to run (default: all). Valid values:\n labels, properties, teams, topics, keywords, description, homepage, readme, workflows, wiki.\n Pass a space-separated list to run a subset, e.g. \"readme\" or \"readme wiki\".\n type: string\n required: false\n secrets:\n HOLOCRON_ADMIN_TOKEN:\n description: Fine-grained PAT with admin scopes (labels, properties, teams).\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n Falls back to the vendor-native AXIOM_TOKEN secret.\n required: false\n AXIOM_TOKEN:\n description: Vendor-native fallback for HOLOCRON_AXIOM_TOKEN.\n required: false\n HOLOCRON_DEPLOY_TOKEN:\n description: Fine-grained PAT for GitHub Pages configuration.\n required: false\n HOLOCRON_ISSUES_TOKEN:\n description: Fine-grained PAT for issue management.\n required: false\n HOLOCRON_ORG_TOKEN:\n description: Org-scoped fine-grained PAT for team sync and org properties.\n required: false\n HOLOCRON_READ_TOKEN:\n description: Fine-grained PAT for read-only GitHub API calls.\n required: false\n HOLOCRON_SYNC_TOKEN:\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for gh CLI calls. Used when\n HOLOCRON_SYNC_TOKEN is not set.\n required: false\n\njobs:\n sync:\n name: Run holocron repo sync\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: write\n pull-requests: write\n # Job-level so the `holocron` composite action's steps inherit them.\n env:\n HOLOCRON_ADMIN_TOKEN: ${{ secrets.HOLOCRON_ADMIN_TOKEN }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN || secrets.AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET || vars.AXIOM_DATASET }}\n POSTHOG_PROJECT_TOKEN: ${{ vars.POSTHOG_PROJECT_TOKEN }}\n HOLOCRON_DEPLOY_TOKEN: ${{ secrets.HOLOCRON_DEPLOY_TOKEN }}\n HOLOCRON_ISSUES_TOKEN: ${{ secrets.HOLOCRON_ISSUES_TOKEN }}\n HOLOCRON_ORG_TOKEN: ${{ secrets.HOLOCRON_ORG_TOKEN }}\n HOLOCRON_READ_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Run holocron repo sync\n # The `holocron` action resolves @theholocron/cli from node_modules (or\n # builds the workspace when it's an unbuilt checkout — the holocron repo\n # itself). `inputs.steps` is a space-separated list or empty; the\n # `--steps <list>` arg is omitted when empty.\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n command: repo sync\n args: ${{ inputs.steps != '' && format('--steps {0}', inputs.steps) || '' }}\n\n - name: Format generated files\n run: |\n pnpm exec prettier --write README.md docs/src/content/docs/index.mdx 2>/dev/null || true\n # `holocron repo sync` writes package.json fields (keywords, description,\n # homepage) with a plain assignment, which appends new keys at the\n # end — re-apply the canonical order the lint config enforces.\n pnpm exec eslint --fix --no-warn-ignored package.json 2>/dev/null || true\n\n - uses: theholocron/.github/.github/actions/auto-commit@main\n id: auto-commit\n name: Commit sync changes\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n branch: chore/auto-sync\n commit-message: \"chore: sync from holocron.config\"\n commit-options: \"--no-verify\"\n\n - name: Open PR if changes were committed\n if: steps.auto-commit.outputs.changes-detected == 'true'\n run: |\n gh pr create \\\n --title \"chore: sync README and repo metadata\" \\\n --body \"Automated sync triggered by changes to config or package files. Merge to apply.\" \\\n --base main \\\n --head chore/auto-sync \\\n || echo \"PR already open — branch updated.\"\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Broadcast wiki sync if navbar changed\n if: >-\n steps.auto-commit.outputs.changes-detected == 'true' &&\n github.event_name == 'push' &&\n (inputs.steps == '' || contains(inputs.steps, 'wiki'))\n run: |\n if git diff HEAD~1 --name-only | grep -q 'fern/docs.yml'; then\n gh workflow run sync-dispatch.yml \\\n --repo theholocron/.github \\\n --field \"steps=wiki\" \\\n || echo \"skipping broadcast — insufficient permissions\"\n fi\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n";
|
|
164
169
|
//#endregion
|
|
165
170
|
//#region src/templates/reusable/platform.repoValidation.yml
|
|
166
171
|
var platform_repoValidation_default$1 = "name: Repo Validation\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n TURBO_TOKEN:\n required: false\n\njobs:\n adrs:\n name: Validate ADRs and specs\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Validate ADR and spec frontmatter\n with:\n task: platform.repoValidation\n job: adrs\n\n registry:\n name: Validate registry consistency\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Validate registry consistency\n with:\n task: platform.repoValidation\n job: registry\n\n docs-presence:\n name: Validate docs presence\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Validate docs presence for new packages\n with:\n task: platform.repoValidation\n job: docsPresence\n env:\n BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}\n\n conclusion:\n name: Conclusion\n runs-on: ubuntu-latest\n if: always()\n needs: [adrs, registry, docs-presence]\n steps:\n - name: Check job statuses\n run: |\n if [[ \"$RESULTS\" == *\"failure\"* ]] || [[ \"$RESULTS\" == *\"cancelled\"* ]]; then\n exit 1\n fi\n env:\n RESULTS: ${{ join(needs.*.result, ',') }}\n";
|
|
@@ -196,7 +201,7 @@ var stale_default$1 = "name: Stale\n\non: # yamllint disable-line rule:truthy\n
|
|
|
196
201
|
var sync_dispatch_default = "name: Sync Dispatch\n\non: # yamllint disable-line rule:truthy\n workflow_dispatch:\n inputs:\n steps:\n description: >\n Sync steps to pass to each repo's sync.yml. Default is \"readme\"\n (only README marker blocks are updated).\n type: string\n required: false\n default: readme\n\npermissions:\n contents: read\n\njobs:\n broadcast:\n name: Broadcast sync to all repos\n runs-on: ubuntu-latest\n timeout-minutes: 15\n steps:\n - name: Dispatch sync to all repos with sync.yml\n run: |\n gh api /orgs/theholocron/repos --paginate --jq '.[].name' \\\n | while IFS= read -r repo; do\n gh api \"/repos/theholocron/$repo/contents/.github/workflows/sync.yml\" --silent 2>/dev/null || continue\n echo \"Dispatching sync to theholocron/$repo\"\n gh workflow run sync.yml \\\n --repo \"theholocron/$repo\" \\\n --field \"steps=$STEPS\" \\\n || echo \"Warning: could not dispatch to theholocron/$repo — skipping\"\n done\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n STEPS: ${{ inputs.steps }}\n";
|
|
197
202
|
//#endregion
|
|
198
203
|
//#region src/templates/reusable/sync-github.yml
|
|
199
|
-
var sync_github_default = "name: Sync workflow templates\n\n# Builds the holocron CLI from source and pushes updated workflow templates\n# and composite actions to downstream .github repos. Runs whenever the\n# template source files change on main or alpha.\n#\n# Secrets required:\n# HOLOCRON_SYNC_TOKEN — fine-grained PAT (resource owner: org) with:\n# Actions: Read and write (dispatch workflow runs via gh workflow run)\n# Contents: Read and write (git trees, blobs, refs)\n# Pull requests: Read and write (open sync PR)\n# Workflows: Read and write (write .github/workflows/*.yml)\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n primary-repo:\n description: >\n Primary .github repo — receives composite actions, reusable workflows,\n and thin-caller templates. Requires a PR (branch protection assumed).\n type: string\n required: false\n default: theholocron/.github\n secondary-repos:\n description: >\n Space-separated list of secondary repos (reusable workflows + thin\n callers only, no composite actions). Changes are delivered via pull\n request, same as the primary repo.\n type: string\n required: false\n default: \"\"\n sync-branch:\n description: Branch name used for the primary and secondary repo PRs\n type: string\n required: false\n default: chore/sync-templates\n secrets:\n HOLOCRON_SYNC_TOKEN:\n required: true\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to HOLOCRON_SYNC_TOKEN.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when neither\n HOLOCRON_READ_TOKEN nor HOLOCRON_SYNC_TOKEN is set.\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n Falls back to the vendor-native AXIOM_TOKEN secret.\n required: false\n AXIOM_TOKEN:\n description: Vendor-native fallback for HOLOCRON_AXIOM_TOKEN.\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n sync:\n name: Sync templates\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN || secrets.AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET || vars.AXIOM_DATASET }}\n POSTHOG_PROJECT_TOKEN: ${{ vars.POSTHOG_PROJECT_TOKEN }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm build\n name: Build CLI\n\n - name: Cache actionlint\n id: cache-actionlint\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: /tmp/actionlint\n key: actionlint-v1.7.7-linux-amd64\n\n - name: Download actionlint\n if: steps.cache-actionlint.outputs.cache-hit != 'true'\n run: |\n curl -fsSL https://github.com/rhysd/actionlint/releases/download/v1.7.7/actionlint_1.7.7_linux_amd64.tar.gz \\\n | tar -xz -C /tmp actionlint\n\n - name: Validate generated workflows\n run: |\n node packages/cli/dist/cli.mjs sync
|
|
204
|
+
var sync_github_default = "name: Sync workflow templates\n\n# Builds the holocron CLI from source and pushes updated workflow templates\n# and composite actions to downstream .github repos. Runs whenever the\n# template source files change on main or alpha.\n#\n# Secrets required:\n# HOLOCRON_SYNC_TOKEN — fine-grained PAT (resource owner: org) with:\n# Actions: Read and write (dispatch workflow runs via gh workflow run)\n# Contents: Read and write (git trees, blobs, refs)\n# Pull requests: Read and write (open sync PR)\n# Workflows: Read and write (write .github/workflows/*.yml)\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n primary-repo:\n description: >\n Primary .github repo — receives composite actions, reusable workflows,\n and thin-caller templates. Requires a PR (branch protection assumed).\n type: string\n required: false\n default: theholocron/.github\n secondary-repos:\n description: >\n Space-separated list of secondary repos (reusable workflows + thin\n callers only, no composite actions). Changes are delivered via pull\n request, same as the primary repo.\n type: string\n required: false\n default: \"\"\n sync-branch:\n description: Branch name used for the primary and secondary repo PRs\n type: string\n required: false\n default: chore/sync-templates\n secrets:\n HOLOCRON_SYNC_TOKEN:\n required: true\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to HOLOCRON_SYNC_TOKEN.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when neither\n HOLOCRON_READ_TOKEN nor HOLOCRON_SYNC_TOKEN is set.\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n Falls back to the vendor-native AXIOM_TOKEN secret.\n required: false\n AXIOM_TOKEN:\n description: Vendor-native fallback for HOLOCRON_AXIOM_TOKEN.\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n sync:\n name: Sync templates\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN || secrets.AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET || vars.AXIOM_DATASET }}\n POSTHOG_PROJECT_TOKEN: ${{ vars.POSTHOG_PROJECT_TOKEN }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm build\n name: Build CLI\n\n - name: Cache actionlint\n id: cache-actionlint\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: /tmp/actionlint\n key: actionlint-v1.7.7-linux-amd64\n\n - name: Download actionlint\n if: steps.cache-actionlint.outputs.cache-hit != 'true'\n run: |\n curl -fsSL https://github.com/rhysd/actionlint/releases/download/v1.7.7/actionlint_1.7.7_linux_amd64.tar.gz \\\n | tar -xz -C /tmp actionlint\n\n - name: Validate generated workflows\n run: |\n node packages/cli/dist/cli.mjs sync github \\\n --repo \"$PRIMARY_REPO\" \\\n --output-dir /tmp/sync-validate\n /tmp/actionlint /tmp/sync-validate/.github/workflows/*.yml\n env:\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n\n - name: Sync primary repo (PR)\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n node packages/cli/dist/cli.mjs sync github \\\n --repo \"$PRIMARY_REPO\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$PRIMARY_REPO\" \"$SYNC_BRANCH\" 2>/dev/null || true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n\n - name: Sync secondary repos (PR)\n if: ${{ inputs.secondary-repos != '' }}\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n for repo in $SECONDARY_REPOS; do\n node packages/cli/dist/cli.mjs sync github \\\n --repo \"$repo\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$repo\" \"$SYNC_BRANCH\" 2>/dev/null || true\n done\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n SECONDARY_REPOS: ${{ inputs.secondary-repos }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n";
|
|
200
205
|
//#endregion
|
|
201
206
|
//#region src/templates/reusable/tag.yml
|
|
202
207
|
var tag_default = "name: Tag\n\n# Release Please — fully automated tag and GitHub Release from Conventional Commits.\n# No package.json or npm publishing required. Operates in \"simple\" mode by default:\n# analyzes commits since the last tag, maintains a rolling Release PR, and creates\n# a tag + GitHub Release when that PR is merged.\n#\n# The calling repo must have two files at the root:\n# release-please-config.json — declares packages and release-type\n# .release-please-manifest.json — tracks the current version\n\non: # yamllint disable-line rule:truthy\n # Self-trigger: when this workflow lives in theholocron/.github itself,\n # push to main runs Release Please for that repo's own releases.\n push:\n branches:\n - main\n workflow_call:\n inputs:\n release-type:\n description: Release Please release type (simple, node, python, etc.)\n type: string\n required: false\n default: simple\n config-file:\n description: Path to release-please-config.json\n type: string\n required: false\n default: release-please-config.json\n manifest-file:\n description: Path to .release-please-manifest.json\n type: string\n required: false\n default: .release-please-manifest.json\n\njobs:\n tag:\n name: Tag release\n permissions:\n contents: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: google-github-actions/release-please-action@e4dc86ba9405554aeba3c6bb2d169500e7d3b4ee # v4.1.1\n name: Run Release Please\n with:\n release-type: ${{ inputs.release-type }}\n config-file: ${{ inputs.config-file }}\n manifest-file: ${{ inputs.manifest-file }}\n";
|
|
@@ -216,7 +221,7 @@ var verification_unitTests_default$1 = "name: Test\n\non: # yamllint disable-lin
|
|
|
216
221
|
*
|
|
217
222
|
* `WORKFLOW_TEMPLATES` holds each reusable `.github/workflows/<name>.yml`
|
|
218
223
|
* (synced to `theholocron/.github`); `generateThinCallerContent` wraps one
|
|
219
|
-
* into the thin caller `holocron setup` / `holocron sync` write locally.
|
|
224
|
+
* into the thin caller `holocron repo setup` / `holocron repo sync` write locally.
|
|
220
225
|
*/
|
|
221
226
|
const WORKFLOW_TEMPLATES = {
|
|
222
227
|
"verification.unitTests": "name: Verification\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/verification.unitTests.yml@main\n with:\n run-unit: true\n secrets: inherit\n",
|
|
@@ -436,7 +441,7 @@ function generateCombinedDeployContent(deployWith, paths, preview) {
|
|
|
436
441
|
* - run-chromatic object → run-chromatic: true + chromatic-projects
|
|
437
442
|
* - plain arrays → JSON-stringified for YAML scalar quoting
|
|
438
443
|
*
|
|
439
|
-
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
444
|
+
* Used by both `holocron repo setup` and `sync-workflow-templates`.
|
|
440
445
|
*/
|
|
441
446
|
function normalizeWorkflowWith(raw) {
|
|
442
447
|
const result = { ...raw };
|
|
@@ -469,7 +474,7 @@ function normalizeWorkflowWith(raw) {
|
|
|
469
474
|
}
|
|
470
475
|
/**
|
|
471
476
|
* Derive on.push.paths entries from the deploy with: shorthand.
|
|
472
|
-
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
477
|
+
* Used by both `holocron repo setup` and `sync-workflow-templates`.
|
|
473
478
|
*/
|
|
474
479
|
function deriveDeployPaths(raw) {
|
|
475
480
|
const paths = [];
|
|
@@ -497,7 +502,7 @@ function deriveDeployPaths(raw) {
|
|
|
497
502
|
//#region src/generators/github-action-reusable-workflows.ts
|
|
498
503
|
/**
|
|
499
504
|
* The **reusable** GitHub Actions surface pushed to `theholocron/.github` by
|
|
500
|
-
* `holocron sync
|
|
505
|
+
* `holocron sync github` — the `workflow_call` implementations and composite
|
|
501
506
|
* actions that every repo's thin callers delegate to.
|
|
502
507
|
*
|
|
503
508
|
* `theholocron/.github` is a **pure sync target**: `.github/workflows/*`,
|
|
@@ -557,19 +562,19 @@ const SOURCE = "packages/astromech/src/generators/github-action-reusable-workflo
|
|
|
557
562
|
* "AUTO-GENERATED — do not edit" header for the YAML pushed to
|
|
558
563
|
* `theholocron/.github`. Matches `createHeader().workflowHeader({ forPrimary: true })`
|
|
559
564
|
* field-for-field, minus any timestamp — a live `Synced:` line makes the content
|
|
560
|
-
* hash change on every run and defeats `sync
|
|
565
|
+
* hash change on every run and defeats `sync github`'s unchanged-file skip.
|
|
561
566
|
*/
|
|
562
567
|
function reusableHeader() {
|
|
563
568
|
return [
|
|
564
569
|
`# AUTO-GENERATED — do not edit in theholocron/.github directly.`,
|
|
565
570
|
`# Source: theholocron/holocron · ${SOURCE}`,
|
|
566
|
-
`# Tool: holocron sync
|
|
571
|
+
`# Tool: holocron sync github`,
|
|
567
572
|
`# Changes: edit source in theholocron/holocron`,
|
|
568
573
|
``
|
|
569
574
|
].join("\n");
|
|
570
575
|
}
|
|
571
576
|
/**
|
|
572
|
-
* The complete file batch `holocron sync
|
|
577
|
+
* The complete file batch `holocron sync github` pushes to `theholocron/.github`:
|
|
573
578
|
* path → content. YAML gets the "do not edit" header; the starter-workflow
|
|
574
579
|
* `.properties.json` files are left bare (native JSON, not a commented file).
|
|
575
580
|
*/
|
|
@@ -783,7 +788,7 @@ const CI_ORDER = [
|
|
|
783
788
|
* {@link WORKFLOW_CHECK_CONTEXTS} entry; `config.extraRequiredChecks` adds
|
|
784
789
|
* contexts not backed by a task (codecov, DCO is prepended by the caller).
|
|
785
790
|
*
|
|
786
|
-
* `@theholocron/cli`'s `holocron setup` calls this instead of the old
|
|
791
|
+
* `@theholocron/cli`'s `holocron repo setup` calls this instead of the old
|
|
787
792
|
* hand-maintained `repo.requiredChecks` array. Policy-free — it only knows the
|
|
788
793
|
* manifest.
|
|
789
794
|
*/
|
|
@@ -1761,6 +1766,65 @@ function taskName(item) {
|
|
|
1761
1766
|
return typeof item === "string" ? item : item.name;
|
|
1762
1767
|
}
|
|
1763
1768
|
//#endregion
|
|
1769
|
+
//#region src/tasks/group.ts
|
|
1770
|
+
/**
|
|
1771
|
+
* `holocron run <namespace>` — run every configured task under one
|
|
1772
|
+
* namespace prefix (`sourceQuality.*`, `verification.*`, …) in sequence.
|
|
1773
|
+
* `Astromech.run()` falls back here whenever the given name isn't an exact
|
|
1774
|
+
* `TASKS` entry but matches the prefix of one or more configured tasks —
|
|
1775
|
+
* same "aggregate several real tasks into one report" shape `runCi` already
|
|
1776
|
+
* established, scoped to one namespace instead of the merge-gating set.
|
|
1777
|
+
*
|
|
1778
|
+
* Every task in the namespace always runs — one task's failure never skips
|
|
1779
|
+
* the rest, same as `runCi`'s own loop — so a single invocation surfaces
|
|
1780
|
+
* every problem in the namespace at once instead of stopping at the first.
|
|
1781
|
+
*/
|
|
1782
|
+
/** Every configured task whose name starts with `"<namespace>."`, in manifest declaration order. */
|
|
1783
|
+
function matchNamespace(config, namespace) {
|
|
1784
|
+
const prefix = `${namespace}.`;
|
|
1785
|
+
return (config.tasks ?? []).map(normalizeTaskEntry).reduce((names, entry) => {
|
|
1786
|
+
if (entry.name.startsWith(prefix) && !names.includes(entry.name)) names.push(entry.name);
|
|
1787
|
+
return names;
|
|
1788
|
+
}, []);
|
|
1789
|
+
}
|
|
1790
|
+
function runGroup(input) {
|
|
1791
|
+
const { print, namespace } = input;
|
|
1792
|
+
const names = matchNamespace(input.config, namespace);
|
|
1793
|
+
if (names.length === 0) return {
|
|
1794
|
+
status: "unknown",
|
|
1795
|
+
jobs: []
|
|
1796
|
+
};
|
|
1797
|
+
print(`holocron run ${namespace} — ${names.length} task(s)`);
|
|
1798
|
+
print("");
|
|
1799
|
+
const jobs = [];
|
|
1800
|
+
for (const name of names) {
|
|
1801
|
+
print(`▶ ${name}`);
|
|
1802
|
+
const r = runTask({
|
|
1803
|
+
print: (line) => print(` ${line}`),
|
|
1804
|
+
logger: input.logger,
|
|
1805
|
+
exec: input.exec,
|
|
1806
|
+
readFile: input.readFile,
|
|
1807
|
+
fileExists: input.fileExists,
|
|
1808
|
+
listDir: input.listDir,
|
|
1809
|
+
lookPath: input.lookPath,
|
|
1810
|
+
task: name,
|
|
1811
|
+
cwd: input.cwd,
|
|
1812
|
+
dryRun: input.dryRun ?? false,
|
|
1813
|
+
...input.filter ? { filter: input.filter } : {}
|
|
1814
|
+
});
|
|
1815
|
+
jobs.push({
|
|
1816
|
+
task: name,
|
|
1817
|
+
status: r.status,
|
|
1818
|
+
command: r.command,
|
|
1819
|
+
message: r.message
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
return {
|
|
1823
|
+
status: jobs.some((j) => j.status === "fail") ? "fail" : jobs.every((j) => j.status === "dry-run") ? "dry-run" : "ok",
|
|
1824
|
+
jobs
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
//#endregion
|
|
1764
1828
|
//#region src/astromech.ts
|
|
1765
1829
|
/**
|
|
1766
1830
|
* `createAstromech(options)` — the self-contained task runner.
|
|
@@ -1795,17 +1859,46 @@ function createAstromech(options) {
|
|
|
1795
1859
|
lookPath: options.lookPath ?? realLookPath
|
|
1796
1860
|
};
|
|
1797
1861
|
const items = () => (options.config?.tasks ?? []).map((i) => normalizeTaskEntry(i));
|
|
1862
|
+
/**
|
|
1863
|
+
* Task names this repo's manifest can actually run locally — shared by
|
|
1864
|
+
* {@link packageScripts} and by `holocron`'s own interactive task picker.
|
|
1865
|
+
* Deliberately independent of `syncScripts`/`holocronScript`/`hooks`:
|
|
1866
|
+
* those govern whether/how `package.json` scripts get *written*, not
|
|
1867
|
+
* which tasks exist to run. A repo with `syncScripts: false` (this repo
|
|
1868
|
+
* itself, among others) still has real runnable tasks — found live when
|
|
1869
|
+
* `packageScripts()`'s `{}` short-circuit silently emptied the picker too.
|
|
1870
|
+
*/
|
|
1871
|
+
const runnableTaskNames = () => {
|
|
1872
|
+
const out = [];
|
|
1873
|
+
for (const entry of items()) {
|
|
1874
|
+
const def = TASKS[entry.name];
|
|
1875
|
+
const hasNoLocalRunner = def?.local === null && !def.linterGroup && !def.jobs;
|
|
1876
|
+
if (entry.local === false || !KNOWN_TASKS.has(entry.name) || hasNoLocalRunner) continue;
|
|
1877
|
+
out.push(entry.name);
|
|
1878
|
+
}
|
|
1879
|
+
return out;
|
|
1880
|
+
};
|
|
1798
1881
|
return {
|
|
1799
|
-
run: (task, opts = {}) =>
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1882
|
+
run: (task, opts = {}) => {
|
|
1883
|
+
if (!TASKS[task] && options.config && matchNamespace(options.config, task).length > 0) return runGroup({
|
|
1884
|
+
...deps,
|
|
1885
|
+
cwd: options.cwd,
|
|
1886
|
+
config: options.config,
|
|
1887
|
+
namespace: task,
|
|
1888
|
+
dryRun: opts.dryRun ?? false,
|
|
1889
|
+
...opts.filter ? { filter: opts.filter } : {}
|
|
1890
|
+
});
|
|
1891
|
+
return runTask({
|
|
1892
|
+
...deps,
|
|
1893
|
+
task,
|
|
1894
|
+
cwd: options.cwd,
|
|
1895
|
+
...opts.job !== void 0 ? { job: opts.job } : {},
|
|
1896
|
+
passthrough: opts.passthrough ?? [],
|
|
1897
|
+
dryRun: opts.dryRun ?? false,
|
|
1898
|
+
required: opts.required ?? false,
|
|
1899
|
+
...opts.filter ? { filter: opts.filter } : {}
|
|
1900
|
+
});
|
|
1901
|
+
},
|
|
1809
1902
|
ci: (opts = {}) => runCi({
|
|
1810
1903
|
...deps,
|
|
1811
1904
|
cwd: options.cwd,
|
|
@@ -1850,16 +1943,12 @@ function createAstromech(options) {
|
|
|
1850
1943
|
const out = {};
|
|
1851
1944
|
if (!options.config || options.config.syncScripts === false) return out;
|
|
1852
1945
|
out.holocron = options.config.holocronScript ?? "holocron";
|
|
1853
|
-
for (const
|
|
1854
|
-
const def = TASKS[entry.name];
|
|
1855
|
-
const hasNoLocalRunner = def?.local === null && !def.linterGroup && !def.jobs;
|
|
1856
|
-
if (entry.local === false || !KNOWN_TASKS.has(entry.name) || hasNoLocalRunner) continue;
|
|
1857
|
-
out[entry.name] = `holocron run ${entry.name} --`;
|
|
1858
|
-
}
|
|
1946
|
+
for (const name of runnableTaskNames()) out[name] = `holocron run ${name} --`;
|
|
1859
1947
|
const hooks = options.config.hooks;
|
|
1860
1948
|
if (hooks === true || typeof hooks === "object" && hooks.prePush !== false) out.prepare = "husky";
|
|
1861
1949
|
return out;
|
|
1862
1950
|
},
|
|
1951
|
+
runnableTasks: () => runnableTaskNames(),
|
|
1863
1952
|
reusableTemplates: () => reusableTemplates(),
|
|
1864
1953
|
requiredChecks: () => requiredChecks(options.config ?? {}),
|
|
1865
1954
|
codecovConfig: (existing) => codecovConfig(options.cwd, existing),
|
|
@@ -1913,7 +2002,7 @@ function createTsconfig(options) {
|
|
|
1913
2002
|
* `channel` defaults to `main` (a stable release); set a prerelease identifier
|
|
1914
2003
|
* such as `alpha` to deploy those releases instead.
|
|
1915
2004
|
*
|
|
1916
|
-
* The shared `delivery.publish` workflow runs this (`holocron deploy
|
|
2005
|
+
* The shared `delivery.publish` workflow runs this (`holocron deploy on-release`)
|
|
1917
2006
|
* as its own `deploy` job after the release job, so a deploy is a separate,
|
|
1918
2007
|
* re-runnable check rather than a step buried in the release log. Each package
|
|
1919
2008
|
* with such a task deploys when the release is on its `channel` and changes a file under its paths: the package itself plus its
|
|
@@ -1935,16 +2024,20 @@ const DEPLOY_TASK = "delivery.deploy";
|
|
|
1935
2024
|
/** How long to wait for npm to serve the release's versions, and how often to ask. */
|
|
1936
2025
|
const WAIT_TIMEOUT_MS = 3e5;
|
|
1937
2026
|
const WAIT_INTERVAL_MS = 1e4;
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2027
|
+
function scanWorkspaceDir(dirPath, { readJson, listDirs }) {
|
|
2028
|
+
let folders;
|
|
2029
|
+
try {
|
|
2030
|
+
folders = listDirs(dirPath);
|
|
2031
|
+
} catch {
|
|
2032
|
+
return [];
|
|
2033
|
+
}
|
|
2034
|
+
return folders.flatMap((folder) => {
|
|
1942
2035
|
try {
|
|
1943
|
-
const manifest = readJson(join(
|
|
2036
|
+
const manifest = readJson(join(dirPath, folder, "package.json"));
|
|
1944
2037
|
if (!manifest.name) return [];
|
|
1945
2038
|
return [{
|
|
1946
2039
|
name: manifest.name,
|
|
1947
|
-
dir: join(
|
|
2040
|
+
dir: join(dirPath, folder),
|
|
1948
2041
|
version: manifest.version,
|
|
1949
2042
|
dependencies: manifest.dependencies ?? {}
|
|
1950
2043
|
}];
|
|
@@ -1953,6 +2046,14 @@ function workspacePackages(repoRoot, { readJson, listDirs }) {
|
|
|
1953
2046
|
}
|
|
1954
2047
|
});
|
|
1955
2048
|
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Every package under `<repoRoot>/packages/*` and `<repoRoot>/plugins/*`
|
|
2051
|
+
* with a readable, named `package.json` — `holocron`'s own layout splits
|
|
2052
|
+
* vendor plugin packages into `plugins/*`, sibling to `packages/*`.
|
|
2053
|
+
*/
|
|
2054
|
+
function workspacePackages(repoRoot, tree) {
|
|
2055
|
+
return [...scanWorkspaceDir(join(repoRoot, "packages"), tree), ...scanWorkspaceDir(join(repoRoot, "plugins"), tree)];
|
|
2056
|
+
}
|
|
1956
2057
|
/** The `workspace:*` dependencies of `pkg`, as workspace packages (with their current version). */
|
|
1957
2058
|
function workspaceDependencies(pkg, all) {
|
|
1958
2059
|
const names = new Set(Object.entries(pkg.dependencies).filter(([, spec]) => String(spec).startsWith("workspace:")).map(([name]) => name));
|
|
@@ -2149,4 +2250,4 @@ async function deployOnRelease(options, { run = defaultRun, readJson = defaultRe
|
|
|
2149
2250
|
};
|
|
2150
2251
|
}
|
|
2151
2252
|
//#endregion
|
|
2152
|
-
export { CI_ORDER, KNOWN_TASKS, KNOWN_WORKFLOWS, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, createTsconfig, deployOnRelease, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, reusableTemplates, runCi, runTask };
|
|
2253
|
+
export { CI_ORDER, KNOWN_TASKS, KNOWN_WORKFLOWS, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, createTsconfig, deployOnRelease, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, matchNamespace, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, reusableTemplates, runCi, runGroup, runTask };
|
|
@@ -58,11 +58,11 @@ interface TasksConfig {
|
|
|
58
58
|
/**
|
|
59
59
|
* Required status-check contexts not backed by a task — codecov gates,
|
|
60
60
|
* a bundle-build check, … Appended to `astromech.requiredChecks()` after the
|
|
61
|
-
* `required`-task contexts. (`DCO` is prepended by `holocron setup` itself.)
|
|
61
|
+
* `required`-task contexts. (`DCO` is prepended by `holocron repo setup` itself.)
|
|
62
62
|
*/
|
|
63
63
|
extraRequiredChecks?: string[];
|
|
64
64
|
/**
|
|
65
|
-
* Git hooks `holocron setup` installs. `true` / `{ prePush: true }` writes
|
|
65
|
+
* Git hooks `holocron repo setup` installs. `true` / `{ prePush: true }` writes
|
|
66
66
|
* `.husky/pre-push` (runs `holocron run ci`); `false` / `{ prePush: false }`
|
|
67
67
|
* opts out. Omitted → on for `protection: "strict"` repos, off otherwise.
|
|
68
68
|
* When enabled, `packageScripts()` also emits `prepare: "husky"`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/astromech",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.133",
|
|
4
4
|
"description": "The Holocron task runner — one task manifest drives `holocron run`, `holocron run 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": "5.0.0-alpha.
|
|
40
|
+
"@theholocron/datapad": "5.0.0-alpha.133"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@theholocron/eslint-config": "^8.7.1",
|
|
@@ -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": "5.0.0-alpha.
|
|
56
|
+
"@theholocron/rollup-plugin-transform-template": "5.0.0-alpha.133"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=22"
|