NPM Publish 400

这是谁的 BUG2022-01-23

背景

公司内部有一个私有基于 cnpm 部署的 NPM Registry,带上指定前缀的 scope 进行 npm publish 即可发布到私有存储,方便托管存放一些不方便公开的业务模块 NPM 仓库。最近部分同事反馈执行 publish 一直出现 400 错误,排查了 registry 源正常、登录权限正常、推送公有 Registry 正常,换过 yarn、pnpm 均出现相同错误。


_10
npm ERR! 400 Bad Request - PUT https://xxx.com/@xxx/xxx - maintainers error

找找原因

收集了正常使用、出现问题的同事的开发环境信息进行对比,发现出现问题的环境 NodeJS 版本均为 15、16 这两个版本,而正常使用的均小于 15 版本。

  • NodeJS@14 包含 npm 版本为 v6
  • NodeJS@15 包含 npm 版本为 v7
  • NodeJS@16 包含 npm 版本为 v8

以这个推断进行了重复多次实验,验证出结论确实如此。但其中的原因又是为何呢?带着这个疑问走读了一下 npm 源码。从 npm publish 这个动作的代码逻辑链路来说,核心在于 libnpmpublish 这个模块。这个模块在 npm@6 依赖版本为 ^1.1.2,而 npm@7、npm@8 依赖版本突变为 ^4.0.0。其中原有版本逻辑在进行 publish 请求时的 metadata 数据结构为


_30
{
_30
_id: '@xxx/xxx',
_30
name: '@xxx/xxx',
_30
description: undefined,
_30
'dist-tags': { latest: '0.0.3' },
_30
versions: {
_30
'0.0.4': {
_30
name: '@xxx/xxx',
_30
version: '0.0.4',
_30
main: 'index.js',
_30
license: 'MIT',
_30
readme: 'ERROR: No README data found!',
_30
_id: '@xxx/xxx@0.0.4',
_30
_nodeVersion: '14.18.2',
_30
_npmVersion: '6.14.15',
_30
_npmUser: [Object],
_30
maintainers: [Array],
_30
dist: [Object]
_30
}
_30
},
_30
readme: 'ERROR: No README data found!',
_30
maintainers: [ { name: 'lijialiang', email: 'lijialiang@joyy.com' } ],
_30
_attachments: {
_30
'@xxx/xxx-0.0.4.tgz': {
_30
content_type: 'application/octet-stream',
_30
data: 'xxx',
_30
length: 234
_30
}
_30
}
_30
}

但升级后版本的逻辑中 metadata 数据不再带有 maintainers 字段,导致请求提交到内部仓库时,出现找不到的情况。

尝试在 libnpmpublish@4 版本 libnpmpublish 硬编码 hack 注入 maintainers 字段,可以成功正常 publish。

历史

再排查发现内部私有部署的 cnpm 版本为 2.19.3,该版本发布于 2017-03-22,过去约有 5 年之久,当下 cnpm 版本最新为 3.0.0-rc.50,该版本发布于 2021-12-04。后续升级版本再次验证 npm@7、npm@8 能否正常 publish。