Skip to content

Commit e4dea3f

Browse files
agswindowclaude
andcommitted
fix(build): isolate subshell+redirect to bash helper for Windows
Bun Shell on Windows doesn't reliably handle `( ... ) > file` (subshell with stdout redirect) -- see oven-sh/bun#11066, #11968. The four such constructs in `package.json`'s `build`: ( git rev-parse HEAD 2>/dev/null || true ) > {browse,design,make-pdf}/dist/.version (rm -f .*.bun-build || true) silently break the `&&` chain mid-build on Windows. Symptoms: stale binaries with no `.version` stamp, leftover `.bun-build` temp files, and the post-build `chmod` step never runs. Move the four constructs into a new `scripts/stamp-versions.sh` -- same shell syntax, but invoked via `bash` so they never enter Bun Shell. Matches the existing pattern in `browse/scripts/build-node-server.sh` (which exists for a related bun-on-Windows reason). Update `test/build-script-shell-compat.test.ts` (D-1460): the assertion now accepts inline subshells OR a reference to `scripts/stamp-versions.sh`, with a file-existence guard so a rename/move can't silently break stamping. The anti-brace-group constraint is preserved. Verified on Windows 11 (bun 1.3.11): `bun run build` completes, all 5 binaries are rebuilt + `.version` stamped + temp files cleaned. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 026751e commit e4dea3f

3 files changed

Lines changed: 40 additions & 6 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"make-pdf": "./make-pdf/dist/pdf"
1010
},
1111
"scripts": {
12-
"build": "bun run vendor:xterm && bun run gen:skill-docs --host all; bun build --compile browse/src/cli.ts --outfile browse/dist/browse && bun build --compile browse/src/find-browse.ts --outfile browse/dist/find-browse && bun build --compile design/src/cli.ts --outfile design/dist/design && bun build --compile make-pdf/src/cli.ts --outfile make-pdf/dist/pdf && bun build --compile bin/gstack-global-discover.ts --outfile bin/gstack-global-discover && bash browse/scripts/build-node-server.sh && ( git rev-parse HEAD 2>/dev/null || true ) > browse/dist/.version && ( git rev-parse HEAD 2>/dev/null || true ) > design/dist/.version && ( git rev-parse HEAD 2>/dev/null || true ) > make-pdf/dist/.version && chmod +x browse/dist/browse browse/dist/find-browse design/dist/design make-pdf/dist/pdf bin/gstack-global-discover && (rm -f .*.bun-build || true)",
12+
"build": "bun run vendor:xterm && bun run gen:skill-docs --host all; bun build --compile browse/src/cli.ts --outfile browse/dist/browse && bun build --compile browse/src/find-browse.ts --outfile browse/dist/find-browse && bun build --compile design/src/cli.ts --outfile design/dist/design && bun build --compile make-pdf/src/cli.ts --outfile make-pdf/dist/pdf && bun build --compile bin/gstack-global-discover.ts --outfile bin/gstack-global-discover && bash browse/scripts/build-node-server.sh && bash scripts/stamp-versions.sh && chmod +x browse/dist/browse browse/dist/find-browse design/dist/design make-pdf/dist/pdf bin/gstack-global-discover",
1313
"vendor:xterm": "mkdir -p extension/lib && cp node_modules/xterm/lib/xterm.js extension/lib/xterm.js && cp node_modules/xterm/css/xterm.css extension/lib/xterm.css && cp node_modules/xterm-addon-fit/lib/xterm-addon-fit.js extension/lib/xterm-addon-fit.js",
1414
"dev:make-pdf": "bun run make-pdf/src/cli.ts",
1515
"dev:design": "bun run design/src/cli.ts",

scripts/stamp-versions.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
# Stamp git HEAD into per-binary .version files and clean up bun build
3+
# temp artifacts.
4+
#
5+
# Extracted from package.json `build` script because Bun Shell on Windows
6+
# doesn't handle `( ... ) > file` (subshell + stdout redirect) reliably
7+
# (oven-sh/bun#11066, #11968) — chained `&&` builds break midway,
8+
# leaving stale binaries with no version stamp. Real bash (Git Bash,
9+
# WSL, macOS, Linux) handles the same syntax without issue, so isolating
10+
# it in a `.sh` file keeps the build cross-platform.
11+
12+
set -e
13+
14+
GSTACK_DIR="$(cd "$(dirname "$0")/.." && pwd)"
15+
cd "$GSTACK_DIR"
16+
17+
for dir in browse design make-pdf; do
18+
( git rev-parse HEAD 2>/dev/null || true ) > "$dir/dist/.version"
19+
done
20+
21+
rm -f .*.bun-build 2>/dev/null || true

test/build-script-shell-compat.test.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,26 @@ describe('package.json build scripts — POSIX shell compat (D-1460)', () => {
2828
expect(offending).toEqual([]);
2929
});
3030

31-
test('every `> path/.version` redirect is preceded by a subshell, not a brace group', () => {
32-
// The original PR #1460 target: package.json line 12 had three of these.
31+
test('build script does version stamping (inline subshells or via bash helper)', () => {
32+
// PR #1460 added inline `( ... ) > .version` subshells. Later, those
33+
// were extracted to `scripts/stamp-versions.sh` because Bun Shell on
34+
// Windows didn't handle `( ... ) > file` reliably (oven-sh/bun#11066).
35+
// Either form is acceptable; what matters is (a) version stamping
36+
// happens, and (b) any inline form still uses subshell, not brace group.
37+
// We intentionally do a loose substring match on the helper path rather
38+
// than pin the exact invocation form (`bash xxx`, `bash ./xxx`,
39+
// `bash -e xxx`, etc.) -- pinning the form turns this into a formatting
40+
// contract that blocks legitimate refactors. The file-existence guard
41+
// catches rename/move regressions.
3342
const build = PKG.scripts.build ?? '';
34-
const versionRedirects = [...build.matchAll(/(\([^)]*\)|\{[^}]*\})\s*>\s*\S+\/\.version/g)];
35-
expect(versionRedirects.length).toBeGreaterThan(0);
36-
for (const m of versionRedirects) {
43+
const inlineRedirects = [...build.matchAll(/(\([^)]*\)|\{[^}]*\})\s*>\s*\S+\/\.version/g)];
44+
const referencesHelper = build.includes('scripts/stamp-versions.sh');
45+
expect(inlineRedirects.length > 0 || referencesHelper).toBe(true);
46+
for (const m of inlineRedirects) {
3747
expect(m[1].startsWith('(')).toBe(true);
3848
}
49+
if (referencesHelper) {
50+
expect(fs.existsSync(path.join(ROOT, 'scripts/stamp-versions.sh'))).toBe(true);
51+
}
3952
});
4053
});

0 commit comments

Comments
 (0)