@theholocron/cli 3.29.11 → 3.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.mjs +199 -104
  2. package/dist/cli.mjs.map +1 -1
  3. package/package.json +23 -10
package/dist/cli.mjs CHANGED
@@ -14,6 +14,8 @@ import { homedir } from "node:os";
14
14
  import { createHash } from "node:crypto";
15
15
  import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
16
16
  import { pathToFileURL } from "node:url";
17
+ import { generateReadme } from "@theholocron/components-doc/markdown";
18
+ import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
17
19
  import { createGitHubClient } from "@theholocron/github-client";
18
20
  import { promisify } from "node:util";
19
21
  import * as Sentry from "@sentry/node";
@@ -2696,6 +2698,9 @@ var greetings_default$1 = "name: Greetings\n\non: # yamllint disable-line rule:t
2696
2698
  //#region src/commands/workflows/lint.yml
2697
2699
  var lint_default$1 = "name: Lint\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: lint-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write\n issues: write\n statuses: write\n\njobs:\n lint:\n name: Lint\n uses: theholocron/.github/.github/workflows/lint.yml@main\n secrets: inherit\n with:\n enable-auto-commit: true\n";
2698
2700
  //#endregion
2701
+ //#region src/commands/workflows/post-release.yml
2702
+ var post_release_default$1 = "name: Post-release Sync\n\non: # yamllint disable-line rule:truthy\n release:\n types: [published]\n\npermissions:\n contents: read\n\njobs:\n broadcast:\n name: Post-release Sync\n uses: theholocron/.github/.github/workflows/post-release.yml@main\n secrets: inherit\n";
2703
+ //#endregion
2699
2704
  //#region src/commands/workflows/release.yml
2700
2705
  var release_default$1 = "name: Release\n\non: # yamllint disable-line rule:truthy\n push:\n branches:\n - main\n - alpha\n workflow_dispatch:\n\npermissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n\nconcurrency:\n group: ${{ github.workflow }}-${{ github.ref }}\n cancel-in-progress: false\n\njobs:\n release:\n uses: theholocron/.github/.github/workflows/release.yml@main\n secrets: inherit\n";
2701
2706
  //#endregion
@@ -2705,6 +2710,9 @@ var review_default$1 = "name: Review\n\non: # yamllint disable-line rule:truthy\
2705
2710
  //#region src/commands/workflows/stale.yml
2706
2711
  var stale_default$1 = "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 secrets: inherit\n";
2707
2712
  //#endregion
2713
+ //#region src/commands/workflows/sync.yml
2714
+ var sync_default$1 = "name: Sync\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n paths:\n - holocron.config.ts\n - package.json\n - pnpm-workspace.yaml\n workflow_dispatch:\n inputs:\n steps:\n description: \"Sync steps to run (default: all)\"\n type: string\n required: false\n\nconcurrency:\n group: sync-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write\n pull-requests: write\n\njobs:\n sync:\n name: Sync\n uses: theholocron/.github/.github/workflows/sync.yml@main\n with:\n steps: ${{ inputs.steps }}\n secrets: inherit\n";
2715
+ //#endregion
2708
2716
  //#region src/commands/workflows/test.yml
2709
2717
  var test_default$1 = "name: Test\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: test-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n id-token: write\n statuses: write\n\njobs:\n test:\n name: Test\n uses: theholocron/.github/.github/workflows/test.yml@main\n with:\n run-unit: true\n secrets: inherit\n";
2710
2718
  //#endregion
@@ -2712,13 +2720,22 @@ var test_default$1 = "name: Test\n\non: # yamllint disable-line rule:truthy\n p
2712
2720
  var typecheck_default$1 = "name: Typecheck\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: typecheck-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n\njobs:\n typecheck:\n name: Typecheck\n uses: theholocron/.github/.github/workflows/typecheck.yml@main\n secrets: inherit\n";
2713
2721
  //#endregion
2714
2722
  //#region src/commands/setup-workflows.ts
