@dimina-kit/devtools 0.3.1 → 0.3.2-dev.20260522085907

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 (190) hide show
  1. package/README.md +297 -133
  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 +84 -36
  6. package/dist/main/app/bootstrap.js +2 -1
  7. package/dist/main/app/launch.d.ts +2 -2
  8. package/dist/main/app/launch.js +10 -8
  9. package/dist/main/index.bundle.js +1567 -477
  10. package/dist/main/ipc/app.d.ts +2 -1
  11. package/dist/main/ipc/app.js +5 -4
  12. package/dist/main/ipc/index.d.ts +5 -4
  13. package/dist/main/ipc/index.js +5 -4
  14. package/dist/main/ipc/panels.d.ts +2 -1
  15. package/dist/main/ipc/panels.js +16 -13
  16. package/dist/main/ipc/popover.d.ts +4 -1
  17. package/dist/main/ipc/popover.js +15 -5
  18. package/dist/main/ipc/projects.d.ts +6 -1
  19. package/dist/main/ipc/projects.js +127 -6
  20. package/dist/main/ipc/session.d.ts +4 -1
  21. package/dist/main/ipc/session.js +34 -6
  22. package/dist/main/ipc/settings.d.ts +4 -1
  23. package/dist/main/ipc/settings.js +51 -23
  24. package/dist/main/ipc/simulator-module.d.ts +8 -0
  25. package/dist/main/ipc/simulator-module.js +19 -0
  26. package/dist/main/ipc/simulator.d.ts +2 -1
  27. package/dist/main/ipc/simulator.js +23 -18
  28. package/dist/main/ipc/toolbar.d.ts +2 -1
  29. package/dist/main/ipc/toolbar.js +3 -2
  30. package/dist/main/menu/index.js +11 -0
  31. package/dist/main/services/automation/exec.d.ts +2 -0
  32. package/dist/main/services/automation/exec.js +7 -9
  33. package/dist/main/services/automation/handlers/app.js +37 -51
  34. package/dist/main/services/automation/handlers/element.js +27 -41
  35. package/dist/main/services/automation/index.js +88 -13
  36. package/dist/main/services/default-adapter.js +3 -3
  37. package/dist/main/services/mcp/index.d.ts +1 -0
  38. package/dist/main/services/mcp/index.js +1 -0
  39. package/dist/main/services/mcp/server.d.ts +2 -1
  40. package/dist/main/services/mcp/server.js +10 -0
  41. package/dist/main/services/mcp/status.d.ts +10 -0
  42. package/dist/main/services/mcp/status.js +14 -0
  43. package/dist/main/services/mcp/target-manager.js +2 -1
  44. package/dist/main/services/module.d.ts +18 -0
  45. package/dist/main/services/module.js +2 -0
  46. package/dist/main/services/notifications/renderer-notifier.d.ts +5 -3
  47. package/dist/main/services/notifications/renderer-notifier.js +3 -6
  48. package/dist/main/services/projects/builtin-templates.d.ts +3 -0
  49. package/dist/main/services/projects/builtin-templates.js +24 -0
  50. package/dist/main/services/projects/create-project-service.d.ts +7 -0
  51. package/dist/main/services/projects/create-project-service.js +84 -0
  52. package/dist/main/services/projects/index.d.ts +7 -0
  53. package/dist/main/services/projects/index.js +9 -0
  54. package/dist/main/services/projects/local-provider.d.ts +3 -0
  55. package/dist/main/services/projects/local-provider.js +25 -0
  56. package/dist/main/services/projects/project-repository.d.ts +1 -1
  57. package/dist/main/services/projects/project-repository.js +3 -0
  58. package/dist/main/services/projects/templates.d.ts +21 -0
  59. package/dist/main/services/projects/templates.js +39 -0
  60. package/dist/main/services/projects/thumbnail.d.ts +8 -0
  61. package/dist/main/services/projects/thumbnail.js +37 -0
  62. package/dist/main/services/projects/types.d.ts +118 -0
  63. package/dist/main/services/projects/types.js +19 -0
  64. package/dist/main/services/settings/index.d.ts +11 -0
  65. package/dist/main/services/settings/index.js +18 -2
  66. package/dist/main/services/simulator/custom-apis.d.ts +10 -0
  67. package/dist/main/services/simulator/custom-apis.js +26 -0
  68. package/dist/main/services/simulator-storage/index.d.ts +22 -1
  69. package/dist/main/services/simulator-storage/index.js +334 -97
  70. package/dist/main/services/update/update-manager.d.ts +16 -1
  71. package/dist/main/services/update/update-manager.js +18 -15
  72. package/dist/main/services/views/view-manager.d.ts +7 -7
  73. package/dist/main/services/views/view-manager.js +53 -29
  74. package/dist/main/services/window-service.d.ts +24 -0
  75. package/dist/main/services/window-service.js +26 -0
  76. package/dist/main/services/workbench-context.d.ts +49 -2
  77. package/dist/main/services/workbench-context.js +15 -2
  78. package/dist/main/services/workspace/workspace-service.d.ts +9 -6
  79. package/dist/main/services/workspace/workspace-service.js +71 -11
  80. package/dist/main/simulator-apis.d.ts +23 -0
  81. package/dist/main/simulator-apis.js +24 -0
  82. package/dist/main/utils/disposable.d.ts +13 -0
  83. package/dist/main/utils/disposable.js +60 -0
  84. package/dist/main/utils/ipc-registry.d.ts +39 -0
  85. package/dist/main/utils/ipc-registry.js +109 -0
  86. package/dist/main/utils/ipc-schema.d.ts +28 -0
  87. package/dist/main/utils/ipc-schema.js +38 -0
  88. package/dist/main/utils/paths.d.ts +6 -0
  89. package/dist/main/utils/paths.js +6 -0
  90. package/dist/main/utils/sender-policy.d.ts +25 -0
  91. package/dist/main/utils/sender-policy.js +44 -0
  92. package/dist/main/utils/theme.d.ts +24 -0
  93. package/dist/main/utils/theme.js +38 -1
  94. package/dist/main/windows/main-window/create.d.ts +6 -0
  95. package/dist/main/windows/main-window/create.js +66 -10
  96. package/dist/main/windows/main-window/events.d.ts +2 -1
  97. package/dist/main/windows/main-window/events.js +25 -2
  98. package/dist/main/windows/navigation-hardening.d.ts +24 -0
  99. package/dist/main/windows/navigation-hardening.js +55 -0
  100. package/dist/main/windows/settings-window/create.js +11 -1
  101. package/dist/preload/index.d.ts +22 -0
  102. package/dist/preload/index.js +19 -0
  103. package/dist/preload/instrumentation/app-data.d.ts +32 -2
  104. package/dist/preload/instrumentation/app-data.js +253 -79
  105. package/dist/preload/instrumentation/index.d.ts +2 -2
  106. package/dist/preload/instrumentation/index.js +2 -2
  107. package/dist/preload/instrumentation/wxml.d.ts +16 -4
  108. package/dist/preload/instrumentation/wxml.js +188 -178
  109. package/dist/preload/miniapp-snapshot/host.d.ts +12 -0
  110. package/dist/preload/miniapp-snapshot/host.js +117 -0
  111. package/dist/preload/miniapp-snapshot/types.d.ts +62 -0
  112. package/dist/preload/miniapp-snapshot/types.js +15 -0
  113. package/dist/preload/runtime/bridge.js +5 -24
  114. package/dist/preload/runtime/custom-apis.d.ts +6 -0
  115. package/dist/preload/runtime/custom-apis.js +43 -0
  116. package/dist/preload/runtime/host.d.ts +1 -0
  117. package/dist/preload/runtime/host.js +13 -0
  118. package/dist/preload/shared/expose.d.ts +15 -0
  119. package/dist/preload/shared/expose.js +33 -0
  120. package/dist/preload/shared/page-iframe.d.ts +3 -0
  121. package/dist/preload/shared/page-iframe.js +6 -0
  122. package/dist/preload/shared/types.d.ts +2 -0
  123. package/dist/preload/windows/main.cjs +47 -0
  124. package/dist/preload/windows/main.cjs.map +7 -0
  125. package/dist/preload/windows/simulator.js +501 -272
  126. package/dist/renderer/assets/constants-DI382mTP.js +2 -0
  127. package/dist/renderer/assets/index-D8VFYBh-.js +46 -0
  128. package/dist/renderer/assets/{input-DxfYnzX_.js → input-CoWcfa8u.js} +2 -2
  129. package/dist/renderer/assets/ipc-transport-Cd2xPrsa.js +9 -0
  130. package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +1 -0
  131. package/dist/renderer/assets/popover-Nq5gT9DO.js +2 -0
  132. package/dist/renderer/assets/select-By5KH-UG.js +2 -0
  133. package/dist/renderer/assets/{settings-DteGraEU.js → settings-Dj_N70qR.js} +2 -2
  134. package/dist/renderer/assets/settings-api-Do0_Smq1.js +2 -0
  135. package/dist/renderer/assets/workbenchSettings-CazSLGYt.js +8 -0
  136. package/dist/renderer/entries/main/index.html +13 -5
  137. package/dist/renderer/entries/popover/index.html +8 -5
  138. package/dist/renderer/entries/settings/index.html +7 -5
  139. package/dist/renderer/entries/workbench-settings/index.html +7 -4
  140. package/dist/shared/ipc-channels.d.ts +46 -13
  141. package/dist/shared/ipc-channels.js +60 -14
  142. package/dist/shared/ipc-schemas.d.ts +118 -0
  143. package/dist/shared/ipc-schemas.js +133 -0
  144. package/dist/shared/simulator-route.d.ts +72 -0
  145. package/dist/shared/simulator-route.js +169 -0
  146. package/dist/shared/types.d.ts +82 -8
  147. package/dist/simulator/assets/simulator-DJCozrU5.js +9 -0
  148. package/dist/simulator/simulator.html +6 -43
  149. package/package.json +18 -29
  150. package/templates/blank/app.js +6 -0
  151. package/templates/blank/app.json +11 -0
  152. package/templates/blank/app.wxss +5 -0
  153. package/templates/blank/pages/index/index.js +6 -0
  154. package/templates/blank/pages/index/index.json +3 -0
  155. package/templates/blank/pages/index/index.wxml +3 -0
  156. package/templates/blank/pages/index/index.wxss +11 -0
  157. package/templates/blank/project.config.json +13 -0
  158. package/templates/blank/sitemap.json +9 -0
  159. package/templates/taro-todo/app.js +1 -0
  160. package/templates/taro-todo/app.json +1 -0
  161. package/templates/taro-todo/app.wxss +1 -0
  162. package/templates/taro-todo/base.wxml +851 -0
  163. package/templates/taro-todo/common.js +1 -0
  164. package/templates/taro-todo/comp.js +1 -0
  165. package/templates/taro-todo/comp.json +1 -0
  166. package/templates/taro-todo/comp.wxml +2 -0
  167. package/templates/taro-todo/custom-wrapper.js +1 -0
  168. package/templates/taro-todo/custom-wrapper.json +1 -0
  169. package/templates/taro-todo/custom-wrapper.wxml +4 -0
  170. package/templates/taro-todo/pages/index/index.js +2 -0
  171. package/templates/taro-todo/pages/index/index.json +1 -0
  172. package/templates/taro-todo/pages/index/index.wxml +2 -0
  173. package/templates/taro-todo/pages/index/index.wxss +1 -0
  174. package/templates/taro-todo/project.config.json +44 -0
  175. package/templates/taro-todo/runtime.js +1 -0
  176. package/templates/taro-todo/taro.js +2 -0
  177. package/templates/taro-todo/utils.wxs +39 -0
  178. package/templates/taro-todo/vendors.js +2 -0
  179. package/dist/preload/instrumentation/disposable.d.ts +0 -11
  180. package/dist/preload/instrumentation/disposable.js +0 -15
  181. package/dist/preload/windows/main.d.ts +0 -6
  182. package/dist/preload/windows/main.js +0 -6
  183. package/dist/renderer/assets/index-D1p6crtf.js +0 -46
  184. package/dist/renderer/assets/ipc-transport-ClIvDcYY.js +0 -9
  185. package/dist/renderer/assets/ipc-transport-EiWxu_hG.css +0 -1
  186. package/dist/renderer/assets/popover-CtXcBJtM.js +0 -2
  187. package/dist/renderer/assets/select-DMfp49f1.js +0 -2
  188. package/dist/renderer/assets/settings-api-CY84UMpN.js +0 -2
  189. package/dist/renderer/assets/workbenchSettings-DjELLnSJ.js +0 -8
  190. package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
