Skip to content

Commit 5f96eea

Browse files
authored
Merge pull request #3 from NexusGPU/codex/manual-release-flow
fix: manual release flow
2 parents c75a019 + 3515db5 commit 5f96eea

5 files changed

Lines changed: 373 additions & 4 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ name: Build
22

33
on:
44
push:
5-
branches: [main, master]
65
pull_request:
7-
branches: [main, master]
86
workflow_dispatch:
97

108
permissions:
@@ -68,7 +66,7 @@ jobs:
6866
name: Semantic Release
6967
runs-on: ubuntu-latest
7068
needs: [test, lint]
71-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
69+
if: github.event_name == 'workflow_dispatch'
7270
outputs:
7371
published: ${{ steps.semantic.outputs.new_release_published }}
7472
version: ${{ steps.semantic.outputs.new_release_version }}
@@ -101,7 +99,7 @@ jobs:
10199
name: Build & Release
102100
runs-on: ubuntu-latest
103101
needs: semantic-release
104-
if: needs.semantic-release.outputs.published == 'true'
102+
if: needs.semantic-release.outputs.published == 'true' && github.event_name == 'workflow_dispatch'
105103
steps:
106104
- name: Checkout
107105
uses: actions/checkout@v4
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Manual Release Flow Implementation Plan
2+
3+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4+
5+
**Goal:** Gate tagging and R2 uploads to manual workflow_dispatch runs while keeping build/lint/test on all branches, and add a gh CLI trigger script.
6+
7+
**Architecture:** Add lightweight bash tests to assert the build workflow’s triggers and release gating. Refactor `.github/workflows/build.yml` to remove branch filters and to run semantic-release/release only on workflow_dispatch. Add a CLI helper script that wraps `gh workflow run` for manual dispatch.
8+
9+
**Tech Stack:** GitHub Actions YAML, Bash, gh CLI
10+
11+
---
12+
13+
### Task 1: Assert build workflow gating and refactor triggers
14+
15+
**Files:**
16+
- Create: `scripts/build-workflow.test.sh`
17+
- Modify: `.github/workflows/build.yml`
18+
19+
**Step 1: Write the failing test**
20+
21+
```bash
22+
#!/usr/bin/env bash
23+
set -euo pipefail
24+
25+
workflow=".github/workflows/build.yml"
26+
27+
if [[ ! -f "$workflow" ]]; then
28+
echo "Missing $workflow" >&2
29+
exit 1
30+
fi
31+
32+
if grep -qE '^[[:space:]]+branches:' "$workflow"; then
33+
echo "Expected no branch filters in build workflow" >&2
34+
exit 1
35+
fi
36+
37+
semantic_block=$(awk '
38+
$1=="semantic-release:" {in=1; next}
39+
in && /^[^[:space:]]/ {in=0}
40+
in {print}
41+
' "$workflow")
42+
43+
if ! grep -q "if: github.event_name == 'workflow_dispatch'" <<<"$semantic_block"; then
44+
echo "Expected semantic-release gated by workflow_dispatch" >&2
45+
exit 1
46+
fi
47+
48+
release_block=$(awk '
49+
$1=="release:" {in=1; next}
50+
in && /^[^[:space:]]/ {in=0}
51+
in {print}
52+
' "$workflow")
53+
54+
if ! grep -q "needs.semantic-release.outputs.published == 'true'" <<<"$release_block"; then
55+
echo "Expected release job to depend on semantic-release published output" >&2
56+
exit 1
57+
fi
58+
59+
if ! grep -q "github.event_name == 'workflow_dispatch'" <<<"$release_block"; then
60+
echo "Expected release job gated by workflow_dispatch" >&2
61+
exit 1
62+
fi
63+
64+
echo "OK"
65+
```
66+
67+
**Step 2: Run test to verify it fails**
68+
69+
Run: `bash scripts/build-workflow.test.sh`
70+
71+
Expected: FAIL because branch filters exist and semantic-release is not gated by workflow_dispatch.
72+
73+
**Step 3: Write minimal implementation**
74+
75+
Update `.github/workflows/build.yml`:
76+
- Remove `branches:` filters under `push` and `pull_request`.
77+
- Set `semantic-release` job to `if: github.event_name == 'workflow_dispatch'`.
78+
- Gate `release` job with `if: needs.semantic-release.outputs.published == 'true' && github.event_name == 'workflow_dispatch'`.
79+
80+
**Step 4: Run test to verify it passes**
81+
82+
Run: `bash scripts/build-workflow.test.sh`
83+
84+
Expected: PASS
85+
86+
**Step 5: Commit**
87+
88+
```bash
89+
git add scripts/build-workflow.test.sh .github/workflows/build.yml
90+
git commit -m "ci: gate releases behind manual workflow_dispatch"
91+
```
92+
93+
---
94+
95+
### Task 2: Add gh CLI trigger script (TDD)
96+
97+
**Files:**
98+
- Create: `scripts/trigger-release.test.sh`
99+
- Create: `scripts/trigger-release.sh`
100+
101+
**Step 1: Write the failing test**
102+
103+
```bash
104+
#!/usr/bin/env bash
105+
set -euo pipefail
106+
107+
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
108+
script="$root/scripts/trigger-release.sh"
109+
110+
tmp="$(mktemp -d)"
111+
trap 'rm -rf "$tmp"' EXIT
112+
113+
stub="$tmp/gh"
114+
cat > "$stub" <<'STUB'
115+
#!/usr/bin/env bash
116+
echo "$@" > "$CAPTURE_FILE"
117+
STUB
118+
chmod +x "$stub"
119+
120+
export GH_BIN="$stub"
121+
122+
CAPTURE_FILE="$tmp/args1" "$script" --ref feature/test --workflow build.yml
123+
expected="workflow run build.yml --ref feature/test"
124+
got="$(cat "$tmp/args1")"
125+
if [[ "$got" != "$expected" ]]; then
126+
echo "expected: $expected" >&2
127+
echo "got: $got" >&2
128+
exit 1
129+
fi
130+
131+
CAPTURE_FILE="$tmp/args2" "$script" --workflow build.yml
132+
expected="workflow run build.yml --ref main"
133+
got="$(cat "$tmp/args2")"
134+
if [[ "$got" != "$expected" ]]; then
135+
echo "expected: $expected" >&2
136+
echo "got: $got" >&2
137+
exit 1
138+
fi
139+
140+
echo "OK"
141+
```
142+
143+
**Step 2: Run test to verify it fails**
144+
145+
Run: `bash scripts/trigger-release.test.sh`
146+
147+
Expected: FAIL because `scripts/trigger-release.sh` does not exist.
148+
149+
**Step 3: Write minimal implementation**
150+
151+
Create `scripts/trigger-release.sh`:
152+
153+
```bash
154+
#!/usr/bin/env bash
155+
set -euo pipefail
156+
157+
usage() {
158+
cat <<'USAGE'
159+
Usage: scripts/trigger-release.sh [--ref <git-ref>] [--workflow <workflow-file>]
160+
161+
Triggers the manual release workflow via gh CLI.
162+
163+
Options:
164+
--ref, -r Git ref to run the workflow on (default: main)
165+
--workflow, -w Workflow file name (default: build.yml)
166+
--help, -h Show help
167+
USAGE
168+
}
169+
170+
ref="main"
171+
workflow="build.yml"
172+
173+
while [[ $# -gt 0 ]]; do
174+
case "$1" in
175+
-r|--ref)
176+
ref="$2"
177+
shift 2
178+
;;
179+
-w|--workflow)
180+
workflow="$2"
181+
shift 2
182+
;;
183+
-h|--help)
184+
usage
185+
exit 0
186+
;;
187+
*)
188+
echo "Unknown argument: $1" >&2
189+
usage
190+
exit 1
191+
;;
192+
esac
193+
done
194+
195+
gh_bin="${GH_BIN:-gh}"
196+
if command -v "$gh_bin" >/dev/null 2>&1; then
197+
gh_cmd="$gh_bin"
198+
elif [[ -x "$gh_bin" ]]; then
199+
gh_cmd="$gh_bin"
200+
else
201+
echo "gh CLI not found. Install from https://cli.github.com" >&2
202+
exit 1
203+
fi
204+
205+
repo_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
206+
if [[ -z "$repo_root" ]]; then
207+
echo "Not inside a git repository." >&2
208+
exit 1
209+
fi
210+
211+
cd "$repo_root"
212+
"$gh_cmd" workflow run "$workflow" --ref "$ref"
213+
214+
echo "Triggered $workflow on ref $ref"
215+
```
216+
217+
Make it executable: `chmod +x scripts/trigger-release.sh`
218+
219+
**Step 4: Run test to verify it passes**
220+
221+
Run: `bash scripts/trigger-release.test.sh`
222+
223+
Expected: PASS
224+
225+
**Step 5: Commit**
226+
227+
```bash
228+
git add scripts/trigger-release.test.sh scripts/trigger-release.sh
229+
git commit -m "scripts: add gh workflow dispatch helper"
230+
```

