diff --git a/home-manager/modules/npm-globals/install-npm-globals.sh b/home-manager/modules/npm-globals/install-npm-globals.sh index 39f6607c3..f6fd22eed 100755 --- a/home-manager/modules/npm-globals/install-npm-globals.sh +++ b/home-manager/modules/npm-globals/install-npm-globals.sh @@ -115,6 +115,20 @@ missing_native_optional_dep() { [ "$matched" -eq 1 ] && [ "$present" -eq 0 ] } +# Remove the npm "bun" wrapper package from global node_modules. +# Some transitive deps pull in the "bun" npm package whose postinstall +# downloads a bun binary. When that postinstall is skipped/fails, it leaves +# a stub .bin/bun shim that shadows the real system bun, breaking every +# subsequent postinstall that shells out to bun (e.g. @railway/cli). +purge_bun_npm_shim() { + local gm="${HOME}/.bun/install/global/node_modules" + if [ -d "${gm}/bun" ]; then + rm -rf "${gm}/bun" + rm -f "${gm}/.bin/bun" "${gm}/.bin/bunx" + echo "Removed broken bun npm shim from global node_modules" + fi +} + echo "Installing npm global packages from package.json using bun..." cd "${HOME}/dotfiles" @@ -154,6 +168,8 @@ if [ "${#STALE[@]}" -gt 0 ]; then done fi +purge_bun_npm_shim + # Build list of packages that need installing or updating GLOBAL_MODULES="${HOME}/.bun/install/global/node_modules" DEPS=$(jq -r '.dependencies | to_entries[] | "\(.key)=\(.value)"' "$PACKAGE_JSON" 2>/dev/null || true) @@ -198,6 +214,7 @@ if [ "${#MISSING[@]}" -gt 0 ]; then echo "Installing ${#MISSING[@]} missing packages..." for dep in "${MISSING[@]}"; do timeout 600 bun add --global "$dep" 2>/dev/null || echo "Install failed: $dep" + purge_bun_npm_shim done else echo "All npm global packages already installed" @@ -222,6 +239,7 @@ if [ -n "$OVERRIDES" ]; then jq --argjson overrides "$OVERRIDES" '.overrides = $overrides' "$GLOBAL_PKG" >"${GLOBAL_PKG}.tmp" && mv "${GLOBAL_PKG}.tmp" "$GLOBAL_PKG" (cd "${HOME}/.bun/install/global" && bun install 2>/dev/null || true) + purge_bun_npm_shim echo "Applied dependency overrides to global install" fi fi @@ -319,6 +337,7 @@ if [ -n "$OPTIONAL_DEPS" ]; then fi echo "Installing platform-native: $spec" timeout 600 bun add --global "$spec" --minimum-release-age 0 2>/dev/null || echo "Install failed: $spec" + purge_bun_npm_shim # Verify the payload actually materialized; bun can silently no-op. if [ ! -f "${GLOBAL_MODULES}/${dep}/package.json" ]; then echo "Warning: $dep still missing after install ($spec)" >&2 diff --git a/spec/npm_globals_spec.sh b/spec/npm_globals_spec.sh index 9d5bd00c3..df3a9309c 100644 --- a/spec/npm_globals_spec.sh +++ b/spec/npm_globals_spec.sh @@ -187,6 +187,28 @@ The output should include 'find' End End +Describe 'bun npm shim purge' + It 'defines a purge_bun_npm_shim function' + When run bash -c "grep 'purge_bun_npm_shim()' '$SCRIPT'" + The output should include 'purge_bun_npm_shim' + End + + It 'removes the bun package from global node_modules' + When run bash -c "grep 'rm -rf.*gm.*bun' '$SCRIPT'" + The output should include 'bun' + End + + It 'removes bun shims from .bin' + When run bash -c "grep 'rm -f.*\.bin/bun' '$SCRIPT'" + The output should include '.bin/bun' + End + + It 'calls purge after each bun add --global' + When run bash -c "grep -A 1 'bun add --global.*dep.*2>/dev/null' '$SCRIPT' | grep 'purge_bun_npm_shim'" + The output should include 'purge_bun_npm_shim' + End +End + Describe 'native addon repair' It 'has a sqlite3 native binding repair step' When run bash -c "grep 'repair_sqlite3_native_binding' '$SCRIPT'"