diff --git a/home-manager/modules/npm-globals/install-npm-globals.sh b/home-manager/modules/npm-globals/install-npm-globals.sh index d31a56868..9a37f16a4 100755 --- a/home-manager/modules/npm-globals/install-npm-globals.sh +++ b/home-manager/modules/npm-globals/install-npm-globals.sh @@ -42,21 +42,49 @@ if [ -n "$TRUSTED_DEPS" ]; then done fi -# Install global packages in batches (bun hangs when resolving too many at once) -DEPS=$(jq -r '.dependencies | keys[]' "$PACKAGE_JSON" 2>/dev/null || true) +# 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) +MISSING=() if [ -n "$DEPS" ]; then - BATCH_SIZE=10 + while IFS= read -r entry; do + dep="${entry%%=*}" + wanted="${entry#*=}" + # Extract minimum version from semver spec (e.g. "^2.1.92" -> "2.1.92") + wanted_ver="${wanted//[^0-9.]/}" + installed_ver="" + pkg_json="${GLOBAL_MODULES}/${dep}/package.json" + if [ -f "$pkg_json" ]; then + installed_ver=$(jq -r '.version // empty' "$pkg_json" 2>/dev/null || true) + fi + if [ -n "$installed_ver" ] && [ -n "$wanted_ver" ] && [ "$installed_ver" = "$wanted_ver" ]; then + echo "$dep@$installed_ver already installed, skipping" + elif [ -n "$installed_ver" ]; then + echo "$dep@$installed_ver installed, want $wanted_ver, updating" + MISSING+=("$dep") + else + MISSING+=("$dep") + fi + done <<<"$DEPS" +fi + +# Install missing packages in small batches +if [ "${#MISSING[@]}" -gt 0 ]; then + echo "Installing ${#MISSING[@]} missing packages..." + BATCH_SIZE=5 BATCH=() - while IFS= read -r dep; do + for dep in "${MISSING[@]}"; do BATCH+=("$dep") if [ "${#BATCH[@]}" -ge "$BATCH_SIZE" ]; then - bun add --global "${BATCH[@]}" 2>/dev/null || true + bun add --global "${BATCH[@]}" 2>/dev/null || echo "Batch install failed: ${BATCH[*]}" BATCH=() fi - done <<<"$DEPS" + done if [ "${#BATCH[@]}" -gt 0 ]; then - bun add --global "${BATCH[@]}" 2>/dev/null || true + bun add --global "${BATCH[@]}" 2>/dev/null || echo "Batch install failed: ${BATCH[*]}" fi +else + echo "All npm global packages already installed" fi # Apply dependency overrides to the global install diff --git a/home-manager/programs/neovim/default.nix b/home-manager/programs/neovim/default.nix index 712ac5685..a2da8d51a 100644 --- a/home-manager/programs/neovim/default.nix +++ b/home-manager/programs/neovim/default.nix @@ -37,7 +37,6 @@ in home.file.".config/nvim/lua" = { source = ./lua; - recursive = true; force = true; }; diff --git a/spec/clipboard_paste_spec.sh b/spec/clipboard_paste_spec.sh index e102f57fc..707729804 100644 --- a/spec/clipboard_paste_spec.sh +++ b/spec/clipboard_paste_spec.sh @@ -36,13 +36,15 @@ setup() { MOCK_BIN="$(mktemp -d)" MOCK_ORIGINAL_PATH="${PATH:-}" export MOCK_BIN MOCK_ORIGINAL_PATH - export PATH="$MOCK_BIN:$MOCK_ORIGINAL_PATH" export WAYLAND_DISPLAY=wayland-0 cat >"$MOCK_BIN/wl-paste" <<'EOF' #!/usr/bin/env bash printf 'hello' EOF chmod +x "$MOCK_BIN/wl-paste" + local bash_dir + bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")" + export PATH="$MOCK_BIN:$bash_dir" } cleanup() { export PATH="$MOCK_ORIGINAL_PATH" @@ -61,16 +63,23 @@ End Describe 'when xclip is available' setup() { - mock_bin_setup xclip + MOCK_BIN="$(mktemp -d)" + MOCK_ORIGINAL_PATH="${PATH:-}" + export MOCK_BIN MOCK_ORIGINAL_PATH unset WAYLAND_DISPLAY cat >"$MOCK_BIN/xclip" <<'EOF' #!/usr/bin/env bash printf 'hello' EOF chmod +x "$MOCK_BIN/xclip" + local bash_dir + bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")" + export PATH="$MOCK_BIN:$bash_dir" } cleanup() { - mock_bin_cleanup + export PATH="$MOCK_ORIGINAL_PATH" + rm -rf "$MOCK_BIN" + unset MOCK_BIN MOCK_ORIGINAL_PATH } Before 'setup' After 'cleanup' diff --git a/spec/npm_globals_spec.sh b/spec/npm_globals_spec.sh index fe18eee14..efe8719bf 100644 --- a/spec/npm_globals_spec.sh +++ b/spec/npm_globals_spec.sh @@ -68,25 +68,69 @@ The output should include 'exit 0' End End -Describe 'package installation' +Describe 'trusted dependencies' It 'reads trustedDependencies from package.json' When run bash -c "grep 'trustedDependencies' '$SCRIPT'" The output should include 'trustedDependencies' End +It 'trusts postinstall scripts before installing' +When run bash -c "grep 'bun pm -g trust' '$SCRIPT'" +The output should include 'bun pm -g trust' +End +End + +Describe 'skip already installed' +It 'checks global node_modules for installed packages' +When run bash -c "grep 'GLOBAL_MODULES=' '$SCRIPT' | head -1" +The output should include '.bun/install/global/node_modules' +End + +It 'reads installed version from package.json' +When run bash -c "grep 'installed_ver' '$SCRIPT'" +The output should include 'installed_ver' +End + +It 'extracts wanted version from semver spec' +When run bash -c "grep 'wanted_ver' '$SCRIPT'" +The output should include 'wanted_ver' +End + +It 'skips when installed version matches wanted' +When run bash -c "grep 'already installed, skipping' '$SCRIPT'" +The output should include 'already installed, skipping' +End + +It 'detects when update is needed' +When run bash -c "grep 'updating' '$SCRIPT'" +The output should include 'updating' +End + +It 'builds a MISSING array of packages to install' +When run bash -c "grep 'MISSING' '$SCRIPT'" +The output should include 'MISSING' +End +End + +Describe 'batch installation' +It 'uses batch size of 5' +When run bash -c "grep 'BATCH_SIZE=5' '$SCRIPT'" +The output should include 'BATCH_SIZE=5' +End + It 'uses bun add --global in batches' When run bash -c "grep 'bun add --global' '$SCRIPT'" The output should include 'bun add --global' End -It 'batches packages to avoid bun resolution hangs' -When run bash -c "grep 'BATCH_SIZE' '$SCRIPT'" -The output should include 'BATCH_SIZE' +It 'reports batch failures' +When run bash -c "grep 'Batch install failed' '$SCRIPT'" +The output should include 'Batch install failed' End -It 'parses dependencies with jq' -When run bash -c "grep 'dependencies' '$SCRIPT'" -The output should include 'dependencies' +It 'reports when all packages are already installed' +When run bash -c "grep 'All npm global packages already installed' '$SCRIPT'" +The output should include 'All npm global packages already installed' End End