@dimina-kit/devtools 0.4.0-dev.20260831134112 → 0.4.0-dev.20260907055341

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 (275) hide show
  1. package/README.md +52 -729
  2. package/dist/main/app/app.d.ts +27 -7
  3. package/dist/main/app/app.js +285 -681
  4. package/dist/main/app/editor-view.d.ts +21 -0
  5. package/dist/main/app/editor-view.js +71 -0
  6. package/dist/main/app/global-mirrors.d.ts +23 -0
  7. package/dist/main/app/global-mirrors.js +37 -0
  8. package/dist/main/app/host-sidebar-default.d.ts +19 -0
  9. package/dist/main/app/host-sidebar-default.js +44 -0
  10. package/dist/main/app/menu-setup.d.ts +13 -0
  11. package/dist/main/app/menu-setup.js +49 -0
  12. package/dist/main/app/open-project-ipc.d.ts +13 -0
  13. package/dist/main/app/open-project-ipc.js +21 -0
  14. package/dist/main/app/project-list-surface.d.ts +31 -0
  15. package/dist/main/app/project-list-surface.js +20 -0
  16. package/dist/main/app/project-window.d.ts +36 -0
  17. package/dist/main/app/project-window.js +93 -0
  18. package/dist/main/app/servers.d.ts +17 -0
  19. package/dist/main/app/servers.js +60 -0
  20. package/dist/main/app/ui-extension-targets.d.ts +42 -0
  21. package/dist/main/app/ui-extension-targets.js +101 -0
  22. package/dist/main/app/window-close-reveal.harness.d.ts +34 -0
  23. package/dist/main/app/window-close-reveal.harness.js +65 -0
  24. package/dist/main/app/window-events.d.ts +15 -0
  25. package/dist/main/app/window-events.js +45 -0
  26. package/dist/main/app/window-runtime-services.d.ts +41 -0
  27. package/dist/main/app/window-runtime-services.js +178 -0
  28. package/dist/main/app/workbench-modules.d.ts +24 -0
  29. package/dist/main/app/workbench-modules.js +51 -0
  30. package/dist/main/app/workbench-window.d.ts +85 -0
  31. package/dist/main/app/workbench-window.js +310 -0
  32. package/dist/main/index.bundle.js +10669 -9096
  33. package/dist/main/ipc/app.d.ts +4 -1
  34. package/dist/main/ipc/app.js +4 -3
  35. package/dist/main/ipc/device-picker.d.ts +16 -0
  36. package/dist/main/ipc/device-picker.js +27 -0
  37. package/dist/main/ipc/index.d.ts +1 -0
  38. package/dist/main/ipc/index.js +1 -0
  39. package/dist/main/ipc/internal-devtools.d.ts +2 -1
  40. package/dist/main/ipc/internal-devtools.js +4 -3
  41. package/dist/main/ipc/popover.d.ts +4 -1
  42. package/dist/main/ipc/popover.js +25 -9
  43. package/dist/main/ipc/project-create.d.ts +2 -1
  44. package/dist/main/ipc/project-create.js +6 -5
  45. package/dist/main/ipc/project-fs.d.ts +3 -1
  46. package/dist/main/ipc/project-fs.js +17 -17
  47. package/dist/main/ipc/projects.d.ts +3 -3
  48. package/dist/main/ipc/projects.js +13 -12
  49. package/dist/main/ipc/session.d.ts +4 -1
  50. package/dist/main/ipc/session.js +16 -11
  51. package/dist/main/ipc/settings.d.ts +4 -1
  52. package/dist/main/ipc/settings.js +12 -19
  53. package/dist/main/ipc/simulator-module.d.ts +5 -0
  54. package/dist/main/ipc/simulator-module.js +9 -5
  55. package/dist/main/ipc/simulator.d.ts +4 -1
  56. package/dist/main/ipc/simulator.js +12 -18
  57. package/dist/main/ipc/tooltip.d.ts +2 -1
  58. package/dist/main/ipc/tooltip.js +8 -7
  59. package/dist/main/ipc/views.d.ts +4 -1
  60. package/dist/main/ipc/views.js +20 -9
  61. package/dist/main/runtime/devtools-backend-before-quit.harness.d.ts +2 -20
  62. package/dist/main/runtime/devtools-backend-before-quit.harness.js +14 -283
  63. package/dist/main/runtime/devtools-backend.js +14 -3
  64. package/dist/main/runtime/devtools-runtime-mock.harness.d.ts +21 -0
  65. package/dist/main/runtime/devtools-runtime-mock.harness.js +313 -0
  66. package/dist/main/services/app-services.d.ts +62 -0
  67. package/dist/main/services/app-services.js +64 -0
  68. package/dist/main/services/automation/connection-target.d.ts +36 -0
  69. package/dist/main/services/automation/connection-target.js +29 -0
  70. package/dist/main/services/automation/console-bridge.d.ts +28 -0
  71. package/dist/main/services/automation/console-bridge.js +91 -0
  72. package/dist/main/services/automation/handlers/app.js +4 -4
  73. package/dist/main/services/automation/handlers/element.js +55 -21
  74. package/dist/main/services/automation/index.d.ts +12 -1
  75. package/dist/main/services/automation/index.js +54 -133
  76. package/dist/main/services/default-adapter.js +1 -1
  77. package/dist/main/services/fs-bridge-protocol.d.ts +61 -0
  78. package/dist/main/services/fs-bridge-protocol.js +34 -0
  79. package/dist/main/services/fs-watch-sse.d.ts +8 -1
  80. package/dist/main/services/fs-watch-sse.js +19 -5
  81. package/dist/main/services/mcp/connection-owner.d.ts +38 -0
  82. package/dist/main/services/mcp/connection-owner.js +79 -0
  83. package/dist/main/services/mcp/index.d.ts +3 -1
  84. package/dist/main/services/mcp/index.js +4 -3
  85. package/dist/main/services/mcp/opened-project.d.ts +88 -0
  86. package/dist/main/services/mcp/opened-project.js +60 -0
  87. package/dist/main/services/mcp/target-manager.d.ts +71 -23
  88. package/dist/main/services/mcp/target-manager.js +269 -142
  89. package/dist/main/services/mcp/target-selection.d.ts +47 -0
  90. package/dist/main/services/mcp/target-selection.js +71 -0
  91. package/dist/main/services/mcp/tools/context-tools.js +20 -5
  92. package/dist/main/services/mcp/tools/project-tools.d.ts +29 -6
  93. package/dist/main/services/mcp/tools/project-tools.js +37 -25
  94. package/dist/main/services/module.d.ts +13 -5
  95. package/dist/main/services/notifications/renderer-notifier.d.ts +28 -4
  96. package/dist/main/services/notifications/renderer-notifier.js +15 -3
  97. package/dist/main/services/projects/local-provider.js +2 -0
  98. package/dist/main/services/projects/project-repository.d.ts +31 -1
  99. package/dist/main/services/projects/project-repository.js +144 -20
  100. package/dist/main/services/projects/types.d.ts +31 -8
  101. package/dist/main/services/safe-area/index.d.ts +19 -5
  102. package/dist/main/services/safe-area/index.js +38 -13
  103. package/dist/main/services/simulator/ui-extensions.d.ts +8 -0
  104. package/dist/main/services/simulator/ui-extensions.js +16 -6
  105. package/dist/main/services/simulator-storage/index.d.ts +12 -1
  106. package/dist/main/services/simulator-storage/index.js +22 -7
  107. package/dist/main/services/simulator-storage/window-scope.d.ts +20 -0
  108. package/dist/main/services/simulator-storage/window-scope.js +20 -0
  109. package/dist/main/services/views/host-slot-port-channel.d.ts +20 -3
  110. package/dist/main/services/views/host-slot-port-channel.js +33 -5
  111. package/dist/main/services/views/managed-web-contents-view.js +21 -1
  112. package/dist/main/services/views/native-simulator-view.js +13 -16
  113. package/dist/main/services/views/overlay-panels-view.d.ts +13 -0
  114. package/dist/main/services/views/overlay-panels-view.js +31 -0
  115. package/dist/main/services/views/view-manager.d.ts +1 -1
  116. package/dist/main/services/views/view-manager.js +3 -0
  117. package/dist/main/services/window-contexts/context-router.d.ts +36 -0
  118. package/dist/main/services/window-contexts/context-router.js +55 -0
  119. package/dist/main/services/workbench-coi-host.d.ts +52 -0
  120. package/dist/main/services/workbench-coi-host.js +227 -0
  121. package/dist/main/services/workbench-coi-server.js +75 -133
  122. package/dist/main/services/workbench-coi-shutdown.d.ts +40 -0
  123. package/dist/main/services/workbench-coi-shutdown.js +69 -0
  124. package/dist/main/services/workbench-coi-static.d.ts +23 -0
  125. package/dist/main/services/workbench-coi-static.js +87 -0
  126. package/dist/main/services/workbench-context.d.ts +27 -3
  127. package/dist/main/services/workbench-context.js +10 -5
  128. package/dist/main/services/workspace/compile-mode-adapter.d.ts +18 -0
  129. package/dist/main/services/workspace/compile-mode-adapter.js +57 -0
  130. package/dist/main/services/workspace/compile-mode-store.d.ts +25 -0
  131. package/dist/main/services/workspace/compile-mode-store.js +71 -0
  132. package/dist/main/services/workspace/workspace-compile-modes.d.ts +59 -0
  133. package/dist/main/services/workspace/workspace-compile-modes.js +93 -0
  134. package/dist/main/services/workspace/workspace-referer.d.ts +17 -0
  135. package/dist/main/services/workspace/workspace-referer.js +26 -0
  136. package/dist/main/services/workspace/workspace-service.d.ts +4 -0
  137. package/dist/main/services/workspace/workspace-service.js +51 -84
  138. package/dist/main/services/workspace/workspace-thumbnails.d.ts +22 -0
  139. package/dist/main/services/workspace/workspace-thumbnails.js +67 -0
  140. package/dist/main/utils/ipc-context-source.d.ts +40 -0
  141. package/dist/main/utils/ipc-context-source.js +23 -0
  142. package/dist/main/utils/ipc-registry.d.ts +42 -36
  143. package/dist/main/utils/ipc-registry.js +117 -50
  144. package/dist/main/utils/sender-policy.d.ts +9 -0
  145. package/dist/main/utils/sender-policy.js +26 -3
  146. package/dist/main/utils/theme.d.ts +3 -2
  147. package/dist/main/utils/theme.js +3 -2
  148. package/dist/main/windows/main-window/create.d.ts +16 -0
  149. package/dist/main/windows/main-window/create.js +19 -11
  150. package/dist/main/windows/main-window/events.d.ts +1 -1
  151. package/dist/main/windows/main-window/events.js +26 -13
  152. package/dist/main/windows/main-window/index.d.ts +1 -1
  153. package/dist/main/windows/main-window/index.js +1 -1
  154. package/dist/native-host/common/common.js +94 -73
  155. package/dist/native-host/render/render.js +4079 -3171
  156. package/dist/native-host/service/service.js +2 -2
  157. package/dist/preload/runtime/native-host.d.ts +4 -0
  158. package/dist/preload/runtime/temp-files.js +9 -2
  159. package/dist/preload/runtime/wait-for-slot-root.d.ts +7 -4
  160. package/dist/preload/runtime/wait-for-slot-root.js +36 -8
  161. package/dist/preload/windows/host-dialog-runtime.cjs +10 -3
  162. package/dist/preload/windows/host-dialog-runtime.cjs.map +2 -2
  163. package/dist/preload/windows/host-sidebar-runtime.cjs +10 -3
  164. package/dist/preload/windows/host-sidebar-runtime.cjs.map +2 -2
  165. package/dist/preload/windows/host-toolbar-runtime.cjs +10 -3
  166. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  167. package/dist/preload/windows/main.cjs +181 -4
  168. package/dist/preload/windows/main.cjs.map +4 -4
  169. package/dist/preload/windows/simulator.cjs +10 -0
  170. package/dist/preload/windows/simulator.cjs.map +2 -2
  171. package/dist/preload/windows/simulator.js +10 -0
  172. package/dist/renderer/assets/button-CB2MWjHY.js +2 -0
  173. package/dist/renderer/assets/constants-CkoGPQTS.js +2 -0
  174. package/dist/renderer/assets/constants-zQZ2-y21.js +2 -0
  175. package/dist/renderer/assets/{createLucideIcon-Bqi8hpdi.js → createLucideIcon-cZ9FlZZw.js} +2 -2
  176. package/dist/renderer/assets/devicePicker-H2eIO4e1.js +2 -0
  177. package/dist/renderer/assets/dialog-pHzEA8Vx.js +46 -0
  178. package/dist/renderer/assets/dist-B5HADirS.js +2 -0
  179. package/dist/renderer/assets/{dist-DKWdE1TK.js → dist-DIFlRMKe.js} +2 -2
  180. package/dist/renderer/assets/hostSidebarDefault-DRWh6OCt.js +2 -0
  181. package/dist/renderer/assets/index-CEL_XFge.js +2 -0
  182. package/dist/renderer/assets/{input-Ct3alvkq.js → input-CAObw95L.js} +2 -2
  183. package/dist/renderer/assets/ipc-channels-overlays-gIPUFnqc.js +2 -0
  184. package/dist/renderer/assets/jsx-runtime-DICEQH_0.css +1 -0
  185. package/dist/renderer/assets/{jsx-runtime-DGLHmmOq.js → jsx-runtime-DIw5wnYl.js} +1 -1
  186. package/dist/renderer/assets/plus-BQ9u5EbL.js +2 -0
  187. package/dist/renderer/assets/popover-Cw7G2Ctp.js +2 -0
  188. package/dist/renderer/assets/presets-DMZOb9ay.js +2 -0
  189. package/dist/renderer/assets/project-api-YTOr_Yvo.js +2 -0
  190. package/dist/renderer/assets/{projectCreateDialog-Bbjz75bP.js → projectCreateDialog-BmLqiYxn.js} +2 -2
  191. package/dist/renderer/assets/search-BMvuYF48.js +2 -0
  192. package/dist/renderer/assets/select-nFP95TxM.js +2 -0
  193. package/dist/renderer/assets/settings-CnpVfSWt.js +2 -0
  194. package/dist/renderer/assets/settings-api-Ccsf-Nsj.js +2 -0
  195. package/dist/renderer/assets/settings-tab-bar-CAzrv32m.js +2 -0
  196. package/dist/renderer/assets/smartphone-Bb2GdIKy.js +2 -0
  197. package/dist/renderer/assets/tabs-CPKN3Mmq.js +2 -0
  198. package/dist/renderer/assets/{tooltip-B_-_YVbw.js → tooltip-D6AxXRk5.js} +2 -2
  199. package/dist/renderer/assets/types-Bh0hA7El.js +2 -0
  200. package/dist/renderer/assets/updateDialog-CwndJ0Bl.js +2 -0
  201. package/dist/renderer/assets/{utils-kfQov6Pa.js → utils-sEB5OZEQ.js} +2 -2
  202. package/dist/renderer/assets/view-api-B-2zPBR5.js +2 -0
  203. package/dist/renderer/assets/view-ids-BfaDVV5P.js +2 -0
  204. package/dist/renderer/assets/workbench-BLJ0wKsg.js +5 -0
  205. package/dist/renderer/assets/{workbenchSettings-CV4LVA9b.js → workbenchSettings-YD0Ctbm_.js} +3 -3
  206. package/dist/renderer/entries/device-picker/index.html +28 -0
  207. package/dist/renderer/entries/host-sidebar-default/index.html +6 -5
  208. package/dist/renderer/entries/main/index.html +16 -17
  209. package/dist/renderer/entries/popover/index.html +16 -10
  210. package/dist/renderer/entries/project-create-dialog/index.html +12 -12
  211. package/dist/renderer/entries/settings/index.html +10 -10
  212. package/dist/renderer/entries/tooltip/index.html +5 -5
  213. package/dist/renderer/entries/update-dialog/index.html +10 -10
  214. package/dist/renderer/entries/workbench/index.html +49 -0
  215. package/dist/renderer/entries/workbench-settings/index.html +12 -11
  216. package/dist/service-host/host-env-update.cjs +23 -0
  217. package/dist/service-host/preload.cjs +24 -49
  218. package/dist/service-host/service-message-queue.cjs +71 -0
  219. package/dist/service-host/sync-impls/system-info.js +31 -9
  220. package/dist/shared/compile-mode-state.d.ts +73 -0
  221. package/dist/shared/compile-mode-state.js +100 -0
  222. package/dist/shared/compile-modes.d.ts +152 -0
  223. package/dist/shared/compile-modes.js +237 -0
  224. package/dist/shared/ipc-channels-overlays.d.ts +8 -2
  225. package/dist/shared/ipc-channels-overlays.js +19 -2
  226. package/dist/shared/ipc-channels.d.ts +5 -10
  227. package/dist/shared/ipc-channels.js +17 -14
  228. package/dist/shared/ipc-schemas.d.ts +76 -24
  229. package/dist/shared/ipc-schemas.js +55 -10
  230. package/dist/shared/project-window-types.d.ts +42 -0
  231. package/dist/shared/project-window-types.js +2 -0
  232. package/dist/shared/types.d.ts +43 -17
  233. package/dist/shared/view-ids.d.ts +1 -0
  234. package/dist/shared/view-ids.js +3 -1
  235. package/dist/simulator/assets/bridge-channels-B3JAmRfl.js +2 -0
  236. package/dist/simulator/assets/device-shell-ATEhHV2_.css +1 -0
  237. package/dist/simulator/assets/device-shell-pxnZZp10.js +431 -0
  238. package/dist/simulator/assets/simulator-Dwp7lnvu.js +10 -0
  239. package/dist/simulator/assets/simulator-mini-app-DFo7fhi5.js +2 -0
  240. package/dist/simulator/assets/simulator-ui-B2ddJB1R.js +2 -0
  241. package/dist/simulator/simulator.html +3 -3
  242. package/dist/simulator/temp-files.d.ts +6 -0
  243. package/dist/simulator/temp-files.js +46 -4
  244. package/dist/simulator/types.d.ts +7 -0
  245. package/dist/vscode-workbench/assets/__vite-browser-external-JIkWewFf.js +1 -0
  246. package/dist/vscode-workbench/assets/{dist-CMfmHflW.js → dist-CXG-u1VB.js} +3 -3
  247. package/dist/vscode-workbench/assets/{iconv-lite-umd-Bx8CgaZq.js → iconv-lite-umd-Dxx9Qyb4.js} +1 -1
  248. package/dist/vscode-workbench/assets/{index-CdG6YWhW.js → index-6VJMsaWj.js} +460 -460
  249. package/dist/vscode-workbench/assets/{jschardet-M0qgd2lN.js → jschardet-Ba3D16vt.js} +1 -1
  250. package/dist/vscode-workbench/index.html +1 -1
  251. package/package.json +11 -8
  252. package/dist/renderer/assets/button-D5Q2wkXq.js +0 -2
  253. package/dist/renderer/assets/constants-D7DfVqMp.js +0 -2
  254. package/dist/renderer/assets/constants-D8zsejFq.js +0 -2
  255. package/dist/renderer/assets/dialog-e1TecTOY.js +0 -46
  256. package/dist/renderer/assets/hostSidebarDefault-B3yLfy8j.js +0 -2
  257. package/dist/renderer/assets/index-CsPD68N_.js +0 -5
  258. package/dist/renderer/assets/ipc-channels-overlays-LsDzGAtV.js +0 -2
  259. package/dist/renderer/assets/jsx-runtime-2vXHouwr.css +0 -1
  260. package/dist/renderer/assets/popover-Dix5cm_b.js +0 -2
  261. package/dist/renderer/assets/project-api-k7Oq9iaq.js +0 -2
  262. package/dist/renderer/assets/select-acGb0AmJ.js +0 -2
  263. package/dist/renderer/assets/settings-CQrTMr43.js +0 -2
  264. package/dist/renderer/assets/settings-api-CrUQ9rYV.js +0 -2
  265. package/dist/renderer/assets/settings-tab-bar-Cw1kVIf2.js +0 -2
  266. package/dist/renderer/assets/types-CsEKtFyr.js +0 -2
  267. package/dist/renderer/assets/updateDialog-NsF8223A.js +0 -2
  268. package/dist/renderer/assets/view-api-CLn3rcQE.js +0 -2
  269. package/dist/simulator/assets/bridge-channels-zhsumfky.js +0 -2
  270. package/dist/simulator/assets/device-shell-CmtM1QxP.js +0 -2
  271. package/dist/simulator/assets/device-shell-iFZo1igL.css +0 -1
  272. package/dist/simulator/assets/simulator-CIobn3Mh.js +0 -10
  273. package/dist/simulator/assets/simulator-mini-app-CRe6n9br.js +0 -2
  274. package/dist/simulator/assets/simulator-ui-C_cP602X.js +0 -2
  275. package/dist/vscode-workbench/assets/__vite-browser-external-Bc1vizTf.js +0 -1
