Repository navigation
lib/client.js ships unbundled ESM — tsc in the build step overwrites the browser bundle, so the plugin crashes DSH on load #13
Description
Environment
- DSH:
0.2.0-rc.1(desktop app, macOS arm64) @perrylink/dsh-github:0.7.15(latest as of 2026-09-25)
Summary
The browser half of this plugin cannot load on DSH 0.2, and it takes the app's client boot down with it:
Uncaught SyntaxError: Cannot use import statement outside a module
@ dsh-app://app/plugins/??@perrylink/dsh-github/client.js&rev=…:23
package.json points exports["./client"] at ./lib/client.js, and DSH injects that file into the renderer as a
classic script. But the published file is TypeScript-compiled ESM source, not a browser bundle, so the first
import is a syntax error.
Evidence from the published tarball
$ npm pack @perrylink/dsh-github@0.7.15 && tar xzf perrylink-dsh-github-0.7.15.tgz
$ wc -c package/lib/client.js
12473 package/lib/client.js
$ grep -c '__ModuleLoader__' package/lib/client.js
0
$ grep -n '^import ' package/lib/client.js
23:import { createElement, useState } from 'react';
24:import { Button, IconLoadingOutlineRegular, } from '@deepseek-ai/dsh-client-ui-primitives';
DSH 0.2's client loader only accepts the bundled form:
window.__ModuleLoader__.load({ id: '@scope/plugin', factory(require) { … } });0 occurrences of __ModuleLoader__ plus two top-level imports means the file cannot be executed as a classic
script. The rest of the bundle looks intact — this is purely a packaging/build-order problem.
Likely cause
package.json:
"scripts": {
"build": "tsc --noEmitOnError && node scripts/fix-dts.mjs",
"prepublishOnly": "npm run build && npm test"
}tsc is run with emit and outputs next to the sources, so it rewrites lib/client.js with compiled module
code. Whatever bundler step produced the real browser bundle is either not wired into prepublishOnly or runs
before this tsc and gets overwritten.
The file's own header comment suggests this is unintentional:
/**
* dsh-github browser half — …
* The shipped `lib/client.js` is the built bundle of this module. The browser
* module loader executes that bundle, not this file.
*/
import { createElement, useState } from 'react';Line 19 says the shipped file is the built bundle; line 23 is a bare ESM import. The doc and the artifact
disagree — consistent with a build step clobbering a correct bundle.
Suggested fix
- Make the browser bundle the last thing written to
lib/client.js— either run the bundler aftertsc
(e.g.tsc --noEmitfor types, then the bundler forlib/client.js), or emittscoutput to a separate
directory so it cannot overwrite the bundle. - Point
exports["./client"]at the bundle explicitly if it lives elsewhere. - The repo already has a
verify:artifactsscript and averify:self-containedscript — a check that
lib/client.jscontains__ModuleLoader__(and has no top-levelimport) would catch this before publish.
I'd be happy to send that as a small PR if useful.
Impact note
This is not cosmetic for DSH users: the renderer aborts its client boot, and the desktop app reports
web boot: N entry did not activate and shows "应用无法启动", then runs its crash recovery (disables all
third-party bundles and resets the profile). One malformed client artifact costs the whole plugin set.
I confirmed this reproduces on a clean profile with 0.7.15 as the only third-party plugin.
中文摘要
@perrylink/dsh-github@0.7.15 的 lib/client.js 不是浏览器打包产物,而是 tsc 编译出来的 ESM 源码
(12,473 字节,__ModuleLoader__ 出现 0 次,第 23/24 行是裸 import)。DSH 0.2 把它当普通脚本注入
→ SyntaxError: Cannot use import statement outside a module → 客户端启动失败,应用弹「无法启动」。
成因很可能是构建顺序:"build": "tsc --noEmitOnError && node scripts/fix-dts.mjs" 里的 tsc
带 emit,把编译产物写回 lib/client.js,覆盖掉了打包器的输出。文件第 19 行的注释还写着
"The shipped lib/client.js is the built bundle of this module" —— 注释和产物自相矛盾,说明是构建步骤把它冲掉了。
修复建议:tsc 改成 --noEmit(或输出到别处),确保打包产物最后写入;并在发布前加一条
「lib/client.js 必须含 __ModuleLoader__」的校验 —— 仓库里已经有 verify:artifacts 脚本,加进去很自然。
需要的话我可以直接提个小 PR。
Activity
Swcmb commentedon Oct 2, 2026
Confirmed on
0.2.0-rc.2as well — this is not specific to0.2.0-rc.1.Environment: DSH
0.2.0-rc.2(desktop, win32 x64, Electron 44.0.0, Node 24.18.1),@perrylink/dsh-github@0.7.15from npmlatest. Identical failure:Scope check. I swept the whole profile to confirm this is isolated rather than a profile-wide incompatibility:
bundlesentries,@perrylink/dsh-githubis the only one whoseengines.dshdeclares a host range below0.2.0(it caps at<0.2.0). Every other bundle either declares>=0.2.0-rc.1, declares no host range at all, or is an official@deepseek-ai/*bundle supplied by the app itself.exports["./client"]entry — every one of those 10client.jsfiles is a classic script with 0 top-levelimport/export. The other 13 resolve their client half through the host manifest.SyntaxError, 18 clean imports, and 5ERR_MODULE_NOT_FOUNDfor@deepseek-ai/dsh-*packages the host provides from its built-in bundles — expected outside the host.So the client artifact shape is the defect, exactly as #13 describes.
Two things that may help decide what to merge:
fix(client): ship lib/client.js as the DSH browser bundle, not tsc output #11 and fix(client): register the browser half with the module loader #14 overlap and will conflict. Both replace the same build step that produces
lib/client.js(scripts/client-bundle.mjsvsscripts/build-client.mjs), and both extendscripts/verify-artifacts.mjs. fix(client): register the browser half with the module loader #14 additionally widensengines.dshand the@deepseek-ai/dsh-*peer union with|| >=0.2.0-rc.1 <0.3.0-0, and moves the Compat workflow'sprofilejob to both ends of the band. Picking one and closing the other avoids a three-way conflict inprepare.mjs,package.jsonand the lockfile.There may be no recovery path through the market. My profile's
state.json.disabledwas empty, so I had no off switch for this package to begin with — but the market's toggle is loader-entry based (for other bundles it logsno loader entry matchedand still leaves them in the boot list), so a failure that happens during renderer boot, before any row is registered, looks unreachable from that toggle. What actually worked was editing the profile'sdsh.profile.bundlesand restarting.Recovery detail worth recording: after removing the row from
dsh.profile.bundles, the host started normally even thoughnode_modules/@perrylink/dsh-github/andpnpm-lock.yamlboth still contained the package. Only thebundlesrow drives renderer boot, so removing the installed copy and pruning the lockfile is not required to recover — which matters when the UI is down and the market cannot be used to uninstall anything.I have it disabled this way for now and will reinstall once a release declares
0.2support.ljyzww commentedon Oct 8, 2026
补充一份 0.7.19 上的复现,与 #13 同根因。
【环境】
"DSH >=0.1.2-rc.1 <0.2.0 || >=…",安装时只给出一条 cordis 版本风险警告就放行。
【实际行为】
安装后重启桌面端,启动页出现 "Failed to load plugins",桌面端随即进入
恢复模式窗口(内核针对 renderer 启动失败的标准降级路径),应用无法正常进入。
在恢复模式中卸载该插件后重启,立即恢复正常(1/1 复现)。
【日志】
%APPDATA%\DSH Desktop\logs\dsh-2026-10-08.error.log
dsh-plugin-desktop: renderer boot failed (plugins: @perrylink/dsh-github):
The client Loader did not provide an error message.
RendererStartupFailure: Renderer boot failed for 1 plugin(s)
at start (file:///G:/DSH Desktop/resources/app/lib/main.js:4967:49)
【期望行为】
【补充证据:0.7.19 仍未修复】
含 2 条顶层 bare import —— 与
lib/client.jsships unbundled ESM —tscin the build step overwrites the browser bundle, so the plugin crashes DSH on load #13 描述的"tsc 产物覆盖浏览器 bundle"一致。【另两点建议】
但 peerDependencies 要求 @deepseek-ai/cordis ^4.0.5-alpha.1(只存在于 0.2.1-alpha 线),
本机宿主 4.0.4 不满足 —— 两处声明口径不一致,导致市场按 engines 放行、装完即崩。建议同步收窄。
见
lib/client.jsships unbundled ESM —tscin the build step overwrites the browser bundle, so the plugin crashes DSH on load #13 的评论),本次因找不到说明只能整包卸载。【脱敏说明】已自行检查,正文不含 token / 凭据 / 请求头。
fishdivinity commentedon Oct 14, 2026
Confirmed on
0.7.20— still the current npmlatest(gitHead 1b42270), i.e. two releases after the 0.7.19 report.Env: DSH Desktop
0.2.0-rc.2(win32 x64, Electron 44.0.0, Node 24.18.1), host@deepseek-ai/cordis@4.0.4.Two details that may help narrow the fix:
include:dsh-githubis in the loader tree and the plugin's own tools (gh_repo,gh_issue,gh_search, …) are callable — while the client slot it declares (plugins.item) never registers. The defect is confined to the./clientartifact.warn toggle: @perrylink/dsh-github: enable failed; leaving it disabled rather than persisting a state that crashes at boot (#575). But installing goes through the host's own replay —install: the host's own replay activated it; adopted instead of mounting a second copy (#814, #827)— which writes the package intodsh.profile.bundles. The profile is then "fine until the next restart", and the restart fails:On the build side, 0.7.20's
package.jsoncontains no client-bundle step at all (buildistsc --noEmitOnError && node scripts/fix-dts.mjs,prepublishOnlyisnpm run build && npm test), andverify:artifactsis not wired intoprepublishOnly— so nothing between thattscandnpm publishcan catch the overwrite.Recovery: removing the
@perrylink/dsh-githubrow fromdsh.profile.bundles(leaving the package installed) is enough to boot again.中文摘要:
0.7.20(当前 npm 最新版)上仍然复现,比 0.7.19 那条报告又晚了两个版本;本机宿主0.2.0-rc.2/ cordis4.0.4,lib/client.js12255 字节、__ModuleLoader__0 次。补充两点:①只有浏览器半坏——宿主半已加载(include:dsh-github在 loader 里、插件工具可调用),它声明的客户端槽位plugins.item始终未注册;②market 的启用路径会拦(#575拒绝落盘),但安装路径走宿主 replay,会把它写进dsh.profile.bundles,用户于是处在"装完到下次重启之间正常"的状态,一重启就web boot: 1 entry did not activate。另外 0.7.20 的 scripts 里没有任何客户端打包步骤,verify:artifacts也没接进prepublishOnly。恢复方式:把该行从dsh.profile.bundles移除即可启动(包可留在node_modules)。