# Changelog ## 0.8.8 (2026-10-16) 接 0.8.7。0.8.7 自己新加的那条「不得把作者本机路径当取值来源」的检验,**覆盖面有洞**, 发出去之后复核时发现,本版本把它补上并清掉漏网的实例。 ### Fixes - **`tools/probe_button_e2e.mjs` 的 Chrome 路径改为按平台探测**。 与 0.8.7 修掉的 `shoot_portal.py` 是同一类问题(原本写死 `C:\Program Files\Google\Chrome\Application\chrome.exe`,只有作者的 Windows 机器成立)。 新增 `findChrome()`:`--chrome` > `$CHROME` > 按 `process.platform` 找常见安装位置; 都找不到时**返回退出码 2**(跳过),而不是拿着不存在的路径去 spawn 再报一句看不懂的错。 ### Tests - **扫描覆盖面改由 `package.json` 的 `files` 推导**(递归 + 另扫 `client/`), 不再硬编码 `lib/index.js` + `tools/*.py`。实测原覆盖面漏了两类真问题: - `lib/client.js` —— 客户端面同样发货,在它里面写死路径一样会泄; - `tools` 里的 `.mjs` —— `probe_button_e2e.mjs` 的 Chrome 路径正是从这里漏掉的。 三个方向都证伪过:往 `lib/client.js` 贴路径 → 红;往 `client/index.jsx` 贴路径 → 红; 把扫描收窄回只扫 `lib/index.js` → 由新增的 `REQUIRED_SCANNED` 覆盖自检拦下 → 红。 - **加一条更狠的兜底**:作者的宿主仓库目录名(`DshProject`)在**整棵被扫代码树里 一次都不许出现**,含注释。原判定只拦「非注释行里的绝对路径字面量」,注释里写全路径 它放行 —— 而 0.8.7 起注释里其实已经不需要提那个路径了。已把 `lib/index.js` / `tools/portal_run.py` 两处注释改成中性描述(历史细节留在 CHANGELOG)。 证伪:往注释里贴 `D:\DshProject\...` → 该条即红。 - 修正 0.8.7 一节的一处**说满的话**:那里写「改判据后只揪出 1 条真问题」, 实际是扫描器当时只扫了 `lib/index.js` + `tools/*.py`,所以只看见 1 条; 扩面后又揪出 `probe_button_e2e.mjs`(本节修复)。 ### Docs - `RELEASING.md` 增加发货前守卫:`lib/ tools/ client/` 里不得出现 `DshProject`, 且 `client/` 必须被扫描覆盖。 ## 0.8.7 (2026-10-16) 用户:「把默认值改为空值,再发布」。 ### Fixes - **`repoPath` 默认值不再预置作者本机路径**(原 `D:\DshProject\ld-rl-poker`)。 那是「第一用户」的机器路径,对任何别人都不成立。更糟的是它**看起来像配好了** —— 界面不报错,用户以为装完即可用,实际 `pythonFor` 会去那台并不存在的机器上探 `.venv313` / `.runtime`,全落空后静默回落到 PATH 的 python,拉服务时用的是错的解释器。 改为三处默认值为空: - `lib/index.js` 的 `DEFAULT_REPO` - `tools/portal_run.py` 的 `DEFAULT_HOST_REPO` - `tools/start_public_gui.py` 的 `DEFAULT_HOST_REPO` / `DEFAULT_PYTHON` `portal_run.py`(新门户)里 `host_repo` **只写进状态给界面显示**,服务清单走 `runs/portal_registry.json`,所以留空不影响启动 —— 已实测:空 `repoPath` 下 `startPublic` 成功、端口在听、`HTTP 200`、返回的是「对外服务门户」。 - **`pythonFor` 在 `repoPath` 为空时不再拼相对路径**。 `join("", ".venv313", "Scripts", "python.exe")` 会得到 `.venv313/Scripts/python.exe` 这样一个**相对路径**,`existsSync` 按进程 cwd 解析 —— cwd 下恰好有同名目录就会 误命中,等于拿一个不是用户意图的解释器去拉服务。现在空值直接跳过宿主仓库那两个 候选,回落到 PATH 的 python。 - **旧启动器(`start_public_gui.py`)在宿主仓库为空时给出可操作提示**, 而不是让 `os.path.isdir("")` 打印「宿主仓库不存在:」这种空路径。现在会列出 三条可行做法(设置页 `repoPath` / `SERVICE_HOST_REPO` / 自写 `services.json`) 并返回 2。`--python` 缺省值为空时的提示也单独写清楚了,不再出现 「未找到 ,改用 …」这种带空路径的话。 - **`tools/shoot_portal.py` 的 Chrome 路径改为按平台探测**(原本写死 `C:\Program Files\Google\Chrome\Application\chrome.exe`,只有作者的 Windows 机器成立)。 新增 `find_chrome()`:`$CHROME` 环境变量优先,其次 macOS / Windows / Linux 的常见 安装位置。 ### Tests - 新增 5 条控制检验(`test/host.test.mjs`,50 → 55 项;全仓 78 → 83 项): - `repoPath` 默认为空字符串 - 运行期代码(`lib/` + `tools/*.py`)不得把绝对路径当**取值来源** - 配置 schema 的 `repoPath` 默认值不得是作者本机路径 - `pythonFor` 在 `repoPath` 为空时不拼相对路径(用 cwd 下的诱饵目录反向验证) - `pythonFor` 在 `repoPath` 非空时仍照旧优先 `.venv313`(防修过头) 每条都用**退回旧写法**验过会失败。 > 第二条的判据是「不得作为取值来源」,不是「不得出现这几个字」—— > 第一版按「出现盘符路径就报」写,跑出来 5 条里 5 条都不该报(注释里的举例、 > `os.environ.get("SystemRoot", r"C:\Windows")` 这类系统路径兜底)。 > 改判据后只揪出 1 条真问题(`shoot_portal.py` 的 Chrome 路径),已修。 ### Docs - README / README.en.md 的配置表:`repoPath` 默认值改为「**空**」并标注必须自己填。 - `services.example.json` 的 `hostRepo` 改为占位路径并补说明(该文件不随 npm 包分发)。 ## 0.8.6 (2026-10-16) 发布前核查 awesome-dsh-plugin 收录要求时,查出**发布通道上一个致命缺陷**, 以及两处不合规声明。都不影响本地 `link:` 开发形态 —— 这正是它们一直没被发现的原因。 ### Fixes - **npm 包缺少 `tools/`,装到用户机器上根本起不来(发布通道致命)**。 `package.json` 的 `files` 白名单里没有 `tools`,于是 `npm pack` / `npm publish` 打出来的包里**一个 Python 脚本都没有**(11 个文件、286 kB,全是 lib/ 与文档)。 而 `lib/index.js` 是靠 `infraPath/tools/portal_run.py` 启动门户的。 实测(装 tarball 后读状态): ``` 找不到 .../node_modules/dsh-service-portal/tools/start_public_gui.py (外网启动器不在该仓库,或 infraPath 配错) ``` 即:用户 `dsh plugin add dsh-service-portal` 装完,点「开启」必然失败。 **为什么此前没暴露**:唯一验证过的安装方式是 `link:` 本地链接 —— 符号链接指向真实仓库,`tools/` 一直在。开发形态恰好是唯一不暴露问题的形态。 修法:`files` 加 `"tools"`,并补打包契约断言(见下)。 - **`@deepseek-ai/cordis` 的 peer 范围是 `>=4`,会静默排除所有预发布版**。 node-semver 只在范围里**某个比较符**与该版本的 `major.minor.patch` 元组完全一致、 且自身带预发布标签时才放行预发布版,所以 `>=4` 匹配不到 `4.0.5-alpha.1` / `4.0.5-alpha.2`,用户在预发布版 harness 上装会撞 `ERESOLVE`。改成 contributing.md 给出的显式 `||` 分支写法: ```jsonc "@deepseek-ai/cordis": ">=4.0.1-rc.1 <4.0.5-0 || >=4.0.5-alpha.1 <5.0.0-0" ``` 用**两段式**而不是逐个枚举预发布版:`4.0.5-alpha.3`、`4.0.5-rc.1` 这类将来发布 的新预发布版会被自动吸收,不必再改 package.json(逐个枚举的写法在上游发新预发布版 时会重新变成「静默排除」)。已验证放行 cordis 全部 9 个已发布版本、拒绝 `5.x`。 - **`@deepseek-ai/schemastery` 改为「peer 声明 + dependencies 兜底」**。 原来只在 `dependencies` 里 pin 死了 `3.18.4`,不合规(contributing.md 要求官方包 声明为 `peerDependencies`,否则可能与宿主装出两份实例)。 ⚠️ **但只改成 peer 是错的 —— 这一步差点把插件改坏,必须记下来。** 本仓库把 schemastery 标成 `optional` peer,而 pnpm 会**直接跳过** optional peer, 一个都不装。于是: ``` profile vp-a:仅 peer 声明,无 dependencies 兜底 node_modules/ 下只有 dsh-service-portal -> import "@deepseek-ai/schemastery" ERR_MODULE_NOT_FOUND,插件整个加载不了 profile vp-b:peer 声明 + dependencies 兜底 node_modules/ 下出现 @deepseek-ai/schemastery -> 加载成功 ``` **是 `dsh plugin add` 实装进一次性 profile 才查出来的**:76 项单测当时全绿, 因为单测跑在开发检出上,本地 node_modules 里那份一直在 —— 与上面 Defect A 同一种「开发形态恰好看不见」的陷阱,同一个发布日里踩了两次。 最终形态为两者并存(peer 让宿主已有的那份可被复用,dependencies 保证宿主没提供时 也能跑),与已收录的 `DDDMUC/dsh-free-search` 同形: ```jsonc "peerDependencies": { "@deepseek-ai/schemastery": ">=3.18.1-rc.1 <3.18.5-0 || >=3.18.5-alpha.1 <4.0.0-0" }, "dependencies": { "@deepseek-ai/schemastery": ">=3.18.4 <3.18.5-0 || >=3.18.5-alpha.1 <4.0.0-0" } ``` ### Features - 新增 9 条**打包契约**控制检验(`test/client.test.mjs`,19 → 28 项): - `tools/` 必须在 `files` 白名单里 - 两个运行期入口脚本必须存在 - `portal_run.py` 的本地 import 闭包 8 个文件必须齐全 - `files` 白名单每一项都必须真实存在(写错名字会被 npm 静默忽略) - 官方 `@deepseek-ai/*` 必须声明为 `peerDependencies` - `schemastery` 必须在 `dependencies` 里有兜底(上面那次事故的正面断言) - `cordis` peer 范围必须放行全部已发布版本、且拒绝 `5.x` - `schemastery` peer 范围必须放行全部已发布版本、且拒绝 `4.x` - **产物里每个 bare import 都必须有对应声明** —— 这条是那次事故的通用化收口: 静态解析 `lib/` 下**全部** `.js` 的 import(`import x from` / `import "p"` / `import("p")` / `require("p")` / 模板字面量,且不限行首),逐个要求能在 `dependencies`/`peerDependencies` 里找到归属(宿主注入的 `react` 走显式白名单), 相对导入必须真实存在。今后「加了 import 却忘了声明依赖」会当场失败。 > 初版这条只扫 `lib/index.js` + `lib/client.js`,且正则漏掉 `await import("p")` > 这类不在行首的动态导入 —— 被发布前的对抗性复核当场指出并补齐。 > 记在这里是因为它说明一件事:**「我已加了兜底检验」本身也需要被验**。 每条都用**退回旧写法**验过会失败(可证伪,不是装饰)。 - 新增 `test/lib/semver-range.mjs`:极简 semver 判定器,用于 peer 范围断言。 不引第三方测试依赖(CI 只装 schemastery),且**认不出的语法一律抛错**, 而不是「看着像就放行」——`>=4`、`^3.18.4`、`4.x` 都会直接抛错; 非法版本(前导零如 `1.2.3-alpha.01`,node-semver 判 false)同样抛错, 不会静默给出一个错的布尔值。已与真 `semver` 对比 **2808 组用例(含 alpha/beta/rc × 数字标识符的预发布组合)、0 处差异**。 ### Docs - 修正测试数字漂移:README 两处「24 / 17 项」与「46 项」→ 实测 **50 / 28 项**; README.en.md 同步;RELEASING.md 的「18 / 14 项」→ **50 / 28 项**; `test/acceptance_portal.py` 的「29 项」→ 实跑 **30/30**(README.md 两处 + README.en.md)。 - README / README.en.md 的控制检验概述改为与仓库实际一致 (26 个 Python 控制检验脚本 + 8 个端到端脚本 + 78 项 Node 检验)。 - 新增「界面」章节与截图:`docs/assets/` 三张(窄屏浅色 / 窄屏深色 / 宽屏) 由 `tools/shoot_portal.py` 用**真门户进程 + 无头浏览器**实拍;根目录 `screenshots.json` 按注册表约定声明(awesome-dsh-plugin 推荐项)。 README 里用**绝对 URL** 引用这三张图:`docs/` 不在 npm 包的 `files` 白名单里, 相对路径在 npmjs.com 上会裂图。 - RELEASING.md 第 7 步补上 `tools/` 的发布前断言(`npm pack --dry-run` 必须看到 两个入口脚本),否则 v0.8.6 修掉的「包里没有运行期脚本」可以无声复现。 ## 0.8.5 (2026-10-16) 用户:「访问页我看也没有符合要求变成一个只读的测试页,并且这个页面也需要美化, 并需要增加一个 鼓励一下的连接」。 ### 先核事实:页面**早就是只读的**了 我把线上 8899 的 HTML 抓下来,把**每一个可交互元素**列出来核对: ```