Skip to content

Release Process

github-actions[bot] edited this page Oct 13, 2026 · 1 revision

Release Process

简体中文

This is the maintainer-facing release procedure. End users install the published package per Getting-Started in the wiki — they don't need to read this.

Where the package lives

Registry Name Auth Audience
npmjs.com (canonical) hexo-blog-encrypt OIDC trusted publishing All end users. This is the install path advertised in the README.
GitHub Packages (mirror) @d0n9x1n/hexo-blog-encrypt GITHUB_TOKEN Discoverability only — populates the repo's "Packages" sidebar. Identical tarball, scoped name.

Both publish flows run with provenance (npm publish --provenance) so the attestation proves the tarball was built from this repo at the exact tagged commit.

Pre-flight checklist (before tagging)

Prepare these changes on a feature branch, then merge after the PR checks pass. There is no CHANGELOG file to update: the GitHub Release generated on tag push is the release history. Make sure the PRs in the release link their issues with Closes #N so they appear under "Resolved issues" (see Release notes).

  1. demo/package.json — use a published semver range such as ^x.y.z, never file:... Raise the minimum when the demo exercises the new fix. The release workflow rejects file: references; it does not require an exact demo-version match.
  2. package.json version — should already match the tag from your bump commit; the workflow re-verifies.

Run npm test, npm pack --dry-run --json > pack.json, and node build/verify-pack.js pack.json before pushing. Keep the generated pack.json and the built bundle out of commits (package-lock.json is committed; CI installs with npm ci). Merge the release changes only after the Tests workflow passes; create the tag from that exact commit.

Heads-up: deploy-demo can fail before publication. The Pages deploy runs npm install against the demo site, which now wants hexo-blog-encrypt@^X.Y.Z from npmjs — and that version doesn't exist yet. This is expected. Retrigger deploy-demo once the release workflow has published (see step 3 under Post-publish).

Tag + push

git tag -a vX.Y.Z -m "hexo-blog-encrypt vX.Y.Z

<one-paragraph release summary>

Migration notes / links."
git push origin vX.Y.Z

This single tag push triggers two workflows in parallel:

Workflow What it publishes
.github/workflows/release.yml hexo-blog-encrypt@X.Y.Z to npmjs.com via OIDC + provenance
.github/workflows/publish-gh-packages.yml @d0n9x1n/hexo-blog-encrypt@X.Y.Z to npm.pkg.github.com via GITHUB_TOKEN + provenance

Both run the same gates before publishing: version matches the tag, npm test (lint + server tests with coverage + e2e), and build/verify-pack.js. A version that fails canonical validation never reaches the mirror. The mirror keeps its own tag trigger rather than workflow_run on release.yml, because under workflow_run the provenance would name master's HEAD, not the tagged commit.

Watch them:

gh run list --workflow=release.yml --limit 1
gh run list --workflow=publish-gh-packages.yml --limit 1

The publish-gh-packages.yml job skips publish if the version already exists. The canonical npm publish step does not: if npm succeeded but GitHub Release creation failed, re-run only the failed job, not the successful publish job. Every re-run uses the same tagged commit; newer master changes do not apply.

Post-publish

  1. Verify on npm:

    curl -s "https://registry.npmjs.org/hexo-blog-encrypt/latest" \
      | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['version'])"
  2. Verify the GitHub Release. After npm publication, release.yml automatically creates or updates the GitHub Release in its github-release job, with notes generated by scripts/release-notes.sh (see Release notes below). Do not create a duplicate release manually.

    gh release view vX.Y.Z
  3. Retrigger deploy-demo so the live site pulls the now-published tarball and refreshes:

    gh workflow run deploy-demo.yml --ref master
  4. Verify the GH Packages mirror at https://github.com/D0n9X1n/hexo-blog-encrypt/pkgs/npm/hexo-blog-encrypt shows the new version. Confirm the repo's right-hand "Packages" sidebar lists @d0n9x1n/hexo-blog-encrypt.

Release notes

The github-release job runs bash scripts/release-notes.sh "$GITHUB_REF_NAME" (the same approach as SonicTerm). GitHub Releases are the project's release history; there is no CHANGELOG file. The generated notes contain, in order:

  1. # hexo-blog-encrypt vX.Y.Z, the npm install hexo-blog-encrypt@X.Y.Z line, and links to npmjs.com and the GitHub Packages mirror (@d0n9x1n/hexo-blog-encrypt).
  2. ## Resolved issues — issues proven closed by non-reverted commits or merged PRs in the tag range, collected by scripts/release-issues.py. Issues closed by hand (no closing commit or PR recorded by GitHub) are listed separately under Manually closed issues (unverified release linkage).
  3. ## Changes since <previous tag> — git log --no-merges subjects with short SHAs, plus a compare link.
  4. ## Verification — lint, 100% server coverage, Playwright e2e, and npm provenance.

For an issue to appear under "Resolved issues", link it with a closing keyword: put Closes #N (or Fixes #N / Resolves #N) in the PR description or a commit message, so GitHub records the PR or commit as the issue's closer. Plain mentions such as Refs #N or #N are not counted, and neither is milestone membership. Reverted commits and PRs are excluded.

