@dimina-kit/devtools 0.3.1-dev.20260513123738 → 0.3.1-dev.20260515105504

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.
Files changed (109) hide show
  1. package/README.md +103 -41
  2. package/dist/main/api.d.ts +6 -2
  3. package/dist/main/api.js +5 -2
  4. package/dist/main/app/app.d.ts +1 -1
  5. package/dist/main/app/app.js +71 -36
  6. package/dist/main/app/launch.d.ts +2 -2
  7. package/dist/main/app/launch.js +6 -3
  8. package/dist/main/index.bundle.js +1074 -289
  9. package/dist/main/ipc/app.d.ts +2 -1
  10. package/dist/main/ipc/app.js +5 -4
  11. package/dist/main/ipc/index.d.ts +5 -4
  12. package/dist/main/ipc/index.js +5 -4
  13. package/dist/main/ipc/panels.d.ts +2 -1
  14. package/dist/main/ipc/panels.js +14 -9
  15. package/dist/main/ipc/popover.d.ts +4 -1
  16. package/dist/main/ipc/popover.js +15 -5
  17. package/dist/main/ipc/projects.d.ts +4 -1
  18. package/dist/main/ipc/projects.js +42 -5
  19. package/dist/main/ipc/session.d.ts +4 -1
  20. package/dist/main/ipc/session.js +26 -6
  21. package/dist/main/ipc/settings.d.ts +4 -1
  22. package/dist/main/ipc/settings.js +30 -17
  23. package/dist/main/ipc/simulator-module.d.ts +8 -0
  24. package/dist/main/ipc/simulator-module.js +19 -0
  25. package/dist/main/ipc/simulator.d.ts +2 -1
  26. package/dist/main/ipc/simulator.js +29 -15
  27. package/dist/main/ipc/toolbar.d.ts +2 -1
  28. package/dist/main/ipc/toolbar.js +3 -2
  29. package/dist/main/services/automation/index.js +88 -13
  30. package/dist/main/services/mcp/server.d.ts +2 -1
  31. package/dist/main/services/mcp/server.js +5 -0
  32. package/dist/main/services/module.d.ts +18 -0
  33. package/dist/main/services/module.js +2 -0
  34. package/dist/main/services/notifications/renderer-notifier.d.ts +2 -0
  35. package/dist/main/services/projects/project-repository.d.ts +1 -0
  36. package/dist/main/services/projects/project-repository.js +3 -0
  37. package/dist/main/services/settings/index.d.ts +4 -0
  38. package/dist/main/services/settings/index.js +12 -1
  39. package/dist/main/services/simulator/custom-apis.d.ts +10 -0
  40. package/dist/main/services/simulator/custom-apis.js +26 -0
  41. package/dist/main/services/simulator-storage/index.d.ts +22 -1
  42. package/dist/main/services/simulator-storage/index.js +365 -98
  43. package/dist/main/services/update/update-manager.d.ts +16 -1
  44. package/dist/main/services/update/update-manager.js +18 -15
  45. package/dist/main/services/views/view-manager.d.ts +4 -0
  46. package/dist/main/services/views/view-manager.js +33 -2
  47. package/dist/main/services/window-service.d.ts +24 -0
  48. package/dist/main/services/window-service.js +26 -0
  49. package/dist/main/services/workbench-context.d.ts +15 -0
  50. package/dist/main/services/workbench-context.js +6 -0
  51. package/dist/main/services/workspace/workspace-service.d.ts +1 -0
  52. package/dist/main/services/workspace/workspace-service.js +7 -3
  53. package/dist/main/simulator-apis.d.ts +23 -0
  54. package/dist/main/simulator-apis.js +24 -0
  55. package/dist/main/utils/disposable.d.ts +13 -0
  56. package/dist/main/utils/disposable.js +60 -0
  57. package/dist/main/utils/ipc-registry.d.ts +39 -0
  58. package/dist/main/utils/ipc-registry.js +109 -0
  59. package/dist/main/utils/ipc-schema.d.ts +28 -0
  60. package/dist/main/utils/ipc-schema.js +38 -0
  61. package/dist/main/utils/paths.d.ts +6 -0
  62. package/dist/main/utils/paths.js +6 -0
  63. package/dist/main/utils/sender-policy.d.ts +23 -0
  64. package/dist/main/utils/sender-policy.js +42 -0
  65. package/dist/main/windows/main-window/create.d.ts +6 -0
  66. package/dist/main/windows/main-window/create.js +65 -9
  67. package/dist/main/windows/main-window/events.d.ts +2 -1
  68. package/dist/main/windows/main-window/events.js +25 -2
  69. package/dist/main/windows/navigation-hardening.d.ts +24 -0
  70. package/dist/main/windows/navigation-hardening.js +55 -0
  71. package/dist/main/windows/settings-window/create.js +11 -1
  72. package/dist/preload/index.d.ts +17 -0
  73. package/dist/preload/index.js +17 -0
  74. package/dist/preload/instrumentation/app-data.js +178 -19
  75. package/dist/preload/runtime/custom-apis.d.ts +6 -0
  76. package/dist/preload/runtime/custom-apis.js +18 -0
  77. package/dist/preload/runtime/host.d.ts +1 -0
  78. package/dist/preload/runtime/host.js +13 -0
  79. package/dist/preload/windows/main.cjs +47 -0
  80. package/dist/preload/windows/main.cjs.map +7 -0
  81. package/dist/preload/windows/simulator.js +174 -18
  82. package/dist/renderer/assets/index-DW_0thr-.js +46 -0
  83. package/dist/renderer/assets/{input-DxfYnzX_.js → input-C5M5Wxn6.js} +2 -2
  84. package/dist/renderer/assets/{ipc-transport-ClIvDcYY.js → ipc-transport-CUsl-fS2.js} +2 -2
  85. package/dist/renderer/assets/ipc-transport-CltRuvhi.css +1 -0
  86. package/dist/renderer/assets/{popover-CtXcBJtM.js → popover-BSBPVO0o.js} +2 -2
  87. package/dist/renderer/assets/{select-DMfp49f1.js → select-B1tF3UAc.js} +2 -2
  88. package/dist/renderer/assets/{settings-DteGraEU.js → settings-7w54H6p-.js} +2 -2
  89. package/dist/renderer/assets/settings-api-BjJalghB.js +2 -0
  90. package/dist/renderer/assets/workbenchSettings-q2_-nc8t.js +8 -0
  91. package/dist/renderer/entries/main/index.html +12 -5
  92. package/dist/renderer/entries/popover/index.html +7 -5
  93. package/dist/renderer/entries/settings/index.html +7 -5
  94. package/dist/renderer/entries/workbench-settings/index.html +6 -4
  95. package/dist/shared/ipc-channels.d.ts +22 -0
  96. package/dist/shared/ipc-channels.js +23 -0
  97. package/dist/shared/ipc-schemas.d.ts +113 -0
  98. package/dist/shared/ipc-schemas.js +128 -0
  99. package/dist/shared/types.d.ts +2 -0
  100. package/dist/simulator/assets/simulator-DL6kOCq-.js +9 -0
  101. package/dist/simulator/simulator.html +1 -1
  102. package/package.json +11 -27
  103. package/dist/preload/windows/main.d.ts +0 -6
  104. package/dist/preload/windows/main.js +0 -6
  105. package/dist/renderer/assets/index-D1p6crtf.js +0 -46
  106. package/dist/renderer/assets/ipc-transport-EiWxu_hG.css +0 -1
  107. package/dist/renderer/assets/settings-api-CY84UMpN.js +0 -2
  108. package/dist/renderer/assets/workbenchSettings-DjELLnSJ.js +0 -8
  109. package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