@@ -1,5 +1,7 @@
1
1
  import { WebContentsView, webContents } from 'electron';
2
2
  import path from 'path';
3
+ import { mainPreloadPath } from '../../utils/paths.js';
4
+ import { applyNavigationHardening } from '../../windows/navigation-hardening.js';
3
5
  import * as layout from '../layout/index.js';
4
6
  import { getDefaultTab } from '../workbench-context.js';
5
7
  /**
@@ -22,9 +24,9 @@ export function createViewManager(ctx) {
22
24
  function destroyViewInternal(view) {
23
25
  if (!view)
24
26
  return;
25
- if (!ctx.mainWindow.isDestroyed()) {
27
+ if (!ctx.windows.mainWindow.isDestroyed()) {
26
28
  try {
27
- ctx.mainWindow.contentView.removeChildView(view);
29
+ ctx.windows.mainWindow.contentView.removeChildView(view);
28
30
  }
29
31
  catch { /* already removed */ }
30
32
  }
@@ -36,21 +38,21 @@ export function createViewManager(ctx) {
36
38
  catch { /* ignore */ }
37
39
  }
38
40
  function applySimulatorBounds(simWidth) {
39
- if (!simulatorView || ctx.mainWindow.isDestroyed())
41
+ if (!simulatorView || ctx.windows.mainWindow.isDestroyed())
40
42
  return;
41
- const [w = 0, h = 0] = ctx.mainWindow.getContentSize();
43
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
42
44
  simulatorView.setBounds(layout.computeSimulatorBounds(w, h, simWidth));
43
45
  }
