起初在構建 MoeKoe Music 插件生態時,還沒有線上插件市場這個功能
後來在社區的建議下便有了這個插件市場 增加官方/社區插件倉庫以擴展產品功能

插件可以由社區開發,那插件市場應該怎麼管?

最直接的做法當然是把所有插件源碼都收進一個大倉庫裡。但這個方案越想越彆扭。每個插件都有自己的作者、自己的發佈節奏、自己的構建方式。把它們全塞進官方倉庫,不僅維護成本高,也會把責任邊界搞得很模糊。

所以這個倉庫最後沒有被設計成「插件源碼倉庫」,而是變成了一個「插件登記與索引倉庫」。它只做幾件事:

  • 接收插件上架、更新、下架和舉報申請
  • 自動做一輪基礎校驗和靜態識別
  • 把人工審核通過時的插件快照記錄下來
  • 維護客戶端可以讀取的 plugins.json
  • 順手把 README 裡的插件列表更新出來,方便人看

一個可追溯的登記簿:誰提交的、審核的是哪個版本、下載地址是什麼、是否有網絡或文件權限,都落在一份清楚的數據裡。

市場索引,而不是源碼倉庫

倉庫根目錄最重要的文件是 plugins.json。它就是插件市場真正消費的數據源。

{
  "id": "custom-app-background",
  "name": "自定義背景圖",
  "description": "為MoeKoe Music提供自定義背景圖能力,支持透明度調節。",
  "iconUrl": "...",
  "version": "1.0.0",
  "minversion": "1.6.1",
  "...":"....",
  "buildRequired": false,
  "networkAccess": false,
  "fileAccess": false,
  "binaryContent": false,
  "snapshot": {
    "iconUrl": "...",
    "repository": "MoeKoeMusic/custom-app-background-plugin",
    "commitSha": "dbf72d38c8cf6b1d1cefdf8ce15798d565678995",
    "downloadUrl": "hxx.zip",
    "release": null
  }
}

我不想讓市場記錄永遠指向插件倉庫「當前最新代碼」。因為作者今天提交的代碼,和審核那天看到的代碼,可能已經不是同一份東西了。插件市場真正應該承認的是:審核通過時的那個版本。

所以這個專案裡面有一個核心原則:

上架的不是一個會漂移的倉庫地址,而是一個已經鎖定的快照。

對於不需要編譯的插件,快照鎖到默認分支當時的 commit。對於需要編譯的插件,快照鎖到對應的 Release tag 和發行附件。這樣後面真出了問題,也能回頭知道當時到底審核了什麼。

Action 先跑,人最後拍板

用戶提交 Issue 時,需要選擇:

  • 是「新上架」還是「更新插件」
  • GitHub 倉庫地址
  • 安裝前是否需要編譯

Action做自動校驗,然後把結果寫回 Issue 評論,同時給 Issue 打上 check-passedcheck-failed 標籤。

自動化負責整理事實,人負責做最終判斷:

  1. 用戶用 Issue 模板提交插件
  2. Action 解析 Issue 表單
  3. 校驗倉庫、manifest、版本、作者權限
  4. 鎖定插件快照
  5. 自動評論校驗結果
  6. 維護者人工查看
  7. 維護者用 Close as completed 關閉 Issue
  8. 另一個 Action 讀取校驗結果,生成更新 plugins.jsonREADME.md 的 PR
  9. PR 合併後,插件正式進入市場索引

這裡的「關閉方式」也被利用了起來。如果 Issue 是 Close as not planned,腳本不會執行入庫。也就是說,GitHub 原生的 Issue 狀態就被拿來當成了審核按鈕。

快照機制

如果用戶說「安裝前不需要編譯」:

  • 讀取倉庫默認分支
  • 拿到當前 commit sha
  • 用這個 commit 去讀取 manifest.json
  • 生成固定 commit 的源碼 zip 地址
  • 下載 zip,解壓後做權限掃描
  • 生成 repository-tree 類型快照

如果用戶說「需要編譯」:

  • 找最新可用 Release
  • 取 Release 裡的第一個附件
  • 下載發行附件
  • 解壓附件並讀取其中的 manifest.json
  • 生成 release-asset 類型快照

這兩個路徑解決的是同一個問題:不管插件源頭怎麼發佈,最後都要得到一份可審核、可下載、可追溯的快照,不會因為最新倉庫的變動而導致用戶下載的插件非審核時的版本。

權限識別

安全審核這件事,不能只靠腳本拍腦袋。但腳本可以先幫維護者把風險點標出來:

  • manifest 裡聲明了什麼權限
  • 源碼或發行包裡是否出現了典型 API 或可執行文件

它不是只給出「有風險」這幾個字,而是會告訴你為什麼判斷有這個能力,比如:

  • manifest 聲明了網絡訪問
  • 源碼裡用了 fetch
  • 發現了 .exe.dll.node 之類可執行內容
  • 源碼裡用了 localStorageindexedDB

