Skip to content

Commit e9c9915

Browse files
feat(ci): add automated dev release workflow (#1217)
* feat(ci): add automated dev release workflow * fix(ci): revert hardcoded prerelease: true — restore conditional for stable releases * fix(ci): handle cancelled CI and use robust self-exclusion filter Address two Greptile review findings: 1. Add `cancelled` and `startup_failure` to the CI failure pattern. Previously, a cancelled CI run would not block a dev release. 2. Replace fragile hardcoded job name strings with check_suite.id filtering. The old filter relied on exact job name matches ("Pre-flight checks", "Create dev release") which would break if jobs were renamed. Now uses GITHUB_RUN_ID to get the current run's check suite ID and exclude all check runs from it. Co-authored-by: Bob <bob@superuserlabs.org> * fix(ci): re-enable dependabot submodule updates * fix(ci): simplify dev-release tag checkout * fix(ci): pin dev release tag to verified SHA * fix(ci): reduce submodule dependabot noise * fix(ci): replace Python heredoc with pure bash for version bump The <<'PY' heredoc terminator was indented inside the YAML run: | block. YAML literal blocks strip leading whitespace but heredoc terminators must appear at column 0 in bash (only <<- strips leading tabs, not spaces). Replace with equivalent pure bash arithmetic — no Python dependency needed for simple semver bumping. * fix(ci): fix --jq/--arg flag order, biweekly parity, trailing newline - Move --arg before --jq so gh api receives the correct jq expression instead of consuming '--arg' as the expression (causing silent failure and conclusions always being 'unknown') - Replace ISO week parity with reference-date calculation to avoid 3-week gaps at year boundaries (ISO week resets in Dec/Jan) - Add trailing newline at end of file * fix(ci): add missing permissions and use PAT for tag push - Add actions: read and checks: read to permissions block so the preflight CI gate can call /actions/runs/{id} and /commits/{sha}/check-runs - Switch create-tag checkout from GITHUB_TOKEN to AWBOT_GH_TOKEN so the pushed dev release tag actually triggers build.yml and build-tauri.yml (GITHUB_TOKEN-originated pushes are blocked from spawning new workflow runs) * fix(ci): add ref: master to preflight checkout to prevent non-master dispatch tags
1 parent 5548a0b commit e9c9915

4 files changed

Lines changed: 209 additions & 9 deletions

File tree

.github/dependabot.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ updates:
77
schedule:
88
interval: "monthly"
99

10-
# Maintain submodule versions
11-
# NOTE: too noisy, easier to update by hand
12-
#- package-ecosystem: "gitsubmodule"
13-
# directory: "/"
14-
# schedule:
15-
# interval: "monthly"
10+
# Maintain submodule versions so module releases propagate into the meta-repo
11+
- package-ecosystem: "gitsubmodule"
12+
directory: "/"
13+
schedule:
14+
interval: "monthly"
1615

1716
# Maintain dependencies for pip/poetry
1817
# NOTE: too noisy, easier to update by hand

.github/workflows/build-tauri.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ jobs:
5151
echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> $GITHUB_ENV
5252
echo "TAURI_BUILD=true" >> $GITHUB_ENV
5353
54+
- name: Set tag metadata
55+
if: startsWith(github.ref, 'refs/tags/v')
56+
run: |
57+
echo "VERSION_TAG=${GITHUB_REF_NAME}" >> $GITHUB_ENV
58+
5459
- name: Set up Python
5560
uses: actions/setup-python@v5
5661
with:
@@ -160,7 +165,7 @@ jobs:
160165
161166
make dist/notarize
162167
fi
163-
mv dist/ActivityWatch.dmg dist/activitywatch-$(scripts/package/getversion.sh)-macos-x86_64.dmg
168+
mv dist/ActivityWatch.dmg dist/activitywatch-${VERSION_TAG:-$(scripts/package/getversion.sh)}-macos-x86_64.dmg
164169
env:
165170
APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }}
166171
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
@@ -242,4 +247,4 @@ jobs:
242247
draft: true
243248
files: dist/*/activitywatch-*.*
244249
body_path: dist/release_notes_tauri/release_notes.md
245-
prerelease: ${{ !(steps.version.outputs.is_stable == 'true') }}
250+
prerelease: ${{ !(steps.version.outputs.is_stable == 'true') }} # must compare to true, since boolean outputs are actually just strings, and "false" is truthy since it's not empty: https://github.com/actions/runner/issues/1483#issuecomment-994986996

.github/workflows/build.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ jobs:
5252
run: |
5353
echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> $GITHUB_ENV
5454
55+
- name: Set tag metadata
56+
if: startsWith(github.ref, 'refs/tags/v')
57+
run: |
58+
echo "VERSION_TAG=${GITHUB_REF_NAME}" >> $GITHUB_ENV
59+
5560
- name: Set up Python
5661
uses: actions/setup-python@v5
5762
with:
@@ -173,7 +178,7 @@ jobs:
173178
# Notarize
174179
make dist/notarize
175180
fi
176-
mv dist/ActivityWatch.dmg dist/activitywatch-$(scripts/package/getversion.sh)-macos-x86_64.dmg
181+
mv dist/ActivityWatch.dmg dist/activitywatch-${VERSION_TAG:-$(scripts/package/getversion.sh)}-macos-x86_64.dmg
177182
env:
178183
APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }}
179184
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}

.github/workflows/dev-release.yml

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
name: Create dev release
2+
3+
# Create prerelease tags on a schedule (every other Thursday) or manually.
4+
# The existing build workflows already know how to package tag builds and create
5+
# draft GitHub prereleases, so this workflow only needs to decide whether a new
6+
# prerelease is warranted and push the next prerelease tag.
7+
8+
on:
9+
schedule:
10+
- cron: '0 12 * * 4'
11+
workflow_dispatch:
12+
inputs:
13+
release_line:
14+
description: 'Release line to prerelease from'
15+
required: true
16+
default: patch
17+
type: choice
18+
options:
19+
- patch
20+
- minor
21+
22+
permissions:
23+
contents: write
24+
actions: read # needed for /actions/runs/{run_id} (check_suite_id lookup)
25+
checks: read # needed for /commits/{sha}/check-runs
26+
27+
concurrency:
28+
group: dev-release
29+
cancel-in-progress: false
30+
31+
jobs:
32+
preflight:
33+
name: Pre-flight checks
34+
runs-on: ubuntu-latest
35+
outputs:
36+
should_release: ${{ steps.preflight.outputs.should_release }}
37+
next_tag: ${{ steps.preflight.outputs.next_tag }}
38+
since_ref: ${{ steps.preflight.outputs.since_ref }}
39+
commits_since_ref: ${{ steps.preflight.outputs.commits_since_ref }}
40+
head_sha: ${{ steps.preflight.outputs.head_sha }}
41+
steps:
42+
- uses: actions/checkout@v4
43+
with:
44+
ref: master # explicit: prevent workflow_dispatch from a non-master branch tagging the wrong commit
45+
fetch-depth: 0
46+
47+
- name: Decide whether to create a dev release
48+
id: preflight
49+
env:
50+
GH_TOKEN: ${{ github.token }}
51+
RELEASE_LINE: ${{ github.event.inputs.release_line || 'patch' }}
52+
run: |
53+
set -euo pipefail
54+
55+
if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then
56+
# Use a fixed reference Thursday to compute biweekly parity, avoiding
57+
# ISO week resets at year boundaries (which cause a 3-week gap in Dec/Jan).
58+
ref_epoch=$(date -d "2024-01-04" +%s) # a known even-week Thursday
59+
now_epoch=$(date -u +%s)
60+
weeks_since=$(( (now_epoch - ref_epoch) / 604800 ))
61+
if [ $((weeks_since % 2)) -eq 1 ]; then
62+
echo "Skipping this week to keep the cadence biweekly."
63+
echo "should_release=false" >> "$GITHUB_OUTPUT"
64+
exit 0
65+
fi
66+
fi
67+
68+
bump_version() {
69+
local version="$1" release_line="$2"
70+
IFS='.' read -r major minor patch <<< "$version"
71+
if [ "$release_line" = "minor" ]; then
72+
minor=$((minor + 1))
73+
patch=0
74+
else
75+
patch=$((patch + 1))
76+
fi
77+
echo "${major}.${minor}.${patch}"
78+
}
79+
80+
latest_stable=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true)
81+
if [ -z "$latest_stable" ]; then
82+
echo "No stable tag found, refusing to create prerelease tags."
83+
echo "should_release=false" >> "$GITHUB_OUTPUT"
84+
exit 0
85+
fi
86+
87+
next_base=$(bump_version "${latest_stable#v}" "$RELEASE_LINE")
88+
prerelease_pattern="^v${next_base//./\\.}b[0-9]+$"
89+
last_prerelease=$(git tag --sort=-version:refname | grep -E "$prerelease_pattern" | head -1 || true)
90+
91+
if [ -n "$last_prerelease" ]; then
92+
since_ref="$last_prerelease"
93+
last_prerelease_num=${last_prerelease##*b}
94+
next_tag="v${next_base}b$((last_prerelease_num + 1))"
95+
else
96+
since_ref="$latest_stable"
97+
next_tag="v${next_base}b1"
98+
fi
99+
100+
commits_since_ref=$(git rev-list "${since_ref}..HEAD" --count)
101+
echo "latest_stable=$latest_stable"
102+
echo "last_prerelease=${last_prerelease:-<none>}"
103+
echo "since_ref=$since_ref"
104+
echo "next_tag=$next_tag"
105+
echo "commits_since_ref=$commits_since_ref"
106+
107+
if [ "$commits_since_ref" -eq 0 ]; then
108+
echo "No new commits since $since_ref, skipping dev release."
109+
echo "should_release=false" >> "$GITHUB_OUTPUT"
110+
exit 0
111+
fi
112+
113+
head_sha=$(git rev-parse HEAD)
114+
115+
# Get the current workflow run's check suite ID so we can exclude
116+
# our own check runs without relying on fragile job name strings
117+
current_suite_id=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
118+
--jq '.check_suite_id' 2>/dev/null || echo "0")
119+
120+
conclusions=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${head_sha}/check-runs" \
121+
--paginate \
122+
--arg suite "$current_suite_id" \
123+
--jq '[.check_runs[] | select(
124+
.app.slug == "github-actions" and
125+
((.check_suite.id | tostring) != $suite)
126+
)] | map(.conclusion) | unique | .[]' 2>/dev/null || echo unknown)
127+
128+
echo "CI conclusions: $conclusions"
129+
130+
if echo "$conclusions" | grep -qE 'failure|action_required|timed_out|cancelled|startup_failure'; then
131+
echo "CI has failures on HEAD, skipping dev release."
132+
echo "should_release=false" >> "$GITHUB_OUTPUT"
133+
exit 0
134+
fi
135+
136+
if echo "$conclusions" | grep -qE 'null|pending'; then
137+
echo "CI is still running on HEAD, skipping dev release."
138+
echo "should_release=false" >> "$GITHUB_OUTPUT"
139+
exit 0
140+
fi
141+
142+
if [ -z "$conclusions" ] || [ "$conclusions" = "unknown" ]; then
143+
echo "CI status unavailable on HEAD, skipping dev release."
144+
echo "should_release=false" >> "$GITHUB_OUTPUT"
145+
exit 0
146+
fi
147+
148+
if ! echo "$conclusions" | grep -q 'success'; then
149+
echo "No successful CI checks found on HEAD, skipping dev release."
150+
echo "should_release=false" >> "$GITHUB_OUTPUT"
151+
exit 0
152+
fi
153+
154+
echo "should_release=true" >> "$GITHUB_OUTPUT"
155+
echo "next_tag=$next_tag" >> "$GITHUB_OUTPUT"
156+
echo "since_ref=$since_ref" >> "$GITHUB_OUTPUT"
157+
echo "commits_since_ref=$commits_since_ref" >> "$GITHUB_OUTPUT"
158+
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
159+
160+
create-tag:
161+
name: Create dev release tag
162+
needs: preflight
163+
if: needs.preflight.outputs.should_release == 'true'
164+
runs-on: ubuntu-latest
165+
steps:
166+
- uses: actions/checkout@v4
167+
with:
168+
ref: ${{ needs.preflight.outputs.head_sha }}
169+
fetch-depth: 1
170+
token: ${{ secrets.AWBOT_GH_TOKEN }} # PAT required — GITHUB_TOKEN cannot trigger downstream tag-based workflows
171+
172+
- name: Configure git
173+
run: |
174+
git config user.name "github-actions[bot]"
175+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
176+
177+
- name: Create and push prerelease tag
178+
run: |
179+
set -euo pipefail
180+
tag="${{ needs.preflight.outputs.next_tag }}"
181+
git tag -a "$tag" -m "Development prerelease $tag"
182+
git push origin "$tag"
183+
{
184+
echo "## Dev release created"
185+
echo ""
186+
echo "- Tag: \`$tag\`"
187+
echo "- Changes since: \`${{ needs.preflight.outputs.since_ref }}\`"
188+
echo "- Commits: \`${{ needs.preflight.outputs.commits_since_ref }}\`"
189+
echo ""
190+
echo "The existing tag-triggered build workflows will now build artifacts and create/update the draft prerelease."
191+
} >> "$GITHUB_STEP_SUMMARY"

0 commit comments

Comments
 (0)