Skip to content

dsh-plugin-backpack

Verified

@kxreas/dsh-plugin-backpack · v0.1.1 · MIT · Web UI

插件背包(Plugin Backpack):在「设置 → 插件背包」里记录并导出当前 DSH 插件(不含本插件)。导出四选一:完整插件包 zip、TGZ 混合包(npm 来源只写清单、非 npm 来源附带物理包)、带 SHA-256 校验的文本清单、压缩口令;清单/口令会提示非 npm 来源不能带源码。支持添加导入与全量替换导入,全量替换需输入「我确定」并自动备份,安装失败只记录一次、结束后弹窗汇总,失败自动回滚;仅非 npm 的部分包会被禁止用于全量替换。

Install

dsh plugin add @kxreas/dsh-plugin-backpack

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Published to npm without a public repository. Inspect the package contents before installing.

Tags

Creators

Readme

插件背包(@kxreas/dsh-plugin-backpack)

DSH WebUI 的插件备份、迁移与恢复工具。入口在 设置 → 插件背包,用于把当前 profile 中用户安装的插件导出为可搬运的插件包、TGZ 混合包、清单或口令,并在另一台设备上导入。

  • 导出四选一:插件包 / TGZ 混合包 / 清单 / 口令
  • 导入两种模式:添加导入 / 全量替换
  • 只处理用户安装、可移除的 profile 插件,不会列出、导出或移除插件背包自身
  • 清单带 SHA-256 与数量校验;安装失败不重试;全量替换自动备份并在失败时回滚
  • 纯 JavaScript,无原生依赖;Node 版本要求见 package.json

当前版本:0.1.1 · 许可:MIT


功能概览

能力 说明
插件信息 列出插件名、版本、安装来源、启用状态与组件行数
插件包导出 生成单个 zip:export-info.json + 每个插件一份离线包
TGZ 混合包 单个 .tgz:npm 来源只写清单,非 npm 来源附带物理包
仅非 npm 打包 只打包 git / link / file / 本地路径等非 npm 来源插件
清单导出 单个 .dsmp 文本文件,带正文 SHA-256 与插件数量校验
口令导出 生成 DSHP1. 压缩单行串,直接显示、可复制,不落盘
添加导入 只新增缺少的插件,同名插件忽略,不动已有包
全量替换 先备份,再移除全部用户插件,再安装导入源;需二次确认
失败处理 单次尝试,不自动重试;失败写入报告并在结束后弹窗汇总
回滚 全量替换任一安装失败时,恢复备份中的插件与启用状态
部分包保护 仅非 npm 的部分包只能添加导入,宿主端拒绝全量替换

环境要求

  • DSH Desktop 或 DSH Web,profile 中已启用 pluginManager 服务
  • 插件设置页依赖 settings.section 客户端插槽
  • Node.js:^22.19.0 || >=24.0.0(与 package.json 的 engines 一致)
  • 目标插件必须是通过 profile 安装、removable: true 的用户插件

开发与验证环境:DSH 0.2.1-rc.2,Node.js 24。更老的 DSH 版本可能缺少 pluginManager 或客户端插槽接口,安装后不会正常挂载。


安装

从 npm 安装

dsh plugin add @kxreas/dsh-plugin-backpack

也可以在 DSH 的插件管理界面选择“安装第三方插件”,输入 npm 包名。

仅执行 npm install 不会启用 DSH bundle。必须通过 dsh plugin add 或 DSH 插件管理器安装,让 profile 选中该 bundle。

安装完成后:

  1. 重启 DSH(宿主端插件模块在进程启动时加载);
  2. 刷新 Web 页面;
  3. 打开 设置 → 插件背包。

本地开发安装

dsh plugin add link:E:/path/to/dsh-plugin-backpack

本地 link: 安装适合开发调试;发布 npm 包后,最终用户使用包名安装即可。


快速开始

备份当前设备

  1. 打开 设置 → 插件背包,确认“当前插件”列表。
  2. 选择导出方式:
    • 插件包:适合完整迁移,包含全部插件源码文件;
    • TGZ 混合包:npm 插件只记录清单,非 npm 插件带物理包,通常更省空间;
    • 清单:只记录名称、版本、来源、启用状态;
    • 口令:只生成一段可复制字符串。
  3. 插件包 / TGZ 混合包 / 清单会弹出目录选择器;每次导出都重新选择,不保存默认目录。
  4. 导出结果会显示文件路径,或直接显示口令与复制按钮。

