Skip to content

ci: gate publish on unreleased shipping changes (#7) - #7

Merged
nabheet merged 1 commit into
mainfrom
ci/release-guard-7
Oct 31, 2026
Merged

nabheet merged 1 commit into
mainfrom
ci/release-guard-7

Conversation

@nabheet

@nabheet nabheet commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Adds the release guard the standard calls for (§14) to the two npm-server repos that never adopted it. Closes nabheet/galactic-config#7.

Why

Every merge to main published. In this wave: 0.1.7 (dev-only audit overrides) and 0.1.8 (.github/workflows/ci.yml only). Neither changed anything a consumer installs, yet each pushed a tag and a GitHub Release.

What

The publish job gains Determine whether there are unreleased shipping changes (structure from pcbway-mcp-server, adapted to this toolchain). It writes changed=true|false; build, publish, tag and both Release steps are gated on it.

Rule:

  • non-shipping: package-lock.json, .github/**, and package.json whose only differences are version or inert devDependencies.
  • Inert devDependencies: markdownlint-cli2 (rtk) / @biomejs/biome + markdownlint-cli2 (failover) — lint only, never in the published package.
  • overrides ships when the package has a runtime dependency (dependencies/optionalDependencies/peerDependencies/bundledDependencies non-empty), because it changes what consumers install; otherwise it can only touch the dev tree and is ignored. Same rule as pcbway/google-services.
  • version is derived (newest stable tag vs registry, then patch), so a lone bump releases nothing; land it with the change it releases. A release that runs with the tree version ahead of both baselines still publishes it as-is.
  • Everything else ships (a future bundler/compiler devDependency, index.js, README, other published files).

Other hardening in the same job:

  • Baseline is git tag --merged <sha> filtered to plain vX.Y.Z. git describe --match 'v*' would accept v1.1.0-rc.1/v2/vdocs and hide commits after the last real release.
  • No stable tag = first release → publishes (skipping would never create the baseline tag).
  • test job concurrency no longer cancels main runs; cancellation kills publish, which with a guard could then skip a release.
  • workflow_dispatch and v*-beta* tag pushes always publish.

Verification (local, real history + synthetic commits)

case expected actual
.github/** only skip changed=false
package-lock.json only skip changed=false
version bump only skip changed=false
inert devDependency bump skip changed=false
new non-inert devDependency publish changed=true
index.js / README change publish changed=true
0.1.7 audit merge (lock + inert devDep + overrides, no runtime deps) skip changed=false
overrides with a runtime dependency present publish changed=true
stray v2.0.0-rc.1 tag present baseline stays v0.1.8 yes
no stable tag publish (first release) changed=true
tag push / workflow_dispatch publish changed=true
path containing a newline one path, one decision (-z) yes

actionlint clean; bash -n clean on the extracted step; yq confirms the gate on every publish step and the new test concurrency group.

The previous PR revision already merged the guard; merging this one is a .github-only change, so changed=false on the merge run remains the live check.

Without a guard every merge to main publishes: a devDependency bump, a
package-lock refresh, an audit `overrides` addition and a workflow-only edit
each cut a patch. This wave did exactly that — 0.1.7 (audit fixes) and 0.1.8
(`.github` only).

Port the release guard already used by pcbway-mcp-server and
google-services-mcp, adapted to this repo:

- `Determine whether there are unreleased shipping changes` diffs the newest
  stable `v*` tag against the pushed SHA and classifies every path.
- `package-lock.json` and `.github/**` never ship. `package.json` is
  non-shipping only when the sole differences are inert devDependencies (the
  explicit lint-only list); a `version` bump ships (it means "publish this
  version"), and `overrides` ships unless the package has no runtime
  dependency for it to affect.
- Build, publish, tag and both Release steps are gated on
  `changed == 'true'`.
- Baseline is `git tag --merged <sha>` filtered to plain `vX.Y.Z`: `git
  describe` would accept `v1.1.0-rc.1`/`v2`/`vdocs` and hide commits after the
  last real release. No stable tag yet = first release, which publishes
  (skipping would never create the baseline tag).
- The `test` job's concurrency group no longer cancels `main` runs, which
  could kill a shipping run's `publish` job.
- `workflow_dispatch` and `v*-beta*` tag pushes always publish.

Verified locally against this repo's real history and synthetic commits:
lock-only, `.github`-only and inert-devDep bumps => false; version bump,
non-inert devDep, `overrides` with a runtime dep, `index.js` and README =>
true; the 0.1.7 audit merge => false; a stray `v2.0.0-rc.1` tag is ignored;
no-tag => true; a path containing a newline stays one entry (-z).
@nabheet
nabheet force-pushed the ci/release-guard-7 branch from ce11797 to 829f91a Compare October 31, 2026 19:55
@nabheet
nabheet marked this pull request as ready for review October 31, 2026 19:59
@nabheet
nabheet merged commit d8095bd into main Oct 31, 2026
6 checks passed
nabheet added a commit that referenced this pull request Oct 31, 2026
Without a guard every merge to main publishes: a devDependency bump, a
package-lock refresh, an audit `overrides` addition and a workflow-only edit
each cut a patch. This wave did exactly that — 0.1.7 (audit fixes) and 0.1.8
(`.github` only).

Port the release guard already used by pcbway-mcp-server and
google-services-mcp, adapted to this repo:

- `Determine whether there are unreleased shipping changes` diffs the newest
  stable `v*` tag against the pushed SHA and classifies every path.
- `package-lock.json` and `.github/**` never ship. `package.json` is
  non-shipping when its only diffs are `version` or an inert devDependency (the
  explicit lint-only list). `overrides` is non-shipping only when the package
  has no runtime dependency (`dependencies`/`optionalDependencies`/
  `peerDependencies`/`bundledDependencies`) for it to affect — the same rule
  pcbway and google-services use.
- Build, publish, tag and both Release steps are gated on
  `changed == 'true'`.
- Baseline is `git tag --merged <sha>` filtered to plain `vX.Y.Z`: `git
  describe --match 'v*'` would accept `v1.1.0-rc.1`/`v2`/`vdocs` and hide
  commits after the last real release. No stable tag yet = first release,
  which publishes (skipping would never create the baseline tag).
- The `test` job's concurrency group no longer cancels `main` runs, which
  could kill a shipping run's `publish` job.
- `workflow_dispatch` and `v*-beta*` tag pushes always publish.

Verified locally against this repo's real history and synthetic commits:
lock-only, `.github`-only, version-only and inert-devDep bumps => false;
non-inert devDep, `overrides` with a runtime dep, `index.js` and README =>
true; the 0.1.7 audit merge => false; a stray `v2.0.0-rc.1` tag is ignored;
no-tag => true; a path containing a newline stays one entry (-z).
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.

1 participant