@@ -12,6 +12,7 @@
12
12
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
13
  import type { CompileLogBuffer } from '../../workspace/compile-log-buffer.js';
14
14
  import type { SessionStatusStore } from '../../workspace/session-status-store.js';
15
+ import type { McpOpenedProject } from '../opened-project.js';
15
16
  /**
16
17
  * Narrow structural slice of WorkspaceService the project tools need.
17
18
  * Structural (not a Pick of the real interface) so tests can hand in a
@@ -32,18 +33,40 @@ export interface McpProjectWorkspace {
32
33
  getProjectPath(): string;
33
34
  hasActiveSession(): boolean;
34
35
  }
35
- /** Everything `registerProjectTools` needs, assembled by app bootstrap. */
36
- export interface McpProjectHost {
36
+ /**
37
+ * One project window's surfaces, resolved together. A project lives in its own
38
+ * window with its own workspace, status store and log buffer, so a tool call
39
+ * that read them one by one could mix two projects: every surface here comes
40
+ * from the same window at the same instant.
41
+ */
42
+ export interface McpProjectTarget {
37
43
  workspace: McpProjectWorkspace;
38
44
  sessionStatus: SessionStatusStore;
39
45
  compileLogs: CompileLogBuffer;
40
- /** Push the renderer to open the project (the user-click-equivalent path). */
46
+ /** Close this target's project window; a no-op when it has none. */
47
+ closeWindow(): void;
48
+ }
49
+ /** Everything `registerProjectTools` needs, assembled by app bootstrap. */
50
+ export interface McpProjectHost {
51
+ /**
52
+ * The project window a call arriving now is aimed at. Taken ONCE per tool
53
+ * call and held for its whole lifetime: tools await compiles and teardowns,
54
+ * and the user is free to focus another project meanwhile — re-resolving
55
+ * afterwards would report one project's compile as another's, or close the
56
+ * window they just moved to.
57
+ */
58
+ currentProject(): McpProjectTarget;
59
+ /**
60
+ * Push the renderer to open the project (the user-click-equivalent path) and
61
+ * resolve with the window it landed in. A project lives in its own window
62
+ * with its own status store, so the caller can only await the right compile
63
+ * by holding that window — the window active when the call arrived is a
64
+ * different project.
65
+ */
41
66
  requestOpenInUi(project: {
42
67
  name: string;
43
68
  path: string;
44
- }): void;
45
- /** Push the renderer back to the project list after a close. */
46
- requestNavigateBack(): void;
69
+ }): Promise<McpOpenedProject>;
47
70
  }
