小类随手记

NixOS 彻底清理 nodejs/pnpm 全面转向 bun

从系统层面彻底移除 nodejs/npm/pnpm,统一到 bun 的完整迁移记录:审计、配置修改、shebang 兼容层、缓存清理

本文记录一次在 NixOS(Flakes + home-manager)上从 nodejs/npm/pnpm 全面迁移到 bun 的过程,包括审计、配置修改、踩坑修复和缓存清理,供同样想统一 JS/TS 工具链的读者参考。

背景与目标

  • 本机原先并存两套环境:nodejs_26 + pnpm(nix 安装)与 bun(一体化运行时 + 包管理器)
  • 目标:彻底移除 node/npm/pnpm 命令,所有 JS/TS 运行、包管理、临时执行统一走 bun
  • 迁移前先审计,确认没有不可迁移的软件

一、审计:npm/pnpm 安装的软件能否迁移

  • npm 全局包:空(npm ls -g 无输出,历史已清空)
  • pnpm 全局包:空(pnpm ls -g 无输出)
  • npx 历史缓存(~/.npm/_npx/):playwright、wrangler、eslint、prettier、tsx、shadcn、chrome-devtools-mcp 等,全部可用 bunx 替代
  • 项目内 npm 安装点:modules/ai/cfg_opencode/ 插件目录(npm install @opencode-ai/plugin),纯 JS 包,bun install 完全兼容
  • 结论:无阻塞项,可以彻底迁移

二、修改 NixOS 配置

1. 移除运行时与包管理器

modules/dev/lang_runtime_and_tools.nix 中注释并最终删除 nodejs_26、pnpm,只保留 bun:

1
2
# ===== Node / Bun(JS/TS 运行时 + 包管理)=====
bun # 一体化 JS/TS 运行时 + 包管理器

2. 移除环境变量与 PATH

modules/system/env_manager.nix 中删除:

  • PNPM_HOME(pnpm 全局目录)
  • NODE_OPTIONS(node 专属的代理参数,bun 不读取;bun 原生支持环境代理)
  • PATH 中的 ~/.local/share/pnpm/bin

3. 移除 permittedInsecurePackages 遗留

modules/system/nixpkgs_system.nix 中删除 nodejs-20.20.2、nodejs-slim-20.20.2。删除前用 nix-store 验证:

1
nix-store -q --referrers /nix/store/*nodejs-20.20.2

无任何包依赖,纯历史遗留,可安全移除。

4. 迁移项目插件目录

cfg_opencode 由 npm 迁移到 bun:

1
2
cd modules/ai/cfg_opencode
bun install

生成 bun.lock,package-lock.json 归档到 archive.7z 以便回退。

5. 保留项

  • ~/.npmrc 符号链接保留:bun 完全兼容 //registry.npmjs.org/:_authToken,供 bun install/bunx 认证
  • nix 打包的 LSP 工具(typescript-language-server、vscode-langservers-extracted、prettierd)保留:它们内部自带 nodejs,不依赖系统 node

三、关键坑:bun 全局包的 node shebang

rebuild 后立即踩坑:pi-coding-agent(bun 全局包)运行报错:

1
env: 'node': No such file or directory

原因:

  • bun install -g 安装的包,bin 入口是直接 symlink 到源文件,且不重写 shebang
  • 源文件 shebang 多为 #!/usr/bin/env node,移除系统 node 后自然找不到

修复方案(~/.local/bin/node 符号链接指向 bun):

1
".local/bin/node".source = config.lib.file.mkOutOfStoreSymlink "/etc/profiles/per-user/${username}/bin/bun";

要点:

  • 必须用 symlink 而非 shell 包装脚本:symlink 时 argv[0]=node,bun 自动进入 node 兼容模式;shell 脚本 exec bun 会把 argv[0] 变为 bun(exec -a 仅 bash 支持,/bin/sh 为 dash),失去兼容模式
  • 已知限制:bun 的 node 兼容模式不支持 node –version / node -v 等版本查询参数,node -e 与脚本执行正常
  • 部分工具自带 bun 原生入口可优先使用:pi-coding-agent 有 dist/bun/cli.js(含 bun-oauth 适配),把 ~/.bun/bin/pi 指过去并以 chmod +x 即可完全走 bun

npx 兼容层:为硬编码调用兜底

除 node shebang 外,生态里还有大量工具硬编码调用 npx(VSCode 的 Prettier/ESLint 扩展、#!/usr/bin/env npx 脚本、spawn('npx', ...) 等)。给 ~/.local/bin/npx 加一个 shell 包装脚本,指向 bunx:

1
2
3
4
5
6
7
".local/bin/npx" = {
  text = ''
    #!/bin/sh
    exec /etc/profiles/per-user/${username}/bin/bunx "$@"
  '';
  executable = true;
};

要点:

  • 不能 symlink 到 bunx:bun 靠 argv[0] 判断是否进入 x 模式,symlink 名为 npx 时 argv[0]=npx 不触发,报 error: Script not found;shim 里 exec bunx 后 argv[0]=bunx,正确触发
  • 与 node 恰好相反:node 需要 argv[0]=node(所以用 symlink),npx 需要 argv[0]=bunx(所以用 shell 包装)
  • 为什么不用 alias:alias 只对交互式 shell 生效,VSCode 插件/脚本 spawn npx 是直接按 PATH 找可执行文件,完全绕过 shell
  • 踩坑:新版 home-manager(2026-04-24 起)的 home.file 已移除 mode 选项,text 文件设执行位用 executable = true,写 mode = "755" 会报 option does not exist
  • 验证:npx --yes prettier --version → 3.8.3

四、清理历史缓存

1
2
3
rm -rf ~/.npm          # _cacache 3.4G + _npx 缓存
rm -rf ~/.local/share/pnpm   # pnpm store 373M
rm -rf ~/.pnpm-store

五、验证结果

1
2
3
4
5
6
command -v node npm pnpm npx bun
# 输出:/home/y/.local/bin/node(shim)、/home/y/.local/bin/npx(shim)与 /etc/profiles/per-user/y/bin/bun
bun -v        # 1.3.13
npx --yes prettier --version  # 3.8.3(bunx 执行)
pi --version  # 0.84.1(bun 原生入口)
opencode --version  # 1.18.15(ELF 原生二进制,无需 node)
  • npm/pnpm 命令完全消失
  • bun 全局包 pi-coding-agent、opencode-ai 全部可用
  • LSP 工具正常
  • 迁移记录归档在 os-config 仓库 static_file/archive.7z 内的 nodejs_pnpm.md

相关文章

comments powered by Disqus
Theme Stack