Repository navigation
Guard npm latest, detect skipped releases, check the CHANGELOG heading (#33) - #56
Merged
Merged
Conversation
On 2026-09-29 the 0.29.5 and 0.30.0 publish runs were cancelled: GitHub keeps one pending run per concurrency group, so a later merge's publish replaced them while they waited for approval. - publish.yml passes an explicit dist-tag to npm publish (scripts/release-plan.mjs, SemVer 2.0.0 comparison incl. prereleases, no dependency): latest, or backport-<major>.<minor> for a version lower than the current latest, or next for a prerelease, so latest never moves backwards. Such a GitHub Release is not marked Latest. - The release job summary warns about CHANGELOG versions between the last published version and this one that are missing on npm. - ci.yml checks the top CHANGELOG heading (scripts/check-changelog-heading.mjs): exactly "## <package.json version>" into main; "## <x.y.z> (unreleased)" is also accepted into release/**. - CONTRIBUTING.md, CLAUDE.md and the publish.yml header document the concurrency behaviour, the recovery and "merge release PRs one at a time". - test/release-scripts.spec.ts covers the scripts, release-state.sh end to end with a fake npm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…op publishConfig.tag (#33) - release-state.sh fails, writing no outputs, unless the dist-tag from release-plan.mjs is latest, next or backport-<n>.<n>. - The fake-npm end-to-end tests record and assert the exact npm arguments, so dropping `versions dist-tags` or `--prefer-online` fails a test. - package.json publishConfig no longer sets tag: "latest". The guard relied on --tag overriding it, and a manual or older-npm publish would still force latest. npm publish --dry-run still defaults to latest and honours --tag backport-0.29. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
planadecu
force-pushed
the
feat/1.0-publish-guards
branch
from
September 29, 2026 21:21
12d33a2 to
ed2552a
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #33. The issue closes when
release/1.0reachesmain, since GitHub only auto-closes on merges into the default branch.Why
On 2026-09-29 the 0.29.5 and 0.30.0 publish runs were cancelled. The
npm-publishconcurrency group never cancels a publish that is already running. But GitHub keeps only one pending run per group, so each later merge's publish job replaced the older one while it waited for approval. This PR implements the "Decision (2026-09-29)" comment: options 3 (guardlatest), 2 (detect) and 1 (document), plus the CHANGELOG heading check. The concurrency behaviour itself stays as it is. There is no version bump, per the release-branch rules.What changed
Guard
latest(scripts/release-plan.mjs, called byscripts/release-state.sh)release-state.shnow also runsnpm view glassnode-api versions dist-tags --json, with the same failure policy as before: an E404 means the package was never published, and any other failure fails the job instead of publishing. It outputsdist_tagandlatest.publish.ymlalways passes the tag explicitly:npm publish --tag "$DIST_TAG".latest: for a higher or equal version, or when there is nolatestyet.backport-<major>.<minor>: for a version lower than the currentlatest, e.g. re-running the cancelled 0.29.5 after 0.30.1 →backport-0.29. The name identifies the release line. It is also not a valid semver range, which npm requires of tag names:v0.29would parse as>=0.29.0 <0.30.0-0. I checked this withsemver.validRange.next: for a prerelease that is not lower. Otherwise npm would movelatestonto a prerelease.npm publish, so the tag reflectslatestas it is at that moment, even after a long wait for approval.--latest=falsewhen the dist-tag is notlatest. It gets--prereleasefor a prerelease version.gitHead→tag-onlyre-run path,skip, tagging and the GitHub Release.Detect skipped versions. The job summary shows a warning and the log gets a
::warning::annotation. Together they list every## x.y.zCHANGELOG version between the last published version and the current one that is missing on npm. "Last published" is the highest npm version below the current one, so a re-run after the current version is out still reports the gap.CHANGELOG heading check in
ci.yml(scripts/check-changelog-heading.mjs, the first step of thetestjob, before install)github.base_ref, orgithub.ref_name(main) whenpublish.ymlcallsci.yml.main: the top##heading must be exactly## <package.json version>.release/**:## <x.y.z> (unreleased)is also accepted. It is not compared with package.json, sorelease/1.0(## 1.0.0 (unreleased), package.json0.30.1) passes.Docs
publish.ymlheader, plus a new CONTRIBUTING.md "Releases (for maintainers)" section, explain:backport-x.y.npm dist-tag ls glassnode-apiand restorelatestby hand if needed.## 1.0.0 (unreleased)→### Tooling. The published package does not change.How it was verified
test/release-scripts.spec.ts(38 tests, part of the Vitest suite, so CI runs it):compareSemver: the semver.org precedence list, checked pairwise, plus build metadata and invalid input.distTagFor: older, older patch, older major, newer, equal, missinglatest, prerelease above / below / with nolatest.skippedVersions(the 2026-09-29 scenario) andparseNpmView(E404, other errors, unparseable output, a single version returned as a string, nolatesttag).release-state.shend to end, with a fakenpmonPATH: new above latest →latest; lower →backport-0.30plus the skipped warning; tag-only; skip; package not on npm →latest; prerelease →next; npmETIMEDOUTon either query → non-zero exit with no outputs.distTagForalways returnlatestfails 5 tests.compareSemveragainst thesemverpackage: 529 version pairs, 0 mismatches. This was a one-off check;semveris not added as a dependency.actionlintandshellcheck scripts/release-state.share clean.pnpm run lint,tsc -p tsconfig.test.json,prettier --check .andpnpm run test:coveragepass (689 passed, 1 skipped). The pre-commit hook (tests, lint-staged, build) also passed.release-state.shagainst the real registry works: it reportsaction=publish,dist_tag=latestand the skipped-version warning.Impact
latestcan no longer move backwards through this workflow, and skipped versions show up in the release summary.mainnow fail on a CHANGELOG heading mismatch. That includes the finalrelease/1.0→mainPR until it bumps package.json to1.0.0and renames the heading to## 1.0.0.