2715
- /** Header prepended when holocron setup writes a generated file to a repo. */
2716
- function workflowHeader(source = "packages/cli/src/commands/setup-workflows.ts") {
2723
+ /**
2724
+ * Header prepended to every auto-generated workflow thin caller.
2725
+ *
2726
+ * Used by both `holocron setup` (initial creation) and `holocron sync-github`
2727
+ * (subsequent updates) so the header is always identical regardless of which
2728
+ * command last wrote the file.
2729
+ *
2730
+ * @param source - path within theholocron/holocron that owns the template
2731
+ * @param forPrimary - true only when writing to theholocron/.github itself
2732
+ */
2733
+ function workflowHeader(source = "packages/cli/src/commands/setup-workflows.ts", forPrimary = false) {
2717
2734
  return [
2718
- `# AUTO-GENERATED — do not edit directly.`,
2735
+ forPrimary ? `# AUTO-GENERATED — do not edit in theholocron/.github directly.` : `# AUTO-GENERATED — do not edit directly.`,
2719
2736
  `# Source: theholocron/holocron · ${source}`,
2720
- `# Tool: holocron setup`,
2721
- `# Changes: run \`holocron setup\` to regenerate.`,
2737
+ `# Tool: holocron sync-github`,
2738
+ `# Changes: edit source in theholocron/holocron and push to alpha or main.`,
2722
2739
  ``
2723
2740
  ].join("\n");
2724
2741
  }
@@ -2735,8 +2752,10 @@ const WORKFLOW_TEMPLATES = {
2735
2752
  typecheck: typecheck_default$1,
2736
2753
  codeql: codeql_default$1,
2737
2754
  review: review_default$1,
2755
+ "post-release": post_release_default$1,
2738
2756
  release: release_default$1,
2739
2757
  stale: stale_default$1,
2758
+ sync: sync_default$1,
2740
2759
  greetings: greetings_default$1,
2741
2760
  dependencies: dependencies_default$1,
2742
2761
  bookkeeping: bookkeeping_default$1,
@@ -3978,6 +3997,138 @@ function runSkillsUpdate(input) {
3978
3997
  return { status: exitCode === 0 ? "ok" : "fail" };
3979
3998
  }
3980
3999
  //#endregion
4000
+ //#region src/commands/sync-readme.ts
4001
+ const MARKER_INSTALL_START = "<!-- holocron:installation -->";
4002
+ const MARKER_INSTALL_END = "<!-- /holocron:installation -->";
4003
+ const REGISTRY_MAP = {
4004
+ "@theholocron/clients": getClients,
4005
+ "@theholocron/configs": getConfigs,
4006
+ "@theholocron/docs": getDocs,
4007
+ "@theholocron/holocron": getPlugins,
4008
+ "@theholocron/skills": getSkills,
4009
+ "@theholocron/themes": getThemes,
4010
+ "@theholocron/utils": getUtils
4011
+ };
4012
+ function generateInstallBlock(pkg) {
4013
+ const { name = "", bin, peerDependencies = {} } = pkg;
4014
+ const isCli = Boolean(bin);
4015
+ const isReact = Boolean(peerDependencies["react"]);
4016
+ const lines = [];
4017
+ lines.push("## Installation", "");
4018
+ if (isCli) lines.push("```bash", `npm install --global ${name}`, "```");
4019
+ else lines.push("```bash", `pnpm install ${name}`, "```");
4020
+ lines.push("", "## Usage", "");
4021
+ if (isCli) {
4022
+ const commands = typeof bin === "string" ? [name.split("/").at(-1)] : Object.keys(bin);
4023
+ lines.push("```bash");
4024
+ for (const cmd of commands) lines.push(`${cmd} --help`);
4025
+ lines.push("```");
4026
+ } else if (isReact) lines.push("```tsx", `import { } from "${name}";`, "", "function App() {", ` return <></>;`, "}", "```");
4027
+ else lines.push("```typescript", `import { } from "${name}";`, "```");
4028
+ return lines.join("\n");
4029
+ }
4030
+ /**
4031
+ * Replace content between `<!-- holocron:marker -->` / `<!-- /holocron:marker -->` tags.
4032
+ * Returns the updated README string, or the original if the marker pair is absent.
4033
+ */
4034
+ function replaceMarkerBlock(readme, marker, content) {
4035
+ const start = `<!-- holocron:${marker} -->`;
4036
+ const end = `<!-- /holocron:${marker} -->`;
4037
+ const lines = readme.split("\n");
4038
+ const startIdx = lines.findIndex((l) => l.trim() === start);
4039
+ const endIdx = lines.findIndex((l) => l.trim() === end);
4040
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return readme;
4041
+ lines.splice(startIdx + 1, endIdx - startIdx - 1, "", ...content.split("\n"), "");
4042
+ return lines.join("\n");
4043
+ }
4044
+ /**
4045
+ * Apply all README updates in a single read-write cycle.
4046
+ * Returns true if the README was found (even if no changes were needed).
4047
+ */
4048
+ async function updateReadme(repoRoot, installBlock, sections, dryRun, readFileFn, writeFileFn) {
4049
+ const readmePath = join(repoRoot, "README.md");
4050
+ let content;
4051
+ try {
4052
+ content = await readFileFn(readmePath, "utf8");
4053
+ } catch {
4054
+ return false;
4055
+ }
4056
+ let updated = content;
4057
+ for (const [marker, text] of Object.entries(sections)) updated = replaceMarkerBlock(updated, marker, text);
4058
+ const lines = updated.split("\n");
4059
+ const startIdx = lines.findIndex((l) => l.trim() === MARKER_INSTALL_START);
4060
+ const endIdx = lines.findIndex((l) => l.trim() === MARKER_INSTALL_END);
4061
+ const blockLines = installBlock.split("\n");
4062
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
4063
+ lines.splice(startIdx + 1, endIdx - startIdx - 1, "", ...blockLines, "");
4064
+ updated = lines.join("\n");
4065
+ } else {
4066
+ const descEnd = lines.findIndex((l) => l.trim() === "<!-- /holocron:description -->");
4067
+ const insertAfter = descEnd !== -1 ? descEnd : lines.findIndex((l) => /^# /.test(l));
4068
+ if (insertAfter === -1) return false;
4069
+ lines.splice(insertAfter + 1, 0, "", MARKER_INSTALL_START, "", ...blockLines, "", MARKER_INSTALL_END);
4070
+ updated = lines.join("\n");
4071
+ }
4072
+ if (!dryRun) await writeFileFn(readmePath, updated, "utf8");
4073
+ return true;
4074
+ }
4075
+ async function updateIndexMdxDescription(repoRoot, description, dryRun, readFileFn, writeFileFn) {
4076
+ const mdxPath = join(repoRoot, "docs", "src", "content", "docs", "index.mdx");
4077
+ let content;
4078
+ try {
4079
+ content = await readFileFn(mdxPath, "utf8");
4080
+ } catch {
4081
+ return;
4082
+ }
4083
+ const updated = content.replace(/^(description:\s*).*$/m, `$1${description}`);
4084
+ if (updated !== content && !dryRun) await writeFileFn(mdxPath, updated, "utf8");
4085
+ }
4086
+ async function runSyncReadme(input) {
4087
+ const { loaded, context, print = console.log, readFileFn = readFile, writeFileFn = writeFile } = input;
4088
+ const { repoRoot, dryRun = false } = context;
4089
+ const namespaces = loaded.resolved.env?.namespaces ?? [];
4090
+ print(style.header(`Syncing README installation block${dryRun ? " (dry-run)" : ""}…`));
4091
+ if (namespaces.length > 0) print(style.hint(` namespaces: ${namespaces.join(", ")}`));
4092
+ let pkg;
4093
+ try {
4094
+ const raw = await readFileFn(join(repoRoot, "package.json"), "utf8");
4095
+ pkg = JSON.parse(raw);
4096
+ } catch {
4097
+ return {
4098
+ status: "fail",
4099
+ updated: false,
4100
+ message: "Could not read package.json"
4101
+ };
4102
+ }
4103
+ const description = loaded.resolved.description ?? "";
4104
+ const sections = generateReadme({
4105
+ description,
4106
+ homepage: loaded.resolved.homepage ?? pkg.homepage ?? "",
4107
+ scripts: pkg.scripts ?? {},
4108
+ packages: pkg.name ? REGISTRY_MAP[pkg.name]?.() : void 0
4109
+ });
4110
+ const markerSections = {
4111
+ ...sections.packages !== void 0 && { packages: sections.packages },
4112
+ development: sections.development,
4113
+ releases: sections.releases
4114
+ };
4115
+ const updated = await updateReadme(repoRoot, generateInstallBlock(pkg), markerSections, dryRun, readFileFn, writeFileFn);
4116
+ if (!updated) {
4117
+ print(style.warn("README.md not found or has no writable location for installation block"));
4118
+ return {
4119
+ status: "fail",
4120
+ updated: false,
4121
+ message: "README.md update failed"
4122
+ };
4123
+ }
4124
+ if (description) await updateIndexMdxDescription(repoRoot, description, dryRun, readFileFn, writeFileFn);
4125
+ print(dryRun ? style.hint(" would update README.md") : style.success(" updated README.md"));
4126
+ return {
4127
+ status: dryRun ? "dry-run" : "ok",
4128
+ updated
4129
+ };
4130
+ }
4131
+ //#endregion
3981
4132
  //#region src/commands/sync.ts
3982
4133
  const SYNC_STEPS = [
3983
4134
  "labels",
@@ -3986,12 +4137,14 @@ const SYNC_STEPS = [
3986
4137
  "topics",
3987
4138
  "keywords",
3988
4139
  "description",
3989
- "homepage"
4140
+ "homepage",
4141
+ "readme"
3990
4142
  ];
3991
4143
  const LOCAL_STEPS = /* @__PURE__ */ new Set([
3992
4144
  "keywords",
3993
4145
  "description",
3994
- "homepage"
4146
+ "homepage",
4147
+ "readme"
3995
4148
  ]);
3996
4149
  async function runSync(input) {
3997
4150
  const print = input.print ?? ((line) => console.log(line));
@@ -4129,7 +4282,8 @@ async function runSync(input) {
4129
4282
  for (const stepName of [
4130
4283
  "keywords",
4131
4284
  "description",
4132
- "homepage"
4285
+ "homepage",
4286
+ "readme"
4133
4287
  ]) {
4134
4288
  if (requestedSteps !== void 0 && !requestedSteps.includes(stepName)) continue;
4135
4289
  if (stepName === "keywords") {
@@ -4197,6 +4351,20 @@ async function runSync(input) {
4197
4351
  print(formatSyncStep(steps[steps.length - 1]));
4198
4352
  }
4199
4353
  }
4354
+ if (stepName === "readme") {
4355
+ const report = await runSyncReadme({
4356
+ loaded: input.loaded,
4357
+ context: input.context,
4358
+ print: () => {}
4359
+ });
4360
+ steps.push({
4361
+ capability: "local",
4362
+ step: "sync readme",
4363
+ status: report.status === "ok" ? "ok" : report.status === "dry-run" ? "dry-run" : "skip",
4364
+ ...report.message ? { message: report.message } : {}
4365
+ });
4366
+ print(formatSyncStep(steps[steps.length - 1]));
4367
+ }
4200
4368
  }
4201
4369
  const summary = steps.reduce((acc, s) => {
4202
4370
  if (s.status === "ok") acc.ok += 1;
@@ -4285,6 +4453,9 @@ async function updateReadmeDescription(repoRoot, description) {
4285
4453
  return true;
4286
4454
  }
4287
4455
  //#endregion
4456
+ //#region src/templates/actions/auto-commit.yml
4457
+ 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 - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0\n id: commit\n with:\n branch: ${{ inputs.branch }}\n commit_message: ${{ inputs.commit-message }}\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";
4458
+ //#endregion
4288
4459
  //#region src/templates/actions/install.yml
4289
4460
  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";
4290
4461
  //#endregion
@@ -4313,7 +4484,10 @@ var deploy_default = "name: Deploy\n\non: # yamllint disable-line rule:truthy\n
4313
4484
  var greetings_default = "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";
4314
4485
  //#endregion
4315
4486
  //#region src/templates/workflows/lint.yml
4316
- var lint_default = "name: Lint\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n eslint-config:\n description: >\n Filename for the ESLint flat config used by super-linter for\n JavaScript, JSX, TSX, and TypeScript (ES) files. Defaults to\n eslint.config.ts (the org standard). Note: ESLint 9 requires\n --flag unstable_ts_config to load .ts configs; if super-linter\n cannot load it, override with eslint.config.mjs or eslint.config.js.\n type: string\n required: false\n default: eslint.config.ts\n prettier-config:\n description: >\n Filename for the Prettier config. Defaults to prettier.config.ts\n (the org standard). Prettier 3.x loads .ts configs natively.\n type: string\n required: false\n default: prettier.config.ts\n yaml-config:\n description: >\n Filename for the yamllint config. Defaults to yamllint.config.yml.\n type: string\n required: false\n default: yamllint.config.yml\n enable-auto-commit:\n description: >\n Auto-commit super-linter fixes as a verified commit via a GitHub App.\n Requires SUPER_LINTER_APP_ID and SUPER_LINTER_PRIVATE_KEY secrets.\n type: boolean\n required: false\n default: false\n secrets:\n SUPER_LINTER_APP_ID:\n required: false\n SUPER_LINTER_PRIVATE_KEY:\n required: false\n\njobs:\n super-lint:\n name: Lint entire codebase\n permissions:\n contents: write\n issues: write\n statuses: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n env:\n APP_ID_SET: ${{ secrets.SUPER_LINTER_APP_ID != '' }}\n steps:\n - name: Generate GitHub App token\n id: app-token\n # Runs before checkout so the token is used as the checkout credential,\n # which makes the subsequent push go through the App and produce a\n # Verified commit. Skipped when auto-commit is disabled or secrets unset.\n if: >\n inputs.enable-auto-commit == true &&\n github.event.pull_request != null &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.ref_name != github.event.repository.default_branch &&\n env.APP_ID_SET == 'true'\n uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0\n with:\n app-id: ${{ secrets.SUPER_LINTER_APP_ID }}\n private-key: ${{ secrets.SUPER_LINTER_PRIVATE_KEY }}\n\n - name: Resolve App bot identity\n id: app-bot\n # GitHub marks commits as Verified when the author email matches the\n # App bot's noreply address (<numeric-id>+<slug>[bot]@users.noreply.github.com).\n # The numeric ID must be fetched via the API — it differs from the App ID.\n # app-slug is passed via env rather than interpolated into the script to\n # prevent code injection (CWE-78).\n if: steps.app-token.conclusion == 'success'\n run: |\n BOT_SLUG=\"${APP_SLUG}[bot]\"\n BOT_ID=$(gh api \"/users/${BOT_SLUG}\" --jq .id)\n echo \"name=${BOT_SLUG}\" >> \"$GITHUB_OUTPUT\"\n echo \"email=${BOT_ID}+${BOT_SLUG}@users.noreply.github.com\" >> \"$GITHUB_OUTPUT\"\n env:\n GH_TOKEN: ${{ steps.app-token.outputs.token }}\n APP_SLUG: ${{ steps.app-token.outputs.app-slug }}\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n # Use the App token when available so the push credential is the App\n # bot — GitHub marks those commits as Verified automatically.\n token: ${{ steps.app-token.outputs.token || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n\n - name: Detect project features\n # Writes VALIDATE_*/FIX_* to GITHUB_ENV only when the feature exists.\n # Also writes step outputs for values referenced in expression context\n # (GITHUB_ENV is not readable via steps.*.outputs — they need GITHUB_OUTPUT).\n # All values written are hardcoded 'true' — no user input in the script.\n # Config file paths (inputs.*) stay in GH Actions expression context in\n # the super-linter env: block below, never shell-evaluated (CWE-78).\n id: detect\n run: |\n has() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n\n if { has 'eslint.config.ts' || has 'eslint.config.mjs' || has 'eslint.config.js' || has '.eslintrc.json' || has '.eslintrc.yml'; }; then\n {\n echo \"VALIDATE_JAVASCRIPT_ES=true\"\n echo \"VALIDATE_TYPESCRIPT_ES=true\"\n } >> \"$GITHUB_ENV\"\n echo \"eslint=true\" >> \"$GITHUB_OUTPUT\"\n fi\n\n if { has '*.js' || has '*.jsx' || has '*.mjs' || has '*.cjs' || has '*.ts' || has '*.tsx'; }; then\n {\n echo \"VALIDATE_JAVASCRIPT_PRETTIER=true\"\n echo \"VALIDATE_JSX_PRETTIER=true\"\n echo \"VALIDATE_TYPESCRIPT_PRETTIER=true\"\n echo \"VALIDATE_TSX=true\"\n echo \"FIX_JAVASCRIPT_PRETTIER=true\"\n echo \"FIX_JSX_PRETTIER=true\"\n echo \"FIX_TYPESCRIPT_PRETTIER=true\"\n echo \"FIX_TSX=true\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has '*.css' || has '*.scss' || has 'stylelint.config.ts' || has 'stylelint.config.mjs' || has 'stylelint.config.js'; }; then\n {\n echo \"VALIDATE_CSS=true\"\n echo \"STYLELINT_CONFIG_FILE=stylelint.config.ts\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has '*.graphql' || has '*.gql'; }; then\n {\n echo \"VALIDATE_GRAPHQL_PRETTIER=true\"\n echo \"FIX_GRAPHQL_PRETTIER=true\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has '*.html' || has '*.htm'; }; then\n {\n echo \"VALIDATE_HTML_PRETTIER=true\"\n echo \"FIX_HTML_PRETTIER=true\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has '.env' || has '.env.example' || has '.env.local'; }; then\n {\n echo \"VALIDATE_ENV=true\"\n echo \"FIX_ENV=true\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has 'Dockerfile' || has '*.Dockerfile'; }; then\n echo \"VALIDATE_DOCKERFILE=true\" >> \"$GITHUB_ENV\"\n fi\n\n - uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0\n name: Run Super Linter\n env:\n GITHUB_TOKEN: ${{ github.token }}\n DEFAULT_BRANCH: ${{ github.event.pull_request.base.ref || github.event.repository.default_branch }}\n ANNOTATE_ONLY: true\n DISABLE_COMMENTS: false\n IGNORE_GITIGNORED_FILES: true\n LINTER_RULES_PATH: /\n EDITORCONFIG_FILE_NAME: \".editorconfig-checker.json\"\n # Config file paths — inputs stay in expression context, never shell-evaluated.\n # When VALIDATE_JAVASCRIPT_ES is not set by detect, ESLint doesn't run so\n # the empty-string fallback (→ eslint.config.mjs in container) is safe.\n JAVASCRIPT_ES_CONFIG_FILE: ${{ steps.detect.outputs.eslint == 'true' && inputs.eslint-config || '' }}\n TYPESCRIPT_ES_CONFIG_FILE: ${{ steps.detect.outputs.eslint == 'true' && inputs.eslint-config || '' }}\n PRETTIER_CONFIG: ${{ inputs.prettier-config }}\n YAML_CONFIG_FILE: ${{ inputs.yaml-config }}\n # Always-on linters\n FIX_MARKDOWN_PRETTIER: true\n VALIDATE_EDITORCONFIG: true\n VALIDATE_GIT_COMMITLINT: true\n VALIDATE_GIT_MERGE_CONFLICT_MARKERS: true\n VALIDATE_GITHUB_ACTIONS: true\n VALIDATE_GITLEAKS: true\n VALIDATE_MARKDOWN_PRETTIER: true\n VALIDATE_YAML: true\n\n - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0\n name: Commit and push linting fixes\n if: >\n inputs.enable-auto-commit == true &&\n github.event.pull_request != null &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.ref_name != github.event.repository.default_branch &&\n env.APP_ID_SET == 'true'\n with:\n branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }}\n commit_message: \"chore: fix linting issues\\n\\nSigned-off-by: ${{ steps.app-bot.outputs.name }} <${{ steps.app-bot.outputs.email }}>\"\n commit_options: \"--no-verify\"\n commit_user_name: ${{ steps.app-bot.outputs.name }}\n commit_user_email: ${{ steps.app-bot.outputs.email }}\n commit_author: \"${{ steps.app-bot.outputs.name }} <${{ steps.app-bot.outputs.email }}>\"\n";
4487
+ var lint_default = "name: Lint\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n eslint-config:\n description: >\n Filename for the ESLint flat config used by super-linter for\n JavaScript, JSX, TSX, and TypeScript (ES) files. Defaults to\n eslint.config.ts (the org standard). Note: ESLint 9 requires\n --flag unstable_ts_config to load .ts configs; if super-linter\n cannot load it, override with eslint.config.mjs or eslint.config.js.\n type: string\n required: false\n default: eslint.config.ts\n prettier-config:\n description: >\n Filename for the Prettier config. Defaults to prettier.config.ts\n (the org standard). Prettier 3.x loads .ts configs natively.\n type: string\n required: false\n default: prettier.config.ts\n yaml-config:\n description: >\n Filename for the yamllint config. Defaults to yamllint.config.yml.\n type: string\n required: false\n default: yamllint.config.yml\n enable-auto-commit:\n description: >\n Auto-commit super-linter fixes as a verified commit via a GitHub App.\n Requires SUPER_LINTER_APP_ID and SUPER_LINTER_PRIVATE_KEY secrets.\n type: boolean\n required: false\n default: false\n secrets:\n SUPER_LINTER_APP_ID:\n required: false\n SUPER_LINTER_PRIVATE_KEY:\n required: false\n\njobs:\n super-lint:\n name: Lint entire codebase\n permissions:\n contents: write\n issues: write\n statuses: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n env:\n APP_ID_SET: ${{ secrets.SUPER_LINTER_APP_ID != '' }}\n steps:\n - name: Generate GitHub App token\n id: app-token\n # Runs before checkout so the token is used as the checkout credential,\n # which makes the subsequent push go through the App and produce a\n # Verified commit. Skipped when auto-commit is disabled or secrets unset.\n if: >\n inputs.enable-auto-commit == true &&\n github.event.pull_request != null &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.ref_name != github.event.repository.default_branch &&\n env.APP_ID_SET == 'true'\n uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0\n with:\n app-id: ${{ secrets.SUPER_LINTER_APP_ID }}\n private-key: ${{ secrets.SUPER_LINTER_PRIVATE_KEY }}\n\n - name: Resolve App bot identity\n id: app-bot\n # GitHub marks commits as Verified when the author email matches the\n # App bot's noreply address (<numeric-id>+<slug>[bot]@users.noreply.github.com).\n # The numeric ID must be fetched via the API — it differs from the App ID.\n # app-slug is passed via env rather than interpolated into the script to\n # prevent code injection (CWE-78).\n if: steps.app-token.conclusion == 'success'\n run: |\n BOT_SLUG=\"${APP_SLUG}[bot]\"\n BOT_ID=$(gh api \"/users/${BOT_SLUG}\" --jq .id)\n echo \"name=${BOT_SLUG}\" >> \"$GITHUB_OUTPUT\"\n echo \"email=${BOT_ID}+${BOT_SLUG}@users.noreply.github.com\" >> \"$GITHUB_OUTPUT\"\n env:\n GH_TOKEN: ${{ steps.app-token.outputs.token }}\n APP_SLUG: ${{ steps.app-token.outputs.app-slug }}\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n # Use the App token when available so the push credential is the App\n # bot — GitHub marks those commits as Verified automatically.\n token: ${{ steps.app-token.outputs.token || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n\n - name: Detect project features\n # Writes VALIDATE_*/FIX_* to GITHUB_ENV only when the feature exists.\n # Also writes step outputs for values referenced in expression context\n # (GITHUB_ENV is not readable via steps.*.outputs — they need GITHUB_OUTPUT).\n # All values written are hardcoded 'true' — no user input in the script.\n # Config file paths (inputs.*) stay in GH Actions expression context in\n # the super-linter env: block below, never shell-evaluated (CWE-78).\n id: detect\n run: |\n has() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n\n if { has 'eslint.config.ts' || has 'eslint.config.mjs' || has 'eslint.config.js' || has '.eslintrc.json' || has '.eslintrc.yml'; }; then\n {\n echo \"VALIDATE_JAVASCRIPT_ES=true\"\n echo \"VALIDATE_TYPESCRIPT_ES=true\"\n } >> \"$GITHUB_ENV\"\n echo \"eslint=true\" >> \"$GITHUB_OUTPUT\"\n fi\n\n if { has '*.js' || has '*.jsx' || has '*.mjs' || has '*.cjs' || has '*.ts' || has '*.tsx'; }; then\n {\n echo \"VALIDATE_JAVASCRIPT_PRETTIER=true\"\n echo \"VALIDATE_JSX_PRETTIER=true\"\n echo \"VALIDATE_TYPESCRIPT_PRETTIER=true\"\n echo \"VALIDATE_TSX=true\"\n echo \"FIX_JAVASCRIPT_PRETTIER=true\"\n echo \"FIX_JSX_PRETTIER=true\"\n echo \"FIX_TYPESCRIPT_PRETTIER=true\"\n echo \"FIX_TSX=true\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has '*.css' || has '*.scss' || has 'stylelint.config.ts' || has 'stylelint.config.mjs' || has 'stylelint.config.js'; }; then\n {\n echo \"VALIDATE_CSS=true\"\n echo \"STYLELINT_CONFIG_FILE=stylelint.config.ts\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has '*.graphql' || has '*.gql'; }; then\n {\n echo \"VALIDATE_GRAPHQL_PRETTIER=true\"\n echo \"FIX_GRAPHQL_PRETTIER=true\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has '*.html' || has '*.htm'; }; then\n {\n echo \"VALIDATE_HTML_PRETTIER=true\"\n echo \"FIX_HTML_PRETTIER=true\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has '.env' || has '.env.example' || has '.env.local'; }; then\n {\n echo \"VALIDATE_ENV=true\"\n echo \"FIX_ENV=true\"\n } >> \"$GITHUB_ENV\"\n fi\n\n if { has 'Dockerfile' || has '*.Dockerfile'; }; then\n echo \"VALIDATE_DOCKERFILE=true\" >> \"$GITHUB_ENV\"\n fi\n\n - uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0\n name: Run Super Linter\n env:\n GITHUB_TOKEN: ${{ github.token }}\n DEFAULT_BRANCH: ${{ github.event.pull_request.base.ref || github.event.repository.default_branch }}\n ANNOTATE_ONLY: true\n DISABLE_COMMENTS: false\n IGNORE_GITIGNORED_FILES: true\n LINTER_RULES_PATH: /\n EDITORCONFIG_FILE_NAME: \".editorconfig-checker.json\"\n # Config file paths — inputs stay in expression context, never shell-evaluated.\n # When VALIDATE_JAVASCRIPT_ES is not set by detect, ESLint doesn't run so\n # the empty-string fallback (→ eslint.config.mjs in container) is safe.\n JAVASCRIPT_ES_CONFIG_FILE: ${{ steps.detect.outputs.eslint == 'true' && inputs.eslint-config || '' }}\n TYPESCRIPT_ES_CONFIG_FILE: ${{ steps.detect.outputs.eslint == 'true' && inputs.eslint-config || '' }}\n PRETTIER_CONFIG: ${{ inputs.prettier-config }}\n YAML_CONFIG_FILE: ${{ inputs.yaml-config }}\n # Always-on linters\n FIX_MARKDOWN_PRETTIER: true\n VALIDATE_EDITORCONFIG: true\n VALIDATE_GIT_COMMITLINT: true\n VALIDATE_GIT_MERGE_CONFLICT_MARKERS: true\n VALIDATE_GITHUB_ACTIONS: true\n VALIDATE_GITLEAKS: true\n VALIDATE_MARKDOWN_PRETTIER: true\n VALIDATE_YAML: true\n\n - uses: theholocron/.github/.github/actions/auto-commit@main\n name: Commit and push linting fixes\n if: >\n inputs.enable-auto-commit == true &&\n github.event.pull_request != null &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.ref_name != github.event.repository.default_branch &&\n env.APP_ID_SET == 'true'\n with:\n token: ${{ steps.app-token.outputs.token }}\n branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }}\n commit-message: \"chore: fix linting issues\\n\\nSigned-off-by: ${{ steps.app-bot.outputs.name }} <${{ steps.app-bot.outputs.email }}>\"\n commit-options: \"--no-verify\"\n commit-user-name: ${{ steps.app-bot.outputs.name }}\n commit-user-email: ${{ steps.app-bot.outputs.email }}\n commit-author: \"${{ steps.app-bot.outputs.name }} <${{ steps.app-bot.outputs.email }}>\"\n";
4488
+ //#endregion
4489
+ //#region src/templates/workflows/post-release.yml
4490
+ var post_release_default = "name: Post-release Sync\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\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 broadcast:\n name: Broadcast readme sync\n runs-on: ubuntu-latest\n timeout-minutes: 5\n steps:\n - name: Trigger broadcast readme sync\n run: |\n gh workflow run sync-broadcast.yml \\\n --repo theholocron/.github \\\n --field \"steps=readme\"\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n";
4317
4491
  //#endregion
4318
4492
  //#region src/templates/workflows/release.yml
4319
4493
  var release_default = "name: Release\n\n# Semantic-release with OIDC Trusted Publishing.\n# actions/setup-node writes a default NODE_AUTH_TOKEN=${{ github.token }}\n# which shadows OIDC auth. We explicitly clear it so npm falls through to\n# the Trusted Publisher OIDC exchange.\n# The calling repo must have a .releaserc.json that configures branches,\n# plugins, and any publish options. npm@11+ is installed to support OIDC.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n run-build:\n description: Run `pnpm build` before releasing\n type: boolean\n required: false\n default: true\n sentry-project:\n description: >\n Sentry project slug for sourcemap upload and release creation after\n publishing. Omit to skip the Sentry release step entirely.\n type: string\n required: false\n default: \"\"\n secrets:\n HOLOCRON_RELEASE_TOKEN:\n description: >\n Fine-grained PAT (Contents + Issues + Pull requests: write) owned by\n an admin. Required when the default branch is protected by a ruleset —\n github.token cannot push through rulesets, but an admin PAT can.\n Takes priority over HOLOCRON_SYNC_TOKEN. Falls back to github.token.\n required: false\n HOLOCRON_SYNC_TOKEN:\n description: >\n Legacy alias for HOLOCRON_RELEASE_TOKEN — kept for backward compatibility.\n Prefer HOLOCRON_RELEASE_TOKEN for new repos.\n required: false\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to github.token.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when\n HOLOCRON_READ_TOKEN is not set.\n required: false\n SENTRY_AUTH_TOKEN:\n description: >\n Sentry auth token for sourcemap upload and release creation.\n Required when sentry-project is set. Use the org-level secret.\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n release:\n name: Semantic release\n permissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n # Do not cancel in-progress releases — a partial release is worse than a slow one.\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n persist-credentials: false\n # Use HOLOCRON_RELEASE_TOKEN when available — git push (tags, release commits)\n # uses the checkout credential, not GITHUB_TOKEN env var. The\n # built-in github.token cannot push through branch protection rulesets.\n token: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Configure git identity\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n git config --global user.name \"$GIT_NAME\"\n git config --global user.email \"$GIT_EMAIL\"\n git config --global format.signoff true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Upgrade npm for OIDC support\n run: npm install -g npm@11 sigstore\n # sigstore is required by libnpmpublish/provenance.js at module parse\n # time — before any config takes effect. Some npm 11.x builds stopped\n # bundling it; installing it globally into the same prefix ensures it\n # resolves regardless of npm version. (Discovered 2026-07-09.)\n\n - run: pnpm build\n name: Build\n if: ${{ inputs.run-build == true }}\n\n - run: npx semantic-release\n name: Release\n env:\n # Prefer HOLOCRON_RELEASE_TOKEN (fine-grained PAT, Contents+Issues+PRs write,\n # owned by an admin with ruleset bypass) so @semantic-release/git can\n # push the version-bump commit through branch protection. Falls back to\n # HOLOCRON_SYNC_TOKEN (legacy) then github.token for unprotected repos.\n GITHUB_TOKEN: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n HUSKY: \"0\"\n NPM_CONFIG_PROVENANCE: true\n\n - name: Get release version\n id: release_version\n if: ${{ inputs.sentry-project != '' }}\n env:\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n run: |\n TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo \"\")\n if [ -n \"$TAG\" ]; then\n echo \"release=${SENTRY_PROJECT}@${TAG#v}\" >> \"$GITHUB_OUTPUT\"\n fi\n\n - name: Create Sentry release\n if: ${{ inputs.sentry-project != '' && steps.release_version.outputs.release != '' }}\n uses: getsentry/action-release@ff07929a6537bac57790c3451cf4d364aca38528 # v3\n env:\n SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n SENTRY_ORG: theholocron\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n with:\n environment: production\n version: ${{ steps.release_version.outputs.release }}\n sourcemaps: \"**/dist\"\n";
@@ -4324,8 +4498,14 @@ var review_default = "name: Review\n\n# ReviewDog is the annotation layer — po
4324
4498
  //#region src/templates/workflows/stale.yml
4325
4499
  var stale_default = "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 is marked stale\n type: number\n required: false\n default: 30\n days-before-close:\n description: Days of inactivity after stale label before closing\n type: number\n required: false\n default: 5\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-stale: ${{ inputs.days-before-stale }}\n exempt-all-pr-milestones: true\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";
4326
4500
  //#endregion
4501
+ //#region src/templates/workflows/sync.yml
4502
+ var sync_default = "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.\n Pass a space-separated list to run a subset, e.g. \"readme\" or \"readme description\".\n type: string\n required: false\n secrets:\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: Sync repo from config\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: write\n pull-requests: write\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 run: |\n if [ -n \"$STEPS\" ]; then\n # shellcheck disable=SC2086\n pnpm exec holocron sync --steps $STEPS\n else\n pnpm exec holocron sync\n fi\n env:\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n STEPS: ${{ inputs.steps }}\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\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";
4503
+ //#endregion
4504
+ //#region src/templates/workflows/sync-broadcast.yml
4505
+ var sync_broadcast_default = "name: Sync Broadcast\n\non: # yamllint disable-line rule:truthy\n workflow_dispatch:\n inputs:\n steps:\n description: >\n Sync steps to pass to each repo's sync.yml. Default is \"readme\"\n (only README marker blocks are updated).\n type: string\n required: false\n default: readme\n\npermissions:\n contents: read\n\njobs:\n broadcast:\n name: Broadcast sync to all repos\n runs-on: ubuntu-latest\n timeout-minutes: 15\n steps:\n - name: Dispatch sync to all repos with sync.yml\n run: |\n gh api /orgs/theholocron/repos --paginate --jq '.[].name' \\\n | while IFS= read -r repo; do\n gh api \"/repos/theholocron/$repo/contents/.github/workflows/sync.yml\" --silent 2>/dev/null || continue\n echo \"Dispatching sync to theholocron/$repo\"\n gh workflow run sync.yml \\\n --repo \"theholocron/$repo\" \\\n --field \"steps=$STEPS\"\n done\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n STEPS: ${{ inputs.steps }}\n";
4506
+ //#endregion
4327
4507
  //#region src/templates/workflows/sync-github.yml
4328
- var sync_github_default = "name: Sync GitHub Templates\n\n# Builds the holocron CLI from source and pushes updated workflow templates\n# and composite actions to downstream .github repos. Runs whenever the\n# template source files change on main or alpha.\n#\n# Secrets required:\n# HOLOCRON_SYNC_TOKEN — fine-grained PAT (resource owner: org) with:\n# Contents: Read and write (git trees, blobs, refs)\n# Pull requests: Read and write (open sync PR)\n# Workflows: Read and write (write .github/workflows/*.yml)\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n primary-repo:\n description: >\n Primary .github repo — receives composite actions, reusable workflows,\n and thin-caller templates. Requires a PR (branch protection assumed).\n type: string\n required: false\n default: theholocron/.github\n secondary-repos:\n description: >\n Space-separated list of secondary repos (reusable workflows + thin\n callers only, no composite actions). Changes are delivered via pull\n request, same as the primary repo.\n type: string\n required: false\n default: \"\"\n sync-branch:\n description: Branch name used for the primary and secondary repo PRs\n type: string\n required: false\n default: chore/sync-templates\n secrets:\n HOLOCRON_SYNC_TOKEN:\n required: true\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to HOLOCRON_SYNC_TOKEN.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when neither\n HOLOCRON_READ_TOKEN nor HOLOCRON_SYNC_TOKEN is set.\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n sync:\n name: Sync templates\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm build\n name: Build CLI\n\n - name: Cache actionlint\n id: cache-actionlint\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: /tmp/actionlint\n key: actionlint-v1.7.7-linux-amd64\n\n - name: Download actionlint\n if: steps.cache-actionlint.outputs.cache-hit != 'true'\n run: |\n curl -fsSL https://github.com/rhysd/actionlint/releases/download/v1.7.7/actionlint_1.7.7_linux_amd64.tar.gz \\\n | tar -xz -C /tmp actionlint\n\n - name: Validate generated workflows\n run: |\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --output-dir /tmp/sync-validate\n /tmp/actionlint /tmp/sync-validate/.github/workflows/*.yml\n env:\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n\n - name: Sync primary repo (PR)\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$PRIMARY_REPO\" \"$SYNC_BRANCH\" 2>/dev/null || true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n\n - name: Sync secondary repos (PR)\n if: ${{ inputs.secondary-repos != '' }}\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n for repo in $SECONDARY_REPOS; do\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$repo\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$repo\" \"$SYNC_BRANCH\" 2>/dev/null || true\n done\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n SECONDARY_REPOS: ${{ inputs.secondary-repos }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n";
4508
+ var sync_github_default = "name: Sync GitHub Templates\n\n# Builds the holocron CLI from source and pushes updated workflow templates\n# and composite actions to downstream .github repos. Runs whenever the\n# template source files change on main or alpha.\n#\n# Secrets required:\n# HOLOCRON_SYNC_TOKEN — fine-grained PAT (resource owner: org) with:\n# Actions: Read and write (dispatch workflow runs via gh workflow run)\n# Contents: Read and write (git trees, blobs, refs)\n# Pull requests: Read and write (open sync PR)\n# Workflows: Read and write (write .github/workflows/*.yml)\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n primary-repo:\n description: >\n Primary .github repo — receives composite actions, reusable workflows,\n and thin-caller templates. Requires a PR (branch protection assumed).\n type: string\n required: false\n default: theholocron/.github\n secondary-repos:\n description: >\n Space-separated list of secondary repos (reusable workflows + thin\n callers only, no composite actions). Changes are delivered via pull\n request, same as the primary repo.\n type: string\n required: false\n default: \"\"\n sync-branch:\n description: Branch name used for the primary and secondary repo PRs\n type: string\n required: false\n default: chore/sync-templates\n secrets:\n HOLOCRON_SYNC_TOKEN:\n required: true\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to HOLOCRON_SYNC_TOKEN.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when neither\n HOLOCRON_READ_TOKEN nor HOLOCRON_SYNC_TOKEN is set.\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n sync:\n name: Sync templates\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm build\n name: Build CLI\n\n - name: Cache actionlint\n id: cache-actionlint\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: /tmp/actionlint\n key: actionlint-v1.7.7-linux-amd64\n\n - name: Download actionlint\n if: steps.cache-actionlint.outputs.cache-hit != 'true'\n run: |\n curl -fsSL https://github.com/rhysd/actionlint/releases/download/v1.7.7/actionlint_1.7.7_linux_amd64.tar.gz \\\n | tar -xz -C /tmp actionlint\n\n - name: Validate generated workflows\n run: |\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --output-dir /tmp/sync-validate\n /tmp/actionlint /tmp/sync-validate/.github/workflows/*.yml\n env:\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n\n - name: Sync primary repo (PR)\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$PRIMARY_REPO\" \"$SYNC_BRANCH\" 2>/dev/null || true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n\n - name: Sync secondary repos (PR)\n if: ${{ inputs.secondary-repos != '' }}\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n for repo in $SECONDARY_REPOS; do\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$repo\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$repo\" \"$SYNC_BRANCH\" 2>/dev/null || true\n done\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n SECONDARY_REPOS: ${{ inputs.secondary-repos }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n";
4329
4509
  //#endregion
4330
4510
  //#region src/templates/workflows/test.yml
4331
4511
  var test_default = "name: Test\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n run-unit:\n description: Run unit tests with coverage (disable for UI-only repos that use Storybook testing exclusively)\n type: boolean\n required: false\n default: true\n run-storybook:\n description: Run Storybook vitest interaction tests (requires .storybook/ setup)\n type: boolean\n required: false\n default: false\n run-interaction:\n description: Run Storybook interaction and accessibility tests with Playwright\n type: boolean\n required: false\n default: false\n run-chromatic:\n description: Publish Storybook to Chromatic for visual regression testing\n type: boolean\n required: false\n default: false\n chromatic-projects:\n description: >\n JSON array of Chromatic projects to build, one matrix job per entry.\n Each entry: { \"tokenName\": \"WEB\", \"workingDir\": \"apps/web\", \"buildScript\": \"build:storybook\" }.\n tokenName maps to secret CHROMATIC_PROJECT_TOKEN_<TOKENNAME>; use \"default\"\n (or omit for the legacy empty-string form) to use the bare CHROMATIC_PROJECT_TOKEN\n secret for single-project repos.\n buildScript defaults to \"build:storybook\" when omitted.\n Default runs a single job from the repo root using CHROMATIC_PROJECT_TOKEN.\n type: string\n required: false\n default: '[{\"tokenName\":\"default\",\"workingDir\":\".\",\"buildScript\":\"build:storybook\"}]'\n run-user-flow:\n description: Run Cypress E2E user-flow tests (requires cypress.config.*)\n type: boolean\n required: false\n default: false\n wait-on-url:\n description: URL to wait for before running Cypress tests (default is Vite dev server; override for non-Vite stacks e.g. http://localhost:3000 for Next.js)\n type: string\n required: false\n default: \"http://localhost:5173\"\n secrets:\n CHROMATIC_PROJECT_TOKEN:\n required: false\n CYPRESS_RECORD_KEY:\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n unit:\n name: Run tests and collect coverage\n if: ${{ inputs.run-unit }}\n permissions:\n contents: read\n id-token: write\n runs-on: ubuntu-latest\n timeout-minutes: 15\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\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm test:coverage\n name: Run tests with coverage\n\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload coverage to Codecov\n with:\n use_oidc: true\n\n - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1\n name: Upload test results to Codecov\n if: ${{ !cancelled() }}\n with:\n use_oidc: true\n files: '**/test-report.junit.xml'\n\n storybook:\n name: Run Storybook interaction tests\n if: ${{ inputs.run-storybook }}\n permissions:\n contents: read\n id-token: write\n runs-on: ubuntu-latest\n timeout-minutes: 15\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\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm exec playwright install chromium --with-deps\n name: Install Playwright\n\n - run: pnpm test:storybook\n name: Run Storybook tests\n\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload coverage to Codecov\n with:\n use_oidc: true\n\n - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1\n name: Upload test results to Codecov\n if: ${{ !cancelled() }}\n with:\n use_oidc: true\n files: '**/test-report.junit.xml'\n\n visual-and-composition:\n name: Test Visual and Composition (${{ matrix.project.tokenName }})\n if: ${{ inputs.run-chromatic }}\n strategy:\n fail-fast: false\n matrix:\n project: ${{ fromJSON(inputs.chromatic-projects) }}\n permissions:\n contents: read\n statuses: write\n runs-on: ubuntu-latest\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: chromaui/action@14cfaef73576e69f95f47f60058063f46ca38719 # v18\n name: Publish to Chromatic\n with:\n projectToken: ${{ (matrix.project.tokenName == 'default' || matrix.project.tokenName == '') && secrets.CHROMATIC_PROJECT_TOKEN || secrets[format('CHROMATIC_PROJECT_TOKEN_{0}', matrix.project.tokenName)] }}\n token: ${{ github.token }}\n buildScriptName: ${{ matrix.project.buildScript || 'build:storybook' }}\n workingDir: ${{ matrix.project.workingDir || '.' }}\n storybookBaseDir: ${{ matrix.project.storybookBaseDir || '' }}\n untraced: ${{ matrix.project.untraced || '' }}\n onlyStoryFiles: ${{ matrix.project.onlyStoryFiles || '' }}\n exitZeroOnChanges: ${{ matrix.project.exitZeroOnChanges || false }}\n\n interaction-and-accessibility:\n name: Test Interactions and Accessibility\n if: ${{ inputs.run-interaction }}\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 steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm exec playwright install --with-deps\n name: Install Playwright\n\n - run: pnpm test:storybook\n name: Run interaction and accessibility tests\n\n user-flow:\n name: Test User Flow\n if: ${{ inputs.run-user-flow }}\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 15\n strategy:\n fail-fast: false\n matrix:\n containers: [1, 2]\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\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm exec cypress install\n name: Install Cypress binary\n\n - uses: cypress-io/github-action@1052aa98bbbe4f55210f844878213c07d9c8c399 # v6.7.13\n name: Cypress run\n with:\n start: pnpm dev\n wait-on: ${{ inputs.wait-on-url }}\n record: true\n parallel: true\n env:\n CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n";
@@ -4343,8 +4523,9 @@ var typecheck_default = "name: Typecheck\n\non: # yamllint disable-line rule:tru
4343
4523
  * inlines them as string exports at build time.
4344
4524
  */
4345
4525
  const ACTIONS = {
4346
- "setup/action": setup_default,
4526
+ "auto-commit/action": auto_commit_default,
4347
4527
  "install/action": install_default,
4528
+ "setup/action": setup_default,
4348
4529
  "setup-node/action": setup_node_default
4349
4530
  };
4350
4531
  const REUSABLE_WORKFLOWS = {
@@ -4355,10 +4536,13 @@ const REUSABLE_WORKFLOWS = {
4355
4536
  deploy: deploy_default,
4356
4537
  greetings: greetings_default,
4357
4538
  lint: lint_default,
4539
+ "post-release": post_release_default,
4358
4540
  release: release_default,
4359
4541
  review: review_default,
4360
4542
  stale: stale_default,
4543
+ "sync-broadcast": sync_broadcast_default,
4361
4544
  "sync-github": sync_github_default,
4545
+ sync: sync_default,
4362
4546
  test: test_default,
4363
4547
  typecheck: typecheck_default
4364
4548
  };
@@ -4457,15 +4641,6 @@ function reusableHeader(source) {
4457
4641
  ``
4458
4642
  ].join("\n");
4459
4643
  }
4460
- function thinCallerHeader(forPrimary = false) {
4461
- return [
4462
- forPrimary ? `# AUTO-GENERATED — do not edit in theholocron/.github directly.` : `# AUTO-GENERATED — do not edit directly.`,
4463
- `# Source: theholocron/holocron · packages/cli/src/commands/setup-workflows.ts`,
4464
- `# Tool: holocron sync-github`,
4465
- `# Changes: edit source in theholocron/holocron and push to alpha or main.`,
4466
- ``
4467
- ].join("\n");
4468
- }
4469
4644
  function buildBatch(repo, allowedWorkflows, withOverrides) {
4470
4645
  const files = [];
4471
4646
  const isPrimaryGithubRepo = repo === DEFAULT_REPO;
@@ -4481,7 +4656,7 @@ function buildBatch(repo, allowedWorkflows, withOverrides) {
4481
4656
  for (const [name, content] of Object.entries(WORKFLOW_TEMPLATES)) {
4482
4657
  files.push({
4483
4658
  path: `workflow-templates/${name}.yml`,
4484
- content: thinCallerHeader(true) + content
4659
+ content: workflowHeader(void 0, true) + content
4485
4660
  });
4486
4661
  const props = WORKFLOW_TEMPLATE_PROPERTIES[name];
4487
4662
  if (props) files.push({
@@ -4496,7 +4671,7 @@ function buildBatch(repo, allowedWorkflows, withOverrides) {
4496
4671
  if (!content) continue;
4497
4672
  files.push({
4498
4673
  path: `.github/workflows/${name}.yml`,
4499
- content: thinCallerHeader() + content
4674
+ content: workflowHeader() + content
4500
4675
  });
4501
4676
  }
4502
4677
  return files;
@@ -4708,86 +4883,6 @@ async function runSyncGithub(input) {
4708
4883
  };
4709
4884
  }
4710
4885
  //#endregion
4711
- //#region src/commands/sync-readme.ts
4712
- const README_INSTALL_START = "<!-- holocron:installation -->";
4713
- const README_INSTALL_END = "<!-- /holocron:installation -->";
4714
- function generateInstallBlock(pkg) {
4715
- const { name = "", bin, peerDependencies = {} } = pkg;
4716
- const isCli = Boolean(bin);
4717
- const isReact = Boolean(peerDependencies["react"]);
4718
- const lines = [];
4719
- lines.push("## Installation", "");
4720
- if (isCli) lines.push("```bash", `npm install --global ${name}`, "```");
4721
- else lines.push("```bash", `pnpm install ${name}`, "```");
4722
- lines.push("", "## Usage", "");
4723
- if (isCli) {
4724
- const commands = typeof bin === "string" ? [name.split("/").at(-1)] : Object.keys(bin ?? {});
4725
- lines.push("```bash");
4726
- for (const cmd of commands) lines.push(`${cmd} --help`);
4727
- lines.push("```");
4728
- } else if (isReact) lines.push("```tsx", `import { } from "${name}";`, "", "function App() {", ` return <></>;`, "}", "```");
4729
- else lines.push("```typescript", `import { } from "${name}";`, "```");
4730
- return lines.join("\n");
4731
- }
4732
- async function updateReadmeInstallation(repoRoot, block, dryRun, readFileFn, writeFileFn) {
4733
- const readmePath = join(repoRoot, "README.md");
4734
- let content;
4735
- try {
4736
- content = await readFileFn(readmePath, "utf8");
4737
- } catch {
4738
- return false;
4739
- }
4740
- const lines = content.split("\n");
4741
- const startIdx = lines.findIndex((l) => l.trim() === README_INSTALL_START);
4742
- const endIdx = lines.findIndex((l) => l.trim() === README_INSTALL_END);
4743
- const blockLines = block.split("\n");
4744
- if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
4745
- if (dryRun) return true;
4746
- lines.splice(startIdx + 1, endIdx - startIdx - 1, "", ...blockLines, "");
4747
- await writeFileFn(readmePath, lines.join("\n"), "utf8");
4748
- return true;
4749
- }
4750
- const descEnd = lines.findIndex((l) => l.trim() === "<!-- /holocron:description -->");
4751
- const insertAfter = descEnd !== -1 ? descEnd : lines.findIndex((l) => /^# /.test(l));
4752
- if (insertAfter === -1) return false;
4753
- if (dryRun) return true;
4754
- lines.splice(insertAfter + 1, 0, "", README_INSTALL_START, "", ...blockLines, "", README_INSTALL_END);
4755
- await writeFileFn(readmePath, lines.join("\n"), "utf8");
4756
- return true;
4757
- }
4758
- async function runSyncReadme(input) {
4759
- const { loaded, context, print = console.log, readFileFn = readFile, writeFileFn = writeFile } = input;
4760
- const { repoRoot, dryRun = false } = context;
4761
- const namespaces = loaded.resolved.env?.namespaces ?? [];
4762
- print(style.header(`Syncing README installation block${dryRun ? " (dry-run)" : ""}…`));
4763
- if (namespaces.length > 0) print(style.hint(` namespaces: ${namespaces.join(", ")}`));
4764
- let pkg;
4765
- try {
4766
- const raw = await readFileFn(join(repoRoot, "package.json"), "utf8");
4767
- pkg = JSON.parse(raw);
4768
- } catch {
4769
- return {
4770
- status: "fail",
4771
- updated: false,
4772
- message: "Could not read package.json"
4773
- };
4774
- }
4775
- const updated = await updateReadmeInstallation(repoRoot, generateInstallBlock(pkg), dryRun, readFileFn, writeFileFn);
4776
- if (!updated) {
4777
- print(style.warn("README.md not found or has no writable location for installation block"));
4778
- return {
4779
- status: "fail",
4780
- updated: false,
4781
- message: "README.md update failed"
4782
- };
4783
- }
4784
- print(dryRun ? style.hint(" would update README.md") : style.success(" updated README.md"));
4785
- return {
4786
- status: dryRun ? "dry-run" : "ok",
4787
- updated
4788
- };
4789
- }
4790
- //#endregion
4791
4886
  //#region src/commands/upgrade-node.ts
4792
4887
  const SKIP_DIRS = /* @__PURE__ */ new Set([
4793
4888
  "node_modules",
@@ -5527,10 +5622,10 @@ try {
5527
5622
  dryRun: argv.dryRun,
5528
5623
  ...argv.otp ? { otp: argv.otp } : {}
5529
5624
  })).status === "fail") process.exitCode = 1;
5530
- }).demandCommand(1, "Run `holocron npm --help` to see available npm subcommands."), () => {}).command("sync [steps..]", "Sync state from config to the provider and local files (labels, properties, topics, keywords, description)", (y) => y.positional("steps", {
5625
+ }).demandCommand(1, "Run `holocron npm --help` to see available npm subcommands."), () => {}).command("sync [steps..]", "Sync state from config to the provider and local files (labels, properties, topics, keywords, description, homepage, readme)", (y) => y.positional("steps", {
5531
5626
  type: "string",
5532
5627
  array: true,
5533
- describe: "Steps to run: labels, properties, topics, keywords, description (default: all)"
5628
+ describe: "Steps to run: labels, properties, topics, keywords, description, homepage, readme (default: all)"
5534
5629
  }).option("repo", {
5535
5630
  type: "string",
5536
5631
  describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."