@dimina-kit/devtools 0.3.1 → 0.3.2-dev.20260522085907
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +297 -133
- package/dist/main/api.d.ts +6 -2
- package/dist/main/api.js +5 -2
- package/dist/main/app/app.d.ts +1 -1
- package/dist/main/app/app.js +84 -36
- package/dist/main/app/bootstrap.js +2 -1
- package/dist/main/app/launch.d.ts +2 -2
- package/dist/main/app/launch.js +10 -8
- package/dist/main/index.bundle.js +1567 -477
- package/dist/main/ipc/app.d.ts +2 -1
- package/dist/main/ipc/app.js +5 -4
- package/dist/main/ipc/index.d.ts +5 -4
- package/dist/main/ipc/index.js +5 -4
- package/dist/main/ipc/panels.d.ts +2 -1
- package/dist/main/ipc/panels.js +16 -13
- package/dist/main/ipc/popover.d.ts +4 -1
- package/dist/main/ipc/popover.js +15 -5
- package/dist/main/ipc/projects.d.ts +6 -1
- package/dist/main/ipc/projects.js +127 -6
- package/dist/main/ipc/session.d.ts +4 -1
- package/dist/main/ipc/session.js +34 -6
- package/dist/main/ipc/settings.d.ts +4 -1
- package/dist/main/ipc/settings.js +51 -23
- package/dist/main/ipc/simulator-module.d.ts +8 -0
- package/dist/main/ipc/simulator-module.js +19 -0
- package/dist/main/ipc/simulator.d.ts +2 -1
- package/dist/main/ipc/simulator.js +23 -18
- package/dist/main/ipc/toolbar.d.ts +2 -1
- package/dist/main/ipc/toolbar.js +3 -2
- package/dist/main/menu/index.js +11 -0
- package/dist/main/services/automation/exec.d.ts +2 -0
- package/dist/main/services/automation/exec.js +7 -9
- package/dist/main/services/automation/handlers/app.js +37 -51
- package/dist/main/services/automation/handlers/element.js +27 -41
- package/dist/main/services/automation/index.js +88 -13
- package/dist/main/services/default-adapter.js +3 -3
- package/dist/main/services/mcp/index.d.ts +1 -0
- package/dist/main/services/mcp/index.js +1 -0
- package/dist/main/services/mcp/server.d.ts +2 -1
- package/dist/main/services/mcp/server.js +10 -0
- package/dist/main/services/mcp/status.d.ts +10 -0
- package/dist/main/services/mcp/status.js +14 -0
- package/dist/main/services/mcp/target-manager.js +2 -1
- package/dist/main/services/module.d.ts +18 -0
- package/dist/main/services/module.js +2 -0
- package/dist/main/services/notifications/renderer-notifier.d.ts +5 -3
- package/dist/main/services/notifications/renderer-notifier.js +3 -6
- package/dist/main/services/projects/builtin-templates.d.ts +3 -0
- package/dist/main/services/projects/builtin-templates.js +24 -0
- package/dist/main/services/projects/create-project-service.d.ts +7 -0
- package/dist/main/services/projects/create-project-service.js +84 -0
- package/dist/main/services/projects/index.d.ts +7 -0
- package/dist/main/services/projects/index.js +9 -0
- package/dist/main/services/projects/local-provider.d.ts +3 -0
- package/dist/main/services/projects/local-provider.js +25 -0
- package/dist/main/services/projects/project-repository.d.ts +1 -1
- package/dist/main/services/projects/project-repository.js +3 -0
- package/dist/main/services/projects/templates.d.ts +21 -0
- package/dist/main/services/projects/templates.js +39 -0
- package/dist/main/services/projects/thumbnail.d.ts +8 -0
- package/dist/main/services/projects/thumbnail.js +37 -0
- package/dist/main/services/projects/types.d.ts +118 -0
- package/dist/main/services/projects/types.js +19 -0
- package/dist/main/services/settings/index.d.ts +11 -0
- package/dist/main/services/settings/index.js +18 -2
- package/dist/main/services/simulator/custom-apis.d.ts +10 -0
- package/dist/main/services/simulator/custom-apis.js +26 -0
- package/dist/main/services/simulator-storage/index.d.ts +22 -1
- package/dist/main/services/simulator-storage/index.js +334 -97
- package/dist/main/services/update/update-manager.d.ts +16 -1
- package/dist/main/services/update/update-manager.js +18 -15
- package/dist/main/services/views/view-manager.d.ts +7 -7
- package/dist/main/services/views/view-manager.js +53 -29
- package/dist/main/services/window-service.d.ts +24 -0
- package/dist/main/services/window-service.js +26 -0
- package/dist/main/services/workbench-context.d.ts +49 -2
- package/dist/main/services/workbench-context.js +15 -2
- package/dist/main/services/workspace/workspace-service.d.ts +9 -6
- package/dist/main/services/workspace/workspace-service.js +71 -11
- package/dist/main/simulator-apis.d.ts +23 -0
- package/dist/main/simulator-apis.js +24 -0
- package/dist/main/utils/disposable.d.ts +13 -0
- package/dist/main/utils/disposable.js +60 -0
- package/dist/main/utils/ipc-registry.d.ts +39 -0
- package/dist/main/utils/ipc-registry.js +109 -0
- package/dist/main/utils/ipc-schema.d.ts +28 -0
- package/dist/main/utils/ipc-schema.js +38 -0
- package/dist/main/utils/paths.d.ts +6 -0
- package/dist/main/utils/paths.js +6 -0
- package/dist/main/utils/sender-policy.d.ts +25 -0
- package/dist/main/utils/sender-policy.js +44 -0
- package/dist/main/utils/theme.d.ts +24 -0
- package/dist/main/utils/theme.js +38 -1
- package/dist/main/windows/main-window/create.d.ts +6 -0
- package/dist/main/windows/main-window/create.js +66 -10
- package/dist/main/windows/main-window/events.d.ts +2 -1
- package/dist/main/windows/main-window/events.js +25 -2
- package/dist/main/windows/navigation-hardening.d.ts +24 -0
- package/dist/main/windows/navigation-hardening.js +55 -0
- package/dist/main/windows/settings-window/create.js +11 -1
- package/dist/preload/index.d.ts +22 -0
- package/dist/preload/index.js +19 -0
- package/dist/preload/instrumentation/app-data.d.ts +32 -2
- package/dist/preload/instrumentation/app-data.js +253 -79
- package/dist/preload/instrumentation/index.d.ts +2 -2
- package/dist/preload/instrumentation/index.js +2 -2
- package/dist/preload/instrumentation/wxml.d.ts +16 -4
- package/dist/preload/instrumentation/wxml.js +188 -178
- package/dist/preload/miniapp-snapshot/host.d.ts +12 -0
- package/dist/preload/miniapp-snapshot/host.js +117 -0
- package/dist/preload/miniapp-snapshot/types.d.ts +62 -0
- package/dist/preload/miniapp-snapshot/types.js +15 -0
- package/dist/preload/runtime/bridge.js +5 -24
- package/dist/preload/runtime/custom-apis.d.ts +6 -0
- package/dist/preload/runtime/custom-apis.js +43 -0
- package/dist/preload/runtime/host.d.ts +1 -0
- package/dist/preload/runtime/host.js +13 -0
- package/dist/preload/shared/expose.d.ts +15 -0
- package/dist/preload/shared/expose.js +33 -0
- package/dist/preload/shared/page-iframe.d.ts +3 -0
- package/dist/preload/shared/page-iframe.js +6 -0
- package/dist/preload/shared/types.d.ts +2 -0
- package/dist/preload/windows/main.cjs +47 -0
- package/dist/preload/windows/main.cjs.map +7 -0
- package/dist/preload/windows/simulator.js +501 -272
- package/dist/renderer/assets/constants-DI382mTP.js +2 -0
- package/dist/renderer/assets/index-D8VFYBh-.js +46 -0
- package/dist/renderer/assets/{input-DxfYnzX_.js → input-CoWcfa8u.js} +2 -2
- package/dist/renderer/assets/ipc-transport-Cd2xPrsa.js +9 -0
- package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +1 -0
- package/dist/renderer/assets/popover-Nq5gT9DO.js +2 -0
- package/dist/renderer/assets/select-By5KH-UG.js +2 -0
- package/dist/renderer/assets/{settings-DteGraEU.js → settings-Dj_N70qR.js} +2 -2
- package/dist/renderer/assets/settings-api-Do0_Smq1.js +2 -0
- package/dist/renderer/assets/workbenchSettings-CazSLGYt.js +8 -0
- package/dist/renderer/entries/main/index.html +13 -5
- package/dist/renderer/entries/popover/index.html +8 -5
- package/dist/renderer/entries/settings/index.html +7 -5
- package/dist/renderer/entries/workbench-settings/index.html +7 -4
- package/dist/shared/ipc-channels.d.ts +46 -13
- package/dist/shared/ipc-channels.js +60 -14
- package/dist/shared/ipc-schemas.d.ts +118 -0
- package/dist/shared/ipc-schemas.js +133 -0
- package/dist/shared/simulator-route.d.ts +72 -0
- package/dist/shared/simulator-route.js +169 -0
- package/dist/shared/types.d.ts +82 -8
- package/dist/simulator/assets/simulator-DJCozrU5.js +9 -0
- package/dist/simulator/simulator.html +6 -43
- package/package.json +18 -29
- package/templates/blank/app.js +6 -0
- package/templates/blank/app.json +11 -0
- package/templates/blank/app.wxss +5 -0
- package/templates/blank/pages/index/index.js +6 -0
- package/templates/blank/pages/index/index.json +3 -0
- package/templates/blank/pages/index/index.wxml +3 -0
- package/templates/blank/pages/index/index.wxss +11 -0
- package/templates/blank/project.config.json +13 -0
- package/templates/blank/sitemap.json +9 -0
- package/templates/taro-todo/app.js +1 -0
- package/templates/taro-todo/app.json +1 -0
- package/templates/taro-todo/app.wxss +1 -0
- package/templates/taro-todo/base.wxml +851 -0
- package/templates/taro-todo/common.js +1 -0
- package/templates/taro-todo/comp.js +1 -0
- package/templates/taro-todo/comp.json +1 -0
- package/templates/taro-todo/comp.wxml +2 -0
- package/templates/taro-todo/custom-wrapper.js +1 -0
- package/templates/taro-todo/custom-wrapper.json +1 -0
- package/templates/taro-todo/custom-wrapper.wxml +4 -0
- package/templates/taro-todo/pages/index/index.js +2 -0
- package/templates/taro-todo/pages/index/index.json +1 -0
- package/templates/taro-todo/pages/index/index.wxml +2 -0
- package/templates/taro-todo/pages/index/index.wxss +1 -0
- package/templates/taro-todo/project.config.json +44 -0
- package/templates/taro-todo/runtime.js +1 -0
- package/templates/taro-todo/taro.js +2 -0
- package/templates/taro-todo/utils.wxs +39 -0
- package/templates/taro-todo/vendors.js +2 -0
- package/dist/preload/instrumentation/disposable.d.ts +0 -11
- package/dist/preload/instrumentation/disposable.js +0 -15
- package/dist/preload/windows/main.d.ts +0 -6
- package/dist/preload/windows/main.js +0 -6
- package/dist/renderer/assets/index-D1p6crtf.js +0 -46
- package/dist/renderer/assets/ipc-transport-ClIvDcYY.js +0 -9
- package/dist/renderer/assets/ipc-transport-EiWxu_hG.css +0 -1
- package/dist/renderer/assets/popover-CtXcBJtM.js +0 -2
- package/dist/renderer/assets/select-DMfp49f1.js +0 -2
- package/dist/renderer/assets/settings-api-CY84UMpN.js +0 -2
- package/dist/renderer/assets/workbenchSettings-DjELLnSJ.js +0 -8
- package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import { webContents } from 'electron';
|
|
2
1
|
// ── Helpers ───────────────────────────────────────────────────────────
|
|
3
2
|
export function getSimulator(ctx) {
|
|
4
|
-
|
|
5
|
-
if (!ctx.workspace.hasActiveSession() || simWcId == null)
|
|
3
|
+
if (!ctx.workspace.hasActiveSession())
|
|
6
4
|
return null;
|
|
7
|
-
|
|
8
|
-
if (!sim || sim.isDestroyed())
|
|
9
|
-
return null;
|
|
10
|
-
return sim;
|
|
5
|
+
return ctx.views.getSimulatorWebContents();
|
|
11
6
|
}
|
|
12
7
|
export async function evalInSim(ctx, expression) {
|
|
13
8
|
const sim = getSimulator(ctx);
|
|
@@ -17,8 +12,11 @@ export async function evalInSim(ctx, expression) {
|
|
|
17
12
|
}
|
|
18
13
|
/** Build JS that locates an element by selector+index inside the active page iframe. */
|
|
19
14
|
export function buildElAccess(ref, varName = 'el') {
|
|
20
|
-
const
|
|
21
|
-
|
|
15
|
+
return `const ${varName} = _doc.querySelectorAll(${JSON.stringify(ref.selector)})[${ref.index}]`;
|
|
16
|
+
}
|
|
17
|
+
/** Run `body` inside the active page iframe with `varName` bound to the element at `ref`. */
|
|
18
|
+
export function evalInElement(ctx, ref, body, varName = 'el') {
|
|
19
|
+
return evalInSim(ctx, inIframe(`${buildElAccess(ref, varName)}\n${body}`));
|
|
22
20
|
}
|
|
23
21
|
/** Wrap code to run inside the active page iframe (last iframe in the stack). */
|
|
24
22
|
export function inIframe(code) {
|
|
@@ -1,59 +1,36 @@
|
|
|
1
1
|
import { evalInSim, getSimulator, inIframe } from '../exec.js';
|
|
2
|
+
import { parseLocationRoute } from '../../../../shared/simulator-route.js';
|
|
2
3
|
export const appHandlers = {};
|
|
3
4
|
// -- App domain --
|
|
5
|
+
async function readRoute(ctx) {
|
|
6
|
+
const { search } = await evalInSim(ctx, `({ search: location.search })`);
|
|
7
|
+
return parseLocationRoute(search);
|
|
8
|
+
}
|
|
4
9
|
appHandlers['App.getCurrentPage'] = async (ctx) => {
|
|
5
|
-
const
|
|
6
|
-
const clean = hash.replace(/^#/, '');
|
|
7
|
-
const qIdx = clean.indexOf('?');
|
|
8
|
-
const pathPart = qIdx >= 0 ? clean.substring(0, qIdx) : clean;
|
|
9
|
-
const queryPart = qIdx >= 0 ? clean.substring(qIdx + 1) : '';
|
|
10
|
-
// New format: #appid|page1|page2 — last segment is current page
|
|
11
|
-
let pagePath;
|
|
12
|
-
if (pathPart.includes('|')) {
|
|
13
|
-
const parts = pathPart.split('|');
|
|
14
|
-
pagePath = parts[parts.length - 1] ?? '';
|
|
15
|
-
}
|
|
16
|
-
else {
|
|
17
|
-
// Legacy format: #appid/pagePath
|
|
18
|
-
const slashIdx = pathPart.indexOf('/');
|
|
19
|
-
pagePath = slashIdx >= 0 ? pathPart.substring(slashIdx + 1) : pathPart;
|
|
20
|
-
}
|
|
21
|
-
const query = {};
|
|
22
|
-
if (queryPart) {
|
|
23
|
-
for (const pair of queryPart.split('&')) {
|
|
24
|
-
const [k, v] = pair.split('=');
|
|
25
|
-
if (k)
|
|
26
|
-
query[k] = decodeURIComponent(v || '');
|
|
27
|
-
}
|
|
28
|
-
}
|
|
10
|
+
const route = await readRoute(ctx);
|
|
29
11
|
const iframeCount = await evalInSim(ctx, `document.querySelectorAll('iframe').length`);
|
|
30
|
-
return {
|
|
12
|
+
return {
|
|
13
|
+
pageId: iframeCount,
|
|
14
|
+
path: route?.current.pagePath ?? '',
|
|
15
|
+
query: route?.current.query ?? {},
|
|
16
|
+
};
|
|
31
17
|
};
|
|
32
18
|
appHandlers['App.getPageStack'] = async (ctx) => {
|
|
33
|
-
const
|
|
34
|
-
const clean = hash.replace(/^#/, '').replace(/\?.*$/, '');
|
|
19
|
+
const route = await readRoute(ctx);
|
|
35
20
|
const pageStack = [];
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
// Legacy format: #appid/pagePath — single page
|
|
45
|
-
const slashIdx = clean.indexOf('/');
|
|
46
|
-
const currentPath = slashIdx >= 0 ? clean.substring(slashIdx + 1) : clean;
|
|
47
|
-
if (currentPath) {
|
|
48
|
-
pageStack.push({ pageId: 1, path: currentPath, query: {} });
|
|
49
|
-
}
|
|
21
|
+
if (!route)
|
|
22
|
+
return { pageStack };
|
|
23
|
+
// Mirrors upstream HashRouter.parseSearch: `[entry]` or `[entry, current]`.
|
|
24
|
+
pageStack.push({ pageId: 1, path: route.entry.pagePath, query: route.entry.query });
|
|
25
|
+
if (route.current.pagePath !== route.entry.pagePath) {
|
|
26
|
+
pageStack.push({ pageId: 2, path: route.current.pagePath, query: route.current.query });
|
|
50
27
|
}
|
|
51
28
|
return { pageStack };
|
|
52
29
|
};
|
|
53
30
|
appHandlers['App.callWxMethod'] = async (ctx, params) => {
|
|
54
31
|
const method = params.method;
|
|
55
32
|
const args = params.args || [];
|
|
56
|
-
// For navigation methods, handle specially via
|
|
33
|
+
// For navigation methods, handle specially via DOM click / wx.* on the iframe
|
|
57
34
|
if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].includes(method)) {
|
|
58
35
|
const opts = args[0];
|
|
59
36
|
const url = opts?.url;
|
|
@@ -61,20 +38,29 @@ appHandlers['App.callWxMethod'] = async (ctx, params) => {
|
|
|
61
38
|
const cleanUrl = url.startsWith('/') ? url : `/${url}`;
|
|
62
39
|
// Try clicking a matching DOM element first
|
|
63
40
|
const clicked = await evalInSim(ctx, inIframe(`
|
|
64
|
-
const
|
|
41
|
+
const path = ${JSON.stringify(cleanUrl)}
|
|
42
|
+
const el = Array.from(_doc.querySelectorAll('[data-path]'))
|
|
43
|
+
.find(e => e.getAttribute('data-path') === path)
|
|
65
44
|
if (el) { el.click(); return true }
|
|
66
45
|
return false
|
|
67
46
|
`)).catch(() => false);
|
|
68
47
|
if (!clicked) {
|
|
69
|
-
// Fallback:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
48
|
+
// Fallback: drive navigation via wx.* on the rendered page iframe.
|
|
49
|
+
// Upstream's query router doesn't react to URL mutation; the active
|
|
50
|
+
// page's iframe exposes wx.* with the live dimina runtime bindings.
|
|
51
|
+
const apiName = JSON.stringify(method);
|
|
52
|
+
const urlJson = JSON.stringify(cleanUrl);
|
|
53
|
+
await evalInSim(ctx, `(() => {
|
|
54
|
+
const iframes = Array.from(document.querySelectorAll('iframe'))
|
|
55
|
+
for (const f of iframes) {
|
|
56
|
+
try {
|
|
57
|
+
if (f.contentWindow && f.contentWindow.wx && typeof f.contentWindow.wx[${apiName}] === 'function') {
|
|
58
|
+
f.contentWindow.wx[${apiName}]({ url: ${urlJson} })
|
|
59
|
+
return
|
|
77
60
|
}
|
|
61
|
+
} catch (_) {}
|
|
62
|
+
}
|
|
63
|
+
})()`);
|
|
78
64
|
}
|
|
79
65
|
// Wait for navigation
|
|
80
66
|
await new Promise((r) => setTimeout(r, 2000));
|
|
@@ -1,112 +1,100 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { evalInElement } from '../exec.js';
|
|
2
2
|
import { getElementRef, registerElement } from '../registry.js';
|
|
3
3
|
export const elementHandlers = {};
|
|
4
4
|
// -- Element domain --
|
|
5
5
|
elementHandlers['Element.tap'] = async (ctx, params) => {
|
|
6
6
|
const ref = getElementRef(params);
|
|
7
|
-
await
|
|
8
|
-
${buildElAccess(ref)}
|
|
7
|
+
await evalInElement(ctx, ref, `
|
|
9
8
|
if (!el) throw new Error('Element not found')
|
|
10
9
|
el.click()
|
|
11
|
-
`)
|
|
10
|
+
`);
|
|
12
11
|
return {};
|
|
13
12
|
};
|
|
14
13
|
elementHandlers['Element.triggerEvent'] = async (ctx, params) => {
|
|
15
14
|
const ref = getElementRef(params);
|
|
16
15
|
const type = params.type;
|
|
17
16
|
const detail = params.detail || {};
|
|
18
|
-
await
|
|
19
|
-
${buildElAccess(ref)}
|
|
17
|
+
await evalInElement(ctx, ref, `
|
|
20
18
|
if (!el) throw new Error('Element not found')
|
|
21
19
|
el.dispatchEvent(new CustomEvent('${type}', { detail: ${JSON.stringify(detail)}, bubbles: true }))
|
|
22
|
-
`)
|
|
20
|
+
`);
|
|
23
21
|
return {};
|
|
24
22
|
};
|
|
25
23
|
elementHandlers['Element.touchstart'] = async (ctx, params) => {
|
|
26
24
|
const ref = getElementRef(params);
|
|
27
|
-
await
|
|
28
|
-
${buildElAccess(ref)}
|
|
25
|
+
await evalInElement(ctx, ref, `
|
|
29
26
|
if (el) el.dispatchEvent(new TouchEvent('touchstart', { bubbles: true }))
|
|
30
|
-
`)
|
|
27
|
+
`);
|
|
31
28
|
return {};
|
|
32
29
|
};
|
|
33
30
|
elementHandlers['Element.touchmove'] = async (ctx, params) => {
|
|
34
31
|
const ref = getElementRef(params);
|
|
35
|
-
await
|
|
36
|
-
${buildElAccess(ref)}
|
|
32
|
+
await evalInElement(ctx, ref, `
|
|
37
33
|
if (el) el.dispatchEvent(new TouchEvent('touchmove', { bubbles: true }))
|
|
38
|
-
`)
|
|
34
|
+
`);
|
|
39
35
|
return {};
|
|
40
36
|
};
|
|
41
37
|
elementHandlers['Element.touchend'] = async (ctx, params) => {
|
|
42
38
|
const ref = getElementRef(params);
|
|
43
|
-
await
|
|
44
|
-
${buildElAccess(ref)}
|
|
39
|
+
await evalInElement(ctx, ref, `
|
|
45
40
|
if (el) el.dispatchEvent(new TouchEvent('touchend', { bubbles: true }))
|
|
46
|
-
`)
|
|
41
|
+
`);
|
|
47
42
|
return {};
|
|
48
43
|
};
|
|
49
44
|
elementHandlers['Element.getDOMProperties'] = async (ctx, params) => {
|
|
50
45
|
const ref = getElementRef(params);
|
|
51
46
|
const names = params.names;
|
|
52
|
-
const properties = await
|
|
53
|
-
${buildElAccess(ref)}
|
|
47
|
+
const properties = await evalInElement(ctx, ref, `
|
|
54
48
|
if (!el) return ${JSON.stringify(names.map(() => null))}
|
|
55
49
|
return ${JSON.stringify(names)}.map(n => el[n])
|
|
56
|
-
`)
|
|
50
|
+
`);
|
|
57
51
|
return { properties };
|
|
58
52
|
};
|
|
59
53
|
elementHandlers['Element.getAttributes'] = async (ctx, params) => {
|
|
60
54
|
const ref = getElementRef(params);
|
|
61
55
|
const names = params.names;
|
|
62
|
-
const attributes = await
|
|
63
|
-
${buildElAccess(ref)}
|
|
56
|
+
const attributes = await evalInElement(ctx, ref, `
|
|
64
57
|
if (!el) return ${JSON.stringify(names.map(() => null))}
|
|
65
58
|
return ${JSON.stringify(names)}.map(n => el.getAttribute(n))
|
|
66
|
-
`)
|
|
59
|
+
`);
|
|
67
60
|
return { attributes };
|
|
68
61
|
};
|
|
69
62
|
elementHandlers['Element.getStyles'] = async (ctx, params) => {
|
|
70
63
|
const ref = getElementRef(params);
|
|
71
64
|
const names = params.names;
|
|
72
|
-
const styles = await
|
|
73
|
-
${buildElAccess(ref)}
|
|
65
|
+
const styles = await evalInElement(ctx, ref, `
|
|
74
66
|
if (!el) return ${JSON.stringify(names.map(() => ''))}
|
|
75
67
|
const cs = getComputedStyle(el)
|
|
76
68
|
return ${JSON.stringify(names)}.map(n => cs.getPropertyValue(n))
|
|
77
|
-
`)
|
|
69
|
+
`);
|
|
78
70
|
return { styles };
|
|
79
71
|
};
|
|
80
72
|
elementHandlers['Element.getWXML'] = async (ctx, params) => {
|
|
81
73
|
const ref = getElementRef(params);
|
|
82
74
|
const type = params.type || 'inner';
|
|
83
75
|
const prop = type === 'outer' ? 'outerHTML' : 'innerHTML';
|
|
84
|
-
const wxml = await
|
|
85
|
-
${buildElAccess(ref)}
|
|
76
|
+
const wxml = await evalInElement(ctx, ref, `
|
|
86
77
|
return el ? el.${prop} : ''
|
|
87
|
-
`)
|
|
78
|
+
`);
|
|
88
79
|
return { wxml };
|
|
89
80
|
};
|
|
90
81
|
elementHandlers['Element.getOffset'] = async (ctx, params) => {
|
|
91
82
|
const ref = getElementRef(params);
|
|
92
|
-
return
|
|
93
|
-
${buildElAccess(ref)}
|
|
83
|
+
return evalInElement(ctx, ref, `
|
|
94
84
|
if (!el) return { left: 0, top: 0 }
|
|
95
85
|
const r = el.getBoundingClientRect()
|
|
96
86
|
return { left: r.left, top: r.top }
|
|
97
|
-
`)
|
|
87
|
+
`);
|
|
98
88
|
};
|
|
99
89
|
elementHandlers['Element.getElement'] = async (ctx, params) => {
|
|
100
90
|
const parentRef = getElementRef(params);
|
|
101
91
|
const selector = params.selector;
|
|
102
|
-
const
|
|
103
|
-
const info = await evalInSim(ctx, inIframe(`
|
|
104
|
-
${buildElAccess(parentRef, 'parent')}
|
|
92
|
+
const info = await evalInElement(ctx, parentRef, `
|
|
105
93
|
if (!parent) return null
|
|
106
|
-
const child = parent.querySelector(
|
|
94
|
+
const child = parent.querySelector(${JSON.stringify(selector)})
|
|
107
95
|
if (!child) return null
|
|
108
96
|
return { tagName: child.tagName.toLowerCase() }
|
|
109
|
-
|
|
97
|
+
`, 'parent');
|
|
110
98
|
if (!info)
|
|
111
99
|
throw new Error(`Child element not found: ${selector}`);
|
|
112
100
|
const combinedSelector = `${parentRef.selector} ${selector}`;
|
|
@@ -116,14 +104,12 @@ elementHandlers['Element.getElement'] = async (ctx, params) => {
|
|
|
116
104
|
elementHandlers['Element.getElements'] = async (ctx, params) => {
|
|
117
105
|
const parentRef = getElementRef(params);
|
|
118
106
|
const selector = params.selector;
|
|
119
|
-
const
|
|
120
|
-
const items = await evalInSim(ctx, inIframe(`
|
|
121
|
-
${buildElAccess(parentRef, 'parent')}
|
|
107
|
+
const items = await evalInElement(ctx, parentRef, `
|
|
122
108
|
if (!parent) return []
|
|
123
|
-
return Array.from(parent.querySelectorAll(
|
|
109
|
+
return Array.from(parent.querySelectorAll(${JSON.stringify(selector)})).map(el => ({
|
|
124
110
|
tagName: el.tagName.toLowerCase(),
|
|
125
111
|
}))
|
|
126
|
-
|
|
112
|
+
`, 'parent');
|
|
127
113
|
const combinedSelector = `${parentRef.selector} ${selector}`;
|
|
128
114
|
return {
|
|
129
115
|
elements: items.map((item, i) => ({
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* This server handles these messages by delegating to the workbench context
|
|
10
10
|
* (simulator webContents, IPC handlers, etc.) without modifying dimina upstream.
|
|
11
11
|
*/
|
|
12
|
-
import { ipcMain } from 'electron';
|
|
13
12
|
import { WebSocketServer } from 'ws';
|
|
14
13
|
import { AutomationChannel, SimulatorChannel } from '../../../shared/ipc-channels.js';
|
|
14
|
+
import { IpcRegistry } from '../../utils/ipc-registry.js';
|
|
15
15
|
import { getSimulator } from './exec.js';
|
|
16
16
|
import { toolHandlers } from './handlers/tool.js';
|
|
17
17
|
import { appHandlers } from './handlers/app.js';
|
|
@@ -25,7 +25,6 @@ const handlers = {
|
|
|
25
25
|
...elementHandlers,
|
|
26
26
|
};
|
|
27
27
|
let currentPort = null;
|
|
28
|
-
let portIpcRegistered = false;
|
|
29
28
|
export function getAutomationPort() {
|
|
30
29
|
return currentPort;
|
|
31
30
|
}
|
|
@@ -40,10 +39,14 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
40
39
|
// ws always binds via an http.Server, so address() is AddressInfo here.
|
|
41
40
|
const resolvedPort = typeof addr === 'object' && addr ? addr.port : port;
|
|
42
41
|
currentPort = resolvedPort;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
// Gate AutomationChannel.GetPort with the workbench sender policy (only
|
|
43
|
+
// the main renderer + workbench settings/popover overlays are allowed to
|
|
44
|
+
// read the port — see createWorkbenchSenderPolicy; the simulator webview
|
|
45
|
+
// is intentionally NOT trusted for IPC invokes).
|
|
46
|
+
const portIpc = new IpcRegistry(ctx.senderPolicy);
|
|
47
|
+
portIpc.handle(AutomationChannel.GetPort, () => {
|
|
48
|
+
return currentPort;
|
|
49
|
+
});
|
|
47
50
|
function broadcast(event) {
|
|
48
51
|
const msg = JSON.stringify(event);
|
|
49
52
|
for (const ws of clients) {
|
|
@@ -51,19 +54,55 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
51
54
|
ws.send(msg);
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
|
-
// Forward simulator console logs as App.logAdded events
|
|
57
|
+
// Forward simulator console logs as App.logAdded events.
|
|
58
|
+
// Track the polling interval + stop timer + currently-attached sim and
|
|
59
|
+
// the named handler so we can fully detach on close() or sim destruction.
|
|
60
|
+
// Without this the listener accumulates across create/dispose cycles and
|
|
61
|
+
// across simulator rebuilds.
|
|
55
62
|
let consoleForwardingSetup = false;
|
|
63
|
+
let pollInterval = null;
|
|
64
|
+
let pollStopTimer = null;
|
|
65
|
+
let attachedSim = null;
|
|
66
|
+
let ipcMessageHostHandler = null;
|
|
67
|
+
let simDestroyedHandler = null;
|
|
68
|
+
function detachConsoleForwarding() {
|
|
69
|
+
if (attachedSim) {
|
|
70
|
+
if (ipcMessageHostHandler) {
|
|
71
|
+
try {
|
|
72
|
+
if (!attachedSim.isDestroyed()) {
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
+
;
|
|
75
|
+
attachedSim.removeListener('ipc-message-host', ipcMessageHostHandler);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch { /* noop */ }
|
|
79
|
+
}
|
|
80
|
+
if (simDestroyedHandler) {
|
|
81
|
+
try {
|
|
82
|
+
if (!attachedSim.isDestroyed()) {
|
|
83
|
+
attachedSim.removeListener('destroyed', simDestroyedHandler);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch { /* noop */ }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
attachedSim = null;
|
|
90
|
+
ipcMessageHostHandler = null;
|
|
91
|
+
simDestroyedHandler = null;
|
|
92
|
+
}
|
|
56
93
|
function setupConsoleForwarding() {
|
|
57
94
|
if (consoleForwardingSetup)
|
|
58
95
|
return;
|
|
59
96
|
consoleForwardingSetup = true;
|
|
60
|
-
|
|
61
|
-
const check = setInterval(() => {
|
|
97
|
+
pollInterval = setInterval(() => {
|
|
62
98
|
const sim = getSimulator(ctx);
|
|
63
99
|
if (!sim)
|
|
64
100
|
return;
|
|
65
|
-
|
|
66
|
-
|
|
101
|
+
if (pollInterval) {
|
|
102
|
+
clearInterval(pollInterval);
|
|
103
|
+
pollInterval = null;
|
|
104
|
+
}
|
|
105
|
+
const onIpcMessageHost = (_event, channel, data) => {
|
|
67
106
|
if (channel === SimulatorChannel.Console) {
|
|
68
107
|
const logData = data;
|
|
69
108
|
broadcast({
|
|
@@ -71,10 +110,29 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
71
110
|
params: { type: logData.level || 'log', args: logData.args || [] },
|
|
72
111
|
});
|
|
73
112
|
}
|
|
74
|
-
}
|
|
113
|
+
};
|
|
114
|
+
const onSimDestroyed = () => {
|
|
115
|
+
// Sim webContents is dead — drop refs (removeListener on a destroyed
|
|
116
|
+
// sender would throw) and allow re-attach to a new simulator.
|
|
117
|
+
attachedSim = null;
|
|
118
|
+
ipcMessageHostHandler = null;
|
|
119
|
+
simDestroyedHandler = null;
|
|
120
|
+
consoleForwardingSetup = false;
|
|
121
|
+
};
|
|
122
|
+
attachedSim = sim;
|
|
123
|
+
ipcMessageHostHandler = onIpcMessageHost;
|
|
124
|
+
simDestroyedHandler = onSimDestroyed;
|
|
125
|
+
sim.on('ipc-message-host', onIpcMessageHost);
|
|
126
|
+
sim.once('destroyed', onSimDestroyed);
|
|
75
127
|
}, 1000);
|
|
76
128
|
// Stop polling after 30s
|
|
77
|
-
setTimeout(() =>
|
|
129
|
+
pollStopTimer = setTimeout(() => {
|
|
130
|
+
if (pollInterval) {
|
|
131
|
+
clearInterval(pollInterval);
|
|
132
|
+
pollInterval = null;
|
|
133
|
+
}
|
|
134
|
+
pollStopTimer = null;
|
|
135
|
+
}, 30000);
|
|
78
136
|
}
|
|
79
137
|
wss.on('connection', (ws) => {
|
|
80
138
|
clients.add(ws);
|
|
@@ -109,8 +167,25 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
109
167
|
});
|
|
110
168
|
return {
|
|
111
169
|
close: () => {
|
|
170
|
+
if (pollInterval) {
|
|
171
|
+
clearInterval(pollInterval);
|
|
172
|
+
pollInterval = null;
|
|
173
|
+
}
|
|
174
|
+
if (pollStopTimer) {
|
|
175
|
+
clearTimeout(pollStopTimer);
|
|
176
|
+
pollStopTimer = null;
|
|
177
|
+
}
|
|
178
|
+
detachConsoleForwarding();
|
|
179
|
+
for (const ws of clients) {
|
|
180
|
+
try {
|
|
181
|
+
ws.close();
|
|
182
|
+
}
|
|
183
|
+
catch { /* noop */ }
|
|
184
|
+
}
|
|
185
|
+
clients.clear();
|
|
112
186
|
if (currentPort === resolvedPort)
|
|
113
187
|
currentPort = null;
|
|
188
|
+
void portIpc.dispose();
|
|
114
189
|
wss.close();
|
|
115
190
|
},
|
|
116
191
|
port: resolvedPort,
|
|
@@ -4,12 +4,12 @@ import { simulatorDir } from '../utils/paths.js';
|
|
|
4
4
|
export const defaultAdapter = {
|
|
5
5
|
async openProject(opts) {
|
|
6
6
|
const diminaKit = await import('@dimina-kit/devkit');
|
|
7
|
-
const
|
|
8
|
-
return openProject({
|
|
7
|
+
const openProjectOpts = {
|
|
9
8
|
outputDir: path.join(app.getPath('userData'), 'dimina-fe-output'),
|
|
10
9
|
...opts,
|
|
11
10
|
simulatorDir,
|
|
12
|
-
}
|
|
11
|
+
};
|
|
12
|
+
return diminaKit.openProject(openProjectOpts);
|
|
13
13
|
},
|
|
14
14
|
};
|
|
15
15
|
//# sourceMappingURL=default-adapter.js.map
|
|
@@ -6,5 +6,6 @@
|
|
|
6
6
|
* Tool registration lives in `./tool-registry` and `./tools/*`; this file
|
|
7
7
|
* only wires up transports and lifecycle.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
import { type Disposable } from '../../utils/disposable.js';
|
|
10
|
+
export declare function startMcpServer(resolvedCdpPort: number, mcpPort: number): Disposable;
|
|
10
11
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -10,7 +10,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
10
10
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
11
11
|
import { createServer } from 'http';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
|
+
import { toDisposable } from '../../utils/disposable.js';
|
|
13
14
|
import { connectTarget, setCdpPort } from './target-manager.js';
|
|
15
|
+
import { recordMcpFailed, recordMcpStarted, recordMcpStopped } from './status.js';
|
|
14
16
|
import { registerCommonTargetTools } from './tool-registry.js';
|
|
15
17
|
import { registerContextTools } from './tools/context-tools.js';
|
|
16
18
|
import { registerSimulatorTools } from './tools/simulator-tools.js';
|
|
@@ -64,13 +66,21 @@ export function startMcpServer(resolvedCdpPort, mcpPort) {
|
|
|
64
66
|
httpServer.on('error', (err) => {
|
|
65
67
|
if (err.code === 'EADDRINUSE') {
|
|
66
68
|
console.warn(`[MCP] Port ${mcpPort} already in use — MCP server not started`);
|
|
69
|
+
recordMcpFailed('port-in-use');
|
|
67
70
|
}
|
|
68
71
|
else {
|
|
69
72
|
console.error('[MCP] Server error:', err);
|
|
73
|
+
recordMcpFailed(err.message);
|
|
70
74
|
}
|
|
71
75
|
});
|
|
72
76
|
httpServer.listen(mcpPort, '127.0.0.1', () => {
|
|
73
77
|
console.log(`[MCP] SSE server listening on http://127.0.0.1:${mcpPort}/sse`);
|
|
78
|
+
recordMcpStarted(mcpPort);
|
|
79
|
+
});
|
|
80
|
+
return toDisposable(() => {
|
|
81
|
+
transports.clear();
|
|
82
|
+
httpServer.close();
|
|
83
|
+
recordMcpStopped();
|
|
74
84
|
});
|
|
75
85
|
}
|
|
76
86
|
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface McpRuntimeStatus {
|
|
2
|
+
running: boolean;
|
|
3
|
+
port: number | null;
|
|
4
|
+
error: string | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function getMcpStatus(): McpRuntimeStatus;
|
|
7
|
+
export declare function recordMcpStarted(port: number): void;
|
|
8
|
+
export declare function recordMcpFailed(error: string): void;
|
|
9
|
+
export declare function recordMcpStopped(): void;
|
|
10
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
let status = { running: false, port: null, error: null };
|
|
2
|
+
export function getMcpStatus() {
|
|
3
|
+
return { ...status };
|
|
4
|
+
}
|
|
5
|
+
export function recordMcpStarted(port) {
|
|
6
|
+
status = { running: true, port, error: null };
|
|
7
|
+
}
|
|
8
|
+
export function recordMcpFailed(error) {
|
|
9
|
+
status = { running: false, port: null, error };
|
|
10
|
+
}
|
|
11
|
+
export function recordMcpStopped() {
|
|
12
|
+
status = { running: false, port: null, error: null };
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
* - rolling buffers of console log and network request events
|
|
11
11
|
*/
|
|
12
12
|
import CDP from 'chrome-remote-interface';
|
|
13
|
+
import { DEFAULT_CDP_PORT } from '../../../shared/constants.js';
|
|
13
14
|
const SIMULATOR_URL_PATTERN = 'localhost:7788';
|
|
14
15
|
const MAX_BUFFER = 500;
|
|
15
16
|
const RECONNECT_INTERVAL_MS = 3000;
|
|
16
|
-
let cdpPort =
|
|
17
|
+
let cdpPort = DEFAULT_CDP_PORT;
|
|
17
18
|
const targets = {
|
|
18
19
|
simulator: { client: null, connected: false, timer: null, consoleLogs: [], networkRequests: [] },
|
|
19
20
|
workbench: { client: null, connected: false, timer: null, consoleLogs: [], networkRequests: [] },
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Disposable } from '../utils/disposable.js';
|
|
2
|
+
import type { WorkbenchContext } from './workbench-context.js';
|
|
3
|
+
/**
|
|
4
|
+
* A composable unit of workbench functionality.
|
|
5
|
+
*
|
|
6
|
+
* Built-in modules (projects, session, simulator, popover, settings) are
|
|
7
|
+
* implemented as `WorkbenchModule` values; hosts may also inject extra
|
|
8
|
+
* modules via `WorkbenchAppConfig.extraModules`.
|
|
9
|
+
*
|
|
10
|
+
* `setup` is invoked once during workbench bootstrap. The returned
|
|
11
|
+
* Disposable is added to `ctx.registry` so module teardown is symmetric
|
|
12
|
+
* with the rest of the workbench lifecycle.
|
|
13
|
+
*/
|
|
14
|
+
export interface WorkbenchModule {
|
|
15
|
+
/** Set up the module against ctx. Returns a Disposable owned by ctx.registry. */
|
|
16
|
+
setup(ctx: WorkbenchContext): Disposable;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -8,6 +8,8 @@ import type { ProjectSettings } from '../projects/project-repository.js';
|
|
|
8
8
|
export interface ProjectStatusPayload {
|
|
9
9
|
status: string;
|
|
10
10
|
message: string;
|
|
11
|
+
/** True when the status update is emitted by the file-watcher rebuild loop. */
|
|
12
|
+
hotReload?: boolean;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Payload for the `settings:init` event sent into the embedded settings overlay
|
|
@@ -50,8 +52,6 @@ export interface RendererNotifier {
|
|
|
50
52
|
popoverClosed(): void;
|
|
51
53
|
/** Ask the main renderer to relaunch the simulator with a new config. */
|
|
52
54
|
popoverRelaunch(config: CompileConfig): void;
|
|
53
|
-
/** Ask the main renderer to re-initialise the devtools panels. */
|
|
54
|
-
workbenchReset(): void;
|
|
55
55
|
/** Tell the main renderer the toolbar actions list has changed. */
|
|
56
56
|
toolbarActionsChanged(): void;
|
|
57
57
|
/** Initialise the currently shown compile popover overlay. */
|
|
@@ -67,7 +67,9 @@ export interface RendererNotifier {
|
|
|
67
67
|
* between the notifier module and workbench-context.
|
|
68
68
|
*/
|
|
69
69
|
export interface NotifierContext {
|
|
70
|
-
|
|
70
|
+
windows: {
|
|
71
|
+
readonly mainWindow: BrowserWindow;
|
|
72
|
+
};
|
|
71
73
|
views: {
|
|
72
74
|
getSettingsWebContents(): WebContents | null;
|
|
73
75
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProjectChannel, WindowChannel, SettingsChannel, PopoverChannel,
|
|
1
|
+
import { ProjectChannel, WindowChannel, SettingsChannel, PopoverChannel, ToolbarChannel, WorkbenchSettingsChannel, } from '../../../shared/ipc-channels.js';
|
|
2
2
|
/** Safely resolve a WebContents, skipping destroyed / missing targets. */
|
|
3
3
|
function liveWebContents(wc) {
|
|
4
4
|
if (!wc)
|
|
@@ -13,9 +13,9 @@ function liveWebContents(wc) {
|
|
|
13
13
|
*/
|
|
14
14
|
export function createRendererNotifier(ctx) {
|
|
15
15
|
function sendToMain(channel, ...args) {
|
|
16
|
-
if (ctx.mainWindow.isDestroyed())
|
|
16
|
+
if (ctx.windows.mainWindow.isDestroyed())
|
|
17
17
|
return;
|
|
18
|
-
const wc = liveWebContents(ctx.mainWindow.webContents);
|
|
18
|
+
const wc = liveWebContents(ctx.windows.mainWindow.webContents);
|
|
19
19
|
if (!wc)
|
|
20
20
|
return;
|
|
21
21
|
wc.send(channel, ...args);
|
|
@@ -39,9 +39,6 @@ export function createRendererNotifier(ctx) {
|
|
|
39
39
|
popoverRelaunch(config) {
|
|
40
40
|
sendToMain(PopoverChannel.Relaunch, config);
|
|
41
41
|
},
|
|
42
|
-
workbenchReset() {
|
|
43
|
-
sendToMain(WorkbenchChannel.Reset);
|
|
44
|
-
},
|
|
45
42
|
toolbarActionsChanged() {
|
|
46
43
|
sendToMain(ToolbarChannel.ActionsChanged);
|
|
47
44
|
},
|