release-version · git:20261007.971bc2b · 2026-10-07 · sha256 474167999933cbf4

release-version git:20261007.971bc2bA

Immutable. This exact content never changes and is served at /api/v1/blob/474167999933cbf4.

---
name: release-version
description: Release seedcord's packages through changesets pre mode. Cut a prerelease from the long-lived `next` branch, graduate it to a stable release on `main`, then re-sync `next`. Use for any seedcord release on the `next` line.
---

# Releasing seedcord (prerelease, graduate, re-sync)

## Mental model

- `next` is the prerelease line. Its publishes go to the `next` dist-tag as `X.Y.Z-next.N`.
- `main` is stable. Its publishes go to `latest` as a clean `X.Y.Z`.
- seedcord is a monorepo, so one release versions and publishes every package that has a pending changeset at once, under the `@seedcord` scope (plus the unscoped `seedcord`).
- Publishing is CI-only. `.github/workflows/publish.yml` runs after the `checks` workflow finishes green on `main` or `next`, through a `workflow_run` trigger, then hands off to `changesets/action`, which runs `pnpm run release` and publishes. You version locally with `pnpm cs:version`. CI never versions and never opens a release PR, and a push with changesets still pending skips the publish. A `docs-publish` job then syncs docs to R2 and calls `docs-deploy`, which renders every docs page, uploads them to R2, and deploys the docs Worker. On `main`, a `github-release` job also creates one `release-YYYY.MM.DD` tag and one GitHub release for the whole publish. It creates them with the `RELEASE_TOKEN` repository secret, a fine-grained token of the maintainer's with Contents and Workflows read and write, so the release shows that account as its author. A prerelease gets neither.
- The gate `.github/actions/check-eligibility` skips the publish on any branch while a changeset is pending, and on `next` unless `.changeset/pre.json` exists and is in `pre` mode. Exiting pre mode on `next` makes the gate skip the publish, so a clean version can never reach the `next` tag.
- npm's OIDC trusted publishing is bound to `publish.yml` by its filename. Renaming the workflow breaks publishing.
- A published version is permanent on npm. Only the dist-tag can move.

## Before any flow, check what will bump

`pnpm cs:status` writes `.changeset/status.json`. Two seedcord quirks:

- It reads only git-tracked changeset files, so `git add .changeset` first or a new `.md` is invisible to it.
- On the `next` line, run `pnpm cs:status --since next` so the baseline is the prerelease tip. The config `baseBranch` is `main`, so the default compares against stable.

## Pushing to `main` and `next`

The `Require PR for main or next` ruleset covers both branches. It requires a pull request, linear history and signed commits, and it lets admins bypass all three. Every direct `git push` below, and every `git merge` that makes a merge commit, needs that bypass.

## Flow 1, cut a prerelease from `next`

The publish ends on the `next` dist-tag as `X.Y.Z-next.N`. The changesets must already be on `next`.

```sh
git switch next && git pull
pnpm cs:version                 # bump every pending package to X.Y.Z-next.N and tidy the changelogs
pnpm install                         # rewrite internal ranges into the lockfile
git add .changeset                   # pre mode moves each used changeset into .changeset/pre/, which -a would skip
git commit -am "chore(release): version packages"
git push origin next                 # no changesets pending, so publish.yml publishes directly
```

Then confirm the tag moved:

```sh
npm dist-tag ls seedcord             # and each other bumped package; check next moved and latest did not
```

## Flow 2, graduate `next` to stable

Run on `main` so the clean version reaches `latest`. Exiting pre mode and versioning on `next` would make the gate skip the publish, so graduate here.

```sh
git switch main && git pull
git merge next                       # resolve any changelog or pre.json conflicts
pnpm changeset pre exit              # set pre.json to exit mode, then review the changesets in .changeset/pre/
pnpm cs:version                 # write the clean X.Y.Z and delete the superseded prerelease sections
pnpm install                         # update the lockfile
git add .changeset
git commit -am "chore(release): version packages"
git push origin main
```

Then **wait for the `main` publish workflow to finish green**, because CI does the actual publish.

```sh
npm dist-tag ls seedcord             # latest now points at the clean X.Y.Z
```

## Flow 3, re-sync `next` after a graduation

Bring the clean version back and re-enter pre mode for the next cycle.

```sh
git switch next && git pull
git merge main                       # brings the clean versions and changelogs
pnpm changeset pre enter next        # re-enter pre mode
git add .changeset/pre.json          # pre enter writes a new file, -a would skip it
git commit -m "chore: re-enter pre mode"
git push origin next
```

This push publishes nothing (the versions are already on `latest`), and the gate lets it through because `next` is back in pre mode.

## Adding a new package to the release line

A new published package needs one thing before its first release:

- Its first publish cannot be done locally with provenance, because provenance needs the CI OIDC environment. If you must publish it by hand once, use `pnpm publish --filter @seedcord/newpkg --no-provenance`. Prefer letting CI do it.

Confirm the package is public (the changesets config sets `access: "public"`) before the first publish.

## If a publish lands on the wrong tag

Move the tag. You cannot delete or replace the version itself.

```sh
npm dist-tag add seedcord@X.Y.Z latest
npm dist-tag ls seedcord
```

## Don't

- Don't run `changeset pre exit` or `cs:version` on `next` to make a stable release. Graduate on `main` (Flow 2). The gate refuses a non-pre publish on `next`.
- Don't run `pnpm changeset version` directly. Node cannot load the TypeScript changelog module without `--import tsx`, and the changelogs skip the tidy pass.
- Don't run `npm publish` or `pnpm publish` by hand, except for a new package's first release. CI publishes through changesets.
- Don't push `main` while it is in pre mode. Exit pre mode first.
- Don't let a new changeset land between versioning and the publish. The gate skips the publish while one is pending, and the next versioning run bumps past the skipped version.
- Don't rename `.github/workflows/publish.yml`. Its filename is bound to the npm OIDC trust.

## Related

- Changesets prerelease docs, <https://github.com/changesets/changesets/blob/main/docs/prereleases.md>
- The publish workflow, `.github/workflows/publish.yml`, and its gate, `.github/actions/check-eligibility/action.yml`.