package/README.md CHANGED
@@ -150,9 +150,6 @@ src/
150
150
  app.ts # createWorkbenchApp 工厂
151
151
  bootstrap.ts # suppressEpipe / setupCdpPort
152
152
  lifecycle.ts # Electron app 生命周期注册
153
- windows/
154
- main-window/ # 主窗口创建 + 事件接线
155
- settings-window/ # 独立设置窗口
156
153
  ipc/ # 只做 handler 注册,不写业务
157
154
  index.ts # 聚合 register 函数导出
158
155
  simulator.ts # simulator attach/detach/resize
@@ -166,17 +163,33 @@ src/
166
163
  settings/ # 用户级 workbench 设置读写 + 主题应用
167
164
  projects/ # 项目列表 CRUD(持久化)
168
165
  layout/ # 布局常量 + bounds 计算
166
+ views/ # ViewManager(settings / popover overlay 等)
167
+ workspace/ # 项目会话生命周期
168
+ notifications/ # 主进程 → renderer 的事件通知
169
169
  mcp/ # MCP server
170
170
  automation/ # 自动化 WebSocket 服务
171
171
  simulator/ # simulator dir / referer
172
+ simulator-storage/ # CDP DOMStorage + 元素 inspect 桥接
173
+ update/ # UpdateManager + GitHubReleaseChecker
172
174
  workbench-context.ts # 共享状态类型 + 工厂 + 辅助函数
175
+ window-service.ts # main / settings 窗口聚合访问
176
+ module.ts # WorkbenchModule 协议
173
177
  default-adapter.ts # 默认 CompilationAdapter
174
178
  menu/ # 应用菜单
175
- utils/paths.ts # rendererDir / defaultPreloadPath
179
+ utils/
180
+ paths.ts # rendererDir / defaultPreloadPath / simulatorDir
181
+ disposable.ts # DisposableRegistry + AggregateError 聚合
182
+ ipc-registry.ts # ipcMain.handle 包装 + SenderPolicy 闸门
183
+ sender-policy.ts # sender webContents id 白名单
184
+ ipc-schema.ts # zod schema 校验工具
185
+ windows/
186
+ main-window/ # 主窗口创建 + 事件接线
187
+ settings-window/ # 独立设置窗口
188
+ navigation-hardening.ts # will-navigate / setWindowOpenHandler 限流
176
189
 
177
190
  preload/ # 按注入目标分
178
191
  windows/ # contextBridge 类型 preload
179
- main.ts # 主窗口 preload,可组合导出 install*Instrumentation
192
+ main.ts # workbench 三窗口 preload,仅暴露 window.devtools.ipc
180
193
  simulator.ts # simulator webview 的默认 preload
181
194
  shared/ # preload 之间共享
