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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +33 -71
  2. package/dist/main/app/app.js +5 -0
  3. package/dist/main/app/bootstrap.js +2 -1
  4. package/dist/main/app/launch.d.ts +1 -1
  5. package/dist/main/app/launch.js +6 -7
  6. package/dist/main/index.bundle.js +306 -317
  7. package/dist/main/ipc/panels.js +3 -5
  8. package/dist/main/ipc/projects.d.ts +1 -1
  9. package/dist/main/ipc/projects.js +5 -5
  10. package/dist/main/ipc/settings.d.ts +1 -1
  11. package/dist/main/ipc/settings.js +14 -1
  12. package/dist/main/ipc/simulator.d.ts +1 -1
  13. package/dist/main/ipc/simulator.js +1 -10
  14. package/dist/main/services/automation/exec.d.ts +2 -0
  15. package/dist/main/services/automation/exec.js +7 -9
  16. package/dist/main/services/automation/handlers/app.js +37 -51
  17. package/dist/main/services/automation/handlers/element.js +27 -41
  18. package/dist/main/services/default-adapter.js +3 -3
  19. package/dist/main/services/mcp/index.d.ts +1 -0
  20. package/dist/main/services/mcp/index.js +1 -0
  21. package/dist/main/services/mcp/server.js +5 -0
  22. package/dist/main/services/mcp/status.d.ts +10 -0
  23. package/dist/main/services/mcp/status.js +14 -0
  24. package/dist/main/services/mcp/target-manager.js +2 -1
  25. package/dist/main/services/notifications/renderer-notifier.d.ts +3 -3
  26. package/dist/main/services/notifications/renderer-notifier.js +3 -6
  27. package/dist/main/services/projects/local-provider.js +3 -3
  28. package/dist/main/services/projects/project-repository.d.ts +0 -1
  29. package/dist/main/services/settings/index.js +2 -1
  30. package/dist/main/services/simulator-storage/index.js +47 -77
  31. package/dist/main/services/views/view-manager.d.ts +3 -7
  32. package/dist/main/services/views/view-manager.js +20 -27
  33. package/dist/main/services/window-service.d.ts +3 -3
  34. package/dist/main/services/workbench-context.d.ts +3 -6
  35. package/dist/main/services/workbench-context.js +1 -3
  36. package/dist/main/services/workspace/workspace-service.js +8 -24
  37. package/dist/main/utils/theme.d.ts +24 -0
  38. package/dist/main/utils/theme.js +38 -1
  39. package/dist/preload/index.d.ts +5 -2
  40. package/dist/preload/index.js +3 -2
  41. package/dist/preload/instrumentation/app-data.d.ts +32 -2
  42. package/dist/preload/instrumentation/app-data.js +187 -172
  43. package/dist/preload/instrumentation/index.d.ts +2 -2
  44. package/dist/preload/instrumentation/index.js +2 -2
  45. package/dist/preload/instrumentation/wxml.d.ts +16 -4
  46. package/dist/preload/instrumentation/wxml.js +188 -178
  47. package/dist/preload/miniapp-snapshot/host.d.ts +12 -0
  48. package/dist/preload/miniapp-snapshot/host.js +117 -0
  49. package/dist/preload/miniapp-snapshot/types.d.ts +62 -0
  50. package/dist/preload/miniapp-snapshot/types.js +15 -0
  51. package/dist/preload/runtime/bridge.js +5 -24
  52. package/dist/preload/runtime/custom-apis.d.ts +1 -1
  53. package/dist/preload/runtime/custom-apis.js +3 -7
  54. package/dist/preload/shared/expose.d.ts +15 -0
  55. package/dist/preload/shared/expose.js +33 -0
  56. package/dist/preload/shared/page-iframe.d.ts +3 -0
  57. package/dist/preload/shared/page-iframe.js +6 -0
  58. package/dist/preload/shared/types.d.ts +2 -0
  59. package/dist/preload/windows/simulator.js +400 -348
  60. package/dist/renderer/assets/constants-DI382mTP.js +2 -0
  61. package/dist/renderer/assets/index-D8VFYBh-.js +46 -0
  62. package/dist/renderer/assets/{input-DqVQNCx_.js → input-CoWcfa8u.js} +2 -2
  63. package/dist/renderer/assets/ipc-transport-Cd2xPrsa.js +9 -0
  64. package/dist/renderer/assets/popover-Nq5gT9DO.js +2 -0
  65. package/dist/renderer/assets/select-By5KH-UG.js +2 -0
  66. package/dist/renderer/assets/{settings-BFNfnYvU.js → settings-Dj_N70qR.js} +2 -2
  67. package/dist/renderer/assets/settings-api-Do0_Smq1.js +2 -0
  68. package/dist/renderer/assets/workbenchSettings-CazSLGYt.js +8 -0
  69. package/dist/renderer/entries/main/index.html +5 -4
  70. package/dist/renderer/entries/popover/index.html +5 -4
  71. package/dist/renderer/entries/settings/index.html +4 -4
  72. package/dist/renderer/entries/workbench-settings/index.html +4 -3
  73. package/dist/shared/ipc-channels.d.ts +4 -12
  74. package/dist/shared/ipc-channels.js +10 -14
  75. package/dist/shared/simulator-route.d.ts +72 -0
  76. package/dist/shared/simulator-route.js +169 -0
  77. package/dist/shared/types.d.ts +2 -10
  78. package/dist/simulator/assets/simulator-DJCozrU5.js +9 -0
  79. package/dist/simulator/simulator.html +6 -43
  80. package/package.json +4 -4
  81. package/dist/preload/instrumentation/disposable.d.ts +0 -11
  82. package/dist/preload/instrumentation/disposable.js +0 -15
  83. package/dist/renderer/assets/index-Czz_bp6b.js +0 -46
  84. package/dist/renderer/assets/ipc-transport-CCHrCtDr.js +0 -9
  85. package/dist/renderer/assets/popover-BRg4XI_w.js +0 -2
  86. package/dist/renderer/assets/select-CvILHHZU.js +0 -2
  87. package/dist/renderer/assets/settings-api-DOAgmBst.js +0 -2
  88. package/dist/renderer/assets/workbenchSettings-DjVMgMMH.js +0 -8
  89. package/dist/simulator/assets/simulator-DL6kOCq-.js +0 -9
