Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"make-pdf": "./make-pdf/dist/pdf"
},
"scripts": {
"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)",
"build": "bash scripts/build.sh",
"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",
"dev:make-pdf": "bun run make-pdf/src/cli.ts",
"dev:design": "bun run design/src/cli.ts",
Expand Down
38 changes: 38 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -e

ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
cd "$ROOT"

BUN_CMD="${BUN_CMD:-bun}"
BUN_CMD_WAS_COPIED=0

case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*|Windows_NT)
bun_path="$(command -v "$BUN_CMD" 2>/dev/null || true)"
case "$bun_path" in
*[![:ascii:]]*)
bun_copy_dir="$ROOT/.tmp-bun-bin"
mkdir -p "$bun_copy_dir"
cp -f "$bun_path" "$bun_copy_dir/bun.exe"
BUN_CMD="$bun_copy_dir/bun.exe"
BUN_CMD_WAS_COPIED=1
;;
esac
;;
esac

"$BUN_CMD" run vendor:xterm
"$BUN_CMD" run gen:skill-docs --host all
"$BUN_CMD" build --compile browse/src/cli.ts --outfile browse/dist/browse
"$BUN_CMD" build --compile browse/src/find-browse.ts --outfile browse/dist/find-browse
"$BUN_CMD" build --compile design/src/cli.ts --outfile design/dist/design
"$BUN_CMD" build --compile make-pdf/src/cli.ts --outfile make-pdf/dist/pdf
"$BUN_CMD" build --compile bin/gstack-global-discover.ts --outfile bin/gstack-global-discover
bash browse/scripts/build-node-server.sh
bash scripts/write-version-files.sh browse/dist/.version design/dist/.version 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
if [ "$BUN_CMD_WAS_COPIED" -eq 1 ]; then
rm -rf "$ROOT/.tmp-bun-bin"
fi
13 changes: 13 additions & 0 deletions scripts/write-version-files.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e

if git_head="$(git rev-parse HEAD 2>/dev/null)"; then
:
else
git_head=""
fi

for version_file in "$@"; do
mkdir -p "$(dirname "$version_file")"
printf '%s\n' "$git_head" > "$version_file"
done
47 changes: 39 additions & 8 deletions setup
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,37 @@ ensure_playwright_browser() {
fi
}

prepare_bun_for_windows_compile() {
BUN_CMD="bun"
BUN_CMD_WAS_COPIED=0
[ "$IS_WINDOWS" -eq 1 ] || return 0

local bun_path
bun_path="$(command -v bun 2>/dev/null || true)"
case "$bun_path" in
*[![:ascii:]]*)
local bun_copy_dir="$SOURCE_GSTACK_DIR/.tmp-bun-bin"
mkdir -p "$bun_copy_dir"
cp -f "$bun_path" "$bun_copy_dir/bun.exe"
BUN_CMD="$bun_copy_dir/bun.exe"
BUN_CMD_WAS_COPIED=1
;;
esac
}

bun_cmd() {
"$BUN_CMD" "$@"
}

cleanup_copied_bun() {
if [ "${BUN_CMD_WAS_COPIED:-0}" -eq 1 ]; then
rm -rf "$SOURCE_GSTACK_DIR/.tmp-bun-bin"
fi
}

prepare_bun_for_windows_compile
trap cleanup_copied_bun EXIT

# 1. Build browse binary if needed (smart rebuild: stale sources, package.json, lock)
NEEDS_BUILD=0
if [ ! -x "$BROWSE_BIN" ]; then
Expand All @@ -277,8 +308,8 @@ if [ "$NEEDS_BUILD" -eq 1 ]; then
log "Building browse binary..."
(
cd "$SOURCE_GSTACK_DIR"
bun install --frozen-lockfile 2>/dev/null || bun install
bun run build
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
bun_cmd run build
)
# Safety net: write .version if build script didn't (e.g., git not available during build)
if [ ! -f "$SOURCE_GSTACK_DIR/browse/dist/.version" ]; then
Expand Down Expand Up @@ -337,8 +368,8 @@ if [ "$NEEDS_AGENTS_GEN" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
log "Generating .agents/ skill docs..."
(
cd "$SOURCE_GSTACK_DIR"
bun install --frozen-lockfile 2>/dev/null || bun install
bun run gen:skill-docs --host codex
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
bun_cmd run gen:skill-docs --host codex
)
fi

Expand All @@ -347,8 +378,8 @@ if [ "$INSTALL_FACTORY" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
log "Generating .factory/ skill docs..."
(
cd "$SOURCE_GSTACK_DIR"
bun install --frozen-lockfile 2>/dev/null || bun install
bun run gen:skill-docs --host factory
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
bun_cmd run gen:skill-docs --host factory
)
fi

Expand All @@ -357,8 +388,8 @@ if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
log "Generating .opencode/ skill docs..."
(
cd "$SOURCE_GSTACK_DIR"
bun install --frozen-lockfile 2>/dev/null || bun install
bun run gen:skill-docs --host opencode
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
bun_cmd run gen:skill-docs --host opencode
)
fi

Expand Down
30 changes: 22 additions & 8 deletions test/build-script-shell-compat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8')) as {
scripts: Record<string, string>;
};
const BUILD_SCRIPT = fs.readFileSync(path.join(ROOT, 'scripts', 'build.sh'), 'utf-8');

// Strip single-quoted strings so JS code emitted as `echo '{ ... }'` doesn't
// trip the shell-brace-group check. Conservative: only `'...'` segments.
Expand All @@ -15,7 +16,8 @@ function stripSingleQuoted(s: string): string {

describe('package.json build scripts — POSIX shell compat (D-1460)', () => {
// Bun's Windows shell parser doesn't grok bash brace groups `{ cmd; }`.
// Subshells `( cmd )` are POSIX-universal. This test prevents regression.
// Bun 1.3.x on Windows also rejects subshells when the subshell or the
// command inside it uses redirection, so redirected commands must be direct.
test('no bash brace groups in any npm script', () => {
const offending: { script: string; pattern: string }[] = [];
for (const [name, body] of Object.entries(PKG.scripts)) {
Expand All @@ -28,13 +30,25 @@ describe('package.json build scripts — POSIX shell compat (D-1460)', () => {
expect(offending).toEqual([]);
});

test('every `> path/.version` redirect is preceded by a subshell, not a brace group', () => {
// The original PR #1460 target: package.json line 12 had three of these.
const build = PKG.scripts.build ?? '';
const versionRedirects = [...build.matchAll(/(\([^)]*\)|\{[^}]*\})\s*>\s*\S+\/\.version/g)];
expect(versionRedirects.length).toBeGreaterThan(0);
for (const m of versionRedirects) {
expect(m[1].startsWith('(')).toBe(true);
test('build script has no subshells with redirections', () => {
const offending: { script: string; pattern: string }[] = [];
for (const [name, body] of Object.entries({ build: PKG.scripts.build ?? '' })) {
const matches = [
...body.matchAll(/\([^)]*[<>][^)]*\)/g),
...body.matchAll(/\([^)]*\)\s*[<>]/g),
];
for (const match of matches) {
offending.push({ script: name, pattern: match[0] });
}
}
expect(offending).toEqual([]);
});

test('build script delegates .version writes to a shell script', () => {
// Bun rejects `( git ... ) > path/.version`.
const build = PKG.scripts.build ?? '';
expect(build).not.toMatch(/>\s*\S+\/\.version/);
expect(build).toBe('bash scripts/build.sh');
expect(BUILD_SCRIPT).toContain('bash scripts/write-version-files.sh');
});
});