182
195
  api-compat.ts # setupApiCompatHook
@@ -329,33 +342,72 @@ launch({ preloadPath: '/absolute/path/to/my-preload.js' })
329
342
 
330
343
  ---
331
344
 
345
+ ## Simulator 自定义 API
346
+
347
+ 下游可以通过 `registerSimulatorApi` 把业务 API 注入到模拟器内运行的小程序。Handler 在 Electron **主进程**执行,能用任何 Node 能力(fs、net、原生模块、持久状态);模拟器侧拿到 `wx.<name>(params)` 调用后通过 IPC 转发到主进程,await 返回结果,handler 抛错则原型回传给小程序。
348
+
349
+ ```typescript
350
+ import { registerSimulatorApi } from '@dimina-kit/devtools/simulator-apis'
351
+
352
+ const dispose = registerSimulatorApi('myCompany.login', async ({ username }) => {
353
+ // 主进程上下文:可调用内部 HTTP、读取凭证文件、访问原生模块
354
+ return await callInternalAuth(username)
355
+ })
356
+
357
+ // 不再需要时取消注册(不传也行,进程退出会一起清理)
358
+ dispose()
359
+ ```
360
+
361
+ 几点行为约定:
362
+
363
+ - 同名重复注册:后注册的覆盖前者,静默替换。
364
+ - `dispose()` 只会移除自己创建的那次注册——如果之后被别人覆盖了,旧的 disposer 是 no-op。
365
+ - 调用未注册的 name:小程序侧拿到 reject,错误信息里带 name。
366
+ - Handler 可同步或异步返回;返回值需可 JSON 序列化(IPC 限制)。
367
+
368
+ 对小程序侧来说,这些 name 和内置 API(`wx.getStorage` 等)走同一个 `AppManager.apiRegistry`,没有区别。
369
+
370
+ ---
371
+
332
372
  ## 模块导出一览
333
373
 
374
+ ### Stable public exports
375
+
376
+ `@dimina-kit/devtools` 的稳定入口,签名遵循 semver;新版本不会做破坏性改动(除非 major bump)。
377
+
334
378
  ```
335
379
  @dimina-kit/devtools launch, createWorkbenchApp, buildDefaultMenu,
336
380
  openSettingsWindow, suppressEpipe, setupCdpPort,
337
- createWorkbenchContext, createMainWindow, ...
338
- (api.ts 聚合了下方全部入口)
381
+ createWorkbenchContext, createMainWindow,
382
+ createViewManager, register*Ipc,
383
+ registerSimulatorApi,
384
+ setHeaderHeight, UpdateManager,
385
+ createGitHubReleaseChecker, ...
386
+ (api.ts 聚合了所有公共 API;优先从根入口导入)
339
387
  @dimina-kit/devtools/launch launch(config?), buildDefaultMenu, openSettingsWindow
340
388
  @dimina-kit/devtools/app createWorkbenchApp(config?)
341
389
  @dimina-kit/devtools/types TypeScript 类型定义
390
+ @dimina-kit/devtools/bootstrap suppressEpipe(), setupCdpPort()
391
+ @dimina-kit/devtools/paths rendererDir, defaultPreloadPath, simulatorDir,
392
+ getRendererDir, getPreloadDir, getRendererHtml
393
+ @dimina-kit/devtools/simulator-apis registerSimulatorApi(name, handler) →
394
+ 注入由主进程托管的小程序 API(详见上方"Simulator 自定义 API")
395
+ @dimina-kit/devtools/preload installConsoleInstrumentation,
396
+ installStorageInstrumentation,
397
+ installAppDataInstrumentation, sendAllAppData,
398
+ installWxmlInstrumentation, sendWxmlTree,
399
+ setupWxmlObserver, installSimulatorBridge,
400
+ setupApiCompatHook
401
+ ```
402
+
403
+ ### Experimental exports (v0.x — signatures may change in minor versions)
404
+
405
+ 下列子路径主要用于"模块组装"等深度定制场景。在 0.x 阶段,函数签名和模块边界可能在 minor 版本之间调整;如果你不需要逐模块组装,请优先使用上方的 stable 入口或根 barrel。
406
+
407
+ ```
342
408
  @dimina-kit/devtools/context createWorkbenchContext(opts),
343
409
  hasBuiltinPanel(ctx, panelId), getDefaultTab(ctx)
344
410
  @dimina-kit/devtools/create-window createMainWindow(opts)
345
- @dimina-kit/devtools/view-manager createViewManager(ctx) → ViewManager
346
- (attachSimulator, detachSimulator, showSimulator,
347
- hideSimulator, showSettings, hideSettings,
348
- showPopover, hidePopover, resize,
349
- setVisible, repositionAll, disposeAll)
350
- @dimina-kit/devtools/layout computeRightPanelBounds, computeSimulatorBounds,
351
- computeSettingsBounds, computePopoverBounds,
352
- setHeaderHeight, getRightX,
353
- HEADER_H, SPLITTER_W, SETTINGS_W
354
- @dimina-kit/devtools/projects listProjects, addProject, removeProject,
355
- getCompileConfig, saveCompileConfig,
356
- updateLastOpened
357
- @dimina-kit/devtools/paths rendererDir, defaultPreloadPath,
358
- getRendererDir, getPreloadDir, getRendererHtml
359
411
  @dimina-kit/devtools/ipc-simulator registerSimulatorIpc(ctx)
360
412
  @dimina-kit/devtools/ipc-panels registerPanelsIpc(ctx)
361
413
  @dimina-kit/devtools/ipc-toolbar registerToolbarIpc(ctx)
@@ -363,17 +415,17 @@ launch({ preloadPath: '/absolute/path/to/my-preload.js' })
363
415
  @dimina-kit/devtools/ipc-settings registerSettingsIpc(ctx)
364
416
  @dimina-kit/devtools/ipc-projects registerProjectsIpc(ctx)
365
417
  @dimina-kit/devtools/ipc-session registerSessionIpc(ctx), sendStatus(ctx, status, msg)
366
- @dimina-kit/devtools/bootstrap suppressEpipe(), setupCdpPort()
367
418
  @dimina-kit/devtools/workbench-settings loadWorkbenchSettings(), saveWorkbenchSettings(), applyTheme()
368
- @dimina-kit/devtools/preload installConsoleInstrumentation,
369
- installStorageInstrumentation,
370
- installAppDataInstrumentation, sendAllAppData,
371
- installWxmlInstrumentation, sendWxmlTree,
372
- setupWxmlObserver, installSimulatorBridge,
373
- setupApiCompatHook
374
- @dimina-kit/devtools/simulator-dir simulatorDir
375
419
  ```
