Nub 有自己的安装引擎:它解析依赖图并链接 node_modules。使它成为新型包管理器的是它不强迫现有项目进入 Nub 专用格式——它读取重写项目的原生锁文件,所以 npm、pnpm 和 Bun 干净往返,Yarn 只读消费。CLI 是 pnpm 形态的;引擎是内嵌的 aube 引擎,作为库嵌入并由 Nub 自身的 CLI 驱动。

Nub 从不询问你使用哪个包管理器——它推断现任者并镜像它。每个都有自己的页面涵盖锁文件保真度、配置接口和差距:pnpmnpmBunYarn

兼容性

在已使用 npm、pnpm、Yarn 或 Bun 的仓库中运行 Nub,它的行为就像那个包管理器一样——无迁移,无新文件。Nub 推断现任者,然后镜像它:相同锁文件格式、相同配置文件、相同清单字段。推断遍历一个优先级链:

  • packageManager — Corepack 标准
  • devEngines.packageManager — 对象或数组形式
  • 磁盘上的锁文件

在工作区中,链从任何成员运行:Nub 向上遍历到根目录,根目录携带声明和锁文件。两个不同管理器的锁文件,无声明,是硬错误。

现任者锁文件往返
npm文档 →package-lock.jsonnpm-shrinkwrap.json读 + 写
pnpm文档 →pnpm-lock.yaml (v9)读 + 写
Yarn文档 →yarn.lock只读
Bun文档 →bun.lock读 + 写
Nub文档 →nub.lock (pnpm v9 字节)读 + 写

无抖动保护将图相等锁文件保持不变,Nub 从不在它不拥有的项目中投放自己的锁文件。bun.lockb 二进制格式被拒绝——先转换为文本 bun.lock

读取的配置

配置读取与锁文件对称:在每个现任者下 Nub 读取该工具的品牌配置而非其他。中性 .npmrc 级联和 npm_config_* 在每个现任者下都被读取。悬停部分芯片查看分解;每个芯片基于该现任者页面上的详细表格。

包管理器读取的配置
npmpackage-lock.json. 支持. v1 / v2 / v3 读取;旧版 v1 git/file: 依赖需要重新锁定。npm-shrinkwrap.json. 支持.npmrc. 支持overrides. 支持workspaces. 支持engines / os / cpu / libc. 支持npm_config_*. 支持. 仅注册表客户端键。
pnpmpnpm-lock.yaml. 支持. v6/v5.4 被拒绝——在 pnpm 9+ 下重新锁定。pnpm-workspace.yaml. 支持.pnpmfile.cjs. 支持.npmrc. 支持package.json#pnpm. 支持pnpm.overrides. 支持pnpm.packageExtensions. 支持pnpm.patchedDependencies. 支持resolutions. 支持catalog:. 支持workspace:. 支持workspaces. 支持dependenciesMeta.injected. 支持engines / os / cpu. 支持pnpm_config_*. 支持. 任何 pnpm 版本下的通用设置;pnpm v11+ 下的注册表客户端键(registry、proxy、strict-ssl)。npm_config_*. 支持nodeLinker. 部分支持. 无 PnP — node-linker=pnp 报错。
Yarnread-only.npmrc. 支持resolutions. 支持workspace:. 支持workspaces. 支持packageExtensions. 支持dependenciesMeta.built. 支持engines / os / cpu. 支持yarn.lock. 部分支持. 只读;写入被拒绝。.yarnrc.yml. 部分支持. 不读取:每主机代理、PnP。.yarnrc. 部分支持. 仅注册表和认证键。YARN_*. 部分支持. 读取注册表、认证令牌/标识、node-linker、CA 文件、代理和 strict-SSL 环境值;map 形态和 scope 的环境配置不被翻译。nodeLinker. 部分支持. 无 PnP — 警告并改为链接 node_modules。
Bunbun.lock. 支持trustedDependencies. 支持overrides. 支持resolutions. 支持patchedDependencies. 支持catalog:. 支持workspace:. 支持workspaces. 支持engines / os / cpu. 支持bunfig.toml. 部分支持. 仅 [install] 部分。BUN_CONFIG_*. 部分支持. 仅注册表和令牌。bun.lockb. 不支持. 二进制锁文件被拒绝——先转换为 bun.lock 文本。

