Skip to content

fix: dist 产物 ESM require 崩溃 + dsh-qaq 插件零运行时依赖 - #1

Merged
WTStarMark merged 1 commit into
WTStarMark:mainfrom
Townrain:fix/esm-bundle-and-plugin-deps
Aug 15, 2026
Merged

fix: dist 产物 ESM require 崩溃 + dsh-qaq 插件零运行时依赖#1
WTStarMark merged 1 commit into
WTStarMark:mainfrom
Townrain:fix/esm-bundle-and-plugin-deps

Conversation

@Townrain

Copy link
Copy Markdown
Contributor

背景

在 Windows + Node 24 上实际部署时发现两个「开箱即崩」的缺陷,均会阻断正常的本地部署流程。两处修改均经过真实验证。

修复 1:pnpm build 产物启动即崩溃

dist/qaq.mjs 在 Node 24 下启动立刻抛错:

Error: Dynamic require of "events" is not supported
    at node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/websocket.js

原因:esbuild 把 CJS 依赖 ws 打进 ESM 产物后,ws 内部对 Node 内置模块(events/stream 等)的 require() 落到 esbuild 生成的 __require 帮助器上,在 ESM 作用域里没有 require 可用,直接抛错。测试套件从未暴露这个问题——pnpm smoke 走的是 tsx src/cli.ts 源码头,从不执行 dist 产物,而 bin/qaq.mjs 与 .cmd 启动器优先用 dist。

修复:构建命令加 esbuild banner,用 createRequire(import.meta.url) 在产物顶层提供 requirepackage.json)。

修复 2:qaq install-plugin 可能弄崩它要守护的启动

插件 packages/dsh-qaq 在运行时 import '@deepseek-ai/dsh-home-paths'。但插件是通过 junction 链接挂进 profile 的(目录在 QAQ 仓库内,不在 DSH 树里),裸标识符解析会沿着 QAQ 仓库的 node_modules 向上找——那里永远不会包含 @deepseek-ai/*(pnpm 布局下实测解析失败)。结果是:下次启动时插件 entry 加载失败,造成它要防的红屏本身。

修复:把 resolveDshHome(语义与 DSH 的 @deepseek-ai/dsh-home-paths 完全一致:DSH_HOME 非空优先、否则 ~/.dsh)内联进插件,并移除该 peer 依赖——插件变为零运行时依赖(产物仅 import Node 内置模块),junction 挂载后不可能因解析失败破坏 boot。lib/index.js 已同步重建并提交。

验证

  • pnpm test:53/53 全部通过
  • pnpm buildnode dist/qaq.mjs status / --help 正常运行
  • 插件从 junction 位置独立 import 成功(导出 apply, name
  • 隔离 DSH_HOME 下调用 apply() 端到端验证:正确写入 latest-good + history/<ts> + manifest(profile 正确)
  • 挂载过程中 cordis.patch.yml(用户 patch 层)保持不被触碰

…time-dependency

Two deployment-blocking bugs found while deploying on Windows/Node 24:

1. `pnpm build` output (dist/qaq.mjs) crashed immediately on launch with
   "Dynamic require of \"events\" is not supported": esbuild bundles the CJS
   `ws` dependency into ESM output, and ws's internal require() calls to
   node builtins (events/stream) hit esbuild's __require shim, which throws
   in ESM scope. The test suite never caught this because it runs the source
   through tsx, never the dist bundle. Fix: add an esbuild banner that
   defines `require` via createRequire(import.meta.url).

2. `qaq install-plugin` could break the very boot it guards: the plugin
   imported @deepseek-ai/dsh-home-paths at runtime, but the plugin is mounted
   into a profile via a junction pointing outside the DSH tree, and bare
   specifiers resolve up the QAQ repo's node_modules (which never contains
   @deepseek-ai) — so the plugin entry would fail to load on the next boot.
   Fix: inline resolveDshHome (DSH_HOME or ~/.dsh, identical semantics to
   the DSH package) so the plugin has zero runtime dependencies. The rebuilt
   lib/index.js is committed alongside.

Verified: 53/53 unit tests pass; dist/qaq.mjs runs (status/help); plugin
imports cleanly from the junction location and snapshots latest-good +
history correctly in an isolated DSH_HOME.
@WTStarMark
WTStarMark merged commit 891baed into WTStarMark:main Aug 15, 2026
2 checks passed
@Townrain

Copy link
Copy Markdown
Contributor Author

补充提交 c96bda2(评审建议的测试补强,与修复成对落地):

  • test/dist.spec.ts — 构建并真实执行 dist/qaq.mjsbin/qaq.mjs。此前测试套件只跑 tsx 源码头,从不执行构建产物——ESM require shim 崩溃正是因此漏网的;现在回退 banner 修复会直接 CI 失败。
  • test/dsh-qaq.spec.ts — 钉住插件不变量:settle 成功才快照、settle 被拒(失败启动)绝不快照、history 剪枝保留 5、profile 解析顺序(QAQ_PROFILE → cwd → web)、profile 缺失静默跳过。插件 100% lines 覆盖。
  • test/cli.spec.ts — parseCli + cmdDsh 决策流(回滚后恰好重启一次且强制 autoConfirm;用户拒绝回滚绝不自动重启;致命 preflight / 锁被占立即停手)。为此给 cli.ts 加了入口守卫(bin/qaq.mjsQAQ_ENTRY 标记,或 argv[1] 与模块 URL 匹配),测试可安全 import 而不触发 main()
  • 插件再瘦身 — 移除 @deepseek-ai/cordis 类型导入(改用结构化 Ctx/LoaderService 接口)与 peer 依赖:插件现在连类型依赖都没有;同时修复了 root typecheck(spec 把插件拉进类型检查后 cordis 无法解析的问题)。
  • 覆盖率门禁@vitest/coverage-v8(v8 provider,覆盖 src/ + 插件,全局下限 + 核心文件更严下限),新增 pnpm coverage 并加入 CI。
  • CI 修复 — push 分支 mastermain(仓库默认分支,此前 push 到 main 从不触发 CI)。

验证:typecheck 0、76/76 单测、覆盖率门禁通过、smoke 通过(本机用真实 DSH checkout 跑了集成段)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants