@dimina-kit/devtools 0.4.0-dev.20260616102751 → 0.4.0-dev.20260618090552
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 +25 -24
- package/dist/main/index.bundle.js +212 -12
- package/dist/main/ipc/bridge-router.js +44 -1
- package/dist/main/runtime/devtools-backend.d.ts +1 -1
- package/dist/main/runtime/devtools-backend.js +1 -1
- package/dist/main/runtime/miniapp-runtime.d.ts +2 -2
- package/dist/main/services/console-forward/index.js +8 -1
- package/dist/main/services/elements-forward/index.js +31 -7
- package/dist/main/services/layout/index.d.ts +12 -0
- package/dist/main/services/layout/index.js +14 -2
- package/dist/main/services/projects/types.d.ts +1 -1
- package/dist/main/services/projects/types.js +1 -1
- package/dist/main/services/service-console/console-api.d.ts +78 -0
- package/dist/main/services/service-console/console-api.js +104 -0
- package/dist/main/services/service-console/index.d.ts +47 -0
- package/dist/main/services/service-console/index.js +139 -0
- package/dist/main/services/update/update-manager.d.ts +1 -1
- package/dist/main/services/update/update-manager.js +1 -1
- package/dist/main/services/views/view-manager.d.ts +7 -0
- package/dist/main/services/views/view-manager.js +53 -0
- package/dist/main/services/workbench-context.d.ts +7 -6
- package/dist/renderer/assets/index-DmgWoK8N.js +50 -0
- package/dist/renderer/assets/{input-B8NDCYv3.js → input-DiCxYlqY.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-DhrajiC5.js → ipc-transport-DSBmaWpJ.js} +1 -1
- package/dist/renderer/assets/ipc-transport-DZxf2YoM.css +1 -0
- package/dist/renderer/assets/{popover-DbZOg03s.js → popover-CyL_uffj.js} +2 -2
- package/dist/renderer/assets/{select-pW3Ysc9T.js → select-BSwvDMtA.js} +2 -2
- package/dist/renderer/assets/settings-Ch8XbFxf.js +2 -0
- package/dist/renderer/assets/{settings-api-BSKzKiWd.js → settings-api-e9ILe2JO.js} +2 -2
- package/dist/renderer/assets/{workbenchSettings-BGxjl25x.js → workbenchSettings-DZsHf1g_.js} +2 -2
- package/dist/renderer/entries/main/index.html +6 -6
- package/dist/renderer/entries/popover/index.html +5 -5
- package/dist/renderer/entries/settings/index.html +11 -5
- package/dist/renderer/entries/workbench-settings/index.html +4 -4
- package/dist/service-host/preload.cjs +13 -15
- package/dist/service-host/sourcemap-rewrite.cjs +21 -12
- package/package.json +4 -4
- package/dist/renderer/assets/index-Z4Rh52G8.js +0 -50
- package/dist/renderer/assets/ipc-transport-9agi76dX.css +0 -1
- package/dist/renderer/assets/settings-CZJOHt8b.js +0 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
基于 Electron 的小程序开发者工具。提供模拟器、Chrome DevTools 面板、WXML/AppData/Storage/编译 面板、编译配置等功能。
|
|
4
4
|
|
|
5
|
-
下游 host 通过 `launch(config)` 集成并定制 devtools(零配置直接 `launch()`,配置驱动 `launch({...})`;见下方「两种用法」)。两种用法都经领域中立的 [`@dimina-kit/electron-deck`](../electron-deck) 框架编排——框架接管 Electron 进程生命周期(whenReady / will-quit)、wire/trust 原语,devtools 作为 `RuntimeBackend` 注入完整运行时(见 [`
|
|
5
|
+
下游 host 通过 `launch(config)` 集成并定制 devtools(零配置直接 `launch()`,配置驱动 `launch({...})`;见下方「两种用法」)。两种用法都经领域中立的 [`@dimina-kit/electron-deck`](../electron-deck) 框架编排——框架接管 Electron 进程生命周期(whenReady / will-quit)、wire/trust 原语,devtools 作为 `RuntimeBackend` 注入完整运行时(见 [`electron-deck 架构`](../electron-deck/docs/architecture.md))。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -195,7 +195,8 @@ src/
|
|
|
195
195
|
| `menuBuilder` | `(mainWindow, menuContext: MenuContext) => void` | 内置菜单 | 自定义菜单构建器;`menuContext` 为手写窄契约 `MenuContext`(`appName` + workspace 窄集 + `openSettings` + `notify.{projectStatus, windowNavigateBack}`),不含内部管线 |
|
|
196
196
|
| `onSetup` | `(instance) => void` | — | 窗口和 context 创建后的回调,用于注册 Contribution(见下文)|
|
|
197
197
|
| `onBeforeClose` | `(instance) => void` | — | 窗口关闭前的回调,session 关闭由框架自动处理 |
|
|
198
|
-
| `
|
|
198
|
+
| `onBeforeOpenProject` | `(projectPath: string) => void \| Promise<void>` | — | 打开项目前、任何副作用(旧会话拆除 / 编译 / dev-server)之前运行的声明式权限门控。抛错即否决:`openProject` 返回 `{ success: false, error }`,当前活动会话保持不动、适配器不被调用,且框架自动把 error 透到状态条(`notify.projectStatus`)。覆盖 IPC / 菜单 / 内部直调三个打开入口 |
|
|
199
|
+
| `window` | `WorkbenchWindowConfig`(`width`/`height`/`autoShow?`) | — | 主窗口尺寸覆盖;`autoShow`(默认 `true`)控制 `ready-to-show` 是否自动显示窗口。`autoShow: false` 时窗口创建即隐藏(test / 非 test 一致),由 host 在登录通过后自行 `instance.mainWindow.show()` |
|
|
199
200
|
| `updateChecker` | `UpdateChecker` | — | 自定义更新检查器;提供后启用"检查更新"功能 |
|
|
200
201
|
| `updateOptions` | `{ checkInterval?, initialDelay?, getCurrentVersion? }` | — | 仅当 `updateChecker` 提供时生效,默认 1h / 5s |
|
|
201
202
|
|
|
@@ -221,11 +222,11 @@ src/
|
|
|
221
222
|
|
|
222
223
|
## Embedding & Extending the Project Panel
|
|
223
224
|
|
|
224
|
-
|
|
225
|
+
下游宿主通过 `launch({...})` 嵌入 devtools 时,可对项目面板做三个正交扩展:
|
|
225
226
|
|
|
226
227
|
| 扩展点 | 用途 |
|
|
227
228
|
| --- | --- |
|
|
228
|
-
| `projectsProvider` | 接管项目列表存储 —— 替换默认 `<userData>/dimina-projects.json
|
|
229
|
+
| `projectsProvider` | 接管项目列表存储 —— 替换默认 `<userData>/dimina-projects.json`,对接下游宿主后台 / IDE workspace / 远端工程库 |
|
|
229
230
|
| `projectTemplates` + `builtinTemplates` | 注入/覆盖/裁剪"新建项目"模板列表(同 id 覆盖内置;`builtinTemplates` 控制内置策略) |
|
|
230
231
|
| `customCreateProjectDialog` | 用宿主自家页面替换内置"新建项目"对话框(main 进程 hook,可 `new BrowserWindow` 加载任意 URL,通过 IPC/postMessage 回传结果) |
|
|
231
232
|
|
|
@@ -233,8 +234,8 @@ src/
|
|
|
233
234
|
|
|
234
235
|
| 场景 | 需要的扩展点 |
|
|
235
236
|
| --- | --- |
|
|
236
|
-
|
|
|
237
|
-
| 仅替换可选模板(e.g. 只提供 taro /
|
|
237
|
+
| 项目列表来自下游宿主后台 / 团队工程库 | `projectsProvider` |
|
|
238
|
+
| 仅替换可选模板(e.g. 只提供 taro / 宿主自家脚手架) | `projectTemplates` + `builtinTemplates` |
|
|
238
239
|
| 创建项目流程要走宿主自己的 wizard / 登录态 | `customCreateProjectDialog` |
|
|
239
240
|
|
|
240
241
|
### 最小示例
|
|
@@ -246,51 +247,51 @@ import { BrowserWindow } from 'electron'
|
|
|
246
247
|
|
|
247
248
|
const provider: ProjectsProvider = {
|
|
248
249
|
async listProjects() {
|
|
249
|
-
return await
|
|
250
|
+
return await host.api.listProjects()
|
|
250
251
|
},
|
|
251
252
|
async validateProjectDir(dirPath) {
|
|
252
|
-
return (await
|
|
253
|
+
return (await host.api.isMiniApp(dirPath)) ? null : '不是合法的小程序工程'
|
|
253
254
|
},
|
|
254
255
|
async addProject(dirPath) {
|
|
255
|
-
return await
|
|
256
|
+
return await host.api.addProject(dirPath)
|
|
256
257
|
},
|
|
257
258
|
async removeProject(dirPath) {
|
|
258
|
-
await
|
|
259
|
+
await host.api.removeProject(dirPath)
|
|
259
260
|
},
|
|
260
261
|
async updateLastOpened(dirPath) {
|
|
261
|
-
await
|
|
262
|
+
await host.api.touchLastOpened(dirPath)
|
|
262
263
|
},
|
|
263
264
|
async getCompileConfig(dirPath) {
|
|
264
|
-
return await
|
|
265
|
+
return await host.api.getCompileConfig(dirPath)
|
|
265
266
|
},
|
|
266
267
|
async saveCompileConfig(dirPath, cfg) {
|
|
267
|
-
await
|
|
268
|
+
await host.api.saveCompileConfig(dirPath, cfg)
|
|
268
269
|
},
|
|
269
270
|
// 缩略图走云端存储——dataUrl 形如 'data:image/png;base64,...'
|
|
270
271
|
async saveThumbnail(dirPath, imageDataUrl) {
|
|
271
|
-
await
|
|
272
|
+
await host.api.uploadThumbnail(dirPath, imageDataUrl)
|
|
272
273
|
},
|
|
273
274
|
async getThumbnail(dirPath) {
|
|
274
|
-
return await
|
|
275
|
+
return await host.api.fetchThumbnail(dirPath)
|
|
275
276
|
},
|
|
276
277
|
}
|
|
277
278
|
|
|
278
|
-
const
|
|
279
|
+
const hostBlank: ProjectTemplate = {
|
|
279
280
|
id: 'blank', // 同 id 覆盖内置 blank
|
|
280
|
-
name: '
|
|
281
|
-
description: '
|
|
281
|
+
name: '宿主空白工程',
|
|
282
|
+
description: '由下游宿主维护的官方空白模板',
|
|
282
283
|
source: { type: 'directory', path: '/abs/path/to/template' },
|
|
283
284
|
}
|
|
284
285
|
|
|
285
286
|
launch({
|
|
286
287
|
projectsProvider: provider,
|
|
287
|
-
projectTemplates: [
|
|
288
|
+
projectTemplates: [hostBlank],
|
|
288
289
|
builtinTemplates: ['taro-todo'], // 只保留 taro-todo,干掉默认 blank
|
|
289
290
|
customCreateProjectDialog: async ({ parentWindow }) => {
|
|
290
291
|
const win = new BrowserWindow({ parent: parentWindow, modal: true, width: 720, height: 520 })
|
|
291
|
-
await win.loadURL('https://
|
|
292
|
+
await win.loadURL('https://host.example.com/devtools/new-project')
|
|
292
293
|
return await new Promise((resolve) => {
|
|
293
|
-
win.webContents.ipc.once('
|
|
294
|
+
win.webContents.ipc.once('host:create-project:done', (_e, payload) => {
|
|
294
295
|
win.close()
|
|
295
296
|
// payload 三选一(CustomCreateProjectDialogResult):
|
|
296
297
|
// null → 用户取消
|
|
@@ -323,7 +324,7 @@ devtools 自带两个 `source`-style 模板,位于 `packages/devtools/template
|
|
|
323
324
|
- `blank` — 最小空白小程序骨架(pages/index + app.js/json/wxss + project.config.json,约 8 个文件)
|
|
324
325
|
- `taro-todo` — 复制自 `dimina/fe/example/taro-todo`,作为 Taro 编译产物的端到端演示工程
|
|
325
326
|
|
|
326
|
-
`builtinTemplates: 'none'` 排除全部内置;`builtinTemplates: ['taro-todo']` 仅保留指定 id;`projectTemplates` 同 id 注入会覆盖(例如上文 `
|
|
327
|
+
`builtinTemplates: 'none'` 排除全部内置;`builtinTemplates: ['taro-todo']` 仅保留指定 id;`projectTemplates` 同 id 注入会覆盖(例如上文 `hostBlank` 覆盖了内置 `blank`)。
|
|
327
328
|
|
|
328
329
|
### Provider 部分实现 / fallback 行为
|
|
329
330
|
|
|
@@ -547,7 +548,7 @@ instance.context.views.hostToolbar.send('host:cmd', { theme: 'dark' }) // false
|
|
|
547
548
|
|
|
548
549
|
## MiniappRuntime(宿主集成推荐契约)
|
|
549
550
|
|
|
550
|
-
|
|
551
|
+
下游宿主集成时,推荐依赖手写的稳定契约 `MiniappRuntime`,而不是整个 `WorkbenchContext`——后者携带全部内部 service 类型,内部重构会直接破坏下游编译。`asMiniappRuntime(ctx)` 恒等返回(typed view,非拷贝),宿主对 `runtime.workspace.openProject` 的 monkey-patch 会落在真实对象上:
|
|
551
552
|
|
|
552
553
|
```typescript
|
|
553
554
|
import { asMiniappRuntime, type MiniappRuntime } from '@dimina-kit/devtools'
|
|
@@ -679,7 +680,7 @@ pnpm test # 运行测试
|
|
|
679
680
|
本工具仅用于本地开发调试。当前的安全配置:
|
|
680
681
|
|
|
681
682
|
- **Workbench 窗口**(main / settings / popover overlay)均启用 `contextIsolation` 并禁用 `nodeIntegration`;renderer 通过 main preload 经 `contextBridge` 拿到 `window.devtools.ipc`,每个 IPC handler 经 sender whitelist 校验调用方
|
|
682
|
-
- **Simulator 本体** 是主进程顶层 `WebContentsView`(`nativeSimulatorView
|
|
683
|
+
- **Simulator 本体** 是主进程顶层 `WebContentsView`(`nativeSimulatorView`,per-project `persist:miniapp-<key>` partition、无 appId 时回落共享 `persist:simulator`、`nodeIntegration=false`),其 preload 由 `webPreferences.preload = cjsSiblingPreloadPath(ctx.preloadPath)` 在创建时(`attachNativeSimulator`)固定。**小程序的每页代码** 运行在该 WCV 内部 DeviceShell 托管的 render-host `<webview>` guest 里(`nodeIntegration=false`),承载逐页沙箱;guest 不带静态 `partition` 属性,由主进程 `will-attach-webview` 钉到同一 per-project partition,并钉死 contextIsolation/sandbox,渲染进程无法替换
|
|
683
684
|
- **renderer 入口** 启用了保守 CSP;`will-navigate` / `setWindowOpenHandler` 屏蔽外部跳转
|
|
684
685
|
|
|
685
686
|
仍然不要在 devtools 窗口中加载不受信任的远程内容;本工具不打算在非本地环境部署。
|
|
@@ -1276,7 +1276,7 @@ function buildDocumentUpdatedScript() {
|
|
|
1276
1276
|
return buildDispatchScript(msg);
|
|
1277
1277
|
}
|
|
1278
1278
|
var DRAIN_INTERVAL_MS = 150;
|
|
1279
|
-
var INSTALL_POLL_TRIES =
|
|
1279
|
+
var INSTALL_POLL_TRIES = 200;
|
|
1280
1280
|
var INSTALL_POLL_INTERVAL_MS = 50;
|
|
1281
1281
|
function installElementsForward(deps) {
|
|
1282
1282
|
const { devtoolsWc, bridge } = deps;
|
|
@@ -1503,6 +1503,14 @@ function installElementsForward(deps) {
|
|
|
1503
1503
|
const unsubscribeRenderEvents = bridge.onRenderEvent(onRenderEvent);
|
|
1504
1504
|
const onReady = () => {
|
|
1505
1505
|
if (disposed) return;
|
|
1506
|
+
if (installTimer) {
|
|
1507
|
+
clearInterval(installTimer);
|
|
1508
|
+
installTimer = null;
|
|
1509
|
+
}
|
|
1510
|
+
if (drainTimer) {
|
|
1511
|
+
clearInterval(drainTimer);
|
|
1512
|
+
drainTimer = null;
|
|
1513
|
+
}
|
|
1506
1514
|
let tries = 0;
|
|
1507
1515
|
installTimer = setInterval(() => {
|
|
1508
1516
|
tries++;
|
|
@@ -1537,11 +1545,8 @@ function installElementsForward(deps) {
|
|
|
1537
1545
|
});
|
|
1538
1546
|
}, DRAIN_INTERVAL_MS);
|
|
1539
1547
|
};
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
} else {
|
|
1543
|
-
onReady();
|
|
1544
|
-
}
|
|
1548
|
+
devtoolsWc.on("dom-ready", onReady);
|
|
1549
|
+
onReady();
|
|
1545
1550
|
let devtoolsClosedSub;
|
|
1546
1551
|
try {
|
|
1547
1552
|
if (deps.connections) devtoolsClosedSub = deps.connections.acquire(devtoolsWc).on("closed", stop);
|
|
@@ -1557,10 +1562,160 @@ function installElementsForward(deps) {
|
|
|
1557
1562
|
devtoolsClosedSub?.dispose();
|
|
1558
1563
|
} catch {
|
|
1559
1564
|
}
|
|
1565
|
+
try {
|
|
1566
|
+
devtoolsWc.removeListener("dom-ready", onReady);
|
|
1567
|
+
} catch {
|
|
1568
|
+
}
|
|
1560
1569
|
stop();
|
|
1561
1570
|
};
|
|
1562
1571
|
}
|
|
1563
1572
|
|
|
1573
|
+
// src/main/services/service-console/console-api.ts
|
|
1574
|
+
var RENDER_FORWARD_SOURCE_URL = "dimina://render-console-forward";
|
|
1575
|
+
var KNOWN_LEVELS = /* @__PURE__ */ new Set(["log", "warn", "error", "info", "debug"]);
|
|
1576
|
+
function mapConsoleApiType(type) {
|
|
1577
|
+
if (type === "warning") return "warn";
|
|
1578
|
+
if (type && KNOWN_LEVELS.has(type)) return type;
|
|
1579
|
+
return "log";
|
|
1580
|
+
}
|
|
1581
|
+
var BIGINT_LITERAL = /^-?\d+n$/;
|
|
1582
|
+
function remoteObjectToValue(ro) {
|
|
1583
|
+
if (!ro) return ro;
|
|
1584
|
+
if ("value" in ro) return ro.value;
|
|
1585
|
+
if (typeof ro.unserializableValue === "string") {
|
|
1586
|
+
const u = ro.unserializableValue;
|
|
1587
|
+
if (u === "Infinity") return Infinity;
|
|
1588
|
+
if (u === "-Infinity") return -Infinity;
|
|
1589
|
+
if (u === "NaN") return NaN;
|
|
1590
|
+
if (u === "-0") return -0;
|
|
1591
|
+
if (BIGINT_LITERAL.test(u)) return u;
|
|
1592
|
+
return u;
|
|
1593
|
+
}
|
|
1594
|
+
switch (ro.type) {
|
|
1595
|
+
case "undefined":
|
|
1596
|
+
return void 0;
|
|
1597
|
+
case "function":
|
|
1598
|
+
return ro.description ?? "[Function]";
|
|
1599
|
+
case "symbol":
|
|
1600
|
+
return ro.description ?? "[Symbol]";
|
|
1601
|
+
case "object":
|
|
1602
|
+
if (ro.subtype === "null") return null;
|
|
1603
|
+
return ro.description ?? "[Object]";
|
|
1604
|
+
default:
|
|
1605
|
+
return ro.description ?? "[Unknown]";
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function needsDeepFetch(ro) {
|
|
1609
|
+
if (!ro) return false;
|
|
1610
|
+
if ("value" in ro) return false;
|
|
1611
|
+
return ro.type === "object" && ro.subtype !== "null" && typeof ro.objectId === "string";
|
|
1612
|
+
}
|
|
1613
|
+
function isRenderForwardEvent(params, sentinelUrl) {
|
|
1614
|
+
const top = params?.stackTrace?.callFrames?.[0]?.url;
|
|
1615
|
+
return typeof top === "string" && top === sentinelUrl;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// src/main/services/service-console/index.ts
|
|
1619
|
+
function installServiceConsoleForward(deps) {
|
|
1620
|
+
const { serviceWc, emit, connections } = deps;
|
|
1621
|
+
let disposed = false;
|
|
1622
|
+
let selfAttached = false;
|
|
1623
|
+
let tail = Promise.resolve();
|
|
1624
|
+
let closedSub;
|
|
1625
|
+
function usableDebugger() {
|
|
1626
|
+
try {
|
|
1627
|
+
if (serviceWc.isDestroyed()) return false;
|
|
1628
|
+
if (serviceWc.debugger.isAttached()) return true;
|
|
1629
|
+
} catch {
|
|
1630
|
+
return false;
|
|
1631
|
+
}
|
|
1632
|
+
try {
|
|
1633
|
+
serviceWc.debugger.attach("1.3");
|
|
1634
|
+
selfAttached = true;
|
|
1635
|
+
return true;
|
|
1636
|
+
} catch {
|
|
1637
|
+
try {
|
|
1638
|
+
return serviceWc.debugger.isAttached();
|
|
1639
|
+
} catch {
|
|
1640
|
+
return false;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
async function resolveArg(ro) {
|
|
1645
|
+
if (!needsDeepFetch(ro)) return remoteObjectToValue(ro);
|
|
1646
|
+
try {
|
|
1647
|
+
const res = await serviceWc.debugger.sendCommand("Runtime.callFunctionOn", {
|
|
1648
|
+
objectId: ro.objectId,
|
|
1649
|
+
functionDeclaration: "function () { return this }",
|
|
1650
|
+
returnByValue: true
|
|
1651
|
+
});
|
|
1652
|
+
if (res && res.result && "value" in res.result) return res.result.value;
|
|
1653
|
+
} catch {
|
|
1654
|
+
}
|
|
1655
|
+
return remoteObjectToValue(ro);
|
|
1656
|
+
}
|
|
1657
|
+
async function handleConsoleApi(params) {
|
|
1658
|
+
if (disposed) return;
|
|
1659
|
+
if (isRenderForwardEvent(params, RENDER_FORWARD_SOURCE_URL)) return;
|
|
1660
|
+
const level = mapConsoleApiType(params.type);
|
|
1661
|
+
const rawArgs = Array.isArray(params.args) ? params.args : [];
|
|
1662
|
+
let args;
|
|
1663
|
+
try {
|
|
1664
|
+
args = await Promise.all(rawArgs.map((a) => resolveArg(a)));
|
|
1665
|
+
} catch {
|
|
1666
|
+
args = rawArgs.map((a) => remoteObjectToValue(a));
|
|
1667
|
+
}
|
|
1668
|
+
if (disposed) return;
|
|
1669
|
+
emit({ source: "service", level, args, ts: Date.now() });
|
|
1670
|
+
}
|
|
1671
|
+
const onMessage = (_event, method, params) => {
|
|
1672
|
+
if (disposed) return;
|
|
1673
|
+
if (method !== "Runtime.consoleAPICalled") return;
|
|
1674
|
+
const p = params;
|
|
1675
|
+
tail = tail.then(() => handleConsoleApi(p)).catch(() => {
|
|
1676
|
+
});
|
|
1677
|
+
};
|
|
1678
|
+
function stop() {
|
|
1679
|
+
if (disposed) return;
|
|
1680
|
+
disposed = true;
|
|
1681
|
+
try {
|
|
1682
|
+
closedSub?.dispose();
|
|
1683
|
+
} catch {
|
|
1684
|
+
}
|
|
1685
|
+
try {
|
|
1686
|
+
serviceWc.debugger.removeListener("message", onMessage);
|
|
1687
|
+
} catch {
|
|
1688
|
+
}
|
|
1689
|
+
if (selfAttached) {
|
|
1690
|
+
try {
|
|
1691
|
+
if (!serviceWc.isDestroyed() && serviceWc.debugger.isAttached()) serviceWc.debugger.detach();
|
|
1692
|
+
} catch {
|
|
1693
|
+
}
|
|
1694
|
+
selfAttached = false;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
if (!usableDebugger()) {
|
|
1698
|
+
disposed = true;
|
|
1699
|
+
return { stop() {
|
|
1700
|
+
} };
|
|
1701
|
+
}
|
|
1702
|
+
try {
|
|
1703
|
+
serviceWc.debugger.on("message", onMessage);
|
|
1704
|
+
} catch {
|
|
1705
|
+
stop();
|
|
1706
|
+
return { stop() {
|
|
1707
|
+
} };
|
|
1708
|
+
}
|
|
1709
|
+
serviceWc.debugger.sendCommand("Runtime.enable").catch(() => {
|
|
1710
|
+
});
|
|
1711
|
+
try {
|
|
1712
|
+
if (connections) closedSub = connections.acquire(serviceWc).on("closed", stop);
|
|
1713
|
+
else serviceWc.once("destroyed", stop);
|
|
1714
|
+
} catch {
|
|
1715
|
+
}
|
|
1716
|
+
return { stop };
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1564
1719
|
// src/main/services/layout/index.ts
|
|
1565
1720
|
function computeNativeSimulatorViewParams(rect, zoomPercent) {
|
|
1566
1721
|
const zoomFactor = zoomPercent / 100;
|
|
@@ -1574,12 +1729,11 @@ function computeNativeSimulatorViewParams(rect, zoomPercent) {
|
|
|
1574
1729
|
zoomFactor
|
|
1575
1730
|
};
|
|
1576
1731
|
}
|
|
1577
|
-
var SETTINGS_W = 320;
|
|
1578
1732
|
function computeSettingsBounds(contentWidth, contentHeight, headerHeight) {
|
|
1579
1733
|
return {
|
|
1580
|
-
x:
|
|
1734
|
+
x: 0,
|
|
1581
1735
|
y: headerHeight,
|
|
1582
|
-
width: Math.
|
|
1736
|
+
width: Math.max(1, contentWidth),
|
|
1583
1737
|
height: Math.max(1, contentHeight - headerHeight)
|
|
1584
1738
|
};
|
|
1585
1739
|
}
|
|
@@ -1884,6 +2038,7 @@ function createViewManager(ctx) {
|
|
|
1884
2038
|
let nativeDevtoolsSourceWc = null;
|
|
1885
2039
|
let unsubscribeNativeRenderEvents = null;
|
|
1886
2040
|
let stopElementsForward = null;
|
|
2041
|
+
let stopServiceConsole = null;
|
|
1887
2042
|
let nativeDevtoolsRetryTimer = null;
|
|
1888
2043
|
let nativeDevtoolsRetryToken = 0;
|
|
1889
2044
|
let simulatorBoundsOverride = null;
|
|
@@ -1931,6 +2086,7 @@ function createViewManager(ctx) {
|
|
|
1931
2086
|
if (!simulatorViewAdded) {
|
|
1932
2087
|
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
1933
2088
|
simulatorViewAdded = true;
|
|
2089
|
+
raiseTopOverlays();
|
|
1934
2090
|
}
|
|
1935
2091
|
simulatorView.setBounds(bounds);
|
|
1936
2092
|
}
|
|
@@ -2079,6 +2235,11 @@ function createViewManager(ctx) {
|
|
|
2079
2235
|
function closeNativeDevtoolsSource() {
|
|
2080
2236
|
const source = nativeDevtoolsSourceWc;
|
|
2081
2237
|
nativeDevtoolsSourceWc = null;
|
|
2238
|
+
try {
|
|
2239
|
+
stopServiceConsole?.();
|
|
2240
|
+
} catch {
|
|
2241
|
+
}
|
|
2242
|
+
stopServiceConsole = null;
|
|
2082
2243
|
if (!source || source.isDestroyed()) return;
|
|
2083
2244
|
try {
|
|
2084
2245
|
if (source.isDevToolsOpened()) {
|
|
@@ -2192,6 +2353,15 @@ function createViewManager(ctx) {
|
|
|
2192
2353
|
next.openDevTools({ mode: "detach", activate: false });
|
|
2193
2354
|
wireOpenInEditor(next, simulatorView.webContents);
|
|
2194
2355
|
customizeDevtoolsTabs(simulatorView.webContents);
|
|
2356
|
+
try {
|
|
2357
|
+
stopServiceConsole?.();
|
|
2358
|
+
} catch {
|
|
2359
|
+
}
|
|
2360
|
+
stopServiceConsole = installServiceConsoleForward({
|
|
2361
|
+
serviceWc: next,
|
|
2362
|
+
connections: ctx.connections,
|
|
2363
|
+
emit: (entry) => ctx.consoleForwarder?.emit(entry)
|
|
2364
|
+
}).stop;
|
|
2195
2365
|
return true;
|
|
2196
2366
|
} catch {
|
|
2197
2367
|
if (nativeDevtoolsSourceWc?.id === next.id) {
|
|
@@ -2488,6 +2658,7 @@ function createViewManager(ctx) {
|
|
|
2488
2658
|
}
|
|
2489
2659
|
});
|
|
2490
2660
|
applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
|
|
2661
|
+
settingsView.setBackgroundColor("#00000000");
|
|
2491
2662
|
await settingsView.webContents.loadFile(
|
|
2492
2663
|
path7.join(ctx.rendererDir, "entries/settings/index.html")
|
|
2493
2664
|
);
|
|
@@ -2507,6 +2678,12 @@ function createViewManager(ctx) {
|
|
|
2507
2678
|
settingsViewAdded = false;
|
|
2508
2679
|
}
|
|
2509
2680
|
}
|
|
2681
|
+
function raiseTopOverlays() {
|
|
2682
|
+
if (ctx.windows.mainWindow.isDestroyed()) return;
|
|
2683
|
+
const cv = ctx.windows.mainWindow.contentView;
|
|
2684
|
+
if (settingsView && settingsViewAdded) cv.addChildView(settingsView);
|
|
2685
|
+
if (popoverView) cv.addChildView(popoverView);
|
|
2686
|
+
}
|
|
2510
2687
|
function showPopover(data) {
|
|
2511
2688
|
hidePopover();
|
|
2512
2689
|
const popover = new WebContentsView2({
|
|
@@ -2572,6 +2749,7 @@ function createViewManager(ctx) {
|
|
|
2572
2749
|
if (!nativeSimulatorViewAdded) {
|
|
2573
2750
|
ctx.windows.mainWindow.contentView.addChildView(nativeSimulatorView);
|
|
2574
2751
|
nativeSimulatorViewAdded = true;
|
|
2752
|
+
raiseTopOverlays();
|
|
2575
2753
|
}
|
|
2576
2754
|
nativeSimulatorView.setBounds(p.bounds);
|
|
2577
2755
|
const simWc = nativeSimulatorView.webContents;
|
|
@@ -4301,7 +4479,8 @@ var FORWARDABLE_LEVELS = /* @__PURE__ */ new Set(["log", "warn", "error", "info"
|
|
|
4301
4479
|
function buildForwardScript(level, args) {
|
|
4302
4480
|
const method = FORWARDABLE_LEVELS.has(level) ? level : "log";
|
|
4303
4481
|
const argsJson = JSON.stringify(args ?? []);
|
|
4304
|
-
return `(()=>{try{const a=JSON.parse(${JSON.stringify(argsJson)});console[${JSON.stringify(method)}]('[\u89C6\u56FE]',...a)}catch(_){}})()
|
|
4482
|
+
return `(()=>{try{const a=JSON.parse(${JSON.stringify(argsJson)});console[${JSON.stringify(method)}]('[\u89C6\u56FE]',...a)}catch(_){}})()
|
|
4483
|
+
//# sourceURL=${RENDER_FORWARD_SOURCE_URL}`;
|
|
4305
4484
|
}
|
|
4306
4485
|
function createConsoleForwarder(bridge) {
|
|
4307
4486
|
const sinks = /* @__PURE__ */ new Set();
|
|
@@ -4836,6 +5015,27 @@ function findSimulatorWebContents() {
|
|
|
4836
5015
|
}
|
|
4837
5016
|
|
|
4838
5017
|
// src/main/ipc/bridge-router.ts
|
|
5018
|
+
function rewriteSourceMappingUrl(source, scriptUrl) {
|
|
5019
|
+
if (typeof source !== "string" || !source) return source;
|
|
5020
|
+
const re = /(^|\n)[ \t]*\/\/[#@][ \t]*sourceMappingURL=([^\n]*)/g;
|
|
5021
|
+
let lastIndex = -1;
|
|
5022
|
+
let lastValue = "";
|
|
5023
|
+
let m;
|
|
5024
|
+
while ((m = re.exec(source)) !== null) {
|
|
5025
|
+
lastIndex = m.index + m[1].length;
|
|
5026
|
+
lastValue = (m[2] || "").trim();
|
|
5027
|
+
}
|
|
5028
|
+
if (lastIndex < 0 || !lastValue) return source;
|
|
5029
|
+
if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(lastValue) || /^data:/i.test(lastValue)) return source;
|
|
5030
|
+
let absolute;
|
|
5031
|
+
try {
|
|
5032
|
+
absolute = new URL(lastValue, scriptUrl).toString();
|
|
5033
|
+
} catch {
|
|
5034
|
+
return source;
|
|
5035
|
+
}
|
|
5036
|
+
return `${source.slice(0, lastIndex)}
|
|
5037
|
+
//# sourceMappingURL=${absolute}`;
|
|
5038
|
+
}
|
|
4839
5039
|
var STACK_ID = "stack_0";
|
|
4840
5040
|
var PREWARM_MAX_POOL_SIZE = 4;
|
|
4841
5041
|
function resolvePrewarmPoolSize() {
|
|
@@ -5365,7 +5565,7 @@ async function injectLogicBundle(ap) {
|
|
|
5365
5565
|
try {
|
|
5366
5566
|
const res = await fetch(logicUrl);
|
|
5367
5567
|
if (!res.ok) throw new Error(`logic.js fetch ${res.status} at ${logicUrl}`);
|
|
5368
|
-
const logicContent = await res.text();
|
|
5568
|
+
const logicContent = rewriteSourceMappingUrl(await res.text(), logicUrl);
|
|
5369
5569
|
await ap.serviceWc.executeJavaScript(`${logicContent}
|
|
5370
5570
|
//# sourceURL=${logicUrl}`, true);
|
|
5371
5571
|
} catch (error) {
|
|
@@ -8702,7 +8902,7 @@ var UpdateManager = class {
|
|
|
8702
8902
|
downloadedPath = null;
|
|
8703
8903
|
/**
|
|
8704
8904
|
* Whether this instance has already emitted `Available` once. The periodic
|
|
8705
|
-
* check fires forever (interval default 1h), and downstream UI (
|
|
8905
|
+
* check fires forever (interval default 1h), and downstream UI (a downstream host shell
|
|
8706
8906
|
* renders a native toast / Notification) is not idempotent — every event
|
|
8707
8907
|
* stacks. We dedupe at the source: at most one Available per instance
|
|
8708
8908
|
* lifetime, regardless of how the upstream `info.version` shifts. The user
|
|
@@ -11,6 +11,46 @@ import { ServiceHostPool } from '../services/service-host-pool/pool.js';
|
|
|
11
11
|
import { registerMiniappSessionConfigurator, SHARED_MINIAPP_PARTITION, } from '../services/views/miniapp-partition.js';
|
|
12
12
|
import { createConsoleForwarder } from '../services/console-forward/index.js';
|
|
13
13
|
import { STORAGE_API_NAMES } from '../services/simulator-storage/index.js';
|
|
14
|
+
// The compiled `logic.js` ships a RELATIVE `//# sourceMappingURL=logic.js.map`.
|
|
15
|
+
// `injectLogicBundle` loads it via `executeJavaScript`, which gives the injected
|
|
16
|
+
// script no base URL of its own — so DevTools resolves that relative map against
|
|
17
|
+
// the service-host DOCUMENT and 404s, leaving console frames / Sources links
|
|
18
|
+
// pointing at the compiled bundle instead of the developer's source. This rewrite
|
|
19
|
+
// turns the directive absolute.
|
|
20
|
+
//
|
|
21
|
+
// Inlined (not imported from `service-host/sourcemap-rewrite.cjs`) ON PURPOSE:
|
|
22
|
+
// the shipped main entry is the FLAT esbuild bundle (`dist/main/index.bundle.js`,
|
|
23
|
+
// see electron-builder.yml `extraMetadata.main`). A runtime `createRequire(...)`
|
|
24
|
+
// of the relative `.cjs` resolved against `import.meta.url`, which is the bundle
|
|
25
|
+
// at `dist/main/` (one level shallower than the tsc tree `dist/main/ipc/`), so
|
|
26
|
+
// the path 404'd and the PACKAGED app crashed on load. Bundling the logic inline
|
|
27
|
+
// is bundler-proof. KEEP IN SYNC with `service-host/sourcemap-rewrite.cjs` (the
|
|
28
|
+
// copied-not-bundled service-host preload still requires that one).
|
|
29
|
+
function rewriteSourceMappingUrl(source, scriptUrl) {
|
|
30
|
+
if (typeof source !== 'string' || !source)
|
|
31
|
+
return source;
|
|
32
|
+
const re = /(^|\n)[ \t]*\/\/[#@][ \t]*sourceMappingURL=([^\n]*)/g;
|
|
33
|
+
let lastIndex = -1;
|
|
34
|
+
let lastValue = '';
|
|
35
|
+
let m;
|
|
36
|
+
while ((m = re.exec(source)) !== null) {
|
|
37
|
+
lastIndex = m.index + m[1].length;
|
|
38
|
+
lastValue = (m[2] || '').trim();
|
|
39
|
+
}
|
|
40
|
+
if (lastIndex < 0 || !lastValue)
|
|
41
|
+
return source;
|
|
42
|
+
// Already absolute (scheme://, protocol-relative //host, or data:)? Leave it.
|
|
43
|
+
if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(lastValue) || /^data:/i.test(lastValue))
|
|
44
|
+
return source;
|
|
45
|
+
let absolute;
|
|
46
|
+
try {
|
|
47
|
+
absolute = new URL(lastValue, scriptUrl).toString();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return source;
|
|
51
|
+
}
|
|
52
|
+
return `${source.slice(0, lastIndex)}\n//# sourceMappingURL=${absolute}`;
|
|
53
|
+
}
|
|
14
54
|
const STACK_ID = 'stack_0';
|
|
15
55
|
/** Hard ceiling on the pre-warm pool, mirroring ServiceHostPool/doc §3.3. */
|
|
16
56
|
const PREWARM_MAX_POOL_SIZE = 4;
|
|
@@ -690,7 +730,10 @@ async function injectLogicBundle(ap) {
|
|
|
690
730
|
const res = await fetch(logicUrl);
|
|
691
731
|
if (!res.ok)
|
|
692
732
|
throw new Error(`logic.js fetch ${res.status} at ${logicUrl}`);
|
|
693
|
-
|
|
733
|
+
// Rewrite the relative `sourceMappingURL` to an absolute dev-server URL
|
|
734
|
+
// BEFORE injecting — `executeJavaScript` gives the script no base URL, so a
|
|
735
|
+
// relative map would 404 and break sourcemapped console frames / Sources.
|
|
736
|
+
const logicContent = rewriteSourceMappingUrl(await res.text(), logicUrl);
|
|
694
737
|
await ap.serviceWc.executeJavaScript(`${logicContent}\n//# sourceURL=${logicUrl}`, true);
|
|
695
738
|
}
|
|
696
739
|
catch (error) {
|
|
@@ -11,7 +11,7 @@ import type { WorkbenchAppConfig } from '../../shared/types.js';
|
|
|
11
11
|
* v2 scope: the backend owns window creation (the framework skips its own when a
|
|
12
12
|
* backend is present), so `assemble` ignores the framework `runtime` and builds
|
|
13
13
|
* the real devtools main window itself. Trust/frame unification and surfacing
|
|
14
|
-
* the runtime facade are deferred
|
|
14
|
+
* the runtime facade are deferred.
|
|
15
15
|
*
|
|
16
16
|
* NOT migrating onto deck's high-level host API (Window facade / runtime.view /
|
|
17
17
|
* DeckSession / grants) is a DELIBERATE, reviewed decision — not a backlog item.
|
|
@@ -11,7 +11,7 @@ import { registerAppLifecycle } from '../app/lifecycle.js';
|
|
|
11
11
|
* v2 scope: the backend owns window creation (the framework skips its own when a
|
|
12
12
|
* backend is present), so `assemble` ignores the framework `runtime` and builds
|
|
13
13
|
* the real devtools main window itself. Trust/frame unification and surfacing
|
|
14
|
-
* the runtime facade are deferred
|
|
14
|
+
* the runtime facade are deferred.
|
|
15
15
|
*
|
|
16
16
|
* NOT migrating onto deck's high-level host API (Window facade / runtime.view /
|
|
17
17
|
* DeckSession / grants) is a DELIBERATE, reviewed decision — not a backlog item.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* would drag every nested internal service type (ViewManager,
|
|
7
7
|
* BridgeRouterHandle, SimulatorApiRegistry, Electron WebContents…) onto the
|
|
8
8
|
* public semver face, so any internal refactor of those services becomes an
|
|
9
|
-
* unreviewed breaking change for downstream hosts
|
|
9
|
+
* unreviewed breaking change for downstream hosts. Instead this module
|
|
10
10
|
* names ONLY the audited downstream consumption surface, with structural DTOs
|
|
11
11
|
* and zero Electron types — non-Electron consumers can compile against it.
|
|
12
12
|
*
|
|
@@ -171,7 +171,7 @@ export interface MiniappWorkspace {
|
|
|
171
171
|
} | null;
|
|
172
172
|
}
|
|
173
173
|
/**
|
|
174
|
-
* The stable miniapp-kernel surface a downstream host
|
|
174
|
+
* The stable miniapp-kernel surface a downstream host consumes.
|
|
175
175
|
* Compiler-enforced and versionable: widening it back toward internal
|
|
176
176
|
* plumbing is a deliberate semver decision, not an accident of projection.
|
|
177
177
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DisposableRegistry, toDisposable } from '@dimina-kit/electron-deck/main';
|
|
2
|
+
import { RENDER_FORWARD_SOURCE_URL } from '../service-console/console-api.js';
|
|
2
3
|
/** Console levels we re-emit into the service host. Anything else maps to 'log'. */
|
|
3
4
|
const FORWARDABLE_LEVELS = new Set(['log', 'warn', 'error', 'info', 'debug']);
|
|
4
5
|
/**
|
|
@@ -16,7 +17,13 @@ function buildForwardScript(level, args) {
|
|
|
16
17
|
const argsJson = JSON.stringify(args ?? []);
|
|
17
18
|
// `console[method]` (not `console.log`) preserves the original level so the
|
|
18
19
|
// DevTools severity filter still works on the forwarded line.
|
|
19
|
-
|
|
20
|
+
//
|
|
21
|
+
// The trailing `//# sourceURL` stamps this injected line with a sentinel URL.
|
|
22
|
+
// The service-console CDP capture (services/service-console) attached to THIS
|
|
23
|
+
// service host would otherwise re-capture this `console[...]` call as a fresh
|
|
24
|
+
// service entry and re-broadcast it to automation (duplicate). It skips any
|
|
25
|
+
// `consoleAPICalled` whose top frame URL === RENDER_FORWARD_SOURCE_URL.
|
|
26
|
+
return `(()=>{try{const a=JSON.parse(${JSON.stringify(argsJson)});console[${JSON.stringify(method)}]('[视图]',...a)}catch(_){}})()\n//# sourceURL=${RENDER_FORWARD_SOURCE_URL}`;
|
|
20
27
|
}
|
|
21
28
|
export function createConsoleForwarder(bridge) {
|
|
22
29
|
const sinks = new Set();
|
|
@@ -173,7 +173,11 @@ function buildDocumentUpdatedScript() {
|
|
|
173
173
|
}
|
|
174
174
|
const DRAIN_INTERVAL_MS = 150;
|
|
175
175
|
/** Bounded retry for the front-end hook install (front-end boots asynchronously). */
|
|
176
|
-
|
|
176
|
+
// Per-load install poll window. Must outlast the gap between a devtools
|
|
177
|
+
// front-end `dom-ready` and `InspectorFrontendHost` becoming available on a
|
|
178
|
+
// cold/slow open (pool cold-start). 200×50ms = 10s — generous, and each future
|
|
179
|
+
// front-end load re-arms a fresh window anyway (see the `dom-ready` listener).
|
|
180
|
+
const INSTALL_POLL_TRIES = 200;
|
|
177
181
|
const INSTALL_POLL_INTERVAL_MS = 50;
|
|
178
182
|
/**
|
|
179
183
|
* Install Elements forwarding on a DevTools front-end host wc. Returns a disposer
|
|
@@ -504,6 +508,17 @@ export function installElementsForward(deps) {
|
|
|
504
508
|
const onReady = () => {
|
|
505
509
|
if (disposed)
|
|
506
510
|
return;
|
|
511
|
+
// Re-arm safe: a devtools front-end (re)load re-runs this, so clear any
|
|
512
|
+
// in-flight timers from a prior attempt before starting fresh ones — without
|
|
513
|
+
// this a reload would stack intervals (leak) and double-drain.
|
|
514
|
+
if (installTimer) {
|
|
515
|
+
clearInterval(installTimer);
|
|
516
|
+
installTimer = null;
|
|
517
|
+
}
|
|
518
|
+
if (drainTimer) {
|
|
519
|
+
clearInterval(drainTimer);
|
|
520
|
+
drainTimer = null;
|
|
521
|
+
}
|
|
507
522
|
let tries = 0;
|
|
508
523
|
installTimer = setInterval(() => {
|
|
509
524
|
tries++;
|
|
@@ -550,12 +565,17 @@ export function installElementsForward(deps) {
|
|
|
550
565
|
.catch(() => { });
|
|
551
566
|
}, DRAIN_INTERVAL_MS);
|
|
552
567
|
};
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
568
|
+
// Re-install on EVERY devtools front-end load, not once. The hook lives in the
|
|
569
|
+
// front-end's `globalThis` (`__diminaElementsHookInstalled`), so any front-end
|
|
570
|
+
// (re)load wipes it — most importantly a service-host pool SWAP, which re-opens
|
|
571
|
+
// DevTools (`pointNativeDevtoolsAtServiceWc`) and re-bootstraps the front-end.
|
|
572
|
+
// A single-shot `once('dom-ready')` left the hook uninstalled after such a
|
|
573
|
+
// reload, so Elements silently fell back to the native service-host DOM. A
|
|
574
|
+
// PERSISTENT listener re-runs the (idempotent — the sentinel returns 'already')
|
|
575
|
+
// install on each load; the immediate call covers an already-loaded wc and the
|
|
576
|
+
// first `openDevTools` whose `dom-ready` may have fired before this ran.
|
|
577
|
+
devtoolsWc.on('dom-ready', onReady);
|
|
578
|
+
onReady();
|
|
559
579
|
let devtoolsClosedSub;
|
|
560
580
|
try {
|
|
561
581
|
// Devtools front-end wc is never pool-reset → use `'closed'` (fires only on
|
|
@@ -577,6 +597,10 @@ export function installElementsForward(deps) {
|
|
|
577
597
|
devtoolsClosedSub?.dispose();
|
|
578
598
|
}
|
|
579
599
|
catch { /* already gone */ }
|
|
600
|
+
try {
|
|
601
|
+
devtoolsWc.removeListener('dom-ready', onReady);
|
|
602
|
+
}
|
|
603
|
+
catch { /* already gone */ }
|
|
580
604
|
stop();
|
|
581
605
|
};
|
|
582
606
|
}
|