在自身身份下,Nub 只读取中性的、跨工具配置——从不是其他管理器的品牌字段:

包管理器读取的配置
nubnub.lock. 支持.npmrc. 支持overrides. 支持resolutions. 支持patchedDependencies. 支持catalog:. 支持workspace:. 支持workspaces. 支持engines.node. 支持npm_config_*. 支持

你不需要使用 Nub 的包管理器

安装器是可选的。继续像今天一样运行 npmpnpmyarnbun,将 Nub 用于其他一切——运行文件、脚本和二进制文件。

Nub 身份

项目在三种情况下是 Nub 自身的:它通过 nub pm use nub 声明 Nub、nub.lock 是唯一的锁文件信号,或新项目无声明且无锁文件。切换对齐清单和锁文件,将 pnpm 工作区配置迁移到中性 package.json 字段,并将项目移到中性接口:

  • 锁文件: nub.lock — 逐字节的 pnpm v9 模式,使用 Nub 自身的基本名称
  • 包字段: workspacesoverridesresolutionspatchedDependenciesallowBuildsengines.nodescripts
  • 配置和环境: .npmrc 级联、npm_config_*、中性环境(CI、代理),以及 NUB_CACHE_DIRNUB_CONCURRENCYNUB_PRIMER_TTL
  • 安装状态: node_modules/.store/.nub-state,以及 Nub 自身目录下的全局存储

新的 nub install 以非锁定范围记录 Nub 为管理器——devEngines.packageManager 设为 { name: "nub", version: "^<version>", onFail: "warn" },从不是精确的 packageManager 固定。按名称读取 devEngines 的工具看到信号,插入符号是下限,所以升级 Nub 就能工作。要将项目冻结到精确 Nub 版本——corepack 可见的硬固定——用 nub pm use nub@<version> 选择;裸 nub pm use nub 只写入范围。

这是双向的品牌边界:Nub 从不在你的配置中输出自己的品牌,在自身身份下读取中性的、跨工具配置——从不是 pnpm-workspace.yaml.pnpmfile.cjspnpm.* 命名空间、pnpm_config_*.yarnrc.ymlbunfig.toml、Bun trustedDependencies 或 aube 的 AUBE_* 旋钮。要保持 pnpm 钩子或 pnpm 命名的工作区配置活跃,保持 pnpm 拥有或运行 nub pm use pnpm

# 捕获:Nub 身份项目(nub.lock)带游离的 pnpm-workspace.yaml
$ nub install
nub: pnpm-workspace.yaml is not read under nub identity — migrate it
     (`nub pm use nub`), delete it, or return to pnpm (`nub pm use pnpm`).

矛盾是响亮的

当信号不一致时,Nub 停止而非猜测。两个锁文件,无声明:

$ nub install
Error: ERR_NUB_LOCKFILE_AMBIGUOUS

  × multiple lockfiles found: pnpm-lock.yaml, package-lock.json —
  │ cannot tell which package manager owns this project
  help: nub pm use <pm> to declare it, or remove the stale lockfile

声明但其锁文件缺失:

$ nub install   # packageManager: "pnpm@9.0.0"
Error: ERR_NUB_LOCKFILE_DECLARATION_MISMATCH

  × package.json declares `pnpm` (via `packageManager`), but
  │ pnpm-lock.yaml is missing — found package-lock.json instead
  help: nub pm use <pm> to declare it, or remove the stale lockfile

在 Nub 身份下,nub.lock 旁边的外来锁文件是歧义错误。

推断 vs 固定的 PM

