dsh/dsh-client 分支:v0.3.16bf30b4d600bfaf3f5b26170731ae8854639159a(≈ v0.3.0)266ea372c7402d4cb85fedac1ba6d46dbeec5428(v0.3.1)wails.json productVersion 0.2.2 → 0.3.1git diff(变更面)、issues_diff(修复 24 / 新增 32)、quality_list(当前版本问题清单,共 1138 条)本版是"体验增强 + 发布自动化"版本,核心业务代码改动很小,主要三条线:
| 线 | 内容 | 主要文件 |
|---|---|---|
| ① 安装/更新提速 | 安装/更新从纯 npm 改为 pnpm 内容寻址 store 优先 + npm 兜底;新增 pnpm 引导、runtime package.json(onlyBuiltDependencies 白名单)、可单测的回退链 | app/pkg_install.go(新) app/pkg_install_test.go(新) app/update.go |
| ② Windows 真机修复 | 停服不再用 SIGTERM(Windows 返回 not supported,白等 3s)→ killProcessTree(Windows taskkill /T /F);execCommandOutput 补 CREATE_NO_WINDOW 防黑框;新增 --port/--dsh-home 隔离参数 | app/service.go app/procutil.go app/app.go main.go |
| ③ 发布自动化 | 新增 Gitea Actions(tag v* 交叉编译 xtun-server 4 平台并上传 Release);新增 package-server.sh;publish-gitea.sh 跨平台化 + 自动打包上传服务端 ZIP | .gitea/workflows/release.yml(新) (新) |
另含:前端 3 处状态文案("安装依赖" → "安装/更新 DeepSeek Harness")、StatusBar 主状态语义修正、AGENTS.md / docs 文档同步、frontend/package.json.md5 更新。
新增文件:app/pkg_install.go、app/pkg_install_test.go、.gitea/workflows/release.yml、scripts/package-server.sh、scripts/release-notes-v0.3.0.md、docs/经验/2026-08-22-windows-pnpm安装与停服taskkill.md。
issues_diff 真实数据:24 条)⚠️ 重要说明:这 24 条 几乎全部是"行号漂移"造成的伪修复——同一问题因上方新增代码(
App字段 +OnStartup覆盖逻辑 +killProcessTree等)整体下移若干行,工具按行号比对后判为"base 有、head 无"。逐条与"新增"清单配对后可见:app/app.go:66→69、app/app.go:107→117、app/app.go:321→331、app/app.go:382→392、app/service.go:157→149、app/service.go:271→263、StatusBar.tsx:117→123等均为同一问题的位移,实质未修复。
真实的(非漂移)变化只有一条:
[Low/Dependency] 扇入扇出异常 @ app/runtime.go:87(npmInstall):head 中该 fan-in/out 告警消失(当前版本 quality_list 中 app/runtime.go:87 仅剩"圈复杂度高 + 高耦合")——属阈值边缘的自然消长,非专门治理。结论:本版没有针对存量质量问题做实质性修复,24 条"修复"不宜计入成果。
issues_diff 真实数据:32 条)同样区分两类:
app/app.go(god class、emit 扇入扇出、StartWithRole/Shutdown/OpenRemoteAccess/ConnectRemote/ConnectLocal 高耦合、goroutine 泄漏、缺 default、重复代码块)、app/service.go(startBorrow/TakeoverAndRestart 高耦合、goroutine 泄漏、圈复杂度、命令注入)、frontend/src/components/StatusBar.tsx 重复代码块——均可在 base 清单中找到同问题旧行号。
其中 [Critical/Security] 命令注入风险 @ app/service.go:269 与 base 的 app/service.go:277 为同一处(killProcessOnPort 里 exec.Command("lsof","-ti","tcp:"+strconv.Itoa(port),...))。实为误报:无 shell、参数显式传参、端口为 int,不存在注入面。因此本版无新增真实 Critical。
| 严重度 | 问题 | 位置 | 说明 |
|---|---|---|---|
| Medium/Complexity | 圈复杂度过高 (count=11) | app/pkg_install.go:135 | pnpmInstall 参数拼装 + 引导 + 管道扫描 + 标记写入揉在一个函数 |
| Medium/Dependency | 高耦合(直接依赖 20 个函数) | app/pkg_install.go:135 | 同上 |
| Low/Dependency | 扇入扇出异常(fan-in 1 / fan-out 20) | app/pkg_install.go:135 | 同上 |
| Low/Maintainability | 重复代码块(4 行 ×2) | app/pkg_install.go:40 | pkgToolDir() / storeDirFn 两处 UserConfigDir()→TempDir() 兜底样板重复(runtimeDir()、managedPidPath() 同款) |
| Medium/Complexity | 圈复杂度过高 (count=12) | app/pkg_install_test.go:23 |
净新增真实问题:8 条,全部为复杂度/耦合/可维护性(Low~Medium),无功能性 Bug、无新增 Critical。
以下为结合 diff 逐行核对后发现的、工具清单未覆盖的实质风险:
app/service.go 新增的 killProcessTree(pid) 在非 Windows 分支用 os.FindProcess(pid) + 新起 goroutine p.Wait() 作为"是否已退出"的信号:
if p, err := os.FindProcess(pid); err == nil {
_ = p.Signal(syscall.SIGTERM)
done := make(chan struct{})
go func() { _, _ = p.Wait(); close(done) }() // 非子进程 → wait4 立即返回 ECHILD
select {
case <-done: // ← 立即命中
case <-time.After(3 * time.Second):
_ = p.Kill()
}
}
stopManaged() 里 pid 是本壳子的子进程,p.Wait() 能正常阻塞等待 → 行为正确。cleanupOrphans() 复用,此处 pid 来自上一次壳子进程(managed.pid 记录),对当前进程不是子进程 → wait4 立刻返回 ECHILD → done 立即关闭 → 永不执行 p.Kill()。SIGTERM → sleep(800ms) → 无条件 p.Kill()),新版在"上次残留托管进程忽略 SIGTERM"时不再强杀,孤儿可能残留。建议:让 cleanupOrphans 走"发 SIGTERM → 轮询 processExistsUnix(pid) / waitPortFree → 超时强制 Kill"的独立路径,或给 killProcessTree 加 isChild bool 参数区分两类语义。
node_modules,可能留下混合布局pnpmInstall 与 npmInstall 都作用于 runtimeDir()。若 pnpm 在物化阶段失败(部分包已写入 .pnpm/ + 符号链接),随后 npm install --prefix runtimeDir() 在同一目录运行,npm 不会清理 pnpm 的 .pnpm 目录与链接 → 可能形成 pnpm/npm 混合树。虽然失败路径不写 .dsh-installed-ok 标记(不会被误判为"已安装"),但下次成功安装前该目录是脏的,且 npm 在含符号链接的目录上的行为未在本版验证。
建议:pkgInstall 回退前清理/隔离目录(如 pnpm 失败即 rm -rf runtimeDir/node_modules 再走 npm),或在文档中明确"回退前需清理",并补一条集成测试。
frontend/package.json.md5 变更但 frontend/package.json 未变diff 中仅 frontend/package.json.md5 内容变化(2a558e26… → 5d04c526…),frontend/package.json 未出现在 diff 中。该 md5 是 Wails 用于判断"是否需要重装前端依赖"的标记:若其值与 package.json 实际内容不符,每次 wails dev/build 都会重写该文件造成工作区脏改。
建议:确认是"有意触发一次依赖重装"还是误提交;若误提交,用实际 md5 覆盖。
publish-gitea.sh 本地发布时会 push tag,而 .gitea/workflows/release.yml 由 push.tags: ['v*'] 触发——本地发布 + CI 会被同时触发,两者都执行"删同名资产再上传"。CI 用 ${{ gitea.token }}、脚本用 GITEA_USER:GITEA_TOKEN,Release 也可能被各自创建。虽然双方都做了幂等删除,但并发情况下仍可能出现资产被后写覆盖或残留。
建议:明确单一发布通道(CI 只管服务端包 or 本地先 push tag 再等 CI),或让 CI 仅在 workflow_dispatch 下运行。
Wait() 子进程stopManaged() 改为 taskkill 后不再调用 cmd.Wait()(Unix 分支由 killProcessTree 内的 p.Wait() 兜住)。Windows 上进程句柄依赖 GC finalizer 释放,属轻微资源延迟释放,非功能性缺陷,可接受但建议补 cmd.Wait() 或在注释中说明。
storeDirFn 为包级可变变量var storeDirFn = func() string {...} 被 pkg_install_test.go 覆写(defer 还原)。若将来并行化测试(t.Parallel())或跨包复用,存在数据竞争;当前串行测试下安全。
StatusBar tooltip 与主文案可能矛盾新逻辑中 statusTitle 在 role=local && !localConfigured 时恒为"运行中(远程访问未配置,点「开启远程访问」可配置)",而 statusLabel 走 roleName ?? (info ? '未运行' : '…')。当本地角色但服务实际未运行(如 dsh 未安装、启动失败)时,会出现"未运行"标签 + "运行中(…)"tooltip 的自相矛盾。建议 tooltip 也按实际运行态分支。
release-notes-v0.3.0.md 为 v0.3.0 说明,但本次目标是 v0.3.1;AGENTS.md、docs/ 多处仍写 v0.3.0、docs/进度/…P4 记录到 v0.3.0。发布前请确认 v0.3.1 是否需要独立 release notes 及文档同步。
killProcessTree)两处,风险面清晰可控。issues_diff 报出的"修复 24 / 新增 32"中,约 24/24 与 24/32 为行号漂移伪信号,不宜作为成果/回退依据;真正新增 8 条均为 Low~Medium 的复杂度/耦合问题,集中在 app/pkg_install.go;无新增 Critical(唯一的 Critical 命令注入为存量误报)。cleanupOrphans 路径)——这是本版唯一有实际行为退化的点;runtime/node_modules 是否可用(真机回退演练);node bin.js --version / --profile web 正常。pnpmInstall 拆分(配置构建 / 进程执行 / 输出与标记三段)以降圈复杂度与耦合;抽取 UserConfigDir 兜底样板函数消除重复块(pkg_install.go:40、runtimeDir、managedPidPath 三处同款)。scripts/package-server.shscripts/publish-gitea.sh| 单测断言分支过多 |
| Low/Maintainability | 行过长 (>120) | app/pkg_install_test.go:59 | 断言行超长 |
| Medium/Dependency | 高耦合(直接依赖 90 个函数) | main.go:26 | main 接线 wails 选项,属度量噪声 |
| Low/Dependency | 扇入扇出异常(fan-in 0 / fan-out 90) | main.go:26 | 同上,为入口函数固有形态 |