From 1229bec1eb32761648045ca9369d21dae3daf366 Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:51:08 -0700 Subject: [PATCH] fix(postinstall): cross-platform node shim instead of POSIX shell (#1554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(postinstall): cross-platform node shim instead of POSIX shell The postinstall script used POSIX shell syntax ('command -v', '>/dev/null 2>&1', '1>&2') which Bun's built-in script parser rejects on Windows. 'bun install' aborted with 'expected a command or assignment but got: "Redirect"' before gbrain could ever be probed. Replace with a one-liner 'node -e' shim that: * uses spawnSync to probe 'gbrain --version' (shell:true on win32 so the Windows shim/.cmd resolution works) * on success: runs 'gbrain apply-migrations --yes --non-interactive' and propagates its exit code * on failure: writes the same skip message to stderr and exits 0, so fresh clones (where gbrain isn't on PATH yet) still complete install POSIX hosts retain the original behavior; Windows hosts now succeed instead of failing the whole install. Fixes #1486 * fix(postinstall): move logic to scripts/postinstall.ts to survive Bun Windows script-runner The `node -e` inline shim still failed on Windows under Bun. Embedding a program inside the package.json postinstall string lets the lifecycle shell mangle it: Bun's Windows script-runner expands the `\n` in the hint string into a REAL newline before node sees it, producing `SyntaxError: Invalid or unexpected token` and aborting the whole install. `node` is also not guaranteed present under a Bun install (bun is the guaranteed runtime), and `shell: win32` re-opened a quoting surface. Move the logic into a checked-in `scripts/postinstall.ts` run via `bun run scripts/postinstall.ts`, matching the repo's existing convention of ~19 scripts under scripts/*.ts. This sidesteps all three failure modes: * `which('gbrain')` from bun does Windows-aware PATH resolution (finds gbrain / gbrain.exe / gbrain.cmd) with no shell. * `Bun.spawnSync` with an argv array invokes apply-migrations directly — no shell, nothing to quote, no `\n` expansion. * No dependency on `node` being present; bun runs the script. Behavior is preserved exactly: same `apply-migrations --yes --non-interactive` command, same issue-218 skip hint, and the same never-fail-the-install guarantee (every path exits 0). Verified on macOS: `bun run scripts/postinstall.ts` exits 0 on the skip path (gbrain absent), on a failing migration, and on a successful migration. Fixes #1486 --- package.json | 2 +- scripts/postinstall.ts | 49 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 scripts/postinstall.ts diff --git a/package.json b/package.json index 6022bf409..6afa30f09 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "check:fixture-privacy": "scripts/check-fixture-privacy.sh", "check:conversation-parser": "bun src/cli.ts eval conversation-parser test/fixtures/conversation-formats/all.jsonl --no-llm", "check:source-scope-onboard": "scripts/check-source-scope-onboard.sh", - "postinstall": "command -v gbrain >/dev/null 2>&1 && gbrain apply-migrations --yes --non-interactive || echo '[gbrain] postinstall skipped. If installed via bun install -g github:...: run `gbrain doctor` and `gbrain apply-migrations --yes` manually. See https://github.com/garrytan/gbrain/issues/218' 1>&2", + "postinstall": "bun run scripts/postinstall.ts", "prepublish:clawhub": "bun run build:all", "publish:clawhub": "clawhub package publish . --family bundle-plugin" }, diff --git a/scripts/postinstall.ts b/scripts/postinstall.ts new file mode 100644 index 000000000..ca86829db --- /dev/null +++ b/scripts/postinstall.ts @@ -0,0 +1,49 @@ +#!/usr/bin/env bun +// scripts/postinstall.ts +// +// Postinstall hook: after `bun install`, apply any pending schema migrations so +// a freshly-installed gbrain is immediately usable. Wired via package.json +// ("postinstall": "bun run scripts/postinstall.ts") as a real Bun script rather +// than an inline `node -e` one-liner. +// +// Why a script file and not an inline command: +// Embedding a program inside the package.json postinstall string lets the +// lifecycle shell mangle it. Bun's Windows script-runner expands `\n` in the +// hint string into a REAL newline before node sees it, producing +// `SyntaxError: Invalid or unexpected token` and aborting the whole install. +// `node` is also not guaranteed present under a Bun install (bun is the +// guaranteed runtime), and `shell: win32` re-opens a quoting surface. A +// checked-in .ts run by `bun run` sidesteps all three. +// +// Uses Bun APIs only — `which()` for Windows-aware PATH resolution (finds +// gbrain.exe / gbrain.cmd) and an argv-array `Bun.spawnSync` (no shell, nothing +// to quote). It NEVER fails the install: every path exits 0. + +import { which } from 'bun'; + +const HINT = + '[gbrain] postinstall skipped. If installed via bun install -g github:...: ' + + 'run `gbrain doctor` and `gbrain apply-migrations --yes` manually. ' + + 'See https://github.com/garrytan/gbrain/issues/218'; + +// Windows-aware PATH resolution — finds gbrain, gbrain.exe or gbrain.cmd. +const bin = which('gbrain'); + +if (!bin) { + // Fresh clone / global install where gbrain isn't on PATH yet: skip cleanly. + console.error(HINT); + process.exit(0); +} + +try { + const r = Bun.spawnSync({ + cmd: [bin, 'apply-migrations', '--yes', '--non-interactive'], + stdout: 'inherit', + stderr: 'inherit', + }); + if (r.exitCode !== 0) console.error(HINT); +} catch { + console.error(HINT); +} + +process.exit(0); // never abort the install