376
420
 
421
+ > 注:以下子路径在过去版本曾被导出,已收敛到根 barrel 或内部实现,请改从 `@dimina-kit/devtools` 根入口导入(`createViewManager`、`registerAppIpc`、`simulatorDir`、`Project` 等类型):
422
+ >
423
+ > - `@dimina-kit/devtools/view-manager` → `import { createViewManager, type ViewManager } from '@dimina-kit/devtools'`
424
+ > - `@dimina-kit/devtools/ipc-app` → `import { registerAppIpc } from '@dimina-kit/devtools'`
425
+ > - `@dimina-kit/devtools/projects` → 通过 `ctx.workspace.listProjects()` 等服务方法访问;`Project` 类型从 `@dimina-kit/devtools` 根入口导入
426
+ > - `@dimina-kit/devtools/layout` → `import { setHeaderHeight } from '@dimina-kit/devtools'`(其余布局细节回归内部实现)
427
+ > - `@dimina-kit/devtools/simulator-dir` → `import { simulatorDir } from '@dimina-kit/devtools/paths'`
428
+
377
429
  ---
378
430
 
379
431
  ## WorkbenchContext
@@ -382,7 +434,6 @@ launch({ preloadPath: '/absolute/path/to/my-preload.js' })
382
434
 
383
435
  ```typescript
384
436
  interface WorkbenchContext {
385
- mainWindow: BrowserWindow
386
437
  adapter: CompilationAdapter
387
438
  preloadPath: string
388
439
  rendererDir: string
@@ -391,12 +442,21 @@ interface WorkbenchContext {
391
442
  appName: string
392
443
  toolbarActions?: () => Promise<ToolbarAction[]> | ToolbarAction[]
393
444
  brandingProvider?: () => Promise<{ appName: string }> | { appName: string }
394
- workbenchSettingsWindow: BrowserWindow | null
395
445
 
396
446
  // ── Services(运行时状态封装在内部,通过方法访问)──
447
+ windows: WindowService // ctx.windows.mainWindow, ctx.windows.settingsWindow, ...
397
448
  views: ViewManager // ctx.views.repositionAll(), ctx.views.getSimulatorWebContentsId(), ...
398
449
  notify: RendererNotifier // ctx.notify.projectStatus(), ctx.notify.windowNavigateBack(), ...
399
450
  workspace: WorkspaceService // ctx.workspace.getProjectPath(), ctx.workspace.hasActiveSession(), ...
451
+
452
+ // ── Internal lifecycle / security (host 不直接消费) ──
453
+ registry: DisposableRegistry // 框架在创建时统一聚合 dispose
454
+ senderPolicy: SenderPolicy // IpcRegistry 自动校验 sender 白名单
455
+
456
+ /** @deprecated 用 ctx.windows.mainWindow */
457
+ mainWindow: BrowserWindow
458
+ /** @deprecated 用 ctx.windows.settingsWindow / setSettingsWindow */
459
+ workbenchSettingsWindow: BrowserWindow | null
400
460
  }
401
461
  ```
402
462
 
