Skip to content

fix(ci): choose a release's dist-tag when it publishes, and move next after it - #3794

Open
armando-navarro wants to merge 2 commits into
angular:mainfrom
armando-navarro:a64-release-dist-tags
Open

armando-navarro wants to merge 2 commits into
angular:mainfrom
armando-navarro:a64-release-dist-tags

Conversation

@armando-navarro

Copy link
Copy Markdown
Collaborator

Fixes #3790
Fixes #3791

The publish job now picks a release's dist-tag when it publishes, moves next up after a stable release, and waits until npm lists each publish before the next one starts.

Changes

The first commit moves the publish job's new steps into tools/publish-job.js and the version rules into tools/release-tag.js, each with a spec.

  • A release's dist-tag chosen in a new step for GitHub Releases:
    • A stable version goes to latest only if it ranks above npm's latest and every other stable git tag. Git tags count because a higher release can still be in npm's publish-time scan.
    • Otherwise it goes to v<major>-lts, the name Angular uses for its own older majors.
    • The job fails instead if the version does not rank above the current v<major>-lts, or above another stable release of its own major.
    • A prerelease goes to next, as before.
  • Waiting until npm lists each publish.
    • After npm publish, the job polls npm's dist-tags every 15 seconds for up to 20 minutes.
    • Canary and release publishes now share one npm-publish concurrency group, so the next publish starts only after the wait.
    • If the wait times out after a release on latest, the job fails and prints the command that moves next.
  • Moving next.
    • After a release npm lists as latest, the job runs npm dist-tag add @angular/fire@<version> next through the publishing proxy when next is lower.
    • A failure prints the same command.
  • Re-runs. A re-run of a release npm already lists skips npm publish and continues with the wait and the next move.
  • tools/build.sh only sets the version now. The publish step passes the tag to npm publish, so publish.sh is gone.
  • The job has a 30-minute timeout.

The second commit moves the existing canary check from #3784 into tools/publish-job.js, with the same decisions and messages, and runs it only for pushes and scheduled runs.

Behavior to know

  • Release branches run their own copy of the workflow. 20.1.x needs this change before its next release, or that release still publishes to latest. A separate PR will bring it there. 20.0.x gets no further releases.
  • The next move has not run for this repository before. The first release after this merges is its first real run. If it fails, the job prints the command to run by hand.
  • A tagged prerelease still moves next, even below its current value.
  • The concurrency group is renamed from canary-publish to npm-publish, so a canary still queued under the old name when this merges is not ordered with the first run under the new one.

Verification

  • npm run test:node: 381 specs, 0 failures. The new specs cover:
    • the tag rules, including a comparison against semver over 50 version pairs
    • each step against a fake npm and git
    • the real git and $GITHUB_OUTPUT calls, against a local repository
  • The concurrency queue and the publishing proxy can't be run locally. This PR's CI shows whether GitHub accepts the workflow file, and the publish job itself only runs on main and on releases.

… after it

A stable tag published to latest unconditionally, so a 20.x release
tagged after 21.0.0 would move latest back to 20.x. A stable release now
goes to latest only when it ranks above npm's latest and every other
stable git tag, since a higher release can still be in npm's publish-time
malware scan. Otherwise it goes to v<major>-lts, and the job fails if it
does not rank above that tag or another release of its major.

Every publish now waits until npm lists it, in one npm-publish queue, so
the next publish reads the real dist-tags. That stops a queued canary from
reading a stale canary tag. After a release reaches latest, next moves up
to it, and a failure there prints the npm dist-tag command that finishes
it. A re-run of a published release skips npm publish and goes on to the
wait and the next move.

The steps live in tools/publish-job.js and tools/release-tag.js so specs
cover them, and build.sh no longer picks a tag.
The check that skips a canary of an earlier commit keeps the same
decisions and messages, and its specs now cover each branch. It runs
only for pushes and scheduled runs, and a failed read of npm's dist-tags
now says why.
@armando-navarro armando-navarro added backport: 20.1.x Cherry-pick onto the 20.1.x branch for a 20.1 patch release comp: build/pipeline Build, bundling, packaging, release pipeline. type: bug Defect: expected behavior doesn't happen. labels Oct 8, 2026

@tyler-reitz tyler-reitz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. Two notes on the description, nothing on the code.

The specs hold up under mutation. Five targeted breaks each go red and the baseline stays green: dropping the prerelease tiebreak in isAbove (4 failures), renaming the lts tag (8), dropping the STABLE guard in movesNext (1), never setting listed in the wait step (1), and skipping the already-published shortcut (1). I also ran releaseTag over ten realistic scenarios, including both refusal paths and v-prefixed tags, and all of them land where the rules say.

The npm dist-tag add step authenticates, since setup-node points registry-url at the wombat host and the step carries the token. The canary-check refactor keeps all four messages identical, with ::error:: now applied centrally by runStep instead of inline, and swapping the *-canary.* guard for github.event_name != 'release' is equivalent, because every non-release run of this job is on main, where build.sh always builds a canary.

Note 1: the re-runs bullet is narrower than it reads. publishedUnder('21.0.0-rc.1', {latest: '21.0.0', next: '21.0.0'}) is undefined, so re-running an old prerelease's job after next has moved on takes the publish path and fails on the existing version rather than skipping.

Note 2: releasesAbove does not guard a missing distTags.latest, unlike blocking and movesNext. releaseTag('21.0.0', {}, ['21.0.0']) throws undefined is not a semver version. Unreachable for a published package, and throwing is right, so this is only about the message.

@armando-navarro armando-navarro added this to the 21.0.0-rc.2 milestone Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport: 20.1.x Cherry-pick onto the 20.1.x branch for a 20.1 patch release comp: build/pipeline Build, bundling, packaging, release pipeline. type: bug Defect: expected behavior doesn't happen.

Projects

None yet

2 participants