diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2314f08..743c15dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,11 +71,24 @@ jobs: - if: github.event_name == 'push' name: Publish the libraries run: | - if [[ "${{ github.ref }}" == *"-alpha"* ]]; then - pnpm publish -r --access=public --no-git-checks --tag=alpha - else + # Prerelease tags publish under their own identifier (alpha, beta, rc); + # only a plain vX.Y.Z tag is allowed to move the `latest` dist-tag. + VERSION="${GITHUB_REF_NAME#v}" + if [[ "$VERSION" != *-* ]]; then pnpm publish -r --access=public --no-git-checks + exit 0 fi + + PREID="${VERSION#*-}" + PREID="${PREID%%.*}" + # npm rejects a dist-tag that parses as a semver range, so a numeric + # identifier has no channel to publish under, and `latest` is the + # stable channel a prerelease must never take over. + if [[ ! "$PREID" =~ ^[A-Za-z][A-Za-z0-9-]*$ || "${PREID,,}" == "latest" ]]; then + echo "Refusing to publish $GITHUB_REF_NAME: '$PREID' cannot be a prerelease dist-tag" >&2 + exit 1 + fi + pnpm publish -r --access=public --no-git-checks --tag="$PREID" env: NPM_CONFIG_PROVENANCE: true # Surfaces npm's OIDC token exchange, which is otherwise silent. @@ -85,6 +98,10 @@ jobs: name: Create a draft release run: | NEW_VERSION=$(pnpm lerna list --json | jq -r '.[] | select(.name == "@codspeed/core") | .version') - gh release create v$NEW_VERSION --title "v$NEW_VERSION" --generate-notes -d + PRERELEASE="" + if [[ "$NEW_VERSION" == *-* ]]; then + PRERELEASE="--prerelease" + fi + gh release create "v$NEW_VERSION" --title "v$NEW_VERSION" --generate-notes -d $PRERELEASE env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..f1ce6c8d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing + +## Releasing a New Version + +To create a new version, run: + +```bash +./scripts/release.sh patch # Increment PATCH component (e.g., 1.2.3 -> 1.2.4) +./scripts/release.sh minor # Increment MINOR component (e.g., 1.2.3 -> 1.3.0) +./scripts/release.sh major # Increment MAJOR component (e.g., 1.2.3 -> 2.0.0) +``` + +All packages share a single version, bumped in lockstep by `lerna version`. + +### Prereleases + +A new prerelease series is started by passing the version explicitly, and +continued with `prerelease`, which keeps the identifier it already has (the +label between the `-` and the counter): + +```bash +./scripts/release.sh 5.8.0-beta.0 # 5.7.1 -> 5.8.0-beta.0 +./scripts/release.sh prerelease # 5.8.0-beta.0 -> 5.8.0-beta.1 +``` + +The identifier becomes the npm dist-tag, so a prerelease is installed only by +asking for it: + +```bash +pnpm add @codspeed/vitest-plugin@beta +``` + +`latest` keeps pointing at the most recent stable release, and `^5` never +resolves to a prerelease. + +### What happens + +1. **`scripts/release.sh`**: + - Refuses to run outside `main` or with a dirty working tree + - Runs `lerna version`, which bumps every package, commits, creates a signed + `vX.Y.Z` tag and pushes it + +2. **CI release workflow** (`.github/workflows/release.yml`): + - Triggered automatically when the tag is pushed + - Builds the native addon prebuilds for linux-arm and darwin-arm + - Builds the libraries + - Publishes to npm via OIDC trusted publishing, under the dist-tag derived + from the tag's prerelease identifier (`latest` for a plain `vX.Y.Z` tag) + - Creates a draft GitHub release, flagged as a prerelease when the version + has a prerelease identifier diff --git a/scripts/release.sh b/scripts/release.sh index 46b9b932..2a605156 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,5 +1,21 @@ #!/bin/bash -# Usage: ./scripts/release.sh +# +# Usage: ./scripts/release.sh +# +# version major | minor | patch stable release +# prerelease next prerelease of the current version, +# keeping its identifier +# X.Y.Z-.N explicit version, used to start a new +# prerelease series +# +# The identifier of a prerelease version (e.g. "beta" in 5.8.0-beta.0) becomes +# the npm dist-tag the release workflow publishes under, so consumers opt in +# with `pnpm add @codspeed/core@beta` while `latest` keeps pointing at the last +# stable release. +# +# ./scripts/release.sh patch 5.7.1 -> 5.7.2 (dist-tag latest) +# ./scripts/release.sh 5.8.0-beta.0 5.7.1 -> 5.8.0-beta.0 (dist-tag beta) +# ./scripts/release.sh prerelease 5.8.0-beta.0 -> 5.8.0-beta.1 (dist-tag beta) set -ex # Fail if not on main @@ -9,7 +25,7 @@ if [ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then fi if [ $# -ne 1 ]; then - echo "Usage: ./release.sh " + echo "Usage: ./release.sh " exit 1 fi