Skip to content

Commit 7502d3e

Browse files
authored
Merge pull request #53 from MaxMB15/chore/release-pipeline-workflow
feat: add one-click release pipeline workflow
2 parents 8925409 + f5f4af5 commit 7502d3e

3 files changed

Lines changed: 285 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,9 @@ jobs:
161161
keep_files: true
162162

163163
# ── macOS Desktop (push, or PRs with macOS-relevant changes) ─────────────
164+
# Skip platform builds on version-bump PRs (only version numbers change).
164165
build-macos:
165-
if: needs.detect-changes.outputs.macos == 'true'
166+
if: needs.detect-changes.outputs.macos == 'true' && !startsWith(github.head_ref, 'chore-bump-v')
166167
needs: [test, detect-changes]
167168
runs-on: macos-latest
168169
steps:
@@ -214,7 +215,7 @@ jobs:
214215

215216
# ── Linux Desktop (push, or PRs with Linux-relevant changes) ────────────
216217
build-linux:
217-
if: needs.detect-changes.outputs.linux == 'true'
218+
if: needs.detect-changes.outputs.linux == 'true' && !startsWith(github.head_ref, 'chore-bump-v')
218219
needs: [test, detect-changes]
219220
runs-on: ubuntu-latest
220221
steps:
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# One-click release pipeline: dev -> main -> version bump -> tag -> release build.
2+
#
3+
# Replaces the manual 13-step process:
4+
# 1. Merges dev into main (fast-forward or merge commit)
5+
# 2. Bumps version on main
6+
# 3. Pushes annotated tag vX.Y.Z (triggers release.yml)
7+
#
8+
# After release.yml completes, its sync-dev job auto-merges main back to dev.
9+
#
10+
# Prerequisites:
11+
# - Secret: RELEASE_AUTOMATION_PAT (fine-grained PAT: Contents + Pull requests RW)
12+
# - Secret: TAURI_SIGNING_PRIVATE_KEY (for release.yml)
13+
# - Optional var: RELEASE_ALLOWED_ACTORS (comma-separated usernames)
14+
#
15+
# The existing release-bump.yml and release-tag.yml still work as manual fallbacks.
16+
17+
name: Release — one-click pipeline
18+
19+
on:
20+
workflow_dispatch:
21+
inputs:
22+
release_type:
23+
description: "Semver bump type (see https://semver.org)"
24+
type: choice
25+
required: true
26+
default: patch
27+
options:
28+
- patch
29+
- minor
30+
- major
31+
dry_run:
32+
description: "Dry run — show what would happen without pushing"
33+
type: boolean
34+
default: false
35+
36+
permissions:
37+
contents: write
38+
pull-requests: write
39+
40+
concurrency:
41+
group: release-pipeline
42+
cancel-in-progress: false
43+
44+
jobs:
45+
release-pipeline:
46+
runs-on: ubuntu-latest
47+
steps:
48+
- name: Verify release permission
49+
env:
50+
ACTOR: ${{ github.actor }}
51+
REPO_OWNER: ${{ github.repository_owner }}
52+
ALLOWED_ACTORS: ${{ vars.RELEASE_ALLOWED_ACTORS }}
53+
run: |
54+
set -euo pipefail
55+
python3 << 'PY'
56+
import os
57+
actor = os.environ["ACTOR"]
58+
owner = os.environ["REPO_OWNER"]
59+
raw = os.environ.get("ALLOWED_ACTORS", "").strip()
60+
if raw:
61+
allowed = [x.strip() for x in raw.split(",") if x.strip()]
62+
else:
63+
allowed = [owner]
64+
if actor not in allowed:
65+
print(f"::error::User '{actor}' is not allowed to run releases. "
66+
f"Allowed: {allowed}. Set repository variable RELEASE_ALLOWED_ACTORS.")
67+
raise SystemExit(1)
68+
print(f"OK: {actor} is authorized to release.")
69+
PY
70+
71+
- name: Require automation PAT
72+
env:
73+
RELEASE_AUTOMATION_PAT: ${{ secrets.RELEASE_AUTOMATION_PAT }}
74+
run: |
75+
if [ -z "${RELEASE_AUTOMATION_PAT:-}" ]; then
76+
echo "::error::RELEASE_AUTOMATION_PAT secret is required."
77+
exit 1
78+
fi
79+
80+
- name: Checkout repo
81+
uses: actions/checkout@v4
82+
with:
83+
fetch-depth: 0
84+
token: ${{ secrets.RELEASE_AUTOMATION_PAT }}
85+
86+
- name: Configure git
87+
run: |
88+
git config user.name "github-actions[bot]"
89+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
90+
91+
- name: Merge dev into main
92+
run: |
93+
set -euo pipefail
94+
git fetch origin main dev
95+
96+
# Check that dev is ahead of main
97+
BEHIND=$(git rev-list --count origin/main..origin/dev)
98+
if [ "$BEHIND" = "0" ]; then
99+
echo "::warning::dev has no new commits over main. Continuing with version bump only."
100+
fi
101+
102+
git checkout main
103+
git reset --hard origin/main
104+
105+
# Merge dev into main (non-interactive)
106+
echo "Merging origin/dev into main..."
107+
git merge origin/dev --no-edit -m "Merge branch 'dev' into main for release"
108+
109+
if [ "${{ inputs.dry_run }}" = "true" ]; then
110+
echo "::notice::DRY RUN — would merge dev into main"
111+
echo "Commits from dev:"
112+
git log origin/main..HEAD --oneline
113+
else
114+
git push origin main
115+
echo "Pushed merged main."
116+
fi
117+
118+
- name: Install Rust (for Cargo.lock refresh)
119+
uses: dtolnay/rust-toolchain@stable
120+
121+
- name: Bump version
122+
id: ver
123+
env:
124+
RELEASE_TYPE: ${{ inputs.release_type }}
125+
run: |
126+
set -euo pipefail
127+
CURRENT=$(python3 -c "import json; print(json.load(open('apps/desktop/src-tauri/tauri.conf.json'))['version'])")
128+
export CURRENT
129+
NEW=$(python3 - <<'PY'
130+
import os
131+
cur = os.environ["CURRENT"]
132+
rt = os.environ["RELEASE_TYPE"]
133+
major, minor, patch = map(int, cur.split("."))
134+
if rt == "patch":
135+
print(f"{major}.{minor}.{patch + 1}")
136+
elif rt == "minor":
137+
print(f"{major}.{minor + 1}.0")
138+
elif rt == "major":
139+
print(f"{major + 1}.0.0")
140+
else:
141+
raise SystemExit(1)
142+
PY
143+
)
144+
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
145+
echo "new=$NEW" >> "$GITHUB_OUTPUT"
146+
echo "Bumping $CURRENT -> $NEW ($RELEASE_TYPE)"
147+
python3 scripts/bump-version.py "$NEW"
148+
149+
- name: Refresh Cargo.lock
150+
run: cargo generate-lockfile
151+
152+
- name: Commit version bump
153+
env:
154+
NEW: ${{ steps.ver.outputs.new }}
155+
run: |
156+
set -euo pipefail
157+
git add apps/desktop/src-tauri/tauri.conf.json \
158+
apps/desktop/src-tauri/Cargo.toml \
159+
apps/desktop/src/components/settings/Settings.tsx \
160+
Cargo.lock
161+
git commit -m "chore: bump version to $NEW"
162+
163+
if [ "${{ inputs.dry_run }}" = "true" ]; then
164+
echo "::notice::DRY RUN — would commit and push version bump"
165+
else
166+
git push origin main
167+
echo "Pushed version bump to main."
168+
fi
169+
170+
- name: Create and push tag
171+
id: tag
172+
env:
173+
NEW: ${{ steps.ver.outputs.new }}
174+
run: |
175+
set -euo pipefail
176+
TAG="v${NEW}"
177+
178+
# Check tag doesn't already exist
179+
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q .; then
180+
echo "::error::Tag $TAG already exists on remote. Delete it first or use a different version."
181+
exit 1
182+
fi
183+
184+
git tag -a "$TAG" -m "Release $TAG"
185+
186+
if [ "${{ inputs.dry_run }}" = "true" ]; then
187+
echo "::notice::DRY RUN — would push tag $TAG (triggers release.yml)"
188+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
189+
else
190+
git push origin "$TAG"
191+
echo "Pushed tag $TAG — release.yml will now build and publish."
192+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
193+
fi
194+
195+
- name: Summary
196+
env:
197+
NEW: ${{ steps.ver.outputs.new }}
198+
CURRENT: ${{ steps.ver.outputs.current }}
199+
TAG: ${{ steps.tag.outputs.tag }}
200+
DRY_RUN: ${{ inputs.dry_run }}
201+
run: |
202+
if [ "$DRY_RUN" = "true" ]; then
203+
echo "### Dry run complete" >> "$GITHUB_STEP_SUMMARY"
204+
echo "Would have:" >> "$GITHUB_STEP_SUMMARY"
205+
else
206+
echo "### Release pipeline complete" >> "$GITHUB_STEP_SUMMARY"
207+
fi
208+
echo "- Merged \`dev\` into \`main\`" >> "$GITHUB_STEP_SUMMARY"
209+
echo "- Bumped version: \`$CURRENT\` -> \`$NEW\`" >> "$GITHUB_STEP_SUMMARY"
210+
echo "- Pushed tag: \`$TAG\`" >> "$GITHUB_STEP_SUMMARY"
211+
if [ "$DRY_RUN" != "true" ]; then
212+
echo "" >> "$GITHUB_STEP_SUMMARY"
213+
echo "**Next:** [release.yml](${{ github.server_url }}/${{ github.repository }}/actions/workflows/release.yml) builds and publishes the release, then auto-syncs \`main\` back to \`dev\`." >> "$GITHUB_STEP_SUMMARY"
214+
fi

