diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ee26137 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,121 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + +permissions: + contents: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + name: Validate and publish Glaze + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Validate annotated tag and main provenance + shell: bash + run: | + set -euo pipefail + if [[ "$(git cat-file -t "$GITHUB_REF_NAME")" != "tag" ]]; then + echo "release tag must be annotated" >&2 + exit 1 + fi + git fetch origin main + if ! git merge-base --is-ancestor HEAD origin/main; then + echo "release commit must be part of origin/main" >&2 + exit 1 + fi + + - name: Install Racket + uses: Bogdanp/setup-racket@v1.11 + with: + version: '8.12' + + - name: Validate release metadata + shell: bash + run: | + set -euo pipefail + if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "release tag must be vMAJOR.MINOR.PATCH" >&2 + exit 1 + fi + tag_version="${GITHUB_REF_NAME#v}" + package_version="$(sed -n 's/^(define version "\([^"]*\)")/\1/p' info.rkt)" + release_version="$(sed -n 's/^(define release-version "\([^"]*\)")/\1/p' info.rkt)" + if ! racket -e '(require version/utils) (exit (if (valid-version? (vector-ref (current-command-line-arguments) 0)) 0 1))' "$package_version"; then + echo "info.rkt version $package_version is not a valid Racket package version" >&2 + exit 1 + fi + if [[ "$tag_version" != "$release_version" ]]; then + echo "tag version $tag_version does not match release-version $release_version" >&2 + exit 1 + fi + if ! grep -Eq "^## \\[$release_version\\]( |$)" CHANGELOG.md; then + echo "CHANGELOG.md has no $release_version section" >&2 + exit 1 + fi + echo "GLAZE_VERSION=$release_version" >> "$GITHUB_ENV" + + - name: Install and test Glaze + shell: bash + run: | + set -euo pipefail + raco pkg install --auto --no-docs --name glaze --link "$PWD" + raco test --package glaze + raco scribble ++xref-in setup/xref load-collections-xref \ + --htmls --dest "$RUNNER_TEMP/glaze-doc" \ + glaze-doc/scribblings/glaze.scrbl + + - name: Build source package and release notes + shell: bash + run: | + set -euo pipefail + mkdir -p dist + raco pkg create --from-dir --source --format zip --dest dist "$PWD" + archive="$(find dist -maxdepth 1 -type f -name '*.zip' -print -quit)" + destination="dist/glaze-${GLAZE_VERSION}.zip" + mv "$archive" "$destination" + sha256sum "$destination" > "${destination}.sha256" + awk -v version="$GLAZE_VERSION" ' + $0 ~ ("^## \\[" version "\\]") { capture = 1; next } + capture && /^## / { exit } + capture { print } + ' CHANGELOG.md > dist/release-notes.md + test -s dist/release-notes.md + + - name: Smoke-test exact source package + shell: bash + run: | + set -euo pipefail + smoke_root="$(mktemp -d)" + PLTUSERHOME="$smoke_root/racket-user" \ + raco pkg install --auto --no-docs --name glaze \ + "$PWD/dist/glaze-${GLAZE_VERSION}.zip" + PLTUSERHOME="$smoke_root/racket-user" \ + racket -e '(require glaze glaze/server glaze/events)' + PLTUSERHOME="$smoke_root/racket-user" raco glaze help >/dev/null + + - name: Publish GitHub Release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + gh release create "$GITHUB_REF_NAME" \ + "dist/glaze-${GLAZE_VERSION}.zip" \ + "dist/glaze-${GLAZE_VERSION}.zip.sha256" \ + --verify-tag \ + --title "Glaze ${GLAZE_VERSION}" \ + --notes-file dist/release-notes.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e8a5d..748b2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to Glaze will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). -## [Unreleased] +## [0.7.0] - 2026-09-29 ### Fixed - Build the Scribble reference manual in CI and group modules that share a diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..1757c1f --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,15 @@ +# Releasing Glaze + +Glaze uses two equivalent version forms: `info.rkt` carries the canonical +Racket package version (for example `0.7`), while `release-version`, Git tags, +the changelog, and GitHub Releases use three-component SemVer (`0.7.0`). + +1. Move the pending changelog entries into a dated release section. +2. Update both version bindings and the README release badges. +3. Merge the fully green release-preparation pull request into `main`. +4. Create and push an annotated `vMAJOR.MINOR.PATCH` tag on that exact commit. + +The tag workflow verifies that the commit belongs to `main`, runs the complete +package test suite, builds the Scribble manual, creates the Racket source ZIP, +installs that exact ZIP into a fresh package scope, and publishes the archive, +checksum, and changelog notes as a GitHub Release. diff --git a/info.rkt b/info.rkt index 02b6abc..c752600 100644 --- a/info.rkt +++ b/info.rkt @@ -25,6 +25,7 @@ ;; NOTE: Racket's `valid-version?` rejects a trailing ".0" component ;; ("0.7.0" is invalid; "0.7" is the same release). (define version "0.7") +(define release-version "0.7.0") ;; `raco test --package` otherwise executes every backend implementation as a ;; standalone test module. The dispatcher and platform CI exercise these