Skip to content

Commit c40ae89

Browse files
committed
fix(release): esbuild 先打成单 CJS 再 pkg(解 ESM 打包崩溃)
rc3 定位到真因:package.json "type":"module" 让 pkg 把每个 src/*.js 当 ESM 加载, 但其 bytecode/fallback 产物是 CommonJS(module.exports)→ 启动即 "module is not defined in ES module scope"(config.js:25)。 改用 esbuild 先把整个 ESM 图打成单个 CJS(src/_bundle.cjs,无 per-file ESM/CJS 歧义),再 pkg 包这个 CJS。bundle 用 banner shim 保留 import.meta.url (pathToFileURL(__filename)),使 config.js/server.js/devin-connect-models 仍能 相对 bundle 解析 dashboard/ 与 data/;pkg assets 把这些文件按同路径打进 snapshot。 本地已验:bundle 起服务 /health 200 + /dashboard 484KB + /v1/models 200。 - package.json: build:bundle(esbuild)+ build:exe(bundle 后 pkg src/_bundle.cjs); pkg.assets 加 src/data/*.json + package.json。 - release.yml windows-exe: npm run build:bundle → pkg 单 CJS。 - .gitignore 忽略 dist-windows/ 与 src/_bundle.cjs(CI 构建产物不进 git)。
1 parent 2901911 commit c40ae89

4 files changed

Lines changed: 27 additions & 16 deletions

File tree

.github/workflows/release.yml

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,23 @@ jobs:
128128
# runner → fails). node22 is LTS and has multiple prebuilts.
129129
node-version: '22'
130130

131-
# Build the single .exe with @yao-pkg/pkg. The Windows runner has the
132-
# network + toolchain to fetch (or, worst case, compile) the pkg base
133-
# binary — neither is reliably available on a dev box. Zero runtime deps,
134-
# so nothing to `npm ci`; pkg reads the `pkg` config block in package.json
135-
# (assets = the dashboard html/i18n/data files server.js reads at runtime,
136-
# served from pkg's snapshot FS so no source change is needed).
131+
# Build the single .exe. pkg can't ingest our ESM tree directly:
132+
# package.json has "type":"module", so pkg loads each src/*.js as ESM but
133+
# its bytecode/fallback path emits CommonJS (module.exports) → "module is
134+
# not defined in ES module scope" at boot (seen in rc1/rc2). Fix: esbuild
135+
# first bundles the whole ESM graph into ONE CommonJS file (src/_bundle.cjs)
136+
# — no per-file ESM/CJS ambiguity — then pkg wraps that single CJS file.
137+
# The bundle keeps import.meta.url meaningful via a banner shim
138+
# (pathToFileURL(__filename)), so config.js/server.js/devin-connect-models
139+
# still resolve dashboard/ + data/ relative to the bundle; pkg's `assets`
140+
# (in package.json) map those files into the snapshot at the same paths.
141+
# The Windows runner has the network to fetch pkg's node22 base binary
142+
# (node20 has none → don't use it). Zero runtime deps, so no `npm ci`.
137143
- name: Build windsurfapi.exe
138144
run: |
139-
npm i -g @yao-pkg/pkg@6.21.0
140-
# --fallback-to-source: our ESM entry can't be turned into V8 bytecode
141-
# by pkg (server.js/config.js warn "Failed to make bytecode"); ship
142-
# those as plain source inside the snapshot instead of failing/booting
143-
# a half-compiled exe.
144-
pkg . --targets node22-win-x64 --fallback-to-source --output dist-windows/windsurfapi.exe
145+
npm i -g @yao-pkg/pkg@6.21.0 esbuild
146+
npm run build:bundle
147+
pkg src/_bundle.cjs --config package.json --targets node22-win-x64 --output dist-windows/windsurfapi.exe
145148
146149
- name: Smoke-check the exe boots
147150
shell: pwsh

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,7 @@ docs/*.bak
9999
2026-*dprojectwindsurfapiclaudetxt*.txt
100100
*这个是*.txt
101101
*这也是*.txt
102+
103+
# Windows single-exe build artifacts (built in CI, never committed)
104+
/dist-windows/
105+
/src/_bundle.cjs

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,17 @@
2424
"probe:web-search": "node scripts/web-search-direct-probe.mjs",
2525
"sync:contributors": "node scripts/sync-docs-contributors.mjs",
2626
"secret-scan": "node scripts/secret-scan.mjs",
27-
"build:exe": "pkg . --targets node22-win-x64 --fallback-to-source --output dist-windows/windsurfapi.exe"
27+
"build:bundle": "esbuild src/index.js --bundle --platform=node --format=cjs --target=node22 --outfile=src/_bundle.cjs --external:node:* --define:import.meta.url=__WA_IMPORT_META_URL__ --banner:js=\"const __WA_IMPORT_META_URL__=require('node:url').pathToFileURL(__filename).href;\"",
28+
"build:exe": "npm run build:bundle && pkg src/_bundle.cjs --config package.json --targets node22-win-x64 --output dist-windows/windsurfapi.exe"
2829
},
2930
"pkg": {
30-
"scripts": ["src/**/*.js"],
3131
"assets": [
3232
"src/dashboard/index.html",
3333
"src/dashboard/index-sketch.html",
3434
"src/dashboard/i18n/*.json",
3535
"src/dashboard/data/*.json",
36-
"src/vendor/**/*"
36+
"src/data/*.json",
37+
"package.json"
3738
],
3839
"targets": ["node22-win-x64"],
3940
"outputPath": "dist-windows"

test/release-workflow.test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ describe('release workflow', () => {
3131
const winExe = jobBlock('windows-exe');
3232
assert.match(winExe, /\bneeds:\s*test\b/);
3333
assert.match(winExe, /runs-on:\s*windows-latest/);
34-
assert.match(winExe, /pkg \. --targets node22-win-x64/);
34+
// esbuild bundles the ESM graph to one CJS file, then pkg wraps it (pkg
35+
// can't ingest the raw "type":"module" tree — emits CJS into ESM scope).
36+
assert.match(winExe, /npm run build:bundle/);
37+
assert.match(winExe, /pkg src\/_bundle\.cjs .*node22-win-x64/);
3538
// Must smoke-check the exe actually boots + serves the dashboard, so a
3639
// broken asset bundle fails the release rather than shipping a dead exe.
3740
assert.match(winExe, /\/health/);

0 commit comments

Comments
 (0)