The generator is fail-closed. It needs complete history (fetch-depth: 0; shallow clones are rejected) and a token with issues: read and pull-requests: read. A GitHub API failure fails the job without publishing partial notes; re-run the failed github-release job, which uses the same tagged commit. For the very first tag, or to override the previous tag, set RELEASE_FIRST=1 or PREVIOUS_TAG=<ref>.

Preview the notes locally (needs gh auth login):

bash scripts/release-notes.sh vX.Y.Z

The release tooling has offline tests that run in the Tests workflow's release-tooling job:

python3 scripts/release-issues_tests.py
bash scripts/test-release-notes.sh

scripts/ is not in package.json files, so none of this ships in the npm tarball.

Backfilling the GH Packages mirror

If a tag already exists but publish-gh-packages.yml didn't run for it (e.g. the workflow was added later), dispatch it manually:

gh workflow run publish-gh-packages.yml --ref master -f tag=vX.Y.Z

The workflow checks out the requested tag, applies the same scope rewrite, and publishes. If the version is already present, it skips.

Auth setup (one-time)

npmjs.com — OIDC trusted publishing

Configure on npmjs once per package:

  1. https://www.npmjs.com/package/hexo-blog-encrypt/access → Trusted Publishers
  2. Add a GitHub Actions trusted publisher with:
    • Organization or user: D0n9X1n
    • Repository: hexo-blog-encrypt
    • Workflow filename: release.yml
    • Environment: (leave blank)

The release workflow then exchanges its short-lived OIDC token for publish authority — no NPM_TOKEN secret required, no rotation. The docs test (tests/docs.test.js) fails closed if secrets.NPM_TOKEN or NODE_AUTH_TOKEN is re-introduced into release.yml.

GitHub Packages

No setup. The workflow uses the auto-provided GITHUB_TOKEN with permissions: packages: write declared inline.

npm CLI version requirement

OIDC trusted publishing and provenance attestations both require npm CLI ≥ 11.5.1. Node 20 ships with npm 10.x, so both publish workflows include an explicit npm install -g npm@^11 step. If that step disappears, npm publish --provenance fails with ENEEDAUTH (npm 10 doesn't know how to do the OIDC dance, so it falls back to checking for a token that isn't there).

Why scoped name on GitHub Packages?

GitHub Packages npm requires the package name to be scoped to the repo owner. hexo-blog-encrypt (unscoped) cannot be published as-is to npm.pkg.github.com. The publish-gh-packages.yml workflow mutates package.json in-CI before npm publish:

{
  "name": "@d0n9x1n/hexo-blog-encrypt",
  "publishConfig": { "registry": "https://npm.pkg.github.com", "access": "public" }
}

The runner is ephemeral so this rewrite never reaches git. End users who want to install from GH Packages instead of npmjs use the scoped name (see Getting Started for the install snippet).

Troubleshooting

Symptom Likely cause Fix
ENEEDAUTH from npm publish in release.yml npm CLI < 11.5.1 (the npm install -g npm@^11 step is missing or failed), OR trusted publisher misconfigured on npmjs.com Verify the upgrade step ran; double-check the trusted-publisher fields match exactly (workflow filename release.yml, no environment).
github-release job fails at "Generate release notes" GitHub API error/rate limit, missing issues: read / pull-requests: read, or a shallow checkout npm is already published — re-run only the failed github-release job; a re-run uses the same tagged commit, not newer master changes.
An issue is missing from "Resolved issues" Its PR or commit did not use a closing keyword (Closes #N), or it was closed by hand Link issues with Closes #N in future PRs; manual closures appear only in the unverified section.
release.yml fails at "Verify demo references published package" demo/package.json still says file:.. Fix the manifest before tagging a new version; a re-run uses the same tagged commit.
publish-gh-packages.yml fails with 403 Forbidden The runner's GITHUB_TOKEN lost packages: write (e.g. branch protection stripped it) Restore permissions: packages: write at the workflow / job scope.
GitHub Packages page returns 404 after publish Eventually consistent; CDN catches up within seconds. Wait, refresh. If still 404 after a few minutes, the publish step probably no-op'd — check the workflow log for the "Skip if … already exists" branch.
Tag points at the wrong commit (you forgot a fix) Squash merges drop late commits; the tag was created before the fix Do not move a published tag or overwrite an npm version. Apply the correction and release a new patch version. Inspect both registries before retrying any failed publication.

Lessons learned

  • Push pre-flight + verify CI green BEFORE creating the tag. The tag triggers a real publish; once npm publish succeeds you cannot republish that version. Rolling back means a new patch version.
  • Squash-merging a PR mid-flight orphans late commits on the source branch. If you push a fix to a feature branch after the squash merge, that fix lives only on the dead branch — it never reaches master. Always push fixes BEFORE merging, or apply them as a fresh commit on master after merge.
  • Both publish jobs are independent, so a failure in one doesn't abort the other. Don't assume "both green" from a single ✓ — check each workflow's run.

See also

Clone this wiki locally