44
46
  function applySettingsBounds() {
45
- if (!settingsView || ctx.mainWindow.isDestroyed())
47
+ if (!settingsView || ctx.windows.mainWindow.isDestroyed())
46
48
  return;
47
- const [w = 0, h = 0] = ctx.mainWindow.getContentSize();
49
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
48
50
  settingsView.setBounds(layout.computeSettingsBounds(w, h));
49
51
  }
50
52
  function applyPopoverBounds() {
51
- if (!popoverView || ctx.mainWindow.isDestroyed())
53
+ if (!popoverView || ctx.windows.mainWindow.isDestroyed())
52
54
  return;
53
- const [w = 0, h = 0] = ctx.mainWindow.getContentSize();
55
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
54
56
  popoverView.setBounds(layout.computePopoverBounds(w, h));
55
57
  }
56
58
  // ── ViewManager methods ─────────────────────────────────────────────────
@@ -103,7 +105,7 @@ export function createViewManager(ctx) {
103
105
  `).catch(() => { });
104
106
  });
105
107
  if (getDefaultTab(ctx) === 'simulator') {
106
- ctx.mainWindow.contentView.addChildView(simulatorView);
108
+ ctx.windows.mainWindow.contentView.addChildView(simulatorView);
107
109
  simulatorViewAdded = true;
108
110
  applySimulatorBounds(simWidth);
109
111
  }
@@ -124,7 +126,7 @@ export function createViewManager(ctx) {
124
126
  if (!simulatorView)
125
127
  return;
126
128
  if (!simulatorViewAdded) {
127
- ctx.mainWindow.contentView.addChildView(simulatorView);
129
+ ctx.windows.mainWindow.contentView.addChildView(simulatorView);
128
130
  simulatorViewAdded = true;
129
131
  }
130
132
  applySimulatorBounds(simWidth);
@@ -132,7 +134,7 @@ export function createViewManager(ctx) {
132
134
  function hideSimulator() {
133
135
  if (simulatorView && simulatorViewAdded) {
134
136
  try {
135
- ctx.mainWindow.contentView.removeChildView(simulatorView);
137
+ ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
136
138
  }
137
139
  catch (e) {
138
140
  console.error('[workbench] hideSimulator error', e);
@@ -143,12 +145,20 @@ export function createViewManager(ctx) {
143
145
  async function showSettings() {
144
146
  if (!settingsView) {
145
147
  settingsView = new WebContentsView({
146
- webPreferences: { nodeIntegration: true, contextIsolation: false },
148
+ webPreferences: {
149
+ nodeIntegration: false,
150
+ contextIsolation: true,
151
+ sandbox: false,
152
+ preload: mainPreloadPath,
153
+ },
147
154
  });
155
+ // Overlay loads mainPreloadPath, so the same navigation rules as the
156
+ // main window apply — see navigation-hardening.ts.
157
+ applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
148
158
  await settingsView.webContents.loadFile(path.join(ctx.rendererDir, 'entries/settings/index.html'));
149
159
  }
150
160
  if (!settingsViewAdded) {
151
- ctx.mainWindow.contentView.addChildView(settingsView);
161
+ ctx.windows.mainWindow.contentView.addChildView(settingsView);
152
162
  settingsViewAdded = true;
153
163
  }
154
164
  applySettingsBounds();
@@ -156,7 +166,7 @@ export function createViewManager(ctx) {
156
166
  function hideSettings() {
157
167
  if (settingsView && settingsViewAdded) {
158
168
  try {
159
- ctx.mainWindow.contentView.removeChildView(settingsView);
169
+ ctx.windows.mainWindow.contentView.removeChildView(settingsView);
160
170
  }
161
171
  catch { /* ignore */ }
162
172
  settingsViewAdded = false;
@@ -165,11 +175,18 @@ export function createViewManager(ctx) {
165
175
  function showPopover(data) {
166
176
  hidePopover();
167
177
  const popover = new WebContentsView({
168
- webPreferences: { nodeIntegration: true, contextIsolation: false },
178
+ webPreferences: {
179
+ nodeIntegration: false,
180
+ contextIsolation: true,
181
+ sandbox: false,
182
+ preload: mainPreloadPath,
183
+ },
169
184
  });
185
+ // Popover overlay loads mainPreloadPath — same navigation rules apply.
186
+ applyNavigationHardening(popover.webContents, ctx.rendererDir);
170
187
  popoverView = popover;
171
188
  popover.setBackgroundColor('#00000000');
172
- ctx.mainWindow.contentView.addChildView(popover);
189
+ ctx.windows.mainWindow.contentView.addChildView(popover);
173
190
  applyPopoverBounds();
174
191
  popover.webContents.once('did-finish-load', () => {
175
192
  ctx.notify.popoverInit(popover, data);
@@ -194,16 +211,6 @@ export function createViewManager(ctx) {
194
211
  function disposeAll() {
195
212
  detachSimulator();
196
213
  }
197
- function logChildViews(_label) {
198
- // Debug helper — intentionally a no-op in production. Kept on the
199
- // ViewManager interface so call sites and tests stay stable.
200
- }
201
- function positionSimulator(simWidth) {
202
- applySimulatorBounds(simWidth);
203
- }
204
- function positionSettings() {
205
- applySettingsBounds();
206
- }
207
214
  function resize(simWidth) {
208
215
  lastSimWidth = simWidth;
209
216
  if (simulatorViewAdded)
@@ -233,10 +240,13 @@ export function createViewManager(ctx) {
233
240
  hidePopover,
234
241
  repositionAll,
235
242
  disposeAll,
236
- logChildViews,
237
- positionSimulator,
238
- positionSettings,
239
243
  getSimulatorWebContentsId: () => simulatorWebContentsId,
244
+ getSimulatorWebContents: () => {
245
+ if (simulatorWebContentsId == null)
246
+ return null;
247
+ const wc = webContents.fromId(simulatorWebContentsId);
248
+ return wc && !wc.isDestroyed() ? wc : null;
249
+ },
240
250
  getLastSimWidth: () => lastSimWidth,
241
251
  isSimulatorAdded: () => simulatorViewAdded,
242
252
  hasSimulatorView: () => simulatorView !== null,
@@ -247,6 +257,20 @@ export function createViewManager(ctx) {
247
257
  return null;
248
258
  return settingsView.webContents;
249
259
  },
260
+ getSettingsWebContentsId: () => {
261
+ if (!settingsView)
262
+ return null;
263
+ if (settingsView.webContents.isDestroyed())
264
+ return null;
265
+ return settingsView.webContents.id;
266
+ },
267
+ getPopoverWebContentsId: () => {
268
+ if (!popoverView)
269
+ return null;
270
+ if (popoverView.webContents.isDestroyed())
271
+ return null;
272
+ return popoverView.webContents.id;
273
+ },
250
274
  resize,
251
275
  setVisible,
252
276
  };
@@ -0,0 +1,24 @@
1
+ import type { BrowserWindow } from 'electron';
2
+ /**
3
+ * Owns every top-level BrowserWindow the workbench is responsible for.
4
+ *
5
+ * This service is the single source of truth for the main and settings
6
+ * windows; callers reach them via `ctx.windows.mainWindow` /
7
+ * `ctx.windows.settingsWindow`.
8
+ */
9
+ export interface WindowService {
10
+ /** The primary devtools window. Created during app bootstrap. */
11
+ readonly mainWindow: BrowserWindow;
12
+ /** Standalone workbench-settings BrowserWindow, or null when closed. */
13
+ readonly settingsWindow: BrowserWindow | null;
14
+ /** Update the tracked settings window (null when closed/destroyed). */
15
+ setSettingsWindow(win: BrowserWindow | null): void;
16
+ /** Close the settings window if it is open and clear the reference. */
17
+ closeSettingsWindow(): void;
18
+ /** True when `webContentsId` belongs to the (alive) main window renderer. */
19
+ isMainSender(webContentsId: number): boolean;
20
+ /** True when `webContentsId` belongs to the (alive) settings window renderer. */
21
+ isSettingsWindowSender(webContentsId: number): boolean;
22
+ }
23
+ export declare function createWindowService(mainWindow: BrowserWindow): WindowService;
24
+ //# sourceMappingURL=window-service.d.ts.map
@@ -0,0 +1,26 @@
1
+ export function createWindowService(mainWindow) {
2
+ let settingsWin = null;
3
+ return {
4
+ get mainWindow() {
5
+ return mainWindow;
6
+ },
7
+ get settingsWindow() {
8
+ return settingsWin;
9
+ },
10
+ setSettingsWindow(win) {
11
+ settingsWin = win;
12
+ },
13
+ closeSettingsWindow() {
14
+ if (settingsWin && !settingsWin.isDestroyed())
15
+ settingsWin.close();
16
+ settingsWin = null;
17
+ },
18
+ isMainSender(id) {
19
+ return !mainWindow.isDestroyed() && mainWindow.webContents.id === id;
20
+ },
21
+ isSettingsWindowSender(id) {
22
+ return !!settingsWin && !settingsWin.isDestroyed() && settingsWin.webContents.id === id;
23
+ },
24
+ };
25
+ }
26
+ //# sourceMappingURL=window-service.js.map
@@ -1,20 +1,24 @@
1
1
  import type { BrowserWindow } from 'electron';
2
2
  import type { CompilationAdapter } from '../../shared/types.js';
3
+ import { DisposableRegistry } from '../utils/disposable.js';
4
+ import type { SenderPolicy } from '../utils/ipc-registry.js';
3
5
  import { type RendererNotifier } from './notifications/renderer-notifier.js';
4
6
  import { type ViewManager } from './views/view-manager.js';
7
+ import { type WindowService } from './window-service.js';
5
8
  import { type WorkspaceService } from './workspace/workspace-service.js';
9
+ import { sanitizeTemplates } from './projects/templates.js';
10
+ import type { ProjectsProvider, ProjectTemplate } from './projects/types.js';
11
+ import type { CustomCreateProjectDialogResult } from '../../shared/types.js';
6
12
  /**
7
13
  * Shared mutable state for the workbench application.
8
14
  * Passed to each IPC module so they can read/write shared state without closures.
9
15
  */
10
16
  export interface WorkbenchContext {
11
- mainWindow: BrowserWindow;
12
17
  adapter: CompilationAdapter;
13
18
  /** Absolute path to the preload script loaded into the simulator webview */
14
19
  preloadPath: string;
15
20
  /** Absolute path to the renderer dist directory */
16
21
  rendererDir: string;
17
- workbenchSettingsWindow: BrowserWindow | null;
18
22
  /** Built-in panel IDs to display (default: all) */
19
23
  panels: string[];
20
24
  /** Custom API namespace names (e.g. ['qd']) passed to the simulator */
@@ -37,10 +41,44 @@ export interface WorkbenchContext {
37
41
  };
38
42
  /** Unified lifecycle manager for all overlay WebContentsViews */
39
43
  views: ViewManager;
44
+ /** Owns BrowserWindow instances (main + settings) and their sender-id checks. */
45
+ windows: WindowService;
40
46
  /** Unified main → renderer event dispatcher */
41
47
  notify: RendererNotifier;
42
48
  /** Single source of truth for project + session + per-project settings */
43
49
  workspace: WorkspaceService;
50
+ /**
51
+ * Pluggable backend for the project list. Defaults to a LocalProjectsProvider
52
+ * that persists to `<userData>/dimina-projects.json`. Hosts can inject a
53
+ * custom implementation (e.g. read from a remote workspace).
54
+ */
55
+ projectsProvider: ProjectsProvider;
56
+ /**
57
+ * Merged template catalog used by the create-project flow. Combines the
58
+ * built-in templates with `WorkbenchAppConfig.projectTemplates` according
59
+ * to `WorkbenchAppConfig.builtinTemplates`. Keeps any `generate`
60
+ * functions intact — for IPC delivery, `sanitizeTemplates` is applied at
61
+ * the IPC boundary.
62
+ */
63
+ projectTemplates: ProjectTemplate[];
64
+ /**
65
+ * Optional host hook for the "新建项目" dialog. When present, the
66
+ * `projects:openCreateDialog` IPC returns whatever this hook resolves to;
67
+ * when absent, the IPC returns null and the renderer shows the built-in
68
+ * dialog.
69
+ */
70
+ customCreateProjectDialog?: (ctx: {
71
+ parentWindow: BrowserWindow;
72
+ templates: ProjectTemplate[];
73
+ }) => Promise<CustomCreateProjectDialogResult>;
74
+ /**
75
+ * Trust predicate consulted by `IpcRegistry` for every incoming IPC.
76
+ * Resolves the currently-trusted senders (main renderer + overlays)
77
+ * lazily so it stays correct as windows/views come and go.
78
+ */
79
+ senderPolicy: SenderPolicy;
80
+ /** Aggregates dispose handlers for every IPC handler, listener, watcher, and CDP session registered by the workbench. */
81
+ registry: DisposableRegistry;
44
82
  }
45
83
  export interface CreateContextOptions {
46
84
  mainWindow: BrowserWindow;
@@ -52,8 +90,17 @@ export interface CreateContextOptions {
52
90
  appName?: string;
53
91
  toolbarActions?: WorkbenchContext['toolbarActions'];
54
92
  brandingProvider?: WorkbenchContext['brandingProvider'];
93
+ /** Host-supplied project list backend. Defaults to LocalProjectsProvider. */
94
+ projectsProvider?: ProjectsProvider;
95
+ /** Templates injected by the host; same-id overrides a built-in. */
96
+ projectTemplates?: ProjectTemplate[];
97
+ /** Built-in template policy (default 'all'). */
98
+ builtinTemplates?: 'all' | 'none' | readonly string[];
99
+ /** Host-supplied "新建项目" dialog hook. */
100
+ customCreateProjectDialog?: WorkbenchContext['customCreateProjectDialog'];
55
101
  }
56
102
  export declare function hasBuiltinPanel(ctx: Pick<WorkbenchContext, 'panels'>, panelId: string): boolean;
57
103
  export declare function getDefaultTab(ctx: Pick<WorkbenchContext, 'panels'>): string;
58
104
  export declare function createWorkbenchContext(opts: CreateContextOptions): WorkbenchContext;
105
+ export { sanitizeTemplates };
59
106
  //# sourceMappingURL=workbench-context.d.ts.map
@@ -1,7 +1,13 @@
1
+ import { DisposableRegistry } from '../utils/disposable.js';
2
+ import { createWorkbenchSenderPolicy } from '../utils/sender-policy.js';
1
3
  import { defaultAdapter } from './default-adapter.js';
2
4
  import { createRendererNotifier, } from './notifications/renderer-notifier.js';
3
5
  import { createViewManager } from './views/view-manager.js';
6
+ import { createWindowService } from './window-service.js';
4
7
  import { createWorkspaceService, } from './workspace/workspace-service.js';
8
+ import { createLocalProjectsProvider } from './projects/local-provider.js';
9
+ import { resolveTemplates, sanitizeTemplates } from './projects/templates.js';
10
+ import { BUILTIN_TEMPLATES } from './projects/builtin-templates.js';
5
11
  export function hasBuiltinPanel(ctx, panelId) {
6
12
  return ctx.panels.includes(panelId);
7
13
  }
@@ -14,7 +20,6 @@ export function getDefaultTab(ctx) {
14
20
  }
15
21
  export function createWorkbenchContext(opts) {
16
22
  const ctx = {
17
- mainWindow: opts.mainWindow,
18
23
  adapter: opts.adapter ?? defaultAdapter,
19
24
  preloadPath: opts.preloadPath,
20
25
  rendererDir: opts.rendererDir,
@@ -23,11 +28,19 @@ export function createWorkbenchContext(opts) {
23
28
  appName: opts.appName ?? 'Dimina DevTools',
24
29
  toolbarActions: opts.toolbarActions,
25
30
  brandingProvider: opts.brandingProvider,
26
- workbenchSettingsWindow: null,
27
31
  };
32
+ ctx.registry = new DisposableRegistry();
33
+ ctx.windows = createWindowService(opts.mainWindow);
28
34
  ctx.views = createViewManager(ctx);
29
35
  ctx.notify = createRendererNotifier(ctx);
36
+ ctx.projectsProvider = opts.projectsProvider ?? createLocalProjectsProvider();
37
+ ctx.projectTemplates = resolveTemplates(BUILTIN_TEMPLATES, opts.projectTemplates ?? [], opts.builtinTemplates ?? 'all');
38
+ ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
30
39
  ctx.workspace = createWorkspaceService(ctx);
40
+ ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
31
41
  return ctx;
32
42
  }
43
+ // Re-export at this stable module path so callers don't have to know about
44
+ // the projects/ subtree layout.
45
+ export { sanitizeTemplates };
33
46
  //# sourceMappingURL=workbench-context.js.map
@@ -25,10 +25,11 @@ export interface OpenProjectResult {
25
25
  * rather than touching the repository or adapter directly.
26
26
  */
27
27
  export interface WorkspaceService {
28
- listProjects(): Project[];
29
- addProject(dirPath: string): Project;
30
- removeProject(dirPath: string): void;
31
- validateProjectDir(dirPath: string): string | null;
28
+ listProjects(): Promise<Project[]>;
29
+ addProject(dirPath: string): Promise<Project>;
30
+ removeProject(dirPath: string): Promise<void>;
31
+ hasProject(dirPath: string): Promise<boolean>;
32
+ validateProjectDir(dirPath: string): Promise<string | null>;
32
33
  openProject(projectPath: string): Promise<OpenProjectResult>;
33
34
  closeProject(): Promise<void>;
34
35
  getSession(): {
@@ -38,9 +39,11 @@ export interface WorkspaceService {
38
39
  } | null;
39
40
  getProjectPath(): string;
40
41
  hasActiveSession(): boolean;
42
+ captureThumbnail(projectPath: string): Promise<string | null>;
43
+ getThumbnail(projectPath: string): Promise<string | null>;
41
44
  getProjectPages(projectPath: string): ProjectPages;
42
- getCompileConfig(projectPath: string): CompileConfig;
43
- saveCompileConfig(projectPath: string, config: CompileConfig): void;
45
+ getCompileConfig(projectPath: string): Promise<CompileConfig>;
46
+ saveCompileConfig(projectPath: string, config: CompileConfig): Promise<void>;
44
47
  getProjectSettings(projectPath: string): ProjectSettings;
45
48
  updateProjectSettings(projectPath: string, patch: Partial<ProjectSettings>): void;
46
49
  }
@@ -1,11 +1,13 @@
1
1
  import * as repo from '../projects/project-repository.js';
2
+ import { DEFAULT_COMPILE_CONFIG } from '../projects/types.js';
2
3
  import { clearSimulatorServicewechatReferer, setSimulatorServicewechatReferer, } from '../simulator/referer.js';
4
+ import { loadWorkbenchSettings } from '../settings/index.js';
3
5
  /** Build a workspace service bound to the given workbench context. */
4
6
  export function createWorkspaceService(ctx) {
5
7
  let currentSession = null;
6
8
  let currentProjectPath = '';
7
- function sendStatus(status, message) {
8
- ctx.notify.projectStatus({ status, message });
9
+ function sendStatus(status, message, hotReload) {
10
+ ctx.notify.projectStatus(hotReload ? { status, message, hotReload: true } : { status, message });
9
11
  }
10
12
  function bestEffort(label, fn) {
11
13
  try {
@@ -36,11 +38,25 @@ export function createWorkspaceService(ctx) {
36
38
  setSimulatorServicewechatReferer(appInfo.appId, typeof appInfo.version === 'string' ? appInfo.version : undefined);
37
39
  }
38
40
  }
41
+ // Delegate to the host-injected (or default LocalProjectsProvider)
42
+ // ProjectsProvider on the context. When the host omits an optional method,
43
+ // we apply a safe documented default — never silently fall back to
44
+ // `repo.*`, because a remote provider's project paths are not on the local
45
+ // filesystem.
46
+ const provider = ctx.projectsProvider;
39
47
  return {
40
- listProjects: () => repo.listProjects(),
41
- addProject: (dirPath) => repo.addProject(dirPath),
42
- removeProject: (dirPath) => repo.removeProject(dirPath),
43
- validateProjectDir: (dirPath) => repo.validateProjectDir(dirPath),
48
+ listProjects: async () => provider.listProjects(),
49
+ addProject: async (dirPath) => provider.addProject(dirPath),
50
+ removeProject: async (dirPath) => {
51
+ await provider.removeProject(dirPath);
52
+ },
53
+ hasProject: async (dirPath) => {
54
+ const projects = await provider.listProjects();
55
+ return projects.some((p) => p.path === dirPath);
56
+ },
57
+ validateProjectDir: async (dirPath) => provider.validateProjectDir
58
+ ? provider.validateProjectDir(dirPath)
59
+ : null,
44
60
  async openProject(projectPath) {
45
61
  clearSimulatorServicewechatReferer();
46
62
  await disposeSession();
@@ -52,18 +68,22 @@ export function createWorkspaceService(ctx) {
52
68
  // `fetch(…).then(r=>r.text()).then(JSON.parse)` pipeline dies on the
53
69
  // dev server's HTML SPA fallback with
54
70
  // `SyntaxError: Unexpected token '<', "<!doctype "…`.
55
- const dirError = repo.validateProjectDir(projectPath);
71
+ const dirError = provider.validateProjectDir
72
+ ? await provider.validateProjectDir(projectPath)
73
+ : null;
56
74
  if (dirError) {
57
75
  sendStatus('error', dirError);
58
76
  return { success: false, error: dirError };
59
77
  }
60
78
  sendStatus('compiling', '正在编译...');
79
+ const { compile } = loadWorkbenchSettings();
61
80
  let session;
62
81
  try {
63
82
  session = await ctx.adapter.openProject({
64
83
  projectPath,
65
84
  sourcemap: true,
66
- onRebuild: () => sendStatus('ready', '编译完成,已热更新'),
85
+ watch: compile.watch,
86
+ onRebuild: () => sendStatus('ready', '编译完成,已热更新', true),
67
87
  onBuildError: (err) => sendStatus('error', String(err)),
68
88
  });
69
89
  }
@@ -74,7 +94,10 @@ export function createWorkspaceService(ctx) {
74
94
  }
75
95
  currentSession = session;
76
96
  currentProjectPath = projectPath;
77
- bestEffort('updateLastOpened', () => repo.updateLastOpened(projectPath));
97
+ bestEffort('updateLastOpened', () => {
98
+ if (provider.updateLastOpened)
99
+ provider.updateLastOpened(projectPath);
100
+ });
78
101
  bestEffort('sendStatus', () => sendStatus('ready', '编译完成'));
79
102
  bestEffort('applyReferer', () => applyRefererFromSession(session));
80
103
  return {
@@ -92,9 +115,46 @@ export function createWorkspaceService(ctx) {
92
115
  getSession: () => currentSession,
93
116
  getProjectPath: () => currentProjectPath,
94
117
  hasActiveSession: () => currentSession !== null,
118
+ async captureThumbnail(projectPath) {
119
+ const wc = ctx.views.getSimulatorWebContents();
120
+ if (!wc)
121
+ return null;
122
+ try {
123
+ const image = await wc.capturePage();
124
+ const dataUrl = `data:image/png;base64,${image.toPNG().toString('base64')}`;
125
+ if (provider.saveThumbnail) {
126
+ await provider.saveThumbnail(projectPath, dataUrl);
127
+ }
128
+ // Always hand the renderer back the freshly-captured frame so the
129
+ // UI updates immediately even if the host's saveThumbnail is
130
+ // async or stores out-of-band.
131
+ return dataUrl;
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ },
137
+ async getThumbnail(projectPath) {
138
+ if (provider.getThumbnail) {
139
+ return (await provider.getThumbnail(projectPath)) ?? null;
140
+ }
141
+ return null;
142
+ },
143
+ // `getProjectPages` reads the project's own `app.json` from disk and is
144
+ // independent of the project registry — it stays on the repo helper.
95
145
  getProjectPages: (projectPath) => repo.getProjectPages(projectPath),
96
- getCompileConfig: (projectPath) => repo.getCompileConfig(projectPath),
97
- saveCompileConfig: (projectPath, config) => repo.saveCompileConfig(projectPath, config),
146
+ getCompileConfig: async (projectPath) => (provider.getCompileConfig
147
+ ? await provider.getCompileConfig(projectPath)
148
+ : DEFAULT_COMPILE_CONFIG),
149
+ saveCompileConfig: async (projectPath, config) => {
150
+ if (provider.saveCompileConfig) {
151
+ await provider.saveCompileConfig(projectPath, config);
152
+ }
153
+ // No persistence when the host opts out; the renderer's edits then
154
+ // do not survive a reload, matching the documented contract.
155
+ },
156
+ // Per-project settings (uploadWithSourceMap etc.) live in the project's
157
+ // own `project.config.json`, not the registry — keep direct repo calls.
98
158
  getProjectSettings: (projectPath) => repo.getProjectSettings(projectPath),
99
159
  updateProjectSettings: (projectPath, patch) => repo.updateProjectSettings(projectPath, patch),
100
160
  };
@@ -0,0 +1,23 @@
1
+ import { type SimulatorApiHandler } from './services/simulator/custom-apis.js';
2
+ export type { SimulatorApiHandler } from './services/simulator/custom-apis.js';
3
+ /**
4
+ * Register a handler for a simulator API callable from mini-program code.
5
+ *
6
+ * The handler runs in the Electron main process (Node.js context) and may
7
+ * use any main-process capability (filesystem, network, native deps). When
8
+ * the mini-program invokes `wx.<name>(params)` inside the simulator, the
9
+ * simulator forwards the call over IPC and resolves to the handler's return
10
+ * value. Errors thrown by the handler propagate back as a rejection.
11
+ *
12
+ * Re-registering the same name silently replaces the previous handler.
13
+ * The returned disposer removes only the registration it created — if the
14
+ * name was overwritten by a later `registerSimulatorApi`, calling the
15
+ * earlier disposer is a no-op.
16
+ *
17
+ * @example
18
+ * registerSimulatorApi('myCompany.login', async ({ username }) => {
19
+ * return await callInternalAuth(username)
20
+ * })
21
+ */
22
+ export declare function registerSimulatorApi(name: string, handler: SimulatorApiHandler): () => void;
23
+ //# sourceMappingURL=simulator-apis.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { simulatorApiRegistry } from './services/simulator/custom-apis.js';
2
+ /**
3
+ * Register a handler for a simulator API callable from mini-program code.
4
+ *
5
+ * The handler runs in the Electron main process (Node.js context) and may
6
+ * use any main-process capability (filesystem, network, native deps). When
7
+ * the mini-program invokes `wx.<name>(params)` inside the simulator, the
8
+ * simulator forwards the call over IPC and resolves to the handler's return
9
+ * value. Errors thrown by the handler propagate back as a rejection.
10
+ *
11
+ * Re-registering the same name silently replaces the previous handler.
12
+ * The returned disposer removes only the registration it created — if the
13
+ * name was overwritten by a later `registerSimulatorApi`, calling the
14
+ * earlier disposer is a no-op.
15
+ *
16
+ * @example
17
+ * registerSimulatorApi('myCompany.login', async ({ username }) => {
18
+ * return await callInternalAuth(username)
19
+ * })
20
+ */
21
+ export function registerSimulatorApi(name, handler) {
22
+ return simulatorApiRegistry.register(name, handler);
23
+ }
24
+ //# sourceMappingURL=simulator-apis.js.map
@@ -0,0 +1,13 @@
1
+ export interface Disposable {
2
+ dispose(): void | Promise<void>;
3
+ }
4
+ export type DisposeFn = () => void | Promise<void>;
5
+ export declare function toDisposable(fn: DisposeFn): Disposable;
6
+ export declare class DisposableRegistry implements Disposable {
7
+ private entries;
8
+ private _disposed;
9
+ add(d: Disposable | DisposeFn): Disposable;
10
+ disposeAll(): Promise<void>;
11
+ dispose(): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=disposable.d.ts.map
@@ -0,0 +1,60 @@
1
+ export function toDisposable(fn) {
2
+ let done = false;
3
+ return {
4
+ dispose() {
5
+ if (done)
6
+ return;
7
+ done = true;
8
+ return fn();
9
+ },
10
+ };
11
+ }
12
+ export class DisposableRegistry {
13
+ entries = [];
14
+ _disposed = false;
15
+ add(d) {
16
+ if (this._disposed) {
17
+ throw new Error('cannot add to disposed registry');
18
+ }
19
+ const fn = typeof d === 'function' ? d : () => d.dispose();
20
+ const entry = { fn, released: false };
21
+ this.entries.push(entry);
22
+ return {
23
+ dispose: () => {
24
+ if (entry.released)
25
+ return;
26
+ entry.released = true;
27
+ const i = this.entries.indexOf(entry);
28
+ if (i >= 0)
29
+ this.entries.splice(i, 1);
30
+ return fn();
31
+ },
32
+ };
33
+ }
34
+ async disposeAll() {
35
+ if (this._disposed)
36
+ return;
37
+ this._disposed = true;
38
+ const items = this.entries.slice().reverse();
39
+ this.entries = [];
40
+ const errors = [];
41
+ for (const entry of items) {
42
+ if (entry.released)
43
+ continue;
44
+ entry.released = true;
45
+ try {
46
+ await entry.fn();
47
+ }
48
+ catch (e) {
49
+ errors.push(e);
50
+ }
51
+ }
52
+ if (errors.length > 0) {
53
+ throw new AggregateError(errors, 'DisposableRegistry encountered errors during disposeAll');
54
+ }
55
+ }
56
+ dispose() {
57
+ return this.disposeAll();
58
+ }
59
+ }
60
+ //# sourceMappingURL=disposable.js.map