Skip to content

fix: 打包 zod 进 dist——薄安装(dsh/pi 插件裸拷)启动即崩的 P0(#3511) - #3519

Merged
ranxianglei merged 3 commits into
masterfrom
fix/dist-zod-selfcontained
Oct 17, 2026
Merged

ranxianglei merged 3 commits into
masterfrom
fix/dist-zod-selfcontained

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

ai: zhipuai-lb/glm-5.3

修 #3511 的 P0(jimp 把 zod 带进 dist,薄安装启动即崩),顺带加构建门禁防复发。

病根

#3425 做图片降采样引入 jimp(devDependency、tsup noExternal 内联)。jimp 自己的代码在模块顶层 import { z } from "zod",而 zod 是运行时依赖(opencode V1 宿主互作用,086e80a2 有意为之)所以保持 external——两边一叠加,内联的 jimp 把 16 处静态 from"zod" 导入泄漏进了 dist/index.js 和 dist/agent/pi-native.js。

静态导入在模块加载时解析、没有降级路径:凡是「包旁边没有 node_modules」的安装形态(dsh profile 裸拷、pi 插件薄拷——真实插件安装路径)直接 ERR_MODULE_NOT_FOUND,代理起不来,流量静默直连不压缩。带依赖树的正常安装(npm/pnpm)不受影响,所以 CI 没炸。

修法(三层)

  1. zod 进 noExternal 打包:dist/index.js/dist/agent/pi-native.js 恢复自包含(各 +1.4MB),静态导入消失。实测:裸拷 dist/ + 零 node_modules,node dist/index.js --version 干净退出(exit 0)。
  2. opencode V1 互用不受影响:它那个「拿宿主 zod 实例」的懒加载改成不透明说明符(const s="zod"; await import(s)),esbuild 无法静态改写到内联副本——运行时行为与之前逐字节等价(薄安装照样走文档化的降级),28/28 测试过。
  3. 构建门禁:新 scripts/check-dist-external-zod.mjs 挂进 npm run build,dist 里再出现任何静态 zod 导入(import-from/require/字面量动态 import)直接构建失败;合法的懒加载路径(变量说明符)和普通 "zod" 字符串(zod 自己的 vendor 标记)不误报。

验证

Ref #3511

… P0)

#3425 inlined jimp (devDependency, noExternal), and jimp imports zod at module scope: 16 STATIC from"zod" imports landed in dist/index.js and dist/agent/pi-native.js. Static imports have no degrade path, so installs shipping the package without its node_modules (dsh profile bare copies, thin pi plugin copies) died with ERR_MODULE_NOT_FOUND and traffic silently went direct, uncompressed. Bundle zod like every other transitive of an inlined dep (~1.4 MB on the two affected entries). The one sanctioned external consumer — opencode V1 host interop, which wants the HOST zod instance and already degrades when unresolvable — keeps its runtime resolution via an opaque specifier, unchanged behavior. New build gate scripts/check-dist-external-zod.mjs fails the build if a static zod import ever reappears; verified a bare dist copy with zero node_modules now runs --version clean.
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: fix/dist-zod-selfcontained (1e1f7d7)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.4165-pr.3519.3340a52

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr3519.tgz
npm install -g package

Install instructions are refreshed on each push.

Gives the two guard helpers real consumers (unused-exports gate) and locks the regex: the four #3425 leak forms are violations, the sanctioned opaque lazy import and zod vendor strings are not.
Same shape as tests/dist-import-annotations.test.ts: a variable-specifier dynamic import keeps tsc away from the untyped .mjs while still giving the gate helpers a consumer.
@ranxianglei
ranxianglei merged commit 766720f into master Oct 17, 2026
42 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.529-master.766720f (rolling master tag).

npm install -g billion-context@0.0.529-master.766720f  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

ranxianglei added a commit that referenced this pull request Oct 17, 2026
…ernal-zod build gate