scripts/build-workflow.test.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
workflow=".github/workflows/build.yml"
5+
6+
if [[ ! -f "$workflow" ]]; then
7+
echo "Missing $workflow" >&2
8+
exit 1
9+
fi
10+
11+
if grep -qE '^[[:space:]]+branches:' "$workflow"; then
12+
echo "Expected no branch filters in build workflow" >&2
13+
exit 1
14+
fi
15+
16+
semantic_block=$(awk '
17+
$1=="semantic-release:" {inside=1; next}
18+
inside && /^[^[:space:]]/ {inside=0}
19+
inside {print}
20+
' "$workflow")
21+
22+
if ! grep -q "if: github.event_name == 'workflow_dispatch'" <<<"$semantic_block"; then
23+
echo "Expected semantic-release gated by workflow_dispatch" >&2
24+
exit 1
25+
fi
26+
27+
release_block=$(awk '
28+
$1=="release:" {inside=1; next}
29+
inside && /^[^[:space:]]/ {inside=0}
30+
inside {print}
31+
' "$workflow")
32+
33+
if ! grep -q "needs.semantic-release.outputs.published == 'true'" <<<"$release_block"; then
34+
echo "Expected release job to depend on semantic-release published output" >&2
35+
exit 1
36+
fi
37+
38+
if ! grep -q "github.event_name == 'workflow_dispatch'" <<<"$release_block"; then
39+
echo "Expected release job gated by workflow_dispatch" >&2
40+
exit 1
41+
fi
42+
43+
echo "OK"

scripts/trigger-release.sh

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
usage() {
5+
cat <<'USAGE'
6+
Usage: scripts/trigger-release.sh [--ref <git-ref>] [--workflow <workflow-file>]
7+
8+
Triggers the manual release workflow via gh CLI.
9+
10+
Options:
11+
--ref, -r Git ref to run the workflow on (default: main)
12+
--workflow, -w Workflow file name (default: build.yml)
13+
--help, -h Show help
14+
USAGE
15+
}
16+
17+
ref="main"
18+
workflow="build.yml"
19+
20+
while [[ $# -gt 0 ]]; do
21+
case "$1" in
22+
-r|--ref)
23+
ref="$2"
24+
shift 2
25+
;;
26+
-w|--workflow)
27+
workflow="$2"
28+
shift 2
29+
;;
30+
-h|--help)
31+
usage
32+
exit 0
33+
;;
34+
*)
35+
echo "Unknown argument: $1" >&2
36+
usage
37+
exit 1
38+
;;
39+
esac
40+
done
41+
42+
gh_bin="${GH_BIN:-gh}"
43+
if command -v "$gh_bin" >/dev/null 2>&1; then
44+
gh_cmd="$gh_bin"
45+
elif [[ -x "$gh_bin" ]]; then
46+
gh_cmd="$gh_bin"
47+
else
48+
echo "gh CLI not found. Install from https://cli.github.com" >&2
49+
exit 1
50+
fi
51+
52+
repo_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
53+
if [[ -z "$repo_root" ]]; then
54+
echo "Not inside a git repository." >&2
55+
exit 1
56+
fi
57+
58+
cd "$repo_root"
59+
"$gh_cmd" workflow run "$workflow" --ref "$ref"
60+
61+
echo "Triggered $workflow on ref $ref"

0 commit comments

Comments
 (0)