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

77 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`:
```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,实例在下次重启时生效;若要求"立即生效"需调用方再触发一次实例重启。