feat(ci): support release candidates via changesets pre mode (#3628)

## Summary

Enables shipping `X.Y.Z-rc.N` prereleases of `@trigger.dev/*` via
changesets pre mode. RCs publish under the `rc` npm dist-tag, never
claim `latest`, and don't trigger marketing-site changelog PRs. The
plumbing is hyphen-in-version detection in `release.yml` — no separate
workflow, no opt-in flag at publish time.

Validated end-to-end against a sandbox repo (real npm publishes, Docker
builds, Helm chart pushes, GitHub releases) before porting back. Full RC
lifecycle tested: pre enter → rc.0 → iterate to rc.1 → pre exit →
stable. Plus interaction with the existing release-branch hotfix flow.

## What changes

### `release.yml`
- New `is_prerelease` output (hyphen-in-version)
- GitHub release adds `--prerelease` flag for RC publishes (Pre-release
badge, not Latest)
- `dispatch-changelog` job gated on `is_prerelease != 'true'` — no
marketing-site PR per RC

### Docker workflows
- Removes the `:v4-beta` floating tag entirely from `publish-webapp.yml`
and `publish-worker-v4.yml`. v4 is GA; the tag is a misnomer and is
already inconsistent with the npm side (npm `v4-beta` dist-tag was
frozen at 4.0.4 months ago while Docker `:v4-beta` kept bumping).
Self-hosters should pin to a versioned tag going forward — the last
value of `:v4-beta` stays frozen wherever it currently points.

### CLI version-check fix
(`packages/cli-v3/src/utilities/initialBanner.ts`)
Switches the "new version available" comparison from JavaScript
`localeCompare` to `semver.lt`. The old comparison handled `X.Y.Z-rc.N`
vs `X.Y.Z` incorrectly — a user on `4.5.0-rc.0` would never be prompted
to upgrade once `4.5.0` stable shipped (lex order put the prerelease
ahead of the bare version). Real semver gets this right.

Stable users were never affected: the check queries the `@latest`
dist-tag, which by convention never points at a prerelease.

## How an RC actually publishes after this

1. `pnpm exec changeset pre enter rc` on main, push the `pre.json`
2. Bot regenerates the release PR as `chore: release v<X.Y.Z>-rc.0`
3. Merge → `release.yml` runs `changeset publish` which reads
`pre.json.tag` and publishes under `--tag rc`. GitHub release marked
Pre-release. No marketing-site dispatch.
4. Iterate by adding changesets normally; bot bumps to `rc.1`, `rc.2`, …
5. When ready: `pnpm exec changeset pre exit`, push, merge regenerated
PR → stable ships under `latest` and the marketing-site dispatch fires.
This commit is contained in:
Eric Allam
2026-05-15 16:43:26 +01:00
committed by GitHub
parent 4c42f6cc0b
commit dfa3ede209
4 changed files with 21 additions and 21 deletions
@@ -1,5 +1,6 @@
import chalk from "chalk";
import { getLatestVersion } from "fast-npm-meta";
import * as semver from "semver";
import { VERSION } from "../version.js";
import { chalkGrey, chalkRun, chalkTask, chalkWorker, logo } from "./cliOutput.js";
import { logger } from "./logger.js";
@@ -105,18 +106,16 @@ async function doUpdateCheck(): Promise<string | undefined> {
return;
}
const compareVersions = (a: string, b: string) =>
a.localeCompare(b, "en-US", { numeric: true });
const comparison = compareVersions(VERSION, meta.version);
if (comparison === -1) {
// Use real semver comparison (loose) so prereleases sort correctly against
// their stable counterpart — e.g. a user on `4.5.0-rc.0` sees `4.5.0` as
// newer. String/locale comparison gets this wrong for `X.Y.Z-rc.N` vs `X.Y.Z`.
if (semver.lt(VERSION, meta.version, true)) {
return meta.version;
}
return;
} catch (err) {
// ignore error
// ignore error (covers both network failures and any version-parse oddities)
logger.debug(err);
return;