48
71
  export declare function registerProjectTools(server: McpServer, host: McpProjectHost): void;
49
72
  //# sourceMappingURL=project-tools.d.ts.map
@@ -30,13 +30,14 @@ function publicPhase(snapshot, sessionActive) {
30
30
  return 'idle';
31
31
  return snapshot.phase;
32
32
  }
33
- function statusPayload(host) {
34
- const snapshot = host.sessionStatus.get();
35
- const sessionActive = host.workspace.hasActiveSession();
33
+ /** Reads one window's own status: the pinned target, or the window an open landed in. */
34
+ function statusPayload(source) {
35
+ const snapshot = source.sessionStatus.get();
36
+ const sessionActive = source.workspace.hasActiveSession();
36
37
  return {
37
38
  phase: publicPhase(snapshot, sessionActive),
38
39
  message: snapshot.message,
39
- projectPath: host.workspace.getProjectPath(),
40
+ projectPath: source.workspace.getProjectPath(),
40
41
  sessionActive,
41
42
  watcherAlive: snapshot.watcherAlive,
42
43
  updatedAt: snapshot.updatedAt,
@@ -49,32 +50,37 @@ export function registerProjectTools(server, host) {
49
50
  timeoutMs: z.number().optional().default(DEFAULT_OPEN_TIMEOUT_MS)
50
51
  .describe('Max time to wait for the compile to settle'),
51
52
  }, async ({ path: rawPath, timeoutMs }) => {
53
+ const target = host.currentProject();
52
54
  const dir = path.resolve(rawPath);
53
55
  // Already open and settled: report instead of re-triggering a compile.
54
- const current = host.sessionStatus.get();
55
- if (host.workspace.getProjectPath() === dir
56
- && host.workspace.hasActiveSession()
56
+ const current = target.sessionStatus.get();
57
+ if (target.workspace.getProjectPath() === dir
58
+ && target.workspace.hasActiveSession()
57
59
  && current.phase === 'ready') {
58
- return text({ ...statusPayload(host), note: 'project already open' });
60
+ return text({ ...statusPayload(target), note: 'project already open' });
59
61
  }
60
- const invalid = await host.workspace.validateProjectDir(dir);
62
+ const invalid = await target.workspace.validateProjectDir(dir);
61
63
  if (invalid)
62
64
  return errorText(invalid);
63
65
  let project;
64
- if (await host.workspace.hasProject(dir)) {
65
- const known = (await host.workspace.listProjects()).find((p) => p.path === dir);
66
+ if (await target.workspace.hasProject(dir)) {
67
+ const known = (await target.workspace.listProjects()).find((p) => p.path === dir);
66
68
  project = known ?? { name: path.basename(dir), path: dir };
67
69
  }
68
70
  else {
69
- project = await host.workspace.addProject(dir);
71
+ project = await target.workspace.addProject(dir);
70
72
  }
71
- // Snapshot the generation BEFORE the trigger so a previous session's
72
- // settled state can never be mistaken for this open's result.
73
- const afterGeneration = host.sessionStatus.get().generation;
74
- host.requestOpenInUi({ name: project.name, path: project.path });
73
+ // The open hands back the window it landed in; that window's store is the
74
+ // only place this compile is reported, and its `afterGeneration` keeps a
75
+ // state that predates the open from being read as this open's result.
76
+ let opened;
75
77
  let settled;
76
78
  try {
77
- settled = await host.sessionStatus.waitForSettled({ afterGeneration, timeoutMs });
79
+ opened = await host.requestOpenInUi({ name: project.name, path: project.path });
80
+ settled = await opened.sessionStatus.waitForSettled({
81
+ afterGeneration: opened.afterGeneration,
82
+ timeoutMs,
83
+ });
78
84
  }
79
85
  catch (err) {
80
86
  return errorText(err instanceof Error ? err.message : String(err));
@@ -82,30 +88,36 @@ export function registerProjectTools(server, host) {
82
88
  if (settled.phase === 'error') {
83
89
  return errorText(`compile failed: ${settled.message} — call compile_logs (stream: "stderr") for details`);
84
90
  }
85
- return text(statusPayload(host));
91
+ return text(statusPayload(opened));
86
92
  });
87
93
  server.tool('project_close', 'Close the currently open project session and return the workbench to the project list.', {}, async () => {
88
- if (!host.workspace.getProjectPath() && !host.workspace.hasActiveSession()) {
94
+ // Held across the await: `closeProject()` yields, and a window the user
95
+ // focuses while it runs must not become the window this close takes down.
96
+ const target = host.currentProject();
97
+ if (!target.workspace.getProjectPath() && !target.workspace.hasActiveSession()) {
89
98
  return text({ closed: false, note: 'no project is open' });
90
99
  }
91
- await host.workspace.closeProject();
92
- host.requestNavigateBack();
100
+ await target.workspace.closeProject();
101
+ target.closeWindow();
93
102
  return text({ closed: true });
94
103
  });
95
- server.tool('project_status', 'Query the current project compile status (phase: idle | compiling | ready | error). Includes `generation` — pass it to project_wait_ready to await the NEXT settled state.', {}, async () => text(statusPayload(host)));
104
+ server.tool('project_status', 'Query the current project compile status (phase: idle | compiling | ready | error). Includes `generation` — pass it to project_wait_ready to await the NEXT settled state.', {}, async () => text(statusPayload(host.currentProject())));
96
105
  server.tool('project_wait_ready', 'Wait until the compile pipeline settles (phase leaves "compiling"). Watcher rebuilds emit no "compiling" phase, so to await a rebuild after editing files pass the `generation` from a prior project_status.', {
97
106
  afterGeneration: z.number().optional()
98
107
  .describe('Only accept states recorded after this generation (from project_status). Omit to accept the current state when already settled.'),
99
108
  timeoutMs: z.number().optional().default(DEFAULT_WAIT_TIMEOUT_MS)
100
109
  .describe('Max time to wait'),
101
110
  }, async ({ afterGeneration, timeoutMs }) => {
111
+ // Held across the wait: the state this call reports must come from the
112
+ // window it was aimed at, whichever window is focused when it settles.
113
+ const target = host.currentProject();
102
114
  try {
103
- await host.sessionStatus.waitForSettled({ afterGeneration, timeoutMs });
115
+ await target.sessionStatus.waitForSettled({ afterGeneration, timeoutMs });
104
116
  }
105
117
  catch (err) {
106
118
  return errorText(err instanceof Error ? err.message : String(err));
107
119
  }
108
- return text(statusPayload(host));
120
+ return text(statusPayload(target));
109
121
  });
110
122
  server.tool('compile_logs', 'Pull compile log lines incrementally. Pass the previous call\'s nextCursor as `cursor` to receive only new lines. The buffer resets on each fresh project open; watcher rebuilds append to the same timeline.', {
111
123
  cursor: z.number().optional().default(0)
@@ -114,7 +126,7 @@ export function registerProjectTools(server, host) {
114
126
  stream: z.enum(['stdout', 'stderr']).optional()
115
127
  .describe('Only lines from this stream (stderr carries compile errors)'),
116
128
  }, async ({ cursor, limit, stream }) => {
117
- const read = host.compileLogs.read({ afterSeq: cursor, limit, stream });
129
+ const read = host.currentProject().compileLogs.read({ afterSeq: cursor, limit, stream });
118
130
  return text(read);
119
131
  });
120
132
  }
@@ -1,17 +1,25 @@
1
1
  import type { Disposable } from '@dimina-kit/electron-deck/main';
2
2
  import type { WorkbenchContext } from './workbench-context.js';
3
+ import type { IpcContextSource } from '../utils/ipc-context-source.js';
3
4
  /**
4
5
  * A composable unit of workbench functionality.
5
6
  *
6
7
  * Built-in modules (projects, session, simulator, popover, settings) are
7
8
  * implemented as `WorkbenchModule` values.
8
9
  *
9
- * `setup` is invoked once during workbench bootstrap. The returned
10
- * Disposable is added to `ctx.registry` so module teardown is symmetric
11
- * with the rest of the workbench lifecycle.
10
+ * The two hooks differ in what they own. `setup` registers IPC once for the
11
+ * application and answers each message with the context the sender belongs to,
12
+ * so its handlers must read everything from that argument. `setupWindow` is the
13
+ * escape hatch for wiring that genuinely belongs to a single window — it
14
+ * receives that window's concrete context and may mutate it.
15
+ *
16
+ * Both returned Disposables are added to a registry so module teardown is
17
+ * symmetric with the rest of the workbench lifecycle.
12
18
  */
13
19
  export interface WorkbenchModule {
14
- /** Set up the module against ctx. Returns a Disposable owned by ctx.registry. */
15
- setup(ctx: WorkbenchContext): Disposable;
20
+ /** Register the module's IPC against every live window context. */
21
+ setup(source: IpcContextSource<WorkbenchContext>): Disposable;
22
+ /** Optional per-window wiring, run once for each window that opens. */
23
+ setupWindow?(ctx: WorkbenchContext): Disposable;
16
24
  }
17
25
  //# sourceMappingURL=module.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import type { BrowserWindow, WebContents, WebContentsView } from 'electron';
2
- import type { CompileConfig } from '../../../shared/types.js';
2
+ import type { CompileModeChange } from '../workspace/compile-mode-store.js';
3
3
  import { type EditorOpenFilePayload } from '../../../shared/ipc-channels.js';
4
4
  import type { WorkbenchSettings } from '../settings/index.js';
5
5
  import type { ProjectSettings, ProjectType } from '../projects/project-repository.js';
@@ -65,7 +65,6 @@ export interface CompileLogPayload {
65
65
  */
66
66
  export interface SettingsInitPayload {
67
67
  projectPath: string;
68
- config: CompileConfig;
69
68
  projectSettings: ProjectSettings;
70
69
  }
71
70
  /**
@@ -108,8 +107,21 @@ export interface RendererNotifier {
108
107
  }): void;
109
108
  /** Tell the main renderer the compile popover has been closed. */
110
109
  popoverClosed(): void;
111
- /** Ask the main renderer to relaunch the simulator with a new config. */
112
- popoverRelaunch(config: CompileConfig): void;
110
+ /**
111
+ * Push the open project's `CompileModeStore` after it advanced — via the
112
+ * popover or any other command source. `relaunch` is true when the change
113
+ * affects what is currently running, so the renderer restarts the
114
+ * simulator with it.
115
+ */
116
+ compileModesChanged(change: CompileModeChange): void;
117
+ /**
118
+ * Push a rejected `applyCompileModeCommand` (e.g. a failed persist). The
119
+ * renderer surfaces this as a compile-status error without touching its
120
+ * mirrored `compileModes`.
121
+ */
122
+ compileModesApplyFailed(payload: {
123
+ message: string;
124
+ }): void;
113
125
  /**
114
126
  * Push the reserved host-toolbar height to the main renderer so its toolbar
115
127
  * placeholder div resizes (closes the host-toolbar dynamic-height loop).
@@ -141,6 +153,14 @@ export interface RendererNotifier {
141
153
  path: string;
142
154
  templateId: string;
143
155
  }): void;
156
+ /**
157
+ * Relay the device picked in the device-picker overlay back to the main
158
+ * renderer, whose simulator toolbar owns the device state — the panel only
159
+ * renders the list.
160
+ */
161
+ devicePickerSelected(payload: {
162
+ deviceName: string;
163
+ }): void;
144
164
  /** Initialise the currently shown compile popover overlay. */
145
165
  popoverInit(popoverView: WebContentsView, payload: unknown): void;
146
166
  /** Initialise the currently shown settings overlay (no-op if hidden). */
@@ -154,6 +174,10 @@ export interface RendererNotifier {
154
174
  }): void;
155
175
  /** Push a discovered update's info into the update overlay. */
156
176
  updateAvailable(dialogView: WebContentsView, payload: UpdateInfo): void;
177
+ /** Push the currently selected device into the device-picker overlay. */
178
+ devicePickerInit(pickerView: WebContentsView, payload: {
179
+ deviceName: string;
180
+ }): void;
157
181
  /** Initialise the standalone workbench-settings window. */
158
182
  workbenchSettingsInit(window: BrowserWindow, payload: WorkbenchSettingsInitPayload): void;
159
183
  }
@@ -1,5 +1,5 @@
1
1
  import { ProjectChannel, SessionChannel, WindowChannel, WorkbenchSettingsChannel, EditorChannel, } from '../../../shared/ipc-channels.js';
2
- import { SettingsChannel, PopoverChannel, TooltipChannel, ProjectCreateChannel, UpdateChannel, ViewChannel, } from '../../../shared/ipc-channels-overlays.js';
2
+ import { SettingsChannel, PopoverChannel, TooltipChannel, ProjectCreateChannel, DevicePickerChannel, UpdateChannel, ViewChannel, } from '../../../shared/ipc-channels-overlays.js';
3
3
  /** Safely resolve a WebContents, skipping destroyed / missing targets. */
4
4
  function liveWebContents(wc) {
5
5
  if (!wc)
@@ -40,8 +40,11 @@ export function createRendererNotifier(ctx) {
40
40
  popoverClosed() {
41
41
  sendToMain(PopoverChannel.Closed);
42
42
  },
43
- popoverRelaunch(config) {
44
- sendToMain(PopoverChannel.Relaunch, config);
43
+ compileModesChanged(change) {
44
+ sendToMain(ProjectChannel.CompileModesChanged, change);
45
+ },
46
+ compileModesApplyFailed(payload) {
47
+ sendToMain(ProjectChannel.CompileModesApplyFailed, payload);
45
48
  },
46
49
  hostToolbarHeightChanged(height) {
47
50
  sendToMain(ViewChannel.HostToolbarHeightChanged, height);
@@ -58,6 +61,9 @@ export function createRendererNotifier(ctx) {
58
61
  projectCreateSubmitted(payload) {
59
62
  sendToMain(ProjectCreateChannel.Submitted, payload);
60
63
  },
64
+ devicePickerSelected(payload) {
65
+ sendToMain(DevicePickerChannel.Selected, payload);
66
+ },
61
67
  popoverInit(popoverView, payload) {
62
68
  const wc = liveWebContents(popoverView.webContents);
63
69
  if (!wc)
@@ -82,6 +88,12 @@ export function createRendererNotifier(ctx) {
82
88
  return;
83
89
  wc.send(UpdateChannel.Available, payload);
84
90
  },
91
+ devicePickerInit(pickerView, payload) {
92
+ const wc = liveWebContents(pickerView.webContents);
93
+ if (!wc)
94
+ return;
95
+ wc.send(DevicePickerChannel.Init, payload);
96
+ },
85
97
  settingsInit(payload) {
86
98
  const wc = liveWebContents(ctx.views.getSettingsWebContents());
87
99
  if (!wc)
@@ -24,6 +24,8 @@ export function createLocalProjectsProvider() {
24
24
  updateLastOpened: (dirPath) => repo.updateLastOpened(dirPath),
25
25
  getCompileConfig: (dirPath) => repo.getCompileConfig(dirPath),
26
26
  saveCompileConfig: (dirPath, cfg) => repo.saveCompileConfig(dirPath, cfg),
27
+ getCompileModes: (dirPath) => repo.getCompileModes(dirPath),
28
+ saveCompileModes: (dirPath, modes) => repo.saveCompileModes(dirPath, modes),
27
29
  saveThumbnail: (dirPath, dataUrl) => saveThumbnailFromDataUrl(dirPath, dataUrl),
28
30
  getThumbnail: (dirPath) => loadThumbnail(dirPath),
29
31
  };
@@ -1,4 +1,4 @@
1
- import type { CompileConfig, ProjectType } from '../../../shared/types.js';
1
+ import type { CompileConfig, CompileModes, ProjectType } from '../../../shared/types.js';
2
2
  export type { ProjectType };
3
3
  export interface Project {
4
4
  name: string;
@@ -46,8 +46,38 @@ export declare function removeProject(dirPath: string): void;
46
46
  */
47
47
  export declare function updateProject(dirPath: string, patch: ProjectPatch): Project | null;
48
48
  export declare function updateLastOpened(dirPath: string): void;
49
+ /**
50
+ * Read a project's compile modes from `project.config.json`'s
51
+ * `condition.miniprogram` — WeChat DevTools' own location and shape, so a
52
+ * project checked into git carries its modes to every machine, and the same
53
+ * directory opened in that tool shows the same list.
54
+ *
55
+ * Projects imported before compile modes existed fall back to migrating the
56
+ * legacy single config out of the project list file.
57
+ */
58
+ export declare function getCompileModes(dirPath: string): CompileModes;
59
+ /**
60
+ * Persist compile modes, merged so the rest of the target file survives —
61
+ * including the rest of `condition`, which for a mini-game project holds its
62
+ * own `condition.game` block.
63
+ */
64
+ export declare function saveCompileModes(dirPath: string, modes: CompileModes): void;
65
+ /**
66
+ * The launch parameters the selected mode resolves to. 普通编译 resolves to
67
+ * an empty start page; it is filled in here with the project's real entry
68
+ * page (mirrors getProjectPages: 'game' for mini-games, app.json's
69
+ * entryPagePath for mini-programs) rather than a mini-program-only literal —
70
+ * the simulator URL builders' own 'pages/index/index' fallback only fires
71
+ * when this is still empty (unreadable/malformed manifest).
72
+ */
49
73
  export declare function getCompileConfig(dirPath: string): CompileConfig;
50
74
  export declare function getProjectPages(dirPath: string): ProjectPages;
75
+ /**
76
+ * @deprecated Compile modes are the stored form — call `saveCompileModes`.
77
+ * Kept working for embedding hosts still on the single-config setter: the
78
+ * config lands on the selected mode, or becomes a new selected mode when
79
+ * 普通编译 is selected and the config asks for more than it can express.
80
+ */
51
81
  export declare function saveCompileConfig(dirPath: string, config: CompileConfig): void;
52
82
  /** Read a subset of `project.config.json` exposed to the settings panel. */
53
83
  export declare function getProjectSettings(projectPath: string): ProjectSettings;
@@ -1,9 +1,12 @@
1
1
  import { app } from 'electron';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
- import { DEFAULT_SCENE } from '../../../shared/constants.js';
4
+ import { NORMAL_COMPILE_INDEX, compileConfigToMode, compileConfigToModes, emptyCompileModes, isNormalCompile, normalizeCompileModes, resolveCompileConfig, } from '../../../shared/compile-modes.js';
5
5
  import { createLogger } from '../../utils/logger.js';
6
6
  const log = createLogger('projects');
7
+ function isRecord(value) {
8
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ }
7
10
  /**
8
11
  * Merge project.config.json + project.private.config.json (private wins),
9
12
  * mirroring `dimina/fe/packages/compiler/src/env.js`'s `storeInfo`.
@@ -206,21 +209,130 @@ export function updateLastOpened(dirPath) {
206
209
  save(projects);
207
210
  }
208
211
  }
209
- export function getCompileConfig(dirPath) {
212
+ /**
213
+ * Read a project's compile modes from `project.config.json`'s
214
+ * `condition.miniprogram` — WeChat DevTools' own location and shape, so a
215
+ * project checked into git carries its modes to every machine, and the same
216
+ * directory opened in that tool shows the same list.
217
+ *
218
+ * Projects imported before compile modes existed fall back to migrating the
219
+ * legacy single config out of the project list file.
220
+ */
221
+ export function getCompileModes(dirPath) {
222
+ if (!dirPath)
223
+ return emptyCompileModes();
224
+ const condition = readProjectConfig(dirPath).condition;
225
+ const stored = isRecord(condition) && condition.miniprogram !== undefined
226
+ ? normalizeCompileModes(condition.miniprogram)
227
+ : null;
228
+ // A stored list with entries is the answer — it is the user's own edit. An
229
+ // EMPTY block is not evidence that the project has no modes: WeChat DevTools
230
+ // writes `"miniprogram": {"list": []}` into any project it opens, and taking
231
+ // that as "no modes" would drop the start page a user configured before
232
+ // compile modes existed.
233
+ if (stored && stored.list.length > 0)
234
+ return stored;
235
+ return migrateLegacyCompileConfig(dirPath) ?? stored ?? emptyCompileModes();
236
+ }
237
+ /**
238
+ * Which config file owns `condition` for this project — the one a save has to
239
+ * land in to be read back.
240
+ *
241
+ * `readProjectConfig` merges the private file over the public one key by key,
242
+ * so a private file carrying any `condition` at all (even one that only holds
243
+ * a mini-game block, with no `miniprogram` in it) replaces the whole merged
244
+ * `condition`. Writing modes to the public file in that case reports success
245
+ * and then reads back as if nothing had been saved.
246
+ *
247
+ * With no private `condition`, modes stay in the shared public file, where the
248
+ * team gets them from git.
249
+ */
250
+ function compileModesConfigPath(dirPath) {
251
+ const privatePath = path.join(dirPath, 'project.private.config.json');
252
+ if (fs.existsSync(privatePath)) {
253
+ try {
254
+ const privateConfig = JSON.parse(fs.readFileSync(privatePath, 'utf-8'));
255
+ if (isRecord(privateConfig) && privateConfig.condition !== undefined)
256
+ return privatePath;
257
+ }
258
+ catch {
259
+ // Unparseable — readProjectConfig skips this file too, so the public one
260
+ // is what a read actually sees.
261
+ }
262
+ }
263
+ return path.join(dirPath, 'project.config.json');
264
+ }
265
+ /**
266
+ * Persist compile modes, merged so the rest of the target file survives —
267
+ * including the rest of `condition`, which for a mini-game project holds its
268
+ * own `condition.game` block.
269
+ */
270
+ export function saveCompileModes(dirPath, modes) {
271
+ if (!dirPath)
272
+ return;
273
+ const configPath = compileModesConfigPath(dirPath);
274
+ let config = {};
275
+ if (fs.existsSync(configPath)) {
276
+ try {
277
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
278
+ }
279
+ catch {
280
+ // Refuse rather than overwrite: this file also holds the project's
281
+ // build settings, which a blind rewrite would drop.
282
+ throw new Error(`无法保存编译模式:${configPath} 不是合法 JSON`);
283
+ }
284
+ }
285
+ const condition = isRecord(config.condition) ? config.condition : {};
286
+ fs.writeFileSync(configPath, JSON.stringify({ ...config, condition: { ...condition, miniprogram: modes } }, null, 2));
287
+ // The user has now said what this project's modes are, so the pre-compile-
288
+ // modes single config is spent. Retiring it here is what makes deleting a
289
+ // migrated mode stick: an empty stored list would otherwise be read as
290
+ // "nothing stored yet" and migrate the old config back.
291
+ clearLegacyCompileConfig(dirPath);
292
+ }
293
+ /**
294
+ * Projects imported before compile modes existed stored one unnamed compile
295
+ * config in the project list file. Surface it as a selected mode rather than
296
+ * silently dropping the user's start page — but only when it asks for
297
+ * something 普通编译 can't already do, so the common case stays an empty list.
298
+ * Returns null when there is nothing to migrate.
299
+ *
300
+ * Read-only: opening a project must not rewrite the user's git-tracked
301
+ * project.config.json. The legacy record is retired instead by the first
302
+ * explicit save (see `saveCompileModes`), which is also what keeps a deleted
303
+ * mode deleted — without that, an empty stored list would migrate again and
304
+ * put the mode back.
305
+ */
306
+ function migrateLegacyCompileConfig(dirPath) {
307
+ const legacy = load().find((p) => p.path === dirPath)?.compileConfig;
308
+ if (!legacy)
309
+ return null;
310
+ const modes = compileConfigToModes(legacy, getProjectPages(dirPath).entryPagePath);
311
+ return modes.list.length > 0 ? modes : null;
312
+ }
313
+ /** Remove the pre-compile-modes single config from a project record. */
314
+ function clearLegacyCompileConfig(dirPath) {
210
315
  const projects = load();
211
- const project = projects.find((p) => p.path === dirPath);
212
- if (project?.compileConfig)
213
- return project.compileConfig;
214
- // Default startPage to the project's real entry page (mirrors
215
- // getProjectPages: 'game' for mini-games, app.json's entryPagePath for
216
- // mini-programs) rather than a mini-program-only literal — the simulator
217
- // URL builders' own 'pages/index/index' fallback only fires when this is
218
- // still empty (unreadable/malformed manifest).
219
- return {
220
- startPage: getProjectPages(dirPath).entryPagePath,
221
- scene: DEFAULT_SCENE,
222
- queryParams: [],
223
- };
316
+ const idx = projects.findIndex((p) => p.path === dirPath);
317
+ if (idx < 0 || projects[idx].compileConfig === undefined)
318
+ return;
319
+ const { compileConfig: _legacy, ...rest } = projects[idx];
320
+ projects[idx] = rest;
321
+ save(projects);
322
+ }
323
+ /**
324
+ * The launch parameters the selected mode resolves to. 普通编译 resolves to
325
+ * an empty start page; it is filled in here with the project's real entry
326
+ * page (mirrors getProjectPages: 'game' for mini-games, app.json's
327
+ * entryPagePath for mini-programs) rather than a mini-program-only literal —
328
+ * the simulator URL builders' own 'pages/index/index' fallback only fires
329
+ * when this is still empty (unreadable/malformed manifest).
330
+ */
331
+ export function getCompileConfig(dirPath) {
332
+ const config = resolveCompileConfig(getCompileModes(dirPath));
333
+ if (config.startPage)
334
+ return config;
335
+ return { ...config, startPage: getProjectPages(dirPath).entryPagePath };
224
336
  }
225
337
  export function getProjectPages(dirPath) {
226
338
  if (detectRuntimeType(dirPath) === 'minigame') {
@@ -242,13 +354,25 @@ export function getProjectPages(dirPath) {
242
354
  return { pages: [], entryPagePath: '' };
243
355
  }
244
356
  }
357
+ /**
358
+ * @deprecated Compile modes are the stored form — call `saveCompileModes`.
359
+ * Kept working for embedding hosts still on the single-config setter: the
360
+ * config lands on the selected mode, or becomes a new selected mode when
361
+ * 普通编译 is selected and the config asks for more than it can express.
362
+ */
245
363
  export function saveCompileConfig(dirPath, config) {
246
- const projects = load();
247
- const idx = projects.findIndex((p) => p.path === dirPath);
248
- if (idx >= 0) {
249
- projects[idx] = { ...projects[idx], compileConfig: config };
250
- save(projects);
364
+ const modes = getCompileModes(dirPath);
365
+ if (modes.current === NORMAL_COMPILE_INDEX) {
366
+ if (isNormalCompile(config, getProjectPages(dirPath).entryPagePath))
367
+ return;
368
+ saveCompileModes(dirPath, {
369
+ current: modes.list.length,
370
+ list: [...modes.list, compileConfigToMode(config, '')],
371
+ });
372
+ return;
251
373
  }
374
+ const list = modes.list.map((mode, i) => (i === modes.current ? { ...mode, ...compileConfigToMode(config, mode.name) } : mode));
375
+ saveCompileModes(dirPath, { ...modes, list });
252
376
  }
253
377
  /** Read a subset of `project.config.json` exposed to the settings panel. */
254
378
  export function getProjectSettings(projectPath) {
@@ -4,7 +4,7 @@
4
4
  * `WorkbenchAppConfig` to fully take over the project source-of-truth, the
5
5
  * template catalog, and the "新建项目" dialog.
6
6
  */
7
- import type { CompileConfig } from '../../../shared/types.js';
7
+ import type { CompileConfig, CompileModes } from '../../../shared/types.js';
8
8
  import type { Project, ProjectPatch } from './project-repository.js';
9
9
  export type { Project, ProjectPatch };
10
10
  export type { ProjectTemplate, CreateProjectInput } from '../../../shared/types.js';
@@ -59,20 +59,43 @@ export interface ProjectsProvider {
59
59
  */
60
60
  updateLastOpened?(dirPath: string): void | Promise<void>;
61
61
  /**
62
- * Read the per-project compile config.
62
+ * Read the per-project compile config — the launch parameters the
63
+ * SELECTED compile mode resolves to.
63
64
  *
64
- * Default when omitted: returns `DEFAULT_COMPILE_CONFIG`
65
- * (`{ startPage: '', scene: 1001, queryParams: [] }`).
65
+ * Default when omitted: derived from `getCompileModes`, or
66
+ * `DEFAULT_COMPILE_CONFIG` (`{ startPage: '', scene: 1001, queryParams: [] }`)
67
+ * when that is absent too. Implement this only if you can resolve the
68
+ * project's own entry page for 普通编译 — the derived default leaves
69
+ * `startPage` empty and lets the renderer substitute it.
66
70
  */
67
71
  getCompileConfig?(dirPath: string): CompileConfig | Promise<CompileConfig>;
68
72
  /**
69
- * Persist the per-project compile config.
73
+ * @deprecated Implement `saveCompileModes` instead — the named mode list
74
+ * is the stored form, and this setter can only express the selected
75
+ * mode's parameters.
70
76
  *
71
- * Default when omitted: silently no-ops — the renderer's edits will
72
- * not survive a reload. Implement this if your UI exposes the compile
73
- * config panel.
77
+ * Default when omitted: silently no-ops.
74
78
  */
75
79
  saveCompileConfig?(dirPath: string, cfg: CompileConfig): void | Promise<void>;
80
+ /**
81
+ * Read the project's compile modes and which one is selected. The stored
82
+ * form behind the toolbar's compile-mode dropdown; `LocalProjectsProvider`
83
+ * keeps it in the project's own `project.config.json` under
84
+ * `condition.miniprogram` (WeChat DevTools' location and shape).
85
+ *
86
+ * Default when omitted: `{ current: -1, list: [] }` — the dropdown then
87
+ * offers 普通编译 only.
88
+ */
89
+ getCompileModes?(dirPath: string): CompileModes | Promise<CompileModes>;
90
+ /**
91
+ * Persist the project's compile modes and selection.
92
+ *
93
+ * Default when omitted: falls back to `saveCompileConfig` with the
94
+ * resolved launch parameters, so a host on the older setter keeps the
95
+ * selection's effect but not the named list. With neither, edits do not
96
+ * survive a reload.
97
+ */
98
+ saveCompileModes?(dirPath: string, modes: CompileModes): void | Promise<void>;
76
99
  /**
77
100
  * Persist a captured screenshot for the given project. `imageDataUrl`
78
101
  * is a `data:image/png;base64,...` string so a remote provider can ship