From 4d08744635bfe22f1f66a904601f4b0c2568468e Mon Sep 17 00:00:00 2001 From: KT Date: Tue, 7 Jul 2026 23:16:31 +0800 Subject: [PATCH] ci: publish a draft github release with assets, add release conventions release.yml only uploaded the wheel to Actions artifacts, so a pushed tag never produced a downloadable Release asset -- install.sh, which resolves the latest Release wheel, silently kept serving the old version. Build a draft Release with the wheel and sdist attached so a release is one review plus a Publish click. - release.yml: on a version tag, create a draft Release with assets, a dated title, notes prefilled from a template; idempotent re-runs; prerelease tags stay out of /releases/latest; tag vs pyproject version guard; concurrency; align action versions with ci/commits. - RELEASING.md + .github/release-notes-template.md: release conventions and the CI notes skeleton. Co-authored-by: Claude (claude-opus-4-8) --- .github/release-notes-template.md | 29 +++++++++++++ .github/workflows/release.yml | 62 +++++++++++++++++++++++++-- RELEASING.md | 71 +++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 .github/release-notes-template.md create mode 100644 RELEASING.md diff --git a/.github/release-notes-template.md b/.github/release-notes-template.md new file mode 100644 index 0000000..a0cb857 --- /dev/null +++ b/.github/release-notes-template.md @@ -0,0 +1,29 @@ + + +## Highlights + + + +## Install + +```bash +curl -fsSL https://raven.evermind.ai/install.sh | bash +``` + +Then reload your shell and run: + +```bash +raven onboard +``` + +## Release Status + +- Version: `__VERSION__` +- Tag: `__TAG__` +- Stability: +- Assets: wheel and source distribution attached to this release + +## Notes + +- Raven is still pre-1.0; CLI surfaces, plugin contracts, and runtime internals may continue to evolve. +- PyPI publishing is not enabled yet; the supported public install path uses the GitHub Release wheel asset. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9664c24..951f830 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,14 +18,20 @@ on: - "v*" workflow_dispatch: {} +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + jobs: build-wheel: runs-on: ubuntu-latest + permissions: + contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 # 1) Node toolchain to build the TUI bundle. - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: "22" @@ -44,7 +50,7 @@ jobs: # an sdist and then builds the wheel from that sdist, which drops this # gitignored bundle before the wheel hook can include it. - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v8.2.0 - name: Build wheel + sdist run: | @@ -73,7 +79,55 @@ jobs: name: dist path: dist/* - # 5) (Optional, wire up when ready) publish to PyPI on tag: + # 5) On a version tag, publish a DRAFT GitHub Release with the built + # wheel + sdist attached. install.sh resolves the wheel from the + # latest Release asset, so a human reviews the draft and clicks + # Publish to make it live. Manual dispatch (no tag) stops after the + # artifact upload above. + - name: Check tag matches pyproject version + if: startsWith(github.ref, 'refs/tags/v') + run: | + tag="${{ github.ref_name }}" + v=$(grep -m1 '^version = ' pyproject.toml | sed -E 's/.*"([^"]+)".*/\1/') + base="${tag#v}"; base="${base%%-*}" # v0.1.3-rc1 -> 0.1.3 + [ "$base" = "$v" ] || { echo "tag $tag does not match pyproject version $v"; exit 1; } + + - name: Create draft GitHub Release with assets + if: startsWith(github.ref, 'refs/tags/v') + env: + GH_TOKEN: ${{ github.token }} + run: | + tag="${{ github.ref_name }}" + repo="${{ github.repository }}" + ver="${tag#v}" + today="$(date -u +%Y-%m-%d)" + # prerelease tags (contain a hyphen, e.g. v0.1.3-rc1) must never become + # /releases/latest, which install.sh serves to curl|sh users. + pre="" + case "$tag" in *-*) pre="--prerelease" ;; esac + # Prefill notes from the template; the summary and Highlights stay TODO + # for a human to fill in the draft before publishing. + sed "s/__VERSION__/$ver/g; s/__TAG__/$tag/g" \ + .github/release-notes-template.md > "$RUNNER_TEMP/notes.md" + # Detect an existing release via the LIST endpoint: the tag endpoint is + # published-only and the Actions token cannot see a draft through it + # (cli/cli #3037), so a re-run would 422 on create. Delete a stale draft + # (this keeps the git tag) and recreate; leave a published release alone. + rel=$(gh api "repos/$repo/releases?per_page=100" --jq "map(select(.tag_name==\"$tag\"))[0] // {}") + id=$(printf '%s' "$rel" | jq -r '.id // empty') + isdraft=$(printf '%s' "$rel" | jq -r '.draft // false') + if [ -n "$id" ] && [ "$isdraft" = "true" ]; then + gh api -X DELETE "repos/$repo/releases/$id" + id="" + fi + if [ -z "$id" ]; then + gh release create "$tag" dist/*.whl dist/*.tar.gz \ + --title "Raven $ver ($today)" --notes-file "$RUNNER_TEMP/notes.md" --draft $pre + else + echo "Release $tag already published (id=$id); leaving as-is." + fi + + # (Optional, wire up when ready) publish to PyPI on tag: # - name: Publish to PyPI # if: startsWith(github.ref, 'refs/tags/v') # run: uv publish diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..adc0cc0 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,71 @@ +# Releasing Raven + +How a new Raven version is cut and published. + +## Versioning + +- Semantic versioning `MAJOR.MINOR.PATCH`. The source of truth is `version` in + `pyproject.toml`. +- Tags: `vX.Y.Z` for a stable release, `vX.Y.Z-rcN` for a pre-release. The tag + must match the `pyproject.toml` version -- CI enforces this (`release.yml`). +- For a pre-release, keep `pyproject.toml` at the base version (e.g. `0.1.3` + while tagging `v0.1.3-rc1`); CI compares only the base. Do NOT set the + version to `0.1.3-rc1` -- that is not a version hatch will build. + +## Release title + +`Raven X.Y.Z (YYYY-MM-DD)` -- for example `Raven 0.1.3 (2026-07-08)`. The CI +draft fills this in automatically (date is the build date; adjust when +publishing if needed). + +## Release notes + +Notes are hand-written and curated. The CI draft prefills the boilerplate +(Install, Release Status, Notes); a human writes the one-line summary and the +Highlights before publishing. Structure: + +``` + + +## Highlights +- + +## Install + curl -fsSL https://raven.evermind.ai/install.sh | bash + then: raven onboard + +## Release Status +- Version: `X.Y.Z` +- Tag: `vX.Y.Z` +- Stability: # fill by hand per release type +- Assets: wheel and source distribution attached to this release + +## Notes +- pre-1.0 evolution caveat +- PyPI not enabled; install via the GitHub Release wheel +``` + +`Stability` is not boilerplate -- set it by release type (patch / minor / rc). + +## Flow + +1. Bump `version` in `pyproject.toml`; open a PR; merge to `main`. +2. `git tag vX.Y.Z && git push origin vX.Y.Z`. +3. CI (`release.yml`) builds the wheel + sdist and creates a **draft** GitHub + Release with both attached, titled and prefilled from the template. +4. Fill the summary + Highlights in the draft, then click **Publish**. + Publishing makes it `/releases/latest`, which `install.sh` serves. + +## Pre-releases + +- `vX.Y.Z-rcN` tags build a draft marked **pre-release**. A pre-release is never + `/releases/latest`, so `curl | sh` users are unaffected. Use an rc tag to + verify the release pipeline before cutting the stable tag; delete the rc + release and tag afterward. + +## Notes + +- `main` is squash-merge + PR-only. The release itself is not automated past the + draft: publishing is a deliberate human step. +- PyPI publishing is not wired up; the supported install path is the GitHub + Release wheel asset resolved by `install.sh`.