Skip to content

ci: gated release pipeline (npm Trusted Publishing + GitHub Release) - #5

Merged
alexanderolvera merged 3 commits into
mainfrom
feat/release-pipeline
Aug 9, 2026
Merged

ci: gated release pipeline (npm Trusted Publishing + GitHub Release)#5
alexanderolvera merged 3 commits into
mainfrom
feat/release-pipeline

Conversation

@alexanderolvera

Copy link
Copy Markdown
Owner

What

Adds .github/workflows/release.yml — a tag-triggered release pipeline that publishes to npm and cuts a matching GitHub Release, with an Azure-DevOps-style manual approval gate before publish.

How you cut a release

npm version <patch|minor|major>   # bumps package.json, commits, tags vX.Y.Z
git push --follow-tags

The v* tag push triggers the workflow.

Flow

Job 1 — verify (no special perms):

  • Tag must equal package.json version (fails otherwise).
  • Refuse to republish a version already on npm.
  • npm ci → build → typecheck → lint → test.
  • npm pack → upload the tarball as an artifact (built once, promoted).

Job 2 — publish (needs: verify, environment: npm-production):

  • Pauses at the approval gate until a required reviewer clicks approve.
  • Publishes the same tarball via npm Trusted Publishing (OIDC, id-token: write) — no stored NPM_TOKEN — with a provenance attestation.
  • Creates the GitHub Release, notes pulled from the matching CHANGELOG.md section (regex-free extraction; autogenerated notes as fallback).

One-time setup (required before the first tag)

  1. Approval gate — Settings → Environments → new environment npm-production → enable Required reviewers, add yourself.
  2. npm Trusted Publishing — npmjs.com → the package → Settings → Trusted Publishing → add publisher: repo alexanderolvera/dfhack-remote-node, workflow release.yml, environment npm-production.

Provenance requires the repo to stay public (it is).

GitHub vs Azure DevOps note

GitHub has no separate "Releases pipeline" entity — this is a normal workflow. The ADO release-approval gate maps to a protected Environment with required reviewers; artifact promotion maps to upload-artifactdownload-artifact across jobs.

🤖 Generated with Claude Code

Tag-triggered release workflow. Pushing a vX.Y.Z tag runs a verify job
(tag/version match, refuse-republish guard, build, typecheck, lint, test,
pack) then a publish job gated behind a protected `npm-production`
Environment. Publish uses npm Trusted Publishing (OIDC, provenance) with no
stored token, promotes the tarball built in verify, and creates a matching
GitHub Release with notes extracted from CHANGELOG.md.

Changelog extraction is literal-substring (regex-free) so it behaves the
same across awk flavors and won't pull in the trailing link-reference block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings July 17, 2026 02:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a tag-triggered GitHub Actions release workflow for this npm package, building and verifying once, then using a protected Environment as a manual approval gate before publishing via npm Trusted Publishing (OIDC + provenance) and creating a matching GitHub Release.

Changes:

  • Introduces a verify job that validates tag/version alignment, checks npm for an existing version, runs CI steps, and uploads a single built tarball artifact.
  • Introduces a gated publish job (environment: npm-production) that downloads the promoted tarball, publishes to npm with provenance, and creates a GitHub Release with notes extracted from CHANGELOG.md (or autogenerated as fallback).

