@dimina-kit/devkit 0.1.2-dev.20260706064107 → 0.1.2-dev.20260706131625

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,7 +48,7 @@ await session.close()
48
48
  | `outputDir` | `string` | -- | 编译产物输出目录,默认 `<os.tmpdir()>/dimina-kit/<projectPath 哈希前 12 位>`(每个项目独立) |
49
49
  | `watch` | `boolean` | `true` | 为 `false` 时跳过 chokidar 文件监听 / 自动重编译循环 |
50
50
  | `onRebuild` | `() => void` | -- | 文件变更触发重新编译后的回调 |
51
- | `onBuildError` | `(err: unknown) => void` | -- | 编译出错时的回调 |
51
+ | `onBuildError` | `(err: unknown) => void` | -- | watch 重编译出错时的回调;**初次编译失败不走这里,而是直接 reject `openProject` 本身**(见下) |
52
52
  | `onLog` | `(entry) => void` | -- | 逐行编译日志回调;`entry = { stream: 'stdout' \| 'stderr', text }`,已经过内置噪音过滤(`filterDmccLogLine`) |
53
53
 
54
54
  #### 返回值 ProjectSession
@@ -59,6 +59,12 @@ await session.close()
59
59
  | `port` | `number` | 实际监听的端口 |
60
60
  | `close` | `() => Promise<void>` | 关闭文件监听和预览服务器 |
61
61
 
62
+ #### 失败语义
63
+
64
+ - **初次编译失败 → `openProject` reject**,错误即编译失败原因(`[compiler] stage "…" failed: …`);不会带着 project.config.json 的兜底 appId 启动一个必然 404 的会话。reject 前编译日志行已全部送达 `onLog`,已启动的编译子进程会被清理。
65
+ - **watch 重编译失败 → `onBuildError(err)`**,会话保持存活,修复文件保存后自动重编译恢复。
66
+ - **Electron 打包分发**:编译路径需要 oxc-parser 的运行时绑定实际存在于分发包内(`@oxc-parser/binding-<platform>-<arch>` 或 `@oxc-parser/binding-wasm32-wasi` 之一),它们不是宿主的直接依赖,容易被 electron-builder 依赖收集丢弃——缺失时每次编译都会以 `Cannot find native binding` 失败(错误信息会附带打包提示)。详见 `@dimina-kit/compiler` README 的打包注意一节。
67
+
62
68
  ### enableCompileWorkerStandby(热备胎,可选加速器)
63
69
 
64
70
  进程级开关:预先 fork 一个**项目无关**的编译子进程并预热编译器(不 chdir、不编译任何项目),之后每次 `openProject` 自动领养它——首编省掉 fork + 编译器加载(实测 ~1.6s);每次 `session.close()` 后自动补一个新备胎。**纯加速器**:备胎的任何失效(悄悄死亡、健康检查失败、崩溃熔断)都自动退化为原有的冷 fork 路径,绝不影响 openProject 的成败。