@@ -1,4 +1,3 @@
1
- import { webContents } from 'electron';
2
1
  import { PanelChannel } from '../../shared/ipc-channels.js';
3
2
  import { PanelEvalSchema, PanelSelectSchema, } from '../../shared/ipc-schemas.js';
4
3
  import { validate } from '../utils/ipc-schema.js';
@@ -16,11 +15,10 @@ export function registerPanelsIpc(ctx) {
16
15
  })
17
16
  .handle(PanelChannel.Eval, async (_event, ...args) => {
18
17
  const [expression] = validate(PanelChannel.Eval, PanelEvalSchema, args);
19
- const simWcId = ctx.views.getSimulatorWebContentsId();
20
- if (!ctx.workspace.hasActiveSession() || simWcId == null)
18
+ if (!ctx.workspace.hasActiveSession())
21
19
  return undefined;
22
- const sim = webContents.fromId(simWcId);
23
- if (!sim || sim.isDestroyed())
20
+ const sim = ctx.views.getSimulatorWebContents();
21
+ if (!sim)
24
22
  return undefined;
25
23
  try {
26
24
  return await sim.executeJavaScript(expression);
@@ -1,7 +1,7 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
2
  import type { WorkbenchModule } from '../services/module.js';
3
3
  import type { Disposable } from '../utils/disposable.js';
4
- type ProjectsIpcCtx = Pick<WorkbenchContext, 'workspace' | 'mainWindow' | 'senderPolicy' | 'projectsProvider' | 'projectTemplates' | 'customCreateProjectDialog'>;
4
+ type ProjectsIpcCtx = Pick<WorkbenchContext, 'workspace' | 'windows' | 'senderPolicy' | 'projectsProvider' | 'projectTemplates' | 'customCreateProjectDialog'>;
5
5
  export declare function registerProjectsIpc(ctx: ProjectsIpcCtx): Disposable;
6
6
  export declare const projectsModule: WorkbenchModule;
7
7
  export {};
@@ -13,7 +13,7 @@ export function registerProjectsIpc(ctx) {
13
13
  return ctx.workspace.listProjects();
14
14
  })
15
15
  .handle(DialogChannel.OpenDirectory, async () => {
16
- const result = await dialog.showOpenDialog(ctx.mainWindow, {
16
+ const result = await dialog.showOpenDialog(ctx.windows.mainWindow, {
17
17
  properties: ['openDirectory'],
18
18
  title: '选择小程序项目目录',
19
19
  });
@@ -23,7 +23,7 @@ export function registerProjectsIpc(ctx) {
23
23
  const [dirPath] = validate(ProjectsChannel.Add, ProjectsAddSchema, args);
24
24
  const dirError = await ctx.workspace.validateProjectDir(dirPath);
25
25
  if (dirError) {
26
- await dialog.showMessageBox(ctx.mainWindow, {
26
+ await dialog.showMessageBox(ctx.windows.mainWindow, {
27
27
  type: 'error',
28
28
  title: '无法导入项目',
29
29
  message: '该目录不是有效的小程序项目',
@@ -35,7 +35,7 @@ export function registerProjectsIpc(ctx) {
35
35
  const duplicate = await ctx.workspace.hasProject(dirPath);
36
36
  const project = await ctx.workspace.addProject(dirPath);
37
37
  if (duplicate) {
38
- await dialog.showMessageBox(ctx.mainWindow, {
38
+ await dialog.showMessageBox(ctx.windows.mainWindow, {
39
39
  type: 'info',
40
40
  title: '项目已存在',
41
41
  message: '该项目已在列表中',
@@ -61,7 +61,7 @@ export function registerProjectsIpc(ctx) {
61
61
  return null;
62
62
  const sanitized = sanitizeTemplates(ctx.projectTemplates ?? []);
63
63
  return await ctx.customCreateProjectDialog({
64
- parentWindow: ctx.mainWindow,
64
+ parentWindow: ctx.windows.mainWindow,
65
65
  templates: sanitized,
66
66
  });
67
67
  })
@@ -87,7 +87,7 @@ export function registerProjectsIpc(ctx) {
87
87
  // non-empty target dir, missing template, remote backend reject,
88
88
  // …) as a native dialog so users see *why* the create silently
89
89
  // fizzled. The renderer's catch then just bails out.
90
- await dialog.showMessageBox(ctx.mainWindow, {
90
+ await dialog.showMessageBox(ctx.windows.mainWindow, {
91
91
  type: 'error',
92
92
  title: '创建项目失败',
93
93
  message: '无法创建项目',
@@ -1,6 +1,6 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
2
  import type { Disposable } from '../utils/disposable.js';
3
3
  import type { WorkbenchModule } from '../services/module.js';
4
- export declare function registerSettingsIpc(ctx: Pick<WorkbenchContext, 'workbenchSettingsWindow' | 'views' | 'notify' | 'workspace' | 'mainWindow' | 'rendererDir' | 'senderPolicy' | 'windows'>): Disposable;
4
+ export declare function registerSettingsIpc(ctx: Pick<WorkbenchContext, 'views' | 'notify' | 'workspace' | 'rendererDir' | 'senderPolicy' | 'windows'>): Disposable;
5
5
  export declare const settingsModule: WorkbenchModule;
6
6
  //# sourceMappingURL=settings.d.ts.map
@@ -2,6 +2,8 @@ import { app } from 'electron';
2
2
  import { openSettingsWindow } from '../app/launch.js';
3
3
  import { loadWorkbenchSettings, saveWorkbenchSettings, applyTheme, } from '../services/settings/index.js';
4
4
  import { WorkbenchSettingsChannel, SettingsChannel } from '../../shared/ipc-channels.js';
5
+ import { DEFAULT_CDP_PORT } from '../../shared/constants.js';
6
+ import { getMcpStatus } from '../services/mcp/status.js';
5
7
  import { SettingsConfigChangedSchema, SettingsProjectSettingsChangedSchema, SettingsSetVisibleSchema, WorkbenchSettingsSaveSchema, WorkbenchSettingsSetThemeSchema, WorkbenchSettingsSetVisibleSchema, } from '../../shared/ipc-schemas.js';
6
8
  import { validate } from '../utils/ipc-schema.js';
7
9
  import { IpcRegistry } from '../utils/ipc-registry.js';
@@ -23,7 +25,7 @@ export function registerSettingsIpc(ctx) {
23
25
  .handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
24
26
  const settings = loadWorkbenchSettings();
25
27
  const switchValue = app.commandLine.getSwitchValue('remote-debugging-port');
26
- const implicitDevDefault = !app.isPackaged && !settings.cdp.enabled && switchValue === '9222';
28
+ const implicitDevDefault = !app.isPackaged && !settings.cdp.enabled && switchValue === String(DEFAULT_CDP_PORT);
27
29
  return {
28
30
  configured: settings.cdp.enabled,
29
31
  port: settings.cdp.port,
@@ -31,6 +33,17 @@ export function registerSettingsIpc(ctx) {
31
33
  activePort: switchValue ? parseInt(switchValue, 10) : null,
32
34
  implicitDevDefault,
33
35
  };
36
+ })
37
+ .handle(WorkbenchSettingsChannel.GetMcpStatus, () => {
38
+ const settings = loadWorkbenchSettings();
39
+ const runtime = getMcpStatus();
40
+ return {
41
+ configured: settings.mcp.enabled,
42
+ configuredPort: settings.mcp.port,
43
+ running: runtime.running,
44
+ activePort: runtime.port,
45
+ error: runtime.error,
46
+ };
34
47
  })
35
48
  .handle(WorkbenchSettingsChannel.SetVisible, async (_, ...args) => {
36
49
  const [visible] = validate(WorkbenchSettingsChannel.SetVisible, WorkbenchSettingsSetVisibleSchema, args);
@@ -1,4 +1,4 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
2
  import type { Disposable } from '../utils/disposable.js';
3
- export declare function registerSimulatorIpc(ctx: Pick<WorkbenchContext, 'views' | 'panels' | 'apiNamespaces' | 'notify' | 'senderPolicy'>): Disposable;
3
+ export declare function registerSimulatorIpc(ctx: Pick<WorkbenchContext, 'views' | 'notify' | 'senderPolicy'>): Disposable;
4
4
  //# sourceMappingURL=simulator.d.ts.map
@@ -1,4 +1,4 @@
1
- import { SimulatorChannel, SimulatorCustomApiChannel, WorkbenchChannel } from '../../shared/ipc-channels.js';
1
+ import { SimulatorChannel, SimulatorCustomApiChannel } from '../../shared/ipc-channels.js';
2
2
  import { SimulatorAttachSchema, SimulatorCustomApiInvokeSchema, SimulatorResizeSchema, SimulatorSetVisibleSchema, } from '../../shared/ipc-schemas.js';
3
3
  import { simulatorApiRegistry } from '../services/simulator/custom-apis.js';
4
4
  import { validate } from '../utils/ipc-schema.js';
@@ -26,15 +26,6 @@ export function registerSimulatorIpc(ctx) {
26
26
  .handle(SimulatorCustomApiChannel.Invoke, (_, ...args) => {
27
27
  const [name, params] = validate(SimulatorCustomApiChannel.Invoke, SimulatorCustomApiInvokeSchema, args);
28
28
  return simulatorApiRegistry.invoke(name, params);
29
- })
30
- .handle(WorkbenchChannel.GetPanelConfig, () => {
31
- return ctx.panels;
32
- })
33
- .handle(WorkbenchChannel.GetApiNamespaces, () => {
34
- return ctx.apiNamespaces;
35
- })
36
- .on(WorkbenchChannel.Reset, () => {
37
- ctx.notify.workbenchReset();
38
29
  });
39
30
  }
40
31
  //# sourceMappingURL=simulator.js.map
@@ -4,6 +4,8 @@ export declare function getSimulator(ctx: WorkbenchContext): Electron.WebContent
4
4
  export declare function evalInSim<T = unknown>(ctx: WorkbenchContext, expression: string): Promise<T>;
5
5
  /** Build JS that locates an element by selector+index inside the active page iframe. */
6
6
  export declare function buildElAccess(ref: ElementRef, varName?: string): string;
7
+ /** Run `body` inside the active page iframe with `varName` bound to the element at `ref`. */
8
+ export declare function evalInElement<T = unknown>(ctx: WorkbenchContext, ref: ElementRef, body: string, varName?: string): Promise<T>;
7
9
  /** Wrap code to run inside the active page iframe (last iframe in the stack). */
8
10
  export declare function inIframe(code: string): string;
9
11
  //# sourceMappingURL=exec.d.ts.map
@@ -1,13 +1,8 @@
1
- import { webContents } from 'electron';
2
1
  // ── Helpers ───────────────────────────────────────────────────────────
3
2
  export function getSimulator(ctx) {
4
- const simWcId = ctx.views.getSimulatorWebContentsId();
5
- if (!ctx.workspace.hasActiveSession() || simWcId == null)
3
+ if (!ctx.workspace.hasActiveSession())
6
4
  return null;
7
- const sim = webContents.fromId(simWcId);
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 escaped = ref.selector.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
21
- return `const ${varName} = _doc.querySelectorAll('${escaped}')[${ref.index}]`;
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 hash = await evalInSim(ctx, 'location.hash');
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 { pageId: iframeCount, path: pagePath, query };
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 hash = await evalInSim(ctx, 'location.hash');
34
- const clean = hash.replace(/^#/, '').replace(/\?.*$/, '');
19
+ const route = await readRoute(ctx);
35
20
  const pageStack = [];
36
- if (clean.includes('|')) {
37
- // New format: #appid|page1|page2 — segments after appid are pages
38
- const parts = clean.split('|');
39
- for (let i = 1; i < parts.length; i++) {
40
- pageStack.push({ pageId: i, path: parts[i] ?? '', query: {} });
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 hash change + click
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 el = _doc.querySelector('[data-path="${cleanUrl}"]')
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: change hash
70
- const hash = await evalInSim(ctx, 'location.hash');
71
- const appId = hash.replace(/^#/, '').split('/')[0];
72
- if (method === 'reLaunch' || method === 'redirectTo') {
73
- await evalInSim(ctx, `location.href = location.pathname + '#${appId}${cleanUrl}'`);
74
- }
75
- else {
76
- await evalInSim(ctx, `location.hash = '#${appId}${cleanUrl}'`);
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 { buildElAccess, evalInSim, inIframe } from '../exec.js';
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 evalInSim(ctx, inIframe(`
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 evalInSim(ctx, inIframe(`
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 evalInSim(ctx, inIframe(`
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 evalInSim(ctx, inIframe(`
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 evalInSim(ctx, inIframe(`
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 evalInSim(ctx, inIframe(`
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 evalInSim(ctx, inIframe(`
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 evalInSim(ctx, inIframe(`
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 evalInSim(ctx, inIframe(`
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 evalInSim(ctx, inIframe(`
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 escaped = selector.replace(/'/g, "\\'");
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('${escaped}')
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 escaped = selector.replace(/'/g, "\\'");
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('${escaped}')).map(el => ({
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) => ({
@@ -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 openProject = diminaKit.openProject;
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
@@ -12,4 +12,5 @@
12
12
  * - `./target-manager.ts` dual-target CDP connection + reconnect
13
13
  */
14
14
  export { startMcpServer } from './server.js';
15
+ export { getMcpStatus, type McpRuntimeStatus } from './status.js';
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -12,4 +12,5 @@
12
12
  * - `./target-manager.ts` dual-target CDP connection + reconnect
13
13
  */
14
14
  export { startMcpServer } from './server.js';
15
+ export { getMcpStatus } from './status.js';
15
16
  //# sourceMappingURL=index.js.map
@@ -12,6 +12,7 @@ import { createServer } from 'http';
12
12
  import { createRequire } from 'node:module';
13
13
  import { toDisposable } from '../../utils/disposable.js';
14
14
  import { connectTarget, setCdpPort } from './target-manager.js';
15
+ import { recordMcpFailed, recordMcpStarted, recordMcpStopped } from './status.js';
15
16
  import { registerCommonTargetTools } from './tool-registry.js';
16
17
  import { registerContextTools } from './tools/context-tools.js';
17
18
  import { registerSimulatorTools } from './tools/simulator-tools.js';
@@ -65,17 +66,21 @@ export function startMcpServer(resolvedCdpPort, mcpPort) {
65
66
  httpServer.on('error', (err) => {
66
67
  if (err.code === 'EADDRINUSE') {
67
68
  console.warn(`[MCP] Port ${mcpPort} already in use — MCP server not started`);
69
+ recordMcpFailed('port-in-use');
68
70
  }
69
71
  else {
70
72
  console.error('[MCP] Server error:', err);
73
+ recordMcpFailed(err.message);
71
74
  }
72
75
  });
73
76
  httpServer.listen(mcpPort, '127.0.0.1', () => {
74
77
  console.log(`[MCP] SSE server listening on http://127.0.0.1:${mcpPort}/sse`);
78
+ recordMcpStarted(mcpPort);
75
79
  });
76
80
  return toDisposable(() => {
77
81
  transports.clear();
78
82
  httpServer.close();
83
+ recordMcpStopped();
79
84
  });
80
85
  }
81
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 = 9222;
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: [] },
@@ -52,8 +52,6 @@ export interface RendererNotifier {
52
52
  popoverClosed(): void;
53
53
  /** Ask the main renderer to relaunch the simulator with a new config. */
54
54
  popoverRelaunch(config: CompileConfig): void;
55
- /** Ask the main renderer to re-initialise the devtools panels. */
56
- workbenchReset(): void;
57
55
  /** Tell the main renderer the toolbar actions list has changed. */
58
56
  toolbarActionsChanged(): void;
59
57
  /** Initialise the currently shown compile popover overlay. */
@@ -69,7 +67,9 @@ export interface RendererNotifier {
69
67
  * between the notifier module and workbench-context.
70
68
  */
71
69
  export interface NotifierContext {
72
- mainWindow: BrowserWindow;
70
+ windows: {
71
+ readonly mainWindow: BrowserWindow;
72
+ };
73
73
  views: {
74
74
  getSettingsWebContents(): WebContents | null;
75
75
  };
@@ -1,4 +1,4 @@
1
- import { ProjectChannel, WindowChannel, SettingsChannel, PopoverChannel, WorkbenchChannel, ToolbarChannel, WorkbenchSettingsChannel, } from '../../../shared/ipc-channels.js';
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
  },
@@ -3,9 +3,9 @@
3
3
  * `<userData>/dimina-projects.json` and exposes the validation / compile-
4
4
  * config helpers historically provided by `project-repository.ts`.
5
5
  *
6
- * The repository module is kept for thumbnail/paths/page helpers and for
7
- * back-compat re-exports; the canonical list & validation surface for the
8
- * workspace service is this provider.
6
+ * The repository module is kept for thumbnail/paths/page helpers; the
7
+ * canonical list & validation surface for the workspace service is this
8
+ * provider.
9
9
  */
10
10
  import * as repo from './project-repository.js';
11
11
  import { loadThumbnail, saveThumbnailFromDataUrl } from './thumbnail.js';
@@ -12,7 +12,6 @@ export interface ProjectPages {
12
12
  export interface ProjectSettings {
13
13
  uploadWithSourceMap: boolean;
14
14
  }
15
- export type { CompileConfig } from '../../../shared/types.js';
16
15
  export declare function listProjects(): Project[];
17
16
  export declare function validateProjectDir(dirPath: string): string | null;
18
17
  export declare function hasProject(dirPath: string): boolean;