From 895d46d627962f40cd35b63d9965d6962d775864 Mon Sep 17 00:00:00 2001 From: Eriks Reks Date: Fri, 5 Jun 2026 13:15:48 -0400 Subject: [PATCH] Add release tag enforcement --- .github/workflows/release.yml | 127 ++++++++++++++++++++++++++++++++++ RELEASE.md | 50 +++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 RELEASE.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0ae1d8a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,127 @@ +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Tag to validate, for example v0.2.0-alpha.0' + required: true + +permissions: + contents: write + id-token: write + +jobs: + release: + name: Validate, publish, and release + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org + + - name: Verify tag matches package version + id: version + shell: bash + run: | + set -euo pipefail + + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + tag="${{ inputs.tag }}" + else + tag="${GITHUB_REF_NAME}" + fi + + if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "Release tags must look like v0.1.4 or v0.2.0-alpha.0; got $tag" >&2 + exit 1 + fi + + package_version="$(node -p "require('./package.json').version")" + tag_version="${tag#v}" + + if [ "$package_version" != "$tag_version" ]; then + echo "Tag $tag does not match package.json version $package_version" >&2 + exit 1 + fi + + npm_tag="latest" + prerelease="false" + if [[ "$package_version" == *-* ]]; then + prerelease="true" + npm_tag="${package_version#*-}" + npm_tag="${npm_tag%%.*}" + fi + + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "version=$package_version" >> "$GITHUB_OUTPUT" + echo "npm_tag=$npm_tag" >> "$GITHUB_OUTPUT" + echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT" + + - name: Install dependencies + run: npm ci + + - name: Test + run: npm test + + - name: Build + run: npm run build + + - name: Publish to npm + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + set -euo pipefail + + package="radius-cli@${{ steps.version.outputs.version }}" + if npm view "$package" version >/dev/null 2>&1; then + echo "$package is already published; skipping npm publish." + exit 0 + fi + + npm publish --provenance --access public --tag ${{ steps.version.outputs.npm_tag }} + + - name: Ensure npm dist-tag + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + set -euo pipefail + + npm dist-tag add \ + "radius-cli@${{ steps.version.outputs.version }}" \ + "${{ steps.version.outputs.npm_tag }}" + + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + args=( + "${{ steps.version.outputs.tag }}" + --title "radius-cli ${{ steps.version.outputs.tag }}" + --generate-notes + ) + + if [ "${{ steps.version.outputs.prerelease }}" = "true" ]; then + args+=(--prerelease) + else + args+=(--latest) + fi + + if gh release view "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then + echo "GitHub release ${{ steps.version.outputs.tag }} already exists; skipping release creation." + else + gh release create "${args[@]}" + fi diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..92a7020 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,50 @@ +# Release Process + +The release source of truth is the version in `package.json`. Every published +npm version should have a matching git tag and GitHub release. + +`v0.1.4` is the baseline release for the already-published npm package. Future +releases should be created by bumping the package version and pushing the +matching `v*` tag. + +## Stable Release + +```bash +npm version 0.1.5 +git push origin main --tags +``` + +Pushing the `v0.1.5` tag starts the release workflow. The workflow verifies that +the tag matches `package.json`, runs tests and build, publishes `radius-cli` to +npm with the `latest` dist-tag, ensures the npm dist-tag points at that version, +and creates the GitHub release. + +## Alpha Release + +```bash +npm version 0.2.0-alpha.0 +git push origin main --tags +``` + +Pushing the `v0.2.0-alpha.0` tag starts the same workflow. Because the package +version is a prerelease, npm gets the `alpha` dist-tag and the GitHub release is +marked as a prerelease. The workflow also ensures `radius-cli@alpha` points at +that version. + +Consumers can install the alpha explicitly: + +```bash +npm install -g radius-cli@alpha +npx radius-cli@alpha --help +``` + +## Required Repository Setup + +The workflow needs npm publish permission. Configure one of: + +- npm trusted publishing for this repository and workflow, or +- an `NPM_TOKEN` repository secret with publish access to the `radius-cli` + package. + +Do not publish to npm manually without also creating the matching git tag, +GitHub release, and npm dist-tag.