Skip to content

feat(ci): switch npm publish from continuous to tag-triggered#522

Merged
Niks988 merged 7 commits into
mainfrom
feat/npm-tag-triggered-publish
May 15, 2026
Merged

feat(ci): switch npm publish from continuous to tag-triggered#522
Niks988 merged 7 commits into
mainfrom
feat/npm-tag-triggered-publish

Conversation

@Niks988
Copy link
Copy Markdown
Collaborator

@Niks988 Niks988 commented May 15, 2026

Summary

  • Stop publishing on every merge to main — only publish when a v* tag is pushed
  • Use the version from package.json as-is — no more dev suffix; the version bump PR is the version that hits npm
  • Derive npm dist-tag automatically: -rc.*rc, -beta.*beta, -alpha.*alpha, stable → latest

Release flow after this merges

  1. Development continues on main — PRs merge freely, CI runs tests, nothing gets published to npm
  2. When ready to release, push a version tag matching the package.json version:
    git tag v<version> && git push origin v<version>
  3. The workflow fires → reviewer approves in the npm-publish Environment → packages hit npm with the correct dist-tag

Any tag starting with v triggers the workflow — v10.0.0-rc.7, v10.0.0, v11.0.0-beta.1, etc.

What changed

Before After
Every merge to main → auto-publish --tag dev with synthetic version Only an explicit v* tag push triggers publish
Hardcoded --tag dev Dynamic dist-tag derived from version string
File: npm-continuous-publish.yml File: npm-publish.yml

Security

All supply-chain hardening controls preserved:

  • Three-job structure (verify-environment → build-and-pack → publish)
  • npm-publish Environment with required reviewers + wait timer
  • SHA-256 manifest verification between build and publish jobs
  • --provenance attestation on every publish
  • No source checkout in the credential-bearing publish job

Admin note

The npm-publish Environment's deployment branch policy may need a v* tag pattern added alongside main, or switch to "Protected branches only."

Replace the continuous publish-on-every-merge workflow with a
tag-triggered publish. Only `v*` tags (e.g. `v10.0.0-rc.7`,
`v10.0.0`) fire the workflow now — merges to main no longer
produce npm releases.

Key changes:
- Trigger: `on.push.branches: [main]` → `on.push.tags: ['v*']`
- Remove dev version suffix bump — publish the version from
  package.json as-is (set by the release PR)
- Derive npm dist-tag from the git tag: `-rc.*` → rc,
  `-beta.*` → beta, `-alpha.*` → alpha, else → latest
- Remove `head_commit` guards (not available on tag push events)
- Rename workflow file: npm-continuous-publish → npm-publish
- Update verify-environment branch policy check to accept an
  optional `v*` tag pattern alongside `main`
- Artifact name now uses the git tag (e.g. npm-tarballs-v10.0.0-rc.7)

The three-job security structure (verify-environment → build-and-pack
→ publish) and all supply-chain hardening controls are preserved.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread .github/workflows/npm-publish.yml Outdated
Comment thread .github/workflows/npm-publish.yml Outdated
Comment thread .github/workflows/npm-publish.yml Outdated
Comment thread .github/workflows/npm-publish.yml
1. Tighten tag trigger from `v*` to `v[0-9]+.[0-9]+.[0-9]+*` so
   tags like `vnext` or `v-test` are ignored — only semver tags fire

2. Add tag-branch preflight: verify the tagged commit exists on
   `main` via the GitHub API before building, so a tag pushed from
   a feature branch is rejected

3. Fix deployment policy validation: check every entry in the custom
   allow-list is exactly `main` or `v*` — previously `['main', 'staging']`
   would pass because the count check allowed up to 2

4. Add version-match preflight: every public package.json version must
   equal the tag (minus the `v` prefix) before packing, so tagging
   `v10.0.0-rc.8` on a commit with `10.0.0-rc.7` in package.json is
   caught before anything hits npm

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread .github/workflows/npm-publish.yml Outdated
Comment thread .github/workflows/npm-publish.yml Outdated
Comment thread .github/workflows/npm-publish.yml Outdated
1. Fix tag glob: `[0-9]+` is regex, not glob — GitHub Actions uses
   glob syntax. Changed to `v[0-9]*.[0-9]*.[0-9]*`

2. Fix branch-containment check: `branches-where-head` only returns
   branches whose HEAD == commit, so it breaks when main advances.
   Switched to the compare API which checks reachability instead

3. Fail closed on unknown prerelease identifiers: `-dev.1` or
   `-preview.1` now error out instead of silently falling through
   to `--tag latest`. Only rc/beta/alpha and stable are allowed

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread .github/workflows/npm-publish.yml
Comment thread .github/workflows/npm-publish.yml Outdated
Comment thread .github/workflows/npm-publish.yml Outdated
- Move github.ref_name and github.sha into env vars to prevent
  template injection via crafted tag names (zizmor High)
- Remove unused BRANCH_COUNT, HAS_TAG_PATTERN, HAS_BAD_WILDCARD,
  MISMATCHES variables (shellcheck SC2034)

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread .github/workflows/npm-publish.yml Outdated
Comment thread .github/workflows/npm-publish.yml Outdated
Address third round of review findings:

1. Add signed annotated tag check (same logic as release.yml):
   reject lightweight tags and unsigned annotated tags before
   building or publishing anything

2. Align RELEASE_BRANCHES with release.yml: check both `main` and
   `v10-rc` instead of hardcoding just `main`, preventing drift
   between the two workflows

3. Both fixes mirror the structural controls already in release.yml
   so the two tag-triggered workflows have identical security gates

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread .github/workflows/npm-publish.yml Outdated
The deployment policy validation was hardcoding `main` as the only
allowed branch, while the ancestry check accepts both `main` and
`v10-rc`. Now both checks derive from the same allowed set, and the
jq filter validates every entry against the full list of release
branches + the `v*` tag pattern.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Codex review skipped: filtered diff is 6557 lines (cap: 5,000). Please consider splitting this into smaller PRs for reviewability.

Tag-triggered publishes run infrequently (~weekly). A clean install
adds ~30s but eliminates the zizmor cache-poisoning surface, giving
us a fully green security scan with zero accepted-risk footnotes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Codex review skipped: filtered diff is 6572 lines (cap: 5,000). Please consider splitting this into smaller PRs for reviewability.

@Niks988 Niks988 merged commit 8a10b6b into main May 15, 2026
37 of 38 checks passed
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.

2 participants