Skip to content

Commit 7cdcc61

Browse files
authored
ci: automated release tagging via release-pr workflow (#447)
## Summary - Adds `.github/workflows/release-pr.yml` — triggered on PRs labeled `release`; validates the PR title, `CHANGELOG.md` entry, and `package.json` version on every update; pushes the `cli-vX.Y.Z` tag automatically on merge to kick off the existing `release.yml` pipeline - Updates `cli/RELEASE.md` — replaces the manual tag-push step with `gh pr merge --auto --squash` and documents the new workflow - Adds `gh pr merge --auto --squash` to the artifacts PR creation step in `release.yml` so the artifacts PR merges itself ## How it works 1. Open release PR with `--label release` and title `release: CLI vX.Y.Z` 2. Enable auto-merge: `gh pr merge --auto --squash` 3. `release-pr.yml` validates on every push; on merge it pushes `cli-vX.Y.Z`, triggering `release.yml` as before 4. `release.yml` creates the artifacts PR and immediately enables auto-merge on it — no manual step needed ## Notable decisions - **Shell injection hardened**: PR title is passed via `env:` not inline template interpolation - **Bot collaborator access needed**: for the artifacts PR auto-merge to complete, `caffeine-ci-generic-rw[bot]` must be added as a repo collaborator so its PRs run CI without the approval gate - **`release` label prerequisite**: must exist in the repo (Settings → Labels) before the first release run ## Test plan - [ ] Open a dummy PR with label `release` and title `release: CLI vX.Y.Z` — verify `validate` job passes - [ ] Try invalid title / missing changelog entry — verify `validate` job fails with a clear error - [ ] Verify `tag-and-release` is SKIPPED on plain close (not merge)
1 parent 123c7f2 commit 7cdcc61

3 files changed

Lines changed: 111 additions & 15 deletions

File tree

.github/workflows/release-pr.yml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: release-pr
2+
3+
# Triggered on PRs labeled 'release'. Validates changelog and PR title on every
4+
# update, then pushes the tag on merge to kick off the release pipeline.
5+
on:
6+
pull_request:
7+
types: [opened, edited, synchronize, reopened, labeled, closed]
8+
branches:
9+
- main
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
validate:
16+
runs-on: ubuntu-latest
17+
if: contains(github.event.pull_request.labels.*.name, 'release')
18+
outputs:
19+
version: ${{ steps.get_version.outputs.version }}
20+
steps:
21+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
22+
23+
- name: Extract version from PR title
24+
id: get_version
25+
env:
26+
PR_TITLE: ${{ github.event.pull_request.title }}
27+
run: |
28+
VERSION=$(echo "$PR_TITLE" | sed -nE 's/^release: CLI v([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?)$/\1/p')
29+
if [[ -z "$VERSION" ]]; then
30+
echo "::error::Could not extract version from PR title. Title must be: 'release: CLI vX.Y.Z'"
31+
exit 1
32+
fi
33+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
34+
echo "Detected release version: $VERSION"
35+
36+
- name: Validate changelog entry exists
37+
working-directory: cli
38+
env:
39+
VERSION: ${{ steps.get_version.outputs.version }}
40+
run: |
41+
ESCAPED=$(echo "$VERSION" | sed 's/\./\\./g')
42+
NOTES=$(awk "/^##? ${ESCAPED}$/{found=1; next} found && /^##? [0-9]/{exit} found && /^##? Next/{exit} found" CHANGELOG.md)
43+
if [[ -z "$NOTES" ]]; then
44+
echo "::error::No changelog entry found for v${VERSION} in cli/CHANGELOG.md"
45+
exit 1
46+
fi
47+
echo "Changelog entry found for v${VERSION}."
48+
49+
- name: Validate package.json version matches PR title
50+
working-directory: cli
51+
env:
52+
VERSION: ${{ steps.get_version.outputs.version }}
53+
run: |
54+
PKG_VERSION=$(node -p "require('./package.json').version")
55+
if [[ "$PKG_VERSION" != "$VERSION" ]]; then
56+
echo "::error::package.json version ($PKG_VERSION) does not match PR title version ($VERSION)"
57+
exit 1
58+
fi
59+
echo "package.json version matches: $PKG_VERSION"
60+
61+
tag-and-release:
62+
needs: validate
63+
runs-on: ubuntu-latest
64+
permissions: {} # all privileged operations use the GitHub App token, not GITHUB_TOKEN
65+
# action == 'closed' ensures this only fires on the actual merge event,
66+
# not when the 'release' label is retroactively added to an already-merged PR.
67+
if: github.event.action == 'closed' && github.event.pull_request.merged == true
68+
steps:
69+
- name: Create GitHub App Token
70+
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3
71+
id: app-token
72+
with:
73+
app-id: ${{ vars.GENERIC_CI_RW_APP_ID }}
74+
private-key: ${{ secrets.GENERIC_CI_RW_APP_PRIVATE_KEY }}
75+
76+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
77+
with:
78+
# Check out the merge commit so the tag points at the right SHA
79+
ref: ${{ github.event.pull_request.merge_commit_sha }}
80+
token: ${{ steps.app-token.outputs.token }}
81+
82+
- name: Configure git user
83+
run: |
84+
git config --global user.name "github-actions[bot]"
85+
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
86+
87+
- name: Push tag to trigger release pipeline
88+
env:
89+
VERSION: ${{ needs.validate.outputs.version }}
90+
run: |
91+
git tag "cli-v${VERSION}" -m "CLI v${VERSION}"
92+
git push origin "cli-v${VERSION}"

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,4 @@ jobs:
225225
--title "cli-releases: v${VERSION} artifacts" \
226226
--body "Release artifacts generated by CI for CLI v${VERSION}." \
227227
--base main
228+
gh pr merge --auto --squash

cli/RELEASE.md

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,36 @@ cd cli
2121
npm version patch --no-git-tag-version # or: minor / major
2222
```
2323

24-
## 3. Create a release PR
24+
## 3. Create a release PR and enable auto-merge
2525

2626
```bash
2727
git checkout -b <username>/release-X.Y.Z
2828
git add cli/CHANGELOG.md cli/package.json cli/package-lock.json
2929
git commit -m "release: CLI vX.Y.Z"
3030
git push -u origin <username>/release-X.Y.Z
31-
gh pr create --title "release: CLI vX.Y.Z" --body "..."
31+
gh pr create \
32+
--title "release: CLI vX.Y.Z" \
33+
--body "Release CLI vX.Y.Z." \
34+
--label release
35+
gh pr merge --auto --squash
3236
```
3337

34-
Wait for CI to pass, then merge.
38+
The [`release-pr.yml`](../.github/workflows/release-pr.yml) workflow runs on every update and validates:
39+
- PR title matches `release: CLI vX.Y.Z`
40+
- `cli/CHANGELOG.md` has an entry for the version
41+
- `cli/package.json` version matches
3542

36-
## 4. Tag and push
43+
Once all required checks pass the PR merges automatically. On merge, `release-pr.yml` pushes the `cli-vX.Y.Z` tag, which triggers the [`release.yml`](../.github/workflows/release.yml) workflow — it builds, publishes to npm, creates a GitHub Release, and deploys canisters (`cli.mops.one` and `docs.mops.one`).
3744

38-
```bash
39-
git checkout main && git pull
40-
git tag cli-vX.Y.Z
41-
git push origin cli-vX.Y.Z
42-
```
43-
44-
This triggers the [`release.yml`](../.github/workflows/release.yml) workflow which builds, publishes to npm, creates a GitHub Release, deploys canisters (`cli.mops.one` and `docs.mops.one`), and opens a PR with on-chain release artifacts.
45-
46-
Monitor at [Actions → Release CLI](https://github.com/caffeinelabs/mops/actions/workflows/release.yml).
45+
> **Note:** This workflow only deploys the `cli` and `docs` canisters. The `main`, `assets`, `blog`, and `play-frontend` canisters require a manual deploy. If a release includes changes to any of those (e.g. `backend/main/` or `frontend/`), upgrade them manually (staging first, then `ic`):
46+
>
47+
> ```bash
48+
> NODE_ENV=production dfx deploy --no-wallet --identity mops --network <staging|ic> <canister>
49+
> ```
4750
48-
## 5. Merge artifacts PR
51+
## 5. Artifacts PR
4952
50-
After the workflow completes, merge the `cli-releases: vX.Y.Z artifacts` PR.
53+
After the release pipeline completes, it creates and auto-merges a `cli-releases: vX.Y.Z artifacts` PR. No action needed unless it fails — monitor at [Actions → Release CLI](https://github.com/caffeinelabs/mops/actions/workflows/release.yml) and merge the artifacts PR manually if needed.
5154
5255
## Verify build
5356

0 commit comments

Comments
 (0)