@@ -409,6 +469,8 @@ interface WorkbenchContext {
409
469
  | `ctx.simulatorWebContentsId` | `ctx.views.getSimulatorWebContentsId()` |
410
470
  | `ctx.devToolsViewAdded` | `ctx.views.isSimulatorAdded()` |
411
471
  | `ctx.lastSimWidth` | `ctx.views.getLastSimWidth()` |
472
+ | `ctx.mainWindow` | `ctx.windows.mainWindow` |
473
+ | `ctx.workbenchSettingsWindow`| `ctx.windows.settingsWindow` |
412
474
 
413
475
  ---
414
476
 
@@ -545,18 +607,18 @@ pnpm test # 运行测试
545
607
 
546
608
  ## 安全说明
547
609
 
548
- - 本工具仅用于本地开发调试,不应部署到生产环境
549
- - 为方便开发调试,BrowserWindow 启用了 `nodeIntegration` 并禁用了 `contextIsolation`,这在生产环境中存在安全风险
550
- - 仅在受信任的本地开发环境中使用本工具
551
- - 不要在 devtools 窗口中加载不受信任的远程内容
552
- - 如需在非本地环境使用,请自行评估并加固安全配置
610
+ 本工具仅用于本地开发调试。当前的安全配置:
553
611
 
554
- ### MCP Server 风险
612
+ - **Workbench 窗口**(main / settings / popover overlay)均启用 `contextIsolation` 并禁用 `nodeIntegration`;renderer 通过 main preload 经 `contextBridge` 拿到 `window.devtools.ipc`,每个 IPC handler 经 sender whitelist 校验调用方
613
+ - **Simulator 加载的小程序代码** 运行在独立 `<webview>` 中(`partition='persist:simulator'`、`nodeIntegration=false`),preload 由 `session.registerPreloadScript` 在 session 层固定,渲染进程无法替换
614
+ - **renderer 入口** 启用了保守 CSP;`will-navigate` / `setWindowOpenHandler` 屏蔽外部跳转
615
+
616
+ 仍然不要在 devtools 窗口中加载不受信任的远程内容;本工具不打算在非本地环境部署。
555
617
 
556
- 内置的 MCP server 默认关闭,由 `startMcpServer` 入口显式启动,或通过 workbench 设置中的 `mcp.enabled` 开启。开启后会在本机监听 SSE 端点,向任何可以连接到该端点的 MCP 客户端暴露一组工具,能力包括:在页面上下文执行任意 JS(`evaluate`)、读取 DOM 结构、截屏、拉取网络日志、触发导航等。
618
+ ### MCP Server 风险
557
619
 
558
- 由于 workbench 主窗口出于调试需要启用了 `nodeIntegration` 并关闭了 `contextIsolation`,通过 MCP 客户端执行的 `evaluate` 实际拥有 Node API 访问权限。换言之,开启 MCP 等同于允许已连接的本机 MCP 客户端在 DevTools 进程中执行 Node 代码,读写本机文件和启动子进程均在其能力范围内。
620
+ 内置的 MCP server 默认关闭,由 `startMcpServer` 入口显式启动,或通过 workbench 设置中的 `mcp.enabled` 开启。开启后会在本机监听 SSE 端点,向任何可以连接到该端点的 MCP 客户端暴露一组工具,能力包括:读取 DOM 结构、截屏、拉取网络日志、触发导航等。
559
621
 
560
- 因此仅在连接到可信的本机 MCP 客户端或被信任的 AI 工具时启用该功能;不要在公共 WiFi、共享机器或存在远程 SSH 端口转发的场景下开启,以免监听端口被非预期的客户端访问。
622
+ `workbench_evaluate` 已从 MCP 工具集中移除,workbench 只暴露只读诊断工具(截屏、console 日志、DOM、网络日志等)。`simulator_evaluate` 仍然保留,但 simulator 使用 `<webview>`、`nodeIntegration=false`,其 `evaluate` 仅在页面 JS 上下文执行,不具备 Node API 访问能力。
561
623
 
562
- 作为收窄 RCE 面的默认策略,`workbench_evaluate` 已从 MCP 工具集中移除,workbench 只暴露只读诊断工具(截屏、console 日志、DOM、网络日志等)。`simulator_evaluate` 仍然保留,因为 simulator 使用 `<webview>`,默认 `nodeIntegration=false`,其 `evaluate` 仅在页面 JS 上下文执行,不具备 Node API 访问能力。
624
+ 仅在连接到可信的本机 MCP 客户端或被信任的 AI 工具时启用该功能;不要在公共 WiFi、共享机器或存在远程 SSH 端口转发的场景下开启,以免监听端口被非预期的客户端访问。
@@ -7,6 +7,10 @@ export type { WorkbenchContext, CreateContextOptions } from './services/workbenc
7
7
  export { createMainWindow } from './windows/main-window/index.js';
8
8
  export { createViewManager } from './services/views/view-manager.js';
9
9
  export type { ViewManager } from './services/views/view-manager.js';
10
+ export type { WorkspaceService } from './services/workspace/workspace-service.js';
11
+ export type { Project, ProjectPages, ProjectSettings } from './services/projects/project-repository.js';
12
+ export { registerSimulatorApi } from './simulator-apis.js';
13
+ export type { SimulatorApiHandler } from './simulator-apis.js';
10
14
  export { registerAppIpc } from './ipc/app.js';
11
15
  export { registerSimulatorIpc } from './ipc/simulator.js';
12
16
  export { registerPanelsIpc } from './ipc/panels.js';
@@ -15,8 +19,8 @@ export { registerSettingsIpc } from './ipc/settings.js';
15
19
  export { registerProjectsIpc } from './ipc/projects.js';
16
20
  export { registerSessionIpc } from './ipc/session.js';
17
21
  export { registerToolbarIpc } from './ipc/toolbar.js';
18
- export { rendererDir, defaultPreloadPath, getRendererDir, getPreloadDir, getRendererHtml } from './utils/paths.js';
19
- export { simulatorDir } from './services/simulator/dir.js';
22
+ export { rendererDir, defaultPreloadPath, simulatorDir, getRendererDir, getPreloadDir, getRendererHtml, } from './utils/paths.js';
23
+ export { setHeaderHeight } from './services/layout/index.js';
20
24
  export { UpdateManager, createGitHubReleaseChecker } from './services/update/index.js';
21
25
  export type { UpdateManagerOptions, GitHubReleaseCheckerOptions, PickAssetContext, VersionScheme, } from './services/update/index.js';
22
26
  export type { WorkbenchConfig, WorkbenchAppConfig, CompilationAdapter, ProjectSession, ToolbarAction, BuiltinPanelId, BuiltinModuleId, UpdateChecker, UpdateInfo, } from '../shared/types.js';
package/dist/main/api.js CHANGED
@@ -7,6 +7,8 @@ export { suppressEpipe, setupCdpPort } from './app/bootstrap.js';
7
7
  export { createWorkbenchContext, hasBuiltinPanel, getDefaultTab } from './services/workbench-context.js';
8
8
  export { createMainWindow } from './windows/main-window/index.js';
9
9
  export { createViewManager } from './services/views/view-manager.js';
10
+ // ── Simulator extension surface ──────────────────────────────────────────
11
+ export { registerSimulatorApi } from './simulator-apis.js';
10
12
  // ── IPC module registration ──────────────────────────────────────────────
11
13
  export { registerAppIpc } from './ipc/app.js';
12
14
  export { registerSimulatorIpc } from './ipc/simulator.js';
@@ -17,8 +19,9 @@ export { registerProjectsIpc } from './ipc/projects.js';
17
19
  export { registerSessionIpc } from './ipc/session.js';
18
20
  export { registerToolbarIpc } from './ipc/toolbar.js';
19
21
  // ── Paths ────────────────────────────────────────────────────────────────
20
- export { rendererDir, defaultPreloadPath, getRendererDir, getPreloadDir, getRendererHtml } from './utils/paths.js';
21
- export { simulatorDir } from './services/simulator/dir.js';
22
+ export { rendererDir, defaultPreloadPath, simulatorDir, getRendererDir, getPreloadDir, getRendererHtml, } from './utils/paths.js';
23
+ // ── Layout ───────────────────────────────────────────────────────────────
24
+ export { setHeaderHeight } from './services/layout/index.js';
22
25
  // ── Update checker ───────────────────────────────────────────────────────
23
26
  export { UpdateManager, createGitHubReleaseChecker } from './services/update/index.js';
24
27
  //# sourceMappingURL=api.js.map
@@ -12,6 +12,6 @@ export interface WorkbenchAppInstance {
12
12
  }
13
13
  export declare function createWorkbenchApp(config?: WorkbenchAppConfig): {
14
14
  setup: () => Promise<WorkbenchAppInstance>;
15
- start: () => void;
15
+ start: () => Promise<void>;
16
16
  };
17
17
  //# sourceMappingURL=app.d.ts.map
@@ -8,11 +8,12 @@ import { createMainWindow, wireMainWindowEvents } from '../windows/main-window/i
8
8
  import { createWorkbenchContext } from '../services/workbench-context.js';
9
9
  import { loadWorkbenchSettings, applyTheme } from '../services/settings/index.js';
10
10
  import { installAppMenu } from '../menu/index.js';
11
- import { registerAppIpc, registerSimulatorIpc, registerPanelsIpc, registerPopoverIpc, registerProjectsIpc, registerSessionIpc, registerSettingsIpc, registerToolbarIpc, } from '../ipc/index.js';
11
+ import { registerAppIpc, popoverModule, projectsModule, sessionModule, settingsModule, simulatorModule, } from '../ipc/index.js';
12
12
  import { startAutomationServer } from '../services/automation/index.js';
13
13
  import { startMcpServer } from '../services/mcp/index.js';
14
14
  import { setupSimulatorStorage } from '../services/simulator-storage/index.js';
15
15
  import { UpdateManager } from '../services/update/index.js';
16
+ import { toDisposable } from '../utils/disposable.js';
16
17
  const DEFAULT_MODULES = {
17
18
  projects: true,
18
19
  session: true,
@@ -20,16 +21,12 @@ const DEFAULT_MODULES = {
20
21
  popover: true,
21
22
  settings: true,
22
23
  };
23
- const MODULE_REGISTRARS = {
24
- projects: registerProjectsIpc,
25
- session: registerSessionIpc,
26
- simulator: (ctx) => {
27
- registerSimulatorIpc(ctx);
28
- registerPanelsIpc(ctx);
29
- registerToolbarIpc(ctx);
30
- },
31
- popover: registerPopoverIpc,
32
- settings: registerSettingsIpc,
24
+ const BUILTIN_MODULES = {
25
+ projects: projectsModule,
26
+ session: sessionModule,
27
+ simulator: simulatorModule,
28
+ popover: popoverModule,
29
+ settings: settingsModule,
33
30
  };
34
31
  /** Parse --auto, --auto-port, --project from process.argv. */
35
32
  function parseAutoArgs() {
@@ -58,9 +55,11 @@ function resolveModules(config) {
58
55
  ...config.modules,
59
56
  };
60
57
  }
61
- async function disposeContext(ctx, updateManager) {
62
- updateManager?.dispose();
58
+ async function disposeContext(ctx) {
63
59
  await ctx.workspace.closeProject();
60
+ await ctx.registry.dispose().catch((err) => {
61
+ console.warn('[workbench] dispose registry encountered errors:', err);
62
+ });
64
63
  }
65
64
  function createConfiguredMainWindow(config, rendererDir) {
66
65
  const mainWindow = createMainWindow({
@@ -70,6 +69,11 @@ function createConfiguredMainWindow(config, rendererDir) {
70
69
  height: config.window?.height,
71
70
  minWidth: config.window?.minWidth,
72
71
  minHeight: config.window?.minHeight,
72
+ // P1: pass the same preload path the workbench context stores on
73
+ // ctx.preloadPath so hosts that ship a custom simulator preload are the
74
+ // ones registered on the persist:simulator session — otherwise the
75
+ // backstop wouldn't be the host's script.
76
+ simulatorPreloadPath: config.preloadPath ?? defaultPreloadPath,
73
77
  });
74
78
  // Set window/taskbar icon if provided (Linux/Windows; macOS uses app bundle icon)
75
79
  if (config.icon) {
@@ -96,7 +100,7 @@ function registerBuiltinModules(config, context) {
96
100
  const modules = resolveModules(config);
97
101
  Object.keys(modules).forEach((moduleId) => {
98
102
  if (modules[moduleId])
99
- MODULE_REGISTRARS[moduleId](context);
103
+ context.registry.add(BUILTIN_MODULES[moduleId].setup(context));
100
104
  });
101
105
  }
102
106
  function installMenu(config, mainWindow, context) {
@@ -114,6 +118,7 @@ async function setupAutomation(instance) {
114
118
  if (autoArgs.auto) {
115
119
  const server = await startAutomationServer(instance.context, autoArgs.autoPort);
116
120
  instance.automationServer = server;
121
+ instance.context.registry.add(() => server.close());
117
122
  // Stable, parseable line for e2e harnesses that scrape stdout.
118
123
  console.log(`[automation] listening on ws://127.0.0.1:${server.port}`);
119
124
  }
@@ -121,42 +126,56 @@ async function setupAutomation(instance) {
121
126
  function setupMcp() {
122
127
  const settings = loadWorkbenchSettings();
123
128
  if (!settings.mcp.enabled)
124
- return;
129
+ return null;
125
130
  const cdpPortSwitch = app.commandLine.getSwitchValue('remote-debugging-port');
126
131
  const cdpPort = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
127
- startMcpServer(cdpPort, settings.mcp.port);
132
+ return startMcpServer(cdpPort, settings.mcp.port);
128
133
  }
129
134
  function wireAppWindowEvents(config, instance) {
130
135
  const { mainWindow, context } = instance;
131
- wireMainWindowEvents(mainWindow, {
136
+ return wireMainWindowEvents(mainWindow, {
132
137
  context,
133
138
  onResize: () => context.views.repositionAll(),
134
139
  onClose: async (e) => {
135
140
  if (!context.workspace.hasActiveSession())
136
141
  return;
142
+ // Close button while a project session is open: stay in the workbench
143
+ // and surface the project list. Tear down only the session — do NOT
144
+ // dispose `context.registry`, which owns every IPC handler (projects,
145
+ // dialog, settings…). Disposing it would leave the renderer alive but
146
+ // unable to invoke anything, so subsequent clicks on Import/etc. would
147
+ // raise `No handler registered for ...`.
137
148
  e.preventDefault();
138
149
  if (config.onBeforeClose) {
139
150
  await config.onBeforeClose(instance);
140
151
  }
141
- await disposeContext(context);
152
+ await context.workspace.closeProject();
142
153
  context.notify.windowNavigateBack();
143
154
  },
144
155
  });
145
156
  }
146
157
  function enableDevRendererAutoReload(rendererDir) {
147
158
  // Auto-reload renderer windows when dist files change during development
148
- if (!app.isPackaged) {
149
- let reloadTimer = null;
150
- fs.watch(rendererDir, { recursive: true }, () => {
151
- if (reloadTimer)
152
- clearTimeout(reloadTimer);
153
- reloadTimer = setTimeout(() => {
154
- for (const win of BrowserWindow.getAllWindows()) {
155
- win.webContents.reload();
156
- }
157
- }, 300);
158
- });
159
+ if (app.isPackaged) {
160
+ return toDisposable(() => { });
159
161
  }
162
+ let reloadTimer = null;
163
+ const watcher = fs.watch(rendererDir, { recursive: true }, () => {
164
+ if (reloadTimer)
165
+ clearTimeout(reloadTimer);
166
+ reloadTimer = setTimeout(() => {
167
+ for (const win of BrowserWindow.getAllWindows()) {
168
+ win.webContents.reload();
169
+ }
170
+ }, 300);
171
+ });
172
+ return toDisposable(() => {
173
+ if (reloadTimer) {
174
+ clearTimeout(reloadTimer);
175
+ reloadTimer = null;
176
+ }
177
+ watcher.close();
178
+ });
160
179
  }
161
180
  export function createWorkbenchApp(config = {}) {
162
181
  let setupPromise = null;
@@ -170,13 +189,13 @@ export function createWorkbenchApp(config = {}) {
170
189
  const rendererDir = config.rendererDir ?? defaultRendererDir;
171
190
  const mainWindow = createConfiguredMainWindow(config, rendererDir);
172
191
  const context = createContext(config, mainWindow, rendererDir);
173
- registerAppIpc(context);
192
+ context.registry.add(registerAppIpc(context));
174
193
  registerBuiltinModules(config, context);
175
194
  installMenu(config, mainWindow, context);
176
195
  const instance = {
177
196
  mainWindow,
178
197
  context,
179
- dispose: () => disposeContext(context, instance.updateManager),
198
+ dispose: () => disposeContext(context),
180
199
  };
181
200
  if (config.onSetup) {
182
201
  await config.onSetup(instance);
@@ -189,22 +208,38 @@ export function createWorkbenchApp(config = {}) {
189
208
  initialDelay: config.updateOptions?.initialDelay,
190
209
  getCurrentVersion: config.updateOptions?.getCurrentVersion,
191
210
  });
211
+ context.registry.add(() => instance.updateManager.dispose());
192
212
  }
193
213
  await setupAutomation(instance);
194
- setupMcp();
195
- setupSimulatorStorage(mainWindow.webContents);
196
- wireAppWindowEvents(config, instance);
197
- enableDevRendererAutoReload(rendererDir);
214
+ const mcp = setupMcp();
215
+ if (mcp)
216
+ context.registry.add(mcp);
217
+ context.registry.add(setupSimulatorStorage(mainWindow.webContents, {
218
+ senderPolicy: context.senderPolicy,
219
+ // Per-project filter for the simulator-storage panel: the simulator
220
+ // uses a fixed `persist:simulator` partition + a fixed simulator.html
221
+ // origin, so localStorage is shared across every project that has
222
+ // ever opened. The dimina runtime isolates writes with `${appId}_`
223
+ // prefixes; this callback lets the storage panel filter the CDP
224
+ // snapshot/event stream to the active appId.
225
+ getActiveAppId: () => {
226
+ const session = context.workspace.getSession();
227
+ const appInfo = session?.appInfo;
228
+ return appInfo?.appId ?? null;
229
+ },
230
+ }));
231
+ context.registry.add(wireAppWindowEvents(config, instance));
232
+ context.registry.add(enableDevRendererAutoReload(rendererDir));
198
233
  return instance;
199
234
  });
200
235
  return setupPromise;
201
236
  }
202
237
  function start() {
203
- void setup();
204
238
  if (!appEventsRegistered) {
205
239
  appEventsRegistered = true;
206
240
  registerAppLifecycle();
207
241
  }
242
+ return setup().then(() => undefined);
208
243
  }
209
244
  return {
210
245
  setup,
@@ -4,7 +4,7 @@ import type { WorkbenchContext } from '../services/workbench-context.js';
4
4
  * Convenience launcher for the default full-featured app.
5
5
  * Use createWorkbenchApp() when you want to customize modules or window setup.
6
6
  */
7
- export declare function launch(config?: WorkbenchConfig): void;
7
+ export declare function launch(config?: WorkbenchConfig): Promise<void>;
8
8
  export declare function buildDefaultMenu(ctx: WorkbenchContext): void;
9
- export declare function openSettingsWindow(ctx: Pick<WorkbenchContext, 'workbenchSettingsWindow' | 'mainWindow' | 'rendererDir' | 'notify'>): Promise<void>;
9
+ export declare function openSettingsWindow(ctx: Pick<WorkbenchContext, 'workbenchSettingsWindow' | 'mainWindow' | 'rendererDir' | 'notify' | 'windows'>): Promise<void>;
10
10
  //# sourceMappingURL=launch.d.ts.map
@@ -7,16 +7,19 @@ import { loadWorkbenchSettings } from '../services/settings/index.js';
7
7
  * Use createWorkbenchApp() when you want to customize modules or window setup.
8
8
  */
9
9
  export function launch(config = {}) {
10
- createWorkbenchApp(config).start();
10
+ return createWorkbenchApp(config).start();
11
11
  }
12
12
  export function buildDefaultMenu(ctx) {
13
13
  installAppMenu(ctx);
14
14
  }
15
15
  export async function openSettingsWindow(ctx) {
16
16
  if (!ctx.workbenchSettingsWindow || ctx.workbenchSettingsWindow.isDestroyed()) {
17
- ctx.workbenchSettingsWindow = await createSettingsWindow(ctx.mainWindow, ctx.rendererDir);
18
- wireSettingsWindowEvents(ctx.workbenchSettingsWindow, () => {
17
+ const win = await createSettingsWindow(ctx.mainWindow, ctx.rendererDir);
18
+ ctx.workbenchSettingsWindow = win;
19
+ ctx.windows.setSettingsWindow(win);
20
+ wireSettingsWindowEvents(win, () => {
19
21
  ctx.workbenchSettingsWindow = null;
22
+ ctx.windows.setSettingsWindow(null);
20
23
  });
21
24
  }
22
25
  ctx.workbenchSettingsWindow.show();