Comment on lines +41 to +43
verify:
runs-on: ubuntu-latest
steps:
Comment on lines +90 to +92
permissions:
contents: write # create the GitHub Release
id-token: write # OIDC for npm Trusted Publishing + provenance
Comment thread .github/workflows/release.yml Outdated
Comment on lines +111 to +114
set -euo pipefail
tgz=$(ls release-artifact/*.tgz)
echo "Publishing $tgz"
npm publish "$tgz" --provenance --access public
Comment thread .github/workflows/release.yml Outdated
flag && substr($0, 1, 1) == "[" { exit }
flag { print }
' CHANGELOG.md)
tgz=$(ls release-artifact/*.tgz)
Comment thread .github/workflows/release.yml Outdated
Comment on lines +101 to +103
- name: Use an npm new enough for OIDC trusted publishing
run: npm install -g npm@latest

Copilot AI review requested due to automatic review settings July 17, 2026 03:03
@alexanderolvera
alexanderolvera removed the request for review from Copilot July 17, 2026 03:03
Builds on 8596269, which resolved the review feedback. Three follow-ups:

- Drop the `actions:` scopes. Within one run, upload/download-artifact
  authenticate with the Actions runtime token rather than GITHUB_TOKEN; the
  `actions` permission is only required to reach artifacts across runs or
  repos. `actions: write` in particular also grants run cancellation and
  artifact deletion, which the verify job should not hold.

- Treat 11.5.1 as an npm floor instead of installing a flat 11.5.2. Node 24
  already bundles a newer npm, so the pin was a downgrade on every release;
  now the install runs only when the bundled version is genuinely older.

- Resolve the tarball once into $GITHUB_ENV. The publish step and the release
  step were each running their own copy of the same guard, which is exactly
  the kind of pair that drifts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 9, 2026 21:20
@alexanderolvera

Copy link
Copy Markdown
Owner Author

Follow-up on the review feedback. 8596269 had already addressed all five comments; 26058da refines three of them.

The two actions: permission comments were false positives, so I've reverted those. Within a single workflow run, upload-artifact/download-artifact v4 authenticate with the Actions runtime token, not GITHUB_TOKEN — the actions scope is only required to reach artifacts across runs or repositories, which this pipeline never does. Per the download-artifact docs, a token with actions: read is needed "when downloading artifacts from a different repository or from a different workflow run." Granting actions: write to verify was the worse of the two: it also confers run cancellation and artifact deletion on a job that only builds and packs. If a release ever does fail at the upload step, it fails loudly and this is a one-line fix — worth the tighter default.

The npm pin was a downgrade. npm install -g npm@11.5.2 ran unconditionally, but Node 24 already bundles a newer 11.x, so every release was stepping backwards to reach a version that OIDC trusted publishing merely requires as a floor. It's now a floor check that installs only when the bundled npm is genuinely older — still pinned, so nothing depends on npm@latest drift.

The tarball guard was duplicated. Both the publish step and the release step ran their own copy of the same nullglob-and-count check. Resolved once into $GITHUB_ENV up front instead, so the two can't drift apart. Verified the count logic against 0, 1, and 2 tarballs, and the version comparison across 11.3.0 / 11.5.0 / 11.5.1 / 11.5.2 / 11.6.1 / 12.0.0 / 10.9.2.

@alexanderolvera
alexanderolvera merged commit d0c06d4 into main Aug 9, 2026
2 checks passed
@alexanderolvera
alexanderolvera deleted the feat/release-pipeline branch August 9, 2026 21:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.github/workflows/release.yml:149

  • npm publish will fail if this job is re-run after a successful publish (e.g., if the GitHub Release step failed and you re-run only publish). Adding a quick "already on npm" guard here makes the job idempotent and allows the GitHub Release creation to proceed on reruns.
      - name: Publish to npm (Trusted Publishing / provenance)
        run: |
          set -euo pipefail
          echo "Publishing $TGZ"
          npm publish "$TGZ" --provenance --access public

.github/workflows/release.yml:73

  • The npm package name is hardcoded as dfhack-remote-node here. If package.json#name ever changes (e.g., scope move), this republish check and its error message will silently drift from what npm publish actually targets.

This issue also appears on line 145 of the same file.

          pkg=$(node -p "require('./package.json').version")
          if npm view "dfhack-remote-node@$pkg" version >/dev/null 2>&1; then
            echo "::error::dfhack-remote-node@$pkg is already on npm — nothing to publish."
            exit 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants