@theholocron/astromech 5.0.0-alpha.3 → 5.0.0-alpha.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +127 -1
- package/dist/index.mjs +209 -8
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -221,6 +221,12 @@ 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
|
+
//#endregion
|
|
224
230
|
//#region src/astromech.d.ts
|
|
225
231
|
interface AstromechOptions {
|
|
226
232
|
/** Repo root. */
|
|
@@ -315,6 +321,17 @@ interface Astromech {
|
|
|
315
321
|
* config (nothing to write). See {@link TurboTaskConfig} in `registry.ts`.
|
|
316
322
|
*/
|
|
317
323
|
turboConfig(): string | null;
|
|
324
|
+
/**
|
|
325
|
+
* Fixes #692: whenever {@link turboConfig} isn't `null`, this repo's root
|
|
326
|
+
* package needs to actually be a workspace member for the tasks it fans
|
|
327
|
+
* out — otherwise a root that's genuinely a directly-buildable package
|
|
328
|
+
* (not just an orchestrator) silently vanishes from `turbo run <task>`.
|
|
329
|
+
* Pass `pnpm-workspace.yaml`'s current content (or `""` if none exists —
|
|
330
|
+
* a no-op either way, since no `packages:` key means single-package mode
|
|
331
|
+
* already) and root `package.json`'s own `scripts` keys. `changed: false`
|
|
332
|
+
* means don't write anything — the file is already correct.
|
|
333
|
+
*/
|
|
334
|
+
ensureRootWorkspaceMember(workspaceYaml: string, rootScripts: readonly string[]): EnsureRootWorkspaceMemberResult;
|
|
318
335
|
}
|
|
319
336
|
declare function createAstromech(options: AstromechOptions): Astromech;
|
|
320
337
|
//#endregion
|
|
@@ -546,6 +563,46 @@ declare const CI_ORDER: string[];
|
|
|
546
563
|
/** Ordered (task contexts in {@link CI_ORDER}, then extras), de-duplicated. */
|
|
547
564
|
declare function requiredChecks(config: TasksConfig): string[];
|
|
548
565
|
//#endregion
|
|
566
|
+
//#region src/resolver.d.ts
|
|
567
|
+
/**
|
|
568
|
+
* Resolves the absolute path to a Bucket A tool's shared `@theholocron/*-config`
|
|
569
|
+
* entry point, installed in the consuming repo's own `node_modules` — and the
|
|
570
|
+
* CLI flag(s) to hand it via (`--config`, `--extends`, …). `run.ts` splices the
|
|
571
|
+
* result into the command it builds for a tool/detect runner or a linter-group
|
|
572
|
+
* entry, right after the tool's own args.
|
|
573
|
+
*
|
|
574
|
+
* Every mapped package's resolved entry point already carries a ready-to-use
|
|
575
|
+
* default export (a plain re-export for `prettier`/`commitlint`; an invoked,
|
|
576
|
+
* zero-arg preset for `eslint`/`vitest`/`tsdown` — verified end-to-end against
|
|
577
|
+
* each tool's real `--config`/`--extends` loader, not just the file's shape).
|
|
578
|
+
* A repo doesn't need this package installed at all — every lookup degrades to
|
|
579
|
+
* `[]` (no flag added, the tool falls back to its own auto-discovery of a
|
|
580
|
+
* local file, unchanged from today) rather than throwing, so this is additive
|
|
581
|
+
* and safe to run unconditionally.
|
|
582
|
+
*
|
|
583
|
+
* `semantic-release`, `devmoji`, `editorconfig-checker`, and `knip` are
|
|
584
|
+
* deliberately absent — `semantic-release-config`'s `defineConfig()` needs
|
|
585
|
+
* real per-repo data (branches, npm options) a static `--extends <path>` can't
|
|
586
|
+
* carry; `devmoji` runs through a git hook template, not `holocron run
|
|
587
|
+
* <task>`; `editorconfig-checker` has no shared-config package yet; `knip` is
|
|
588
|
+
* repo-specific by nature, never a shared-config candidate. See
|
|
589
|
+
* `.notes/tech-config-resolution.spec.md` (#676).
|
|
590
|
+
*/
|
|
591
|
+
interface ResolverDeps {
|
|
592
|
+
readFile: (path: string) => string;
|
|
593
|
+
fileExists: (path: string) => boolean;
|
|
594
|
+
}
|
|
595
|
+
/** Every tool name the resolver knows how to point at a shared config. */
|
|
596
|
+
declare const RESOLVABLE_TOOLS: ReadonlySet<string>;
|
|
597
|
+
/**
|
|
598
|
+
* `<flag> <absolute path>` for `tool`, resolved against `cwd`'s
|
|
599
|
+
* `node_modules` — or `[]` when the tool isn't mapped, the package isn't
|
|
600
|
+
* installed, its `package.json` doesn't declare the export subpath this
|
|
601
|
+
* needs, or the resolved file doesn't actually exist on disk (a stale
|
|
602
|
+
* install, or a package version that predates the export existing).
|
|
603
|
+
*/
|
|
604
|
+
declare function resolveToolConfig(tool: string, cwd: string, deps: ResolverDeps): string[];
|
|
605
|
+
//#endregion
|
|
549
606
|
//#region src/reusable.d.ts
|
|
550
607
|
/**
|
|
551
608
|
* The **reusable** GitHub Actions surface pushed to `theholocron/.github` by
|
|
@@ -573,4 +630,73 @@ declare const WORKFLOW_TEMPLATE_PROPERTIES: Record<string, string>;
|
|
|
573
630
|
*/
|
|
574
631
|
declare function reusableTemplates(): Map<string, string>;
|
|
575
632
|
//#endregion
|
|
576
|
-
|
|
633
|
+
//#region src/tsconfig.d.ts
|
|
634
|
+
/**
|
|
635
|
+
* `tsconfig.json` generation — Bucket B (config-resolution workstream, #676):
|
|
636
|
+
* the file must stay committed (the TypeScript language server discovers it
|
|
637
|
+
* directly from disk, no `--config` override mechanism to hand it a path
|
|
638
|
+
* instead), but its content is fully derivable, so it's generated rather
|
|
639
|
+
* than hand-authored — the same category as `.editorconfig`
|
|
640
|
+
* ({@link "./templates/configs/editorconfig/create-config.js" createConfig})
|
|
641
|
+
* and {@link codecovConfig}.
|
|
642
|
+
*
|
|
643
|
+
* Surveyed every package-level `tsconfig.json` across the org (`holocron`,
|
|
644
|
+
* `clients`, `utils` — 20+ packages). `extends: "@theholocron/tsconfig/
|
|
645
|
+
* node-lts"`, `compilerOptions.baseUrl: "./"`, `compilerOptions.outDir:
|
|
646
|
+
* "./dist"`, `include: ["src/**\/*.ts"]`, and `exclude: ["node_modules",
|
|
647
|
+
* "dist"]` are uniform in every one — safe defaults here. `paths` (a `@/*`
|
|
648
|
+
* → `./src/*` import alias) is a real per-package choice, present in
|
|
649
|
+
* roughly 40% of packages checked — an opt-in parameter, not a default.
|
|
650
|
+
*
|
|
651
|
+
* Two fields checked and deliberately *not* absorbed as defaults, unlike
|
|
652
|
+
* `eslint-config`'s `tsconfigRootDir`/`settings.node` (which turned out
|
|
653
|
+
* redundant with the shared package's own behavior, `configs`#461):
|
|
654
|
+
* `compilerOptions.module`/`moduleResolution` (only `@theholocron/cli`
|
|
655
|
+
* overrides these, to `esnext`/`bundler` — a genuine deviation from the
|
|
656
|
+
* shared `node-lts` preset's `nodenext`/`nodenext`, needed for its bundler
|
|
657
|
+
* tooling, not something every package should inherit) and
|
|
658
|
+
* `compilerOptions.rootDir` (present in exactly two packages, always the
|
|
659
|
+
* same value `./src` that `include` already implies — an unnecessary
|
|
660
|
+
* override wherever it appears, not a pattern worth generalizing).
|
|
661
|
+
*
|
|
662
|
+
* Monorepo *root* `tsconfig.json` (a TS project-references "solution
|
|
663
|
+
* file" — `holocron`'s own root is `{ files: [], references: [...] }`,
|
|
664
|
+
* listing which packages to build) is a structurally different, genuinely
|
|
665
|
+
* per-repo document — out of scope here, same as Bucket C content.
|
|
666
|
+
*
|
|
667
|
+
* Not yet wired into `holocron setup`'s per-package write loop (every
|
|
668
|
+
* other Bucket B file there is a single repo-root file, safely
|
|
669
|
+
* overwritten every run — `tsconfig.json` is per-package, and most
|
|
670
|
+
* packages' files still carry real hand-authored content today, not yet
|
|
671
|
+
* migrated to a fully-generated state). That per-package iteration +
|
|
672
|
+
* safe-migration design is `#680`'s job, not duplicated here — this is
|
|
673
|
+
* the generator itself, ready for it to call.
|
|
674
|
+
*/
|
|
675
|
+
interface TsconfigOptions {
|
|
676
|
+
/** Human-readable name for the `display` field — the package's own name is the usual choice. */
|
|
677
|
+
display: string;
|
|
678
|
+
/**
|
|
679
|
+
* `@theholocron/tsconfig` variant. Defaults to `"node-lts"` — every
|
|
680
|
+
* checked package uses it; the other three (`astro`, `nextjs`, `react`)
|
|
681
|
+
* exist for a template repo's app-shaped packages, not the plain
|
|
682
|
+
* Node.js library packages this org's currently-migrated repos ship.
|
|
683
|
+
*/
|
|
684
|
+
variant?: "astro" | "nextjs" | "node-lts" | "react";
|
|
685
|
+
/**
|
|
686
|
+
* Add a `@/*` → `./src/*` path alias. A real per-package choice, not a
|
|
687
|
+
* default — roughly 40% of packages checked use one, the rest don't.
|
|
688
|
+
*/
|
|
689
|
+
paths?: boolean;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* A package-level `tsconfig.json` — the uniform shape confirmed across
|
|
693
|
+
* every package checked, parameterized only by the two fields that
|
|
694
|
+
* genuinely vary (`display`, `paths`). No scaffold/workflow header (matches
|
|
695
|
+
* `.alexrc.json`'s existing precedent for a strict-JSON Bucket B file —
|
|
696
|
+
* JSON has no comment syntax to carry one, and TypeScript's own tolerance
|
|
697
|
+
* for JSONC comments in `tsconfig.json` isn't worth relying on for a file
|
|
698
|
+
* this thin).
|
|
699
|
+
*/
|
|
700
|
+
declare function createTsconfig(options: TsconfigOptions): string;
|
|
701
|
+
//#endregion
|
|
702
|
+
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,98 @@ 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
|
+
/** Every tool name the resolver knows how to point at a shared config. */
|
|
368
|
+
const RESOLVABLE_TOOLS = new Set(Object.keys(TOOL_CONFIGS));
|
|
369
|
+
/**
|
|
370
|
+
* `<flag> <absolute path>` for `tool`, resolved against `cwd`'s
|
|
371
|
+
* `node_modules` — or `[]` when the tool isn't mapped, the package isn't
|
|
372
|
+
* installed, its `package.json` doesn't declare the export subpath this
|
|
373
|
+
* needs, or the resolved file doesn't actually exist on disk (a stale
|
|
374
|
+
* install, or a package version that predates the export existing).
|
|
375
|
+
*/
|
|
376
|
+
function resolveToolConfig(tool, cwd, deps) {
|
|
377
|
+
const spec = TOOL_CONFIGS[tool];
|
|
378
|
+
if (!spec) return [];
|
|
379
|
+
const pkgDir = joinPath(cwd, "node_modules", ...spec.package.split("/"));
|
|
380
|
+
const pkgJsonPath = joinPath(pkgDir, "package.json");
|
|
381
|
+
if (!deps.fileExists(pkgJsonPath)) return [];
|
|
382
|
+
let pkg;
|
|
383
|
+
try {
|
|
384
|
+
pkg = JSON.parse(deps.readFile(pkgJsonPath));
|
|
385
|
+
} catch {
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
const relative = resolveExportEntry(pkg.exports?.[spec.exportPath]);
|
|
389
|
+
if (!relative) return [];
|
|
390
|
+
const absolute = joinPath(pkgDir, relative.replace(/^\.\//, ""));
|
|
391
|
+
if (!deps.fileExists(absolute)) return [];
|
|
392
|
+
return [...spec.flag, absolute];
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Unwrap a `package.json#exports` subpath entry down to its file path —
|
|
396
|
+
* either a bare string, or an object with condition keys (`import`,
|
|
397
|
+
* `default`, `require`, …) that may themselves nest one level (`{ import: {
|
|
398
|
+
* types, default } }`, the shape every `@theholocron/*-config` package
|
|
399
|
+
* actually uses). Prefers `import` (this org ships ESM), then `default`,
|
|
400
|
+
* then `require` as a last resort.
|
|
401
|
+
*/
|
|
402
|
+
function resolveExportEntry(entry) {
|
|
403
|
+
if (typeof entry === "string") return entry;
|
|
404
|
+
if (entry === null || typeof entry !== "object") return void 0;
|
|
405
|
+
const conditions = entry;
|
|
406
|
+
const value = conditions.import ?? conditions.default ?? conditions.require;
|
|
407
|
+
if (typeof value === "string") return value;
|
|
408
|
+
if (typeof value === "object" && value !== null) return resolveExportEntry(value);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* A minimal, POSIX-safe `path.join` — avoids importing `node:path` just for
|
|
412
|
+
* this. Every path in play here is either already POSIX (a `package.json`
|
|
413
|
+
* `exports` value, always forward-slashed regardless of platform) or built
|
|
414
|
+
* from platform-neutral segments (`cwd`, `"node_modules"`, a scoped package
|
|
415
|
+
* name split on `/`), so a manual join is safe and keeps this module
|
|
416
|
+
* dependency-free like the rest of `run.ts`'s helpers.
|
|
417
|
+
*/
|
|
418
|
+
function joinPath(...segments) {
|
|
419
|
+
return segments.map((s, i) => i === 0 ? s.replace(/\/+$/, "") : s.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
420
|
+
}
|
|
421
|
+
//#endregion
|
|
330
422
|
//#region src/run.ts
|
|
331
423
|
/**
|
|
332
424
|
* `holocron run <task> [job] [-- <passthrough>]` — run a registry task (or one
|
|
@@ -448,8 +540,13 @@ function runTask(input) {
|
|
|
448
540
|
const runner = resolveRunner(def.local, cwd, listDir);
|
|
449
541
|
if (runner) {
|
|
450
542
|
const flags = (def.flags?.[runner.tool] ?? []).filter((f) => !passthrough.includes(f));
|
|
543
|
+
const configFlags = resolveToolConfig(runner.tool, cwd, {
|
|
544
|
+
readFile,
|
|
545
|
+
fileExists
|
|
546
|
+
});
|
|
451
547
|
return run(resolveBin(cwd, runner.tool, fileExists), [
|
|
452
548
|
...runner.args,
|
|
549
|
+
...configFlags,
|
|
453
550
|
...flags,
|
|
454
551
|
...passthrough
|
|
455
552
|
]);
|
|
@@ -582,7 +679,7 @@ function runAllJobs(input, jobs, passthrough) {
|
|
|
582
679
|
* turbo/a package script would have already caught in steps 1–2.
|
|
583
680
|
*/
|
|
584
681
|
function runLinterGroup(input, names, passthrough) {
|
|
585
|
-
const { print, logger, listDir, lookPath, cwd, task } = input;
|
|
682
|
+
const { print, logger, listDir, lookPath, cwd, task, readFile, fileExists } = input;
|
|
586
683
|
const dryRun = input.dryRun ?? false;
|
|
587
684
|
const runOne = makeRunOne(input);
|
|
588
685
|
let rootFiles;
|
|
@@ -609,7 +706,15 @@ function runLinterGroup(input, names, passthrough) {
|
|
|
609
706
|
print(`! ${name} — ${bin} not on PATH${def.installHint ? `. ${def.installHint}` : ""} (enforced in CI)`);
|
|
610
707
|
continue;
|
|
611
708
|
}
|
|
612
|
-
|
|
709
|
+
const configFlags = resolveToolConfig(name, cwd, {
|
|
710
|
+
readFile,
|
|
711
|
+
fileExists
|
|
712
|
+
});
|
|
713
|
+
reports.push(runOne(found, [
|
|
714
|
+
...configFlags,
|
|
715
|
+
...def.localArgs ?? [],
|
|
716
|
+
...passthrough
|
|
717
|
+
]));
|
|
613
718
|
}
|
|
614
719
|
if (reports.length === 0) {
|
|
615
720
|
const required = input.required && anyHasLocalBin;
|
|
@@ -718,7 +823,7 @@ const WORKFLOW_TEMPLATES = {
|
|
|
718
823
|
"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
824
|
"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
825
|
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:
|
|
826
|
+
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
827
|
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
828
|
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
829
|
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",
|
|
@@ -1197,7 +1302,7 @@ var holocron_default = "name: Holocron\ndescription: >\n Run the Holocron CLI
|
|
|
1197
1302
|
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";
|
|
1198
1303
|
//#endregion
|
|
1199
1304
|
//#region src/templates/reusable/actions/setup.yml
|
|
1200
|
-
var setup_default = "name: Setup\ndescription: Prepare the environment and install project dependencies.\n\ninputs:\n node-version:\n description: Node.js version\n required: false\n default: \"22.x\"\n\nruns:\n using: composite\n\n steps:\n - uses: theholocron/.github/.github/actions/setup-node@main\n with:\n node-version: ${{ inputs.node-version }}\n\n - uses: theholocron/.github/.github/actions/install@main\n\n - name: Cache Turbo artifacts\n if: ${{ hashFiles('turbo.json') != '' }}\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: .turbo\n key: turbo-${{ runner.os }}-${{ github.sha }}\n restore-keys: |\n turbo-${{ runner.os }}-\n";
|
|
1305
|
+
var setup_default = "name: Setup\ndescription: Prepare the environment and install project dependencies.\n\ninputs:\n node-version:\n description: Node.js version\n required: false\n default: \"22.x\"\n\nruns:\n using: composite\n\n steps:\n - uses: theholocron/.github/.github/actions/setup-node@main\n with:\n node-version: ${{ inputs.node-version }}\n\n - uses: theholocron/.github/.github/actions/install@main\n\n # `@theholocron/cli`'s `dist/cli.mjs` doesn't exist yet at this point, so\n # pnpm's workspace bin-linking can't create `node_modules/.bin/holocron`\n # (silent `WARN ... ENOENT`, not a hard failure). Every package whose\n # scripts delegate via `holocron run <task> --` then fails at run time\n # with `holocron: not found` (#698). Same fix the `holocron` composite\n # action already applies for its own direct invocation (see its \"Build\n # the workspace when the CLI resolves to an unbuilt checkout\" step) —\n # mirrored here so it also covers *nested* `holocron run <task> --`\n # calls from inside package.json scripts, which that action's own guard\n # doesn't reach. `pnpm build` (not a scoped `--filter`) so the CLI's own\n # workspace dependencies (e.g. rollup-plugin-transform-template) build\n # in topological order too — a scoped build skips them and fails.\n # No-op in every consumer repo, where the published tarball already\n # ships `dist/`.\n - name: Build the Holocron CLI\n shell: bash\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: Cache Turbo artifacts\n if: ${{ hashFiles('turbo.json') != '' }}\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: .turbo\n key: turbo-${{ runner.os }}-${{ github.sha }}\n restore-keys: |\n turbo-${{ runner.os }}-\n";
|
|
1201
1306
|
//#endregion
|
|
1202
1307
|
//#region src/templates/reusable/actions/setup-node.yml
|
|
1203
1308
|
var setup_node_default = "name: Setup Node\ndescription: Install pnpm and Node.js with pnpm dependency caching.\n\ninputs:\n node-version:\n description: Node.js version\n required: false\n default: \"22.x\"\nruns:\n using: composite\n\n steps:\n - name: Setup pnpm\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4\n\n - name: Setup Node.js\n uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0\n with:\n node-version: ${{ hashFiles('.node-version') != '' && '' || inputs.node-version }}\n node-version-file: ${{ hashFiles('.node-version') != '' && '.node-version' || '' }}\n cache: ${{ hashFiles('pnpm-lock.yaml') != '' && 'pnpm' || '' }}\n # Do NOT add registry-url here. setup-node writes .npmrc with\n # _authToken=${NODE_AUTH_TOKEN} and sets NPM_CONFIG_USERCONFIG to it.\n # pnpm reads that file, fails env-var substitution when the token is\n # empty, and loses auth entirely. Without registry-url, pnpm 10.15+\n # handles Trusted Publishing via its own native OIDC exchange.\n\n - name: Add node_modules/.bin to PATH\n shell: bash\n run: echo \"$GITHUB_WORKSPACE/node_modules/.bin\" >> $GITHUB_PATH\n";
|
|
@@ -1232,12 +1337,12 @@ const REUSABLE_WORKFLOWS = {
|
|
|
1232
1337
|
"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
1338
|
"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
1339
|
"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",
|
|
1340
|
+
"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
1341
|
"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",
|
|
1237
1342
|
"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
1343
|
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
1344
|
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",
|
|
1240
|
-
preview: "name: Preview\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 name:\n description: Repo name prefix used to filter the docs site package (<name>-site); omit to run pnpm -C docs build\n required: false\n type: string\n default: \"\"\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 cloudflare-project:\n description: >\n Cloudflare Pages project name. Falls back to the CLOUDFLARE_PAGES_PROJECT\n org variable when omitted — set that variable once and all repos get previews\n without per-repo config.\n required: false\n type: string\n default: \"\"\n\njobs:\n deploy-preview:\n name: Preview\n if: ${{ github.event.action != 'closed' }}\n runs-on: ubuntu-latest\n permissions:\n contents: read\n deployments: write\n pull-requests: write\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' && inputs.name != '' }}\n env:\n SITE_NAME: ${{ inputs.name }}\n run: pnpm --filter @theholocron/\"$SITE_NAME\"-site build\n\n - name: Build docs site\n if: ${{ inputs.type == 'docs' && inputs.name == '' }}\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 SITE_NAME: ${{ inputs.name }}\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 if [ -n \"$SITE_NAME\" ]; then\n # Site built with base: /<name>/ — nest under _site/<name>/ so asset\n # paths match (/name/_astro/... → served at /name/_astro/...).\n mkdir -p \"_site/${SITE_NAME}\"\n cp -r docs/dist/. \"_site/${SITE_NAME}/\"\n # Redirect root to the base path so the preview URL lands correctly.\n printf \"/ /%s/ 301\\n\" \"$SITE_NAME\" > _site/_redirects\n else\n cp -r docs/dist/. _site/\n fi\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 - name: Install Wrangler\n # The action's install step runs npm/pnpm in the repo root; pnpm fails\n # because it rejects adding to a workspace root without -w, and npm fails\n # because package.json contains pnpm catalog: references it cannot parse.\n # Pre-installing from /tmp (no package.json) lets the action find wrangler\n # already on PATH and skip its own install entirely.\n working-directory: /tmp\n run: npm install -g wrangler@4\n\n - uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0\n id: deploy\n name: Deploy to Cloudflare Pages\n # Skip when neither the per-repo input nor the org variable is set.\n if: ${{ inputs.cloudflare-project != '' || vars.CLOUDFLARE_PAGES_PROJECT != '' }}\n with:\n apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n accountId: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}\n packageManager: npm\n command: >-\n pages deploy _site\n --project-name ${{ inputs.cloudflare-project || vars.CLOUDFLARE_PAGES_PROJECT }}\n --branch ${{ github.event.repository.name }}-pr-${{ github.event.pull_request.number }}\n --commit-dirty=true\n\n - name: Report preview URL\n if: ${{ steps.deploy.outputs.pages-deployment-alias-url != '' }}\n env:\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n ALIAS: ${{ steps.deploy.outputs.pages-deployment-alias-url }}\n SITE_NAME: ${{ inputs.name }}\n PROJECT: ${{ inputs.cloudflare-project || vars.CLOUDFLARE_PAGES_PROJECT }}\n run: |\n PREVIEW_URL=\"${ALIAS%/}${SITE_NAME:+/${SITE_NAME}}/\"\n # GITHUB_HEAD_REF is the PR branch name — links the deployment to this PR's sidebar widget.\n PAYLOAD=$(printf '{\"ref\":\"%s\",\"environment\":\"%s\",\"description\":\"Cloudflare Pages\",\"production_environment\":false,\"auto_merge\":false,\"required_contexts\":[]}' \\\n \"$GITHUB_HEAD_REF\" \"${PROJECT} (Preview)\")\n DEPLOY_ID=$(echo \"$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\n cleanup:\n name: Clean up Preview\n if: ${{ github.event.action == 'closed' }}\n runs-on: ubuntu-latest\n permissions:\n contents: read\n deployments: write\n pull-requests: write\n steps:\n - name: Delete Cloudflare Pages deployments for branch\n if: ${{ inputs.cloudflare-project != '' || vars.CLOUDFLARE_PAGES_PROJECT != '' }}\n env:\n CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}\n PROJECT: ${{ inputs.cloudflare-project || vars.CLOUDFLARE_PAGES_PROJECT }}\n BRANCH: ${{ github.event.repository.name }}-pr-${{ github.event.pull_request.number }}\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n run: |\n DEPLOYMENTS=$(curl -s \\\n \"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${PROJECT}/deployments\" \\\n -H \"Authorization: Bearer ${CLOUDFLARE_API_TOKEN}\" \\\n | jq -r --arg b \"$BRANCH\" \\\n '.result[] | select(.deployment_trigger.metadata.branch == $b) | .id')\n\n if [ -z \"$DEPLOYMENTS\" ]; then\n echo \"No deployments found for branch ${BRANCH} — nothing to clean up.\"\n exit 0\n fi\n\n for id in $DEPLOYMENTS; do\n echo \"Deleting CF Pages deployment: $id\"\n curl -s -X DELETE \\\n \"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${PROJECT}/deployments/${id}?force=true\" \\\n -H \"Authorization: Bearer ${CLOUDFLARE_API_TOKEN}\" | jq -r 'if .success then \" ✓ deleted\" else \" ✗ \\(.errors[0].message)\" end'\n done\n\n # Mark the GitHub Deployment environment as inactive.\n ENV_NAME=\"${PROJECT} (Preview)\"\n gh api \"repos/${GITHUB_REPOSITORY}/deployments\" \\\n | jq -r \".[] | select(.environment == \\\"${ENV_NAME}\\\") | .id\" \\\n | while read -r deploy_id; do\n gh api \"repos/${GITHUB_REPOSITORY}/deployments/${deploy_id}/statuses\" \\\n --method POST --field state=inactive 2>/dev/null || true\n done\n",
|
|
1345
|
+
preview: "name: Preview\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 name:\n description: Repo name prefix used to filter the docs site package (<name>-site); omit to run pnpm -C docs build\n required: false\n type: string\n default: \"\"\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 cloudflare-project:\n description: >\n Cloudflare Pages project name. Falls back to the CLOUDFLARE_PAGES_PROJECT\n org variable when omitted — set that variable once and all repos get previews\n without per-repo config.\n required: false\n type: string\n default: \"\"\n\njobs:\n deploy-preview:\n name: Preview\n if: ${{ github.event.action != 'closed' }}\n runs-on: ubuntu-latest\n permissions:\n contents: read\n deployments: write\n pull-requests: write\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 workspace dependencies\n # The docs site imports workspace packages (e.g. a shared theme) via\n # `workspace:*` — its exports map points at dist/, which doesn't\n # exist until something builds it. Nothing else in this job does,\n # so without this step Vite fails to resolve the import entirely.\n if: ${{ inputs.type == 'docs' }}\n run: pnpm run build\n\n - name: Build docs site\n if: ${{ inputs.type == 'docs' && inputs.name != '' }}\n env:\n SITE_NAME: ${{ inputs.name }}\n run: pnpm --filter @theholocron/\"$SITE_NAME\"-site build\n\n - name: Build docs site\n if: ${{ inputs.type == 'docs' && inputs.name == '' }}\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 SITE_NAME: ${{ inputs.name }}\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 if [ -n \"$SITE_NAME\" ]; then\n # Site built with base: /<name>/ — nest under _site/<name>/ so asset\n # paths match (/name/_astro/... → served at /name/_astro/...).\n mkdir -p \"_site/${SITE_NAME}\"\n cp -r docs/dist/. \"_site/${SITE_NAME}/\"\n # Redirect root to the base path so the preview URL lands correctly.\n printf \"/ /%s/ 301\\n\" \"$SITE_NAME\" > _site/_redirects\n else\n cp -r docs/dist/. _site/\n fi\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 - name: Install Wrangler\n # The action's install step runs npm/pnpm in the repo root; pnpm fails\n # because it rejects adding to a workspace root without -w, and npm fails\n # because package.json contains pnpm catalog: references it cannot parse.\n # Pre-installing from /tmp (no package.json) lets the action find wrangler\n # already on PATH and skip its own install entirely.\n working-directory: /tmp\n run: npm install -g wrangler@4\n\n - uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0\n id: deploy\n name: Deploy to Cloudflare Pages\n # Skip when neither the per-repo input nor the org variable is set.\n if: ${{ inputs.cloudflare-project != '' || vars.CLOUDFLARE_PAGES_PROJECT != '' }}\n with:\n apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n accountId: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}\n packageManager: npm\n command: >-\n pages deploy _site\n --project-name ${{ inputs.cloudflare-project || vars.CLOUDFLARE_PAGES_PROJECT }}\n --branch ${{ github.event.repository.name }}-pr-${{ github.event.pull_request.number }}\n --commit-dirty=true\n\n - name: Report preview URL\n if: ${{ steps.deploy.outputs.pages-deployment-alias-url != '' }}\n env:\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n ALIAS: ${{ steps.deploy.outputs.pages-deployment-alias-url }}\n SITE_NAME: ${{ inputs.name }}\n PROJECT: ${{ inputs.cloudflare-project || vars.CLOUDFLARE_PAGES_PROJECT }}\n run: |\n PREVIEW_URL=\"${ALIAS%/}${SITE_NAME:+/${SITE_NAME}}/\"\n # GITHUB_HEAD_REF is the PR branch name — links the deployment to this PR's sidebar widget.\n PAYLOAD=$(printf '{\"ref\":\"%s\",\"environment\":\"%s\",\"description\":\"Cloudflare Pages\",\"production_environment\":false,\"auto_merge\":false,\"required_contexts\":[]}' \\\n \"$GITHUB_HEAD_REF\" \"${PROJECT} (Preview)\")\n DEPLOY_ID=$(echo \"$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\n cleanup:\n name: Clean up Preview\n if: ${{ github.event.action == 'closed' }}\n runs-on: ubuntu-latest\n permissions:\n contents: read\n deployments: write\n pull-requests: write\n steps:\n - name: Delete Cloudflare Pages deployments for branch\n if: ${{ inputs.cloudflare-project != '' || vars.CLOUDFLARE_PAGES_PROJECT != '' }}\n env:\n CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}\n PROJECT: ${{ inputs.cloudflare-project || vars.CLOUDFLARE_PAGES_PROJECT }}\n BRANCH: ${{ github.event.repository.name }}-pr-${{ github.event.pull_request.number }}\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n run: |\n DEPLOYMENTS=$(curl -s \\\n \"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${PROJECT}/deployments\" \\\n -H \"Authorization: Bearer ${CLOUDFLARE_API_TOKEN}\" \\\n | jq -r --arg b \"$BRANCH\" \\\n '.result[] | select(.deployment_trigger.metadata.branch == $b) | .id')\n\n if [ -z \"$DEPLOYMENTS\" ]; then\n echo \"No deployments found for branch ${BRANCH} — nothing to clean up.\"\n exit 0\n fi\n\n for id in $DEPLOYMENTS; do\n echo \"Deleting CF Pages deployment: $id\"\n curl -s -X DELETE \\\n \"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${PROJECT}/deployments/${id}?force=true\" \\\n -H \"Authorization: Bearer ${CLOUDFLARE_API_TOKEN}\" | jq -r 'if .success then \" ✓ deleted\" else \" ✗ \\(.errors[0].message)\" end'\n done\n\n # Mark the GitHub Deployment environment as inactive.\n ENV_NAME=\"${PROJECT} (Preview)\"\n gh api \"repos/${GITHUB_REPOSITORY}/deployments\" \\\n | jq -r \".[] | select(.environment == \\\"${ENV_NAME}\\\") | .id\" \\\n | while read -r deploy_id; do\n gh api \"repos/${GITHUB_REPOSITORY}/deployments/${deploy_id}/statuses\" \\\n --method POST --field state=inactive 2>/dev/null || true\n done\n",
|
|
1241
1346
|
greetings: "name: Greetings\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n\njobs:\n greeting:\n name: Greet first-time contributors\n permissions:\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 5\n # Group by the issue/PR number so duplicate events don't race each other.\n steps:\n - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0\n name: Greet on first contribution\n with:\n script: |\n // Only greet on the initial open — ignore synchronize, reopened, etc.\n if (context.payload.action !== 'opened') return;\n\n const actor = context.actor;\n const { owner, repo } = context.repo;\n\n // Payload inspection is more reliable than context.eventName for detecting\n // whether this is an issue vs. PR event — works regardless of how GitHub\n // propagates event names through workflow_call chains.\n const isIssue = !!context.payload.issue && !context.payload.pull_request;\n // listForRepo returns both issues and PRs (GitHub treats PRs as issues),\n // sorted newest-first. Filter by type to track first-issue and first-PR\n // independently, and avoid search-index eventual-consistency lag.\n const { data: recent } = await github.rest.issues.listForRepo({\n owner, repo,\n creator: actor,\n state: 'all',\n per_page: 100\n });\n\n const sameType = recent.filter(item =>\n isIssue ? !item.pull_request : !!item.pull_request\n );\n\n if (sameType.length !== 1) return;\n const body = isIssue\n ? `Hey @${actor}!\\n\\nWe really appreciate you taking the time to report an issue. The collaborators on this project attempt to help as many people as possible, but we are a limited number of volunteers, so it is possible that this will not be addressed as swiftly.\\n\\nYour patience is much appreciated and we will get back to you as quickly as possible.`\n : `Hey @${actor}!\\n\\nWe really appreciate you taking the time to help out with this PR. The collaborators on this project attempt to help as many people as possible, but we are a limited number of volunteers, so it is possible that this will not be addressed as swiftly.\\n\\nYour patience is much appreciated and we will get back to you as quickly as possible.`;\n\n await github.rest.issues.createComment({\n owner,\n repo,\n issue_number: context.issue.number,\n body\n });\n",
|
|
1242
1347
|
review: "name: Review\n\n# ReviewDog is the annotation layer — posts inline PR diff annotations.\n# Runs on pull_request only: inline annotations require PR context,\n# and branch protection ensures all changes go through PRs anyway.\n# security.secretDetection.yml / sourceQuality.structuredDataValidation.yml\n# are the CI gates covering push + PR events. Gitleaks and YAML are\n# intentionally duplicated: those gate merges; ReviewDog surfaces exact\n# line annotations in the PR diff.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n\njobs:\n reviewdog:\n name: Post inline lint annotations\n runs-on: ubuntu-latest\n timeout-minutes: 20\n permissions:\n contents: read\n pull-requests: write\n\n steps:\n - name: Checkout repository\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n\n - name: Setup\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n uses: theholocron/.github/.github/actions/setup@main\n\n - name: Install ReviewDog\n uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1\n with:\n reviewdog_version: latest\n\n # Detect which tools are relevant for this repo, excluding node_modules.\n # hashFiles('**/*') recurses into node_modules/.pnpm and produces false\n # positives for repos that don't own those file types.\n # -print -quit stops find after the first match without a pipe, avoiding\n # the SIGPIPE/pipefail exit-141 that find|head-1 triggers under\n # GitHub Actions' default bash --noprofile --norc -e -o pipefail mode.\n - name: Detect project features\n id: detect\n shell: bash\n run: |\n has() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n has_ext() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n { { has 'eslint.config.js' || has 'eslint.config.mjs' || has 'eslint.config.cjs' || \\\n has 'eslint.config.ts' || has '.eslintrc' || has '.eslintrc.js' || \\\n has '.eslintrc.cjs' || has '.eslintrc.json' || has '.eslintrc.yaml' || \\\n has '.eslintrc.yml'; } && grep -qF '\"eslint\":' package.json 2>/dev/null; } && echo \"eslint=true\" >> \"$GITHUB_OUTPUT\" || echo \"eslint=false\" >> \"$GITHUB_OUTPUT\"\n { has 'tsconfig.json' && grep -qF '\"typescript\":' package.json 2>/dev/null; } && echo \"tsconfig=true\" >> \"$GITHUB_OUTPUT\" || echo \"tsconfig=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.sh' && echo \"shell=true\" >> \"$GITHUB_OUTPUT\" || echo \"shell=false\" >> \"$GITHUB_OUTPUT\"\n has 'Dockerfile' || has_ext '*.Dockerfile' || has 'Containerfile' && \\\n echo \"docker=true\" >> \"$GITHUB_OUTPUT\" || echo \"docker=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '.env*' && echo \"dotenv=true\" >> \"$GITHUB_OUTPUT\" || echo \"dotenv=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.md' && echo \"markdown=true\" >> \"$GITHUB_OUTPUT\" || echo \"markdown=false\" >> \"$GITHUB_OUTPUT\"\n\n #\n # Always applicable\n #\n\n - name: Gitleaks (secrets)\n uses: reviewdog/action-gitleaks@2b7b5685e3e3eecddab5d30cfa04f18123031421 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / gitleaks\"\n fail_level: error\n gitleaks_flags: --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}\n\n - name: YamlLint\n if: ${{ hashFiles('.yamllint.yml') != '' }}\n uses: reviewdog/action-yamllint@b5f7217d8c815ae374d1d55840d5e569d82f01f0 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / yamllint\"\n fail_level: error\n yamllint_flags: -c ${{ github.workspace }}/.yamllint.yml ${{ github.workspace }}\n\n - name: ActionLint (GitHub Actions)\n if: ${{ hashFiles('.github/workflows/*.yml', '.github/workflows/*.yaml') != '' }}\n uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / actionlint\"\n fail_level: error\n\n #\n # TypeScript / JavaScript\n #\n\n - name: ESLint\n if: steps.detect.outputs.eslint == 'true'\n uses: reviewdog/action-eslint@556a3fdaf8b4201d4d74d406013386aa4f7dab96 # v1.34.0\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / eslint\"\n fail_level: error\n eslint_flags: .\n\n - name: TypeScript\n if: steps.detect.outputs.tsconfig == 'true'\n uses: EPMatt/reviewdog-action-tsc@63d923a3c5b4497671940b8874f58a404e2351b5 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / tsc\"\n fail_level: error\n\n #\n # Shell\n #\n\n - name: ShellCheck\n if: steps.detect.outputs.shell == 'true'\n uses: reviewdog/action-shellcheck@4c07458293ac342d477251099501a718ae5ef86e # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / shellcheck\"\n fail_level: none\n\n #\n # Docker\n #\n\n - name: Hadolint\n if: steps.detect.outputs.docker == 'true'\n uses: reviewdog/action-hadolint@1b2cfa6ba72072ad35158d7ff3aa49bbdc03506d # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / hadolint\"\n fail_level: none\n\n #\n # Environment files\n #\n\n - name: dotenv-linter\n if: steps.detect.outputs.dotenv == 'true'\n uses: dotenv-linter/action-dotenv-linter@afde61cfda2ecffe7bea35837b6f20b956c88689 # v3.0.0\n with:\n reporter: github-code-suggestions\n\n #\n # Documentation\n #\n\n - name: Alex (inclusive language)\n if: steps.detect.outputs.markdown == 'true'\n uses: reviewdog/action-alex@347481655add010a2ae302df34b57c9bcfa0d6e4 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / alex\"\n",
|
|
1243
1348
|
stale: "name: Stale\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n days-before-stale:\n description: Days of inactivity before an issue or PR is marked stale (applies to both unless type-specific value is set)\n type: number\n required: false\n default: 30\n days-before-close:\n description: Days after stale label before closing (applies to both unless type-specific value is set)\n type: number\n required: false\n default: 5\n days-before-issue-stale:\n description: Days of inactivity before an issue is marked stale; overrides days-before-stale when set to a non-negative value\n type: number\n required: false\n default: -1\n days-before-issue-close:\n description: Days after stale label before closing an issue; overrides days-before-close when set to a non-negative value\n type: number\n required: false\n default: -1\n days-before-pr-stale:\n description: Days of inactivity before a PR is marked stale; overrides days-before-stale when set to a non-negative value\n type: number\n required: false\n default: -1\n days-before-pr-close:\n description: Days after stale label before closing a PR; overrides days-before-close when set to a non-negative value\n type: number\n required: false\n default: -1\n exempt-issue-labels:\n description: Comma-separated labels that exempt an issue from being marked stale\n type: string\n required: false\n default: \"in-progress,wip\"\n exempt-pr-labels:\n description: Comma-separated labels that exempt a PR from being marked stale\n type: string\n required: false\n default: \"\"\n exempt-all-issue-milestones:\n description: Issues assigned to any milestone are never marked stale\n type: boolean\n required: false\n default: true\n exempt-all-pr-milestones:\n description: PRs assigned to any milestone are never marked stale\n type: boolean\n required: false\n default: true\n exempt-all-issue-projects:\n description: Issues assigned to any project are never marked stale\n type: boolean\n required: false\n default: true\n exempt-all-pr-projects:\n description: PRs assigned to any project are never marked stale\n type: boolean\n required: false\n default: true\n\njobs:\n stale:\n name: Mark stale issues and pull requests\n permissions:\n contents: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0\n name: Run Stale\n with:\n close-issue-message: >\n This issue was closed because it has been stalled for\n ${{ inputs.days-before-close }} days with no activity.\n days-before-close: ${{ inputs.days-before-close }}\n days-before-issue-close: ${{ inputs.days-before-issue-close }}\n days-before-issue-stale: ${{ inputs.days-before-issue-stale }}\n days-before-pr-close: ${{ inputs.days-before-pr-close }}\n days-before-pr-stale: ${{ inputs.days-before-pr-stale }}\n days-before-stale: ${{ inputs.days-before-stale }}\n exempt-all-issue-milestones: ${{ inputs.exempt-all-issue-milestones }}\n exempt-all-issue-projects: ${{ inputs.exempt-all-issue-projects }}\n exempt-all-pr-milestones: ${{ inputs.exempt-all-pr-milestones }}\n exempt-all-pr-projects: ${{ inputs.exempt-all-pr-projects }}\n exempt-issue-labels: ${{ inputs.exempt-issue-labels }}\n exempt-pr-labels: ${{ inputs.exempt-pr-labels }}\n stale-issue-label: wontfix\n stale-issue-message: >\n This issue is stale because it has been open ${{ inputs.days-before-stale }}\n days with no activity. Remove the stale label or comment, or this will be\n closed in ${{ inputs.days-before-close }} days.\n stale-pr-label: wontfix\n stale-pr-message: >\n This PR is stale because it has been open ${{ inputs.days-before-stale }}\n days with no activity. Remove the stale label or comment, or this will be\n closed in ${{ inputs.days-before-close }} days.\n",
|
|
@@ -1339,6 +1444,72 @@ function turboConfig(config) {
|
|
|
1339
1444
|
tasks
|
|
1340
1445
|
}, null, 2)}\n`;
|
|
1341
1446
|
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Fixes #692: `turboConfig()` writes a `turbo.json` the moment *any* task in
|
|
1449
|
+
* the manifest has fan-out config — with no check for whether the repo's own
|
|
1450
|
+
* root package is actually covered by `pnpm-workspace.yaml`'s `packages:`
|
|
1451
|
+
* list. A repo shaped like `observability` (the library lives at repo root;
|
|
1452
|
+
* `packages:` lists only an unrelated `docs` site) silently breaks the
|
|
1453
|
+
* instant `turbo.json` exists: `holocron run <task>` switches from running
|
|
1454
|
+
* root's own script directly to `turbo run <task>`, which only sees declared
|
|
1455
|
+
* workspace members — root vanishes, the task "succeeds" with zero real work
|
|
1456
|
+
* done (confirmed empirically: `Packages in scope: docs`, root never
|
|
1457
|
+
* mentioned, exit 0).
|
|
1458
|
+
*
|
|
1459
|
+
* Detection is narrow and specific: root only needs to be an explicit
|
|
1460
|
+
* workspace member if its *own* `package.json` has a script literally named
|
|
1461
|
+
* after one of the tasks turbo.json is about to fan out — a plain
|
|
1462
|
+
* orchestrator root (`holocron`'s own `"build": "turbo run delivery.build"`,
|
|
1463
|
+
* which has no `delivery.build` script itself) never trips this; only a root
|
|
1464
|
+
* that's genuinely a directly-buildable package does. Confirmed the fix
|
|
1465
|
+
* empirically too — adding `.` to `packages:` makes turbo pick root back up
|
|
1466
|
+
* (`Packages in scope: docs, root-lib`) without disturbing the sibling
|
|
1467
|
+
* package's own caching.
|
|
1468
|
+
*
|
|
1469
|
+
* A targeted line-based edit, not a full YAML parse/reserialize — same
|
|
1470
|
+
* pattern `codecov.ts`'s `mergeCodecovComponents()` already uses for editing
|
|
1471
|
+
* an existing generated file. `pnpm-workspace.yaml` routinely carries a
|
|
1472
|
+
* `catalog:`/`catalogs:`/`overrides:` block after `packages:`; a real parser
|
|
1473
|
+
* round-trip risks reformatting or reordering content nobody asked to touch.
|
|
1474
|
+
* Only ever *adds* a line — never rewrites or reorders anything already
|
|
1475
|
+
* there — and is a no-op (`changed: false`) whenever root doesn't need it,
|
|
1476
|
+
* root is already listed, or `packages:` isn't in the plain block-list form
|
|
1477
|
+
* every repo checked actually uses.
|
|
1478
|
+
*/
|
|
1479
|
+
function ensureRootWorkspaceMember(workspaceYaml, rootScripts, taskNames) {
|
|
1480
|
+
if (!taskNames.some((t) => rootScripts.includes(t))) return {
|
|
1481
|
+
content: workspaceYaml,
|
|
1482
|
+
changed: false
|
|
1483
|
+
};
|
|
1484
|
+
const lines = workspaceYaml.split("\n");
|
|
1485
|
+
const packagesLineIdx = lines.findIndex((l) => /^packages:\s*$/.test(l));
|
|
1486
|
+
if (packagesLineIdx === -1) return {
|
|
1487
|
+
content: workspaceYaml,
|
|
1488
|
+
changed: false
|
|
1489
|
+
};
|
|
1490
|
+
const items = [];
|
|
1491
|
+
let cursor = packagesLineIdx + 1;
|
|
1492
|
+
while (cursor < lines.length && /^\s*-\s*/.test(lines[cursor])) {
|
|
1493
|
+
items.push(lines[cursor]);
|
|
1494
|
+
cursor++;
|
|
1495
|
+
}
|
|
1496
|
+
if (items.some((item) => {
|
|
1497
|
+
const value = item.replace(/^\s*-\s*/, "").replace(/^["']|["']$/g, "").trim();
|
|
1498
|
+
return value === "." || value === "";
|
|
1499
|
+
})) return {
|
|
1500
|
+
content: workspaceYaml,
|
|
1501
|
+
changed: false
|
|
1502
|
+
};
|
|
1503
|
+
const newLine = `${items[0]?.match(/^(\s*-\s*)/)?.[1] ?? " - "}"."`;
|
|
1504
|
+
return {
|
|
1505
|
+
content: [
|
|
1506
|
+
...lines.slice(0, packagesLineIdx + 1),
|
|
1507
|
+
newLine,
|
|
1508
|
+
...lines.slice(packagesLineIdx + 1)
|
|
1509
|
+
].join("\n"),
|
|
1510
|
+
changed: true
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1342
1513
|
//#endregion
|
|
1343
1514
|
//#region src/astromech.ts
|
|
1344
1515
|
/**
|
|
@@ -1441,8 +1612,38 @@ function createAstromech(options) {
|
|
|
1441
1612
|
reusableTemplates: () => reusableTemplates(),
|
|
1442
1613
|
requiredChecks: () => requiredChecks(options.config ?? {}),
|
|
1443
1614
|
codecovConfig: (existing) => codecovConfig(options.cwd, existing),
|
|
1444
|
-
turboConfig: () => turboConfig(options.config ?? {})
|
|
1615
|
+
turboConfig: () => turboConfig(options.config ?? {}),
|
|
1616
|
+
ensureRootWorkspaceMember: (workspaceYaml, rootScripts) => {
|
|
1617
|
+
return ensureRootWorkspaceMember(workspaceYaml, rootScripts, (options.config?.tasks ?? []).map(normalizeTaskEntry).filter((e) => e.local !== false).map((e) => e.name));
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
//#endregion
|
|
1622
|
+
//#region src/tsconfig.ts
|
|
1623
|
+
/**
|
|
1624
|
+
* A package-level `tsconfig.json` — the uniform shape confirmed across
|
|
1625
|
+
* every package checked, parameterized only by the two fields that
|
|
1626
|
+
* genuinely vary (`display`, `paths`). No scaffold/workflow header (matches
|
|
1627
|
+
* `.alexrc.json`'s existing precedent for a strict-JSON Bucket B file —
|
|
1628
|
+
* JSON has no comment syntax to carry one, and TypeScript's own tolerance
|
|
1629
|
+
* for JSONC comments in `tsconfig.json` isn't worth relying on for a file
|
|
1630
|
+
* this thin).
|
|
1631
|
+
*/
|
|
1632
|
+
function createTsconfig(options) {
|
|
1633
|
+
const variant = options.variant ?? "node-lts";
|
|
1634
|
+
const compilerOptions = {
|
|
1635
|
+
baseUrl: "./",
|
|
1636
|
+
outDir: "./dist"
|
|
1637
|
+
};
|
|
1638
|
+
if (options.paths) compilerOptions.paths = { "@/*": ["./src/*"] };
|
|
1639
|
+
const config = {
|
|
1640
|
+
display: options.display,
|
|
1641
|
+
extends: `@theholocron/tsconfig/${variant}`,
|
|
1642
|
+
compilerOptions,
|
|
1643
|
+
include: ["src/**/*.ts"],
|
|
1644
|
+
exclude: ["node_modules", "dist"]
|
|
1445
1645
|
};
|
|
1646
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
1446
1647
|
}
|
|
1447
1648
|
//#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 };
|
|
1649
|
+
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.30",
|
|
4
4
|
"description": "The Holocron task runner — one task manifest drives `holocron run`, `holocron ci`, the CI workflows, package.json scripts, linters, and required checks.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@theholocron/datapad": "5.0.0-alpha.
|
|
40
|
+
"@theholocron/datapad": "5.0.0-alpha.30"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@theholocron/eslint-config": "^8.0.0",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"tsdown": "^0.22.14",
|
|
54
54
|
"typescript": "^5.9.3",
|
|
55
55
|
"vitest": "^4.1.11",
|
|
56
|
-
"@theholocron/rollup-plugin-transform-template": "5.0.0-alpha.
|
|
56
|
+
"@theholocron/rollup-plugin-transform-template": "5.0.0-alpha.30"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=22"
|