Skip to content

Commit 8aead13

Browse files
strandedturtleclaude
andcommitted
chore: automate releases with release-please
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Lj6nYJQDtLaZFvvEQJGM4
1 parent caa1696 commit 8aead13

5 files changed

Lines changed: 121 additions & 23 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: release-please
2+
3+
# Maintains a standing "Release PR" that accumulates feat:/fix:/deps: commits
4+
# since the last release into a version bump + changelog (release-please-config.json
5+
# controls which commit types count and where they land in CHANGELOG.md). Every
6+
# push to main updates that one PR in place rather than opening a new one.
7+
#
8+
# The auto-merge step below merges that PR itself the moment required CI passes
9+
# (same pattern as dependabot-auto-merge.yml), so a qualifying change on main
10+
# becomes a tagged release with no manual version bump or `git tag` needed. That
11+
# merge is what actually cuts the release: release-please creates the git tag +
12+
# GitHub Release, which triggers release.yml's tag-triggered image publish
13+
# (pinned :X.Y.Z / :X.Y images).
14+
#
15+
# Want a manual gate instead (open the PR, but decide yourself when to ship)?
16+
# Delete the "Auto-merge the release PR" step below — everything else still
17+
# works, you just click Merge on the PR when you're ready.
18+
on:
19+
push:
20+
branches: [main]
21+
22+
permissions:
23+
contents: write
24+
pull-requests: write
25+
26+
concurrency:
27+
group: release-please
28+
cancel-in-progress: false
29+
30+
jobs:
31+
release-please:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: googleapis/release-please-action@v4
35+
id: release
36+
37+
- name: Auto-merge the release PR once it's open
38+
if: steps.release.outputs.prs_created == 'true'
39+
env:
40+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41+
PR_JSON: ${{ steps.release.outputs.pr }}
42+
run: |
43+
PR_NUMBER=$(echo "$PR_JSON" | jq -r '.number')
44+
gh pr merge --auto --squash "$PR_NUMBER" \
45+
|| echo "Auto-merge not enabled — turn on 'Allow auto-merge' in repo settings, or merge #$PR_NUMBER manually."

.github/workflows/release.yml

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,7 @@ jobs:
8787
cache-from: type=gha
8888
cache-to: type=gha,mode=max
8989

90-
# On a version tag, create a GitHub Release with auto-generated notes. These
91-
# also feed DockPull's own "What's changed" changelog panel.
92-
release:
93-
name: Create GitHub Release
94-
needs: publish
95-
if: startsWith(github.ref, 'refs/tags/v')
96-
runs-on: ubuntu-latest
97-
permissions:
98-
contents: write
99-
steps:
100-
- uses: actions/checkout@v7
101-
with:
102-
fetch-depth: 0
103-
- name: Create release
104-
uses: softprops/action-gh-release@v3
105-
with:
106-
generate_release_notes: true
90+
# NOTE: the GitHub Release itself is created by release-please.yml (with a
91+
# changelog grouped by feat/fix/deps), not here — it creates the git tag as a
92+
# side effect of publishing the Release, which is what triggers this workflow's
93+
# tag push above. See release-please-config.json / release-please.yml.

.release-please-manifest.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
".": "1.0.2"
3+
}

CONTRIBUTING.md

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,42 @@ docker build -f server/Dockerfile -t dockpull .
4343
with route changes.
4444
- `SECURITY.md` — threat model and operator hardening guidance.
4545

46-
## Images / releases
46+
## Commit message convention
4747

48-
`:latest` is published from every push to `main` (multi-arch: `linux/amd64` +
49-
`linux/arm64`). Cutting a version tag (`git tag v1.2.0 && git push origin v1.2.0`)
50-
publishes pinned `:1.2.0` / `:1.2` images and creates a GitHub Release with
51-
auto-generated notes. Bump `version` in `server/package.json` +
52-
`client/package.json` (and their lockfiles) to match the tag.
48+
PR titles / squash-merge commit subjects use a `type: description` prefix —
49+
this drives the automated release below (which commits count as releasable,
50+
which changelog section they land in, and whether it's a patch or minor bump):
51+
52+
| Prefix | Meaning | Bump |
53+
|---|---|---|
54+
| `feat:` | user-facing feature | minor |
55+
| `fix:` | bug fix | patch |
56+
| `perf:` | performance fix | patch |
57+
| `deps:` | dependency bump | patch |
58+
| `security:` | security fix | patch |
59+
| `docs:`, `chore:`, `refactor:`, `test:`, `build:`, `ci:` | no user-facing change | none — no release |
60+
61+
A commit with no recognized prefix doesn't count as releasable either. If the
62+
*only* changes since the last release are unprefixed or in the "no release"
63+
row, no Release PR gets opened — that's intentional (e.g. a docs typo fix
64+
shouldn't ping every user's DockPull with "update available").
65+
66+
## Images / releases — fully automated
67+
68+
Releases are **not** cut by hand. [`release-please`](https://github.com/googleapis/release-please)
69+
(`.github/workflows/release-please.yml`) watches every push to `main` and
70+
maintains one standing "Release PR" with the next version + a changelog
71+
generated from `feat:`/`fix:`/`deps:`/etc. commits since the last release. That
72+
PR is set to auto-merge the moment required CI passes — merging it is what
73+
actually cuts the release: release-please creates the git tag + GitHub Release,
74+
which triggers `release.yml`'s tag-triggered build of pinned `:X.Y.Z` / `:X.Y`
75+
images (`:latest` already tracks every `main` push independently of this).
76+
77+
Nothing to run by hand: no `npm version`, no `git tag`, no manually editing
78+
`server/package.json` / `client/package.json` (the release PR does that too,
79+
via `release-please-config.json`'s `extra-files`).
80+
81+
Want a manual gate instead of full auto-merge (e.g. to batch a few fixes into
82+
one release, or review the changelog before it ships)? Delete the "Auto-merge
83+
the release PR" step in `release-please.yml` — the Release PR still opens and
84+
updates itself automatically, it'll just wait for a manual click on Merge.

release-please-config.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3+
"release-type": "simple",
4+
"packages": {
5+
".": {
6+
"release-type": "simple",
7+
"changelog-path": "CHANGELOG.md",
8+
"extra-files": [
9+
{ "type": "json", "path": "server/package.json", "jsonpath": "$.version" },
10+
{ "type": "json", "path": "server/package-lock.json", "jsonpath": "$.version" },
11+
{ "type": "json", "path": "server/package-lock.json", "jsonpath": "$.packages[''].version" },
12+
{ "type": "json", "path": "client/package.json", "jsonpath": "$.version" },
13+
{ "type": "json", "path": "client/package-lock.json", "jsonpath": "$.version" },
14+
{ "type": "json", "path": "client/package-lock.json", "jsonpath": "$.packages[''].version" }
15+
]
16+
}
17+
},
18+
"changelog-sections": [
19+
{ "type": "feat", "section": "Features" },
20+
{ "type": "fix", "section": "Bug Fixes" },
21+
{ "type": "perf", "section": "Performance" },
22+
{ "type": "deps", "section": "Dependencies" },
23+
{ "type": "security", "section": "Security" },
24+
{ "type": "docs", "section": "Documentation", "hidden": true },
25+
{ "type": "chore", "section": "Miscellaneous", "hidden": true },
26+
{ "type": "refactor", "section": "Miscellaneous", "hidden": true },
27+
{ "type": "test", "section": "Tests", "hidden": true },
28+
{ "type": "build", "section": "Build", "hidden": true },
29+
{ "type": "ci", "section": "CI", "hidden": true }
30+
]
31+
}

0 commit comments

Comments
 (0)