此推断选择安装引擎的现任者——Nub 读取和写入的格式。它与 nub pm 提供的版本分开:元管理器解析一个固定.yarnrc.yml yarnPathpackageManagerdevEngines)来获取和运行精确的 PM 二进制文件。相同信号,不同问题:"我以哪种格式安装?" 对比 "我运行哪个 PM 二进制文件?"。

兼容模式

运行时增强是可逆的。传递 --node,或为整个树设置 truthy 的 NODE_COMPAT,运行项目固定的原生 Node——版本提供保持开启,增强关闭。参见运行时概览了解完整约定。

CLI

安装引擎是一个 CLI——pnpm 的动词和标志,由 Nub 驱动。

nub install

解析图并链接 node_modules。动词、别名和标志遵循 pnpm:

nub install                      # 别名:nub i
nub install --frozen-lockfile    # 锁文件过期时失败
nub install -P                   # --prod / --production
nub install -D                   # 仅开发
nub install --node-linker hoisted
nub ci                           # 从锁文件干净安装

接受的标志是 pnpm 的拼写:

--frozen-lockfile / --no-frozen-lockfile / --prefer-frozen-lockfile
--prod, -P            # 生产安装
--dev, -D
--ignore-scripts
--no-optional
--offline / --prefer-offline
--lockfile-only
--force
--node-linker
--registry
--dir, -C             # pnpm 的拼写,不是 npm 的 --prefix
--reporter <name>     # default, append-only, silent
--silent, -s          # --reporter=silent 的别名
--loglevel <level>    # debug, info, warn, error, silent

要静默进度输出,传递 --silent(或 -s,或 --reporter=silent):除致命错误外没有内容到达 stderr,与 pnpm install --silent 一致。--reporter=append-only 形式放弃实时进度显示但保留依赖摘要,--loglevel error 隐藏警告但不影响其余。这些拼写适用于每个安装族命令,可在命令后(nub install --silent)或命令前(nub --silent install)使用。

在工作区中,installci 接受与脚本运行相同的筛选标志——只安装筛选器匹配的包:

--filter <sel>, -F        # pnpm 的选择器语法(参见 /docs/runner/run#--filter)
--recursive, -r           # 每个工作区包
--filter-prod <sel>       # 选择器,仅生产依赖
--include-workspace-root  # 将根包添加到递归集合
--fail-if-no-match        # 筛选器选择零个包时报错

nub add

解析包,链接它,并将依赖写入 package.json

nub add <pkg>                  # 别名:a
nub add -D <pkg>               # --save-dev
nub add -E <pkg>               # --save-exact(固定,无 ^)
nub add -O <pkg>               # --save-optional
nub add --save-peer <pkg>      # peer + dev 依赖(pnpm 对等)
nub add -g <pkg>               # 全局安装
nub add -w <pkg>               # 写入工作区根
nub add --save-catalog <pkg>   # 添加到工作区目录
nub add --allow-build=<pkg>    # 为此安装预批准其构建脚本
nub add --no-save <pkg>        # 链接但不持久化到 package.json
nub add <pkg>@<version>        # 固定精确版本
nub add <pkg>@<version> --lockfile-only  # 刷新锁文件,跳过 node_modules

nub remove

package.json 删除依赖并重新链接 node_modules

nub remove <pkg>           # rm / uninstall / un / uni
nub remove -D <pkg>        # 仅从 devDependencies 删除
nub remove -g <pkg>        # 删除全局包
nub remove -w <pkg>        # 从工作区根删除

nub update

在范围内重新解析依赖;--latestpackage.json 范围重写为最新解析版本:

nub update                 # up — 刷新范围内所有依赖
nub update <pkg>           # 更新单个依赖
nub update <pkg>@<version> # 固定一个依赖到版本,保持其 ^/~ 运算符
nub update -L              # --latest:超越清单范围
nub update -E -L           # 将重写的范围固定到精确版本
nub update -D              # 仅 devDependencies
nub update -P              # 仅生产
nub update --lockfile-only # 刷新锁文件,不动 node_modules

