@dimina-kit/devtools 0.3.1-dev.20260515105504 → 0.3.1-dev.20260518054451
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 +84 -64
- package/dist/main/index.bundle.js +106 -27
- package/dist/main/ipc/session.js +9 -1
- package/dist/main/menu/index.js +11 -0
- package/dist/main/services/projects/thumbnail.d.ts +4 -0
- package/dist/main/services/projects/thumbnail.js +32 -0
- package/dist/main/services/workspace/workspace-service.d.ts +2 -0
- package/dist/main/services/workspace/workspace-service.js +20 -0
- package/dist/main/utils/sender-policy.d.ts +7 -5
- package/dist/main/utils/sender-policy.js +8 -6
- package/dist/preload/index.d.ts +2 -0
- package/dist/preload/index.js +1 -0
- package/dist/preload/runtime/custom-apis.js +32 -3
- package/dist/preload/windows/simulator.js +26 -5
- package/dist/renderer/assets/index-BFdItKBK.js +46 -0
- package/dist/renderer/assets/{input-C5M5Wxn6.js → input-AH9UDYmj.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-CUsl-fS2.js → ipc-transport-DNgBM6Qz.js} +2 -2
- package/dist/renderer/assets/ipc-transport-Dvmd5JZj.css +1 -0
- package/dist/renderer/assets/{popover-BSBPVO0o.js → popover-D1hoVYiY.js} +2 -2
- package/dist/renderer/assets/{select-B1tF3UAc.js → select-BgDHRj6r.js} +2 -2
- package/dist/renderer/assets/{settings-api-BjJalghB.js → settings-api-DBKlNKV3.js} +2 -2
- package/dist/renderer/assets/{settings-7w54H6p-.js → settings-m-tcmZy_.js} +2 -2
- package/dist/renderer/assets/{workbenchSettings-q2_-nc8t.js → workbenchSettings-CGacu-pI.js} +2 -2
- package/dist/renderer/entries/main/index.html +5 -5
- package/dist/renderer/entries/popover/index.html +5 -5
- package/dist/renderer/entries/settings/index.html +5 -5
- package/dist/renderer/entries/workbench-settings/index.html +4 -4
- package/dist/shared/ipc-channels.d.ts +6 -0
- package/dist/shared/ipc-channels.js +19 -0
- package/dist/shared/ipc-schemas.d.ts +4 -0
- package/dist/shared/ipc-schemas.js +4 -0
- package/package.json +4 -4
- package/dist/renderer/assets/index-DW_0thr-.js +0 -46
- package/dist/renderer/assets/ipc-transport-CltRuvhi.css +0 -1
package/README.md
CHANGED
|
@@ -83,6 +83,7 @@ import { app, globalShortcut } from 'electron'
|
|
|
83
83
|
import path from 'path'
|
|
84
84
|
import { createMainWindow } from '@dimina-kit/devtools/create-window'
|
|
85
85
|
import { createWorkbenchContext } from '@dimina-kit/devtools/context'
|
|
86
|
+
import { registerAppIpc } from '@dimina-kit/devtools'
|
|
86
87
|
import { registerSimulatorIpc } from '@dimina-kit/devtools/ipc-simulator'
|
|
87
88
|
import { registerPanelsIpc } from '@dimina-kit/devtools/ipc-panels'
|
|
88
89
|
import { registerToolbarIpc } from '@dimina-kit/devtools/ipc-toolbar'
|
|
@@ -95,7 +96,7 @@ import { rendererDir, defaultPreloadPath } from '@dimina-kit/devtools/paths'
|
|
|
95
96
|
app.whenReady().then(() => {
|
|
96
97
|
const mainWindow = createMainWindow({
|
|
97
98
|
title: 'My DevTools',
|
|
98
|
-
indexHtml: path.join(rendererDir, 'index.html'),
|
|
99
|
+
indexHtml: path.join(rendererDir, 'entries/main/index.html'),
|
|
99
100
|
})
|
|
100
101
|
|
|
101
102
|
const ctx = createWorkbenchContext({
|
|
@@ -105,6 +106,9 @@ app.whenReady().then(() => {
|
|
|
105
106
|
rendererDir,
|
|
106
107
|
})
|
|
107
108
|
|
|
109
|
+
// registerAppIpc 必装:renderer 启动时调用 app:getPreloadPath / app:getBranding,
|
|
110
|
+
// 缺它会卡在加载阶段。
|
|
111
|
+
registerAppIpc(ctx)
|
|
108
112
|
registerProjectsIpc(ctx)
|
|
109
113
|
registerSessionIpc(ctx)
|
|
110
114
|
registerSimulatorIpc(ctx)
|
|
@@ -112,6 +116,11 @@ app.whenReady().then(() => {
|
|
|
112
116
|
registerToolbarIpc(ctx)
|
|
113
117
|
registerPopoverIpc(ctx)
|
|
114
118
|
|
|
119
|
+
// Storage / 元素选区面板的后端 handler 由 setupSimulatorStorage 单独装。
|
|
120
|
+
// 该函数当前**未列入稳定公共 API**(在 `@dimina-kit/devtools` 之外)。
|
|
121
|
+
// 若需要这两个面板,建议改用上方的 `createWorkbenchApp({ onSetup })` 路线,
|
|
122
|
+
// 框架会自动注册;模块组装路线下你只能用不依赖它们的精简面板集。
|
|
123
|
+
|
|
115
124
|
// 添加自定义 IPC — 通过 ctx.workspace 访问会话状态
|
|
116
125
|
ipcMain.handle('my:action', () => {
|
|
117
126
|
console.log('current project:', ctx.workspace.getProjectPath())
|
|
@@ -197,19 +206,20 @@ src/
|
|
|
197
206
|
types.ts
|
|
198
207
|
instrumentation/ # 注入到小程序 runtime 的探针
|
|
199
208
|
console.ts # 控制台拦截
|
|
200
|
-
storage.ts # localStorage 拦截
|
|
201
209
|
app-data.ts # Worker setData 拦截
|
|
202
|
-
wxml.ts # Vue 组件树提取
|
|
210
|
+
wxml.ts # Vue 组件树提取 + WXML 高亮 overlay
|
|
211
|
+
disposable.ts # instrumentation 共用的清理工具
|
|
203
212
|
runtime/
|
|
204
|
-
bridge.ts # installSimulatorBridge
|
|
205
|
-
|
|
213
|
+
bridge.ts # installSimulatorBridge → window.__simulatorData
|
|
214
|
+
custom-apis.ts # installCustomApisBridge → window.__diminaCustomApis
|
|
215
|
+
host.ts # sendToHost 推送封装
|
|
206
216
|
|
|
207
217
|
renderer/
|
|
208
218
|
entries/ # 各窗口 HTML + React 挂载点
|
|
209
219
|
main/ popover/ settings/ workbench-settings/
|
|
210
220
|
modules/ # 先按窗口分,再按区域
|
|
211
221
|
main/
|
|
212
|
-
|
|
222
|
+
main.tsx # 主窗口 React 根
|
|
213
223
|
features/ # 主窗口内的业务区域
|
|
214
224
|
project-runtime/ # 项目视图 + 工具栏 + 右侧面板切换
|
|
215
225
|
right-panel/ # WXML / AppData / Storage 面板
|
|
@@ -261,6 +271,8 @@ src/
|
|
|
261
271
|
| `onSetup` | `(instance) => void` | — | 窗口和 context 创建后的回调,用于注册自定义 IPC |
|
|
262
272
|
| `onBeforeClose` | `(instance) => void` | — | 窗口关闭前的回调,session 关闭由框架自动处理 |
|
|
263
273
|
| `window` | `WorkbenchWindowConfig` | — | 窗口尺寸覆盖 |
|
|
274
|
+
| `updateChecker` | `UpdateChecker` | — | 自定义更新检查器;提供后启用"检查更新"功能 |
|
|
275
|
+
| `updateOptions` | `{ checkInterval?, initialDelay?, getCurrentVersion? }` | — | 仅当 `updateChecker` 提供时生效,默认 1h / 5s |
|
|
264
276
|
|
|
265
277
|
### 内置面板 ID
|
|
266
278
|
|
|
@@ -298,37 +310,56 @@ const myAdapter: CompilationAdapter = {
|
|
|
298
310
|
|
|
299
311
|
## Preload Instrumentation
|
|
300
312
|
|
|
301
|
-
模拟器 webview 注入 preload
|
|
313
|
+
模拟器 webview 注入 preload 脚本,在 webview 全局上安装两类东西:**桥接**(把数据通道暴露到 webview)+ **instrumentation**(监听运行时事件并写入桥接)。内置 preload 见 `src/preload/windows/simulator.ts`。
|
|
314
|
+
|
|
315
|
+
### 必装:桥接
|
|
316
|
+
|
|
317
|
+
| 函数 | 作用 | 不装会怎样 |
|
|
318
|
+
| --- | --- | --- |
|
|
319
|
+
| `installSimulatorBridge()` | 暴露 `window.__simulatorData`,承载 `highlightElement` / `unhighlightElement` / `getAppdata` / `getWxml` / `getStorageSnapshot` 等只读 API(供主进程 `executeJavaScript` 拉取) | 元素选区 overlay 不显示;automation `Page.getData` 返回空对象;MCP `simulator_get_overview` 在 hints 中追加 `simulator bridge not ready (window.__simulatorData missing)`。注:Storage 面板走主进程 CDP,不受影响;Console/AppData/WXML 推送走 `sendToHost`,也不受影响 |
|
|
320
|
+
| `installCustomApisBridge()` | 暴露 `window.__diminaCustomApis`,承载 `list` / `invoke`,让模拟器侧把主进程注册的 API 代理回小程序 runtime | `registerSimulatorApi` 注册的业务 API 在小程序里调用全部 no-op(参见下方 "Simulator 自定义 API") |
|
|
321
|
+
| `setupApiCompatHook()` | 兼容上游 API 名称变更 | 部分 API 调用兼容性问题 |
|
|
322
|
+
|
|
323
|
+
下游写自定义 preload 时,这三项原则上都要装。若不需要 `registerSimulatorApi`,`installCustomApisBridge` 可省略。
|
|
302
324
|
|
|
303
|
-
|
|
304
|
-
| ----------------------------------------- | ----------------------------------------------- |
|
|
305
|
-
| Console (`installConsoleInstrumentation`) | `console.*`、`window.onerror`、未捕获 rejection |
|
|
306
|
-
| Storage (`installStorageInstrumentation`) | `localStorage.setItem/removeItem`、全量快照 |
|
|
307
|
-
| AppData (`installAppDataInstrumentation`) | Worker `type='u'` 消息、setData 调用 |
|
|
308
|
-
| WXML (`installWxmlInstrumentation`) | Vue 组件树提取、MutationObserver 自动更新 |
|
|
325
|
+
### 按需:instrumentation
|
|
309
326
|
|
|
310
|
-
|
|
327
|
+
| Instrumentation | 捕获内容 | 数据通道 |
|
|
328
|
+
| --- | --- | --- |
|
|
329
|
+
| `installConsoleInstrumentation` | `console.*`、`window.onerror`、未捕获 rejection | IPC sendToHost |
|
|
330
|
+
| `installAppDataInstrumentation` | Worker `type='u'` 消息、setData 调用 | IPC sendToHost + 写入 bridge state |
|
|
331
|
+
| `installWxmlInstrumentation` | Vue 组件树提取、MutationObserver 自动更新 | IPC sendToHost + 写入 bridge state |
|
|
332
|
+
|
|
333
|
+
> Storage 面板的数据由主进程通过 CDP `DOMStorage` 域抓取(`src/main/services/simulator-storage`),**不需要 preload 侧 instrumentation**。
|
|
334
|
+
|
|
335
|
+
各面板的数据流详见下方 "数据流" 章节。
|
|
311
336
|
|
|
312
337
|
### 自定义 Preload
|
|
313
338
|
|
|
314
|
-
|
|
339
|
+
参考 `src/preload/windows/simulator.ts`:
|
|
315
340
|
|
|
316
341
|
```typescript
|
|
317
342
|
// my-preload.ts
|
|
318
343
|
import {
|
|
319
344
|
installSimulatorBridge,
|
|
345
|
+
installCustomApisBridge,
|
|
320
346
|
installConsoleInstrumentation,
|
|
321
|
-
|
|
347
|
+
installAppDataInstrumentation,
|
|
348
|
+
installWxmlInstrumentation,
|
|
322
349
|
setupApiCompatHook,
|
|
323
350
|
} from '@dimina-kit/devtools/preload'
|
|
324
|
-
// 不需要 WXML/AppData,不导入
|
|
325
351
|
|
|
352
|
+
// 1. 兼容 hook + 桥接(必装,且应在 instrumentation 之前)
|
|
326
353
|
setupApiCompatHook()
|
|
327
354
|
installSimulatorBridge()
|
|
355
|
+
installCustomApisBridge() // 若用到 registerSimulatorApi,必装
|
|
356
|
+
|
|
357
|
+
// 2. instrumentation(按需)
|
|
328
358
|
installConsoleInstrumentation()
|
|
329
|
-
|
|
359
|
+
installAppDataInstrumentation()
|
|
360
|
+
installWxmlInstrumentation()
|
|
330
361
|
|
|
331
|
-
// 自定义 hook
|
|
362
|
+
// 3. 自定义 hook
|
|
332
363
|
window.addEventListener('error', (e) => {
|
|
333
364
|
console.error('[my-preload]', e.message)
|
|
334
365
|
})
|
|
@@ -346,26 +377,39 @@ launch({ preloadPath: '/absolute/path/to/my-preload.js' })
|
|
|
346
377
|
|
|
347
378
|
下游可以通过 `registerSimulatorApi` 把业务 API 注入到模拟器内运行的小程序。Handler 在 Electron **主进程**执行,能用任何 Node 能力(fs、net、原生模块、持久状态);模拟器侧拿到 `wx.<name>(params)` 调用后通过 IPC 转发到主进程,await 返回结果,handler 抛错则原型回传给小程序。
|
|
348
379
|
|
|
380
|
+
> ⚠️ 使用本功能要求模拟器 preload 调用 `installCustomApisBridge()`(详见上方 "Preload Instrumentation")。内置 preload 已包含此调用;若你用了自定义 preload 又忘记调用,注册的 API 在小程序运行时**没有任何反应**,且不会报错。
|
|
381
|
+
|
|
382
|
+
### 命名约定
|
|
383
|
+
|
|
384
|
+
`name` 是 dimina service 层 `invokeAPI(name, opts)` 用的**裸名**,不带 `wx.` 前缀。注册之后小程序里通过 `wx.<name>(...)`(以及你在 `apiNamespaces` 配的其它命名空间)调用都会命中:
|
|
385
|
+
|
|
386
|
+
| 小程序里写 | `registerSimulatorApi` 的 name |
|
|
387
|
+
| --- | --- |
|
|
388
|
+
| `wx.login(...)` | `'login'` |
|
|
389
|
+
| `wx.request(...)` | `'request'` |
|
|
390
|
+
| `wx.getStorage(...)` 覆盖内置 | `'getStorage'`(小心:这会**屏蔽** dimina 容器的实现) |
|
|
391
|
+
|
|
349
392
|
```typescript
|
|
350
393
|
import { registerSimulatorApi } from '@dimina-kit/devtools/simulator-apis'
|
|
351
394
|
|
|
352
|
-
|
|
395
|
+
// 提供 wx.login 的实现(dimina 容器默认未实现,注册后才能用)
|
|
396
|
+
const dispose = registerSimulatorApi('login', async ({ success }) => {
|
|
353
397
|
// 主进程上下文:可调用内部 HTTP、读取凭证文件、访问原生模块
|
|
354
|
-
|
|
398
|
+
const code = await callInternalAuth()
|
|
399
|
+
return { code }
|
|
355
400
|
})
|
|
356
401
|
|
|
357
402
|
// 不再需要时取消注册(不传也行,进程退出会一起清理)
|
|
358
403
|
dispose()
|
|
359
404
|
```
|
|
360
405
|
|
|
361
|
-
|
|
406
|
+
### 行为约定
|
|
362
407
|
|
|
408
|
+
- **命中顺序**:小程序运行时按 `apiRegistry[name] → MiniApp 实例方法 → 扩展模块兜底` 查找,所以 `registerSimulatorApi` 注册的 name **优先于内置实现**——既能给"未实现"的 wx.* 补功能(如 `wx.login`),也能覆盖默认的 wx.* 行为(如 `wx.request`,dimina-kit 本身就在用这条路把 `request` 接到主进程 fetch)。
|
|
363
409
|
- 同名重复注册:后注册的覆盖前者,静默替换。
|
|
364
410
|
- `dispose()` 只会移除自己创建的那次注册——如果之后被别人覆盖了,旧的 disposer 是 no-op。
|
|
365
|
-
-
|
|
366
|
-
- Handler
|
|
367
|
-
|
|
368
|
-
对小程序侧来说,这些 name 和内置 API(`wx.getStorage` 等)走同一个 `AppManager.apiRegistry`,没有区别。
|
|
411
|
+
- 调用未注册且容器内也没有的 name:小程序侧拿到 reject,错误信息里带 name。
|
|
412
|
+
- Handler 可同步或异步返回;**返回值需可 JSON 序列化**(IPC 限制)。函数、Symbol、Map/Set、循环引用都会丢失。
|
|
369
413
|
|
|
370
414
|
---
|
|
371
415
|
|
|
@@ -392,12 +436,14 @@ dispose()
|
|
|
392
436
|
getRendererDir, getPreloadDir, getRendererHtml
|
|
393
437
|
@dimina-kit/devtools/simulator-apis registerSimulatorApi(name, handler) →
|
|
394
438
|
注入由主进程托管的小程序 API(详见上方"Simulator 自定义 API")
|
|
395
|
-
@dimina-kit/devtools/preload
|
|
396
|
-
|
|
439
|
+
@dimina-kit/devtools/preload installSimulatorBridge,
|
|
440
|
+
installCustomApisBridge,
|
|
441
|
+
installConsoleInstrumentation,
|
|
397
442
|
installAppDataInstrumentation, sendAllAppData,
|
|
398
443
|
installWxmlInstrumentation, sendWxmlTree,
|
|
399
|
-
setupWxmlObserver,
|
|
444
|
+
setupWxmlObserver,
|
|
400
445
|
setupApiCompatHook
|
|
446
|
+
(storage 面板数据由主进程 CDP 抓取,无 preload 侧 hook)
|
|
401
447
|
```
|
|
402
448
|
|
|
403
449
|
### Experimental exports (v0.x — signatures may change in minor versions)
|
|
@@ -414,18 +460,10 @@ dispose()
|
|
|
414
460
|
@dimina-kit/devtools/ipc-popover registerPopoverIpc(ctx)
|
|
415
461
|
@dimina-kit/devtools/ipc-settings registerSettingsIpc(ctx)
|
|
416
462
|
@dimina-kit/devtools/ipc-projects registerProjectsIpc(ctx)
|
|
417
|
-
@dimina-kit/devtools/ipc-session registerSessionIpc(ctx),
|
|
463
|
+
@dimina-kit/devtools/ipc-session registerSessionIpc(ctx), sessionModule
|
|
418
464
|
@dimina-kit/devtools/workbench-settings loadWorkbenchSettings(), saveWorkbenchSettings(), applyTheme()
|
|
419
465
|
```
|
|
420
466
|
|
|
421
|
-
> 注:以下子路径在过去版本曾被导出,已收敛到根 barrel 或内部实现,请改从 `@dimina-kit/devtools` 根入口导入(`createViewManager`、`registerAppIpc`、`simulatorDir`、`Project` 等类型):
|
|
422
|
-
>
|
|
423
|
-
> - `@dimina-kit/devtools/view-manager` → `import { createViewManager, type ViewManager } from '@dimina-kit/devtools'`
|
|
424
|
-
> - `@dimina-kit/devtools/ipc-app` → `import { registerAppIpc } from '@dimina-kit/devtools'`
|
|
425
|
-
> - `@dimina-kit/devtools/projects` → 通过 `ctx.workspace.listProjects()` 等服务方法访问;`Project` 类型从 `@dimina-kit/devtools` 根入口导入
|
|
426
|
-
> - `@dimina-kit/devtools/layout` → `import { setHeaderHeight } from '@dimina-kit/devtools'`(其余布局细节回归内部实现)
|
|
427
|
-
> - `@dimina-kit/devtools/simulator-dir` → `import { simulatorDir } from '@dimina-kit/devtools/paths'`
|
|
428
|
-
|
|
429
467
|
---
|
|
430
468
|
|
|
431
469
|
## WorkbenchContext
|
|
@@ -452,15 +490,10 @@ interface WorkbenchContext {
|
|
|
452
490
|
// ── Internal lifecycle / security (host 不直接消费) ──
|
|
453
491
|
registry: DisposableRegistry // 框架在创建时统一聚合 dispose
|
|
454
492
|
senderPolicy: SenderPolicy // IpcRegistry 自动校验 sender 白名单
|
|
455
|
-
|
|
456
|
-
/** @deprecated 用 ctx.windows.mainWindow */
|
|
457
|
-
mainWindow: BrowserWindow
|
|
458
|
-
/** @deprecated 用 ctx.windows.settingsWindow / setSettingsWindow */
|
|
459
|
-
workbenchSettingsWindow: BrowserWindow | null
|
|
460
493
|
}
|
|
461
494
|
```
|
|
462
495
|
|
|
463
|
-
|
|
496
|
+
会话和视图状态封装在对应 service 的私有闭包中,通过方法访问:
|
|
464
497
|
|
|
465
498
|
| 旧写法 | 新写法 |
|
|
466
499
|
| ---------------------------- | ----------------------------------------------------------------- |
|
|
@@ -500,28 +533,15 @@ flowchart TB
|
|
|
500
533
|
|
|
501
534
|
### 数据流
|
|
502
535
|
|
|
503
|
-
|
|
536
|
+
按面板拆分,分**三条独立路径**:
|
|
504
537
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
participant R as renderer
|
|
511
|
-
participant M as main<br/>ipc/panels.ts
|
|
512
|
-
participant P as preload<br/>instrumentation
|
|
513
|
-
|
|
514
|
-
User->>UI: 打开面板 / 刷新
|
|
515
|
-
UI->>R: invokeMain('panel:eval', expression)
|
|
516
|
-
R->>M: IPC: panel:eval
|
|
517
|
-
M->>P: webContents.executeJavaScript(expression)
|
|
518
|
-
Note over P: window 上已挂好取值函数
|
|
519
|
-
P-->>M: 结构化数据
|
|
520
|
-
M-->>R: 返回结果
|
|
521
|
-
R-->>UI: 渲染
|
|
522
|
-
```
|
|
538
|
+
| 面板 / 场景 | 模式 | 路径 |
|
|
539
|
+
| --- | --- | --- |
|
|
540
|
+
| Console / AppData / WXML 树 | 推送 | preload instrumentation 通过 `runtime/host.ts:sendToHost` 经 webview `ipcRenderer.sendToHost` 推到宿主 → renderer 监听 `ipc-message` 分发 |
|
|
541
|
+
| Storage | 拉取 + 推送 | renderer `ipcInvoke(SimulatorStorageChannel.GetSnapshot)` → 主进程 CDP `DOMStorage` 域;变更事件由主进程通过 `SimulatorStorageChannel.Event` 推回 renderer |
|
|
542
|
+
| 元素选区 / `Page.getData` / MCP overview | 拉取 | 主进程 `webContents.executeJavaScript(...)` 或 `Runtime.evaluate(...)` 读 `window.__simulatorData.*`(由 `installSimulatorBridge` 暴露) |
|
|
523
543
|
|
|
524
|
-
|
|
544
|
+
> `src/main/ipc/panels.ts` 暴露 `panel:eval` handler 供外部 host 自定义面板复用,内置 renderer 不消费它。
|
|
525
545
|
|
|
526
546
|
---
|
|
527
547
|
|
|
@@ -73,9 +73,9 @@ function setupCdpPort() {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// src/main/app/app.ts
|
|
76
|
-
import { app as
|
|
77
|
-
import
|
|
78
|
-
import
|
|
76
|
+
import { app as app12, BrowserWindow as BrowserWindow3, nativeImage } from "electron";
|
|
77
|
+
import fs6 from "fs";
|
|
78
|
+
import path9 from "path";
|
|
79
79
|
|
|
80
80
|
// src/main/utils/paths.ts
|
|
81
81
|
import fs2 from "fs";
|
|
@@ -441,7 +441,9 @@ var ProjectChannel = {
|
|
|
441
441
|
GetPages: "project:getPages",
|
|
442
442
|
GetCompileConfig: "project:getCompileConfig",
|
|
443
443
|
SaveCompileConfig: "project:saveCompileConfig",
|
|
444
|
-
Status: "project:status"
|
|
444
|
+
Status: "project:status",
|
|
445
|
+
CaptureThumbnail: "project:captureThumbnail",
|
|
446
|
+
GetThumbnail: "project:getThumbnail"
|
|
445
447
|
};
|
|
446
448
|
var ProjectsChannel = {
|
|
447
449
|
List: "projects:list",
|
|
@@ -867,6 +869,9 @@ function createWindowService(mainWindow) {
|
|
|
867
869
|
};
|
|
868
870
|
}
|
|
869
871
|
|
|
872
|
+
// src/main/services/workspace/workspace-service.ts
|
|
873
|
+
import { webContents as webContents2 } from "electron";
|
|
874
|
+
|
|
870
875
|
// src/main/services/projects/project-repository.ts
|
|
871
876
|
import { app as app6 } from "electron";
|
|
872
877
|
import path7 from "path";
|
|
@@ -1054,6 +1059,38 @@ function updateProjectSettings(projectPath, patch) {
|
|
|
1054
1059
|
);
|
|
1055
1060
|
}
|
|
1056
1061
|
|
|
1062
|
+
// src/main/services/projects/thumbnail.ts
|
|
1063
|
+
import { createHash } from "crypto";
|
|
1064
|
+
import fs4 from "fs";
|
|
1065
|
+
import path8 from "path";
|
|
1066
|
+
import { app as app7 } from "electron";
|
|
1067
|
+
function getThumbnailDir() {
|
|
1068
|
+
return path8.join(app7.getPath("userData"), "thumbnails");
|
|
1069
|
+
}
|
|
1070
|
+
function hashProjectPath(projectPath) {
|
|
1071
|
+
return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
|
|
1072
|
+
}
|
|
1073
|
+
function getThumbnailPath(projectPath) {
|
|
1074
|
+
return path8.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
|
|
1075
|
+
}
|
|
1076
|
+
function saveThumbnail(projectPath, image) {
|
|
1077
|
+
const png = image.toPNG();
|
|
1078
|
+
const dir = getThumbnailDir();
|
|
1079
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1080
|
+
const filePath = getThumbnailPath(projectPath);
|
|
1081
|
+
fs4.writeFileSync(filePath, png);
|
|
1082
|
+
return `data:image/png;base64,${png.toString("base64")}`;
|
|
1083
|
+
}
|
|
1084
|
+
function loadThumbnail(projectPath) {
|
|
1085
|
+
const filePath = getThumbnailPath(projectPath);
|
|
1086
|
+
try {
|
|
1087
|
+
const buf = fs4.readFileSync(filePath);
|
|
1088
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
1089
|
+
} catch {
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1057
1094
|
// src/main/services/workspace/workspace-service.ts
|
|
1058
1095
|
function createWorkspaceService(ctx) {
|
|
1059
1096
|
let currentSession = null;
|
|
@@ -1143,6 +1180,21 @@ function createWorkspaceService(ctx) {
|
|
|
1143
1180
|
getSession: () => currentSession,
|
|
1144
1181
|
getProjectPath: () => currentProjectPath,
|
|
1145
1182
|
hasActiveSession: () => currentSession !== null,
|
|
1183
|
+
async captureThumbnail(projectPath) {
|
|
1184
|
+
const simWcId = ctx.views.getSimulatorWebContentsId();
|
|
1185
|
+
if (!simWcId) return null;
|
|
1186
|
+
const wc = webContents2.fromId(simWcId);
|
|
1187
|
+
if (!wc || wc.isDestroyed()) return null;
|
|
1188
|
+
try {
|
|
1189
|
+
const image = await wc.capturePage();
|
|
1190
|
+
return saveThumbnail(projectPath, image);
|
|
1191
|
+
} catch {
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
getThumbnail(projectPath) {
|
|
1196
|
+
return loadThumbnail(projectPath);
|
|
1197
|
+
},
|
|
1146
1198
|
getProjectPages: (projectPath) => getProjectPages(projectPath),
|
|
1147
1199
|
getCompileConfig: (projectPath) => getCompileConfig(projectPath),
|
|
1148
1200
|
saveCompileConfig: (projectPath, config) => saveCompileConfig(projectPath, config),
|
|
@@ -1208,6 +1260,17 @@ function installAppMenu(ctx) {
|
|
|
1208
1260
|
{ role: "quit" }
|
|
1209
1261
|
]
|
|
1210
1262
|
},
|
|
1263
|
+
{
|
|
1264
|
+
label: "\u9879\u76EE",
|
|
1265
|
+
submenu: [
|
|
1266
|
+
{
|
|
1267
|
+
label: "\u6253\u5F00\u9879\u76EE",
|
|
1268
|
+
click: () => {
|
|
1269
|
+
ctx.notify.windowNavigateBack();
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
]
|
|
1273
|
+
},
|
|
1211
1274
|
{
|
|
1212
1275
|
label: "\u7F16\u8F91",
|
|
1213
1276
|
submenu: [
|
|
@@ -1249,7 +1312,7 @@ function installAppMenu(ctx) {
|
|
|
1249
1312
|
}
|
|
1250
1313
|
|
|
1251
1314
|
// src/main/ipc/app.ts
|
|
1252
|
-
import
|
|
1315
|
+
import fs5 from "fs";
|
|
1253
1316
|
|
|
1254
1317
|
// src/main/utils/ipc-registry.ts
|
|
1255
1318
|
import { ipcMain } from "electron";
|
|
@@ -1355,7 +1418,7 @@ var IpcRegistry = class {
|
|
|
1355
1418
|
// src/main/ipc/app.ts
|
|
1356
1419
|
function registerAppIpc(ctx) {
|
|
1357
1420
|
return new IpcRegistry(ctx.senderPolicy).handle(AppChannel.GetPreloadPath, () => {
|
|
1358
|
-
return `file://${
|
|
1421
|
+
return `file://${fs5.realpathSync(ctx.preloadPath)}`;
|
|
1359
1422
|
}).handle(AppChannel.GetBranding, async () => {
|
|
1360
1423
|
if (ctx.brandingProvider) return ctx.brandingProvider();
|
|
1361
1424
|
return { appName: ctx.appName };
|
|
@@ -1424,6 +1487,8 @@ var SettingsProjectSettingsChangedSchema = z.tuple([
|
|
|
1424
1487
|
})
|
|
1425
1488
|
]);
|
|
1426
1489
|
var PanelSelectSchema = z.tuple([z.string().min(1).max(200)]);
|
|
1490
|
+
var ProjectCaptureThumbnailSchema = z.tuple([AbsolutePath]);
|
|
1491
|
+
var ProjectGetThumbnailSchema = z.tuple([AbsolutePath]);
|
|
1427
1492
|
|
|
1428
1493
|
// src/main/services/simulator/custom-apis.ts
|
|
1429
1494
|
function createSimulatorApiRegistry() {
|
|
@@ -1478,7 +1543,7 @@ function registerSimulatorIpc(ctx) {
|
|
|
1478
1543
|
}
|
|
1479
1544
|
|
|
1480
1545
|
// src/main/ipc/panels.ts
|
|
1481
|
-
import { webContents as
|
|
1546
|
+
import { webContents as webContents3 } from "electron";
|
|
1482
1547
|
var BUILTIN_PANELS = [
|
|
1483
1548
|
{ id: "wxml", label: "WXML" },
|
|
1484
1549
|
{ id: "appdata", label: "AppData" },
|
|
@@ -1491,7 +1556,7 @@ function registerPanelsIpc(ctx) {
|
|
|
1491
1556
|
const [expression] = validate(PanelChannel.Eval, PanelEvalSchema, args);
|
|
1492
1557
|
const simWcId = ctx.views.getSimulatorWebContentsId();
|
|
1493
1558
|
if (!ctx.workspace.hasActiveSession() || simWcId == null) return void 0;
|
|
1494
|
-
const sim =
|
|
1559
|
+
const sim = webContents3.fromId(simWcId);
|
|
1495
1560
|
if (!sim || sim.isDestroyed()) return void 0;
|
|
1496
1561
|
try {
|
|
1497
1562
|
return await sim.executeJavaScript(expression);
|
|
@@ -1596,6 +1661,20 @@ function registerSessionIpc(ctx) {
|
|
|
1596
1661
|
return ctx.workspace.saveCompileConfig(projectPath, config);
|
|
1597
1662
|
}).handle(ProjectChannel.Close, () => {
|
|
1598
1663
|
return ctx.workspace.closeProject();
|
|
1664
|
+
}).handle(ProjectChannel.CaptureThumbnail, (_, ...args) => {
|
|
1665
|
+
const [projectPath] = validate(
|
|
1666
|
+
ProjectChannel.CaptureThumbnail,
|
|
1667
|
+
ProjectCaptureThumbnailSchema,
|
|
1668
|
+
args
|
|
1669
|
+
);
|
|
1670
|
+
return ctx.workspace.captureThumbnail(projectPath);
|
|
1671
|
+
}).handle(ProjectChannel.GetThumbnail, (_, ...args) => {
|
|
1672
|
+
const [projectPath] = validate(
|
|
1673
|
+
ProjectChannel.GetThumbnail,
|
|
1674
|
+
ProjectGetThumbnailSchema,
|
|
1675
|
+
args
|
|
1676
|
+
);
|
|
1677
|
+
return ctx.workspace.getThumbnail(projectPath);
|
|
1599
1678
|
});
|
|
1600
1679
|
}
|
|
1601
1680
|
var sessionModule = {
|
|
@@ -1603,7 +1682,7 @@ var sessionModule = {
|
|
|
1603
1682
|
};
|
|
1604
1683
|
|
|
1605
1684
|
// src/main/ipc/settings.ts
|
|
1606
|
-
import { app as
|
|
1685
|
+
import { app as app8 } from "electron";
|
|
1607
1686
|
function registerSettingsIpc(ctx) {
|
|
1608
1687
|
return new IpcRegistry(ctx.senderPolicy).handle(WorkbenchSettingsChannel.Get, () => {
|
|
1609
1688
|
return loadWorkbenchSettings();
|
|
@@ -1625,8 +1704,8 @@ function registerSettingsIpc(ctx) {
|
|
|
1625
1704
|
applyTheme(theme);
|
|
1626
1705
|
}).handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
|
|
1627
1706
|
const settings = loadWorkbenchSettings();
|
|
1628
|
-
const switchValue =
|
|
1629
|
-
const implicitDevDefault = !
|
|
1707
|
+
const switchValue = app8.commandLine.getSwitchValue("remote-debugging-port");
|
|
1708
|
+
const implicitDevDefault = !app8.isPackaged && !settings.cdp.enabled && switchValue === "9222";
|
|
1630
1709
|
return {
|
|
1631
1710
|
configured: settings.cdp.enabled,
|
|
1632
1711
|
port: settings.cdp.port,
|
|
@@ -1710,11 +1789,11 @@ var simulatorModule = {
|
|
|
1710
1789
|
import { WebSocketServer } from "ws";
|
|
1711
1790
|
|
|
1712
1791
|
// src/main/services/automation/exec.ts
|
|
1713
|
-
import { webContents as
|
|
1792
|
+
import { webContents as webContents4 } from "electron";
|
|
1714
1793
|
function getSimulator(ctx) {
|
|
1715
1794
|
const simWcId = ctx.views.getSimulatorWebContentsId();
|
|
1716
1795
|
if (!ctx.workspace.hasActiveSession() || simWcId == null) return null;
|
|
1717
|
-
const sim =
|
|
1796
|
+
const sim = webContents4.fromId(simWcId);
|
|
1718
1797
|
if (!sim || sim.isDestroyed()) return null;
|
|
1719
1798
|
return sim;
|
|
1720
1799
|
}
|
|
@@ -2880,7 +2959,7 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
|
|
|
2880
2959
|
}
|
|
2881
2960
|
|
|
2882
2961
|
// src/main/services/simulator-storage/index.ts
|
|
2883
|
-
import { app as
|
|
2962
|
+
import { app as app9, webContents as wcStatic } from "electron";
|
|
2884
2963
|
function isSimulatorWebview(wc) {
|
|
2885
2964
|
if (wc.isDestroyed()) return false;
|
|
2886
2965
|
if (wc.getType() !== "webview") return false;
|
|
@@ -3130,9 +3209,9 @@ function setupSimulatorStorage(host, options) {
|
|
|
3130
3209
|
wcSubs.delete(wc);
|
|
3131
3210
|
});
|
|
3132
3211
|
};
|
|
3133
|
-
|
|
3212
|
+
app9.on("web-contents-created", onWcCreated);
|
|
3134
3213
|
registry.add(() => {
|
|
3135
|
-
|
|
3214
|
+
app9.removeListener("web-contents-created", onWcCreated);
|
|
3136
3215
|
});
|
|
3137
3216
|
registry.add(async () => {
|
|
3138
3217
|
const subs = Array.from(wcSubs.values());
|
|
@@ -3193,7 +3272,7 @@ async function clearElementInspection() {
|
|
|
3193
3272
|
}
|
|
3194
3273
|
|
|
3195
3274
|
// src/main/services/update/update-manager.ts
|
|
3196
|
-
import { app as
|
|
3275
|
+
import { app as app10, shell as shell3 } from "electron";
|
|
3197
3276
|
var UpdateManager = class {
|
|
3198
3277
|
checker;
|
|
3199
3278
|
mainWindow;
|
|
@@ -3206,7 +3285,7 @@ var UpdateManager = class {
|
|
|
3206
3285
|
constructor(opts) {
|
|
3207
3286
|
this.checker = opts.checker;
|
|
3208
3287
|
this.mainWindow = opts.mainWindow;
|
|
3209
|
-
this.getCurrentVersion = opts.getCurrentVersion ?? (() =>
|
|
3288
|
+
this.getCurrentVersion = opts.getCurrentVersion ?? (() => app10.getVersion());
|
|
3210
3289
|
this.ipc = new IpcRegistry(opts.senderPolicy);
|
|
3211
3290
|
this.registerIpc();
|
|
3212
3291
|
this.startPeriodicCheck(opts.checkInterval ?? 60 * 60 * 1e3, opts.initialDelay ?? 5e3);
|
|
@@ -3247,7 +3326,7 @@ var UpdateManager = class {
|
|
|
3247
3326
|
install() {
|
|
3248
3327
|
if (this.downloadedPath) {
|
|
3249
3328
|
shell3.openPath(this.downloadedPath);
|
|
3250
|
-
|
|
3329
|
+
app10.quit();
|
|
3251
3330
|
}
|
|
3252
3331
|
}
|
|
3253
3332
|
/**
|
|
@@ -3280,7 +3359,7 @@ var UpdateManager = class {
|
|
|
3280
3359
|
};
|
|
3281
3360
|
|
|
3282
3361
|
// src/main/services/update/github-release-checker.ts
|
|
3283
|
-
import { app as
|
|
3362
|
+
import { app as app11 } from "electron";
|
|
3284
3363
|
|
|
3285
3364
|
// src/main/app/app.ts
|
|
3286
3365
|
var DEFAULT_MODULES = {
|
|
@@ -3329,7 +3408,7 @@ async function disposeContext(ctx) {
|
|
|
3329
3408
|
function createConfiguredMainWindow(config, rendererDir2) {
|
|
3330
3409
|
const mainWindow = createMainWindow({
|
|
3331
3410
|
title: config.appName ?? "Dimina DevTools",
|
|
3332
|
-
indexHtml:
|
|
3411
|
+
indexHtml: path9.join(rendererDir2, "entries/main/index.html"),
|
|
3333
3412
|
width: config.window?.width,
|
|
3334
3413
|
height: config.window?.height,
|
|
3335
3414
|
minWidth: config.window?.minWidth,
|
|
@@ -3384,7 +3463,7 @@ async function setupAutomation(instance) {
|
|
|
3384
3463
|
function setupMcp() {
|
|
3385
3464
|
const settings = loadWorkbenchSettings();
|
|
3386
3465
|
if (!settings.mcp.enabled) return null;
|
|
3387
|
-
const cdpPortSwitch =
|
|
3466
|
+
const cdpPortSwitch = app12.commandLine.getSwitchValue("remote-debugging-port");
|
|
3388
3467
|
const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
|
|
3389
3468
|
return startMcpServer(cdpPort2, settings.mcp.port);
|
|
3390
3469
|
}
|
|
@@ -3405,12 +3484,12 @@ function wireAppWindowEvents(config, instance) {
|
|
|
3405
3484
|
});
|
|
3406
3485
|
}
|
|
3407
3486
|
function enableDevRendererAutoReload(rendererDir2) {
|
|
3408
|
-
if (
|
|
3487
|
+
if (app12.isPackaged) {
|
|
3409
3488
|
return toDisposable(() => {
|
|
3410
3489
|
});
|
|
3411
3490
|
}
|
|
3412
3491
|
let reloadTimer = null;
|
|
3413
|
-
const watcher =
|
|
3492
|
+
const watcher = fs6.watch(rendererDir2, { recursive: true }, () => {
|
|
3414
3493
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
3415
3494
|
reloadTimer = setTimeout(() => {
|
|
3416
3495
|
for (const win of BrowserWindow3.getAllWindows()) {
|
|
@@ -3432,7 +3511,7 @@ function createWorkbenchApp(config = {}) {
|
|
|
3432
3511
|
setupCdpPort();
|
|
3433
3512
|
async function setup() {
|
|
3434
3513
|
if (setupPromise) return setupPromise;
|
|
3435
|
-
setupPromise =
|
|
3514
|
+
setupPromise = app12.whenReady().then(async () => {
|
|
3436
3515
|
applyTheme(loadWorkbenchSettings().theme);
|
|
3437
3516
|
const rendererDir2 = config.rendererDir ?? rendererDir;
|
|
3438
3517
|
const mainWindow = createConfiguredMainWindow(config, rendererDir2);
|
|
@@ -3496,7 +3575,7 @@ function createWorkbenchApp(config = {}) {
|
|
|
3496
3575
|
|
|
3497
3576
|
// src/main/windows/settings-window/create.ts
|
|
3498
3577
|
import { BrowserWindow as BrowserWindow4 } from "electron";
|
|
3499
|
-
import
|
|
3578
|
+
import path10 from "path";
|
|
3500
3579
|
async function createSettingsWindow(parent, rendererDir2) {
|
|
3501
3580
|
const win = new BrowserWindow4({
|
|
3502
3581
|
width: 420,
|
|
@@ -3514,7 +3593,7 @@ async function createSettingsWindow(parent, rendererDir2) {
|
|
|
3514
3593
|
}
|
|
3515
3594
|
});
|
|
3516
3595
|
applyNavigationHardening(win.webContents, rendererDir2);
|
|
3517
|
-
await win.loadFile(
|
|
3596
|
+
await win.loadFile(path10.join(rendererDir2, "entries/workbench-settings/index.html"));
|
|
3518
3597
|
return win;
|
|
3519
3598
|
}
|
|
3520
3599
|
|
package/dist/main/ipc/session.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProjectChannel } from '../../shared/ipc-channels.js';
|
|
2
|
-
import { ProjectGetCompileConfigSchema, ProjectGetPagesSchema, ProjectOpenSchema, ProjectSaveCompileConfigSchema, } from '../../shared/ipc-schemas.js';
|
|
2
|
+
import { ProjectCaptureThumbnailSchema, ProjectGetCompileConfigSchema, ProjectGetPagesSchema, ProjectGetThumbnailSchema, ProjectOpenSchema, ProjectSaveCompileConfigSchema, } from '../../shared/ipc-schemas.js';
|
|
3
3
|
import { validate } from '../utils/ipc-schema.js';
|
|
4
4
|
import { IpcRegistry } from '../utils/ipc-registry.js';
|
|
5
5
|
export function registerSessionIpc(ctx) {
|
|
@@ -22,6 +22,14 @@ export function registerSessionIpc(ctx) {
|
|
|
22
22
|
})
|
|
23
23
|
.handle(ProjectChannel.Close, () => {
|
|
24
24
|
return ctx.workspace.closeProject();
|
|
25
|
+
})
|
|
26
|
+
.handle(ProjectChannel.CaptureThumbnail, (_, ...args) => {
|
|
27
|
+
const [projectPath] = validate(ProjectChannel.CaptureThumbnail, ProjectCaptureThumbnailSchema, args);
|
|
28
|
+
return ctx.workspace.captureThumbnail(projectPath);
|
|
29
|
+
})
|
|
30
|
+
.handle(ProjectChannel.GetThumbnail, (_, ...args) => {
|
|
31
|
+
const [projectPath] = validate(ProjectChannel.GetThumbnail, ProjectGetThumbnailSchema, args);
|
|
32
|
+
return ctx.workspace.getThumbnail(projectPath);
|
|
25
33
|
});
|
|
26
34
|
}
|
|
27
35
|
export const sessionModule = {
|
package/dist/main/menu/index.js
CHANGED
|
@@ -23,6 +23,17 @@ export function installAppMenu(ctx) {
|
|
|
23
23
|
{ role: 'quit' },
|
|
24
24
|
],
|
|
25
25
|
},
|
|
26
|
+
{
|
|
27
|
+
label: '项目',
|
|
28
|
+
submenu: [
|
|
29
|
+
{
|
|
30
|
+
label: '打开项目',
|
|
31
|
+
click: () => {
|
|
32
|
+
ctx.notify.windowNavigateBack();
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
26
37
|
{
|
|
27
38
|
label: '编辑',
|
|
28
39
|
submenu: [
|