@@ -56,9 +56,11 @@ export interface CompileWorkerHandlerDeps {
56
56
  * Build the fork-side IPC message handler. Unknown messages are ignored
57
57
  * (no compiler load, no reply). A build command chdirs into the project,
58
58
  * runs the compiler exactly as the old in-process call sites did, and ALWAYS
59
- * replies — `@dimina/compiler` swallows compile errors internally (resolves
60
- * undefined), which is normalized to `appInfo: null`; a genuine throw still
61
- * replies with `error.message` so the parent never hangs on a lost build.
59
+ * replies — a compile failure rejects out of the pool's build() and is
60
+ * relayed as `error.message` (the parent rejects its in-flight build on it),
61
+ * so the parent never hangs on a lost build and never mistakes a failed
62
+ * compile for success. A resolved null/undefined appInfo carries no error:
63
+ * it means "no app info to report" and is normalized to `appInfo: null`.
62
64
  *
63
65
  * Two warm-standby commands ride the same channel:
64
66
  * - `{ cmd: 'ping', id }` → `{ type: 'pong', id }`. Pure liveness; never
@@ -1 +1 @@
1
- {"version":3,"file":"compile-worker-entry.d.ts","sourceRoot":"","sources":["../src/compile-worker-entry.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,gFAAgF;IAChF,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;CAC5E;AAED,MAAM,MAAM,aAAa,GAAG,CAC3B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,OAAO,EACpB,OAAO,EAAE,kBAAkB,KACvB,OAAO,CAAC,aAAa,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;AAE9C,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,aAAa,GAAG,IAAI,CAAA;IAC7B,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAC3B;AAED,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACV;AAED,8EAA8E;AAC9E,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,gBAAgB,CAAA;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,OAAO,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,qBAAqB,GAC9B,mBAAmB,GAAG,iBAAiB,GAAG,0BAA0B,CAAA;AAEvE,MAAM,WAAW,wBAAwB;IACxC;;;;;OAKG;IACH,YAAY,EAAE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAC1D,4DAA4D;IAC5D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,8CAA8C;IAC9C,IAAI,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,IAAI,CAAA;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9B;AA0BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CACzC,IAAI,EAAE,wBAAwB,GAC5B,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CA4CjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACvC,IAAI,EAAE;IACL,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,OAAO,CAAA;IACxF,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B,EACD,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GACtC,IAAI,CASN"}
1
+ {"version":3,"file":"compile-worker-entry.d.ts","sourceRoot":"","sources":["../src/compile-worker-entry.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,gFAAgF;IAChF,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;CAC5E;AAED,MAAM,MAAM,aAAa,GAAG,CAC3B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,OAAO,EACpB,OAAO,EAAE,kBAAkB,KACvB,OAAO,CAAC,aAAa,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;AAE9C,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,aAAa,GAAG,IAAI,CAAA;IAC7B,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAC3B;AAED,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACV;AAED,8EAA8E;AAC9E,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,gBAAgB,CAAA;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,OAAO,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,qBAAqB,GAC9B,mBAAmB,GAAG,iBAAiB,GAAG,0BAA0B,CAAA;AAEvE,MAAM,WAAW,wBAAwB;IACxC;;;;;OAKG;IACH,YAAY,EAAE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAC1D,4DAA4D;IAC5D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,8CAA8C;IAC9C,IAAI,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,IAAI,CAAA;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9B;AA0BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,0BAA0B,CACzC,IAAI,EAAE,wBAAwB,GAC5B,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CA4CjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACvC,IAAI,EAAE;IACL,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,OAAO,CAAA;IACxF,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B,EACD,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GACtC,IAAI,CASN"}
@@ -29,9 +29,11 @@ function isCommandWithId(msg, cmd) {
29
29
  * Build the fork-side IPC message handler. Unknown messages are ignored
30
30
  * (no compiler load, no reply). A build command chdirs into the project,
31
31
  * runs the compiler exactly as the old in-process call sites did, and ALWAYS
32
- * replies — `@dimina/compiler` swallows compile errors internally (resolves
33
- * undefined), which is normalized to `appInfo: null`; a genuine throw still
34
- * replies with `error.message` so the parent never hangs on a lost build.
32
+ * replies — a compile failure rejects out of the pool's build() and is
33
+ * relayed as `error.message` (the parent rejects its in-flight build on it),
34
+ * so the parent never hangs on a lost build and never mistakes a failed
35
+ * compile for success. A resolved null/undefined appInfo carries no error:
36
+ * it means "no app info to report" and is normalized to `appInfo: null`.
35
37
  *
36
38
  * Two warm-standby commands ride the same channel:
37
39
  * - `{ cmd: 'ping', id }` → `{ type: 'pong', id }`. Pure liveness; never
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAgB,eAAe,EAAiB,MAAM,qBAAqB,CAAA;AAEvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAEpG,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACzD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAA;AACxE,YAAY,EACX,oBAAoB,EACpB,2BAA2B,EAC3B,YAAY,EACZ,YAAY,GACZ,MAAM,6BAA6B,CAAA;AAYpC;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACzC,IAAI,CAAC,EAAE,2BAA2B,GAChC,oBAAoB,CAKtB;AAqDD,MAAM,WAAW,OAAO;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oFAAoF;IACpF,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IACxC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC;AAuFD,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAoJnF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACpC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GACrC;IAAE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CA2DtD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAgB,eAAe,EAAiB,MAAM,qBAAqB,CAAA;AAEvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAEpG,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACzD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAA;AACxE,YAAY,EACX,oBAAoB,EACpB,2BAA2B,EAC3B,YAAY,EACZ,YAAY,GACZ,MAAM,6BAA6B,CAAA;AAYpC;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACzC,IAAI,CAAC,EAAE,2BAA2B,GAChC,oBAAoB,CAKtB;AAqDD,MAAM,WAAW,OAAO;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oFAAoF;IACpF,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IACxC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC;AAuFD,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0JnF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACpC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GACrC;IAAE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CA2DtD"}
package/dist/index.js CHANGED
@@ -178,16 +178,22 @@ export async function openProject(opts) {
178
178
  initialAppInfo = (await compileWorker.build(buildRequest));
179
179
  }
180
180
  catch (err) {
181
+ // The first compile failing IS the open failing: a compile error rejects
182
+ // out of the worker (the pool relays it as a real rejection), and a
183
+ // session started anyway could only serve 404s (no artifacts in
184
+ // outputDir) while the real cause stays invisible. Tear down and rethrow
185
+ // so the host sees the compile error as openProject's failure.
181
186
  await compileWorker.close();
182
187
  // A failed open consumed the spare too — refill (fire-and-forget) so the
183
188
  // NEXT open attempt still starts warm.
184
189
  refillStandby();
185
190
  throw err;
186
191
  }
187
- // When the compiler is racing (e.g. opening a project that was just
188
- // closed elsewhere in the same Electron process) `build()` can return
189
- // null even though the manifest on disk is perfectly readable. The
190
- // fallback to `{ appId: 'unknown' }` is load-bearing for
192
+ // A compile FAILURE rejects above a resolved null never means "compile
193
+ // failed". It means the compiler had no app info to report: e.g. racing a
194
+ // project that was just closed elsewhere in the same Electron process,
195
+ // where the manifest on disk is perfectly readable. The fallback to
196
+ // `{ appId: 'unknown' }` is load-bearing for
191
197
  // any consumer that calls `wx.setStorageSync` — the dimina runtime
192
198
  // stores values under `${appId}_${key}` and the devtools storage panel
193
199
  // also derives its IPC prefix from `appInfo.appId`. A stale `unknown`
@@ -208,7 +214,7 @@ export async function openProject(opts) {
208
214
  // real cause here, at the point the invalid id is produced, instead of
209
215
  // letting it fail opaquely at injection time.
210
216
  const reason = `[devkit] could not resolve an appId for ${projectPath}: the compiler produced no app info and project.config.json has no "appid". `
211
- + 'Falling back to "unknown" — the mini-program likely failed to compile or is missing its manifest.';
217
+ + 'Falling back to "unknown" — the mini-program is likely missing its manifest.';
212
218
  console.warn(reason);
213
219
  onBuildError?.(new Error(reason));
214
220
  }
@@ -54,17 +54,29 @@ function expectNoNoise(texts) {
54
54
  }
55
55
  }
56
56
  describe('openProject onLog — first compile (integration, real dmcc)', () => {
57
- it('streams filtered compile-error lines to onLog when the first compile fails', async () => {
57
+ it('rejects openProject itself when the first compile fails, after streaming every filtered log line to onLog', async () => {
58
58
  const root = makeFixture({ brokenIndexJs: true });
59
59
  const entries = [];
60
60
  const logOpts = { onLog: (entry) => entries.push(entry) };
61
- const session = await devkit.openProject({
62
- projectPath: root,
63
- watch: false,
64
- outputDir: path.join(root, '.out'),
65
- ...logOpts,
66
- });
67
- openSessions.push(session);
61
+ let caught;
62
+ try {
63
+ const session = await devkit.openProject({
64
+ projectPath: root,
65
+ watch: false,
66
+ outputDir: path.join(root, '.out'),
67
+ ...logOpts,
68
+ });
69
+ // Only reached if openProject wrongly resolved — track it for teardown
70
+ // so a false-negative pass here still doesn't leak a dev server/watcher.
71
+ openSessions.push(session);
72
+ }
73
+ catch (err) {
74
+ caught = err;
75
+ }
76
+ expect(caught, 'openProject must REJECT when the first compile fails — a broken project must never silently open a session').toBeInstanceOf(Error);
77
+ const message = caught.message;
78
+ expect(message, 'the rejection must name the failing stage, not a generic wrapper message').toMatch(/stage "logic" failed/);
79
+ expect(message, 'the rejection must carry the underlying compile reason (esbuild transform failure), not just the stage name').toMatch(/Transform failed/);
68
80
  expect(entries.length, 'openProject must wire opts.onLog around the first build() — no lines were captured').toBeGreaterThan(0);
69
81
  const texts = entries.map(entry => entry.text);
70
82
  expect(texts.some(text => /esbuild 转换失败/.test(text)), 'the `[logic] esbuild 转换失败 <file>` line is the highest-value error line and must be kept').toBe(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dimina-kit/devkit",
3
- "version": "0.1.2-dev.20260706064107",
3
+ "version": "0.1.2-dev.20260706131625",
4
4
  "description": "Development toolkit for Dimina mini-apps with H5 container preview",
5
5
  "keywords": [
6
6
  "dimina",
@@ -62,7 +62,7 @@
62
62
  "cors": "^2.8.6",
63
63
  "express": "^5.2.1",
64
64
  "open": "^11.0.0",
65
- "@dimina-kit/compiler": "0.0.1-dev.20260706064107"
65
+ "@dimina-kit/compiler": "0.0.1-dev.20260706131625"
66
66
  },
67
67
  "publishConfig": {
68
68
  "access": "public"