前言¶
DSH 的理念是「一切皆插件」,插件裝在哪臺主機、裝到哪個版本,基本靠每臺機器自己維護。當你有桌面、服務器或幾臺開發機時,要麼手動逐臺對齊,要麼維護一個 everyone 都能讀的倉庫包清單。前者費事,後者要比對版本、處理衝突,而且倉庫的讀寫權限還要額外管理。
dsh-plugin-syncup 解決這個問題的思路是:清單本身不進版本庫,而是存到一個私有的 GitHub Gist,由插件負責推送和拉取。它由 jijijitian 維護,源碼在 GitHub 上,當前是 2 stars 的小項目。下面介紹它的定位、安裝和典型用法。
這是什麼¶
一句話定位:這是一個 DSH 插件,用來把本機的 profile bundle 清單寫入一個 secret GitHub Gist,再從其他主機讀回同一份清單,並通過插件頁預覽衝突後由你決定是否應用。
它做的是「同步清單」,不是「同步配置」或「同步代碼」。同步的目標只是插件清單本身(哪個 bundle 啓用、什麼版本、什麼來源),以及一次可選的插件版本刷新。
幾個明確的邊界:
- 2.0.0 起只做手動同步,不再啓動自動同步、定時同步或插件變化自動同步。
- 2.2.0 起清單存儲從 GitHub 倉庫改爲私有 Gist。
- 2.4.0 起按 Gist 名稱查找,默認名稱
dsh-plugin-sync,不存在時首次推送自動創建。 - 當前版本 4.0.0,面向 DSH
0.2.1-rc.3的插件頁配置槽位plugins.bundle.config。 - 插件本身沒有 npm 運行時依賴,許可證是 MIT。
核心能力¶
從 README 可以覈對到的能力:
- 在 DSH 的插件頁直接修改本插件配置,不需要改代碼。
- 第一次點擊「創建 Gist 並推送」,插件會自動創建私有 Gist。
- 其他主機只需要填寫同一個 Gist 名稱,不用自己建倉庫或建 Gist。
- 點擊「拉取線上清單」只讀取和預覽,不安裝任何東西。
- 需要上傳本機清單時,手動點擊「推送本機清單」。
除此之外還有一個獨立的「插件更新」選項卡,繞過清單直接問 npm 和 GitHub 的真實最新版本,並逐項選擇安裝通道。這一部分後面單獨展開。
安裝與啓用¶
安裝命令來自 README:
plugin_manager action=install_bundle target=github:jijijitian/dsh-plugin-syncup
安裝後打開 插件 → 已安裝插件 → dsh-plugin-syncup,就能看到配置和手動同步面板。注意每臺主機都要先裝一次本插件,它不會自動安裝自己。
如果從 1.x 升級,或者首次啓用頁面,需要重啓 DSH。原因是 DSH 的 client-modules 會按 Loader 條目緩存包元數據直到進程重啓;先裝過沒有 dsh.client 的 1.0,再裝 2.x,頁面不會立即出現配置面板,重啓一次後纔會掃描到新的客戶端半。之後普通刷新頁面即可。
GitHub Token¶
Gist 需要 GitHub token,這是插件唯一需要你準備的憑據。
- 經典 token 需要
gistscope。 - fine-grained token 對 Gist 的支持有限,README 建議使用 classic token。
- 在插件頁的「GitHub Token」輸入框填寫即可;token 會保存到 DSH 的憑據服務,不寫入插件配置文件。
- 首次填寫時點擊「如何獲取 Token」,會先顯示所需權限和操作步驟,再提供「打開 GitHub Token 頁面」的鏈接,鏈接已預填描述和
gist權限。 - 如果已有環境變量
GITHUB_TOKEN,頁面會顯示「已配置」,可以留空不修改。
沒有 token 時仍然可以讀取可訪問的 Gist 並預覽,但不能創建或推送。
典型用法:第一臺主機創建 Gist¶
配置好 token 之後,第一臺主機的操作:
- 保持「Gist 名稱」爲默認的
dsh-plugin-sync。 - 點擊「創建 Gist 並推送本機清單」。
插件會依次做這幾件事:
- 創建一個 secret Gist,不是 public。
- 寫入默認文件名
dsh-plugins.json。 - 把本機可移植的 profile bundle 清單寫入
hosts.<hostId>。 - 把 Gist 名稱和 ID 寫入本機狀態。
- 在頁面上顯示 Gist 名稱、ID 和 URL。
之後把 Gist 名稱告訴其他主機即可。同一 GitHub 賬號下會按名稱查找。
典型用法:其他主機拉取與衝突確認¶
其他主機在「Gist 名稱」裏填同一個名稱,保存配置,然後點擊「拉取線上清單」。插件用當前 token 搜索該賬號下的同名 Gist;找不到時,首次推送會自動創建。不需要創建倉庫,也不需要創建新的 Gist。
點擊「拉取線上清單」只讀,不裝。頁面會顯示線上主機數、插件記錄數,並逐條標出缺失、升級、降級、來源變更、啓用、本機獨有,以及衝突數量、來源主機和每個條目的推薦選擇。
默認衝突規則摘錄如下:
| 情況 | 默認處理 |
|---|---|
| 本機缺失,線上啓用 | 安裝線上版本 |
| 本機版本較低 | 升級到線上版本 |
| 本機版本較高 | 保留本機,不降級 |
| 版本相同但來源不同 | 保留本機,避免來源突換 |
| 本機已安裝但停用,線上啓用 | 保持停用 |
| 多臺線上主機版本或來源不一致 | 使用最高版本 |
| 只有本機有 | 保留,可手動選擇刪除 |
| 線上已停用 | 不自動停用本機 |
只有選擇「使用線上版本」或「刪除本機插件」的條目,會在點擊「應用同步」時執行。預覽快照保留 10 分鐘,過期後需要重新拉取。
刪除是不可逆操作,會調用官方 ctx.pluginManager.removeBundle() 卸載包並移除 profile 中的選擇,重啓後生效。所以選中刪除後,應用按鈕會變成危險色並要求二次確認。由 DSH 安裝提供、不可移除的插件只顯示「保留」。
應用同步時,Host 對安裝、升級、來源變更條目調用 ctx.pluginManager.installBundle(),對啓用條目調用 ctx.pluginManager.setBundleEnabled(),對選擇刪除的本機獨有條目調用 ctx.pluginManager.removeBundle()。沒有選擇的插件不會被自動禁用或刪除。單次最多處理 maxInstallsPerRun 項,可能出現 restart-required,按 DSH 提示重啓即可。
插件更新選項卡:問包源而不是問清單¶
Gist 清單隻知道「某臺主機上次推送的版本」,會落後於真實發布版。README 給的例子:清單裏寫 dshmarket@1.70.2,npm 上已經是 1.73.3。
插件更新選項卡繞過清單,直接問包源:
| 來源 | 查詢方式 |
|---|---|
| npm 包 | GET registry.npmjs.org/<name> 的 dist-tags.latest,缺失時取 versions 裏最大的版本號 |
| GitHub 源碼 | GET api.github.com/repos/.../releases,取最新的正式 release |
| npm 包 + GitHub 倉庫 | 從 npm 完整元數據的 repository 字段拿到倉庫,再查 GitHub 並與 npm 版本比較 |
GitHub 側有四條規則:
- 以 release 爲準,而不是 tag。只有成功讀到 release 列表且確實爲空時才退回 tag,並在界面標註「GitHub tag(無 release)」。請求本身失敗會明確報錯,不會悄悄改用 tag。
- 按發佈時間取最新,而不是版本號最大。默認按 release 的
published_at取最新;拿不到時間戳時才退回版本號比較。 - 只取正式版,過濾平臺構建。draft、
prerelease: true、帶預發佈後綴的版本、帶+構建元數據的平臺版本都跳過。 - 版本相同則用 npm。只有 GitHub 版本嚴格更高時才選 GitHub。
npm 規格的包纔會自動補查 GitHub;repository 不存在或不是 GitHub 時退回只查 npm。npm 查詢使用 Accept: application/json,因爲 repository 字段只出現在完整元數據裏。
自動規則只是默認建議。列表裏每個可更新項都帶下拉框,可以逐項指定「自動」「npm」「GitHub」或「不更新」。某一通道沒有可用版本時,對應選項置灰並顯示「無」。流程是:
- 點「檢查最新版本」,日誌逐條實時輸出,每查完一個插件立刻寫一行,進度條同步前進。
- 勾選要更新的插件,並按需爲每項選擇通道,可用「選中全部」「清除選擇」批量操作。
- 點「更新選中項」,只對選中的插件重新檢測並安裝,日誌開頭顯示
checking N selected bundle(s)。
file:、link:、https://...tgz 這類本地或打包來源無法查詢,會標記爲「無法查詢」,而不是猜一個版本。
升級已安裝插件的保護機制¶
dsh 0.2.1 的插件管理器在存在 hmr 服務時,只要包已經在 profile 的 dependencies 裏,installBundle 就一律返回 already-installed。也就是說沒有原地升級這條路,插件頁和 dshmarket 都是用「先卸載、再安裝」繞過去的。
本插件採用同樣的兩步,但加了三層保護,因爲 removeBundle() 一旦以 bundle-in-use 失敗,插件已經被從 dsh.profile.bundles 裏移除了,表現爲裝了但不生效,重啓也回不來。
- 先探測,不盲目刪除。升級前先調
setBundleEnabled(name, false)探測;如果管理器返回restart-required,或插件仍是enabled狀態,就判定 HMR 無法卸載,直接放棄,不執行刪除。 - 任何刪除失敗都立刻恢復選中。刪除被拒後調
setBundleEnabled(name, true)把插件放回 profile,不會留下「裝但停用」的狀態。 - 記住哪些插件只能手動更新。探測失敗的插件寫入
state.manualUpdates,預覽裏顯示「需手動更新」徽標與原因,再次同步時直接跳過,不再重複嘗試刪除。
完整流程是:照常安裝,遇到 already-installed 就記錄當前版本;在 manualUpdates 裏就直接跳過;探測確認能卸下後寫 pendingUpgrades,再 removeBundle() 和 installBundle();刪除失敗就恢復選中;新版裝不上就用舊 spec 裝回並提示已回滾;成功或回滾後清除 pendingUpgrades。
兩個例外:本插件不會升級或刪除自己,因爲刪除會卸載正在運行的代碼;由 DSH 安裝提供、removable: false 的插件只顯示「保留」。
手動更新步驟¶
探測失敗的插件不會自動升級,而是直接給出一段可複製的終端命令。README 中的示例:
1. Quit DSH completely
2. cd "C:\Users\jht\.dsh\profiles\desktop"
3. "C:\Users\jht\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe" ^
"C:\Users\jht\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\bin\pnpm.cjs" ^
add dsh-mnemon@0.5.36
4. Start DSH again
要點是:命令用的是 DSH 自帶的 node 和 pnpm,不依賴系統 PATH,複製即可運行;用 pnpm add 一步完成,不需要先 remove,因爲 DSH 關閉後沒有 HMR 競爭。profile 目錄由 DSH_HOME + DSH_PROFILE 推導,也可以用 DSH_PROFILE_DIR 覆蓋。失敗項裏帶 manual 字段,plugin_sync 工具的 apply 結果也會把這些命令打印出來。
錯誤信息¶
插件管理器的拒絕大多隻帶 code,沒有 message。插件會把這些 code 翻譯成可讀句子,避免頁面上出現 [object Object]。
| code | 頁面顯示 |
|---|---|
already-installed |
該包已在此 profile 中;升級需要先卸載再安裝 |
incompatible-version |
該包要求的 DSH 版本與當前宿主不同 |
not-bundle |
該包沒有 bundle patch,無法啓用 |
management-required |
該 bundle 由 DSH 安裝管理,無法從 profile 修改 |
bundle-in-use |
HMR 無法在運行中卸載該插件;已保持其啓用狀態 |
適用場景與注意¶
適合的場景:有兩三臺以上 DSH 主機,希望插件清單基本一致,又不想維護一個公開的倉庫清單;或者希望順帶把各主機上的插件版本對齊到真實最新。
需要注意的點:
- 插件以當前 DSH 進程的權限運行,同步和安裝動作本質上與插件頁手動操作相同。安裝前三思,最好先檢查一下插件源碼。
- 插件自身有許可證 MIT,清單文件
dsh-plugins.json存的是插件名、版本和來源,不包含 token。 - 它只做手動同步,指望它開機自動對齊是不現實的。
- 同步安裝走 DSH 官方插件管理器
ctx.pluginManager,底層由 DSH 自帶的 pnpm 在 profile 目錄執行,因此仍然可能遇到restart-required。 - 每臺主機都要先裝一次本插件,它不會自動安裝自己。
另外說明一點生態背景:DSH 的社區目錄是獨立站點,與 DeepSeek、幻方無官方從屬關係,本插件屬於社區項目。
結尾¶
dsh-plugin-syncup 的價值在於把「對齊多主機插件清單」這件事收斂到插件頁裏的一次拉取和應用,衝突先預覽再執行,安裝失敗有回滾和手動兜底。它不追求自動化,標準化程度也有限,但對手動同步這個場景覆蓋得比較完整。
目錄頁:https://www.skillhub.cn/plugins/jijijitian/dsh-plugin-syncup
源碼:https://github.com/jijijitian/dsh-plugin-syncup