Files
dsh_shenxian/dsh-server-docs/04-调整方案/36-功能插件分区铺给普通用户.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

4.2 KiB
Raw Blame History

36 · 功能插件分区铺给普通用户 + 新用户自动铺(批次 2)

  • 日期:2026-09-11
  • 触发:用户澄清「让用户在设置里面基于插件模式,启用和禁用插件,而不是让用户去门户管理页面」
  • 状态:✅ 已实施(commit adea865)并验证

一、需求落点

@dsh-local/business-plugins 就是「dsh 设置面板 → 功能插件」分区(档案 16 阶段 2 交付):

它做什么 实现
只列 admin 投放的插件 读门户 GET /api/plugins/mine(数据源 = 功能插件池),社区 3408 款进不来
批量勾选启停 列表 + 应用更改 + 确认弹窗 + 异步进度(⏳ 阶段(N 秒))
生效方式 门户装进该用户 profile → 重启其实例

问题:它只装在 admin profile,普通用户看不到该分区。→ 本档案把它铺给普通用户。

二、实施

A. scripts/ensure-biz-plugins.cjs(新建,幂等;一个实现两处调用)

判定(关键):看 **bundles**,不看 dependencies
  · 包内 cordis.patch.yml 只对 bundles 成员生效 —— 只有 dep 而没进 bundles 等于没装
  · 实测 guest 正是这种「有 dep、没 bundles」的状态 → 只需 reconcile,不必重装
动作:复制产物 tgz 到用户 ws(chown 用户)→ `pnpm add file:<tgz>` → 对齐 dsh plugin add 的 reconcile
参数:默认所有 role=active 的非 admin 用户;可指定 userId/username;--all;--restart

B. 新用户自动铺(双层)

层 做法
审批时 admin.ts 的 /api/users/:id/approve 在改角色后 detached spawn 该脚本(fire-and-forget,不阻塞审批响应)
巡检兜底 /etc/cron.d/dsh-maintenance 增加 每天 04:30 跑同一脚本(覆盖审批时失败的、以及历史遗留用户)

三、顺带修掉的同类隐藏 bug:ERR_PNPM_ADDING_TO_ROOT

guest 的 profile 里有 dsh 自动生成的 pnpm-workspace.yaml:

packages:
  - .
nodeLinker: hoisted
autoInstallPeers: false

→ pnpm 把它当 workspace root,pnpm add 直接拒绝:ERR_PNPM_ADDING_TO_ROOT。 这会让任何有该文件的用户都装不上插件,而现有 mine/apply 路径同样中招(只是 admin 的 profile 恰好没有这个文件,所以一直没暴露)。

修法:所有 pnpm add / pnpm remove 加 --ignore-workspace-root-check(在有无该文件的两种 profile 下都工作)。涉及 business-plugins.ts 与铺装脚本。

四、验证

检查项 结果
guest 铺装 ✅ 从「有 dep、没 bundles」→ bundles=4(含 @dsh-local/business-plugins)
bundle 是否真被加载 ✅ 重启实例后 $DSH_HOME/.dsh-biz-plugins.log 写入新的 apply pid=2 标记(该标记正是 host 面 apply() 打的)→ cordis 确实加载了它,设置面板的分区随之出现
pnpm add 在 guest profile 下可用 ✅ 加 flag 后 + @dsh-local/business-plugins 0.1.0 Done
cron ✅ 每天 04:30 巡检已入 /etc/cron.d/dsh-maintenance
编译 ✅ tsc 零报错(期间修掉 execFile 的 TS 重载问题,改用 spawn + unref())

五、踩坑记录

  1. 编排器 env / TS 重载:execFile(file, args, { stdio: 'ignore' }, cb) 在本 TS 版本报 「'stdio' does not exist in type 'ExecFileOptions'」→ 改用 spawn(..., { stdio:'ignore', detached:true }).unref(), 这也是更地道的 fire-and-forget(无僵尸、真正不阻塞)。
  2. 判断「装没装」要看 bundles:只看 dependencies 会误判(dep 在、bundles 不在 → 等于没装)。
  3. pnpm-workspace.yaml 会让 pnpm 拒绝 add —— 见上。

六、后续

  • portal-entry 不应铺给普通用户(那是 admin 回门户的入口);本脚本只铺 business-plugins,符合既有设计。
  • 批次 3(视觉与交互):改 --dsw-* 官方 token(自动跟随亮/暗/主题)、搜索 + 状态徽章 + 行内失败重试、官方 locale(zh/en)、前端 location.reload()。
  • 巡检脚本不带 --restart → 只补 profile,实例在下次重启时生效;若要求"立即生效"需调用方再触发一次实例重启。