Skip to content

Commit d46a332

Browse files
authored
ci: add a beta release channel (#150)
* ci: add a beta release channel Prerelease versions publish under the `beta` npm dist-tag, stable ones under `latest`. The publish workflow derives the tag from the version string alone, so a prerelease cannot take over `latest` no matter which ref triggered the run. Yarn's `version` has no `--preid`, so a pre* strategy off a stable version lands a bare counter (0.3.1 -> 0.3.2-0). The bump step relabels that once to `-beta.0`; yarn carries the identifier forward on every later prerelease. * docs: add a beta graduation cycle example
1 parent 9226f19 commit d46a332

4 files changed

Lines changed: 141 additions & 14 deletions

File tree

.github/workflows/checks.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
push:
66
branches:
77
- main
8+
- beta
89

910
jobs:
1011
checks:

.github/workflows/release-publish.yml

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ name: Publish Release
33
on:
44
pull_request:
55
types: [closed]
6-
branches: [main]
6+
branches: [main, beta]
77
# Escape hatch: re-run a publish that failed mid-flow (e.g. after a fix to
8-
# this workflow). Reads the version from the package.json currently on main.
8+
# this workflow). Reads the version from the package.json on the chosen ref.
99
workflow_dispatch:
1010
inputs:
1111
package:
@@ -57,6 +57,7 @@ jobs:
5757
EVENT: ${{ github.event_name }}
5858
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
5959
INPUT_PACKAGE: ${{ inputs.package }}
60+
BASE_REF: ${{ github.event.pull_request.base.ref }}
6061
run: |
6162
if [[ "$EVENT" == "workflow_dispatch" ]]; then
6263
PKG="$INPUT_PACKAGE"
@@ -89,13 +90,42 @@ jobs:
8990
esac
9091
fi
9192
VERSION=$(node -p "require('./packages/$DIR/package.json').version")
92-
echo "dir=$DIR" >> $GITHUB_OUTPUT
93-
echo "name=$PKG" >> $GITHUB_OUTPUT
94-
echo "version=$VERSION" >> $GITHUB_OUTPUT
93+
94+
# The dist-tag comes from the version alone, so a prerelease can never
95+
# take over `latest` regardless of which ref dispatched the run.
96+
PRERELEASE="${VERSION#*-}"
97+
if [[ "$PRERELEASE" == "$VERSION" ]]; then
98+
DIST_TAG=latest
99+
elif [[ "$PRERELEASE" == beta.* ]]; then
100+
DIST_TAG=beta
101+
else
102+
echo "::error::unsupported prerelease identifier in $VERSION; expected beta.N"
103+
exit 1
104+
fi
105+
106+
# On the merge path the base branch is known, so hold the two channels
107+
# apart: beta ships prereleases, main ships stable.
108+
if [[ "$EVENT" == "pull_request" ]]; then
109+
case "$BASE_REF:$DIST_TAG" in
110+
beta:beta|main:latest) ;;
111+
*)
112+
echo "::error::$VERSION (dist-tag $DIST_TAG) cannot be released from '$BASE_REF'"
113+
exit 1
114+
;;
115+
esac
116+
fi
117+
118+
{
119+
echo "dir=$DIR"
120+
echo "name=$PKG"
121+
echo "version=$VERSION"
122+
echo "dist_tag=$DIST_TAG"
123+
} >> $GITHUB_OUTPUT
95124
{
96125
echo "### Publishing"
97126
echo "- Package: $PKG"
98127
echo "- Version: $VERSION"
128+
echo "- npm dist-tag: $DIST_TAG"
99129
echo "- Trigger: $EVENT"
100130
} >> $GITHUB_STEP_SUMMARY
101131
@@ -131,4 +161,4 @@ jobs:
131161
run: |
132162
yarn config set npmAuthToken "$NPM_TOKEN"
133163
cd packages/${{ steps.pkg.outputs.dir }}
134-
yarn npm publish --access public --provenance
164+
yarn npm publish --access public --provenance --tag ${{ steps.pkg.outputs.dist_tag }}