nub dedupe

将锁文件中的重复版本折叠为更少的、共享的解析:

nub dedupe          # 用去重解析重写锁文件
nub dedupe --check  # CI:如果 dedupe 会改变任何内容则非零退出

nub import

将另一个包管理器的锁文件转换为 Nub 的 pnpm-lock.yaml,不安装:

nub import          # package-lock.json / yarn.lock / bun.lock → pnpm-lock.yaml
nub import --force  # 覆盖现有的 pnpm-lock.yaml

完整的注册动词集涵盖更多:

why          outdated      list, ls
patch        patch-commit  patch-remove
approve-builds  prune      rebuild
fetch        link, unlink  audit
licenses     bin           root
store        config        pkg
publish      pack          dlx          create

nub pm

安装引擎与 nub pm包元管理器)不同,后者提供并运行你项目固定的确切 pnpm/npm/yarn(corepack 的工作)。

  • "安装依赖",用此引擎。
  • "获取并运行项目固定的 PM",用 nub pm

两者组合:nub pm shim 将裸 npm / pnpm / yarn 通过固定路由,同时你继续使用你偏好的安装器。

生命周期脚本

一些依赖在安装时运行构建步骤——其自身 package.json 中声明的 preinstallinstallpostinstall 脚本(跨 pnpm、npm 和 Bun)。Nub 采用拒绝默认姿态:它不像 npm 那样不加区分地运行它们。你控制哪些包构建。

nub approve-builds                # 交互式批准要构建的包
nub add --allow-build=<pkg> <pkg> # 添加时预批准其构建脚本
nub rebuild                       # 重新运行已批准包的脚本
nub install --ignore-scripts      # 跳过此次安装的依赖构建脚本

哪个清单字段授予权限取决于推断的现任者:pnpm 项目使用 pnpm.onlyBuiltDependencies / pnpm.allowBuilds,Bun 项目使用 trustedDependencies,中性 allowBuilds 字段加 nub approve-builds 适用于任何项目。显式拒绝(allowBuilds: { pkg: false }neverBuiltDependencies)始终优先。想要构建但未被允许的包被跳过,WARN_NUB_IGNORED_BUILD_SCRIPTS 命名它并以 nub approve-builds 作为补救。

默认信任下限

除了你显式批准的包外,一组精选的知名包可以无需批准构建——但仅当三个门同时成立时:

要求失败时
注册表来源从注册表解析。Git、file、link、tarball 和 npm-alias 说明符从不合格——别名不能借用列出名称的信任。不构建
咨询审查对此图运行了 OSV MAL-* 咨询检查, 图继承自已检查的锁文件(冻结安装、nub ci、队友的克隆)。不构建
冷却窗口解析版本的发布时间早于 minimumReleaseAge(默认 24 小时)。不构建——发布时间未知时失败关闭

显式决定在两个方向上优先于下限。你批准的包——通过现任者的白名单(pnpm 下的 pnpm.onlyBuiltDependencies / pnpm.allowBuilds,Bun 下的 trustedDependencies)、中性 allowBuilds 字段或 nub approve-builds——无论如何都构建;显式拒绝(allowBuilds: { pkg: false }neverBuiltDependencies)始终优先。

新的解析,或 Nub 自身写入的锁文件(携带 time: 块),为下限提供所需的一切,所以 esbuild 等精选包自动构建:

# 捕获:nub 0.0.44,pnpm 现任者,esbuild@0.21.5 — 无 allowBuilds 条目
$ nub install
WARN defaultTrust: running build scripts for 1 default-trusted
     package(s): esbuild@0.21.5 code=WARN_NUB_DEFAULT_TRUST_BUILDS
     count=1 packages=["esbuild@0.21.5"]   # ✓ 三个门都通过
dependencies:
+ esbuild@0.21.5