.github/workflows/release.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,71 @@ jobs:
407407
gh release upload "v${VERSION}" latest.json \
408408
--repo "$GITHUB_REPOSITORY" \
409409
--clobber
410+
411+
# ── Sync main back to dev after release ─────────────────────────────────
412+
sync-dev:
413+
runs-on: ubuntu-latest
414+
needs: [publish-latest-json]
415+
if: always() && needs.publish-latest-json.result == 'success'
416+
permissions:
417+
contents: write
418+
pull-requests: write
419+
420+
steps:
421+
- uses: actions/checkout@v4
422+
with:
423+
fetch-depth: 0
424+
token: ${{ secrets.RELEASE_AUTOMATION_PAT || github.token }}
425+
426+
- name: Configure git
427+
run: |
428+
git config user.name "github-actions[bot]"
429+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
430+
431+
- name: Merge main into dev
432+
env:
433+
GH_TOKEN: ${{ secrets.RELEASE_AUTOMATION_PAT || github.token }}
434+
run: |
435+
set -euo pipefail
436+
437+
# Check if dev branch exists before fetching (fetch fails if branch is missing)
438+
if ! git ls-remote --heads origin dev | grep -q .; then
439+
echo "::notice::No dev branch found — skipping sync."
440+
exit 0
441+
fi
442+
443+
git fetch origin main dev
444+
445+
git checkout dev
446+
git reset --hard origin/dev
447+
448+
# Check if main has anything new for dev
449+
AHEAD=$(git rev-list --count origin/dev..origin/main)
450+
if [ "$AHEAD" = "0" ]; then
451+
echo "dev is already up to date with main."
452+
exit 0
453+
fi
454+
455+
# Try fast-forward merge first, fall back to merge commit
456+
if git merge origin/main --ff-only 2>/dev/null; then
457+
echo "Fast-forwarded dev to main."
458+
git push origin dev
459+
elif git merge origin/main --no-edit -m "chore: sync main back to dev after release"; then
460+
echo "Merged main into dev (merge commit)."
461+
git push origin dev
462+
else
463+
echo "::warning::Auto-merge of main into dev failed (conflicts). Creating PR instead."
464+
# Create a PR for manual resolution
465+
BRANCH="chore/sync-main-to-dev-$(date -u +%Y%m%d-%H%M%S)-${GITHUB_RUN_ID}"
466+
git merge --abort || true
467+
git checkout -b "$BRANCH" origin/main
468+
git push -u origin "$BRANCH"
469+
gh pr create \
470+
--base dev \
471+
--head "$BRANCH" \
472+
--title "chore: sync main back to dev after release" \
473+
--body "Automated post-release sync. Main has changes (version bump + release tag) that need to be merged back into dev. Resolve any conflicts and merge."
474+
fi
475+
476+
- name: Summary
477+
run: echo "### main synced back to dev" >> "$GITHUB_STEP_SUMMARY"

0 commit comments

Comments
 (0)