.github/workflows/release.yml

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ on:
1212
- compact-cli
1313
- compact-simulator
1414
version_bump:
15-
description: "Version bump type"
15+
description: "Version bump type (pre* strategies are beta-only)"
1616
required: true
1717
type: choice
1818
options:
1919
- patch
2020
- minor
2121
- major
22+
- prerelease
23+
- prepatch
24+
- preminor
25+
- premajor
2226

2327
jobs:
2428
release:
@@ -31,6 +35,33 @@ jobs:
3135
pull-requests: write # open the PR + enable auto-merge
3236

3337
steps:
38+
# Prerelease versions carry the `beta` npm dist-tag and stable versions
39+
# carry `latest`. Pinning each strategy to its branch keeps a beta out of
40+
# main's history and a stable out of beta's.
41+
- name: Validate bump strategy for this branch
42+
env:
43+
BRANCH: ${{ github.ref_name }}
44+
BUMP: ${{ inputs.version_bump }}
45+
run: |
46+
case "$BUMP" in
47+
prerelease|prepatch|preminor|premajor)
48+
if [[ "$BRANCH" != "beta" ]]; then
49+
echo "::error::$BUMP is beta-only, but this run is on '$BRANCH'"
50+
exit 1
51+
fi
52+
;;
53+
patch|minor|major)
54+
if [[ "$BRANCH" != "main" ]]; then
55+
echo "::error::$BUMP is main-only, but this run is on '$BRANCH'"
56+
exit 1
57+
fi
58+
;;
59+
*)
60+
echo "::error::unknown bump strategy: $BUMP"
61+
exit 1
62+
;;
63+
esac
64+
3465
- name: Get github app token
3566
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
3667
id: gh-app-token
@@ -74,13 +105,29 @@ jobs:
74105
cd packages/${{ steps.pkg.outputs.dir }}
75106
yarn version ${{ inputs.version_bump }}
76107
NEW_VERSION=$(node -p "require('./package.json').version")
77-
echo "new=$NEW_VERSION" >> $GITHUB_OUTPUT
78-
echo "branch=release/${{ inputs.package }}-v$NEW_VERSION" >> $GITHUB_OUTPUT
108+
# Yarn has no --preid, so a pre* strategy off a stable version yields a
109+
# bare counter (0.3.1 -> 0.3.2-0). Relabel it once; from there yarn
110+
# carries the identifier forward (0.3.2-beta.0 -> 0.3.2-beta.1).
111+
if [[ "$NEW_VERSION" =~ -[0-9]+$ ]]; then
112+
yarn version "${NEW_VERSION%-*}-beta.0"
113+
NEW_VERSION=$(node -p "require('./package.json').version")
114+
fi
115+
if [[ "$NEW_VERSION" == *-beta.* ]]; then
116+
DIST_TAG=beta
117+
else
118+
DIST_TAG=latest
119+
fi
120+
{
121+
echo "new=$NEW_VERSION"
122+
echo "branch=release/${{ inputs.package }}-v$NEW_VERSION"
123+
echo "dist_tag=$DIST_TAG"
124+
} >> $GITHUB_OUTPUT
79125
{
80126
echo "### Release Summary"
81127
echo "- Package: ${{ inputs.package }}"
82128
echo "- New version: $NEW_VERSION"
83129
echo "- Bump type: ${{ inputs.version_bump }}"
130+
echo "- npm dist-tag: $DIST_TAG"
84131
} >> $GITHUB_STEP_SUMMARY
85132
86133
- name: Verify package contents
@@ -131,6 +178,8 @@ jobs:
131178
cat > /tmp/pr-body.md <<EOF
132179
Automated release PR for **${{ inputs.package }}** v${{ steps.version.outputs.new }} (${{ inputs.version_bump }} bump).
133180
181+
Publishes to npm under the \`${{ steps.version.outputs.dist_tag }}\` dist-tag.
182+
134183
This PR was opened by the release workflow. Once required checks pass (semgrep, CodeQL, code-owner review), it will auto-merge. Merging will trigger the publish workflow, which tags the release and publishes to npm.
135184
EOF
136185
PR_URL=$(gh pr create \

RELEASING.md

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,69 @@
11
# Releasing
22

3+
## Channels
4+
5+
Two release channels, keyed to the branch the workflow runs from:
6+
7+
| Branch | Versions | npm dist-tag | Install with |
8+
| ------ | --------------- | ------------ | ------------------------- |
9+
| `main` | `0.3.2` | `latest` | `yarn add <pkg>` |
10+
| `beta` | `0.3.2-beta.0` | `beta` | `yarn add <pkg>@beta` |
11+
12+
The dist-tag is derived from the version string, so a prerelease can never
13+
take over `latest`. The bump strategies are pinned to their branch: `pre*`
14+
runs only from `beta`, `patch`/`minor`/`major` only from `main`.
15+
316
## Running the workflow
417

518
1. Go to "Release Package" in Actions.
619
2. Click on the "Run workflow" dropdown menu.
7-
3. Choose the package to release and the version bump type.
20+
3. Pick the branch: `main` for a stable release, `beta` for a prerelease.
21+
4. Choose the package to release and the version bump type.
822
Following [SemVer](https://semver.org/):
923
- **Patch** - Backward-compatible bug fixes.
1024
- **Minor** - New functionality in a backward compatible way.
1125
- **Major** - Breaking API changes.
26+
- **Prepatch / preminor / premajor** - Open a new beta cycle at the
27+
corresponding bump (`0.3.1` + preminor -> `0.4.0-beta.0`).
28+
- **Prerelease** - Advance the current beta cycle (`0.4.0-beta.0` ->
29+
`0.4.0-beta.1`). From a stable version it behaves like prepatch.
1230

13-
4. A maintainer must approve the release before it proceeds.
14-
5. Once approved, the CI will automatically:
31+
5. A maintainer must approve the release before it proceeds.
32+
6. Once approved, the CI will automatically:
1533
- Run tests.
1634
- Bump the version.
35+
- Open a release PR against the branch you ran from, and auto-merge it.
1736
- Create a git tag.
18-
- Publish the package to npm.
19-
6. Once published, go to "Releases" and create a GitHub release using the generated tag.
37+
- Publish the package to npm under the channel's dist-tag.
38+
7. Once published, go to "Releases" and create a GitHub release using the
39+
generated tag. Mark beta tags as pre-releases.
40+
41+
## Graduating a beta to stable
42+
43+
The bump strategies read the version already in `package.json`, so promote in
44+
this order:
45+
46+
1. Merge `beta` into `main`, carrying the `-beta.N` version with it.
47+
2. Run "Release Package" from `main` with `patch`, `minor`, or `major`. Each
48+
drops the prerelease suffix: `0.4.0-beta.3` + patch -> `0.4.0`.
49+
50+
Use `patch` to ship the beta's version as-is. Use `minor` or `major` only when
51+
the final release warrants a higher bump than the beta cycle assumed.
52+
53+
A full cycle, starting from `0.3.1` on `main`:
54+
55+
| Branch | Bump | Version | dist-tag |
56+
| ------ | ------------ | -------------- | -------- |
57+
| `beta` | `preminor` | `0.4.0-beta.0` | `beta` |
58+
| `beta` | `prerelease` | `0.4.0-beta.1` | `beta` |
59+
| `beta` | `prerelease` | `0.4.0-beta.2` | `beta` |
60+
| `main` | `patch` | `0.4.0` | `latest` |
61+
62+
The `pre*` bump names the version the cycle is heading for, so pick it from
63+
what the finished release will be rather than from the size of the first beta:
64+
`prepatch` for a bugfix, `preminor` for new features, `premajor` for a
65+
breaking change. After that, only `prerelease` moves the counter. Running
66+
`preminor` again mid-cycle starts a new one (`0.4.0-beta.2` -> `0.5.0-beta.0`).
2067

2168
## First-release order
2269

0 commit comments

Comments
 (0)