這當然不是完美的安全掃描。它會有漏報,也可能有誤報。但它的定位不是取代人工審核,而是把維護者最應該多看一眼的地方提前圈出來。

它不是「審判官」,更像「標註筆」。

隱藏快照數據

校驗完成後,會在 Issue 下寫一條評論。評論裡除了人能讀懂的結果,還有一個隱藏的 payload。

它會生成類似這樣的 HTML 註解:

<!-- plugin-publish-snapshot:base64... -->

這個設計挺巧的。因為 GitHub Actions 的不同 workflow 之間不是一直共享內存的。上架校驗發生在 Issue 創建或編輯時,真正入庫發生在 Issue 被關閉時。中間可能隔了幾小時甚至幾天。

那關閉時怎麼知道當初校驗通過的是哪個快照?

答案就是:從 Issue 評論裡讀回來。

這讓 Issue 評論不只是「提示信息」,還變成了一份輕量的審核記錄。它不需要額外數據庫,也不用搭服務,直接借用了 GitHub 本身的記錄能力。

入庫動作

真正寫入 plugins.json 的腳本是 scripts/publish-plugin-close.js

它先做幾道門:

  • 當前事件必須是 publish 類 Issue
  • Issue 必須是 state_reason === 'completed'
  • 關閉 Issue 的用戶必須是倉庫維護者
  • 校驗評論裡的 payload 必須是 check-passed
  • payload 必須有鎖定的 downloadUrl

這幾道門保證了一個事情:普通用戶不能靠關閉 Issue 或偽造流程把插件寫進市場。

這裡有一個細節:更新插件時,會保留原作者,不會因為別人提交更新 Issue 就改掉作者字段。同時更新必須由原作者本人提交,倉庫地址也必須和現有記錄一致。

新的或更新後的插件會放到列表最前面。這個選擇也挺符合插件市場的直覺:最新通過審核的內容排在前面,用戶和維護者都更容易看到最近變化。

最後用當前 plugins.json 生成 Markdown 表格替換 README 中的插件列表。

下架和舉報

它的流程和上架很像:

  1. 用戶創建下架或舉報 Issue
  2. plugin-moderation-validate.js 校驗插件是否存在
  3. 如果是作者主動下架,校驗提交人是否為作者
  4. 維護者人工審核
  5. 維護者用 Close as completed 關閉
  6. plugin-moderation-close.js 把插件狀態改成 delisted
  7. 自動生成 PR 更新 plugins.json 和 README

AI 審核

專案裡還有一個 publish-plugin-ai-audit.js,它會對插件快照做一次 AI 靜態審查。

這個腳本的思路是:

  • 下載同一個快照
  • 選擇最多 24 個候選文件
  • 優先看 manifest.jsonpackage.json、入口文件、網絡/文件/存儲相關文件
  • 把內容傳給 AI 接口
  • 要求 AI 返回固定 JSON
  • 把審查結果寫回 Issue 評論

這裡我覺得比較好的地方是:AI 審核沒有被設計成「最終裁判」。它只是給維護者多一份參考。

這比「AI 說安全所以自動上架」靠譜得多。

為什麼不用數據庫或後台服務?

這個專案最有意思的地方,其實不是代碼多複雜,而是它盡量不引入額外系統。

它把 GitHub 自帶的東西用到了比較完整:

  • Issue Form 當申請表
  • Issue 評論當審查反饋和快照記錄
  • Label 當自動校驗狀態
  • Close reason 當人工審核信號
  • Pull Request 當數據變更入口
  • plugins.json 當市場索引
  • README 當公開展示頁
  • Git 歷史當審計日誌

所以它不需要一個後台,不需要數據庫,也不需要單獨做管理面板。對於一個開源音樂播放器的插件市場來說,這個複雜度剛剛好。

ps: GitHub Actions真的好用,包括我之前的 阿珏のBlog 的国际化之路

結尾

回頭看這個專案,它並不是那種「架構很重」的插件平台。它更像一個長得很樸素、但每個步驟都能追溯的登記系統。

用戶通過 Issue 提交,Action 自動檢查,維護者人工確認,PR 合併生效。所有關鍵動作都留在 GitHub 上,所有市場數據都落在 plugins.json 裡。出問題時能查,更新時有記錄,下架時也不抹掉歷史。

它沒有試圖一步到位做成一個完整後台,而是先把插件市場最核心的信任鏈路搭起來。

一個社區插件生態,真正需要的第一件事可能不是華麗的頁面,而是一套大家都能看懂、能信任、能覆盤、能繼續改進的流程。

這個倉庫 MoeKoeMusic-Plugins 就是這樣來的。

插件市場網頁版

當然以上這些都不是一步到位的,是一步一步更新完善到現在的結果的