让 npm 按包作用域选注册表,而不是按 VPN
一条作用域配置把公司包发往私有注册表,其余一律走公共源。路由、凭据与优先级是另外三个决定。
连接 VPN 不该意味着一天两次去改 npm 的注册表设置。项目级配置文件里的一条作用域条目,就能把公司包发往一个注册表、其余全部发往公共注册表,既不用来回切换,也不会在周五下午留下一个覆盖项。问题在于这只解决了命名;路由、凭据和配置优先级是三件彼此独立、各自会以不同方式出错的事。
两个名字,两个去向
npm 包名要么带作用域,要么不带。带作用域的包名以 at 符号加一个作用域标识符开头,配置能映射的正是这个标识符。包的其他任何信息对映射都不重要。注册表从不会根据包是内部还是公共来决定是否接收请求;它只看到一个名字。
因此下面这个文件用三行表达两个意图:公共包走公共注册表,公司作用域下的包带着该主机的令牌走公司注册表。
registry=https://registry.npmjs.org/
@acme:registry=https://packages.corp.example/
//packages.corp.example/:_authToken=${NPM_TOKEN}
把作用域、主机和变量名换成组织实际提供的东西。重要的是结构,而不是示例值。两行配置就承载了整套策略,这正是它们值得理解而不是照抄的原因。
在心里把映射和网络放进两个分开的格子。一条作用域条目无法让不可达的主机变得可达,一条能用的隧道也无法告诉 npm 某个包属于哪个注册表。
按正确的顺序写这个文件
尽管 npm 会读多个文件,推理时仍要把项目文件放在第一位。项目目录里的 .npmrc 是同事通过代码仓库继承的那一份,所以它应当表达共享策略,而不是你的个人令牌。审核者要读的也是这个文件。让它小到一眼就能看完。
除非组织另有决定,否则让默认的 registry 一行继续指向公共注册表。删掉它会让机器依赖用户级文件说了什么,而这正是那种会让两台笔记本得出不同结果的隐式依赖。
作用域那一行不是通配符。作用域之外的名字会继续使用默认值,对于每个不是你自己写的依赖,这正是你想要的行为。
在提交之前先确认文件会解析成什么,用一个临时包名而不是真正安装,这样即使出错也没有代价。
凭据属于主机,不属于项目
认证条目按注册表主机来键控,在注册表需要时还按路径来键控。这就是令牌那一行以双斜杠加主机开头、而不是以作用域开头的原因。一旦你看到主机出现在那个位置,这一行剩下的部分就不再像随意的标点了。它是一个地址,令牌挂在它上面。
使用环境变量可以让字面令牌不进仓库里的文件。这并不会让令牌到处都看不见;它仍然出现在进程环境里,一条打印配置的命令就可能把它暴露出来。
通过组织认可的任何本地或 CI 凭据机制来提供这个变量,永远不要把真实令牌粘进可能被复制到文档里的示例。
如果涉及两个注册表,各自都要有自己的条目。单一而不限定主机的令牌设置,往好里说是含糊,往坏里说会被发到错误的主机。
当错误的注册表胜出
配置是分层的,生效值未必就是你刚改的那个文件里的值。项目设置、用户设置、全局设置和环境变量都参与其中。正是这种分层,使得一个修复在你的机器上有效、在同事的容器里却无效。也正因如此,在争论之前值得先把生效值打印出来。
CI 里常见的元凶是环境变量:某个任务设一次注册表,之后每一步都继承它。第二常见的是开发者机器上的用户级文件,它覆盖了仓库所说的内容。
在排查其他任何东西之前,先打印出一个带作用域和一个不带作用域的名字的生效配置与解析出的注册表。如果这两个答案符合预期,问题在网络;如果不符合,问题在配置,做多少 VPN 的活都没用。
提交前的检查清单
确认作用域名与实际发布的包相符、默认注册表是明确写出的、令牌那一行指向正确的主机。作用域里的一个笔误会造成所有故障里最奇怪的一种,因为包会从公共注册表解析,并返回别人同名的模块。先检查名字,再去排查令牌。
确认私有注册表能从执行安装的那个环境里的机器访问,而不只是从你的浏览器访问。当代理或容器与你的 shell 路由不同时,这个区别很关键。
然后在没有缓存包的干净检出上确认行为。全新安装是唯一能展示新同事会经历什么的测试。