From 21f65471345834e114387e64d74f71d052d92237 Mon Sep 17 00:00:00 2001 From: Jack Platten Date: Tue, 7 Jul 2026 17:32:13 -0700 Subject: [PATCH] ci: automate releases with release-please (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Replace manual `mise run release ` / `scripts/release.sh` with [release-please](https://github.com/googleapis/release-please) — it maintains an open release PR (version bump + generated `CHANGELOG.md`) derived from Conventional Commit PR titles, and tags/publishes automatically on merge - Add a follow-up job that force-updates the floating `v7`/`v7.0` convenience tags after a real release, using release-please's own documented pattern (delete + recreate, not force-push) - Add a `pr-title` CI check (`amannn/action-semantic-pull-request`) enforcing Conventional Commit PR titles — this is what actually makes release-please's changelog generation correct, since squash-merge turns the PR title into the commit message - Update `CONTRIBUTING.md`, `agents.md`, and the PR template to document the new title requirement and release flow This also closes a gap noticed along the way: direct pushes to `main` bypass CI entirely (`CI.yml` only triggers on `pull_request`), so releases could previously ship untested code. Releases are now normal PR merges, which CI already gates. ## Test plan - [x] `mise run lint` passes - [x] `mise run test` passes (full suite, all fish version-string / `--version` behavior unaffected by the `x-release-please-version` marker comment) - [x] `actionlint` passes on both workflow files - [ ] First real release: merge the release-please PR once it appears and confirm the tag, GitHub release, and floating `v7`/`v7.0` tags all land correctly — this is the first live trial of the new path 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 5 --- .github/PULL_REQUEST_TEMPLATE.md | 2 + .github/workflows/CI.yml | 9 ++++ .github/workflows/release-please.yml | 44 ++++++++++++++++++ .release-please-manifest.json | 3 ++ CONTRIBUTING.md | 34 +++++++++++--- agents.md | 5 ++ functions/tide.fish | 2 +- mise.toml | 7 --- release-please-config.json | 13 ++++++ scripts/release.sh | 69 ---------------------------- 10 files changed, 105 insertions(+), 83 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json delete mode 100755 scripts/release.sh diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 84ad65d..39119b2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,6 @@ + + #### Description diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e1b9584..ee25828 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -58,3 +58,12 @@ jobs: - uses: gitleaks/gitleaks-action@v3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + pr-title: + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..cefc522 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,44 @@ +name: Release Please + +on: + push: + branches: [main] + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + major: ${{ steps.release.outputs.major }} + minor: ${{ steps.release.outputs.minor }} + steps: + - uses: googleapis/release-please-action@v5 + id: release + + update-floating-tags: + needs: release-please + if: ${{ needs.release-please.outputs.release_created == 'true' }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + - name: Tag major and minor versions + env: + MAJOR: ${{ needs.release-please.outputs.major }} + MINOR: ${{ needs.release-please.outputs.minor }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -d "v$MAJOR" || true + git tag -d "v$MAJOR.$MINOR" || true + git push origin ":v$MAJOR" || true + git push origin ":v$MAJOR.$MINOR" || true + git tag -a "v$MAJOR" -m "Release v$MAJOR" + git tag -a "v$MAJOR.$MINOR" -m "Release v$MAJOR.$MINOR" + git push origin "v$MAJOR" "v$MAJOR.$MINOR" diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..2ffe868 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "7.0.15" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 25da336..ea14503 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,18 @@ If you have any questions that aren't addressed in this document, please don't h - For anything more complex use `if`, `else`, and `else if` - Piping > command substitution (only when convenient, i.e no extra commands) +### Pull Request Titles + +PR titles must follow [Conventional Commits][]: `[(scope)][!]: `, +e.g. `fix: correct jj branch color` or `feat(vcs)!: unify git/jj into a single item`. + +- Allowed types: `feat`, `fix`, `build`, `chore`, `ci`, `docs`, `style`, `refactor`, `perf`, `test`. +- Add `!` before the `:` (or a `BREAKING CHANGE:` footer) for breaking changes. + +This isn't just style: since PRs are squash-merged, the title becomes the +commit message on `main`, which [release-please][] reads to generate the +next release's changelog entry. A CI check enforces this on every PR. + ### Naming Conventions - Everything should be in `snake_case`. @@ -82,17 +94,27 @@ All links should be in reference style, with references at the bottom of the doc - Extendable: 13pt, 55x9 - PWD: 17pt, 42x14 -## Release Checklist +## Releasing + +Releases are automated by [release-please][]. It watches `main` and keeps an +open "release PR" containing the next version bump and a generated +`CHANGELOG.md` entry, derived from Conventional Commit PR titles merged +since the last release. -- [ ] Update version number in `tide.fish` -- [ ] Put the current date in the changelog -- [ ] Make a commit containing above edits, titled with the version number -- [ ] Create a new tag -- [ ] Push to GitHub +- [ ] Before merging the release PR, hand-edit its `CHANGELOG.md` diff to + clean up the generated bullets into prose, and add any narrative + notes (e.g. security callouts, migration instructions, wiki links) +- [ ] Merge the release PR +- [ ] release-please tags the release, publishes it on GitHub, and updates + `functions/tide.fish`'s version string automatically +- [ ] The floating `vN`/`vN.M` convenience tags (used by `fisher install + plttn/tide@v7`) update automatically in a follow-up job [`fish --no-execute`]: https://fishshell.com/docs/current/cmds/fish.html [`fish_indent`]: https://fishshell.com/docs/current/cmds/fish_indent.html [clownfish]: https://github.com/IlanCosman/clownfish +[conventional commits]: https://www.conventionalcommits.org/ [littlecheck]: https://github.com/ridiculousfish/littlecheck [mega-linter]: https://github.com/nvuillam/mega-linter [prettier]: https://github.com/prettier/prettier +[release-please]: https://github.com/googleapis/release-please diff --git a/agents.md b/agents.md index 95c426c..d390486 100644 --- a/agents.md +++ b/agents.md @@ -5,3 +5,8 @@ All agent-driven work must be opened as pull requests against `plttn/tide`. Do not open pull requests against `IlanCosman/tide` (upstream) for work done in this fork. For testing, use `mise` rather than `make`. + +PR titles must follow [Conventional Commits](https://www.conventionalcommits.org/) +(e.g. `fix: ...`, `feat: ...`) — see `CONTRIBUTING.md`. Since PRs are +squash-merged, the title becomes the commit message that release-please +reads to generate the changelog. diff --git a/functions/tide.fish b/functions/tide.fish index 12a18fb..0bf41ec 100644 --- a/functions/tide.fish +++ b/functions/tide.fish @@ -2,7 +2,7 @@ function tide --description 'Manage your Tide prompt' argparse --stop-nonopt v/version h/help -- $argv if set -q _flag_version - echo 'tide, version 7.0.15' + echo 'tide, version 7.0.15' # x-release-please-version else if set -q _flag_help _tide_help else if functions --query _tide_sub_$argv[1] diff --git a/mise.toml b/mise.toml index 1819228..565e1b4 100644 --- a/mise.toml +++ b/mise.toml @@ -33,10 +33,3 @@ run = 'rm -f littlecheck.py' [tasks.all] description = 'Format, lint, install, and test' depends = ['fmt', 'lint', 'install', 'test'] -[tasks.release] -confirm = 'Are you sure you want to cut a new release?' -description = 'Cut a new release' -file = 'scripts/release.sh' -usage = ''' -arg "" help="Version to release (e.g. 7.1.0)" -''' diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..7c5dc7b --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "simple", + "changelog-path": "CHANGELOG.md", + "include-component-in-tag": false, + "extra-files": [ + { "type": "generic", "path": "functions/tide.fish" } + ] + } + } +} diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 89dac8e..0000000 --- a/scripts/release.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash -#MISE description="Cut a release: mise run release (e.g. mise run release 7.1.0)" -#MISE -set -euo pipefail - -VERSION="${1:-}" - -if [[ -z "$VERSION" ]]; then - echo "Usage: mise run release (e.g. mise run release 7.1.0)" >&2 - exit 1 -fi - -if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Error: version must be X.Y.Z semver (got '$VERSION')" >&2 - exit 1 -fi - -TIDE_FISH="functions/tide.fish" -CHANGELOG="CHANGELOG.md" -TAG="v$VERSION" - -# Confirm the version heading exists in the changelog -if ! grep -qF "## [$TAG][]" "$CHANGELOG"; then - echo "Error: no entry for $TAG found in $CHANGELOG" >&2 - echo "Add a changelog entry before releasing." >&2 - exit 1 -fi - -# Date formatted like existing changelog entries: "Mar 14 2026" -TODAY=$(python3 -c "import datetime; d=datetime.date.today(); print(d.strftime('%b %-d %Y'))") - -echo "Releasing $TAG on $TODAY" - -# 1. Update version in tide.fish -sed -i.bak "s/echo 'tide, version [0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*'/echo 'tide, version $VERSION'/" "$TIDE_FISH" -rm -f "$TIDE_FISH.bak" - -# 2. Stamp the date in CHANGELOG (replace (???) for this version's heading) -sed -i.bak "s|## \[$TAG\]\[\] (???)|## [$TAG][] ($TODAY)|" "$CHANGELOG" -rm -f "$CHANGELOG.bak" - -# 3. Append reference link if not already present -REPO=$(git remote get-url origin | sed 's|.*github.com[:/]||; s|\.git$||') -LINK="[$TAG]: https://github.com/$REPO/tree/$TAG" -if ! grep -qF "$LINK" "$CHANGELOG"; then - echo "$LINK" >> "$CHANGELOG" -fi - -# 4. Commit and push -git add "$TIDE_FISH" "$CHANGELOG" -git commit -m "$TAG" -git push - -# 5. Extract release notes from CHANGELOG (content between first and second ## headings) -NOTES_FILE=$(mktemp) -awk 'found && /^## /{exit} /^## /{found=1; next} found' "$CHANGELOG" \ - | sed -E 's/\[(#[0-9]+)\]\[\]/\1/g' > "$NOTES_FILE" - -gh release create "$TAG" --title "$TAG" --notes-file "$NOTES_FILE" -rm -f "$NOTES_FILE" - -# 6. Force-update higher-level convenience tags (v7, v7.0) -MAJOR="v$(echo "$VERSION" | cut -d. -f1)" -MINOR="v$(echo "$VERSION" | cut -d. -f1-2)" -git tag -f "$MAJOR" -git tag -f "$MINOR" -git push origin "$MAJOR" "$MINOR" --force - -echo "Done! $TAG released on GitHub (also updated $MAJOR and $MINOR)." -- knotmirror 0.0.1