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