@theholocron/astromech 5.0.0-alpha.172 → 5.0.0-alpha.174
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.mjs +3 -3
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -113,7 +113,7 @@ function codecovConfig(repoRoot, existing) {
|
|
|
113
113
|
}
|
|
114
114
|
//#endregion
|
|
115
115
|
//#region src/templates/reusable/actions/auto-commit.yml
|
|
116
|
-
var auto_commit_default = "name: Auto Commit\ndescription: >\n Commit and push any file changes using stefanzweifel/git-auto-commit-action.\n If a branch name is provided and differs from the current branch, a new branch\n is created automatically.\n\ninputs:\n token:\n description: >\n GitHub token with push access to the target branch. Used to authenticate\n the git remote so the commit can be pushed.\n required: true\n branch:\n description: >\n Branch to commit to. Leave empty to commit to the current branch (useful\n in PR contexts). Provide an explicit name (e.g. chore/auto-sync) to push\n to a separate branch.\n required: false\n default: \"\"\n commit-message:\n description: Commit message.\n required: true\n commit-options:\n description: >\n Additional options passed to git commit (e.g. --no-verify).\n required: false\n default: \"\"\n commit-user-name:\n description: Git user.name for the commit.\n required: false\n default: \"github-actions[bot]\"\n commit-user-email:\n description: Git user.email for the commit.\n required: false\n default: \"41898282+github-actions[bot]@users.noreply.github.com\"\n commit-author:\n description: >\n Git author string in \"Name <email>\" format.\n required: false\n default: \"github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>\"\n\noutputs:\n changes-detected:\n description: \"'true' if any files were committed, 'false' otherwise.\"\n value: ${{ steps.commit.outputs.changes_detected }}\n\nruns:\n using: composite\n steps:\n - name: Configure git remote with token\n shell: bash\n run: |\n REMOTE_URL=$(git remote get-url origin)\n AUTHED_URL=$(echo \"$REMOTE_URL\" | sed \"s|https://|https://x-access-token:${TOKEN}@|\")\n git remote set-url origin \"$AUTHED_URL\"\n env:\n TOKEN: ${{ inputs.token }}\n\n - name: Reset branch to current HEAD\n if: ${{ inputs.branch != '' }}\n shell: bash\n run: git checkout -B \"$BRANCH\"\n env:\n BRANCH: ${{ inputs.branch }}\n\n - name: Build signed commit message\n id: msg\n shell: bash\n run: |\n TRAILER=\"Signed-off-by: ${COMMIT_USER_NAME} <${COMMIT_USER_EMAIL}>\"\n if echo \"$COMMIT_MESSAGE\" | grep -qF \"$TRAILER\"; then\n FINAL=\"$COMMIT_MESSAGE\"\n else\n FINAL=\"${COMMIT_MESSAGE}\"$'\\n\\n'\"${TRAILER}\"\n fi\n {\n echo \"value<<EOF_TRAILER\"\n printf '%s' \"$FINAL\"\n echo \"\"\n echo \"EOF_TRAILER\"\n } >> \"$GITHUB_OUTPUT\"\n env:\n COMMIT_MESSAGE: ${{ inputs.commit-message }}\n COMMIT_USER_NAME: ${{ inputs.commit-user-name }}\n COMMIT_USER_EMAIL: ${{ inputs.commit-user-email }}\n\n - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0\n id: commit\n with:\n branch: ${{ inputs.branch }}\n commit_message: ${{ steps.msg.outputs.value }}\n commit_options: ${{ inputs.commit-options }}\n commit_user_name: ${{ inputs.commit-user-name }}\n commit_user_email: ${{ inputs.commit-user-email }}\n commit_author: ${{ inputs.commit-author }}\n";
|
|
116
|
+
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-<sha>) to push\n to a separate branch.\n required: false\n default: \"\"\n commit-message:\n description: Commit message.\n required: true\n commit-options:\n description: >\n Additional options passed to git commit (e.g. --no-verify).\n required: false\n default: \"\"\n commit-user-name:\n description: Git user.name for the commit.\n required: false\n default: \"github-actions[bot]\"\n commit-user-email:\n description: Git user.email for the commit.\n required: false\n default: \"41898282+github-actions[bot]@users.noreply.github.com\"\n commit-author:\n description: >\n Git author string in \"Name <email>\" format.\n required: false\n default: \"github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>\"\n\noutputs:\n changes-detected:\n description: \"'true' if any files were committed, 'false' otherwise.\"\n value: ${{ steps.commit.outputs.changes_detected }}\n\nruns:\n using: composite\n steps:\n - name: Configure git remote with token\n shell: bash\n run: |\n REMOTE_URL=$(git remote get-url origin)\n AUTHED_URL=$(echo \"$REMOTE_URL\" | sed \"s|https://|https://x-access-token:${TOKEN}@|\")\n git remote set-url origin \"$AUTHED_URL\"\n env:\n TOKEN: ${{ inputs.token }}\n\n - name: Reset branch to current HEAD\n if: ${{ inputs.branch != '' }}\n shell: bash\n run: git checkout -B \"$BRANCH\"\n env:\n BRANCH: ${{ inputs.branch }}\n\n - name: Build signed commit message\n id: msg\n shell: bash\n run: |\n TRAILER=\"Signed-off-by: ${COMMIT_USER_NAME} <${COMMIT_USER_EMAIL}>\"\n if echo \"$COMMIT_MESSAGE\" | grep -qF \"$TRAILER\"; then\n FINAL=\"$COMMIT_MESSAGE\"\n else\n FINAL=\"${COMMIT_MESSAGE}\"$'\\n\\n'\"${TRAILER}\"\n fi\n {\n echo \"value<<EOF_TRAILER\"\n printf '%s' \"$FINAL\"\n echo \"\"\n echo \"EOF_TRAILER\"\n } >> \"$GITHUB_OUTPUT\"\n env:\n COMMIT_MESSAGE: ${{ inputs.commit-message }}\n COMMIT_USER_NAME: ${{ inputs.commit-user-name }}\n COMMIT_USER_EMAIL: ${{ inputs.commit-user-email }}\n\n - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0\n id: commit\n with:\n branch: ${{ inputs.branch }}\n commit_message: ${{ steps.msg.outputs.value }}\n commit_options: ${{ inputs.commit-options }}\n commit_user_name: ${{ inputs.commit-user-name }}\n commit_user_email: ${{ inputs.commit-user-email }}\n commit_author: ${{ inputs.commit-author }}\n";
|
|
117
117
|
//#endregion
|
|
118
118
|
//#region src/templates/reusable/actions/holocron.yml
|
|
119
119
|
var holocron_default = "name: Holocron\ndescription: >\n Run the Holocron CLI — `holocron run <task> [job]` by default, or any other\n subcommand via `command:` (`repo sync`, `sync github`, …). Resolves\n `@theholocron/cli` from `node_modules` (published tarball) or builds it from\n source when it resolves to an unbuilt workspace checkout (the holocron repo\n itself). Requires dependencies to be installed already (run the setup action\n first).\n\ninputs:\n command:\n description: >\n Holocron subcommand. Default `run` — then `task` (and optional `job`)\n name the manifest task. Set to `repo sync` / `sync github` / … to run that\n subcommand directly; `args` becomes everything after it.\n required: false\n default: run\n task:\n description: >\n Manifest task to run when command is run — typecheck, test, build, audit.\n required: false\n default: \"\"\n job:\n description: Optional sub-job within the task (e.g. knip, performance for audit).\n required: false\n default: \"\"\n args:\n description: >\n For `command: run`, extra arguments forwarded to the tool after `--`.\n For any other command, the arguments passed straight to it.\n required: false\n default: \"\"\n\noutputs:\n log-file:\n description: >\n Path to a file holding the full captured stdout+stderr of this\n invocation — the same text the live Actions log shows, saved for a\n caller that wants to ship it elsewhere (e.g. Sentinel's dispatched\n checks shipping it to Axiom, holocron#769/#794).\n value: ${{ steps.run.outputs.log-file }}\n\nruns:\n using: composite\n\n steps:\n - name: Build the workspace when the CLI resolves to an unbuilt checkout\n shell: bash\n # The holocron repo carries @theholocron/cli as `workspace:*`; its bin\n # (dist/cli.mjs) does not exist on a fresh checkout. `pnpm build` is a\n # turbo run — cached and cheap once warm. A no-op everywhere else, where\n # the published tarball already ships dist/.\n #\n # pnpm creates every workspace package's node_modules/.bin/holocron shim\n # once, during the setup action's earlier `pnpm install` — before this\n # build has run, so every one of them is silently skipped (\"Failed to\n # create bin ... ENOENT\"), not just @theholocron/cli's own. That's a\n # one-time decision: a later build doesn't retroactively fix a shim that\n # was never created. Re-running `pnpm install` here (fast — the lockfile\n # hasn't changed, so it only re-links `.bin`) is what actually fixes it,\n # confirmed against a real cold install: `holocron` stays unresolvable\n # from inside any package until this second install runs.\n run: |\n if [ ! -f node_modules/@theholocron/cli/dist/cli.mjs ]; then\n echo \"→ @theholocron/cli dist/ missing — building the workspace from source\" >&2\n pnpm build\n echo \"→ re-linking node_modules/.bin — pnpm skipped it pre-build\" >&2\n pnpm install --frozen-lockfile\n fi\n\n - name: holocron ${{ inputs.command }} ${{ inputs.task }}\n id: run\n shell: bash\n env:\n HOLOCRON_COMMAND: ${{ inputs.command }}\n HOLOCRON_TASK: ${{ inputs.task }}\n HOLOCRON_JOB: ${{ inputs.job }}\n HOLOCRON_ARGS: ${{ inputs.args }}\n run: |\n if [ \"$HOLOCRON_COMMAND\" = \"run\" ]; then\n set -- run \"$HOLOCRON_TASK\"\n [ -n \"$HOLOCRON_JOB\" ] && set -- \"$@\" \"$HOLOCRON_JOB\"\n # shellcheck disable=SC2086\n [ -n \"$HOLOCRON_ARGS\" ] && set -- \"$@\" -- $HOLOCRON_ARGS\n else\n # `command` may be a multi-word path (`repo sync`, `deploy on-release`):\n # word-split it so each token is its own argument.\n # shellcheck disable=SC2086\n set -- $HOLOCRON_COMMAND $HOLOCRON_ARGS\n fi\n # Invoke the built entry directly. `pnpm exec holocron` relies on a\n # node_modules/.bin/holocron shim that pnpm does not create for a\n # workspace package whose dist/ was absent at install time (the holocron\n # repo itself). Resolving the package's main export gives dist/index.mjs;\n # the bin sits next to it as dist/cli.mjs. This is `require.resolve` from\n # node_modules — it works in the holocron monorepo AND in every consumer\n # repo (theholocron/holocron#655), unlike a hard-coded `packages/cli/…`.\n cli=\"$(dirname \"$(node -e 'process.stdout.write(require.resolve(\"@theholocron/cli\"))')\")/cli.mjs\"\n if [ ! -f \"$cli\" ]; then\n echo \"::error::@theholocron/cli entry not found at $cli — is the workspace built?\" >&2\n exit 1\n fi\n echo \"→ $cli $*\" >&2\n # `tee` keeps the live Actions log streaming exactly as before while\n # also capturing it to a file callers can ship elsewhere (log-file\n # output, above). PIPESTATUS[0] -- not $? -- is `node`'s own exit\n # code; $? after a pipe is tee's, which succeeds even when node fails.\n log_file=\"${RUNNER_TEMP}/holocron-output.log\"\n node \"$cli\" \"$@\" 2>&1 | tee \"$log_file\"\n exit_code=${PIPESTATUS[0]}\n echo \"log-file=$log_file\" >> \"$GITHUB_OUTPUT\"\n exit \"$exit_code\"\n";
|
|
@@ -143,7 +143,7 @@ var delivery_deploy_default$1 = "name: Deploy\n\non: # yamllint disable-line rul
|
|
|
143
143
|
var delivery_previewCheck_default$1 = "name: Preview Check\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n preview-url:\n description: The PR's Vercel preview deployment URL, extracted from the triggering `deployment_status` event.\n required: true\n type: string\n sha:\n description: The deployed commit SHA — checked out explicitly, since GitHub evaluates which version of this workflow file to run from the default branch for a deployment_status event (unlike push/pull_request), and actions/checkout defaults to that same ref absent an explicit override.\n required: true\n type: string\n cwd:\n description: Directory to run the check from — the opted-in package's own directory in a monorepo. Defaults to the repo root.\n required: false\n type: string\n default: \".\"\n vercel-project:\n description: >\n Expected Vercel project id/name the preview URL must resolve\n to (verified live via Vercel's API) before\n VERCEL_PROTECTION_BYPASS_SECRET is ever attached to a request\n (holocron#1032 review) — a bare *.vercel.app hostname proves\n nothing on its own, since anyone can register their own\n Vercel project. Omitted → the bypass secret is never\n attached, regardless of whether it's configured.\n required: false\n type: string\n default: \"\"\n secrets:\n VERCEL_TOKEN:\n description: >\n Required alongside vercel-project to verify deployment\n ownership via Vercel's API (read-only use here). Not required\n when vercel-project is omitted.\n required: false\n VERCEL_PROTECTION_BYPASS_SECRET:\n description: >\n Vercel's \"Protection Bypass for Automation\" secret for the\n target project (holocron#1030) — sent as the\n x-vercel-protection-bypass header so this check's request\n isn't redirected to Vercel's own SSO login by Deployment\n Protection, which is on by default for Preview deployments.\n Optional: a check against a URL with protection disabled (or\n a provider other than Vercel) needs nothing here.\n required: false\n\njobs:\n preview-check:\n name: Run monitoring checks against the preview deployment\n permissions:\n contents: read\n # Both read-only, needed by the \"Resolve author trust\" step\n # below: pull-requests to find which PR `inputs.sha` belongs to,\n # and the collaborators/permission lookup (Metadata: read on a\n # PAT, already covered by contents) that step makes against the\n # real PR author.\n pull-requests: read\n runs-on: ubuntu-latest\n timeout-minutes: 15\n steps:\n - name: Validate the preview URL\n id: validate\n env:\n CANDIDATE: ${{ inputs.preview-url }}\n run: |\n # `preview-url` comes from the triggering deployment_status\n # event's environment_url -- set by whoever created that\n # deployment status, not necessarily this repo's own\n # delivery.previewDeploy job (anyone with `deployments: write`\n # could create one directly via the API). Validated here,\n # independently of the event's origin, before this runner\n # ever issues an HTTP request to it (the monitoring check\n # below): without this, a crafted environment_url could point\n # the runner at an internal or attacker-controlled host\n # (SSRF) instead of a real Vercel preview. Forwarding the\n # re-serialized `url.href` rather than the raw input also\n # closes off argument injection through embedded whitespace\n # or shell metacharacters -- a string that parses can't carry\n # either.\n host=$(node -e 'try { process.stdout.write(new URL(process.argv[1]).hostname) } catch { process.exit(1) }' \"$CANDIDATE\") || host=\"\"\n if [[ \"$host\" != *.vercel.app ]]; then\n echo \"::error::preview-url '$CANDIDATE' does not parse to a *.vercel.app host (parsed host: '${host:-<unparseable>}')\"\n exit 1\n fi\n url=$(node -e 'const u = new URL(process.argv[1]); if (u.protocol !== \"https:\") process.exit(1); process.stdout.write(u.href)' \"$CANDIDATE\") || {\n echo \"::error::preview-url '$CANDIDATE' did not parse to an https:// URL\"\n exit 1\n }\n echo \"url=$url\" >> \"$GITHUB_OUTPUT\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n ref: ${{ inputs.sha }}\n # This checks out and runs a PR's own holocron.config.ts —\n # untrusted until reviewed. Never leave a credential sitting\n # in .git/config for that code to read and exfiltrate; no\n # later step in this job needs to push or pull authenticated.\n persist-credentials: false\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Resolve author trust\n id: trust\n env:\n # Ambient, always-available -- no new secret. Only used for\n # two read-only lookups, both within what this token already\n # has by default.\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n SHA: ${{ inputs.sha }}\n run: |\n # Deliberately NOT a value carried in through the\n # deployment's own `payload` field (sourcery review on\n # #1058's first attempt): that field is attacker-controlled\n # by anyone with `deployments: write` -- same precondition\n # already needed to create a deployment_status event at all\n # -- so trusting whatever it claims would let that same\n # attacker forge payload.collaborator_permission = \"admin\"\n # on their own crafted deployment and unlock the bypass\n # secret regardless of who actually authored the code. This\n # step derives trust itself, from data it verifies\n # independently: which real PR `inputs.sha` belongs to, and\n # that PR's real author's current push-access level -- an\n # attacker can't forge either without already having\n # genuine write access to this repo.\n #\n # First attempt used\n # github.event.pull_request.author_association instead, and\n # separately turned out to be wrong for a different reason:\n # that field only reports MEMBER for a *publicly visible* org\n # membership, so a maintainer with private org membership\n # reads as CONTRIBUTOR and would wrongly fail-closed on their\n # own PRs (confirmed live on this exact repo, holocron#1057\n # follow-up). This lookup has neither problem: it reflects\n # real access -- direct collaborator, team membership, or\n # org default -- independent of any profile visibility\n # setting, and needs only \"Pull requests: read\" plus the\n # baseline \"Metadata: read\" every token already has.\n #\n # No PR found for this SHA, or no login on it -> \"none\" ->\n # untrusted. admin/maintain/write are the three roles GitHub\n # itself treats as push-capable. Missing or unrecognized ->\n # untrusted (fail-closed): an absent credential is safer\n # than a wrongly granted one.\n author=$(gh api \"repos/${GITHUB_REPOSITORY}/commits/${SHA}/pulls\" --jq '(.[0] // {}) | .user.login // empty' 2>/dev/null) || author=\"\"\n permission=\"none\"\n if [ -n \"$author\" ]; then\n permission=$(gh api \"repos/${GITHUB_REPOSITORY}/collaborators/${author}/permission\" --jq '.permission // \"none\"' 2>/dev/null) || permission=\"none\"\n fi\n case \"$permission\" in\n admin | maintain | write) trusted=true ;;\n *) trusted=false ;;\n esac\n echo \"trusted=$trusted\" >> \"$GITHUB_OUTPUT\"\n\n - name: Resolve monitoring checks\n id: resolve\n # Untrusted: loads and runs a PR's own holocron.config.ts, same\n # as the checkout above. No secrets in this step's env at all\n # (holocron#1032 review) -- VERCEL_TOKEN and\n # VERCEL_PROTECTION_BYPASS_SECRET live only on the step below,\n # which never loads this repo's config and so never runs a\n # single line of PR-authored code.\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n command: monitoring resolve-checks\n args: --cwd=${{ inputs.cwd }} --out=${{ runner.temp }}/monitoring-checks.json\n\n - name: Verify project and run monitoring checks\n env:\n # Read directly by `monitoring run-resolved`\n # (packages/cli/src/cli.ts) -- not part of the CLI's own\n # `--token`-resolution chain, since neither is a vendor API\n # credential this command calls anything with: VERCEL_TOKEN\n # only verifies, read-only, that the preview URL resolves to\n # vercel-project before VERCEL_PROTECTION_BYPASS_SECRET (a\n # plain header forwarded as-is) is ever attached. The command\n # itself never attaches the bypass header when vercel-project\n # is empty, regardless of whether these secrets are\n # configured -- never attached to an unverified URL.\n #\n # Both gated on `steps.trust.outputs.trusted` (holocron#1057):\n # confirmed live that Vercel forwards this header straight\n # through to the deployed function, so an untrusted PR's own\n # code could read and exfiltrate it off its own incoming\n # request. An untrusted deployment gets neither secret.\n VERCEL_TOKEN: ${{ steps.trust.outputs.trusted == 'true' && secrets.VERCEL_TOKEN || '' }}\n VERCEL_PROTECTION_BYPASS_SECRET: ${{ steps.trust.outputs.trusted == 'true' && secrets.VERCEL_PROTECTION_BYPASS_SECRET || '' }}\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n command: monitoring run-resolved\n # vercel-project is NOT gated on trust (sourcery review on\n # #1058's first attempt) -- it isn't a secret, and clearing\n # it for an untrusted deployment would make\n # resolveVercelBypassHeaders treat \"no project given\" as\n # trivially verified (its early-return for an omitted\n # project), skipping ownership verification entirely rather\n # than failing. Always passing the real project here means\n # an untrusted deployment instead hits the \"project given\n # but no token\" branch -- VERCEL_TOKEN is empty above --\n # which fails the check outright, before ever issuing the\n # request, rather than letting an unverified host's response\n # (e.g. a 3xx redirect a status-less assertion wouldn't\n # catch) report green.\n args: --checks-file=${{ runner.temp }}/monitoring-checks.json --vercel-project=${{ inputs.vercel-project }} ${{ steps.validate.outputs.url }}\n";
|
|
144
144
|
//#endregion
|
|
145
145
|
//#region src/templates/reusable/delivery.previewDeploy.yml
|
|
146
|
-
var delivery_previewDeploy_default$1 = "name: Preview Deploy\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n cwd:\n description: Directory to run the delivery.previewDeploy task from — the opted-in package's own directory in a monorepo. Defaults to the repo root.\n required: false\n type: string\n default: \".\"\n paths:\n description: >\n JSON array of repo-relative path prefixes (each ending in \"/\") --\n a PR touching none of them skips the actual deploy work instead\n of spinning up a Vercel preview for every PR in the monorepo\n regardless of relevance (holocron#1023). Defaults to an empty\n array: no filter, every PR is relevant -- correct for a\n single-repo consumer, where every PR IS a PR to that package.\n required: false\n type: string\n default: \"[]\"\n secrets:\n VERCEL_TOKEN:\n required: false\n HOLOCRON_DEPLOYMENTS_TOKEN:\n description: >\n A dedicated fine-grained PAT (Deployments: read/write only — see\n docs/tokens.md), not the default GITHUB_TOKEN — GitHub does not\n start a new workflow run for a deployment_status event created\n using a run's own GITHUB_TOKEN, which would make a\n deployment_status-triggered check (e.g. delivery.previewCheck)\n silently never fire. No existing HOLOCRON_<FEATURE>_TOKEN carries\n the Deployments permission, so `secrets: inherit` on the thin\n caller forwards nothing for this unless a repo/org secret\n literally named HOLOCRON_DEPLOYMENTS_TOKEN has been provisioned.\n required: false\n\njobs:\n # Two jobs, deliberately not one: \"report\" never checks out the repo at\n # all, so it never runs a single line of this PR's own code. \"deploy\"\n # does — it has to, to build and stage the preview — and runs with\n # VERCEL_TOKEN present for that (tracked as a residual risk, #1020: the\n # build step still runs this monorepo's own PR-affectable CLI build,\n # pre-review). Without the split, a malicious build/stage script could\n # persist a `gh` shim onto $GITHUB_PATH (or similar runner-state\n # tampering) that a LATER step in the SAME job — the one that finally\n # gets GH_TOKEN — would then execute instead of the real `gh` CLI,\n # stealing that token too. Separate jobs run on separate, fresh\n # runners; \"report\" has nothing of the first job's runner state to\n # inherit.\n deploy:\n name: Deploy preview\n runs-on: ubuntu-latest\n # Setup/build/stage, plus the deploy capability's own\n # wait-for-ready polling (up to 10 minutes by itself) — 10 would kill\n # this before that polling could even finish.\n timeout-minutes: 20\n permissions:\n contents: read\n # Listing the PR's changed files (below, via `gh api`) needs this —\n # must be granted here too, not only on the thin caller: GitHub\n # hard-fails the whole run at startup, zero jobs dispatched, if a\n # called reusable workflow's job asks for more than the caller\n # grants (holocron#1058).\n pull-requests: read\n outputs:\n url: ${{ steps.extract.outputs.url }}\n steps:\n - name: Check whether the PR touches any watched path\n id: filter\n env:\n GH_TOKEN: ${{ github.token }}\n PATHS: ${{ inputs.paths }}\n run: |\n # No checkout needed — the PR's changed-file list comes from the\n # API, not local git history (which this job's later shallow\n # checkout, below, doesn't have enough of to diff against the\n # base branch anyway).\n #\n # `jq -e` both parses PATHS and rejects anything that isn't a\n # JSON array (holocron#1069 review) -- a malformed input (bad\n # JSON, or valid JSON that isn't an array) fails the job loudly\n # instead of the process-substitution loop below silently\n # reading zero prefixes and skipping the deploy with a green\n # check and no explanation.\n paths=$(echo \"$PATHS\" | jq -e 'if type != \"array\" then error(\"paths input is not a JSON array\") else . end') || {\n echo \"::error::delivery.previewDeploy's paths input is not a valid JSON array: $PATHS\"\n exit 1\n }\n # `jq 'length'` on the PARSED value, not a comparison against the\n # literal string \"[]\" -- any valid JSON encoding of an empty\n # array (e.g. \" [ ] \") means the same \"no filter\" thing.\n if [ \"$(echo \"$paths\" | jq 'length')\" = \"0\" ]; then\n echo \"relevant=true\" >> \"$GITHUB_OUTPUT\"\n exit 0\n fi\n # Both the new name AND the old one for a rename -- a file\n # renamed OUT of a watched package (old path matches, new\n # doesn't) is still a real change to that package's tree.\n files=$(gh api \"repos/${GITHUB_REPOSITORY}/pulls/${{ github.event.pull_request.number }}/files\" --paginate --jq '.[] | .filename, (.previous_filename // empty)')\n file_count=$(echo \"$files\" | grep -c . || true)\n if [ \"$file_count\" -ge 3000 ]; then\n # The pulls-files endpoint caps out at 3,000 entries even with\n # --paginate -- past that, a watched path could be among the\n # files we never saw. Can't prove the PR irrelevant, so fail\n # safe to relevant rather than risk silently skipping a real\n # deploy.\n echo \"::warning::PR has >= 3000 changed files -- can't reliably determine path relevance, treating as relevant\"\n echo \"relevant=true\" >> \"$GITHUB_OUTPUT\"\n exit 0\n fi\n relevant=false\n while IFS= read -r prefix; do\n [ -z \"$prefix\" ] && continue\n while IFS= read -r file; do\n [ -z \"$file\" ] && continue\n # Unquoted $prefix (deliberately) -- case pattern-matches it\n # as a glob, anchored at the START of the repo-relative path\n # (holocron#1069 review: grep -F matched the prefix as a\n # literal substring anywhere in the filename, and couldn't\n # match a configured glob like \"custom/**\" at all). The\n # trailing `*` covers a plain directory prefix like\n # \"apps/sentinel/\"; a caller-supplied prefix that already\n # has its own wildcards (e.g. \"custom/**\") just gets one\n # more, harmlessly.\n case \"$file\" in\n $prefix*) relevant=true; break 2 ;;\n esac\n done <<< \"$files\"\n done < <(echo \"$paths\" | jq -r '.[]')\n echo \"relevant=$relevant\" >> \"$GITHUB_OUTPUT\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n if: ${{ steps.filter.outputs.relevant == 'true' }}\n with:\n # The PR's exact head SHA, not the default pull_request\n # merge-commit ref — what gets built and deployed must be the\n # exact commit the PR actually points at.\n ref: ${{ github.event.pull_request.head.sha }}\n persist-credentials: false\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n if: ${{ steps.filter.outputs.relevant == 'true' }}\n\n - name: Check deploy credentials are available\n if: ${{ steps.filter.outputs.relevant == 'true' }}\n id: creds\n env:\n VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n run: |\n # `secrets` isn't valid in a step `if:` (actionlint-enforced) —\n # a fork PR never receives this secret, so the deploy step below\n # skips cleanly on an empty token instead of failing on a\n # missing credential. That skip is silent to the fork\n # contributor -- no preview, no explanation why -- tracked\n # as #1027 rather than fixed here (the real fix needs either\n # a status comment or a maintainer-gated trusted-deploy path,\n # not a quick addition to this step).\n echo \"available=$([ -n \"$VERCEL_TOKEN\" ] && echo true || echo false)\" >> \"$GITHUB_OUTPUT\"\n\n - name: Deploy preview\n id: deploy\n if: ${{ steps.creds.outputs.available == 'true' }}\n env:\n VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n command: run delivery.previewDeploy\n args: --cwd=${{ inputs.cwd }}\n\n - name: Extract and validate the preview URL\n id: extract\n # Only after a real deploy — false both when the step above\n # failed and when it skipped cleanly (fork PR, no credential).\n if: ${{ steps.deploy.outcome == 'success' }}\n env:\n LOG_FILE: ${{ steps.deploy.outputs.log-file }}\n run: |\n # A loose https://\\S+ grab would match whatever text follows an\n # attacker-chosen URL this log happens to contain; grabbing\n # candidate text here and separately requiring the PARSED\n # hostname end in .vercel.app (below) closes that regardless of\n # how the candidate text is shaped — e.g. a userinfo trick like\n # \"https://x.vercel.app@127.0.0.1/\" would satisfy almost any\n # hand-written regex character class while actually parsing to\n # host 127.0.0.1, which a naive regex-only check already missed\n # once.\n candidate=$(grep -oE 'https://[^[:space:]\"]+' \"$LOG_FILE\" | tail -1)\n if [ -z \"$candidate\" ]; then\n echo \"::error::deploy succeeded but no https:// URL was found in its output\"\n exit 1\n fi\n host=$(node -e 'try { process.stdout.write(new URL(process.argv[1]).hostname) } catch { process.exit(1) }' \"$candidate\") || host=\"\"\n if [[ \"$host\" != *.vercel.app ]]; then\n echo \"::error::extracted URL '$candidate' does not parse to a *.vercel.app host (parsed host: '${host:-<unparseable>}')\"\n exit 1\n fi\n echo \"url=$candidate\" >> \"$GITHUB_OUTPUT\"\n\n report:\n name: Report deployment status\n needs: deploy\n if: ${{ needs.deploy.outputs.url != '' }}\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n deployments: write\n steps:\n - name: Create GitHub deployment and report status\n env:\n # gh reads GH_TOKEN as its auto-auth env var by its own\n # convention -- the secret feeding it is named\n # HOLOCRON_DEPLOYMENTS_TOKEN, matching this org's\n # HOLOCRON_<FEATURE>_TOKEN naming (docs/tokens.md).\n GH_TOKEN: ${{ secrets.HOLOCRON_DEPLOYMENTS_TOKEN }}\n # The PR's head BRANCH, not its SHA — GitHub's PR \"Environments\"\n # widget associates a Deployment with a PR by matching `ref` to\n # the PR's own branch name, not by SHA. GitHub resolves the\n # branch to whatever commit it points to AT CREATION TIME,\n # though, which is a separate moment from the deploy job's own\n # checkout (below, SHA) -- cross-checked after creation rather\n # than assumed.\n REF: ${{ github.event.pull_request.head.ref }}\n # The exact commit the deploy job checked out and built,\n # captured from the same frozen event payload this whole run\n # was triggered by -- stays constant for the run's lifetime\n # even if the PR branch itself moves while this job is running.\n SHA: ${{ github.event.pull_request.head.sha }}\n URL: ${{ needs.deploy.outputs.url }}\n run: |\n if [ -z \"$GH_TOKEN\" ]; then\n echo \"::error::HOLOCRON_DEPLOYMENTS_TOKEN secret is not set -- posting this status with the default GITHUB_TOKEN would not trigger a deployment_status-triggered check\"\n exit 1\n fi\n\n post_status() {\n gh api \"repos/${GITHUB_REPOSITORY}/deployments/${deploy_id}/statuses\" \\\n --method POST --field state=\"$1\" --field environment_url=\"$URL\" >/dev/null\n }\n latest_status() {\n gh api \"repos/${GITHUB_REPOSITORY}/deployments/${deploy_id}/statuses\" --jq '.[0].state // empty' 2>/dev/null\n }\n # A deployment this very run may have already created\n # server-side on an earlier attempt whose client-side response\n # got lost (e.g. a dropped connection) -- reused instead of\n # creating a second one, which would leave the first\n # permanently pending (nothing ever posts its status). Scoped\n # to the last 2 minutes so an OLDER deployment for the same\n # ref/environment (a previous, unrelated run) is never\n # mistaken for this one.\n find_recent_deployment() {\n gh api \"repos/${GITHUB_REPOSITORY}/deployments\" --method GET -f ref=\"$REF\" -f environment=Preview --jq '\n [.[] | select((now - (.created_at | fromdateiso8601)) < 120)] | sort_by(.created_at) | last | .id // empty' 2>/dev/null\n }\n\n # jq, not a hand-built string -- a branch name containing a\n # literal double quote (git allows it; GitHub's branch-naming\n # rules don't forbid it either) would otherwise break out of a\n # printf-constructed JSON string and produce an invalid payload\n # gh api can't send.\n payload=$(jq -n --arg ref \"$REF\" '{\n ref: $ref,\n environment: \"Preview\",\n description: \"Vercel Preview (file deploy)\",\n production_environment: false,\n auto_merge: false,\n required_contexts: []\n }')\n deploy_id=\"\"\n for attempt in 1 2 3; do\n deploy_id=$(find_recent_deployment)\n if [ -n \"$deploy_id\" ]; then\n echo \"reusing deployment ${deploy_id} created by an earlier attempt of this run\" >&2\n break\n fi\n deploy_id=$(echo \"$payload\" | gh api \"repos/${GITHUB_REPOSITORY}/deployments\" --method POST --input - | jq -r '.id // empty')\n if [ -n \"$deploy_id\" ]; then break; fi\n echo \"deployment creation failed (attempt ${attempt}/3) -- retrying...\" >&2\n sleep 5\n done\n if [ -z \"$deploy_id\" ]; then\n echo \"::error::failed to create a GitHub deployment after 3 attempts -- Vercel already has the preview, but nothing will report or check it\"\n exit 1\n fi\n\n # `ref` resolves to the branch's CURRENT tip at creation time --\n # only guaranteed to be the commit this job actually built if\n # the branch hasn't moved since the triggering push. A push to\n # the same PR while this job was still running would otherwise\n # silently associate the preview URL (built from the OLD\n # commit) with the NEW commit's deployment. A newer workflow\n # run already covers that new commit (cancel-in-progress:\n # false queues rather than cancels), so failing this stale one\n # loses nothing.\n deployed_sha=$(gh api \"repos/${GITHUB_REPOSITORY}/deployments/${deploy_id}\" --jq '.sha // empty')\n if [ \"$deployed_sha\" != \"$SHA\" ]; then\n echo \"::error::deployment ${deploy_id} resolved to ${deployed_sha}, not the deployed commit ${SHA} -- the PR branch moved while this job was running; a newer run already covers the new commit\"\n exit 1\n fi\n\n for attempt in 1 2 3; do\n if post_status success; then\n exit 0\n fi\n # The POST may have actually succeeded server-side even though\n # this client call reported failure (e.g. a dropped connection\n # after GitHub processed it) — checked before retrying, so a\n # transient client-side error never posts a second success and\n # fires delivery.previewCheck twice for the same preview.\n if [ \"$(latest_status)\" = \"success\" ]; then\n exit 0\n fi\n echo \"status POST failed (attempt ${attempt}/3) for deployment ${deploy_id} -- retrying...\" >&2\n sleep 5\n done\n\n # Exhausted retries — one last attempt to mark the deployment\n # failed rather than leaving it permanently \"pending\" in GitHub's\n # UI; best-effort, its own failure doesn't change this step's\n # already-failed outcome.\n post_status error || true\n echo \"::error::failed to post a success status for deployment ${deploy_id} after 3 attempts -- marked it failed instead of leaving it pending\"\n exit 1\n";
|
|
146
|
+
var delivery_previewDeploy_default$1 = "name: Preview Deploy\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n cwd:\n description: Directory to run the delivery.previewDeploy task from — the opted-in package's own directory in a monorepo. Defaults to the repo root.\n required: false\n type: string\n default: \".\"\n paths:\n description: >\n JSON array of repo-relative path prefixes (each ending in \"/\") --\n a PR touching none of them skips the actual deploy work instead\n of spinning up a Vercel preview for every PR in the monorepo\n regardless of relevance (holocron#1023). Defaults to an empty\n array: no filter, every PR is relevant -- correct for a\n single-repo consumer, where every PR IS a PR to that package.\n required: false\n type: string\n default: \"[]\"\n secrets:\n VERCEL_TOKEN:\n required: false\n HOLOCRON_DEPLOYMENTS_TOKEN:\n description: >\n A dedicated fine-grained PAT (Deployments: read/write only — see\n docs/tokens.md), not the default GITHUB_TOKEN — GitHub does not\n start a new workflow run for a deployment_status event created\n using a run's own GITHUB_TOKEN, which would make a\n deployment_status-triggered check (e.g. delivery.previewCheck)\n silently never fire. No existing HOLOCRON_<FEATURE>_TOKEN carries\n the Deployments permission, so `secrets: inherit` on the thin\n caller forwards nothing for this unless a repo/org secret\n literally named HOLOCRON_DEPLOYMENTS_TOKEN has been provisioned.\n required: false\n\njobs:\n # Two jobs, deliberately not one: \"report\" never checks out the repo at\n # all, so it never runs a single line of this PR's own code. \"deploy\"\n # does — it has to, to build and stage the preview — and runs with\n # VERCEL_TOKEN present for that (tracked as a residual risk, #1020: the\n # build step still runs this monorepo's own PR-affectable CLI build,\n # pre-review). Without the split, a malicious build/stage script could\n # persist a `gh` shim onto $GITHUB_PATH (or similar runner-state\n # tampering) that a LATER step in the SAME job — the one that finally\n # gets GH_TOKEN — would then execute instead of the real `gh` CLI,\n # stealing that token too. Separate jobs run on separate, fresh\n # runners; \"report\" has nothing of the first job's runner state to\n # inherit.\n deploy:\n name: Deploy preview\n runs-on: ubuntu-latest\n # Setup/build/stage, plus the deploy capability's own\n # wait-for-ready polling (up to 10 minutes by itself) — 10 would kill\n # this before that polling could even finish.\n timeout-minutes: 20\n permissions:\n contents: read\n # Listing the PR's changed files (below, via `gh api`) needs this —\n # must be granted here too, not only on the thin caller: GitHub\n # hard-fails the whole run at startup, zero jobs dispatched, if a\n # called reusable workflow's job asks for more than the caller\n # grants (holocron#1058).\n pull-requests: read\n outputs:\n url: ${{ steps.extract.outputs.url }}\n steps:\n - name: Check whether the PR touches any watched path\n id: filter\n env:\n GH_TOKEN: ${{ github.token }}\n PATHS: ${{ inputs.paths }}\n run: |\n # No checkout needed — the PR's changed-file list comes from the\n # API, not local git history (which this job's later shallow\n # checkout, below, doesn't have enough of to diff against the\n # base branch anyway).\n #\n # `jq -e` both parses PATHS and rejects anything that isn't a\n # JSON array (holocron#1069 review) -- a malformed input (bad\n # JSON, or valid JSON that isn't an array) fails the job loudly\n # instead of the process-substitution loop below silently\n # reading zero prefixes and skipping the deploy with a green\n # check and no explanation.\n paths=$(echo \"$PATHS\" | jq -e 'if type != \"array\" then error(\"paths input is not a JSON array\") else . end') || {\n echo \"::error::delivery.previewDeploy's paths input is not a valid JSON array: $PATHS\"\n exit 1\n }\n # `jq 'length'` on the PARSED value, not a comparison against the\n # literal string \"[]\" -- any valid JSON encoding of an empty\n # array (e.g. \" [ ] \") means the same \"no filter\" thing.\n if [ \"$(echo \"$paths\" | jq 'length')\" = \"0\" ]; then\n echo \"relevant=true\" >> \"$GITHUB_OUTPUT\"\n exit 0\n fi\n # Both the new name AND the old one for a rename -- a file\n # renamed OUT of a watched package (old path matches, new\n # doesn't) is still a real change to that package's tree.\n files=$(gh api \"repos/${GITHUB_REPOSITORY}/pulls/${{ github.event.pull_request.number }}/files\" --paginate --jq '.[] | .filename, (.previous_filename // empty)')\n file_count=$(echo \"$files\" | grep -c . || true)\n if [ \"$file_count\" -ge 3000 ]; then\n # The pulls-files endpoint caps out at 3,000 entries even with\n # --paginate -- past that, a watched path could be among the\n # files we never saw. Can't prove the PR irrelevant, so fail\n # safe to relevant rather than risk silently skipping a real\n # deploy.\n echo \"::warning::PR has >= 3000 changed files -- can't reliably determine path relevance, treating as relevant\"\n echo \"relevant=true\" >> \"$GITHUB_OUTPUT\"\n exit 0\n fi\n relevant=false\n while IFS= read -r prefix; do\n [ -z \"$prefix\" ] && continue\n while IFS= read -r file; do\n [ -z \"$file\" ] && continue\n # Unquoted $prefix (deliberately) -- case pattern-matches it\n # as a glob, anchored at the START of the repo-relative path\n # (holocron#1069 review: grep -F matched the prefix as a\n # literal substring anywhere in the filename, and couldn't\n # match a configured glob like \"custom/**\" at all). The\n # trailing `*` covers a plain directory prefix like\n # \"apps/sentinel/\"; a caller-supplied prefix that already\n # has its own wildcards (e.g. \"custom/**\") just gets one\n # more, harmlessly.\n case \"$file\" in\n $prefix*) relevant=true; break 2 ;;\n esac\n done <<< \"$files\"\n done < <(echo \"$paths\" | jq -r '.[]')\n echo \"relevant=$relevant\" >> \"$GITHUB_OUTPUT\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n if: ${{ steps.filter.outputs.relevant == 'true' }}\n with:\n # The PR's exact head SHA, not the default pull_request\n # merge-commit ref — what gets built and deployed must be the\n # exact commit the PR actually points at.\n ref: ${{ github.event.pull_request.head.sha }}\n persist-credentials: false\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n if: ${{ steps.filter.outputs.relevant == 'true' }}\n\n - name: Check deploy credentials are available\n if: ${{ steps.filter.outputs.relevant == 'true' }}\n id: creds\n env:\n VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n run: |\n # `secrets` isn't valid in a step `if:` (actionlint-enforced) —\n # a fork PR never receives this secret, so the deploy step below\n # skips cleanly on an empty token instead of failing on a\n # missing credential -- correct, and must stay that way: a\n # fork-triggered pull_request run never gets a write-scoped\n # token either, so there's no way to post a PR comment or check\n # run from here regardless (that needs a separate,\n # pull_request_target-triggered job in the base repo's\n # context -- a real feature with its own review surface, not\n # this step; holocron#1027 explicitly scoped this fix to\n # observability only). The ::notice:: below needs no extra\n # permission -- it's a log annotation the runner itself renders,\n # not an API write -- so it's the cheap half of #1027 that IS\n # worth doing here: a visible, on-PR explanation instead of\n # silence.\n if [ -n \"$VERCEL_TOKEN\" ]; then\n echo \"available=true\" >> \"$GITHUB_OUTPUT\"\n else\n echo \"available=false\" >> \"$GITHUB_OUTPUT\"\n echo \"::notice title=No preview deployed::This run has no VERCEL_TOKEN available, so no preview was deployed -- most commonly because GitHub withholds repository secrets from a pull_request run triggered by a fork, though any run missing this secret ends up here the same way. Since this run creates no deployment, it won't trigger delivery.previewCheck for this PR. A maintainer can deploy one manually if needed.\"\n fi\n\n - name: Deploy preview\n id: deploy\n if: ${{ steps.creds.outputs.available == 'true' }}\n env:\n VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n command: run delivery.previewDeploy\n args: --cwd=${{ inputs.cwd }}\n\n - name: Extract and validate the preview URL\n id: extract\n # Only after a real deploy — false both when the step above\n # failed and when it skipped cleanly (fork PR, no credential).\n if: ${{ steps.deploy.outcome == 'success' }}\n env:\n LOG_FILE: ${{ steps.deploy.outputs.log-file }}\n run: |\n # A loose https://\\S+ grab would match whatever text follows an\n # attacker-chosen URL this log happens to contain; grabbing\n # candidate text here and separately requiring the PARSED\n # hostname end in .vercel.app (below) closes that regardless of\n # how the candidate text is shaped — e.g. a userinfo trick like\n # \"https://x.vercel.app@127.0.0.1/\" would satisfy almost any\n # hand-written regex character class while actually parsing to\n # host 127.0.0.1, which a naive regex-only check already missed\n # once.\n candidate=$(grep -oE 'https://[^[:space:]\"]+' \"$LOG_FILE\" | tail -1)\n if [ -z \"$candidate\" ]; then\n echo \"::error::deploy succeeded but no https:// URL was found in its output\"\n exit 1\n fi\n host=$(node -e 'try { process.stdout.write(new URL(process.argv[1]).hostname) } catch { process.exit(1) }' \"$candidate\") || host=\"\"\n if [[ \"$host\" != *.vercel.app ]]; then\n echo \"::error::extracted URL '$candidate' does not parse to a *.vercel.app host (parsed host: '${host:-<unparseable>}')\"\n exit 1\n fi\n echo \"url=$candidate\" >> \"$GITHUB_OUTPUT\"\n\n report:\n name: Report deployment status\n needs: deploy\n if: ${{ needs.deploy.outputs.url != '' }}\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n deployments: write\n steps:\n - name: Create GitHub deployment and report status\n env:\n # gh reads GH_TOKEN as its auto-auth env var by its own\n # convention -- the secret feeding it is named\n # HOLOCRON_DEPLOYMENTS_TOKEN, matching this org's\n # HOLOCRON_<FEATURE>_TOKEN naming (docs/tokens.md).\n GH_TOKEN: ${{ secrets.HOLOCRON_DEPLOYMENTS_TOKEN }}\n # The PR's head BRANCH, not its SHA — GitHub's PR \"Environments\"\n # widget associates a Deployment with a PR by matching `ref` to\n # the PR's own branch name, not by SHA. GitHub resolves the\n # branch to whatever commit it points to AT CREATION TIME,\n # though, which is a separate moment from the deploy job's own\n # checkout (below, SHA) -- cross-checked after creation rather\n # than assumed.\n REF: ${{ github.event.pull_request.head.ref }}\n # The exact commit the deploy job checked out and built,\n # captured from the same frozen event payload this whole run\n # was triggered by -- stays constant for the run's lifetime\n # even if the PR branch itself moves while this job is running.\n SHA: ${{ github.event.pull_request.head.sha }}\n URL: ${{ needs.deploy.outputs.url }}\n run: |\n if [ -z \"$GH_TOKEN\" ]; then\n echo \"::error::HOLOCRON_DEPLOYMENTS_TOKEN secret is not set -- posting this status with the default GITHUB_TOKEN would not trigger a deployment_status-triggered check\"\n exit 1\n fi\n\n post_status() {\n gh api \"repos/${GITHUB_REPOSITORY}/deployments/${deploy_id}/statuses\" \\\n --method POST --field state=\"$1\" --field environment_url=\"$URL\" >/dev/null\n }\n latest_status() {\n gh api \"repos/${GITHUB_REPOSITORY}/deployments/${deploy_id}/statuses\" --jq '.[0].state // empty' 2>/dev/null\n }\n # A deployment this very run may have already created\n # server-side on an earlier attempt whose client-side response\n # got lost (e.g. a dropped connection) -- reused instead of\n # creating a second one, which would leave the first\n # permanently pending (nothing ever posts its status). Scoped\n # to the last 2 minutes so an OLDER deployment for the same\n # ref/environment (a previous, unrelated run) is never\n # mistaken for this one.\n find_recent_deployment() {\n gh api \"repos/${GITHUB_REPOSITORY}/deployments\" --method GET -f ref=\"$REF\" -f environment=Preview --jq '\n [.[] | select((now - (.created_at | fromdateiso8601)) < 120)] | sort_by(.created_at) | last | .id // empty' 2>/dev/null\n }\n\n # jq, not a hand-built string -- a branch name containing a\n # literal double quote (git allows it; GitHub's branch-naming\n # rules don't forbid it either) would otherwise break out of a\n # printf-constructed JSON string and produce an invalid payload\n # gh api can't send.\n payload=$(jq -n --arg ref \"$REF\" '{\n ref: $ref,\n environment: \"Preview\",\n description: \"Vercel Preview (file deploy)\",\n production_environment: false,\n auto_merge: false,\n required_contexts: []\n }')\n deploy_id=\"\"\n for attempt in 1 2 3; do\n deploy_id=$(find_recent_deployment)\n if [ -n \"$deploy_id\" ]; then\n echo \"reusing deployment ${deploy_id} created by an earlier attempt of this run\" >&2\n break\n fi\n deploy_id=$(echo \"$payload\" | gh api \"repos/${GITHUB_REPOSITORY}/deployments\" --method POST --input - | jq -r '.id // empty')\n if [ -n \"$deploy_id\" ]; then break; fi\n echo \"deployment creation failed (attempt ${attempt}/3) -- retrying...\" >&2\n sleep 5\n done\n if [ -z \"$deploy_id\" ]; then\n echo \"::error::failed to create a GitHub deployment after 3 attempts -- Vercel already has the preview, but nothing will report or check it\"\n exit 1\n fi\n\n # `ref` resolves to the branch's CURRENT tip at creation time --\n # only guaranteed to be the commit this job actually built if\n # the branch hasn't moved since the triggering push. A push to\n # the same PR while this job was still running would otherwise\n # silently associate the preview URL (built from the OLD\n # commit) with the NEW commit's deployment. A newer workflow\n # run already covers that new commit (cancel-in-progress:\n # false queues rather than cancels), so failing this stale one\n # loses nothing.\n deployed_sha=$(gh api \"repos/${GITHUB_REPOSITORY}/deployments/${deploy_id}\" --jq '.sha // empty')\n if [ \"$deployed_sha\" != \"$SHA\" ]; then\n echo \"::error::deployment ${deploy_id} resolved to ${deployed_sha}, not the deployed commit ${SHA} -- the PR branch moved while this job was running; a newer run already covers the new commit\"\n exit 1\n fi\n\n for attempt in 1 2 3; do\n if post_status success; then\n exit 0\n fi\n # The POST may have actually succeeded server-side even though\n # this client call reported failure (e.g. a dropped connection\n # after GitHub processed it) — checked before retrying, so a\n # transient client-side error never posts a second success and\n # fires delivery.previewCheck twice for the same preview.\n if [ \"$(latest_status)\" = \"success\" ]; then\n exit 0\n fi\n echo \"status POST failed (attempt ${attempt}/3) for deployment ${deploy_id} -- retrying...\" >&2\n sleep 5\n done\n\n # Exhausted retries — one last attempt to mark the deployment\n # failed rather than leaving it permanently \"pending\" in GitHub's\n # UI; best-effort, its own failure doesn't change this step's\n # already-failed outcome.\n post_status error || true\n echo \"::error::failed to post a success status for deployment ${deploy_id} after 3 attempts -- marked it failed instead of leaving it pending\"\n exit 1\n";
|
|
147
147
|
//#endregion
|
|
148
148
|
//#region src/templates/reusable/delivery.publish.yml
|
|
149
149
|
var delivery_publish_default$1 = "name: Release\n\n# Semantic-release with OIDC Trusted Publishing.\n# actions/setup-node writes a default NODE_AUTH_TOKEN=${{ github.token }}\n# which shadows OIDC auth. We explicitly clear it so npm falls through to\n# the Trusted Publisher OIDC exchange.\n# The calling repo must have a .releaserc.json that configures branches,\n# plugins, and any publish options. npm@11+ is installed to support OIDC.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n dry-run:\n description: >\n When true, runs semantic-release --dry-run: analyzes commits and\n previews the next release without git writes or publish. Only\n meaningful for workflow_dispatch triggers; push-triggered runs\n always run fully.\n type: boolean\n required: false\n default: false\n post-release:\n description: >\n When true, dispatches a readme sync broadcast after a successful\n release. Enable for packages consumed by other repos via the\n registry-doc system so downstream README installation blocks\n stay current.\n type: boolean\n required: false\n default: false\n run-build:\n description: Run `pnpm build` before releasing\n type: boolean\n required: false\n default: true\n deploy-on-release:\n description: >\n When true, runs a separate `deploy` job after a successful release\n (holocron#930): `holocron deploy on-release` deploys every workspace\n package whose `delivery.deploy` task is `with: { on: release }`, when\n the release is on its channel and changes its paths. The job is its\n own check, re-runnable on its own, and gets VERCEL_TOKEN; the release\n job never sees it. Off by default, so a repo that doesn't deploy from\n its release never exposes the token.\n type: boolean\n required: false\n default: false\n sentry-project:\n description: >\n Sentry project slug for sourcemap upload and release creation after\n publishing. Omit to skip the Sentry release step entirely.\n type: string\n required: false\n default: \"\"\n secrets:\n HOLOCRON_RELEASE_TOKEN:\n description: >\n Fine-grained PAT (Contents + Issues + Pull requests: write) owned by\n an admin. Required when the default branch is protected by a ruleset —\n github.token cannot push through rulesets, but an admin PAT can.\n Takes priority over HOLOCRON_SYNC_TOKEN. Falls back to github.token.\n required: false\n HOLOCRON_SYNC_TOKEN:\n description: >\n Legacy alias for HOLOCRON_RELEASE_TOKEN — kept for backward compatibility.\n Prefer HOLOCRON_RELEASE_TOKEN for new repos.\n required: false\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to github.token.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when\n HOLOCRON_READ_TOKEN is not set.\n required: false\n SENTRY_AUTH_TOKEN:\n description: >\n Sentry auth token for sourcemap upload and release creation.\n Required when sentry-project is set. Use the org-level secret.\n required: false\n TURBO_TOKEN:\n required: false\n VERCEL_TOKEN:\n description: >\n Vercel token for the deploy job. Only used when deploy-on-release is\n true, and only by that job.\n required: false\n\njobs:\n release:\n name: Semantic release\n permissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n outputs:\n # Read by the deploy job (holocron#930). Empty unless semantic-release\n # published: `commit` is the release commit, `previous` the commit of\n # the release before it (to diff against), `channel` the new version's\n # prerelease identifier (`alpha`), empty for a stable release (`main`).\n commit: ${{ steps.release.outputs.commit }}\n previous: ${{ steps.release.outputs.previous }}\n channel: ${{ steps.release.outputs.channel }}\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n # Do not cancel in-progress releases — a partial release is worse than a slow one.\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n persist-credentials: false\n # Use HOLOCRON_RELEASE_TOKEN when available — git push (tags, release commits)\n # uses the checkout credential, not GITHUB_TOKEN env var. The\n # built-in github.token cannot push through branch protection rulesets.\n token: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Configure git identity\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n git config --global user.name \"$GIT_NAME\"\n git config --global user.email \"$GIT_EMAIL\"\n git config --global format.signoff true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Upgrade npm for OIDC support\n run: npm install -g npm@11 sigstore\n # sigstore is required by libnpmpublish/provenance.js at module parse\n # time — before any config takes effect. Some npm 11.x builds stopped\n # bundling it; installing it globally into the same prefix ensures it\n # resolves regardless of npm version. (Discovered 2026-07-09.)\n\n - run: pnpm build\n name: Build\n if: ${{ inputs.run-build == true }}\n\n - name: Release\n id: release\n run: |\n BEFORE=$(git rev-parse HEAD)\n PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true)\n PREVIOUS=\"\"\n if [ -n \"$PREVIOUS_TAG\" ]; then PREVIOUS=$(git rev-list -n 1 \"$PREVIOUS_TAG\"); fi\n if [ \"$DRY_RUN\" = \"true\" ]; then\n npx semantic-release --dry-run\n else\n npx semantic-release\n fi\n AFTER=$(git rev-parse HEAD)\n # semantic-release only commits (`chore(release): … [skip ci]`) when it\n # actually publishes. That commit gets no CI (and so no coverage\n # upload), which makes the next PR's `codecov/project` compare a full\n # report against a stale ancestor — a phantom drop (holocron#644).\n # Emit the new SHA so the steps below upload coverage for it.\n if [ \"$BEFORE\" != \"$AFTER\" ]; then\n echo \"commit=$AFTER\" >> \"$GITHUB_OUTPUT\"\n echo \"previous=$PREVIOUS\" >> \"$GITHUB_OUTPUT\"\n # The new version's channel: the prerelease identifier of its tag\n # (v5.0.0-alpha.108 -> alpha), empty for a stable release.\n VERSION=$(git describe --tags --abbrev=0)\n VERSION=${VERSION#v}\n CHANNEL=\"\"\n case \"$VERSION\" in *-*) PRE=${VERSION#*-}; CHANNEL=${PRE%%.*} ;; esac\n echo \"channel=$CHANNEL\" >> \"$GITHUB_OUTPUT\"\n fi\n env:\n DRY_RUN: ${{ inputs.dry-run }}\n # Prefer HOLOCRON_RELEASE_TOKEN (fine-grained PAT, Contents+Issues+PRs write,\n # owned by an admin with ruleset bypass) so @semantic-release/git can\n # push the version-bump commit through branch protection. Falls back to\n # HOLOCRON_SYNC_TOKEN (legacy) then github.token for unprotected repos.\n GITHUB_TOKEN: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n HUSKY: \"0\"\n NPM_CONFIG_PROVENANCE: true\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Coverage for the release commit\n if: ${{ steps.release.outputs.commit != '' }}\n with:\n task: verification.unitTests\n\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload release-commit coverage to Codecov\n if: ${{ steps.release.outputs.commit != '' }}\n # fail_ci_if_error makes a failed upload fail this step, so it is visible, but\n # continue-on-error keeps it from failing the job: the release has already\n # published, and the Sentry release steps below must still run (holocron#934).\n continue-on-error: true\n with:\n use_oidc: true\n fail_ci_if_error: true\n override_commit: ${{ steps.release.outputs.commit }}\n override_branch: ${{ github.ref_name }}\n\n - name: Get release version\n id: release_version\n if: ${{ inputs.sentry-project != '' && inputs.dry-run != true }}\n env:\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n run: |\n TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo \"\")\n if [ -n \"$TAG\" ]; then\n echo \"release=${SENTRY_PROJECT}@${TAG#v}\" >> \"$GITHUB_OUTPUT\"\n fi\n\n - name: Create Sentry release\n if: ${{ inputs.sentry-project != '' && steps.release_version.outputs.release != '' && inputs.dry-run != true }}\n uses: getsentry/action-release@ff07929a6537bac57790c3451cf4d364aca38528 # v3\n env:\n SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n SENTRY_ORG: theholocron\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n with:\n environment: production\n version: ${{ steps.release_version.outputs.release }}\n sourcemaps: \"**/dist\"\n\n deploy:\n name: Deploy\n needs: release\n # Only after a release that actually published (`commit` is empty on a dry\n # run or when nothing was released). A failed deploy fails only this job:\n # the release is already on npm, and this job re-runs on its own.\n if: ${{ inputs.deploy-on-release == true && inputs.dry-run != true && needs.release.outputs.commit != '' }}\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 30\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout the release commit\n with:\n ref: ${{ needs.release.outputs.commit }}\n fetch-depth: 0\n persist-credentials: false\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Deploy packages that deploy on release\n env:\n VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n with:\n command: deploy on-release\n args: >-\n --channel=${{ needs.release.outputs.channel }}\n --from=${{ needs.release.outputs.previous }}\n --to=${{ needs.release.outputs.commit }}\n\n sync-readme:\n name: Post-release readme sync\n needs: release\n if: ${{ inputs.post-release == true && inputs.dry-run != true }}\n runs-on: ubuntu-latest\n timeout-minutes: 5\n steps:\n - name: Trigger broadcast readme sync\n run: |\n gh workflow run sync-dispatch.yml \\\n --repo theholocron/.github \\\n --field \"steps=readme\"\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n";
|
|
@@ -164,7 +164,7 @@ var platform_commitStandards_default$1 = "name: Commit Standards\n\non: # yamlli
|
|
|
164
164
|
var platform_dispatchedCheck_default = "name: Dispatched Check\n\non: # yamllint disable-line rule:truthy\n workflow_dispatch:\n inputs:\n repo:\n description: Target repo, \"owner/name\", to check out and run the task against.\n type: string\n required: true\n ref:\n description: Git ref (branch or commit SHA) to check out.\n type: string\n required: true\n task:\n description: The holocron task name to run (e.g. \"verification.typeSafety\").\n type: string\n required: true\n check-run-id:\n description: >\n ID of the check run already posted as \"queued\" on the target repo —\n patched to \"completed\" once the task finishes.\n type: string\n required: true\n\npermissions:\n contents: read\n\njobs:\n run-task:\n name: Run a Sentinel-dispatched task against another repo\n runs-on: ubuntu-latest\n timeout-minutes: 20\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n # actions/create-github-app-token defaults to scoping its token to the\n # *current* repo (.github, where this workflow runs) when owner/\n # repositories are omitted — useless here, since every real use of this\n # token targets inputs.repo instead. Split \"owner/name\" up front so the\n # token step below can scope explicitly to the actual target.\n - name: Parse target repo\n id: target\n run: |\n echo \"owner=${INPUT_REPO%%/*}\" >> \"$GITHUB_OUTPUT\"\n echo \"name=${INPUT_REPO##*/}\" >> \"$GITHUB_OUTPUT\"\n env:\n INPUT_REPO: ${{ inputs.repo }}\n\n # Sentinel's own dispatch call only carries plain, non-secret inputs\n # (repo/ref/task/check-run-id — all visible in the Actions UI/logs).\n # This step mints a fresh, short-lived installation token here instead,\n # scoped by the same App's stored credentials — never a raw token\n # passed through a dispatch input.\n - name: Generate installation token\n id: app-token\n uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0\n with:\n app-id: ${{ secrets.SENTINEL_APP_ID }}\n private-key: ${{ secrets.SENTINEL_APP_PRIVATE_KEY }}\n owner: ${{ steps.target.outputs.owner }}\n repositories: ${{ steps.target.outputs.name }}\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout target repository\n with:\n repository: ${{ inputs.repo }}\n ref: ${{ inputs.ref }}\n token: ${{ steps.app-token.outputs.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Run the task\n id: run-task\n continue-on-error: true\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n task: ${{ inputs.task }}\n\n # A bare .../actions/runs/<id> link lands on the run summary, one click\n # short of the actual step-by-step log a normal check's details_url\n # points at. This job is always alone in its run, so the first (only)\n # job in the run's own job list is unambiguously this one.\n - name: Resolve this job's numeric ID\n id: job-info\n if: always()\n run: |\n job_id=$(gh api \"repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs\" --jq '.jobs[0].id')\n echo \"job_id=$job_id\" >> \"$GITHUB_OUTPUT\"\n env:\n GH_TOKEN: ${{ steps.app-token.outputs.token }}\n\n # Ships the task's real captured output (tsc/vitest/etc — whatever\n # `holocron run` actually printed) to the same Axiom dataset Sentinel's\n # own structured logs already flow into, tagged so it's queryable by\n # this exact run. Reuses the existing dataset rather than provisioning\n # a new one — a prototype-stage call, revisit if CI-log volume ever\n # warrants a dedicated dataset. Builds a permalink filtered to this\n # run's own logs (Axiom's own \"copy query link\" URL shape:\n # /<org>/query?initForm={\"apl\":\"...\"} ) so a viewer lands directly on\n # the relevant lines, not a bare dataset view.\n - name: Ship task output to Axiom\n id: axiom\n if: always()\n run: |\n message=$(tail -c 40000 \"$LOG_FILE\" 2>/dev/null || echo \"(no output captured)\")\n # \"level\" is one of the field names Axiom's Stream/Query tabs\n # recognize for severity-color highlighting (docs: level, @level,\n # severity, @severity, status.code) -- conclusion alone isn't.\n level=$([ \"$CONCLUSION\" = \"success\" ] && echo \"info\" || echo \"error\")\n event=$(jq -cn \\\n --arg time \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --arg repo \"$REPO\" --arg task \"$TASK\" --arg run_id \"$GITHUB_RUN_ID\" \\\n --arg job_id \"$JOB_ID\" --arg check_run_id \"$CHECK_RUN_ID\" \\\n --arg conclusion \"$CONCLUSION\" --arg level \"$level\" --arg message \"$message\" \\\n '{_time: $time, source: \"dispatched-check\", repo: $repo, task: $task,\n run_id: $run_id, job_id: $job_id, check_run_id: $check_run_id,\n conclusion: $conclusion, level: $level, message: $message}')\n curl -sf -X POST \"https://api.axiom.co/v1/datasets/${AXIOM_DATASET}/ingest\" \\\n -H \"Authorization: Bearer ${AXIOM_TOKEN}\" \\\n -H \"Content-Type: application/json\" \\\n -d \"[${event}]\" || echo \"::warning::Axiom ingest failed — continuing, the check itself still reports the real result\"\n\n # The ['...'] is Axiom's own APL array-dataset-reference syntax, not\n # a shell quote boundary — $AXIOM_DATASET/$CHECK_RUN_ID both expand\n # fine here; shellcheck's single-quote heuristic just can't tell.\n # shellcheck disable=SC2016\n apl=\"['${AXIOM_DATASET}'] | where check_run_id == \\\"${CHECK_RUN_ID}\\\"\"\n init_form=$(jq -cn --arg apl \"$apl\" '{apl: $apl}')\n encoded=$(jq -rn --arg s \"$init_form\" '$s | @uri')\n echo \"query-url=https://app.axiom.co/${AXIOM_ORG}/query?initForm=${encoded}\" >> \"$GITHUB_OUTPUT\"\n env:\n LOG_FILE: ${{ steps.run-task.outputs.log-file }}\n REPO: ${{ inputs.repo }}\n TASK: ${{ inputs.task }}\n JOB_ID: ${{ steps.job-info.outputs.job_id }}\n CHECK_RUN_ID: ${{ inputs.check-run-id }}\n CONCLUSION: ${{ steps.run-task.outcome == 'success' && 'success' || 'failure' }}\n AXIOM_ORG: the-holocron-7bbe\n AXIOM_DATASET: holocron-sentinel\n AXIOM_TOKEN: ${{ secrets.SENTINEL_AXIOM_INGEST_TOKEN }}\n\n # Always runs, success or failure — the queued check run on the target\n # repo must be resolved either way, not left hanging. Full JSON body via\n # jq + --input, not -f flags: output.text needs real nesting (gh api -f\n # has no dot-notation for that), and the captured task output can\n # contain characters (quotes, $, backticks) that would break a\n # hand-built -f string.\n - name: Report result to the target repo's check run\n if: always()\n run: |\n excerpt=$(tail -c 3000 \"$LOG_FILE\" 2>/dev/null || echo \"(no output captured)\")\n # The single-quoted format string is intentional — every %s fills\n # via printf's own args below, not shell expansion; nothing in it\n # needs to expand.\n # shellcheck disable=SC2016\n text=$(printf '**Task:** `%s`\\n\\n**Result:** %s\\n\\n[Full logs on Axiom](%s)\\n\\n### Last output\\n\\n```\\n%s\\n```\\n' \\\n \"$TASK\" \"$CONCLUSION\" \"$QUERY_URL\" \"$excerpt\")\n body=$(jq -n \\\n --arg conclusion \"$CONCLUSION\" \\\n --arg details_url \"$RUN_URL\" \\\n --arg title \"$TASK ($CONCLUSION)\" \\\n --arg summary \"Dispatched via theholocron/.github — see the log excerpt below, or the full run/Axiom links.\" \\\n --arg text \"$text\" \\\n '{status: \"completed\", conclusion: $conclusion, details_url: $details_url,\n output: {title: $title, summary: $summary, text: $text}}')\n echo \"$body\" | gh api --method PATCH \"/repos/${REPO}/check-runs/${CHECK_RUN_ID}\" --input -\n env:\n # `GH_TOKEN` is the literal name `gh api` requires to auto-authenticate\n # -- not a stand-in for a shared/blanket credential. Its value is the\n # short-lived, Sentinel-App-scoped token minted above, same as the\n # checkout step uses; never secrets.HOLOCRON_SYNC_TOKEN (the one\n # genuinely shared PAT in this org, used by sync-dispatch.yml).\n GH_TOKEN: ${{ steps.app-token.outputs.token }}\n REPO: ${{ inputs.repo }}\n TASK: ${{ inputs.task }}\n CHECK_RUN_ID: ${{ inputs.check-run-id }}\n CONCLUSION: ${{ steps.run-task.outcome == 'success' && 'success' || 'failure' }}\n LOG_FILE: ${{ steps.run-task.outputs.log-file }}\n QUERY_URL: ${{ steps.axiom.outputs.query-url }}\n RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ steps.job-info.outputs.job_id }}\n";
|
|
165
165
|
//#endregion
|
|
166
166
|
//#region src/templates/reusable/platform.repoSync.yml
|
|
167
|
-
var platform_repoSync_default$1 = "name: Sync\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n steps:\n description: >\n Sync steps to run (default: all). Valid values:\n labels, properties, teams, topics, keywords, description, homepage, readme, workflows, wiki.\n Pass a space-separated list to run a subset, e.g. \"readme\" or \"readme wiki\".\n type: string\n required: false\n secrets:\n HOLOCRON_ADMIN_TOKEN:\n description: Fine-grained PAT with admin scopes (labels, properties, teams).\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n Falls back to the vendor-native AXIOM_TOKEN secret.\n required: false\n AXIOM_TOKEN:\n description: Vendor-native fallback for HOLOCRON_AXIOM_TOKEN.\n required: false\n HOLOCRON_DEPLOY_TOKEN:\n description: Fine-grained PAT for GitHub Pages configuration.\n required: false\n HOLOCRON_ISSUES_TOKEN:\n description: Fine-grained PAT for issue management.\n required: false\n HOLOCRON_ORG_TOKEN:\n description: Org-scoped fine-grained PAT for team sync and org properties.\n required: false\n HOLOCRON_READ_TOKEN:\n description: Fine-grained PAT for read-only GitHub API calls.\n required: false\n HOLOCRON_SYNC_TOKEN:\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for gh CLI calls. Used when\n HOLOCRON_SYNC_TOKEN is not set.\n required: false\n\njobs:\n sync:\n name: Run holocron repo sync\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: write\n pull-requests: write\n # Job-level so the `holocron` composite action's steps inherit them.\n env:\n HOLOCRON_ADMIN_TOKEN: ${{ secrets.HOLOCRON_ADMIN_TOKEN }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN || secrets.AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET || vars.AXIOM_DATASET }}\n POSTHOG_PROJECT_TOKEN: ${{ vars.POSTHOG_PROJECT_TOKEN }}\n HOLOCRON_DEPLOY_TOKEN: ${{ secrets.HOLOCRON_DEPLOY_TOKEN }}\n HOLOCRON_ISSUES_TOKEN: ${{ secrets.HOLOCRON_ISSUES_TOKEN }}\n HOLOCRON_ORG_TOKEN: ${{ secrets.HOLOCRON_ORG_TOKEN }}\n HOLOCRON_READ_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Run holocron repo sync\n # The `holocron` action resolves @theholocron/cli from node_modules (or\n # builds the workspace when it's an unbuilt checkout — the holocron repo\n # itself). `inputs.steps` is a space-separated list or empty; the\n # `--steps <list>` arg is omitted when empty.\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n command: repo sync\n args: ${{ inputs.steps != '' && format('--steps {0}', inputs.steps) || '' }}\n\n - name: Format generated files\n run: |\n pnpm exec prettier --write README.md docs/src/content/docs/index.mdx 2>/dev/null || true\n # `holocron repo sync` writes package.json fields (keywords, description,\n # homepage) with a plain assignment, which appends new keys at the\n # end — re-apply the canonical order the lint config enforces.\n pnpm exec eslint --fix --no-warn-ignored package.json 2>/dev/null || true\n\n - uses: theholocron/.github/.github/actions/auto-commit@main\n id: auto-commit\n name: Commit sync changes\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n branch: chore/auto-sync\n commit-message: \"chore: sync from holocron.config\"\n commit-options: \"--no-verify\"\n\n - name: Open PR
|
|
167
|
+
var platform_repoSync_default$1 = "name: Sync\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n steps:\n description: >\n Sync steps to run (default: all). Valid values:\n labels, properties, teams, topics, keywords, description, homepage, readme, workflows, wiki.\n Pass a space-separated list to run a subset, e.g. \"readme\" or \"readme wiki\".\n type: string\n required: false\n secrets:\n HOLOCRON_ADMIN_TOKEN:\n description: Fine-grained PAT with admin scopes (labels, properties, teams).\n required: false\n HOLOCRON_AXIOM_TOKEN:\n description: >\n Axiom API token. When set alongside the HOLOCRON_AXIOM_DATASET\n repo/org variable, the CLI ships this run's structured logs to Axiom.\n Falls back to the vendor-native AXIOM_TOKEN secret.\n required: false\n AXIOM_TOKEN:\n description: Vendor-native fallback for HOLOCRON_AXIOM_TOKEN.\n required: false\n HOLOCRON_DEPLOY_TOKEN:\n description: Fine-grained PAT for GitHub Pages configuration.\n required: false\n HOLOCRON_ISSUES_TOKEN:\n description: Fine-grained PAT for issue management.\n required: false\n HOLOCRON_ORG_TOKEN:\n description: Org-scoped fine-grained PAT for team sync and org properties.\n required: false\n HOLOCRON_READ_TOKEN:\n description: Fine-grained PAT for read-only GitHub API calls.\n required: false\n HOLOCRON_SYNC_TOKEN:\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for gh CLI calls. Used when\n HOLOCRON_SYNC_TOKEN is not set.\n required: false\n\njobs:\n sync:\n name: Run holocron repo sync\n runs-on: ubuntu-latest\n timeout-minutes: 10\n # Serialise the find-or-create-PR and supersede steps repo-wide: the caller's\n # concurrency key is per ref, so runs from different refs could otherwise\n # interleave and leave several sync PRs open.\n concurrency:\n group: repo-sync-pr-${{ github.repository }}\n cancel-in-progress: false\n permissions:\n contents: write\n pull-requests: write\n # Job-level so the `holocron` composite action's steps inherit them.\n env:\n HOLOCRON_ADMIN_TOKEN: ${{ secrets.HOLOCRON_ADMIN_TOKEN }}\n HOLOCRON_AXIOM_TOKEN: ${{ secrets.HOLOCRON_AXIOM_TOKEN || secrets.AXIOM_TOKEN }}\n HOLOCRON_AXIOM_DATASET: ${{ vars.HOLOCRON_AXIOM_DATASET || vars.AXIOM_DATASET }}\n POSTHOG_PROJECT_TOKEN: ${{ vars.POSTHOG_PROJECT_TOKEN }}\n HOLOCRON_DEPLOY_TOKEN: ${{ secrets.HOLOCRON_DEPLOY_TOKEN }}\n HOLOCRON_ISSUES_TOKEN: ${{ secrets.HOLOCRON_ISSUES_TOKEN }}\n HOLOCRON_ORG_TOKEN: ${{ secrets.HOLOCRON_ORG_TOKEN }}\n HOLOCRON_READ_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Run holocron repo sync\n # The `holocron` action resolves @theholocron/cli from node_modules (or\n # builds the workspace when it's an unbuilt checkout — the holocron repo\n # itself). `inputs.steps` is a space-separated list or empty; the\n # `--steps <list>` arg is omitted when empty.\n uses: theholocron/.github/.github/actions/holocron@main\n with:\n command: repo sync\n args: ${{ inputs.steps != '' && format('--steps {0}', inputs.steps) || '' }}\n\n - name: Format generated files\n run: |\n pnpm exec prettier --write README.md docs/src/content/docs/index.mdx 2>/dev/null || true\n # `holocron repo sync` writes package.json fields (keywords, description,\n # homepage) with a plain assignment, which appends new keys at the\n # end — re-apply the canonical order the lint config enforces.\n pnpm exec eslint --fix --no-warn-ignored package.json 2>/dev/null || true\n\n - uses: theholocron/.github/.github/actions/auto-commit@main\n id: auto-commit\n name: Commit sync changes\n with:\n token: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n # One branch per source commit (full SHA, so distinct commits never collide): a PR left unreviewed never blocks the\n # next sync, and a re-run for the same commit updates its own branch.\n branch: ${{ format('chore/auto-sync-{0}', github.sha) }}\n commit-message: \"chore: sync from holocron.config\"\n commit-options: \"--no-verify\"\n\n - name: Open PR and enable auto-merge\n if: steps.auto-commit.outputs.changes-detected == 'true'\n run: |\n # Find or create this run's PR first: older PRs are only closed once\n # a replacement exists, so a failure here never leaves the changes\n # without an open PR.\n url=$(gh pr view \"$BRANCH\" --json url --jq .url 2>/dev/null || gh pr create \\\n --title \"chore: sync README and repo metadata\" \\\n --body \"Automated sync from holocron.config. Auto-merges once required checks pass.\" \\\n --base main \\\n --head \"$BRANCH\")\n created=$(gh pr view \"$url\" --json createdAt --jq .createdAt)\n\n # Supersede older sync PRs: same-repo only, exact generated or legacy\n # branch name, and created before this one (a slow older run can\n # never close a newer PR). --limit covers more than the default page.\n gh pr list --state open --limit 200 --json number,headRefName,isCrossRepository,createdAt \\\n --jq \".[] | select(.isCrossRepository | not) | select(.headRefName == \\\"chore/auto-sync\\\" or (.headRefName | test(\\\"^chore/auto-sync-[0-9a-f]{40}$\\\"))) | select(.headRefName != \\\"$BRANCH\\\") | select(.createdAt < \\\"$created\\\") | .number\" |\n while read -r n; do\n gh pr close \"$n\" --delete-branch --comment \"Superseded by $url.\" || true\n done\n\n # Delete pushed sync branches that never got a PR (e.g. a run\n # cancelled between push and PR creation). The repo-wide concurrency\n # group means no other run is mid-flight here.\n open=$(gh pr list --state open --limit 200 --json headRefName --jq '.[].headRefName')\n git ls-remote --heads origin 'refs/heads/chore/auto-sync-*' | sed 's|.*refs/heads/||' |\n while read -r b; do\n [ \"$b\" = \"$BRANCH\" ] && continue\n echo \"$b\" | grep -Eq '^chore/auto-sync-[0-9a-f]{40}$' || continue\n echo \"$open\" | grep -qxF \"$b\" || git push origin --delete \"$b\" || true\n done\n\n # A PR opened with the default GITHUB_TOKEN does not trigger\n # pull_request workflows, so required checks would never start and\n # auto-merge would wait forever.\n if [ \"$HAS_PAT\" != \"true\" ]; then\n echo \"::warning::Sync PR $url opened with GITHUB_TOKEN: checks will not run and auto-merge is skipped. Set HOLOCRON_SYNC_TOKEN.\"\n echo \"Sync PR $url — needs manual merge (no PAT).\" >> \"$GITHUB_STEP_SUMMARY\"\n elif gh pr merge \"$url\" --auto --squash; then\n echo \"Sync PR $url — auto-merge enabled.\" | tee -a \"$GITHUB_STEP_SUMMARY\"\n else\n echo \"::warning::Sync PR $url opened but auto-merge could not be enabled; merge it manually.\"\n echo \"Sync PR $url — needs manual merge.\" >> \"$GITHUB_STEP_SUMMARY\"\n fi\n env:\n BRANCH: ${{ format('chore/auto-sync-{0}', github.sha) }}\n HAS_PAT: ${{ secrets.HOLOCRON_SYNC_TOKEN != '' || secrets.GH_TOKEN != '' }}\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Broadcast wiki sync if navbar changed\n if: >-\n steps.auto-commit.outputs.changes-detected == 'true' &&\n github.event_name == 'push' &&\n (inputs.steps == '' || contains(inputs.steps, 'wiki'))\n run: |\n if git diff HEAD~1 --name-only | grep -q 'fern/docs.yml'; then\n gh workflow run sync-dispatch.yml \\\n --repo theholocron/.github \\\n --field \"steps=wiki\" \\\n || echo \"skipping broadcast — insufficient permissions\"\n fi\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN || github.token }}\n";
|
|
168
168
|
//#endregion
|
|
169
169
|
//#region src/templates/reusable/platform.repoValidation.yml
|
|
170
170
|
var platform_repoValidation_default$1 = "name: Repo Validation\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n TURBO_TOKEN:\n required: false\n\njobs:\n adrs:\n name: Validate ADRs and specs\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Validate ADR and spec frontmatter\n with:\n task: platform.repoValidation\n job: adrs\n\n registry:\n name: Validate registry consistency\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Validate registry consistency\n with:\n task: platform.repoValidation\n job: registry\n\n docs-presence:\n name: Validate docs presence\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - uses: theholocron/.github/.github/actions/holocron@main\n name: Validate docs presence for new packages\n with:\n task: platform.repoValidation\n job: docsPresence\n env:\n BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}\n\n conclusion:\n name: Conclusion\n runs-on: ubuntu-latest\n if: always()\n needs: [adrs, registry, docs-presence]\n steps:\n - name: Check job statuses\n run: |\n if [[ \"$RESULTS\" == *\"failure\"* ]] || [[ \"$RESULTS\" == *\"cancelled\"* ]]; then\n exit 1\n fi\n env:\n RESULTS: ${{ join(needs.*.result, ',') }}\n";
|
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.174",
|
|
4
4
|
"description": "The Holocron task runner — one task manifest drives `holocron run`, `holocron run ci`, the CI workflows, package.json scripts, linters, and required checks.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@theholocron/fs-utils": "^1.16.0",
|
|
49
49
|
"@theholocron/json-utils": "^1.16.0",
|
|
50
50
|
"@theholocron/pkg-utils": "^1.16.0",
|
|
51
|
-
"@theholocron/datapad": "5.0.0-alpha.
|
|
51
|
+
"@theholocron/datapad": "5.0.0-alpha.174"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@commitlint/config-conventional": "^21.2.3",
|