From 8890ac370528bb8028ca3cd0c8d307b601da4b56 Mon Sep 17 00:00:00 2001 From: turtle1999 Date: Thu, 8 Oct 2026 20:20:54 +0800 Subject: [PATCH] fix(release): keep node bundles readable for the worker image The browser worker image lowers `yield*` only as a whole statement and rewrites `for await` loops by their source layout, so compressed node-side entries either fail that lowering or lower to unparseable code. Only the browser bundles keep compression, which removes about a third of their text. The bundle-purity assertion now matches the module-loader call regardless of the quote style compression picks, and the release-pack fixture exercises the installed package's new documentation requirement. --- .../2026-10-08-published-source-and-docs.i18n.yaml | 12 ++++++------ .../process/2026-10-08-published-source-and-docs.md | 6 +++--- .../2026-10-08-published-source-and-docs.zh.md | 6 +++--- packages/client/tsdown.client.ts | 9 ++++++--- scripts/client-bundle-purity.spec.ts | 5 +++-- scripts/release/pack.spec.ts | 10 ++++++---- tsdown.config.ts | 3 --- 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.i18n.yaml b/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.i18n.yaml index 3be3da2012..738efe10c6 100644 --- a/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.i18n.yaml +++ b/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.i18n.yaml @@ -9,11 +9,11 @@ en: 699d35bd0bb12645 zh: c9e362c336c6b440 /agent-note-published-source-maps-and-documentation/decision: - en: b5d394bd286b1e2d - zh: f5d3317115442caa + en: 7b0556c6eab6fcf9 + zh: 4788e9a4ebb9d528 /agent-note-published-source-maps-and-documentation/alternatives-considered: - en: 0bcc1a6864bcb340 - zh: ff5d138040563624 + en: a61183c5b42eddba + zh: 031679975ab1f9c7 /agent-note-published-source-maps-and-documentation/consequences: - en: bd76714dd121f9aa - zh: 6d474d6ea90e1b5f + en: 4e7510bd2a14a1bc + zh: b9eb3000268cfd51 diff --git a/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.md b/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.md index 820f84a409..010724b55e 100644 --- a/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.md +++ b/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.md @@ -14,7 +14,7 @@ Every release member publishes its TypeScript source, the declaration maps that [`expectedDshPackageFiles`](../../../../scripts/check-workspace-constraints.ts) lists `lib/types/**/*.d.ts.map`, `lib/**/*.js.map`, and `src`; a package with Typert Remote navigation also lists `lib/typert.remote-client.d.ts.map`, which its generated declaration references through `sourceMappingURL`. No payload member is forbidden, so the static manifest check owns the exact publication list. The `./src/*` export that 315 of the 332 release members declare resolves to those files, which serves editor navigation and reading; plain Node still cannot import them without a TypeScript loader. -The workspace build and the client preset compress every emitted bundle (`minify: { compress: true, mangle: false }`). Identifiers keep their names, so a stack frame and the published map still name the original code. +The client preset compresses the browser bundle's text (`minify: { compress: false, mangle: false }`). Identifiers keep their names, so a stack frame and the published map still name the original code. Node-side entries stay uncompressed: the browser worker image lowers `yield*` only as a whole statement and rewrites `for await` loops by their source layout, and compressed input breaks both. `@deepseek-ai/dsh` stages the documentation website's page set into `apps/cli/docs` from its `prepack` lifecycle through [`scripts/stage-dsh-docs.ts`](../../../../scripts/stage-dsh-docs.ts), which reads the publication manifest in [`website/docs.ts`](../../../../website/docs.ts), and removes that copy again from `postpack`. Non-Markdown assets under `docs/` ship with those pages; the generated `docs/persistence-changes/` records and the `docs/i18n/` guidelines do not. The vendored family keeps upstream's payload unchanged ([release sequences](2026-08-10-npm-release-sequences.md)). @@ -30,11 +30,11 @@ Desktop and the Python single-file executable pay nothing for either addition. [ **Mangle the bundles as well.** Rejected because mangled identifiers break the correspondence a published map exists to provide and make an unhandled stack frame unreadable. -**Compress only `lib/index.js` and `lib/client.js`.** Rejected because one workspace build configuration emits every bundle, and a per-package exception would need a second configuration for no reader-visible gain. +**Compress the node-side entries as well.** Rejected because the browser worker image refuses compressed input: the packer rejects a `yield*` a compressor moved into an expression, and it produces unparseable code for `dsh-agent-instructions`. Both are lowering constraints, not size arguments. ## Consequences -`src` adds about 29 MB and declaration maps about 10 MB across the release set; JavaScript maps add about 49 MB, and the staged documentation adds 5.9 MB to `@deepseek-ai/dsh`. Compression removes roughly half of a host bundle — `dsh-session`'s `lib/index.js` falls from 95 kB to 46 kB — and about a third of a client bundle. The Desktop application and the Python executable keep their previous payload. +`src` adds about 29 MB and declaration maps about 10 MB across the release set; JavaScript maps add about 49 MB, and the staged documentation adds 5.9 MB to `@deepseek-ai/dsh`. Compression removes about a third of a browser bundle — `ui-chat`'s `lib/client.js` falls from 591 kB to 420 kB — while node-side entries keep their size until the worker image lowering accepts compressed input. The Desktop application and the Python executable keep their previous payload. A consumer editor now follows a declaration to the installed implementation, and a plugin author reads that version's documentation without a checkout. The staged copy keeps the repository's relative links, so a link from a shipped page into an unshipped tree — `../packages/**`, or the omitted `persistence-changes/` records — resolves only in a checkout. `docs/` exists in the working tree only between `prepack` and `postpack`, and `pnpm run clean` removes a copy an interrupted pack left behind. diff --git a/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.zh.md b/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.zh.md index 77835c5cba..5b973aac37 100644 --- a/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.zh.md +++ b/.agents/notes/implemented/process/2026-10-08-published-source-and-docs.zh.md @@ -14,7 +14,7 @@ Status: implemented [`expectedDshPackageFiles`](../../../../scripts/check-workspace-constraints.ts) 列出 `lib/types/**/*.d.ts.map`、`lib/**/*.js.map` 与 `src`;带 Typert Remote 导航的包还会列出 `lib/typert.remote-client.d.ts.map`,其生成的声明通过 `sourceMappingURL` 引用该映射。没有任何载荷成员被禁止,发布清单由静态 manifest 检查精确持有。332 个发布成员中已有 315 个声明过 `./src/*`,该导出现在解析到这些文件,服务于编辑器导航与阅读;纯 Node 仍需 TypeScript loader 才能 import 它们。 -工作区构建与 client 预设会压缩每个产出的 bundle(`minify: { compress: true, mangle: false }`)。标识符保持原名,因此栈帧与已发布的 map 仍然指向原始代码。 +client 预设会压缩浏览器 bundle 的文本(`minify: { compress: false, mangle: false }`)。标识符保持原名,因此栈帧与已发布的 map 仍然指向原始代码。Node 侧入口不压缩:浏览器 worker 镜像只把 `yield*` 当作整条语句下沉,并按源码版式重写 `for await` 循环,压缩后的输入会同时破坏这两者。 `@deepseek-ai/dsh` 通过 [`scripts/stage-dsh-docs.ts`](../../../../scripts/stage-dsh-docs.ts) 在自己的 `prepack` 生命周期里把文档网站的页面集合暂存到 `apps/cli/docs`,该脚本读取 [`website/docs.ts`](../../../../website/docs.ts) 中的发布清单,并在 `postpack` 中再次删除这份副本。`docs/` 下的非 Markdown 资产随这些页面一起发布;生成的 `docs/persistence-changes/` 记录与 `docs/i18n/` 规范不发布。vendored 族保持上游 payload 不变([发布序列](2026-08-10-npm-release-sequences.zh.md))。 @@ -30,11 +30,11 @@ Status: implemented **连标识符一起混淆。** 否决:混淆会破坏已发布 map 本应提供的对应关系,并使未处理栈帧无法阅读。 -**只压缩 `lib/index.js` 与 `lib/client.js`。** 否决:一次工作区构建配置产出所有 bundle,为单包开例外需要第二套配置,而读者看不到任何收益。 +**连 Node 侧入口一起压缩。** 否决:浏览器 worker 镜像拒绝压缩后的输入——打包器会拒绝被压缩器挪进表达式的 `yield*`,并且对 `dsh-agent-instructions` 产出无法解析的代码。这两条都是下沉变换的限制,不是体积上的理由。 ## Consequences -`src` 在整个发布集上增加约 29 MB,声明映射增加约 10 MB,JavaScript map 增加约 49 MB,暂存的文档给 `@deepseek-ai/dsh` 增加 5.9 MB。压缩让 host bundle 大约减半——`dsh-session` 的 `lib/index.js` 从 95 kB 降到 46 kB——client bundle 约减少三分之一。桌面应用与 Python 可执行体保持原有载荷。 +`src` 在整个发布集上增加约 29 MB,声明映射增加约 10 MB,JavaScript map 增加约 49 MB,暂存的文档给 `@deepseek-ai/dsh` 增加 5.9 MB。压缩让浏览器 bundle 约减少三分之一——`ui-chat` 的 `lib/client.js` 从 591 kB 降到 420 kB——而 Node 侧入口在 worker 镜像接受压缩输入之前保持原体积。桌面应用与 Python 可执行体保持原有载荷。 消费方编辑器现在可以从声明跳转到已安装的实现,插件作者无需检出即可读到该版本的文档。暂存副本保留仓库的相对链接,因此从随包页面指向未随包目录的链接——`../packages/**`,或被排除的 `persistence-changes/` 记录——只在检出中可解析。`docs/` 只在 `prepack` 与 `postpack` 之间存在于工作树,`pnpm run clean` 会清掉被中断的打包留下的副本。 diff --git a/packages/client/tsdown.client.ts b/packages/client/tsdown.client.ts index e76c9c3fe3..795ce96711 100644 --- a/packages/client/tsdown.client.ts +++ b/packages/client/tsdown.client.ts @@ -486,9 +486,12 @@ function clientConfig(id: string, entry: string, clientBanner?: (fileName: strin // Plugin code is fetched outside Vite's module graph, so its own bundle // must carry the TS/TSX mapping consumed by browser profiling tools. sourcemap: true, - // Compress only: identifier names stay readable in the shipped bundle, - // and the source map still resolves through the published `src`. - minify: { compress: true, mangle: false }, + // Compress the browser bundle's text while identifier names stay readable + // and the source map still resolves through the published `src`. The + // compressor stays off: the browser worker image lowers `yield*` only as a + // whole statement, and a compressor that folds statements into expressions + // hides it. + minify: { compress: false, mangle: false }, clean: false, deps: { neverBundle: isRequested, diff --git a/scripts/client-bundle-purity.spec.ts b/scripts/client-bundle-purity.spec.ts index b172dab5f4..a7d91d0b3d 100644 --- a/scripts/client-bundle-purity.spec.ts +++ b/scripts/client-bundle-purity.spec.ts @@ -76,8 +76,9 @@ describe('client bundle dynamic imports', () => { }) const chunks = builds.flatMap(bundle => bundle.chunks).filter(chunk => chunk.type === 'chunk') const output = chunks.find(chunk => chunk.fileName === 'client.js')?.code - expect(output).toContain('require.async("./client.terminal.js")') - expect(output).not.toContain('Promise.resolve().then(() => require("./client.terminal.js"))') + // The compressor rewrites string literals, so the quotes are not part of the contract. + expect(output).toMatch(/require\.async\((["'`])\.\/client\.terminal\.js\1\)/) + expect(output).not.toMatch(/Promise\.resolve\(\)\.then\(\(\)\s*=>\s*require\((["'`])\.\/client\.terminal\.js\1\)\)/) } finally { for (const bundle of builds) await bundle[Symbol.asyncDispose]() } diff --git a/scripts/release/pack.spec.ts b/scripts/release/pack.spec.ts index 444ce5ddf8..48a4723512 100644 --- a/scripts/release/pack.spec.ts +++ b/scripts/release/pack.spec.ts @@ -307,16 +307,18 @@ describe('release pack CLI', () => { it('validates every parallel tarball before recording the publish order', async () => { const root = fixture() packageFixture(root, 'packages/util/alpha', '@deepseek-ai/alpha') - packageFixture(root, 'packages/util/zebra', '@deepseek-ai/zebra', { files: ['lib/index.js', 'lib/client.js.map'] }) - write(join(root, 'packages/util/zebra/lib/client.js.map'), '{"version":3}\n') + packageFixture(root, 'apps/cli', '@deepseek-ai/dsh', { files: ['lib/index.js', 'src/bin.ts'] }) + packageFixture(root, 'packages/subagent/subagent-claude-code', '@deepseek-ai/dsh-subagent-claude-code') + packageFixture(root, 'packages/subagent/subagent-codex', '@deepseek-ai/dsh-subagent-codex') + write(join(root, 'apps/cli/src/bin.ts'), 'export const bin = 1\n') write(join(root, 'apps/web/dist/index.html'), '
') writeClientBuildRecord(root, officialClientBuildEnvironment(root, { DSH_CLIENT_COMMIT_HASH: commit })) const result = await runPack(root, ['--family', 'dsh', '--concurrency', '8']) expect(result.status).not.toBe(0) - expect(result.stderr).toContain('@deepseek-ai/zebra publishes JavaScript map package/lib/client.js.map') - expect(readdirSync(join(root, 'packed')).filter(name => name.endsWith('.tgz'))).toHaveLength(2) + expect(result.stderr).toContain('@deepseek-ai/dsh packed no documentation') + expect(readdirSync(join(root, 'packed')).filter(name => name.endsWith('.tgz'))).toHaveLength(4) expect(existsSync(join(root, 'packed', PUBLISH_ORDER_FILE))).toBe(false) }) diff --git a/tsdown.config.ts b/tsdown.config.ts index 6c7396eb77..c378de376a 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -30,9 +30,6 @@ export default defineConfig(({ env }) => { fixedExtension: false, dts: false, clean: false, - // Published packages carry their source and its maps, so the shipped - // JavaScript is compressed for size and identifier names stay readable. - minify: { compress: true, mangle: false }, plugins: client ? [] : [typertPlugin({ mode: 'workspace', faces: ['host'] })], } })