From 8aead136a23c9e4a8bb9c45d8339a4e16ff2a32d Mon Sep 17 00:00:00 2001 From: strandedturtle Date: Wed, 8 Jul 2026 09:15:56 +0000 Subject: [PATCH] chore: automate releases with release-please MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manual releases were error-prone in practice: v1.0.1 got tagged without package.json ever being bumped to match, and I had to hand-fix that drift today. Replace the whole manual tag-and-bump flow with release-please: - release-please-config.json / .release-please-manifest.json (seeded at the current 1.0.2): release-type 'simple', extra-files keep server+client package.json AND package-lock.json (both the top-level and nested packages[''].version fields) in lockstep automatically. - release-please.yml runs on every push to main, maintaining one standing Release PR (version + changelog from feat:/fix:/deps:/etc. commits since the last release) and auto-merging it once required CI passes — that merge is what cuts the git tag + GitHub Release. - release.yml no longer creates the GitHub Release itself (release-please does, with a much better categorized changelog); it still builds :latest on every main push and pinned :X.Y.Z/:X.Y images off the tag release-please creates. - CONTRIBUTING.md documents the commit-prefix convention (feat/fix/deps/... vs docs/chore/refactor/... which don't trigger a release) and the new fully-automated flow, with a one-line note on how to switch to a manual-merge gate instead of full auto-merge if ever wanted. Verified: config/manifest JSON valid, all workflow YAML valid, the extra-files jsonpaths resolve against the real lockfiles (checked with jq), server tests still 111/111. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_013Lj6nYJQDtLaZFvvEQJGM4 --- .github/workflows/release-please.yml | 45 ++++++++++++++++++++++++++++ .github/workflows/release.yml | 21 +++---------- .release-please-manifest.json | 3 ++ CONTRIBUTING.md | 44 +++++++++++++++++++++++---- release-please-config.json | 31 +++++++++++++++++++ 5 files changed, 121 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..bf3f64c --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,45 @@ +name: release-please + +# Maintains a standing "Release PR" that accumulates feat:/fix:/deps: commits +# since the last release into a version bump + changelog (release-please-config.json +# controls which commit types count and where they land in CHANGELOG.md). Every +# push to main updates that one PR in place rather than opening a new one. +# +# The auto-merge step below merges that PR itself the moment required CI passes +# (same pattern as dependabot-auto-merge.yml), so a qualifying change on main +# becomes a tagged release with no manual version bump or `git tag` needed. That +# merge is what actually cuts the release: release-please creates the git tag + +# GitHub Release, which triggers release.yml's tag-triggered image publish +# (pinned :X.Y.Z / :X.Y images). +# +# Want a manual gate instead (open the PR, but decide yourself when to ship)? +# Delete the "Auto-merge the release PR" step below — everything else still +# works, you just click Merge on the PR when you're ready. +on: + push: + branches: [main] + +permissions: + contents: write + pull-requests: write + +concurrency: + group: release-please + cancel-in-progress: false + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + id: release + + - name: Auto-merge the release PR once it's open + if: steps.release.outputs.prs_created == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_JSON: ${{ steps.release.outputs.pr }} + run: | + PR_NUMBER=$(echo "$PR_JSON" | jq -r '.number') + gh pr merge --auto --squash "$PR_NUMBER" \ + || echo "Auto-merge not enabled — turn on 'Allow auto-merge' in repo settings, or merge #$PR_NUMBER manually." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 608f57a..5765af0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,20 +87,7 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - # On a version tag, create a GitHub Release with auto-generated notes. These - # also feed DockPull's own "What's changed" changelog panel. - release: - name: Create GitHub Release - needs: publish - if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - name: Create release - uses: softprops/action-gh-release@v3 - with: - generate_release_notes: true + # NOTE: the GitHub Release itself is created by release-please.yml (with a + # changelog grouped by feat/fix/deps), not here — it creates the git tag as a + # side effect of publishing the Release, which is what triggers this workflow's + # tag push above. See release-please-config.json / release-please.yml. diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..b870c5e --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.0.2" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb5743a..3034546 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,10 +43,42 @@ docker build -f server/Dockerfile -t dockpull . with route changes. - `SECURITY.md` — threat model and operator hardening guidance. -## Images / releases +## Commit message convention -`:latest` is published from every push to `main` (multi-arch: `linux/amd64` + -`linux/arm64`). Cutting a version tag (`git tag v1.2.0 && git push origin v1.2.0`) -publishes pinned `:1.2.0` / `:1.2` images and creates a GitHub Release with -auto-generated notes. Bump `version` in `server/package.json` + -`client/package.json` (and their lockfiles) to match the tag. +PR titles / squash-merge commit subjects use a `type: description` prefix — +this drives the automated release below (which commits count as releasable, +which changelog section they land in, and whether it's a patch or minor bump): + +| Prefix | Meaning | Bump | +|---|---|---| +| `feat:` | user-facing feature | minor | +| `fix:` | bug fix | patch | +| `perf:` | performance fix | patch | +| `deps:` | dependency bump | patch | +| `security:` | security fix | patch | +| `docs:`, `chore:`, `refactor:`, `test:`, `build:`, `ci:` | no user-facing change | none — no release | + +A commit with no recognized prefix doesn't count as releasable either. If the +*only* changes since the last release are unprefixed or in the "no release" +row, no Release PR gets opened — that's intentional (e.g. a docs typo fix +shouldn't ping every user's DockPull with "update available"). + +## Images / releases — fully automated + +Releases are **not** cut by hand. [`release-please`](https://github.com/googleapis/release-please) +(`.github/workflows/release-please.yml`) watches every push to `main` and +maintains one standing "Release PR" with the next version + a changelog +generated from `feat:`/`fix:`/`deps:`/etc. commits since the last release. That +PR is set to auto-merge the moment required CI passes — merging it is what +actually cuts the release: release-please creates the git tag + GitHub Release, +which triggers `release.yml`'s tag-triggered build of pinned `:X.Y.Z` / `:X.Y` +images (`:latest` already tracks every `main` push independently of this). + +Nothing to run by hand: no `npm version`, no `git tag`, no manually editing +`server/package.json` / `client/package.json` (the release PR does that too, +via `release-please-config.json`'s `extra-files`). + +Want a manual gate instead of full auto-merge (e.g. to batch a few fixes into +one release, or review the changelog before it ships)? Delete the "Auto-merge +the release PR" step in `release-please.yml` — the Release PR still opens and +updates itself automatically, it'll just wait for a manual click on Merge. diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..5bd5caf --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "simple", + "packages": { + ".": { + "release-type": "simple", + "changelog-path": "CHANGELOG.md", + "extra-files": [ + { "type": "json", "path": "server/package.json", "jsonpath": "$.version" }, + { "type": "json", "path": "server/package-lock.json", "jsonpath": "$.version" }, + { "type": "json", "path": "server/package-lock.json", "jsonpath": "$.packages[''].version" }, + { "type": "json", "path": "client/package.json", "jsonpath": "$.version" }, + { "type": "json", "path": "client/package-lock.json", "jsonpath": "$.version" }, + { "type": "json", "path": "client/package-lock.json", "jsonpath": "$.packages[''].version" } + ] + } + }, + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance" }, + { "type": "deps", "section": "Dependencies" }, + { "type": "security", "section": "Security" }, + { "type": "docs", "section": "Documentation", "hidden": true }, + { "type": "chore", "section": "Miscellaneous", "hidden": true }, + { "type": "refactor", "section": "Miscellaneous", "hidden": true }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "build", "section": "Build", "hidden": true }, + { "type": "ci", "section": "CI", "hidden": true } + ] +}