Skip to content

Commit 08dc6ec

Browse files
jahoomaclaude
andcommitted
Run freebuff Windows build + smoke on every push
The tree-sitter wasm regression that crashed freebuff 0.0.62 only manifested on real Windows. CI was Linux-only, macOS dev machines behaved fine, and the Windows binary was only built+smoked at release time (cli-release-build.yml). So the bug shipped twice before being caught by user reports. Add a windows-latest job to freebuff-e2e.yml that builds the freebuff binary natively on Windows and runs the long smoke test against it. The full tmux-based e2e matrix can't follow — Windows runners don't ship tmux, and porting tmuxStart/tmuxSend would be substantial — but smoke-binary.ts catches the failure mode that bit us: it spawns the binary, waits long enough for the late renderer-cleanup rejection handler to fire, and asserts both that no fatal markers appeared and that the boot screen actually rendered. Mirrors the Windows-specific bits from cli-release-build.yml's build-windows-binary job: explicit `bun install --cwd cli` and the @OpenTui workspace symlink fix, both needed because bun workspace linking doesn't work reliably on Windows runners. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7918c2a commit 08dc6ec

1 file changed

Lines changed: 124 additions & 0 deletions

File tree

.github/workflows/freebuff-e2e.yml

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,130 @@ jobs:
5555
path: cli/bin/freebuff
5656
retention-days: 1
5757

58+
# Windows-native build + smoke. The full tmux-based e2e matrix below can't
59+
# run here (Windows runners don't have tmux), but the smoke-binary.ts
60+
# check is what would have caught the post-OpenTUI-upgrade tree-sitter
61+
# wasm regression: that bug only manifested on real Windows, while CI was
62+
# Linux-only and macOS dev machines saw it work. Now every push gets a
63+
# real Windows boot test.
64+
build-and-smoke-freebuff-windows:
65+
runs-on: windows-latest
66+
timeout-minutes: 20
67+
steps:
68+
- name: Checkout repository
69+
uses: actions/checkout@v6
70+
71+
- uses: ./.github/actions/setup-project
72+
73+
- name: Ensure CLI dependencies
74+
run: bun install --frozen-lockfile --cwd cli
75+
shell: bash
76+
77+
# Mirror the symlink fix from cli-release-build.yml's Windows job: bun
78+
# workspace symlinks aren't created reliably on Windows runners, so
79+
# the cli's @opentui imports need explicit junctions to the root
80+
# @opentui packages.
81+
- name: Fix OpenTUI module symlinks
82+
shell: bash
83+
run: |
84+
set -euo pipefail
85+
bun - <<'BUN'
86+
import fs from 'fs';
87+
import path from 'path';
88+
89+
const rootDir = process.cwd();
90+
const rootOpenTui = path.join(rootDir, 'node_modules', '@opentui');
91+
const cliNodeModules = path.join(rootDir, 'cli', 'node_modules');
92+
const cliOpenTui = path.join(cliNodeModules, '@opentui');
93+
94+
if (!fs.existsSync(rootOpenTui)) {
95+
console.log('Root @opentui packages missing; skipping fix');
96+
process.exit(0);
97+
}
98+
99+
fs.mkdirSync(cliOpenTui, { recursive: true });
100+
101+
const packages = ['core', 'react'];
102+
for (const pkg of packages) {
103+
const target = path.join(rootOpenTui, pkg);
104+
const link = path.join(cliOpenTui, pkg);
105+
106+
if (!fs.existsSync(target)) {
107+
console.log(`Target ${target} missing; skipping ${pkg}`);
108+
continue;
109+
}
110+
111+
let linkStats = null;
112+
try {
113+
linkStats = fs.lstatSync(link);
114+
} catch (error) {
115+
if (error?.code !== 'ENOENT') {
116+
throw error;
117+
}
118+
}
119+
120+
if (linkStats) {
121+
let alreadyLinked = false;
122+
try {
123+
const actual = fs.realpathSync(link);
124+
alreadyLinked = actual === target;
125+
} catch {
126+
// Broken symlink or unreadable target; we'll replace it.
127+
}
128+
129+
if (alreadyLinked) {
130+
continue;
131+
}
132+
133+
fs.rmSync(link, { recursive: true, force: true });
134+
}
135+
136+
const type = process.platform === 'win32' ? 'junction' : 'dir';
137+
try {
138+
fs.symlinkSync(target, link, type);
139+
console.log(`Linked ${link} -> ${target}`);
140+
} catch (error) {
141+
if (error?.code === 'EEXIST') {
142+
fs.rmSync(link, { recursive: true, force: true });
143+
fs.symlinkSync(target, link, type);
144+
console.log(`Re-linked ${link} -> ${target}`);
145+
} else {
146+
throw error;
147+
}
148+
}
149+
}
150+
BUN
151+
152+
- name: Set environment variables
153+
env:
154+
SECRETS_CONTEXT: ${{ toJSON(secrets) }}
155+
shell: bash
156+
run: |
157+
VAR_NAMES=$(bun scripts/generate-ci-env.ts --scope client)
158+
echo "$SECRETS_CONTEXT" | jq -r --argjson vars "$VAR_NAMES" '
159+
to_entries | .[] | select(.key as $k | $vars | index($k)) | .key + "=" + .value
160+
' >> $GITHUB_ENV
161+
echo "FREEBUFF_MODE=true" >> $GITHUB_ENV
162+
echo "NEXT_PUBLIC_CB_ENVIRONMENT=prod" >> $GITHUB_ENV
163+
echo "CODEBUFF_GITHUB_ACTIONS=true" >> $GITHUB_ENV
164+
165+
- name: Build Freebuff binary
166+
run: bun freebuff/cli/build.ts 0.0.0-e2e
167+
shell: bash
168+
169+
- name: Smoke test binary
170+
shell: bash
171+
run: |
172+
# --version exits via commander synchronously and won't see async
173+
# startup failures (e.g. the Parser.init rejection from a broken
174+
# tree-sitter wasm load).
175+
./cli/bin/freebuff.exe --version
176+
# Run for several seconds so unhandled rejections during module
177+
# init have time to fire — the freebuff 0.0.62 wasm regression
178+
# surfaced through the *late* renderer-cleanup handler, after the
179+
# boot screen had rendered, so a too-short window can miss it.
180+
bun cli/scripts/smoke-binary.ts cli/bin/freebuff.exe
181+
58182
e2e:
59183
needs: build-freebuff
60184
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)