Conflict resolution (#3519 vs #3524, same P0, two designs):

- tsup.config.ts: keep jimp EXTERNAL + lazy-load degrade (this branch's
  design). Master's noExternal:["zod"] is dropped as moot — with jimp out
  of the bundle nothing statically imports zod, so bundling it had no
  effect anymore. dist/index.js shrinks 7.1MB -> 4.8MB.
- package.json build line: keep master's check-dist-external-zod.mjs gate
  (stricter: flags literal import("zod") too, catches any future static
  zod leak). This branch's check-dist-no-zod.mjs + tests/dist-no-zod.test.ts
  removed as the weaker duplicate.
- ework's optionalDependencies jimp + guardrail knob wiring unchanged.

Verified: npm run build + dist-external-zod gate OK; thin install
(bare dist, no node_modules) `node dist/index.js --version` exit 0;
issue3400-image-rescale 18 + check-dist-external-zod 3 + opencode-native-v1 28
= 49 pass; typecheck clean.
tgazer933-boop pushed a commit to tgazer933-boop/billion-context that referenced this pull request Oct 18, 2026
…all (ranxianglei#3617-C)

New scripts/check-dist-thin-install.mjs, wired into the static-gates job after
> billion-context@0.1.199 build
> npm --prefix kernel run build && npm --prefix pi-subagents run build && tsup && node scripts/write-build-info.mjs && node scripts/check-dist-import-annotations.mjs && node scripts/check-dist-external-zod.mjs

> acp-kernel@0.0.130 build
> npm run clean && tsup && tsc --emitDeclarationOnly

> acp-kernel@0.0.130 clean
> node -e "require('fs').rmSync('dist',{recursive:true,force:true})"

�[34mCLI�[39m Building entry: src/index.ts, src/panel/index.ts, src/persist/index.ts, src/wire/index.ts
�[34mCLI�[39m Using tsconfig: tsconfig.json
�[34mCLI�[39m tsup v8.5.1
�[34mCLI�[39m Using tsup config: /var/lib/ework-daemon/share/opencode-workdir/ranxianglei--billion-context/3617/ework-daemon/kernel/tsup.config.ts
�[34mCLI�[39m Target: es2022
�[34mESM�[39m Build start
�[32mESM�[39m �[1mdist/persist/index.js      �[22m�[32m19.83 KB�[39m
�[32mESM�[39m �[1mdist/wire/index.js         �[22m�[32m58.33 KB�[39m
�[32mESM�[39m �[1mdist/index.js              �[22m�[32m314.09 KB�[39m
�[32mESM�[39m �[1mdist/chunk-KJXO6XX7.js     �[22m�[32m29.34 KB�[39m
�[32mESM�[39m �[1mdist/chunk-6ECDTFU5.js     �[22m�[32m2.02 KB�[39m
�[32mESM�[39m �[1mdist/panel/index.js        �[22m�[32m6.96 KB�[39m
�[32mESM�[39m �[1mdist/persist/index.js.map  �[22m�[32m46.16 KB�[39m
�[32mESM�[39m �[1mdist/wire/index.js.map     �[22m�[32m154.06 KB�[39m
�[32mESM�[39m �[1mdist/chunk-KJXO6XX7.js.map �[22m�[32m53.58 KB�[39m
�[32mESM�[39m �[1mdist/chunk-6ECDTFU5.js.map �[22m�[32m4.38 KB�[39m
�[32mESM�[39m �[1mdist/panel/index.js.map    �[22m�[32m17.58 KB�[39m
�[32mESM�[39m �[1mdist/index.js.map          �[22m�[32m752.42 KB�[39m
�[32mESM�[39m ⚡️ Build success in 92ms

> billion-context-pi-subagents@0.2.0 build
> tsup && tsc --emitDeclarationOnly

�[34mCLI�[39m Building entry: src/index.ts
�[34mCLI�[39m Using tsconfig: tsconfig.json
�[34mCLI�[39m tsup v8.5.1
�[34mCLI�[39m Using tsup config: /var/lib/ework-daemon/share/opencode-workdir/ranxianglei--billion-context/3617/ework-daemon/pi-subagents/tsup.config.ts
�[34mCLI�[39m Target: es2022
�[34mCLI�[39m Cleaning output folder
�[34mESM�[39m Build start
�[32mESM�[39m �[1mdist/index.js     �[22m�[32m464.23 KB�[39m
�[32mESM�[39m �[1mdist/index.js.map �[22m�[32m1.31 MB�[39m
�[32mESM�[39m ⚡️ Build success in 266ms
�[34mCLI�[39m Building entry: {"agent/dsh-native-client":"src/agent/dsh-native-client.ts"}
�[34mCLI�[39m Using tsconfig: tsconfig.json
�[34mCLI�[39m tsup v8.5.1
�[34mCLI�[39m Using tsup config: /var/lib/ework-daemon/share/opencode-workdir/ranxianglei--billion-context/3617/ework-daemon/tsup.config.ts
�[34mCLI�[39m Building entry: src/claude-native-bootstrap.ts, src/index.ts, src/mcp.ts, src/agent/dsh-acp.ts, src/agent/dsh-native.ts, src/agent/omp-native.ts, src/agent/omp.ts, src/agent/opencode-native.ts, src/agent/opencode.ts, src/agent/pi-native.ts, src/agent/pi.ts, src/kimi/bootstrap-hook.ts, src/kimi/native-mcp.ts, src/zcode/bootstrap-hook.ts, src/zcode/mcp-entry.ts
�[34mCLI�[39m Using tsconfig: tsconfig.json
�[34mCLI�[39m tsup v8.5.1
�[34mCLI�[39m Using tsup config: /var/lib/ework-daemon/share/opencode-workdir/ranxianglei--billion-context/3617/ework-daemon/tsup.config.ts
�[34mCLI�[39m Target: es2020
�[34mCJS�[39m Build start
�[34mCLI�[39m Target: node20
�[34mCLI�[39m Cleaning output folder
�[34mESM�[39m Build start
�[32mCJS�[39m �[1mdist/agent/dsh-native-client.js �[22m�[32m14.08 KB�[39m
�[32mCJS�[39m ⚡️ Build success in 36ms
�[32mESM�[39m �[1mdist/agent/dsh-acp.js               �[22m�[32m5.77 KB�[39m
�[32mESM�[39m �[1mdist/mcp.js                         �[22m�[32m731.30 KB�[39m
�[32mESM�[39m �[1mdist/agent/omp.js                   �[22m�[32m1.27 MB�[39m
�[32mESM�[39m �[1mdist/agent/pi.js                    �[22m�[32m1.27 MB�[39m
�[32mESM�[39m �[1mdist/claude-native-bootstrap.js     �[22m�[32m2.13 MB�[39m
�[32mESM�[39m �[1mdist/agent/opencode-native.js       �[22m�[32m2.30 MB�[39m
�[32mESM�[39m �[1mdist/agent/opencode.js              �[22m�[32m2.27 MB�[39m
�[32mESM�[39m �[1mdist/kimi/bootstrap-hook.js         �[22m�[32m2.10 MB�[39m
�[32mESM�[39m �[1mdist/kimi/native-mcp.js             �[22m�[32m2.14 MB�[39m
�[32mESM�[39m �[1mdist/zcode/bootstrap-hook.js        �[22m�[32m2.11 MB�[39m
�[32mESM�[39m �[1mdist/zcode/mcp-entry.js             �[22m�[32m2.16 MB�[39m
�[32mESM�[39m �[1mdist/agent/dsh-native.js            �[22m�[32m2.22 MB�[39m
�[32mESM�[39m �[1mdist/agent/dsh-acp.js.map           �[22m�[32m49.41 KB�[39m
�[32mESM�[39m �[1mdist/agent/omp-native.js            �[22m�[32m2.60 MB�[39m
�[32mESM�[39m �[1mdist/index.js                       �[22m�[32m4.64 MB�[39m
�[32mESM�[39m �[1mdist/agent/pi-native.js             �[22m�[32m4.69 MB�[39m
�[32mESM�[39m �[1mdist/mcp.js.map                     �[22m�[32m2.05 MB�[39m
�[32mESM�[39m �[1mdist/agent/omp.js.map               �[22m�[32m5.32 MB�[39m
�[32mESM�[39m �[1mdist/agent/pi.js.map                �[22m�[32m5.32 MB�[39m
�[32mESM�[39m �[1mdist/claude-native-bootstrap.js.map �[22m�[32m6.92 MB�[39m
�[32mESM�[39m �[1mdist/kimi/native-mcp.js.map         �[22m�[32m6.94 MB�[39m
�[32mESM�[39m �[1mdist/zcode/mcp-entry.js.map         �[22m�[32m6.95 MB�[39m
�[32mESM�[39m �[1mdist/agent/opencode.js.map          �[22m�[32m8.07 MB�[39m
�[32mESM�[39m �[1mdist/agent/opencode-native.js.map   �[22m�[32m8.16 MB�[39m
�[32mESM�[39m �[1mdist/zcode/bootstrap-hook.js.map    �[22m�[32m6.91 MB�[39m
�[32mESM�[39m �[1mdist/agent/dsh-native.js.map        �[22m�[32m7.50 MB�[39m
�[32mESM�[39m �[1mdist/kimi/bootstrap-hook.js.map     �[22m�[32m6.91 MB�[39m
�[32mESM�[39m �[1mdist/agent/omp-native.js.map        �[22m�[32m8.85 MB�[39m
�[32mESM�[39m �[1mdist/index.js.map                   �[22m�[32m13.03 MB�[39m
�[32mESM�[39m �[1mdist/agent/pi-native.js.map         �[22m�[32m14.22 MB�[39m
�[32mESM�[39m ⚡️ Build success in 3342ms
build-info: commit=fd3232fae-dirty
dist-import-annotations: OK
dist-external-zod: OK (additive step; no matrix/leg-name changes, branch protection untouched). It packs the current tree, installs ONLY the tarball into a scratch dir with no repo node_modules, then proves the artifact runs: 0.1.199 (fd3232f-dirty) exits 0, and every Node-consumed dist/agent/*.js entry imports in its own process. dsh-native-client.js is a browser bundle by design (tsup platform:browser, window.__ModuleLoader__ format) — parse-checked instead of imported.\n\nWhy: ranxianglei#3511 (zod statically in dist while undeclared) shipped because PR CI never installed the tarball into a bare dir — ranxianglei#3519's check-dist-external-zod gate is static analysis for ONE known offender and CI ran it only inside the build chain. This smoke catches ANY undeclared runtime dependency at import time, in the same shape a real consumer hits it. Verified negative: injecting a fake undeclared import into a built bundle fails the script with ERR_MODULE_NOT_FOUND.\n\nDeliberately NOT added to the local build chain or the prepare gate: it needs registry access for declared deps and must not slow consumer installs. Also exposed as
> billion-context@0.1.199 check:dist-thin
> node scripts/check-dist-thin-install.mjs

dist-thin-install: OK — cli 0.1.199; imported 8 agent bundle(s) [dsh-acp.js, dsh-native.js, omp-native.js, omp.js, opencode-native.js, opencode.js, pi-native.js, pi.js]; parse-checked browser bundle(s) [dsh-native-client.js] for manual use. No behavior change to the product.
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.

1 participant