Verdaccio|Verdaccio publish 时包含 deprecated 导致历史版本丢失问题原因分析

背景 公司内部的 NPM 因为一些固有的 bug 经常被吐槽,最近刚好有时间可以来做优化,然后就尝试解一下之前遇到的一个 publish 的 bug,下边是分析记录。
问题现象 公司内网 NPM 选择的是使用 verdaccio 来做服务,目前遇到了一个模块 publish 时包含 deprecated 字段导致历史版本丢失,仅剩下本次 publish 的版本信息。
问题原因 NPM CLI 实现 deprecate 的时候流程是这样的:
https://github.com/npm/cli/bl...

  1. 请求 get 接口获取当前模块的信息
  2. 然后修改符合的版本 deprecated 字段
  3. 请求 put 接口更新模块
【Verdaccio|Verdaccio publish 时包含 deprecated 导致历史版本丢失问题原因分析】然后新增模块在 CLI 的实现是:
https://github.com/npm/libnpm...
  1. 读取本地 package.json 内容
  2. 请求 put 接口上传模块
Verdaccio 在实现 server 的时候,更新模块和上传模块是同一个服务接口,两个动作之间又没有处理好:https://github.com/verdaccio/...
此部分逻辑为 deprecated 处理逻辑:https://github.com/verdaccio/...
local-storage 处理逻辑:https://github.com/verdaccio/...
这里删除了无效版本的信息:https://github.com/verdaccio/...
举例场景,如果已经有一个模块存在 1.0.0、1.0.1 的版本,那么触发 publish 上传 1.0.2 版本的时候服务接收到的数据是这样的:
{ "name": "module_name", "version": { "1.0.2": { "deprecated": "xxx" // 如果有的话 }, } }

而如果直接调用 npm deprecate module_name@1.0.0 "xxx" 的时候服务接收到的数据是这样的:
{ "name": "module_name", "version": { "1.0.0": { "deprecated": "xxx" // 如果有的话 }, "1.0.1": {} } }

而两者在服务端的处理逻辑是一样的:
  1. storage 修改对应的版本信息
  2. 过滤移除失效的版本信息(比如这里就会把 1.0.0、1.0.1 的信息移除)
  3. 使用当前 metadata 覆盖原有的 package.json 信息
最终导致如果 publish 的时候 package.json 中包含 deprecated 参数则会出现历史版本丢失的情况。
修复方式 修复方式也比较简单,其实主要就是能够区分出当前接口触发是 deprecate 导致的还是 publish 导致的就可以了。
那么我们就通过手动读取一次当前模块的 versions 信息,然后对比本次接口触发时接收到的 metadata,如果是 publish,那么这里一定不会匹配上的。
那么就可以在触发 deprecated 的时候新增一个检测,检测是否为 publish 时携带了 deprecated,这种情况直接忽略,进入原有的新模块上传流程。
问题总结 总结来说,deprecated 字段更像是一个 NPM 内部约定的字段,而非一个需要使用者写到 package.json 中的显性字段,如果需要对版本添加废弃信息,请用官方推荐的方案:https://doc.codingdict.com/np...
以及已经把 PR 提给官方了,期待一波回复:https://github.com/verdaccio/...

    推荐阅读