当门失败时,下限退到一边而非猜测:相同的包被跳过和披露,以 nub approve-builds 作为补救。将冷却窗口收紧到超过每个已发布版本,即使精选包也失败关闭:

# 捕获:nub 0.0.44,esbuild — minimumReleaseAge 设置超过每个发布
$ nub install
WARN ignored build scripts for 1 package(s): esbuild@0.21.5.
     Run `nub approve-builds` to review and enable them.
     code=WARN_NUB_IGNORED_BUILD_SCRIPTS   # ❌ 冷却窗口门失败关闭
dependencies:
+ esbuild@0.21.5

不携带发布时间数据的外来锁文件——特别是现任者 bun.lock——触发相同的失败关闭路径:冷却门没有可读取的内容,所以包被跳过(参见 Bun 页面获取捕获的 A/B)。

咨询门

OSV 检查在新的解析上查询 api.osv.dev。确认的 MAL-* 命中是硬阻断——安装以 ERR_NUB_MALICIOUS_PACKAGE 中止,从不是跳过并警告,因为恶意包咨询不是判断题。osv.dev 中断被不同对待:检查失败打开,警告并继续,所以网络闪断不能使离线安装瘫痪。该不对称是刻意的——命中始终阻断,中断从不。要在中断上也失败关闭,设置 advisoryCheck=required(也捆绑到下方的 paranoid 中)。

冻结重新安装——nub ci--frozen-lockfile、队友的克隆——继承写入锁文件时记录的咨询审查,并跳过每安装的往返,但在每次安装上仍执行冷却和来源门。

构建沙箱

操作系统级构建沙箱——围绕每个构建脚本的网络阻断、文件系统范围限定的沙箱——已编译但默认关闭。通过中性 paranoid / npm_config_paranoid 设置选择开启,这也将咨询门翻转为失败关闭。沙箱覆盖 macOS 和 Linux;Windows 是直通。

信任降级

Nub 还权衡跨包发布历史的信任证据——OIDC 来源、可信发布者、分阶段发布批准。携带比同包早期发布版本更弱证据的解析版本以 ERR_NUB_TRUST_DOWNGRADE 停止安装,因为维护者的管道突然发布时没有其以前携带的证明,这正是令牌盗窃供应链攻击的形状。

比较按发布日期进行,所以旧主版本上的合法维护版本——在采用来源的新主版本之后发布——可能触发它。Nub 豁免任何超过 14 天的版本,所以老旧的、未撤销的后移植解析,而新发布的降级仍对照完整历史检查。在 .npmrc 中扩大窗口、清除单个包或关闭检查:

trustPolicyIgnoreAfter=20160          # 年龄豁免(分钟)(默认 14 天)
trustPolicyExclude[]=tailwind-merge   # 豁免一个包,无论年龄
trustPolicy=off                       # 完全禁用检查

存储和磁盘布局

无论现任者如何,Nub 通过全局内容寻址存储安装并链接到隔离虚拟存储——aube 的方案,使用 Nub 自身的目录名称。

全局内容存储

包文件通过内容哈希在全局存储 $XDG_DATA_HOME/nub/store/v1/(默认 ~/.local/share/nub/store/v1/)中去重。每次安装从中导入,所以给定包版本在磁盘上落一次并跨项目共享。

$ nub store path
/Users/you/.local/share/nub/store/v1

文件通过 reflink(APFS/btrfs)、hardlink(ext4)或复制回退物化到 node_modules——取决于文件系统支持——所以填充的树几乎不增加磁盘开销。

虚拟存储

默认 node_modules 布局是隔离的:直接依赖在顶层,传递包链接到每项目虚拟存储,幻影依赖失败而非意外解析。Nub 的虚拟存储是 node_modules/.store/(pnpm 使用 node_modules/.pnpm/)——相同形态,不逐字节共享,所以交替工具重新链接树。

