Verdaccio publish 時包含 deprecated 導致歷史版本丟失問題原因分析

賈順名發表於2021-12-14

背景

公司內部的 NPM 因為一些固有的 bug 經常被吐槽,最近剛好有時間可以來做優化,然後就嘗試解一下之前遇到的一個 publish 的 bug,下邊是分析記錄。

問題現象

公司內網 NPM 選擇的是使用 verdaccio 來做服務,目前遇到了一個模組 publish 時包含 deprecated 欄位導致歷史版本丟失,僅剩下本次 publish 的版本資訊。

問題原因

NPM CLI 實現 deprecate 的時候流程是這樣的:
https://github.com/npm/cli/bl...

  1. 請求 get 介面獲取當前模組的資訊
  2. 然後修改符合的版本 deprecated 欄位
  3. 請求 put 介面更新模組

然後新增模組在 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/...

相關文章