@theholocron/astromech 5.0.0-alpha.6 → 5.0.0-alpha.61
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 +22 -22
- package/dist/index.d.mts +153 -1
- package/dist/index.mjs +342 -14
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -53,28 +53,28 @@ what it actually runs, and why. Tool names never appear in
|
|
|
53
53
|
`holocron.config.ts` — they're an implementation detail this table
|
|
54
54
|
documents, not a naming convention repos need to follow.
|
|
55
55
|
|
|
56
|
-
| Task | Runs
|
|
57
|
-
| ---------------------------------------- |
|
|
58
|
-
| `verification.unitTests` | vitest
|
|
59
|
-
| `verification.typeSafety` | tsc
|
|
60
|
-
| `verification.performance` | Lighthouse CI
|
|
61
|
-
| `sourceQuality.staticAnalysis` | eslint, actionlint, git-merge-conflict-markers
|
|
62
|
-
| `sourceQuality.formatting` | prettier, editorconfig-checker, markdownlint-cli2
|
|
63
|
-
| `sourceQuality.structuredDataValidation` | yamllint
|
|
64
|
-
| `sourceQuality.deadCodeAnalysis` | knip
|
|
65
|
-
| `security.secretDetection` | gitleaks
|
|
66
|
-
| `security.codeScanning` | CodeQL
|
|
67
|
-
| `security.dependencyReview` | GitHub's native Dependabot alerts/graph
|
|
68
|
-
| `delivery.build` | tsdown / vite / rollup / tsc, detected from the repo's own config file
|
|
69
|
-
| `delivery.publish` | semantic-release
|
|
70
|
-
| `delivery.deploy` | Cloudflare Pages / Vercel
|
|
71
|
-
| `delivery.bundleSize` | bundle-size upload to Codecov
|
|
72
|
-
| `platform.repoSync` | `holocron sync`
|
|
73
|
-
| `platform.commitStandards` | commitlint
|
|
74
|
-
| `platform.repoValidation` | `scripts/validate-adrs.mjs`, `scripts/validate-registry.mjs`, `scripts/validate-docs-presence.mjs`
|
|
75
|
-
| `knowledge.wiki` | Fern
|
|
76
|
-
| `knowledge.docs` | Astro build
|
|
77
|
-
| `knowledge.components` | Storybook build
|
|
56
|
+
| Task | Runs | Notes |
|
|
57
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `verification.unitTests` | vitest | carries the `--coverage` org default |
|
|
59
|
+
| `verification.typeSafety` | tsc | `tsc --noEmit` |
|
|
60
|
+
| `verification.performance` | Lighthouse CI | only runs with a `lighthouse.config.*` present |
|
|
61
|
+
| `sourceQuality.staticAnalysis` | eslint, actionlint, git-merge-conflict-markers | a `linterGroup` — see below |
|
|
62
|
+
| `sourceQuality.formatting` | prettier, editorconfig-checker, markdownlint-cli2 | a `linterGroup` |
|
|
63
|
+
| `sourceQuality.structuredDataValidation` | yamllint | |
|
|
64
|
+
| `sourceQuality.deadCodeAnalysis` | knip | |
|
|
65
|
+
| `security.secretDetection` | gitleaks | |
|
|
66
|
+
| `security.codeScanning` | CodeQL | no local equivalent — CI only |
|
|
67
|
+
| `security.dependencyReview` | GitHub's native Dependabot alerts/graph | a capability-model method (`Source.enableVulnerabilityAlerts()`), not a task |
|
|
68
|
+
| `delivery.build` | tsdown / vite / rollup / tsc, detected from the repo's own config file, or `package.json`'s own `dependencies`/`devDependencies` if that config file isn't there | |
|
|
69
|
+
| `delivery.publish` | semantic-release | no local equivalent — CI only; carries `preview` (npm dist-tags) |
|
|
70
|
+
| `delivery.deploy` | Cloudflare Pages / Vercel | no local equivalent — CI only; carries `preview` |
|
|
71
|
+
| `delivery.bundleSize` | bundle-size upload to Codecov | no local equivalent — CI only |
|
|
72
|
+
| `platform.repoSync` | `holocron sync` | keeps generated files current |
|
|
73
|
+
| `platform.commitStandards` | commitlint | no local equivalent — enforced by the `commit-msg` hook locally, over the PR's commit range in CI |
|
|
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
|
+
| `knowledge.wiki` | Fern | publishes `docs/wiki/*.md` — not a sync, a publish; carries `preview` |
|
|
76
|
+
| `knowledge.docs` | Astro build | the docs site itself; carries `preview` (defaults on) |
|
|
77
|
+
| `knowledge.components` | Storybook build | a browsable component catalog — "Storybook" is the tool, not the intent; carries `preview` (defaults on) |
|
|
78
78
|
|
|
79
79
|
**`preview` is a cross-cutting feature, not a namespace.** npm has staging
|
|
80
80
|
dist-tags, Cloudflare/Vercel do per-PR preview deploys, Fern previews
|
package/dist/index.d.mts
CHANGED
|
@@ -221,6 +221,16 @@ declare function normalizeWorkflowWith(raw: Record<string, unknown>): Record<str
|
|
|
221
221
|
*/
|
|
222
222
|
declare function deriveDeployPaths(raw: Record<string, unknown>): string[];
|
|
223
223
|
//#endregion
|
|
224
|
+
//#region src/turbo.d.ts
|
|
225
|
+
interface EnsureRootWorkspaceMemberResult {
|
|
226
|
+
content: string;
|
|
227
|
+
changed: boolean;
|
|
228
|
+
}
|
|
229
|
+
interface EnsureTurboDependencyResult {
|
|
230
|
+
content: string;
|
|
231
|
+
changed: boolean;
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
224
234
|
//#region src/astromech.d.ts
|
|
225
235
|
interface AstromechOptions {
|
|
226
236
|
/** Repo root. */
|
|
@@ -315,6 +325,27 @@ interface Astromech {
|
|
|
315
325
|
* config (nothing to write). See {@link TurboTaskConfig} in `registry.ts`.
|
|
316
326
|
*/
|
|
317
327
|
turboConfig(): string | null;
|
|
328
|
+
/**
|
|
329
|
+
* Fixes #692: whenever {@link turboConfig} isn't `null`, this repo's root
|
|
330
|
+
* package needs to actually be a workspace member for the tasks it fans
|
|
331
|
+
* out — otherwise a root that's genuinely a directly-buildable package
|
|
332
|
+
* (not just an orchestrator) silently vanishes from `turbo run <task>`.
|
|
333
|
+
* Pass `pnpm-workspace.yaml`'s current content (or `""` if none exists —
|
|
334
|
+
* a no-op either way, since no `packages:` key means single-package mode
|
|
335
|
+
* already) and root `package.json`'s own `scripts` keys. `changed: false`
|
|
336
|
+
* means don't write anything — the file is already correct.
|
|
337
|
+
*/
|
|
338
|
+
ensureRootWorkspaceMember(workspaceYaml: string, rootScripts: readonly string[]): EnsureRootWorkspaceMemberResult;
|
|
339
|
+
/**
|
|
340
|
+
* `turboConfig()` writes a `turbo.json` but never touches `package.json`
|
|
341
|
+
* — a repo with no local `turbo` devDependency falls back to whatever
|
|
342
|
+
* (if anything) is globally on `PATH`, per `run.ts`'s `resolveBin()`.
|
|
343
|
+
* Pass root `package.json`'s raw content and `pnpm-workspace.yaml`'s
|
|
344
|
+
* current content (checked for an existing `turbo` catalog entry, which
|
|
345
|
+
* takes precedence over the hardcoded fallback version). `changed: false`
|
|
346
|
+
* means don't write anything — `devDependencies.turbo` already exists.
|
|
347
|
+
*/
|
|
348
|
+
ensureTurboDependency(packageJson: string, workspaceYaml: string): EnsureTurboDependencyResult;
|
|
318
349
|
}
|
|
319
350
|
declare function createAstromech(options: AstromechOptions): Astromech;
|
|
320
351
|
//#endregion
|
|
@@ -546,6 +577,58 @@ declare const CI_ORDER: string[];
|
|
|
546
577
|
/** Ordered (task contexts in {@link CI_ORDER}, then extras), de-duplicated. */
|
|
547
578
|
declare function requiredChecks(config: TasksConfig): string[];
|
|
548
579
|
//#endregion
|
|
580
|
+
//#region src/resolver.d.ts
|
|
581
|
+
/**
|
|
582
|
+
* Resolves the absolute path to a Bucket A tool's shared `@theholocron/*-config`
|
|
583
|
+
* entry point, installed in the consuming repo's own `node_modules` — and the
|
|
584
|
+
* CLI flag(s) to hand it via (`--config`, `--extends`, …). `run.ts` splices the
|
|
585
|
+
* result into the command it builds for a tool/detect runner or a linter-group
|
|
586
|
+
* entry, right after the tool's own args.
|
|
587
|
+
*
|
|
588
|
+
* Every mapped package's resolved entry point already carries a ready-to-use
|
|
589
|
+
* default export (a plain re-export for `prettier`/`commitlint`; an invoked,
|
|
590
|
+
* zero-arg preset for `eslint`/`vitest`/`tsdown` — verified end-to-end against
|
|
591
|
+
* each tool's real `--config`/`--extends` loader, not just the file's shape).
|
|
592
|
+
* A repo doesn't need this package installed at all — every lookup degrades to
|
|
593
|
+
* `[]` (no flag added, the tool falls back to its own auto-discovery of a
|
|
594
|
+
* local file, unchanged from today) rather than throwing.
|
|
595
|
+
*
|
|
596
|
+
* **Local file wins.** A `<tool>.config.*` sitting in `cwd` (whether it's
|
|
597
|
+
* pure boilerplate or has real per-package customization on top of the
|
|
598
|
+
* shared bundle, e.g. `mergeConfig(base, { test: { coverage: { exclude }
|
|
599
|
+
* } })`) always skips the splice, even when the shared package is also
|
|
600
|
+
* installed — an explicit `--config <shared path>` flag beats a tool's own
|
|
601
|
+
* auto-discovery every time, so splicing it unconditionally would silently
|
|
602
|
+
* discard that customization the file is still sitting right there on disk.
|
|
603
|
+
* The shared bundle is only ever the fallback for a package with *no* local
|
|
604
|
+
* file at all — that's still the "delete the file to go fully shared" path
|
|
605
|
+
* this was designed for (#676); it just isn't unconditional (#749).
|
|
606
|
+
*
|
|
607
|
+
* `semantic-release`, `devmoji`, `editorconfig-checker`, and `knip` are
|
|
608
|
+
* deliberately absent — `semantic-release-config`'s `defineConfig()` needs
|
|
609
|
+
* real per-repo data (branches, npm options) a static `--extends <path>` can't
|
|
610
|
+
* carry; `devmoji` runs through a git hook template, not `holocron run
|
|
611
|
+
* <task>`; `editorconfig-checker` has no shared-config package yet; `knip` is
|
|
612
|
+
* repo-specific by nature, never a shared-config candidate. See
|
|
613
|
+
* `.notes/tech-config-resolution.spec.md` (#676).
|
|
614
|
+
*/
|
|
615
|
+
interface ResolverDeps {
|
|
616
|
+
readFile: (path: string) => string;
|
|
617
|
+
fileExists: (path: string) => boolean;
|
|
618
|
+
}
|
|
619
|
+
/** Every tool name the resolver knows how to point at a shared config. */
|
|
620
|
+
declare const RESOLVABLE_TOOLS: ReadonlySet<string>;
|
|
621
|
+
/**
|
|
622
|
+
* `<flag> <absolute path>` for `tool`, resolved against `cwd`'s
|
|
623
|
+
* `node_modules` — or `[]` when the tool isn't mapped, `cwd` already has its
|
|
624
|
+
* own local config file for it (#749 — local always wins, customized or
|
|
625
|
+
* not), the shared package isn't installed, its `package.json` doesn't
|
|
626
|
+
* declare the export subpath this needs, or the resolved file doesn't
|
|
627
|
+
* actually exist on disk (a stale install, or a package version that
|
|
628
|
+
* predates the export existing).
|
|
629
|
+
*/
|
|
630
|
+
declare function resolveToolConfig(tool: string, cwd: string, deps: ResolverDeps): string[];
|
|
631
|
+
//#endregion
|
|
549
632
|
//#region src/reusable.d.ts
|
|
550
633
|
/**
|
|
551
634
|
* The **reusable** GitHub Actions surface pushed to `theholocron/.github` by
|
|
@@ -573,4 +656,73 @@ declare const WORKFLOW_TEMPLATE_PROPERTIES: Record<string, string>;
|
|
|
573
656
|
*/
|
|
574
657
|
declare function reusableTemplates(): Map<string, string>;
|
|
575
658
|
//#endregion
|
|
576
|
-
|
|
659
|
+
//#region src/tsconfig.d.ts
|
|
660
|
+
/**
|
|
661
|
+
* `tsconfig.json` generation — Bucket B (config-resolution workstream, #676):
|
|
662
|
+
* the file must stay committed (the TypeScript language server discovers it
|
|
663
|
+
* directly from disk, no `--config` override mechanism to hand it a path
|
|
664
|
+
* instead), but its content is fully derivable, so it's generated rather
|
|
665
|
+
* than hand-authored — the same category as `.editorconfig`
|
|
666
|
+
* ({@link "./templates/configs/editorconfig/create-config.js" createConfig})
|
|
667
|
+
* and {@link codecovConfig}.
|
|
668
|
+
*
|
|
669
|
+
* Surveyed every package-level `tsconfig.json` across the org (`holocron`,
|
|
670
|
+
* `clients`, `utils` — 20+ packages). `extends: "@theholocron/tsconfig/
|
|
671
|
+
* node-lts"`, `compilerOptions.baseUrl: "./"`, `compilerOptions.outDir:
|
|
672
|
+
* "./dist"`, `include: ["src/**\/*.ts"]`, and `exclude: ["node_modules",
|
|
673
|
+
* "dist"]` are uniform in every one — safe defaults here. `paths` (a `@/*`
|
|
674
|
+
* → `./src/*` import alias) is a real per-package choice, present in
|
|
675
|
+
* roughly 40% of packages checked — an opt-in parameter, not a default.
|
|
676
|
+
*
|
|
677
|
+
* Two fields checked and deliberately *not* absorbed as defaults, unlike
|
|
678
|
+
* `eslint-config`'s `tsconfigRootDir`/`settings.node` (which turned out
|
|
679
|
+
* redundant with the shared package's own behavior, `configs`#461):
|
|
680
|
+
* `compilerOptions.module`/`moduleResolution` (only `@theholocron/cli`
|
|
681
|
+
* overrides these, to `esnext`/`bundler` — a genuine deviation from the
|
|
682
|
+
* shared `node-lts` preset's `nodenext`/`nodenext`, needed for its bundler
|
|
683
|
+
* tooling, not something every package should inherit) and
|
|
684
|
+
* `compilerOptions.rootDir` (present in exactly two packages, always the
|
|
685
|
+
* same value `./src` that `include` already implies — an unnecessary
|
|
686
|
+
* override wherever it appears, not a pattern worth generalizing).
|
|
687
|
+
*
|
|
688
|
+
* Monorepo *root* `tsconfig.json` (a TS project-references "solution
|
|
689
|
+
* file" — `holocron`'s own root is `{ files: [], references: [...] }`,
|
|
690
|
+
* listing which packages to build) is a structurally different, genuinely
|
|
691
|
+
* per-repo document — out of scope here, same as Bucket C content.
|
|
692
|
+
*
|
|
693
|
+
* Not yet wired into `holocron setup`'s per-package write loop (every
|
|
694
|
+
* other Bucket B file there is a single repo-root file, safely
|
|
695
|
+
* overwritten every run — `tsconfig.json` is per-package, and most
|
|
696
|
+
* packages' files still carry real hand-authored content today, not yet
|
|
697
|
+
* migrated to a fully-generated state). That per-package iteration +
|
|
698
|
+
* safe-migration design is `#680`'s job, not duplicated here — this is
|
|
699
|
+
* the generator itself, ready for it to call.
|
|
700
|
+
*/
|
|
701
|
+
interface TsconfigOptions {
|
|
702
|
+
/** Human-readable name for the `display` field — the package's own name is the usual choice. */
|
|
703
|
+
display: string;
|
|
704
|
+
/**
|
|
705
|
+
* `@theholocron/tsconfig` variant. Defaults to `"node-lts"` — every
|
|
706
|
+
* checked package uses it; the other three (`astro`, `nextjs`, `react`)
|
|
707
|
+
* exist for a template repo's app-shaped packages, not the plain
|
|
708
|
+
* Node.js library packages this org's currently-migrated repos ship.
|
|
709
|
+
*/
|
|
710
|
+
variant?: "astro" | "nextjs" | "node-lts" | "react";
|
|
711
|
+
/**
|
|
712
|
+
* Add a `@/*` → `./src/*` path alias. A real per-package choice, not a
|
|
713
|
+
* default — roughly 40% of packages checked use one, the rest don't.
|
|
714
|
+
*/
|
|
715
|
+
paths?: boolean;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* A package-level `tsconfig.json` — the uniform shape confirmed across
|
|
719
|
+
* every package checked, parameterized only by the two fields that
|
|
720
|
+
* genuinely vary (`display`, `paths`). No scaffold/workflow header (matches
|
|
721
|
+
* `.alexrc.json`'s existing precedent for a strict-JSON Bucket B file —
|
|
722
|
+
* JSON has no comment syntax to carry one, and TypeScript's own tolerance
|
|
723
|
+
* for JSONC comments in `tsconfig.json` isn't worth relying on for a file
|
|
724
|
+
* this thin).
|
|
725
|
+
*/
|
|
726
|
+
declare function createTsconfig(options: TsconfigOptions): string;
|
|
727
|
+
//#endregion
|
|
728
|
+
export { type Astromech, type AstromechOptions, CI_ORDER, type CiJobReport, type CiOptions, type CiReport, type ExecFn, type JobDef, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, type LinterDef, type LocalRunner, type OrgContext, type PreviewConfig, RESOLVABLE_TOOLS, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, type ResolverDeps, 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, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, resolveLinters, resolveToolConfig, reusableTemplates, runCi, runTask };
|
package/dist/index.mjs
CHANGED
|
@@ -327,6 +327,141 @@ function resolveLinters(opts) {
|
|
|
327
327
|
}));
|
|
328
328
|
}
|
|
329
329
|
//#endregion
|
|
330
|
+
//#region src/resolver.ts
|
|
331
|
+
/**
|
|
332
|
+
* `library` is every mapped tool's org-wide default variant today — every
|
|
333
|
+
* currently-migrated repo (`holocron`, `clients`, `utils`, `configs`,
|
|
334
|
+
* `themes`, `observability`, `skills`) is a publishable library package, and
|
|
335
|
+
* `eslint-config`/`vitest-config`/`tsdown-config` all name their
|
|
336
|
+
* general-purpose preset exactly that. Picking a different variant per repo
|
|
337
|
+
* (an app-type template, say) needs new `holocron.config.ts` surface this
|
|
338
|
+
* doesn't add yet — deliberately out of scope until a real consumer needs it.
|
|
339
|
+
*/
|
|
340
|
+
const TOOL_CONFIGS = {
|
|
341
|
+
eslint: {
|
|
342
|
+
package: "@theholocron/eslint-config",
|
|
343
|
+
exportPath: "./bundles/library",
|
|
344
|
+
flag: ["--config"]
|
|
345
|
+
},
|
|
346
|
+
prettier: {
|
|
347
|
+
package: "@theholocron/prettier-config",
|
|
348
|
+
exportPath: ".",
|
|
349
|
+
flag: ["--config"]
|
|
350
|
+
},
|
|
351
|
+
vitest: {
|
|
352
|
+
package: "@theholocron/vitest-config",
|
|
353
|
+
exportPath: "./bundles/library",
|
|
354
|
+
flag: ["--config"]
|
|
355
|
+
},
|
|
356
|
+
tsdown: {
|
|
357
|
+
package: "@theholocron/tsdown-config",
|
|
358
|
+
exportPath: "./presets/library",
|
|
359
|
+
flag: ["--config"]
|
|
360
|
+
},
|
|
361
|
+
commitlint: {
|
|
362
|
+
package: "@theholocron/commitlint-config",
|
|
363
|
+
exportPath: ".",
|
|
364
|
+
flag: ["--config"]
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Every filename a tool's own auto-discovery would pick up from `cwd` —
|
|
369
|
+
* same extension set `registry.ts`'s `turbo.inputs`/`local.detect` entries
|
|
370
|
+
* already use for these tools, kept in sync by hand (no shared source of
|
|
371
|
+
* truth between the two today). Presence of any one of these means the
|
|
372
|
+
* package has its own local config — customized or not, `resolveToolConfig`
|
|
373
|
+
* can't tell the difference from the filename alone, so it defers to it
|
|
374
|
+
* either way (#749).
|
|
375
|
+
*/
|
|
376
|
+
const LOCAL_CONFIG_FILES = {
|
|
377
|
+
eslint: [
|
|
378
|
+
"eslint.config.ts",
|
|
379
|
+
"eslint.config.js",
|
|
380
|
+
"eslint.config.mjs",
|
|
381
|
+
"eslint.config.cjs"
|
|
382
|
+
],
|
|
383
|
+
prettier: [
|
|
384
|
+
"prettier.config.ts",
|
|
385
|
+
"prettier.config.js",
|
|
386
|
+
"prettier.config.mjs",
|
|
387
|
+
"prettier.config.cjs"
|
|
388
|
+
],
|
|
389
|
+
vitest: [
|
|
390
|
+
"vitest.config.ts",
|
|
391
|
+
"vitest.config.js",
|
|
392
|
+
"vitest.config.mjs"
|
|
393
|
+
],
|
|
394
|
+
tsdown: [
|
|
395
|
+
"tsdown.config.ts",
|
|
396
|
+
"tsdown.config.js",
|
|
397
|
+
"tsdown.config.mjs",
|
|
398
|
+
"tsdown.config.cjs"
|
|
399
|
+
],
|
|
400
|
+
commitlint: [
|
|
401
|
+
"commitlint.config.ts",
|
|
402
|
+
"commitlint.config.js",
|
|
403
|
+
"commitlint.config.mjs",
|
|
404
|
+
"commitlint.config.cjs"
|
|
405
|
+
]
|
|
406
|
+
};
|
|
407
|
+
/** Every tool name the resolver knows how to point at a shared config. */
|
|
408
|
+
const RESOLVABLE_TOOLS = new Set(Object.keys(TOOL_CONFIGS));
|
|
409
|
+
/**
|
|
410
|
+
* `<flag> <absolute path>` for `tool`, resolved against `cwd`'s
|
|
411
|
+
* `node_modules` — or `[]` when the tool isn't mapped, `cwd` already has its
|
|
412
|
+
* own local config file for it (#749 — local always wins, customized or
|
|
413
|
+
* not), the shared package isn't installed, its `package.json` doesn't
|
|
414
|
+
* declare the export subpath this needs, or the resolved file doesn't
|
|
415
|
+
* actually exist on disk (a stale install, or a package version that
|
|
416
|
+
* predates the export existing).
|
|
417
|
+
*/
|
|
418
|
+
function resolveToolConfig(tool, cwd, deps) {
|
|
419
|
+
const spec = TOOL_CONFIGS[tool];
|
|
420
|
+
if (!spec) return [];
|
|
421
|
+
if ((LOCAL_CONFIG_FILES[tool] ?? []).some((name) => deps.fileExists(joinPath(cwd, name)))) return [];
|
|
422
|
+
const pkgDir = joinPath(cwd, "node_modules", ...spec.package.split("/"));
|
|
423
|
+
const pkgJsonPath = joinPath(pkgDir, "package.json");
|
|
424
|
+
if (!deps.fileExists(pkgJsonPath)) return [];
|
|
425
|
+
let pkg;
|
|
426
|
+
try {
|
|
427
|
+
pkg = JSON.parse(deps.readFile(pkgJsonPath));
|
|
428
|
+
} catch {
|
|
429
|
+
return [];
|
|
430
|
+
}
|
|
431
|
+
const relative = resolveExportEntry(pkg.exports?.[spec.exportPath]);
|
|
432
|
+
if (!relative) return [];
|
|
433
|
+
const absolute = joinPath(pkgDir, relative.replace(/^\.\//, ""));
|
|
434
|
+
if (!deps.fileExists(absolute)) return [];
|
|
435
|
+
return [...spec.flag, absolute];
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Unwrap a `package.json#exports` subpath entry down to its file path —
|
|
439
|
+
* either a bare string, or an object with condition keys (`import`,
|
|
440
|
+
* `default`, `require`, …) that may themselves nest one level (`{ import: {
|
|
441
|
+
* types, default } }`, the shape every `@theholocron/*-config` package
|
|
442
|
+
* actually uses). Prefers `import` (this org ships ESM), then `default`,
|
|
443
|
+
* then `require` as a last resort.
|
|
444
|
+
*/
|
|
445
|
+
function resolveExportEntry(entry) {
|
|
446
|
+
if (typeof entry === "string") return entry;
|
|
447
|
+
if (entry === null || typeof entry !== "object") return void 0;
|
|
448
|
+
const conditions = entry;
|
|
449
|
+
const value = conditions.import ?? conditions.default ?? conditions.require;
|
|
450
|
+
if (typeof value === "string") return value;
|
|
451
|
+
if (typeof value === "object" && value !== null) return resolveExportEntry(value);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* A minimal, POSIX-safe `path.join` — avoids importing `node:path` just for
|
|
455
|
+
* this. Every path in play here is either already POSIX (a `package.json`
|
|
456
|
+
* `exports` value, always forward-slashed regardless of platform) or built
|
|
457
|
+
* from platform-neutral segments (`cwd`, `"node_modules"`, a scoped package
|
|
458
|
+
* name split on `/`), so a manual join is safe and keeps this module
|
|
459
|
+
* dependency-free like the rest of `run.ts`'s helpers.
|
|
460
|
+
*/
|
|
461
|
+
function joinPath(...segments) {
|
|
462
|
+
return segments.map((s, i) => i === 0 ? s.replace(/\/+$/, "") : s.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
330
465
|
//#region src/run.ts
|
|
331
466
|
/**
|
|
332
467
|
* `holocron run <task> [job] [-- <passthrough>]` — run a registry task (or one
|
|
@@ -445,11 +580,16 @@ function runTask(input) {
|
|
|
445
580
|
def.local.command,
|
|
446
581
|
...passthrough
|
|
447
582
|
]);
|
|
448
|
-
const runner = resolveRunner(def.local, cwd, listDir);
|
|
583
|
+
const runner = resolveRunner(def.local, cwd, listDir, readFile);
|
|
449
584
|
if (runner) {
|
|
450
585
|
const flags = (def.flags?.[runner.tool] ?? []).filter((f) => !passthrough.includes(f));
|
|
586
|
+
const configFlags = resolveToolConfig(runner.tool, cwd, {
|
|
587
|
+
readFile,
|
|
588
|
+
fileExists
|
|
589
|
+
});
|
|
451
590
|
return run(resolveBin(cwd, runner.tool, fileExists), [
|
|
452
591
|
...runner.args,
|
|
592
|
+
...configFlags,
|
|
453
593
|
...flags,
|
|
454
594
|
...passthrough
|
|
455
595
|
]);
|
|
@@ -503,7 +643,7 @@ function runTask(input) {
|
|
|
503
643
|
* here, flag the rest, let CI enforce.
|
|
504
644
|
*/
|
|
505
645
|
function runUnit(input, label, local, run, passthrough) {
|
|
506
|
-
const { print, logger, listDir, lookPath, cwd } = input;
|
|
646
|
+
const { print, logger, listDir, lookPath, cwd, readFile } = input;
|
|
507
647
|
const skip = (msg) => {
|
|
508
648
|
print(input.required ? `✗ ${msg} (required)` : `· ${msg}`);
|
|
509
649
|
logger[input.required ? "warn" : "debug"]({
|
|
@@ -516,7 +656,7 @@ function runUnit(input, label, local, run, passthrough) {
|
|
|
516
656
|
};
|
|
517
657
|
};
|
|
518
658
|
if (local) {
|
|
519
|
-
const runner = resolveRunner(local, cwd, listDir);
|
|
659
|
+
const runner = resolveRunner(local, cwd, listDir, readFile);
|
|
520
660
|
if (!runner) return skip(`no ${label} runner for this repo`);
|
|
521
661
|
const found = lookPath(cwd, runner.tool);
|
|
522
662
|
if (!found) return skip(`${runner.tool} not installed locally for ${label} — enforced in CI`);
|
|
@@ -582,7 +722,7 @@ function runAllJobs(input, jobs, passthrough) {
|
|
|
582
722
|
* turbo/a package script would have already caught in steps 1–2.
|
|
583
723
|
*/
|
|
584
724
|
function runLinterGroup(input, names, passthrough) {
|
|
585
|
-
const { print, logger, listDir, lookPath, cwd, task } = input;
|
|
725
|
+
const { print, logger, listDir, lookPath, cwd, task, readFile, fileExists } = input;
|
|
586
726
|
const dryRun = input.dryRun ?? false;
|
|
587
727
|
const runOne = makeRunOne(input);
|
|
588
728
|
let rootFiles;
|
|
@@ -609,7 +749,15 @@ function runLinterGroup(input, names, passthrough) {
|
|
|
609
749
|
print(`! ${name} — ${bin} not on PATH${def.installHint ? `. ${def.installHint}` : ""} (enforced in CI)`);
|
|
610
750
|
continue;
|
|
611
751
|
}
|
|
612
|
-
|
|
752
|
+
const configFlags = resolveToolConfig(name, cwd, {
|
|
753
|
+
readFile,
|
|
754
|
+
fileExists
|
|
755
|
+
});
|
|
756
|
+
reports.push(runOne(found, [
|
|
757
|
+
...configFlags,
|
|
758
|
+
...def.localArgs ?? [],
|
|
759
|
+
...passthrough
|
|
760
|
+
]));
|
|
613
761
|
}
|
|
614
762
|
if (reports.length === 0) {
|
|
615
763
|
const required = input.required && anyHasLocalBin;
|
|
@@ -644,19 +792,53 @@ const mkRunner = (tool, args = []) => ({
|
|
|
644
792
|
});
|
|
645
793
|
/**
|
|
646
794
|
* Resolve `{ tool, args }` for a runner, applying `detect[]` against repo
|
|
647
|
-
* files
|
|
795
|
+
* files, falling back (#750) to each candidate's `tool` name against
|
|
796
|
+
* `package.json`'s own declared dependencies when neither matches by
|
|
797
|
+
* filename. Only called for `tool` / `detect` runners — the caller handles
|
|
648
798
|
* `command` runners itself, so `local.detect` is present whenever
|
|
649
799
|
* `local.tool` is not.
|
|
800
|
+
*
|
|
801
|
+
* The dependency fallback matters once a Bucket A migration (#680) deletes
|
|
802
|
+
* a build tool's own config file (`tsdown.config.ts`, `vite.config.ts`, …):
|
|
803
|
+
* that file was the *only* signal `detect[]` had to offer, so removing it
|
|
804
|
+
* used to make `delivery.build` report "no task for this repo" and silently
|
|
805
|
+
* skip the build, even though the tool is still very much in use — a
|
|
806
|
+
* package can't actually build with `tsdown` without it in
|
|
807
|
+
* `dependencies`/`devDependencies`, so that's a signal at least as reliable
|
|
808
|
+
* as the config file, and one deleting the file doesn't remove.
|
|
809
|
+
*
|
|
810
|
+
* **Per-candidate, not two full passes** (#750 follow-up — the first
|
|
811
|
+
* shipped version got this wrong): `delivery.build`'s own `detect[]` ends
|
|
812
|
+
* with `{ when: /^tsconfig\.json$/, tool: "tsc" }` as its last-resort
|
|
813
|
+
* fallback, and `tsconfig.json` exists in essentially every package
|
|
814
|
+
* regardless of build tool. A "check every candidate's filename, *then*
|
|
815
|
+
* check every candidate's dependency" two-pass design would let that
|
|
816
|
+
* always-present fallback file win over a real `tsdown`/`vite`/`rollup`
|
|
817
|
+
* dependency match, the moment the earlier candidates' own config files are
|
|
818
|
+
* gone — exactly backwards. Checking `(file match) || (dependency match)`
|
|
819
|
+
* together for each candidate before moving to the next preserves the
|
|
820
|
+
* intended precedence: a real signal for an *earlier* candidate, by either
|
|
821
|
+
* means, still wins over a later candidate's mere filename presence.
|
|
650
822
|
*/
|
|
651
|
-
function resolveRunner(local, cwd, listDir) {
|
|
823
|
+
function resolveRunner(local, cwd, listDir, readFile) {
|
|
652
824
|
if (local.tool) return mkRunner(local.tool, local.args);
|
|
653
825
|
let files;
|
|
654
826
|
try {
|
|
655
827
|
files = listDir(cwd);
|
|
656
828
|
} catch {
|
|
657
|
-
|
|
829
|
+
files = [];
|
|
830
|
+
}
|
|
831
|
+
const deps = packageJsonDependencies(cwd, readFile);
|
|
832
|
+
for (const candidate of local.detect) if (files.some((f) => candidate.when.test(f)) || deps.has(candidate.tool)) return mkRunner(candidate.tool, candidate.args);
|
|
833
|
+
}
|
|
834
|
+
/** Every key from `package.json`'s `dependencies` + `devDependencies`, or an empty set on any read/parse failure. */
|
|
835
|
+
function packageJsonDependencies(cwd, readFile) {
|
|
836
|
+
try {
|
|
837
|
+
const pkg = JSON.parse(readFile(join(cwd, "package.json")));
|
|
838
|
+
return /* @__PURE__ */ new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]);
|
|
839
|
+
} catch {
|
|
840
|
+
return /* @__PURE__ */ new Set();
|
|
658
841
|
}
|
|
659
|
-
for (const candidate of local.detect) if (files.some((f) => candidate.when.test(f))) return mkRunner(candidate.tool, candidate.args);
|
|
660
842
|
}
|
|
661
843
|
function resolveBin(cwd, tool, fileExists) {
|
|
662
844
|
const local = join(cwd, "node_modules", ".bin", tool);
|
|
@@ -718,7 +900,7 @@ const WORKFLOW_TEMPLATES = {
|
|
|
718
900
|
"platform.repoValidation": "name: Platform\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n repo-validation:\n name: Repo Validation\n uses: theholocron/.github/.github/workflows/platform.repoValidation.yml@main\n secrets: inherit\n",
|
|
719
901
|
"knowledge.wiki": "name: Knowledge\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\nconcurrency:\n group: ${{ github.event_name == 'pull_request' && format('wiki-preview-{0}', github.event.pull_request.number) || 'wiki' }}\n cancel-in-progress: ${{ github.event_name == 'pull_request' }}\n\npermissions:\n contents: read\n deployments: write\n\njobs:\n publish:\n name: Publish\n if: ${{ github.event_name != 'pull_request' }}\n uses: theholocron/.github/.github/workflows/knowledge.wiki.yml@main\n secrets: inherit\n\n preview:\n name: Preview\n if: ${{ github.event_name == 'pull_request' }}\n uses: theholocron/.github/.github/workflows/knowledge.wiki.yml@main\n with:\n preview: true\n preview-id: pr-${{ github.event.pull_request.number }}\n secrets: inherit\n",
|
|
720
902
|
preview: "name: Preview\n\non: # yamllint disable-line rule:truthy\n pull_request:\n branches: [main]\n\nconcurrency:\n group: preview-${{ github.event.pull_request.number }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n deployments: write\n pull-requests: write\n\njobs:\n preview:\n name: Preview\n uses: theholocron/.github/.github/workflows/preview.yml@main\n secrets: inherit\n",
|
|
721
|
-
review: "name:
|
|
903
|
+
review: "name: Source Quality\n\non: # yamllint disable-line rule:truthy\n pull_request:\n\nconcurrency:\n group: review-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n checks: write\n pull-requests: write\n\njobs:\n review:\n name: Annotations\n uses: theholocron/.github/.github/workflows/review.yml@main\n secrets: inherit\n",
|
|
722
904
|
stale: "name: Stale\n\non: # yamllint disable-line rule:truthy\n schedule:\n - cron: \"30 1 * * *\"\n\npermissions:\n contents: write\n issues: write\n pull-requests: write\n\njobs:\n stale:\n uses: theholocron/.github/.github/workflows/stale.yml@main\n with:\n exempt-issue-labels: \"in-progress,wip\"\n exempt-all-issue-milestones: true\n exempt-all-issue-projects: true\n exempt-all-pr-projects: true\n secrets: inherit\n",
|
|
723
905
|
greetings: "name: Platform\n\non: # yamllint disable-line rule:truthy\n pull_request:\n issues:\n\npermissions:\n issues: write\n pull-requests: write\n\njobs:\n greetings:\n name: Greetings\n uses: theholocron/.github/.github/workflows/greetings.yml@main\n secrets: inherit\n",
|
|
724
906
|
dependencies: "name: Platform\n\non: # yamllint disable-line rule:truthy\n pull_request:\n\npermissions:\n contents: write\n pull-requests: write\n\njobs:\n dependencies:\n name: Dependencies\n uses: theholocron/.github/.github/workflows/dependencies.yml@main\n secrets: inherit\n",
|
|
@@ -1191,7 +1373,7 @@ function requiredChecks(config) {
|
|
|
1191
1373
|
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";
|
|
1192
1374
|
//#endregion
|
|
1193
1375
|
//#region src/templates/reusable/actions/holocron.yml
|
|
1194
|
-
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-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 `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\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 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 # 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 node \"$cli\" \"$@\"\n";
|
|
1376
|
+
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-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 `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 # 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";
|
|
1195
1377
|
//#endregion
|
|
1196
1378
|
//#region src/templates/reusable/actions/install.yml
|
|
1197
1379
|
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";
|
|
@@ -1232,8 +1414,9 @@ const REUSABLE_WORKFLOWS = {
|
|
|
1232
1414
|
"delivery.deploy": "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",
|
|
1233
1415
|
"delivery.bundleSize": "name: Audit the Bundle Size\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n CODECOV_TOKEN:\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n bundle-size:\n name: Upload bundle stats to Codecov\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 15\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n # Read by the bundle-stats uploader inside `holocron run delivery.build`.\n CODECOV_TOKEN: ${{ secrets.CODECOV_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 - uses: theholocron/.github/.github/actions/holocron@main\n name: Build and upload bundle stats\n with:\n task: delivery.build\n",
|
|
1234
1416
|
"platform.repoSync": "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",
|
|
1235
|
-
"platform.commitStandards": "name: Commit Standards\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n TURBO_TOKEN:\n required: false\n\njobs:\n commit-standards:\n name: Run commitlint\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 10\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 repository\n with:\n fetch-depth: 0\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n # No local equivalent (linters.ts: commitlint has no localBin — enforced\n # only at commit time via the commit-msg hook, and here in CI over the\n # PR's commit range). Not run through the holocron composite action.\n - name: Run commitlint over the PR range\n if: github.event_name == 'pull_request'\n run: pnpm exec commitlint --from \"${{ github.event.pull_request.base.sha }}\" --to \"${{ github.sha }}\"\n",
|
|
1417
|
+
"platform.commitStandards": "name: Commit Standards\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n TURBO_TOKEN:\n required: false\n\njobs:\n commit-standards:\n name: Run commitlint\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 10\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 repository\n with:\n fetch-depth: 0\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n # No local equivalent (linters.ts: commitlint has no localBin — enforced\n # only at commit time via the commit-msg hook, and here in CI over the\n # PR's commit range). Not run through the holocron composite action.\n #\n # --config <resolved shared path> (config-resolution workstream, #676)\n # — falls back to commitlint's own auto-discovery of a local\n # commitlint.config.ts when the shared package's built dist/ isn't\n # present, same guard as the commit-msg hook.\n - name: Run commitlint over the PR range\n if: github.event_name == 'pull_request'\n run: |\n COMMITLINT_CONFIG=\"node_modules/@theholocron/commitlint-config/dist/index.js\"\n if [ -f \"$COMMITLINT_CONFIG\" ]; then\n pnpm exec commitlint --config \"$COMMITLINT_CONFIG\" --from \"${{ github.event.pull_request.base.sha }}\" --to \"${{ github.sha }}\"\n else\n pnpm exec commitlint --from \"${{ github.event.pull_request.base.sha }}\" --to \"${{ github.sha }}\"\n fi\n",
|
|
1236
1418
|
"platform.repoValidation": "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",
|
|
1419
|
+
"platform.dispatchedCheck": "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",
|
|
1237
1420
|
"knowledge.wiki": "name: Wiki\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n fern-version:\n description: >\n Fern CLI version to install. Pin this to avoid breaking changes when\n Fern updates their config schema.\n type: string\n required: false\n default: \"5.114.1\"\n preview:\n description: >\n When true, publishes a preview instead of production.\n Requires preview-id to be set.\n type: boolean\n required: false\n default: false\n preview-id:\n description: >\n Stable ID for the preview build. Use the PR number (e.g. \"pr-123\")\n so the same preview URL is reused on every push to the branch.\n type: string\n required: false\n default: \"\"\n fern-org:\n description: >\n DEPRECATED — the preview URL is now read from Fern's own output.\n Kept as a fallback for building the URL when that parse fails, and so\n existing callers do not error.\n type: string\n required: false\n default: \"\"\n base-path:\n description: >\n DEPRECATED — see fern-org. Basepath appended to the fallback preview\n URL (e.g. \"holocron\" for multi-source routing).\n type: string\n required: false\n default: \"\"\n secrets:\n HOLOCRON_FERN_TOKEN:\n required: false\n FERN_TOKEN:\n required: false\n\njobs:\n publish:\n name: Publish to Fern\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: read\n deployments: write\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - name: Install Fern CLI\n run: npm install -g \"fern-api@$FERN_VERSION\"\n env:\n FERN_VERSION: ${{ inputs.fern-version }}\n\n - name: Publish docs\n if: ${{ !inputs.preview }}\n run: fern generate --docs\n env:\n FERN_TOKEN: ${{ secrets.HOLOCRON_FERN_TOKEN || secrets.FERN_TOKEN }}\n\n - name: Preview docs\n id: preview\n if: ${{ inputs.preview && inputs.preview-id != '' }}\n env:\n FERN_TOKEN: ${{ secrets.HOLOCRON_FERN_TOKEN || secrets.FERN_TOKEN }}\n PREVIEW_ID: ${{ inputs.preview-id }}\n run: |\n out=$(fern generate --docs --preview --id \"$PREVIEW_ID\" 2>&1) || { printf '%s\\n' \"$out\"; exit 1; }\n printf '%s\\n' \"$out\"\n # Fern prints \"Published docs to https://<org>-preview-<id>.docs.buildwithfern.com/<path>\"\n url=$(printf '%s\\n' \"$out\" | grep -oiE 'https://[a-z0-9.-]+\\.docs\\.buildwithfern\\.com[^ )]*' | head -n1)\n echo \"url=$url\" >> \"$GITHUB_OUTPUT\"\n\n - name: Report preview URL\n if: ${{ inputs.preview && inputs.preview-id != '' }}\n env:\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n CAPTURED_URL: ${{ steps.preview.outputs.url }}\n FERN_ORG: ${{ inputs.fern-org }}\n PREVIEW_ID: ${{ inputs.preview-id }}\n BASE_PATH: ${{ inputs.base-path }}\n run: |\n PREVIEW_URL=\"$CAPTURED_URL\"\n if [ -z \"$PREVIEW_URL\" ] && [ -n \"$FERN_ORG\" ]; then\n PREVIEW_URL=\"https://${FERN_ORG}-preview-${PREVIEW_ID}.docs.buildwithfern.com${BASE_PATH:+/${BASE_PATH}}\"\n fi\n if [ -z \"$PREVIEW_URL\" ]; then\n echo \"::warning::no preview URL parsed from Fern output and no fern-org fallback — skipping the deployment widget\"\n exit 0\n fi\n PAYLOAD=$(printf '{\"ref\":\"%s\",\"environment\":\"wiki (Preview)\",\"description\":\"Wiki\",\"production_environment\":false,\"auto_merge\":false,\"required_contexts\":[]}' \\\n \"$GITHUB_HEAD_REF\")\n DEPLOY_ID=$(printf '%s\\n' \"$PAYLOAD\" | gh api \"repos/${GITHUB_REPOSITORY}/deployments\" \\\n --method POST --input - | jq -r '.id')\n gh api \"repos/${GITHUB_REPOSITORY}/deployments/${DEPLOY_ID}/statuses\" \\\n --method POST --field state=success --field environment_url=\"$PREVIEW_URL\"\n",
|
|
1238
1421
|
bookkeeping: "name: Bookkeeping\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n configuration-path:\n description: Path to the labeler configuration file in the calling repo\n type: string\n required: false\n default: .github/labeler.yml\n\njobs:\n label:\n name: Apply Labels\n permissions:\n contents: read\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 5\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n sparse-checkout: ${{ inputs.configuration-path || '.github/labeler.yml' }}\n sparse-checkout-cone-mode: false\n\n - uses: github/issue-labeler@c1b0f9f52a63158c4adc09425e858e87b32e9685 # v3.4\n if: ${{ github.event_name == 'pull_request' && hashFiles(inputs.configuration-path || '.github/labeler.yml') != '' }}\n # v3.4 bundles Node 20; allow it to run under Actions' current default.\n env:\n ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true\n with:\n # Fall back to default path when triggered directly (not via workflow_call)\n # because inputs.* defaults only apply on workflow_call events.\n configuration-path: ${{ inputs.configuration-path || '.github/labeler.yml' }}\n include-title: 1\n include-body: 0\n sync-labels: 1\n enable-versioned-regex: 0\n repo-token: ${{ github.token }}\n",
|
|
1239
1422
|
dependencies: "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",
|
|
@@ -1339,6 +1522,120 @@ function turboConfig(config) {
|
|
|
1339
1522
|
tasks
|
|
1340
1523
|
}, null, 2)}\n`;
|
|
1341
1524
|
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Fixes #692: `turboConfig()` writes a `turbo.json` the moment *any* task in
|
|
1527
|
+
* the manifest has fan-out config — with no check for whether the repo's own
|
|
1528
|
+
* root package is actually covered by `pnpm-workspace.yaml`'s `packages:`
|
|
1529
|
+
* list. A repo shaped like `observability` (the library lives at repo root;
|
|
1530
|
+
* `packages:` lists only an unrelated `docs` site) silently breaks the
|
|
1531
|
+
* instant `turbo.json` exists: `holocron run <task>` switches from running
|
|
1532
|
+
* root's own script directly to `turbo run <task>`, which only sees declared
|
|
1533
|
+
* workspace members — root vanishes, the task "succeeds" with zero real work
|
|
1534
|
+
* done (confirmed empirically: `Packages in scope: docs`, root never
|
|
1535
|
+
* mentioned, exit 0).
|
|
1536
|
+
*
|
|
1537
|
+
* Detection is narrow and specific: root only needs to be an explicit
|
|
1538
|
+
* workspace member if its *own* `package.json` has a script literally named
|
|
1539
|
+
* after one of the tasks turbo.json is about to fan out — a plain
|
|
1540
|
+
* orchestrator root (`holocron`'s own `"build": "turbo run delivery.build"`,
|
|
1541
|
+
* which has no `delivery.build` script itself) never trips this; only a root
|
|
1542
|
+
* that's genuinely a directly-buildable package does. Confirmed the fix
|
|
1543
|
+
* empirically too — adding `.` to `packages:` makes turbo pick root back up
|
|
1544
|
+
* (`Packages in scope: docs, root-lib`) without disturbing the sibling
|
|
1545
|
+
* package's own caching.
|
|
1546
|
+
*
|
|
1547
|
+
* A targeted line-based edit, not a full YAML parse/reserialize — same
|
|
1548
|
+
* pattern `codecov.ts`'s `mergeCodecovComponents()` already uses for editing
|
|
1549
|
+
* an existing generated file. `pnpm-workspace.yaml` routinely carries a
|
|
1550
|
+
* `catalog:`/`catalogs:`/`overrides:` block after `packages:`; a real parser
|
|
1551
|
+
* round-trip risks reformatting or reordering content nobody asked to touch.
|
|
1552
|
+
* Only ever *adds* a line — never rewrites or reorders anything already
|
|
1553
|
+
* there — and is a no-op (`changed: false`) whenever root doesn't need it,
|
|
1554
|
+
* root is already listed, or `packages:` isn't in the plain block-list form
|
|
1555
|
+
* every repo checked actually uses.
|
|
1556
|
+
*/
|
|
1557
|
+
function ensureRootWorkspaceMember(workspaceYaml, rootScripts, taskNames) {
|
|
1558
|
+
if (!taskNames.some((t) => rootScripts.includes(t))) return {
|
|
1559
|
+
content: workspaceYaml,
|
|
1560
|
+
changed: false
|
|
1561
|
+
};
|
|
1562
|
+
const lines = workspaceYaml.split("\n");
|
|
1563
|
+
const packagesLineIdx = lines.findIndex((l) => /^packages:\s*$/.test(l));
|
|
1564
|
+
if (packagesLineIdx === -1) return {
|
|
1565
|
+
content: workspaceYaml,
|
|
1566
|
+
changed: false
|
|
1567
|
+
};
|
|
1568
|
+
const items = [];
|
|
1569
|
+
let cursor = packagesLineIdx + 1;
|
|
1570
|
+
while (cursor < lines.length && /^\s*-\s*/.test(lines[cursor])) {
|
|
1571
|
+
items.push(lines[cursor]);
|
|
1572
|
+
cursor++;
|
|
1573
|
+
}
|
|
1574
|
+
if (items.some((item) => {
|
|
1575
|
+
const value = item.replace(/^\s*-\s*/, "").replace(/^["']|["']$/g, "").trim();
|
|
1576
|
+
return value === "." || value === "";
|
|
1577
|
+
})) return {
|
|
1578
|
+
content: workspaceYaml,
|
|
1579
|
+
changed: false
|
|
1580
|
+
};
|
|
1581
|
+
const newLine = `${items[0]?.match(/^(\s*-\s*)/)?.[1] ?? " - "}"."`;
|
|
1582
|
+
return {
|
|
1583
|
+
content: [
|
|
1584
|
+
...lines.slice(0, packagesLineIdx + 1),
|
|
1585
|
+
newLine,
|
|
1586
|
+
...lines.slice(packagesLineIdx + 1)
|
|
1587
|
+
].join("\n"),
|
|
1588
|
+
changed: true
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Fallback pin when a repo's own `pnpm-workspace.yaml` has no `turbo`
|
|
1593
|
+
* catalog entry yet — matches this repo's own catalog version (the
|
|
1594
|
+
* canonical source-of-truth repo for the whole org's tooling).
|
|
1595
|
+
*/
|
|
1596
|
+
const DEFAULT_TURBO_VERSION = "^2.10.12";
|
|
1597
|
+
/**
|
|
1598
|
+
* Ensures `turbo` is declared in `package.json#devDependencies` — found
|
|
1599
|
+
* the hard way rolling this out to `observability` (predates #691):
|
|
1600
|
+
* `turboConfig()` writes a `turbo.json` the moment any task has fan-out
|
|
1601
|
+
* config, but never touches `package.json`, so a repo that predates this
|
|
1602
|
+
* feature (or never had `turbo` installed for any other reason) ends up
|
|
1603
|
+
* with a `turbo.json` and nothing local to run it. `resolveBin()`
|
|
1604
|
+
* (`run.ts`) falls back to a bare `turbo` on `PATH` when
|
|
1605
|
+
* `node_modules/.bin/turbo` doesn't exist — on a machine with no global
|
|
1606
|
+
* `turbo` at all that's a hard failure for every task; on one that
|
|
1607
|
+
* happens to have an unrelated global install, silent version skew
|
|
1608
|
+
* (confirmed empirically: a global 2.6.0 rejected this repo's generated
|
|
1609
|
+
* `turbo.json` outright — `Found an unknown key "globalDependencies"`,
|
|
1610
|
+
* a schema key from a newer version than the one actually resolved).
|
|
1611
|
+
*
|
|
1612
|
+
* Prefers `"catalog:"` when the repo's own `pnpm-workspace.yaml` already
|
|
1613
|
+
* has a `turbo` catalog entry — matches every already-migrated repo
|
|
1614
|
+
* (`holocron`, `clients`, …) — falling back to a real pinned version
|
|
1615
|
+
* otherwise. Never touches an existing `devDependencies.turbo` entry, so
|
|
1616
|
+
* a repo pinning its own version on purpose is left alone — idempotent,
|
|
1617
|
+
* a no-op once set either way.
|
|
1618
|
+
*/
|
|
1619
|
+
function ensureTurboDependency(packageJson, workspaceYaml) {
|
|
1620
|
+
const pkg = JSON.parse(packageJson);
|
|
1621
|
+
if (typeof pkg.devDependencies?.turbo === "string") return {
|
|
1622
|
+
content: packageJson,
|
|
1623
|
+
changed: false
|
|
1624
|
+
};
|
|
1625
|
+
const hasTurboCatalogEntry = /^\s*turbo:\s*\S/m.test(workspaceYaml);
|
|
1626
|
+
const devDependencies = {
|
|
1627
|
+
...pkg.devDependencies,
|
|
1628
|
+
turbo: hasTurboCatalogEntry ? "catalog:" : DEFAULT_TURBO_VERSION
|
|
1629
|
+
};
|
|
1630
|
+
const updated = {
|
|
1631
|
+
...pkg,
|
|
1632
|
+
devDependencies
|
|
1633
|
+
};
|
|
1634
|
+
return {
|
|
1635
|
+
content: `${JSON.stringify(updated, null, 2)}\n`,
|
|
1636
|
+
changed: true
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1342
1639
|
//#endregion
|
|
1343
1640
|
//#region src/astromech.ts
|
|
1344
1641
|
/**
|
|
@@ -1441,8 +1738,39 @@ function createAstromech(options) {
|
|
|
1441
1738
|
reusableTemplates: () => reusableTemplates(),
|
|
1442
1739
|
requiredChecks: () => requiredChecks(options.config ?? {}),
|
|
1443
1740
|
codecovConfig: (existing) => codecovConfig(options.cwd, existing),
|
|
1444
|
-
turboConfig: () => turboConfig(options.config ?? {})
|
|
1741
|
+
turboConfig: () => turboConfig(options.config ?? {}),
|
|
1742
|
+
ensureRootWorkspaceMember: (workspaceYaml, rootScripts) => {
|
|
1743
|
+
return ensureRootWorkspaceMember(workspaceYaml, rootScripts, (options.config?.tasks ?? []).map(normalizeTaskEntry).filter((e) => e.local !== false).map((e) => e.name));
|
|
1744
|
+
},
|
|
1745
|
+
ensureTurboDependency: (packageJson, workspaceYaml) => ensureTurboDependency(packageJson, workspaceYaml)
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
//#endregion
|
|
1749
|
+
//#region src/tsconfig.ts
|
|
1750
|
+
/**
|
|
1751
|
+
* A package-level `tsconfig.json` — the uniform shape confirmed across
|
|
1752
|
+
* every package checked, parameterized only by the two fields that
|
|
1753
|
+
* genuinely vary (`display`, `paths`). No scaffold/workflow header (matches
|
|
1754
|
+
* `.alexrc.json`'s existing precedent for a strict-JSON Bucket B file —
|
|
1755
|
+
* JSON has no comment syntax to carry one, and TypeScript's own tolerance
|
|
1756
|
+
* for JSONC comments in `tsconfig.json` isn't worth relying on for a file
|
|
1757
|
+
* this thin).
|
|
1758
|
+
*/
|
|
1759
|
+
function createTsconfig(options) {
|
|
1760
|
+
const variant = options.variant ?? "node-lts";
|
|
1761
|
+
const compilerOptions = {
|
|
1762
|
+
baseUrl: "./",
|
|
1763
|
+
outDir: "./dist"
|
|
1764
|
+
};
|
|
1765
|
+
if (options.paths) compilerOptions.paths = { "@/*": ["./src/*"] };
|
|
1766
|
+
const config = {
|
|
1767
|
+
display: options.display,
|
|
1768
|
+
extends: `@theholocron/tsconfig/${variant}`,
|
|
1769
|
+
compilerOptions,
|
|
1770
|
+
include: ["src/**/*.ts"],
|
|
1771
|
+
exclude: ["node_modules", "dist"]
|
|
1445
1772
|
};
|
|
1773
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
1446
1774
|
}
|
|
1447
1775
|
//#endregion
|
|
1448
|
-
export { CI_ORDER, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, resolveLinters, reusableTemplates, runCi, runTask };
|
|
1776
|
+
export { CI_ORDER, KNOWN_TASKS, KNOWN_WORKFLOWS, LINTERS, LINTER_NAMES, RESOLVABLE_TOOLS, REUSABLE_ACTIONS, REUSABLE_WORKFLOWS, TASKS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, WORKFLOW_TEMPLATE_PROPERTIES, codecovComponentBlock, codecovConfig, createAstromech, createCodecovConfig, createTsconfig, deriveDeployPaths, ensureIfNotFound, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, mergeCodecovComponents, normalizeWorkflowWith, readWorkspacePackages, requiredChecks, resolveLinters, resolveToolConfig, reusableTemplates, runCi, runTask };
|
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.61",
|
|
4
4
|
"description": "The Holocron task runner — one task manifest drives `holocron run`, `holocron ci`, the CI workflows, package.json scripts, linters, and required checks.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@theholocron/datapad": "5.0.0-alpha.
|
|
40
|
+
"@theholocron/datapad": "5.0.0-alpha.61"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@theholocron/eslint-config": "^8.
|
|
44
|
-
"@theholocron/tsconfig": "^8.
|
|
45
|
-
"@theholocron/tsdown-config": "^8.
|
|
46
|
-
"@theholocron/vitest-config": "^8.
|
|
43
|
+
"@theholocron/eslint-config": "^8.4.5",
|
|
44
|
+
"@theholocron/tsconfig": "^8.4.5",
|
|
45
|
+
"@theholocron/tsdown-config": "^8.4.5",
|
|
46
|
+
"@theholocron/vitest-config": "^8.4.5",
|
|
47
47
|
"@types/node": "^26",
|
|
48
48
|
"@vitest/coverage-v8": "^4.1.11",
|
|
49
49
|
"@vitest/eslint-plugin": "^1.6.27",
|
|
@@ -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.61"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=22"
|