每个现任者默认为隔离——npm、Yarn 和 Bun 也包括,与 pnpm 和 Nub 身份项目并排。依赖幻影(未声明的)依赖的项目通过 .npmrc 中一行选择进入扁平的、npm 风格布局:

node-linker=hoisted

--node-linker hoisted 标志对单次命令做同样的事。当未声明的包在运行时解析失败时,Nub 的错误命名它并指向此退出选项。参见虚拟存储了解每包管理器分解和扁平 vs 项目本地选择。

共享 vs 每项目存储

在 CI 之外,隔离虚拟存储跨项目共享:包版本每机器物化一次,每个项目链接到那个副本。它快速且磁盘便宜,但机器本地——链接延伸到项目外部,所以复制到另一台机器的 node_modules 不会解析。

在 CI 中,以及 nub ci 下,每个项目获得自己自包含的虚拟存储——真实目录和相对链接,无共享。该树在 Docker COPY --from 到新镜像时存活,共享存储不存在的地方。为任何安装强制它,在 .npmrc 中设一行:

enableGlobalVirtualStore=false

两种布局解析相同的包——包括传递引入但未声明的类型包(@types/*),在两者中都保持可达。

一些包静态导入你在运行时选择的后端——@hookform/resolvers/zod 导入你应用安装的 zod,但不声明它。在共享存储下,适配器的真实路径位于项目外部,所以 Node 的目录遍历无法到达该后端,导入抛出。Nub 自动将这些适配器物化到项目中以使它们解析,而树的其余部分保持共享。

相同的处理覆盖将生成代码写回自身安装目录的包。Prisma 的 postinstall 运行 prisma generate,它将生成的客户端写入磁盘上 @prisma/client 旁边——在共享存储下该位置是机器全局的,按版本而非按项目或 schema 键控。两个有不同 Prisma schema 的项目否则会共享一个生成的客户端并静默覆盖彼此的模型。Nub 将 @prisma/client 物化到每个项目以使 generate 保持项目本地,与 pnpm 一致。此检测是自动的——它扫描每个包的真实发布代码,所以没有要维护的列表。

按真实路径解析模块的打包器——Metro(裸 React Native)、Next.js、Parcel——只爬取项目目录,所以看不到共享存储。Nub 自动给这些项目自包含的、项目本地存储。在 .npmrc 中扩展列表:

disableGlobalVirtualStoreForPackages[]=my-bundler

Vite 是保持共享存储的例外。其开发服务器通过可配置的白名单而非解析器爬取来门控真实路径文件访问,所以通过 /@fs 服务的存储驻留模块否则会被 403 … outside of Vite serving allow list 拒绝。Nub 自动告诉 Vite 关于存储的信息:它写入 node_modules/.modules.yaml,Vite 8.1+ 原生读取,对于旧 Vite 它将相同的检查回溯移植到项目自身的副本中。vite dev 随后无需 vite.config 更改即可服务存储,修复位于磁盘 node_modules 中——所以无论 Nub 是否在进程中(使用纯 Vite 的队友,或 CI)都有效。

离线安装

与 pnpm 一样,当存储已持有每个包时,Nub 通过 reflink(APFS/btrfs)或 hardlink(ext4)从全局存储重新链接文件到 node_modules——无重新下载,无逐字节复制。下方基准测试衡量热重装情况:node_modules 被删除,包已在磁盘上,安装器重建项目布局。

热重装 · TanStack Start · macOS

nub install171 ms
bun install686 ms · 慢 4×
pnpm install3193 ms · 慢 18.7×
npm ci5316 ms · 慢 31.1×

查看基准 →

热重装,非冷安装

这些数字是热重装情况——填充的存储和现有锁文件,node_modules 已清除——重新链接路径是全部成本。冷安装(空存储,从注册表获取)是不同的负载,Nub 在那里不领先。

nub install            # 存储已持有每个包时离线
nub install --offline  # 强制离线
nub install --prefer-offline  # 先尝试缓存