Skip to content

CORE-4004: Tag each release in publish.bash - #182

Merged
RoyEJohnson merged 14 commits into
mainfrom
release-tag-in-publish-script
Oct 27, 2026
Merged

RoyEJohnson merged 14 commits into
mainfrom
release-tag-in-publish-script

Conversation

@RoyEJohnson

@RoyEJohnson RoyEJohnson commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

publish.bash ran only npm ci, a build and npm publish, so a release had no git tag unless someone made one by hand. Consumers that install from git, such as rex-web with openstax/ui-components#1.24.2, need the tag. 1.26.1 to 1.26.3 are in the registry without tags, and I tagged 1.26.4 by hand.

The script used to tag before the registry switch in #124. This restores that, in a non-interactive form that moves no branch.

The approach

Publishing can't be undone, so the script creates the tag before it publishes. Everything that can go wrong with the tag (the git identity, tag signing, a clash, the package not matching it) then fails before npm publish, using the real tag and not a stand-in for it. An earlier version of this PR tried to predict those failures with a --check mode that made a throwaway tag, and each review round found another way the stand-in differed from the real run. Creating the real tag first removes that whole class, and the throwaway tag with it. The only step left after the publish is pushing the tag, which is one command to repeat.

The tag is an annotated tag named for the version (no v prefix, like 1.25.0) on a "Dist files" commit: the release commit plus the built dist/. The commit is built in a temporary index, so no branch moves and the working tree is untouched, and it is reachable only from the tag. This is the same shape as the existing 1.25.0 and 1.26.4 tags.

What publish.bash does

The release commit is HEAD when the script starts, recorded once. Then, in order:

  1. Stop if the version in package.json isn't MAJOR.MINOR.PATCH or MAJOR.MINOR.PATCH-prerelease (no build metadata). A pre-release is published with --tag next, so it never takes the latest dist-tag.
  2. Stop if the working tree has changes (untracked files included), if the tag exists locally (an earlier release that didn't push it) or on origin, or if origin can't be reached.
  3. Stop if you aren't logged in to the registry (npm whoami).
  4. npm ci and npm run build:clean, then stop if the build changed any tracked file. build:clean regenerates src/theme/theme.css, and a stale committed copy would be published but missing from the tag.
  5. Create the tag on the recorded commit. This is where a missing author or committer identity, or an unusable signing key when tag.gpgSign is on, fails.
  6. scripts/verify-release.mjs <tag>: every file npm pack --dry-run would publish must be in the tag's tree with the same contents. Files are hashed as they are on disk, with no line-ending conversion, because npm packs the bytes on disk and not what git would store. This catches an untracked or ignored file under src/, a checkout with CRLF line endings from core.autocrlf (git treats it as clean, but the package would contain CRLF while the tag holds LF), and anything else that would make the package and the tag differ.
  7. A dry-run push of the tag, which contacts origin the way the real push does, so missing credentials show up now.
  8. Stop if HEAD has moved since the script started.
  9. npm publish to the same registry the login was checked against (--registry), with --tag next for a pre-release, then push the tag.

If the script stops before the publish is attempted, it deletes the tag it created, and only that tag: a tag it didn't create makes it stop at step 1 and is left alone. If npm publish fails, or the push fails after it, the tag is kept and the message says what to run: git push origin refs/tags/<version>, or git tag -d <version> if the package was not published.

The README's "Git Tags" section said tags were used "instead of npm publishing". It now describes both ways to install, the tag naming, the release steps and these checks.

Tested

I ran the script in a scratch clone with a local bare repository as origin and npm stubbed. Each of these stops before npm publish, leaves no tag behind and makes no registry call:

  • A version that isn't MAJOR.MINOR.PATCH[-prerelease] (01.2.3, 1.26, 1.2.3+b, 1.2.3-), before any tag or build. A pre-release such as 1.2.3-pre1 passes validation. The --tag next argument itself is checked only in isolation, not through a full stubbed publish.
  • Not logged in (before npm ci).
  • An unusable signing key with tag.gpgSign on, and no git identity.
  • Cannot push to origin although fetching works.
  • An ignored file under src/ that the package would include.
  • A file with CRLF line endings under core.autocrlf=true, where git reports a clean tree. Hashing it with git's filters would have matched the tag's LF copy, and the unfiltered check rejects it.
  • The build changes a tracked file.
  • HEAD moves between the build and the publish.
  • A tag that already exists locally is reported and left alone, and a tag already on origin is reported.

And these keep the tag, with the message above:

  • The push fails after the publish; running git push origin refs/tags/<version> afterwards pushes it.
  • npm publish fails.

A full run publishes (stub) and pushes an annotated tag whose commit has the release commit as its parent and differs from it only by the dist/ files. If HEAD moves during the publish itself, the tag is still on the release commit. The branch doesn't move and the working tree stays clean.

I also ran the real build in this repo, made a real tag with the same steps, and the verifier passed: the 382 dist/ files and everything in src/ match, so a normal release won't be blocked by it. I have not run any of this against the real registry.

One trade-off: because the tag needs the built dist/, an identity or signing problem now shows up after the npm ci and build rather than before them. It is still before the publish, which is the part that can't be undone.

This PR has no Jira ticket in its title. Add one if the check needs it.

🤖 Generated with Claude Code

This comment was marked as resolved.

@RoyEJohnson RoyEJohnson changed the title Tag each release in publish.bash CORE-4004: Tag each release in publish.bash Oct 23, 2026
@RoyEJohnson
RoyEJohnson requested a lite review from Copilot October 23, 2026 20:28

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

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.

🔵 Needs a closer look

The release automation is high-impact, and reviewer feedback also identifies an unresolved inconsistency in the README installation guidance.

No open issues 🎉

2 resolved since last review

Review effort: Lite

RoyEJohnson and others added 14 commits October 26, 2026 18:02
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nd-in

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@RoyEJohnson
RoyEJohnson force-pushed the release-tag-in-publish-script branch from 899032c to 6f9603d Compare October 26, 2026 23:02
@RoyEJohnson
RoyEJohnson merged commit 1dafab2 into main Oct 27, 2026
3 checks passed
@RoyEJohnson
RoyEJohnson deleted the release-tag-in-publish-script branch October 27, 2026 00:14

This branch was successfully deployed

1 active deployment
refs/heads/release-tag-in-publish-script — 6f9603d5 Deployed Oct 26, 2026 by github-actions[bot]
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