npm 12 Windows 重装报 EEXIST,残留入口其实是自己的 shim
npm 12.2.0、12.2.1 内嵌的 shim 写入器和读取器格式不一致,中断后的残留入口可能被当成外来文件。确认归属后备份处理,避免全局强制覆盖。
Windows 全局安装中断后,包目录已经不在,重新安装却报 EEXIST: file already exists,指向 prefix 下的命令入口。若使用 npm 12.2.0 或 12.2.1,不要直接用 --force 覆盖:它可能是不认识自己生成的 sh shim。
这两个版本的实际发行包中,bin-links 内嵌的 cmd-shim 9.0.2 会写出 $basedir_win 形式的路径,而同一层的 read-cmd-shim 7.0.0 只识别旧的 $basedir 形式。读取失败先得到 ENOTASHIM,bin-links 再将它转为 EEXIST,看起来就像遇到了外来的同名文件。
为什么普通更新正常,重装却失败
正常的版本更新会先挪开旧包和旧命令入口,未必触发这条检查。中断安装、包目录已被清走而 shim 仍在时,重新安装需要检查现有入口;读不懂新格式,就会拒绝覆盖。
以 prettier 为例,全局 prefix 下可能同时留下 prettier、prettier.cmd、prettier.ps1。即使 PowerShell 平时使用的是 .ps1 或 .cmd,安装检查仍会查看其他入口。不能因为错误路径没有扩展名,就认定它与 Windows 无关。
先确认当前 npm 的 prefix 和报错的精确路径:
npm prefix -g
Get-Content -LiteralPath 'C:\path\to\prefix\prettier'
第二行的路径需要替换为当前报错文件。检查它是否确实指向目标 prefix 内已经缺失的 node_modules/prettier/...,以及 sh 内容是否使用 $basedir_win。不读取或公开其他配置中的认证信息。
只处置已经确认归属的残留入口
确认包目录缺失、三个入口确实属于这个包之后,先把这些明确的残留文件移到可恢复的备份位置,再按原先固定的包版本进行获准的重装。保留文件内容及原路径;不要批量删除整个 prefix 或其他包的命令。
若其中一个入口实际指向另一个包、其他 prefix 或手工维护的程序,应先处理真正的命令冲突。不能因为报错文本相同,就把所有 EEXIST 都归为 shim 格式问题。
read-cmd-shim 自身已有能识别 $basedir_win 的版本,但单独升级全局同名包不会替换 npm 发行包内部嵌套的读取器。2026 年 10 月 23 日核对的 npm 12.2.1 仍内嵌 7.0.0;后续采用新版时,应核对实际包含的依赖,而不是只看某个子项目已经修复。
安装成功以后,再确认命令落到哪里
检查命令实际解析路径、入口内容与目标包版本,确认 .cmd、.ps1 和无扩展名入口都指向预期安装。随后按正常流程验证包的无副作用入口,不能只看到安装退出码为零就结束。
避免在同一个 prefix 并发重装。需要恢复时,先核对新安装状态,再按原路径恢复对应备份;不要把旧 shim 覆盖到已经正常的新安装上。--force 会跳过保护性检查,不适合用来代替文件归属和目标路径的判断。


