Skip to content

Commit 58cd2bd

Browse files
chrisleekrclaude
andauthored
chore: migrate release process from semantic-release to release-please (#255)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0cb6d12 commit 58cd2bd

17 files changed

Lines changed: 217 additions & 458 deletions

.github/workflows/ci.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
#
33
# Triggers:
44
# - pull_request: fast feedback on every PR
5-
# - push to main: post-merge sanity (catches squash-regressions; release is manual)
6-
# - workflow_call: invoked by dev-release.yml and release.yml so an image
7-
# is never published without these checks passing.
5+
# - push to main: post-merge sanity (catches squash-regressions). Releases
6+
# are cut by merging the release-please Release PR, which these same PR
7+
# checks gate.
8+
# - workflow_call: retained for reuse; release-please.yml relies on the PR
9+
# checks above rather than calling this workflow.
810
name: CI
911

1012
on:
@@ -64,9 +66,8 @@ jobs:
6466
- name: Checkout source code
6567
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
6668
with:
67-
# Full history kept for parity with dev-release/release workflows
68-
# and to give semantic-release accurate diff context when this
69-
# workflow is invoked via workflow_call. Secret scanning lives
69+
# Full history kept so release-please can walk tags/commits for
70+
# accurate version + changelog inference. Secret scanning lives
7071
# in .github/workflows/secrets-scan.yml now.
7172
fetch-depth: 0
7273

.github/workflows/dev-release.yml

Lines changed: 0 additions & 187 deletions
This file was deleted.

.github/workflows/docker-build.yml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Reusable image builder. No checks, no release: orchestrators invoke this
2-
# via workflow_call after gating on ci.yml + semantic-release.
2+
# via workflow_call after release-please cuts a tag (release-please.yml).
33
#
44
# Ships TWO images per release:
55
# - chrisleekr/github-app:<tag>-orchestrator (lean, webhook server)
@@ -39,6 +39,15 @@ on:
3939
required: false
4040
type: boolean
4141
default: false
42+
secrets:
43+
# Only the Docker Hub push credentials are needed. Declared explicitly so
44+
# callers pass least-privilege secrets instead of `secrets: inherit`
45+
# (which would forward the RELEASE_TOKEN PAT and every other repo secret
46+
# into this build graph). GITHUB_TOKEN is provided implicitly.
47+
DOCKER_USERNAME:
48+
required: true
49+
DOCKER_PASSWORD:
50+
required: true
4251
workflow_dispatch:
4352
inputs:
4453
tag-name:
@@ -111,11 +120,13 @@ jobs:
111120
env:
112121
TAG_NAME: ${{ inputs.tag-name }}
113122
run: |
114-
# Prod release: v1.2.3
115-
# Dev pre-release: v1.2.3-<sanitized-branch>.N: branch name is
116-
# alphanumerics+hyphens (release.config.mjs strips slashes), and N
117-
# is the prerelease counter semantic-release auto-increments.
118-
if ! [[ "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z][a-zA-Z0-9-]*\.[0-9]+)?$ ]]; then
123+
# Stable release: v1.2.3
124+
# Beta prerelease from release-please: the prerelease strategy sets
125+
# the suffix to the prerelease type (v1.2.3-beta) and appends/bumps a
126+
# zero-padded counter only once one exists (v1.2.3-beta01). Matched
127+
# permissively: a hyphen, a leading letter, then alphanumerics / dots
128+
# / hyphens.
129+
if ! [[ "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z][a-zA-Z0-9.-]*)?$ ]]; then
119130
echo "ERROR: tag-name input does not match the expected format." >&2
120131
exit 1
121132
fi
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Release pipeline (replaces the retired semantic-release release.yml + dev-release.yml).
2+
#
3+
# Model: release-please maintains a Release PR per branch. Merging that PR is the
4+
# release gate.
5+
# - main : stable channel. Merging the Release PR cuts v<x.y.z>, updates
6+
# CHANGELOG.md + package.json, creates a GitHub release, then builds
7+
# and pushes the prod image (with :latest).
8+
# - beta : prerelease channel. Merging the Release PR cuts v<x.y.z>-beta.<n>
9+
# (prerelease config) and builds a beta image (no :latest).
10+
#
11+
# Branch selection: one workflow file lives on both branches. target-branch is
12+
# the pushed ref; config-file / manifest-file switch to the .beta variants on
13+
# the beta branch so main and beta never share release-please state.
14+
#
15+
# CI is NOT called here: it runs on the Release PR (branch protection is the
16+
# merge gate). docker-build runs post-merge, gated on release_created.
17+
name: Release Please
18+
19+
on:
20+
push:
21+
branches:
22+
- main
23+
- beta
24+
25+
# RELEASE_TOKEN (a PAT) is required, not the default GITHUB_TOKEN: releases and
26+
# PRs opened by GITHUB_TOKEN do not trigger downstream workflows, so CI would
27+
# never run on the Release PR.
28+
concurrency:
29+
group: release-please-${{ github.ref_name }}
30+
cancel-in-progress: false
31+
32+
# No workflow-level grant: each job requests only what it needs (least
33+
# privilege). The docker job in particular must NOT inherit the release-please
34+
# job's write scopes.
35+
permissions: {}
36+
37+
jobs:
38+
release-please:
39+
name: Release Please (${{ github.ref_name }})
40+
runs-on: ubuntu-24.04
41+
timeout-minutes: 15
42+
permissions:
43+
contents: write # release commit, tag, GitHub release
44+
pull-requests: write # maintain the Release PR
45+
issues: write # release-please label management
46+
outputs:
47+
release_created: ${{ steps.rp.outputs.release_created }}
48+
tag_name: ${{ steps.rp.outputs.tag_name }}
49+
steps:
50+
- name: Run release-please
51+
id: rp
52+
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
53+
with:
54+
token: ${{ secrets.RELEASE_TOKEN }}
55+
target-branch: ${{ github.ref_name }}
56+
config-file: ${{ github.ref_name == 'beta' && 'release-please-config.beta.json' || 'release-please-config.json' }}
57+
manifest-file: ${{ github.ref_name == 'beta' && '.release-please-manifest.beta.json' || '.release-please-manifest.json' }}
58+
59+
docker:
60+
name: Docker
61+
needs: release-please
62+
if: needs.release-please.outputs.release_created == 'true'
63+
# Ceiling for the reusable docker-build.yml: it needs security-events:write
64+
# (Trivy SARIF upload) and id-token:write (Sigstore/attestations). Matches
65+
# the grant the retired release.yml gave this path; the release-please job's
66+
# PR/issues write scopes are deliberately NOT granted here.
67+
permissions:
68+
contents: read
69+
security-events: write
70+
id-token: write
71+
uses: ./.github/workflows/docker-build.yml
72+
with:
73+
tag-name: ${{ needs.release-please.outputs.tag_name }}
74+
is-dev-release: ${{ github.ref_name == 'beta' }}
75+
# Pass only the Docker Hub credentials, not `secrets: inherit`, so the
76+
# RELEASE_TOKEN PAT is not forwarded into the image build graph.
77+
secrets:
78+
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
79+
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

0 commit comments

Comments
 (0)