迁移到另一台设备

  1. 在目标设备安装并启用本插件。
  2. 把插件包 zip 或混合包 tgz 拷到目标设备,或准备清单 / 口令。
  3. 打开 设置 → 插件背包 → 导入,选择来源并点击 校验并预览。
  4. 选择 添加导入(推荐先做增量迁移)或 全量替换。
  5. 导入结束后查看弹窗中的成功 / 失败 / 回滚摘要;刷新页面查看插件变化。

导出

导出面板有四个标签,一次只产出一种结果。

插件包

生成一个 zip 文件:

dsh-plugin-packages-20261017-184500.zip
├── export-info.json
├── README.txt
└── packages/
    ├── export-info.json
    ├── dsh-meme-0.1.43.zip
    ├── dsh-meme-0.1.43.zip.meta.json
    └── ...
  • packages/*.zip 是每个插件的包根目录,包含包内真实的 node_modules 文件(符号链接会被跳过)。
  • packages/*.zip.meta.json 记录来源、启用状态、SHA-256、字节数。
  • 完整包(scope: "all")包含全部用户插件,可用于添加导入或全量替换。
  • 部分包(scope: "non-npm")只包含非 npm 来源插件,partial: true、fullReplaceAllowed: false,只能添加导入。
  • 离线包只保存插件自身文件;pnpm 安装时仍会按 package.json 解析依赖,可能需要本机 store、镜像或网络。

TGZ 混合包

生成一个 .tgz 文件,适合在“完整插件包太大、又需要带非 npm 插件源码”的场景下节省空间:

dsh-plugin-hybrid-20261017-184500.tgz
├── manifest.dsmp
├── export-info.json
├── README.txt
└── packages/
    ├── dsh-at-file-0.7.0.zip
    ├── dsh-at-file-0.7.0.zip.meta.json
    └── ...
  • 清单记录全部插件;npm 来源只写名称、版本、来源、启用状态,不带物理包。
  • 只为非 npm 来源(git / link / file / 本地路径等)插件保存物理包。
  • export-info.json 标记 kind: "dsh-plugin-hybrid"、scope: "hybrid"、fullReplaceAllowed: true。
  • 可用于添加导入或全量替换;导入 npm 插件时需要 registry / 网络可用。
  • packages/ 单独拿出来仍是部分包:sidecar 带 partial: true,直接选择 packages/ 目录不能全量替换。
  • 外层是 tar.gz,支持 .tgz / .tar.gz 上传,也可选择包含该文件的目录导入。

清单

生成一个 .dsmp 文本文件,只包含插件名、版本、安装来源与启用状态:

{"format":"dsh-plugin-manifest","version":1,"created":"2026-10-17T10:00:00.000Z","count":2,"sha256":"<正文 SHA-256>","tool":"@kxreas/dsh-plugin-backpack/0.1.1"}
{"name":"dsh-meme","version":"0.1.43","source":"dsh-meme@^0.1.43","enabled":true}
{"name":"dsh-at-file","version":"0.7.0","source":"git+https://github.com/omdsh-dev/dsh-at-file.git","enabled":false}

清单不包含插件源码。导入时按安装来源重装,因此非 npm 来源(git / link / file / 本地路径)在换机后可能无法安装。

口令

生成一段 DSHP1. 开头的压缩单行串,直接显示在面板下方,可复制,不写文件:

DSHP1.<base64url(zlib(compact-json))>

口令只包含插件名、版本、启用状态和无法从 name@version 推导的非 npm 来源。它不包含源码与离线包,导入时仍需按来源重装。

示例:18 个真实插件约 620 个字符。

非 npm 来源提示

清单和口令无法携带源码。导出前,面板会列出所有非 npm 来源插件,并提示改用“插件包”导出;插件包标签下也可以直接选择“仅打包非 npm 来源插件”。

非 npm 来源包括:

  • git+https://... / git+ssh://... 等 Git 来源
  • link: 本地目录
  • file: 本地 tarball
  • https://... 直接 tarball
  • 本地绝对路径或相对路径
  • workspace: 等非 registry 来源

导入

导入来源

  1. 插件包导出目录:目录中包含 export-info.json 与 packages/*.zip
  2. 插件包 zip/tgz:完整插件包、混合 TGZ 包、仅非 npm 的部分包,或单个插件包
  3. 清单文本:粘贴 .dsmp 内容,或从文件读取
  4. 口令:粘贴 DSHP1. 字符串,或从文件读取

导入前必须点击 校验并预览。预览会显示插件数量、将安装数量、重复数量、将移除数量、校验错误和警告。

添加导入

  • 不修改已安装的同名插件;
  • 按包名判重:本机已安装同名插件(含停用的)直接跳过,不比较版本;
  • 其余插件安装到 profile;
  • 单个安装失败只记录,继续处理后续插件;
  • 导入结束后弹窗列出失败项,不自动重试。

全量替换

全量替换会改变整个用户插件集合,必须满足以下条件:

  1. 输入确认词(面板默认显示为 我确定);
  2. 在确认弹窗中再次确认;
  3. 导入源允许全量替换(部分包不允许)。

执行流程:

  1. 把当前全部用户安装、可移除的插件备份到 ~/.dsh/plugin-backpack/rollback/<jobId>/;
  2. 移除当前用户插件(保留插件背包自身与 DSH 内置插件);
  3. 安装导入源中的插件;
  4. 任一安装失败:移除本次新装插件,恢复备份中的插件与启用状态;
  5. 将失败项、回滚状态、备份目录和报告路径展示在结果弹窗中。

部分包限制

仅非 npm 的部分包(partial: true / fullReplaceAllowed: false)只能添加导入。导入预览会显示“部分包 · 不能全量替换”并禁用全量替换;直接调用接口也会被宿主端以 PARTIAL_PACKAGE 拒绝。

部分包标记同时存在于:

  • export-info.json
  • packages/export-info.json
  • 每个 *.zip.meta.json

因此解压后只选择 packages/ 目录,或只保留 zip + sidecar,仍然能识别部分包。

TGZ 混合包是一个例外:根目录 export-info.json 标记 fullReplaceAllowed: true,因为清单包含全部插件;但 packages/ 里的 sidecar 仍标记为部分包,单独选择 packages/ 目录不能全量替换。

失败与报告

  • 安装 / 移除单次失败不会重试;
  • 失败原因、pnpm 输出末尾、日志路径写入 ~/.dsh/plugin-backpack/reports/import-<时间戳>-<jobId>.json;
  • 导入结束后的弹窗汇总成功、跳过、失败、回滚状态;
  • 回滚失败项会单独列出;
  • 旧报告不会自动删除。

导入或回滚过程中不要停用本插件、关闭 DSH 进程或强制断电。任务随进程中断, 已经移除的插件需要按备份目录手动恢复。


清单格式(开发者)

文件结构

第一行是 meta JSON,之后每行一个插件 JSON:

{"format":"dsh-plugin-manifest","version":1,"created":"2026-10-17T10:00:00.000Z","count":2,"sha256":"<正文 SHA-256>","tool":"@kxreas/dsh-plugin-backpack/0.1.1"}
{"name":"dsh-meme","version":"0.1.43","source":"dsh-meme@^0.1.43","enabled":true}

校验规则

任一规则不满足都会直接中止导入:

  1. 第一行必须是合法 meta JSON,且 format / version / count / sha256 合法;
  2. 正文(去掉 meta 行与末尾单个换行)的 SHA-256 必须等于 meta.sha256;
  3. 正文行数必须等于 meta.count;
  4. 每行必须是合法 JSON,包名、版本、来源、enabled 合法;
  5. 插件名不能重复,也不能是本插件自身;
  6. 清单引用离线包时,包的 SHA-256、字节数、包内 package.json 名称与版本必须一致;
  7. 离线包必须带 dsh.bundle.patch 或 cordis.patch.yml。

清单来自粘贴文本、没有离线包时不会报“包缺失”,但会在预览中警告,导入时按来源重装。

TGZ 混合包格式(开发者)

  • 外层:gzip 压缩的 tar,扩展名 .tgz / .tar.gz。
  • manifest.dsmp:全部插件;npm 来源条目没有 package 字段,非 npm 条目带 package / sha256 / bytes。
  • export-info.json:
    {
      "kind": "dsh-plugin-hybrid",
      "version": 1,
      "scope": "hybrid",
      "partial": false,
      "fullReplaceAllowed": true,
      "physicalScope": "non-npm",
      "count": 18,
      "physicalCount": 11,
      "npmOnly": ["dsh-meme", "..."],
      "packages": [
        { "name": "dsh-at-file", "version": "0.7.0", "file": "packages/dsh-at-file-0.7.0.zip" }
      ]
    }
    
  • packages/*.zip 只包含非 npm 插件;sidecar 带 partial: true / fullReplaceAllowed: false,单独选择 packages/ 时阻止全量替换。
  • 根目录 export-info.json 的 fullReplaceAllowed: true 优先,因此整个 tgz / 导出目录可用于全量替换。

口令格式(开发者)

DSHP1.<base64url(zlib(compact-json))>

compact JSON 仅保存:

  • 插件名
  • 版本
  • 启用状态
  • 无法从 name@version 推导的非 npm 来源

zlib 自带 adler32 校验;解压失败、Base64 非法、版本不支持都会报错。口令不包含离线包与源码。


数据与安全

路径 用途
~/.dsh/plugin-backpack/store/ 导入时生成的稳定 .tgz(离线包转 pnpm tarball)
~/.dsh/plugin-backpack/rollback/<jobId>/ 全量替换前的自动备份
~/.dsh/plugin-backpack/reports/ 导入报告
~/.dsh/plugin-backpack/tmp/ 上传包 / 导出包解压临时目录(24 小时后清理)
~/.dsh/plugin-backpack/incoming/ 浏览器上传的原始 zip(24 小时后清理)

安全约束:

  • HTTP 路由仅接受 127.0.0.1 / ::1 回环请求;
  • 所有请求必须带 x-dsh-plugin-backpack: 1 自定义头,避免网页跨站触发;
  • 浏览器上传限制 2GB;
  • zip 解压有路径穿越、符号链接、条目数量与总大小限制;
  • 安装、移除、启用统一调用 ctx.pluginManager,不手写 profile 的 package.json、锁文件或 cordis.patch.yml;
  • 插件包、TGZ 混合包、清单、口令只包含插件元数据与各插件安装目录文件;插件背包自身不额外收集 DSH 账号令牌或个人配置。

兼容性与边界

  • 只管理用户安装、可移除的 profile 插件;不管理 DSH 内置插件、availability !== "profile" 的包;
  • 只记录 bundle 的启用状态,不导出每个插件自己的配置,也不导出 profile 覆盖层(cordis.patch.yml 条目);
  • 离线包包含插件自身文件,但 pnpm 仍会解析 package.json 依赖;完全离线迁移需要目标机具备依赖缓存或镜像;
  • 从插件包 / TGZ 混合包 / 导出目录导入后,profile 依赖可能写成 file:~/.dsh/plugin-backpack/store/*.tgz,而不是原来的 link: / git 来源;想保留原始来源,请用清单或口令导入;
  • 导入完成后不会自动刷新页面,需要手动刷新以更新插件界面;
  • 安装失败不会自动重试,也不会回退到旧版本;全量替换模式除外(会整体回滚)。

故障排查

设置里看不到“插件背包”

  • 确认插件已通过 dsh plugin add 或插件管理器安装并启用;
  • 重启 DSH 后刷新页面;
  • 检查 DSH 是否包含 pluginManager 与 settings.section 接口。

页面提示“宿主端需要重启”

客户端 client.js 已更新,但当前进程仍加载旧宿主端。关闭并重新打开 DSH,让 lib/*.js 重新求值。

部分包无法全量替换

这是预期行为。仅非 npm 的部分包只包含部分插件,全量替换会删除其余插件;宿主端会返回 PARTIAL_PACKAGE。请使用完整插件包、TGZ 混合包、清单或口令执行全量替换。

非 npm 插件从清单/口令安装失败

清单与口令不包含源码。改用插件包导出,并在导入预览中确认离线包可用。

安装失败

  • 查看结果弹窗中的 pnpm 日志路径;
  • 查看 ~/.dsh/plugin-backpack/reports/ 下的导入报告;
  • 含安装脚本的包可能需要 DSH 单独授权;
  • 失败不会自动重试,修正来源后可重新预览并导入。

导入后界面没变化

刷新 Web 页面。宿主端插件已安装,但客户端插槽与侧边栏需要重新加载。


开发

目录结构

dsh-plugin-backpack/
├── package.json
├── cordis.patch.yml        # 宿主端 bundle 行
├── client.js               # 浏览器端设置页(模块加载器格式)
├── lib/
│   ├── index.js            # 宿主端路由、任务、导出/导入编排
│   ├── manifest.js         # 清单与口令格式
│   ├── plugins.js          # 插件包解析、打包、来源分类
│   └── zip.js              # zip / tar.gz / 校验工具
└── tools/                  # 开发自测,不随 npm 包发布

本地检查

node --check lib/index.js
node --check lib/manifest.js
node --check lib/plugins.js
node --check lib/zip.js
node --check client.js
node tools/smoke-archive.mjs   # zip / tar / 清单 / 口令
node tools/smoke-host.mjs      # 假 profile + 假 pluginManager 跑真实路由
node tools/smoke-client.mjs    # 可选 React SSR 渲染检查

smoke-client.mjs 依次从 DSH_BACKPACK_REACT_DIR、用户全局 node_modules、当前目录 node_modules 解析 React;没有 react-dom 时只运行模块加载检查。推荐显式指定同时包含 react 与 react-dom 的目录:

$env:DSH_BACKPACK_REACT_DIR='E:\path\to\node_modules'
node tools/smoke-client.mjs

License

MIT