@dimina-kit/devtools 0.3.2-dev.20260522131311 → 0.3.2-dev.20260522142649
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -155
- package/dist/main/api.d.ts +3 -11
- package/dist/main/api.js +2 -13
- package/dist/main/app/app.d.ts +14 -1
- package/dist/main/app/app.js +93 -2
- package/dist/main/index.bundle.js +140 -52
- package/dist/main/ipc/app.d.ts +1 -1
- package/dist/main/ipc/app.js +3 -0
- package/dist/main/ipc/simulator.d.ts +1 -1
- package/dist/main/ipc/simulator.js +2 -3
- package/dist/main/ipc/toolbar.d.ts +1 -1
- package/dist/main/ipc/toolbar.js +14 -4
- package/dist/main/services/layout/index.d.ts +4 -10
- package/dist/main/services/layout/index.js +11 -19
- package/dist/main/services/module.d.ts +1 -2
- package/dist/main/services/simulator/custom-apis.d.ts +0 -1
- package/dist/main/services/simulator/custom-apis.js +0 -1
- package/dist/main/services/toolbar/toolbar-store.d.ts +23 -0
- package/dist/main/services/toolbar/toolbar-store.js +23 -0
- package/dist/main/services/views/view-manager.d.ts +6 -0
- package/dist/main/services/views/view-manager.js +6 -3
- package/dist/main/services/workbench-context.d.ts +40 -14
- package/dist/main/services/workbench-context.js +6 -1
- package/dist/main/utils/disposable.d.ts +5 -0
- package/dist/main/utils/disposable.js +7 -0
- package/dist/main/utils/ipc-registry.js +5 -1
- package/dist/main/utils/sender-policy.d.ts +4 -1
- package/dist/main/utils/sender-policy.js +6 -0
- package/dist/preload/runtime/custom-apis.js +72 -1
- package/dist/preload/windows/simulator.js +41 -1
- package/dist/renderer/assets/index-4lkyF644.js +46 -0
- package/dist/renderer/assets/{input-CoWcfa8u.js → input-D4byFOLS.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-Cd2xPrsa.js → ipc-transport-BzPzxPwf.js} +2 -2
- package/dist/renderer/assets/{popover-Nq5gT9DO.js → popover-cxKrg_os.js} +2 -2
- package/dist/renderer/assets/{select-By5KH-UG.js → select-BgSkkdHW.js} +2 -2
- package/dist/renderer/assets/{settings-api-Do0_Smq1.js → settings-api-BXCw9vOZ.js} +2 -2
- package/dist/renderer/assets/{settings-Dj_N70qR.js → settings-oD2K1jdT.js} +2 -2
- package/dist/renderer/assets/{workbenchSettings-CazSLGYt.js → workbenchSettings-CTNBpTft.js} +2 -2
- package/dist/renderer/entries/main/index.html +4 -4
- package/dist/renderer/entries/popover/index.html +4 -4
- package/dist/renderer/entries/settings/index.html +4 -4
- package/dist/renderer/entries/workbench-settings/index.html +3 -3
- package/dist/shared/ipc-channels.d.ts +3 -2
- package/dist/shared/ipc-channels.js +4 -3
- package/dist/shared/ipc-schemas.d.ts +2 -0
- package/dist/shared/ipc-schemas.js +2 -0
- package/dist/shared/types.d.ts +55 -3
- package/dist/simulator/assets/simulator-CILbo0Ji.js +9 -0
- package/dist/simulator/simulator.html +1 -1
- package/package.json +3 -35
- package/dist/main/simulator-apis.d.ts +0 -23
- package/dist/main/simulator-apis.js +0 -24
- package/dist/renderer/assets/index-D8VFYBh-.js +0 -46
- package/dist/simulator/assets/simulator-DyXpdHtT.js +0 -9
package/README.md
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# Dimina DevTools
|
|
2
2
|
|
|
3
|
-
基于 Electron
|
|
3
|
+
基于 Electron 的小程序开发者工具。提供模拟器、Chrome DevTools 面板、WXML/AppData/Storage 面板、编译配置等功能。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
下游 host 通过 `createWorkbenchApp()` 集成并定制 devtools。扩展模型设计见 [`docs/extension-model.md`](docs/extension-model.md)。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 两种接入方式
|
|
10
10
|
|
|
11
|
-
| 级别 | 入口 | 控制力
|
|
12
|
-
| ------------ | ---------------------- |
|
|
13
|
-
| **零配置** | `launch()` | 无
|
|
14
|
-
| **配置驱动** | `createWorkbenchApp()` |
|
|
15
|
-
| **模块组装** | 单独 import 各模块 | 完全控制 | 深度定制、自定义 IPC |
|
|
11
|
+
| 级别 | 入口 | 控制力 | 适合场景 |
|
|
12
|
+
| ------------ | ---------------------- | --------------------- | -------------- |
|
|
13
|
+
| **零配置** | `launch()` | 无 | 直接运行 |
|
|
14
|
+
| **配置驱动** | `createWorkbenchApp()` | 配置 Provider + 扩展点 | 品牌化、深度定制 |
|
|
16
15
|
|
|
17
16
|
### 零配置
|
|
18
17
|
|
|
@@ -23,124 +22,57 @@ launch()
|
|
|
23
22
|
|
|
24
23
|
### 配置驱动
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
import { createWorkbenchApp } from '@dimina-kit/devtools/app'
|
|
28
|
-
|
|
29
|
-
createWorkbenchApp({
|
|
30
|
-
appName: 'My DevTools',
|
|
31
|
-
adapter: myAdapter,
|
|
32
|
-
panels: ['console', 'storage'], // 只显示这两个内置面板
|
|
33
|
-
modules: { settings: false }, // 禁用设置面板模块
|
|
34
|
-
window: { width: 1400, height: 900 },
|
|
35
|
-
}).start()
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### 宿主扩展(推荐用于品牌化 / 自定义功能)
|
|
39
|
-
|
|
40
|
-
基于 `createWorkbenchApp()` 的配置驱动模式,通过 hooks 注入宿主逻辑,无需手动组装模块:
|
|
25
|
+
扩展点分两类:**配置 Provider**(构造期、一对一,替换某个内置能力)走 `createWorkbenchApp` 配置字段;**Contribution**(`onSetup` 期、一对多,添加多个同类条目)走 `onSetup(instance)` 上的 typed 方法。所有 Contribution 都 per-context,随 context 自动销毁。
|
|
41
26
|
|
|
42
27
|
```typescript
|
|
43
28
|
import { suppressEpipe } from '@dimina-kit/devtools/bootstrap'
|
|
44
29
|
import { createWorkbenchApp } from '@dimina-kit/devtools/app'
|
|
45
30
|
import { rendererDir } from '@dimina-kit/devtools/paths'
|
|
46
|
-
import {
|
|
31
|
+
import { Menu } from 'electron'
|
|
47
32
|
|
|
48
33
|
suppressEpipe()
|
|
49
34
|
|
|
50
35
|
createWorkbenchApp({
|
|
36
|
+
// ── 配置 Provider(构造期,一对一)──
|
|
51
37
|
appName: '我的开发工具',
|
|
52
38
|
adapter: myAdapter,
|
|
53
39
|
preloadPath: '/path/to/my-preload.js',
|
|
54
40
|
rendererDir,
|
|
55
41
|
apiNamespaces: ['my'],
|
|
42
|
+
headerHeight: 72,
|
|
56
43
|
brandingProvider: () => ({ appName: '我的开发工具' }),
|
|
57
|
-
toolbarActions: () => [
|
|
58
|
-
{ id: 'deploy', label: '发布' },
|
|
59
|
-
{ id: 'preview', label: '预览' },
|
|
60
|
-
],
|
|
61
44
|
icon: '/path/to/icon.png',
|
|
62
|
-
menuBuilder: (mainWindow,
|
|
63
|
-
//
|
|
45
|
+
menuBuilder: (mainWindow, menuCtx) => {
|
|
46
|
+
// menuCtx 是 MenuContext(只读 menu 相关状态)
|
|
64
47
|
Menu.setApplicationMenu(/* ... */)
|
|
65
48
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
49
|
+
|
|
50
|
+
// ── Contribution(onSetup 期,一对多)──
|
|
51
|
+
onSetup: (instance) => {
|
|
52
|
+
// simulator 自定义 API:per-context
|
|
53
|
+
instance.registerSimulatorApi('login', (params) => myLogin(params))
|
|
54
|
+
|
|
55
|
+
// 工具栏:整表替换,随 host 状态(如登录态)重算时再 set 一次
|
|
56
|
+
const refreshToolbar = () => instance.toolbar.set([
|
|
57
|
+
{ id: 'deploy', label: '发布', handler: () => deploy() },
|
|
58
|
+
{ id: 'preview', label: '预览', handler: () => preview() },
|
|
59
|
+
])
|
|
60
|
+
refreshToolbar()
|
|
61
|
+
|
|
62
|
+
// 自定义 IPC:经 gated 的 IpcRegistry,不再裸 ipcMain.handle
|
|
63
|
+
instance.ipc.handle('my:action', () => collectStats())
|
|
64
|
+
|
|
65
|
+
// host 自己的弹窗须注册为受信 sender 后才能调 instance.ipc
|
|
66
|
+
// const win = createDialogWindow(/* ... */)
|
|
67
|
+
// instance.registerTrustedWindow(win)
|
|
71
68
|
},
|
|
72
69
|
onBeforeClose: ({ context }) => {
|
|
73
|
-
//
|
|
74
|
-
// session 关闭和 view 销毁由框架自动处理
|
|
70
|
+
// 窗口关闭前的自定义清理;session 关闭和 view 销毁由框架自动处理
|
|
75
71
|
},
|
|
76
72
|
}).start()
|
|
77
73
|
```
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
import { app, globalShortcut } from 'electron'
|
|
83
|
-
import path from 'path'
|
|
84
|
-
import { createMainWindow } from '@dimina-kit/devtools/create-window'
|
|
85
|
-
import { createWorkbenchContext } from '@dimina-kit/devtools/context'
|
|
86
|
-
import { registerAppIpc } from '@dimina-kit/devtools'
|
|
87
|
-
import { registerSimulatorIpc } from '@dimina-kit/devtools/ipc-simulator'
|
|
88
|
-
import { registerPanelsIpc } from '@dimina-kit/devtools/ipc-panels'
|
|
89
|
-
import { registerToolbarIpc } from '@dimina-kit/devtools/ipc-toolbar'
|
|
90
|
-
import { registerPopoverIpc } from '@dimina-kit/devtools/ipc-popover'
|
|
91
|
-
import { registerProjectsIpc } from '@dimina-kit/devtools/ipc-projects'
|
|
92
|
-
import { registerSessionIpc } from '@dimina-kit/devtools/ipc-session'
|
|
93
|
-
// registerSettingsIpc 不需要,不导入
|
|
94
|
-
import { rendererDir, defaultPreloadPath } from '@dimina-kit/devtools/paths'
|
|
95
|
-
|
|
96
|
-
app.whenReady().then(() => {
|
|
97
|
-
const mainWindow = createMainWindow({
|
|
98
|
-
title: 'My DevTools',
|
|
99
|
-
indexHtml: path.join(rendererDir, 'entries/main/index.html'),
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
const ctx = createWorkbenchContext({
|
|
103
|
-
mainWindow,
|
|
104
|
-
adapter: myAdapter,
|
|
105
|
-
preloadPath: defaultPreloadPath,
|
|
106
|
-
rendererDir,
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// registerAppIpc 必装:renderer 启动时调用 app:getPreloadPath / app:getBranding,
|
|
110
|
-
// 缺它会卡在加载阶段。
|
|
111
|
-
registerAppIpc(ctx)
|
|
112
|
-
registerProjectsIpc(ctx)
|
|
113
|
-
registerSessionIpc(ctx)
|
|
114
|
-
registerSimulatorIpc(ctx)
|
|
115
|
-
registerPanelsIpc(ctx)
|
|
116
|
-
registerToolbarIpc(ctx)
|
|
117
|
-
registerPopoverIpc(ctx)
|
|
118
|
-
|
|
119
|
-
// Storage / 元素选区面板的后端 handler 由 setupSimulatorStorage 单独装。
|
|
120
|
-
// 该函数当前**未列入稳定公共 API**(在 `@dimina-kit/devtools` 之外)。
|
|
121
|
-
// 若需要这两个面板,建议改用上方的 `createWorkbenchApp({ onSetup })` 路线,
|
|
122
|
-
// 框架会自动注册;模块组装路线下你只能用不依赖它们的精简面板集。
|
|
123
|
-
|
|
124
|
-
// 添加自定义 IPC — 通过 ctx.workspace 访问会话状态
|
|
125
|
-
ipcMain.handle('my:action', () => {
|
|
126
|
-
console.log('current project:', ctx.workspace.getProjectPath())
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
mainWindow.on('resize', () => ctx.views.repositionAll())
|
|
130
|
-
mainWindow.on('close', async (e) => {
|
|
131
|
-
if (ctx.workspace.hasActiveSession()) {
|
|
132
|
-
e.preventDefault()
|
|
133
|
-
await ctx.workspace.closeProject()
|
|
134
|
-
ctx.notify.windowNavigateBack()
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
app.on('window-all-closed', () => {
|
|
140
|
-
globalShortcut.unregisterAll()
|
|
141
|
-
app.quit()
|
|
142
|
-
})
|
|
143
|
-
```
|
|
75
|
+
> Contribution 注册物全部自动进 `context.registry`,host 无需手写 cleanup。devtools 内部仍用 `WorkbenchModule` 组织内置 IPC 模块,但这是实现细节,不再作为对外接入方式导出。
|
|
144
76
|
|
|
145
77
|
---
|
|
146
78
|
|
|
@@ -251,15 +183,15 @@ src/
|
|
|
251
183
|
|
|
252
184
|
`launch()` 和 `createWorkbenchApp()` 共用:
|
|
253
185
|
|
|
254
|
-
| 字段 | 类型
|
|
255
|
-
| ------------------ |
|
|
256
|
-
| `appName` | `string`
|
|
257
|
-
| `adapter` | `CompilationAdapter`
|
|
258
|
-
| `panels` | `BuiltinPanelId[]`
|
|
259
|
-
| `preloadPath` | `string`
|
|
260
|
-
| `apiNamespaces` | `string[]`
|
|
261
|
-
| `brandingProvider` | `() => { appName }`
|
|
262
|
-
| `
|
|
186
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
187
|
+
| ------------------ | -------------------- | ------------------- | ---------------------------------- |
|
|
188
|
+
| `appName` | `string` | `'Dimina DevTools'` | 窗口标题 |
|
|
189
|
+
| `adapter` | `CompilationAdapter` | 内置 | 项目编译适配器 |
|
|
190
|
+
| `panels` | `BuiltinPanelId[]` | 全部四个 | 显示哪些内置面板 |
|
|
191
|
+
| `preloadPath` | `string` | 内置 | 自定义 preload 脚本路径 |
|
|
192
|
+
| `apiNamespaces` | `string[]` | `[]` | 自定义 API 命名空间(如 `['qd']`) |
|
|
193
|
+
| `brandingProvider` | `() => { appName }` | — | 品牌信息 provider |
|
|
194
|
+
| `headerHeight` | `number` | `40` | 头部栏高度(px),同步下发 main + renderer |
|
|
263
195
|
|
|
264
196
|
### WorkbenchAppConfig(扩展 WorkbenchConfig)
|
|
265
197
|
|
|
@@ -267,16 +199,27 @@ src/
|
|
|
267
199
|
|
|
268
200
|
| 字段 | 类型 | 默认值 | 说明 |
|
|
269
201
|
| --------------- | ------------------------------------------- | ----------- | ------------------------------------------------- |
|
|
270
|
-
| `modules` | `Partial<Record<BuiltinModuleId, boolean>>` | 全部 `true` |
|
|
202
|
+
| `modules` | `Partial<Record<BuiltinModuleId, boolean>>` | 全部 `true` | 开关内置 IPC 模块组 |
|
|
271
203
|
| `rendererDir` | `string` | 内置 | 自定义 renderer HTML 目录 |
|
|
272
204
|
| `icon` | `string` | — | 窗口/任务栏图标路径(macOS 使用 app bundle 图标) |
|
|
273
|
-
| `menuBuilder` | `(mainWindow,
|
|
274
|
-
| `onSetup` | `(instance) => void` | — | 窗口和 context
|
|
205
|
+
| `menuBuilder` | `(mainWindow, menuContext: MenuContext) => void` | 内置菜单 | 自定义菜单构建器;`menuContext` 为收窄后的 `MenuContext`,不含 `registry`/`senderPolicy` 等内部管线 |
|
|
206
|
+
| `onSetup` | `(instance) => void` | — | 窗口和 context 创建后的回调,用于注册 Contribution(见下文)|
|
|
275
207
|
| `onBeforeClose` | `(instance) => void` | — | 窗口关闭前的回调,session 关闭由框架自动处理 |
|
|
276
208
|
| `window` | `WorkbenchWindowConfig` | — | 窗口尺寸覆盖 |
|
|
277
209
|
| `updateChecker` | `UpdateChecker` | — | 自定义更新检查器;提供后启用"检查更新"功能 |
|
|
278
210
|
| `updateOptions` | `{ checkInterval?, initialDelay?, getCurrentVersion? }` | — | 仅当 `updateChecker` 提供时生效,默认 1h / 5s |
|
|
279
211
|
|
|
212
|
+
### onSetup Contribution
|
|
213
|
+
|
|
214
|
+
`onSetup(instance)` 收到的 `instance` 上有一组 typed 注册方法,全部 per-context、自动进 `context.registry`:
|
|
215
|
+
|
|
216
|
+
| 方法 | 说明 |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| `instance.registerSimulatorApi(name, handler)` | 注册 simulator 自定义 API,小程序里 `wx.<name>()` 调用(详见下方"Simulator 自定义 API")。返回 `Disposable` |
|
|
219
|
+
| `instance.toolbar.set(actions)` | 原子整表替换工具栏;`actions` 为 `ToolbarActionInput[]`(`{ id, label, handler }`),`id` 重复抛错。host 状态变化(如登录态)时构造新表再 `set` 一次 |
|
|
220
|
+
| `instance.ipc` | `IpcRegistry` 实例,`instance.ipc.handle(channel, fn)` 注册自定义 IPC;已绑定 `senderPolicy` 网关 |
|
|
221
|
+
| `instance.registerTrustedWindow(win)` | 把 host 自有弹窗 `BrowserWindow` 加入受信 sender 集,否则其发起的 `instance.ipc` 调用会被网关拒绝。窗口关闭即移除 |
|
|
222
|
+
|
|
280
223
|
### 内置面板 ID
|
|
281
224
|
|
|
282
225
|
`'wxml'` \| `'console'` \| `'appdata'` \| `'storage'`
|
|
@@ -505,13 +448,13 @@ launch({ preloadPath: '/absolute/path/to/my-preload.js' })
|
|
|
505
448
|
|
|
506
449
|
## Simulator 自定义 API
|
|
507
450
|
|
|
508
|
-
|
|
451
|
+
下游通过 `onSetup` 里的 `instance.registerSimulatorApi(name, handler)` 把业务 API 注入到模拟器内运行的小程序。注册 per-context,随 context 销毁。Handler 在 Electron **主进程**执行,能用任何 Node 能力(fs、net、原生模块、持久状态);模拟器侧拿到 `wx.<name>(params)` 调用后通过 IPC 转发到主进程,await 返回结果,handler 抛错则原型回传给小程序。
|
|
509
452
|
|
|
510
|
-
> ⚠️ 使用本功能要求模拟器 preload 调用 `installCustomApisBridge()`(详见上方 "Preload Instrumentation")。内置 preload
|
|
453
|
+
> ⚠️ 使用本功能要求模拟器 preload 调用 `installCustomApisBridge()`(详见上方 "Preload Instrumentation")。内置 preload 已包含此调用;若用了自定义 preload 又漏装,注册的 API 在小程序运行时**没有任何反应**,simulator 端会有 `console.warn`。
|
|
511
454
|
|
|
512
455
|
### 命名约定
|
|
513
456
|
|
|
514
|
-
`name` 是 dimina service 层 `invokeAPI(name, opts)` 用的**裸名**,不带 `wx.` 前缀。注册之后小程序里通过 `wx.<name>(...)
|
|
457
|
+
`name` 是 dimina service 层 `invokeAPI(name, opts)` 用的**裸名**,不带 `wx.` 前缀。注册之后小程序里通过 `wx.<name>(...)`(以及在 `apiNamespaces` 配的其它命名空间)调用都会命中:
|
|
515
458
|
|
|
516
459
|
| 小程序里写 | `registerSimulatorApi` 的 name |
|
|
517
460
|
| --- | --- |
|
|
@@ -520,17 +463,19 @@ launch({ preloadPath: '/absolute/path/to/my-preload.js' })
|
|
|
520
463
|
| `wx.getStorage(...)` 覆盖内置 | `'getStorage'`(小心:这会**屏蔽** dimina 容器的实现) |
|
|
521
464
|
|
|
522
465
|
```typescript
|
|
523
|
-
|
|
466
|
+
createWorkbenchApp({
|
|
467
|
+
onSetup: (instance) => {
|
|
468
|
+
// 提供 wx.login 的实现(dimina 容器默认未实现,注册后才能用)
|
|
469
|
+
const dispose = instance.registerSimulatorApi('login', async ({ success }) => {
|
|
470
|
+
// 主进程上下文:可调用内部 HTTP、读取凭证文件、访问原生模块
|
|
471
|
+
const code = await callInternalAuth()
|
|
472
|
+
return { code }
|
|
473
|
+
})
|
|
524
474
|
|
|
525
|
-
//
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const code = await callInternalAuth()
|
|
529
|
-
return { code }
|
|
475
|
+
// 注册物已进 context.registry,随 context 销毁;
|
|
476
|
+
// 提前取消用返回的 dispose()
|
|
477
|
+
},
|
|
530
478
|
})
|
|
531
|
-
|
|
532
|
-
// 不再需要时取消注册(不传也行,进程退出会一起清理)
|
|
533
|
-
dispose()
|
|
534
479
|
```
|
|
535
480
|
|
|
536
481
|
### 行为约定
|
|
@@ -547,27 +492,25 @@ dispose()
|
|
|
547
492
|
|
|
548
493
|
## 模块导出一览
|
|
549
494
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
`@dimina-kit/devtools` 的稳定入口,签名遵循 semver;新版本不会做破坏性改动(除非 major bump)。
|
|
495
|
+
`@dimina-kit/devtools` 的公共入口。`api.ts`(根入口)聚合了所有公共 API,优先从根入口导入。
|
|
553
496
|
|
|
554
497
|
```
|
|
555
498
|
@dimina-kit/devtools launch, createWorkbenchApp, buildDefaultMenu,
|
|
556
499
|
openSettingsWindow, suppressEpipe, setupCdpPort,
|
|
557
500
|
createWorkbenchContext, createMainWindow,
|
|
558
|
-
createViewManager,
|
|
559
|
-
|
|
560
|
-
setHeaderHeight, UpdateManager,
|
|
561
|
-
createGitHubReleaseChecker, ...
|
|
562
|
-
(api.ts 聚合了所有公共 API;优先从根入口导入)
|
|
501
|
+
createViewManager, IpcRegistry,
|
|
502
|
+
UpdateManager, createGitHubReleaseChecker, ...
|
|
563
503
|
@dimina-kit/devtools/launch launch(config?), buildDefaultMenu, openSettingsWindow
|
|
564
504
|
@dimina-kit/devtools/app createWorkbenchApp(config?)
|
|
565
505
|
@dimina-kit/devtools/types TypeScript 类型定义
|
|
566
506
|
@dimina-kit/devtools/bootstrap suppressEpipe(), setupCdpPort()
|
|
507
|
+
@dimina-kit/devtools/context createWorkbenchContext(opts),
|
|
508
|
+
hasBuiltinPanel(ctx, panelId), getDefaultTab(ctx)
|
|
509
|
+
@dimina-kit/devtools/create-window createMainWindow(opts)
|
|
567
510
|
@dimina-kit/devtools/paths rendererDir, defaultPreloadPath, simulatorDir,
|
|
568
511
|
getRendererDir, getPreloadDir, getRendererHtml
|
|
569
|
-
@dimina-kit/devtools/
|
|
570
|
-
|
|
512
|
+
@dimina-kit/devtools/projects-provider ProjectsProvider / ProjectTemplate 等类型
|
|
513
|
+
@dimina-kit/devtools/workbench-settings loadWorkbenchSettings(), saveWorkbenchSettings(), applyTheme()
|
|
571
514
|
@dimina-kit/devtools/preload installSimulatorBridge,
|
|
572
515
|
installCustomApisBridge,
|
|
573
516
|
installConsoleInstrumentation,
|
|
@@ -577,29 +520,15 @@ dispose()
|
|
|
577
520
|
(storage 面板数据由主进程 CDP 抓取,无 preload 侧 hook)
|
|
578
521
|
```
|
|
579
522
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
```
|
|
585
|
-
@dimina-kit/devtools/context createWorkbenchContext(opts),
|
|
586
|
-
hasBuiltinPanel(ctx, panelId), getDefaultTab(ctx)
|
|
587
|
-
@dimina-kit/devtools/create-window createMainWindow(opts)
|
|
588
|
-
@dimina-kit/devtools/ipc-simulator registerSimulatorIpc(ctx)
|
|
589
|
-
@dimina-kit/devtools/ipc-panels registerPanelsIpc(ctx)
|
|
590
|
-
@dimina-kit/devtools/ipc-toolbar registerToolbarIpc(ctx)
|
|
591
|
-
@dimina-kit/devtools/ipc-popover registerPopoverIpc(ctx)
|
|
592
|
-
@dimina-kit/devtools/ipc-settings registerSettingsIpc(ctx)
|
|
593
|
-
@dimina-kit/devtools/ipc-projects registerProjectsIpc(ctx)
|
|
594
|
-
@dimina-kit/devtools/ipc-session registerSessionIpc(ctx), sessionModule
|
|
595
|
-
@dimina-kit/devtools/workbench-settings loadWorkbenchSettings(), saveWorkbenchSettings(), applyTheme()
|
|
596
|
-
```
|
|
523
|
+
> `IpcRegistry` 是 host 写自定义 IPC 的网关基类——`onSetup` 里通过 `instance.ipc` 拿到一个已绑定 `senderPolicy` 的实例,无需自行构造。
|
|
524
|
+
> 自定义 simulator API 通过 `instance.registerSimulatorApi` 注册,没有独立的导出入口。
|
|
525
|
+
> 内置 IPC 模块(`register*Ipc` / `WorkbenchModule`)是 devtools 内部实现,不再对外导出。
|
|
597
526
|
|
|
598
527
|
---
|
|
599
528
|
|
|
600
529
|
## WorkbenchContext
|
|
601
530
|
|
|
602
|
-
`WorkbenchContext`
|
|
531
|
+
`WorkbenchContext` 是所有扩展点的唯一容器。配置字段直接暴露,运行时状态通过 service 访问。host hook 拿到的 `instance.context` 即此类型;`menuBuilder` 拿到的是收窄后的 `MenuContext`(剔除 `registry` / `senderPolicy` / `trustedWindowSenderIds` / `simulatorApis` / `toolbar` 等内部管线)。
|
|
603
532
|
|
|
604
533
|
```typescript
|
|
605
534
|
interface WorkbenchContext {
|
|
@@ -609,7 +538,7 @@ interface WorkbenchContext {
|
|
|
609
538
|
panels: string[]
|
|
610
539
|
apiNamespaces: string[]
|
|
611
540
|
appName: string
|
|
612
|
-
|
|
541
|
+
headerHeight: number
|
|
613
542
|
brandingProvider?: () => Promise<{ appName: string }> | { appName: string }
|
|
614
543
|
|
|
615
544
|
// ── Services(运行时状态封装在内部,通过方法访问)──
|
|
@@ -618,6 +547,11 @@ interface WorkbenchContext {
|
|
|
618
547
|
notify: RendererNotifier // ctx.notify.projectStatus(), ctx.notify.windowNavigateBack(), ...
|
|
619
548
|
workspace: WorkspaceService // ctx.workspace.getProjectPath(), ctx.workspace.hasActiveSession(), ...
|
|
620
549
|
|
|
550
|
+
// ── 扩展容器(host 通过 instance 上的方法写入,不直接操作)──
|
|
551
|
+
simulatorApis: SimulatorApiRegistry // instance.registerSimulatorApi 写入
|
|
552
|
+
toolbar: ToolbarStore // instance.toolbar.set 写入
|
|
553
|
+
trustedWindowSenderIds: Map<number, number> // instance.registerTrustedWindow 写入(webContents.id → 引用计数)
|
|
554
|
+
|
|
621
555
|
// ── Internal lifecycle / security (host 不直接消费) ──
|
|
622
556
|
registry: DisposableRegistry // 框架在创建时统一聚合 dispose
|
|
623
557
|
senderPolicy: SenderPolicy // IpcRegistry 自动校验 sender 白名单
|
package/dist/main/api.d.ts
CHANGED
|
@@ -9,18 +9,10 @@ export { createViewManager } from './services/views/view-manager.js';
|
|
|
9
9
|
export type { ViewManager } from './services/views/view-manager.js';
|
|
10
10
|
export type { WorkspaceService } from './services/workspace/workspace-service.js';
|
|
11
11
|
export type { Project, ProjectPages, ProjectSettings } from './services/projects/project-repository.js';
|
|
12
|
-
export {
|
|
13
|
-
export type { SimulatorApiHandler } from './simulator-apis.js';
|
|
14
|
-
export { registerAppIpc } from './ipc/app.js';
|
|
15
|
-
export { registerSimulatorIpc } from './ipc/simulator.js';
|
|
16
|
-
export { registerPanelsIpc } from './ipc/panels.js';
|
|
17
|
-
export { registerPopoverIpc } from './ipc/popover.js';
|
|
18
|
-
export { registerSettingsIpc } from './ipc/settings.js';
|
|
19
|
-
export { registerProjectsIpc } from './ipc/projects.js';
|
|
20
|
-
export { registerSessionIpc } from './ipc/session.js';
|
|
21
|
-
export { registerToolbarIpc } from './ipc/toolbar.js';
|
|
12
|
+
export type { SimulatorApiHandler } from './services/simulator/custom-apis.js';
|
|
22
13
|
export { rendererDir, defaultPreloadPath, simulatorDir, getRendererDir, getPreloadDir, getRendererHtml, } from './utils/paths.js';
|
|
23
|
-
export {
|
|
14
|
+
export { IpcRegistry } from './utils/ipc-registry.js';
|
|
15
|
+
export type { SenderPolicy } from './utils/ipc-registry.js';
|
|
24
16
|
export { UpdateManager, createGitHubReleaseChecker } from './services/update/index.js';
|
|
25
17
|
export type { UpdateManagerOptions, GitHubReleaseCheckerOptions, PickAssetContext, VersionScheme, } from './services/update/index.js';
|
|
26
18
|
export type { WorkbenchConfig, WorkbenchAppConfig, CompilationAdapter, ProjectSession, ToolbarAction, BuiltinPanelId, BuiltinModuleId, UpdateChecker, UpdateInfo, } from '../shared/types.js';
|
package/dist/main/api.js
CHANGED
|
@@ -7,21 +7,10 @@ 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';
|
|
12
|
-
// ── IPC module registration ──────────────────────────────────────────────
|
|
13
|
-
export { registerAppIpc } from './ipc/app.js';
|
|
14
|
-
export { registerSimulatorIpc } from './ipc/simulator.js';
|
|
15
|
-
export { registerPanelsIpc } from './ipc/panels.js';
|
|
16
|
-
export { registerPopoverIpc } from './ipc/popover.js';
|
|
17
|
-
export { registerSettingsIpc } from './ipc/settings.js';
|
|
18
|
-
export { registerProjectsIpc } from './ipc/projects.js';
|
|
19
|
-
export { registerSessionIpc } from './ipc/session.js';
|
|
20
|
-
export { registerToolbarIpc } from './ipc/toolbar.js';
|
|
21
10
|
// ── Paths ────────────────────────────────────────────────────────────────
|
|
22
11
|
export { rendererDir, defaultPreloadPath, simulatorDir, getRendererDir, getPreloadDir, getRendererHtml, } from './utils/paths.js';
|
|
23
|
-
// ──
|
|
24
|
-
export {
|
|
12
|
+
// ── IPC gateway (for host-registered custom IPC) ─────────────────────────
|
|
13
|
+
export { IpcRegistry } from './utils/ipc-registry.js';
|
|
25
14
|
// ── Update checker ───────────────────────────────────────────────────────
|
|
26
15
|
export { UpdateManager, createGitHubReleaseChecker } from './services/update/index.js';
|
|
27
16
|
//# sourceMappingURL=api.js.map
|
package/dist/main/app/app.d.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { BrowserWindow } from 'electron';
|
|
2
|
-
import type { WorkbenchAppConfig } from '../../shared/types.js';
|
|
2
|
+
import type { ToolbarActionInput, WorkbenchAppConfig } from '../../shared/types.js';
|
|
3
|
+
import type { SimulatorApiHandler } from '../services/simulator/custom-apis.js';
|
|
3
4
|
import { type WorkbenchContext } from '../services/workbench-context.js';
|
|
4
5
|
import { type AutomationServer } from '../services/automation/index.js';
|
|
5
6
|
import { UpdateManager } from '../services/update/index.js';
|
|
7
|
+
import { type Disposable } from '../utils/disposable.js';
|
|
8
|
+
import { IpcRegistry } from '../utils/ipc-registry.js';
|
|
6
9
|
export interface WorkbenchAppInstance {
|
|
7
10
|
mainWindow: BrowserWindow;
|
|
8
11
|
context: WorkbenchContext;
|
|
12
|
+
/** Gated custom-IPC registration surface bound to `context.senderPolicy`. */
|
|
13
|
+
readonly ipc: IpcRegistry;
|
|
14
|
+
/** Adds a host-owned BrowserWindow to the trusted-sender set. */
|
|
15
|
+
registerTrustedWindow: (win: BrowserWindow) => Disposable;
|
|
16
|
+
/** Registers a simulator custom API into this context's registry. */
|
|
17
|
+
registerSimulatorApi: (name: string, handler: SimulatorApiHandler) => Disposable;
|
|
18
|
+
/** Per-context toolbar surface — `set()` atomically replaces the table. */
|
|
19
|
+
readonly toolbar: {
|
|
20
|
+
set(actions: ToolbarActionInput[]): void;
|
|
21
|
+
};
|
|
9
22
|
automationServer?: AutomationServer;
|
|
10
23
|
updateManager?: UpdateManager;
|
|
11
24
|
dispose: () => Promise<void>;
|
package/dist/main/app/app.js
CHANGED
|
@@ -15,6 +15,7 @@ import { startMcpServer } from '../services/mcp/index.js';
|
|
|
15
15
|
import { setupSimulatorStorage } from '../services/simulator-storage/index.js';
|
|
16
16
|
import { UpdateManager } from '../services/update/index.js';
|
|
17
17
|
import { toDisposable } from '../utils/disposable.js';
|
|
18
|
+
import { IpcRegistry } from '../utils/ipc-registry.js';
|
|
18
19
|
const DEFAULT_MODULES = {
|
|
19
20
|
projects: true,
|
|
20
21
|
session: true,
|
|
@@ -29,6 +30,57 @@ const BUILTIN_MODULES = {
|
|
|
29
30
|
popover: popoverModule,
|
|
30
31
|
settings: settingsModule,
|
|
31
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Adds `win.webContents` to the context's trusted-sender set and returns a
|
|
35
|
+
* Disposable that removes it again.
|
|
36
|
+
*
|
|
37
|
+
* Trust is reference-counted: registering the SAME window N times keeps it
|
|
38
|
+
* trusted until every one of the N returned Disposables has been disposed.
|
|
39
|
+
* `trustedWindowSenderIds` is a `Map<webContents.id, refCount>`; each register
|
|
40
|
+
* bumps the count, each dispose decrements it, and the window is un-trusted
|
|
41
|
+
* only when the count reaches zero.
|
|
42
|
+
*
|
|
43
|
+
* The window's `closed` event short-circuits the ref-count: it deletes the
|
|
44
|
+
* map entry outright (the window is dead, so it must be un-trusted
|
|
45
|
+
* immediately regardless of how many Disposables are still outstanding).
|
|
46
|
+
* After `closed`, disposing any leftover Disposable is a safe no-op — the
|
|
47
|
+
* map entry is already gone, so the decrement finds `undefined` and bails
|
|
48
|
+
* without driving the count negative or resurrecting trust.
|
|
49
|
+
*
|
|
50
|
+
* Each returned Disposable is itself idempotent (`removed` flag), and its
|
|
51
|
+
* `closed` listener removes itself once fired so a long-lived context
|
|
52
|
+
* doesn't accumulate dead listeners on closed windows.
|
|
53
|
+
*/
|
|
54
|
+
function registerTrustedWindow(context, win) {
|
|
55
|
+
const senderId = win.webContents.id;
|
|
56
|
+
const counts = context.trustedWindowSenderIds;
|
|
57
|
+
counts.set(senderId, (counts.get(senderId) ?? 0) + 1);
|
|
58
|
+
let removed = false;
|
|
59
|
+
const onClosed = () => {
|
|
60
|
+
// The window is gone: zero the ref-count for this sender id outright,
|
|
61
|
+
// regardless of any other outstanding registrations for the same window.
|
|
62
|
+
counts.delete(senderId);
|
|
63
|
+
win.removeListener('closed', onClosed);
|
|
64
|
+
};
|
|
65
|
+
function remove() {
|
|
66
|
+
if (removed)
|
|
67
|
+
return;
|
|
68
|
+
removed = true;
|
|
69
|
+
win.removeListener('closed', onClosed);
|
|
70
|
+
const count = counts.get(senderId);
|
|
71
|
+
// `undefined` → the entry was already cleared (e.g. by `closed` or by a
|
|
72
|
+
// prior sibling's decrement that hit zero). Nothing to do — never go
|
|
73
|
+
// negative, never resurrect trust.
|
|
74
|
+
if (count === undefined)
|
|
75
|
+
return;
|
|
76
|
+
if (count <= 1)
|
|
77
|
+
counts.delete(senderId);
|
|
78
|
+
else
|
|
79
|
+
counts.set(senderId, count - 1);
|
|
80
|
+
}
|
|
81
|
+
win.once('closed', onClosed);
|
|
82
|
+
return toDisposable(remove);
|
|
83
|
+
}
|
|
32
84
|
/** Parse --auto, --auto-port, --project from process.argv. */
|
|
33
85
|
function parseAutoArgs() {
|
|
34
86
|
const argv = process.argv;
|
|
@@ -93,8 +145,8 @@ function createContext(config, mainWindow, rendererDir) {
|
|
|
93
145
|
panels: config.panels,
|
|
94
146
|
appName: config.appName,
|
|
95
147
|
apiNamespaces: config.apiNamespaces,
|
|
148
|
+
headerHeight: config.headerHeight,
|
|
96
149
|
brandingProvider: config.brandingProvider,
|
|
97
|
-
toolbarActions: config.toolbarActions,
|
|
98
150
|
// The host-supplied ProjectsProvider / template types in `shared/types`
|
|
99
151
|
// are structurally compatible with the main-process equivalents —
|
|
100
152
|
// these casts are safe; we re-narrow at the workspace-service /
|
|
@@ -112,10 +164,27 @@ function registerBuiltinModules(config, context) {
|
|
|
112
164
|
context.registry.add(BUILTIN_MODULES[moduleId].setup(context));
|
|
113
165
|
});
|
|
114
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Strip the internal-plumbing fields a host menu builder must not reach
|
|
169
|
+
* (registry / senderPolicy / trustedWindowSenderIds / simulatorApis / toolbar)
|
|
170
|
+
* so `menuBuilder` receives the narrowed `MenuContext` its contract promises —
|
|
171
|
+
* at runtime, not just at the type level.
|
|
172
|
+
*/
|
|
173
|
+
function toMenuContext(context) {
|
|
174
|
+
// Shallow-copy, then drop the internal-plumbing fields. A rest-destructure
|
|
175
|
+
// would be terser but trips no-unused-vars on the dropped siblings.
|
|
176
|
+
const menuContext = { ...context };
|
|
177
|
+
delete menuContext.registry;
|
|
178
|
+
delete menuContext.senderPolicy;
|
|
179
|
+
delete menuContext.trustedWindowSenderIds;
|
|
180
|
+
delete menuContext.simulatorApis;
|
|
181
|
+
delete menuContext.toolbar;
|
|
182
|
+
return menuContext;
|
|
183
|
+
}
|
|
115
184
|
function installMenu(config, mainWindow, context) {
|
|
116
185
|
// Menu: use host-provided builder or fall back to default
|
|
117
186
|
if (config.menuBuilder) {
|
|
118
|
-
config.menuBuilder(mainWindow, context);
|
|
187
|
+
config.menuBuilder(mainWindow, toMenuContext(context));
|
|
119
188
|
}
|
|
120
189
|
else {
|
|
121
190
|
installAppMenu(context);
|
|
@@ -205,9 +274,30 @@ export function createWorkbenchApp(config = {}) {
|
|
|
205
274
|
context.registry.add(installThemeBackgroundSync());
|
|
206
275
|
registerBuiltinModules(config, context);
|
|
207
276
|
installMenu(config, mainWindow, context);
|
|
277
|
+
// Gated custom-IPC surface for the host. Bound to ctx.senderPolicy so
|
|
278
|
+
// host channels go through the same gateway as built-in IPC, and
|
|
279
|
+
// registered into ctx.registry so its handlers are torn down with the
|
|
280
|
+
// context.
|
|
281
|
+
const hostIpc = new IpcRegistry(context.senderPolicy);
|
|
282
|
+
context.registry.add(hostIpc);
|
|
208
283
|
const instance = {
|
|
209
284
|
mainWindow,
|
|
210
285
|
context,
|
|
286
|
+
ipc: hostIpc,
|
|
287
|
+
// Return the registry wrapper, not the raw disposable: disposing the
|
|
288
|
+
// wrapper splices the registry entry out AND drives the underlying
|
|
289
|
+
// teardown, so a single dispose leaves no dead entry behind.
|
|
290
|
+
registerTrustedWindow: (win) => context.registry.add(registerTrustedWindow(context, win)),
|
|
291
|
+
registerSimulatorApi: (name, handler) => context.registry.add(toDisposable(context.simulatorApis.register(name, handler))),
|
|
292
|
+
toolbar: {
|
|
293
|
+
set: (actions) => {
|
|
294
|
+
// `context.toolbar.set` validates id-uniqueness and throws on a
|
|
295
|
+
// duplicate BEFORE mutating, so a rejected batch never reaches
|
|
296
|
+
// the notify below — no phantom ActionsChanged.
|
|
297
|
+
context.toolbar.set(actions);
|
|
298
|
+
context.notify.toolbarActionsChanged();
|
|
299
|
+
},
|
|
300
|
+
},
|
|
211
301
|
dispose: () => disposeContext(context),
|
|
212
302
|
};
|
|
213
303
|
if (config.onSetup) {
|
|
@@ -217,6 +307,7 @@ export function createWorkbenchApp(config = {}) {
|
|
|
217
307
|
instance.updateManager = new UpdateManager({
|
|
218
308
|
checker: config.updateChecker,
|
|
219
309
|
mainWindow,
|
|
310
|
+
senderPolicy: context.senderPolicy,
|
|
220
311
|
checkInterval: config.updateOptions?.checkInterval,
|
|
221
312
|
initialDelay: config.updateOptions?.initialDelay,
|
|
222
313
|
getCurrentVersion: config.updateOptions?.getCurrentVersion,
|