@dimina-kit/devtools 0.3.0 → 0.3.1-dev.20260515105504

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 (116) hide show
  1. package/README.md +103 -41
  2. package/dist/main/api.d.ts +6 -2
  3. package/dist/main/api.js +5 -2
  4. package/dist/main/app/app.d.ts +1 -1
  5. package/dist/main/app/app.js +71 -36
  6. package/dist/main/app/launch.d.ts +2 -2
  7. package/dist/main/app/launch.js +6 -3
  8. package/dist/main/index.bundle.js +1134 -300
  9. package/dist/main/ipc/app.d.ts +2 -1
  10. package/dist/main/ipc/app.js +5 -4
  11. package/dist/main/ipc/index.d.ts +5 -4
  12. package/dist/main/ipc/index.js +5 -4
  13. package/dist/main/ipc/panels.d.ts +2 -1
  14. package/dist/main/ipc/panels.js +14 -9
  15. package/dist/main/ipc/popover.d.ts +4 -1
  16. package/dist/main/ipc/popover.js +15 -5
  17. package/dist/main/ipc/projects.d.ts +4 -1
  18. package/dist/main/ipc/projects.js +42 -5
  19. package/dist/main/ipc/session.d.ts +4 -1
  20. package/dist/main/ipc/session.js +26 -6
  21. package/dist/main/ipc/settings.d.ts +4 -1
  22. package/dist/main/ipc/settings.js +30 -17
  23. package/dist/main/ipc/simulator-module.d.ts +8 -0
  24. package/dist/main/ipc/simulator-module.js +19 -0
  25. package/dist/main/ipc/simulator.d.ts +2 -1
  26. package/dist/main/ipc/simulator.js +29 -15
  27. package/dist/main/ipc/toolbar.d.ts +2 -1
  28. package/dist/main/ipc/toolbar.js +3 -2
  29. package/dist/main/services/automation/index.js +88 -13
  30. package/dist/main/services/default-adapter.js +1 -3
  31. package/dist/main/services/mcp/server.d.ts +2 -1
  32. package/dist/main/services/mcp/server.js +5 -0
  33. package/dist/main/services/module.d.ts +18 -0
  34. package/dist/main/services/module.js +2 -0
  35. package/dist/main/services/notifications/renderer-notifier.d.ts +2 -0
  36. package/dist/main/services/projects/project-repository.d.ts +1 -0
  37. package/dist/main/services/projects/project-repository.js +3 -0
  38. package/dist/main/services/settings/index.d.ts +4 -0
  39. package/dist/main/services/settings/index.js +12 -1
  40. package/dist/main/services/simulator/custom-apis.d.ts +10 -0
  41. package/dist/main/services/simulator/custom-apis.js +26 -0
  42. package/dist/main/services/simulator/dir.d.ts +1 -1
  43. package/dist/main/services/simulator/dir.js +1 -4
  44. package/dist/main/services/simulator-storage/index.d.ts +22 -1
  45. package/dist/main/services/simulator-storage/index.js +390 -84
  46. package/dist/main/services/update/update-manager.d.ts +16 -1
  47. package/dist/main/services/update/update-manager.js +18 -15
  48. package/dist/main/services/views/view-manager.d.ts +4 -0
  49. package/dist/main/services/views/view-manager.js +33 -2
  50. package/dist/main/services/window-service.d.ts +24 -0
  51. package/dist/main/services/window-service.js +26 -0
  52. package/dist/main/services/workbench-context.d.ts +15 -0
  53. package/dist/main/services/workbench-context.js +6 -0
  54. package/dist/main/services/workspace/workspace-service.d.ts +1 -0
  55. package/dist/main/services/workspace/workspace-service.js +7 -3
  56. package/dist/main/simulator-apis.d.ts +23 -0
  57. package/dist/main/simulator-apis.js +24 -0
  58. package/dist/main/utils/disposable.d.ts +13 -0
  59. package/dist/main/utils/disposable.js +60 -0
  60. package/dist/main/utils/ipc-registry.d.ts +39 -0
  61. package/dist/main/utils/ipc-registry.js +109 -0
  62. package/dist/main/utils/ipc-schema.d.ts +28 -0
  63. package/dist/main/utils/ipc-schema.js +38 -0
  64. package/dist/main/utils/paths.d.ts +8 -0
  65. package/dist/main/utils/paths.js +33 -4
  66. package/dist/main/utils/sender-policy.d.ts +23 -0
  67. package/dist/main/utils/sender-policy.js +42 -0
  68. package/dist/main/windows/main-window/create.d.ts +6 -0
  69. package/dist/main/windows/main-window/create.js +65 -9
  70. package/dist/main/windows/main-window/events.d.ts +2 -1
  71. package/dist/main/windows/main-window/events.js +25 -2
  72. package/dist/main/windows/navigation-hardening.d.ts +24 -0
  73. package/dist/main/windows/navigation-hardening.js +55 -0
  74. package/dist/main/windows/settings-window/create.js +11 -1
  75. package/dist/preload/index.d.ts +17 -0
  76. package/dist/preload/index.js +17 -0
  77. package/dist/preload/instrumentation/app-data.js +178 -19
  78. package/dist/preload/instrumentation/wxml-extract.js +134 -3
  79. package/dist/preload/runtime/bridge.d.ts +3 -1
  80. package/dist/preload/runtime/bridge.js +55 -4
  81. package/dist/preload/runtime/custom-apis.d.ts +6 -0
  82. package/dist/preload/runtime/custom-apis.js +18 -0
  83. package/dist/preload/runtime/host.d.ts +1 -0
  84. package/dist/preload/runtime/host.js +13 -0
  85. package/dist/preload/shared/types.d.ts +2 -1
  86. package/dist/preload/windows/main.cjs +47 -0
  87. package/dist/preload/windows/main.cjs.map +7 -0
  88. package/dist/preload/windows/simulator.js +274 -25
  89. package/dist/renderer/assets/index-DW_0thr-.js +46 -0
  90. package/dist/renderer/assets/{input-VLotwer2.js → input-C5M5Wxn6.js} +2 -2
  91. package/dist/renderer/assets/{ipc-transport-COKIC6Ny.js → ipc-transport-CUsl-fS2.js} +6 -6
  92. package/dist/renderer/assets/ipc-transport-CltRuvhi.css +1 -0
  93. package/dist/renderer/assets/{popover-Dn0RwMLI.js → popover-BSBPVO0o.js} +2 -2
  94. package/dist/renderer/assets/{select-DzIsLaKX.js → select-B1tF3UAc.js} +2 -2
  95. package/dist/renderer/assets/{settings-CD-dacWB.js → settings-7w54H6p-.js} +2 -2
  96. package/dist/renderer/assets/settings-api-BjJalghB.js +2 -0
  97. package/dist/renderer/assets/workbenchSettings-q2_-nc8t.js +8 -0
  98. package/dist/renderer/entries/main/index.html +12 -5
  99. package/dist/renderer/entries/popover/index.html +7 -5
  100. package/dist/renderer/entries/settings/index.html +7 -5
  101. package/dist/renderer/entries/workbench-settings/index.html +6 -4
  102. package/dist/shared/ipc-channels.d.ts +45 -0
  103. package/dist/shared/ipc-channels.js +28 -0
  104. package/dist/shared/ipc-schemas.d.ts +113 -0
  105. package/dist/shared/ipc-schemas.js +128 -0
  106. package/dist/shared/types.d.ts +2 -0
  107. package/dist/simulator/assets/simulator-DL6kOCq-.js +9 -0
  108. package/dist/simulator/simulator.html +1 -1
  109. package/package.json +11 -27
  110. package/dist/preload/windows/main.d.ts +0 -6
  111. package/dist/preload/windows/main.js +0 -6
  112. package/dist/renderer/assets/index-D_HFRqZA.js +0 -46
  113. package/dist/renderer/assets/ipc-transport-6Gy3FXIL.css +0 -1
  114. package/dist/renderer/assets/settings-api-BV9MPckG.js +0 -2
  115. package/dist/renderer/assets/workbenchSettings-ClwgsvJL.js +0 -8
  116. package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
@@ -1,3 +1,4 @@
1
+ import { registerSyntheticSid } from '../runtime/bridge.js';
1
2
  const INTERNAL_CLASSES = new Set([
2
3
  'dd-swiper-wrapper', 'dd-swiper-slides', 'dd-swiper-slide-frame', 'dd-swiper-dots',
3
4
  'dd-picker-overlay', 'dd-picker-container', 'dd-picker-header', 'dd-picker-body',
@@ -24,10 +25,44 @@ function resolveTemplateNameFromParent(instance) {
24
25
  }
25
26
  return null;
26
27
  }
28
+ /**
29
+ * Convert a PascalCase / camelCase identifier to kebab-case. Matches the
30
+ * upstream `camelCaseToUnderscore` (dimina/fe/packages/common) so a name
31
+ * already normalized by `withInstall` round-trips identically.
32
+ * `View` -> `view`, `ScrollView` -> `scroll-view`, `CoverImage` -> `cover-image`.
33
+ */
34
+ function pascalToKebab(name) {
35
+ return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
36
+ }
37
+ /**
38
+ * Read the page's source path off Vue's provide/inject. dimina runtime.js 在
39
+ * dd-page 的 setup() 里调用 `provide('path', path)`,Vue 把它存到
40
+ * `instance.provides`。我们利用这个把页面节点的 tagName 从硬编码 `page`
41
+ * 升级为页面全路径(如 `pages/index/index`),对齐微信开发者工具。
42
+ */
43
+ function resolvePagePath(instance) {
44
+ // dimina runtime 在 dd-page 的 setup 里 `instance.proxy.__page__ = true` 并
45
+ // `provide('path', path)`。两个标记同时具备才视为页面层级,避免 dd-page
46
+ // 的子节点继承 provides.path 后被误判(Vue 用 Object.create 链式 provides)。
47
+ const proxy = instance.proxy;
48
+ if (proxy?.__page__ !== true)
49
+ return null;
50
+ const provides = instance.provides;
51
+ const path = provides?.path;
52
+ if (typeof path !== 'string' || !path)
53
+ return null;
54
+ return path.startsWith('/') ? path.slice(1) : path;
55
+ }
27
56
  function resolveTagName(instance) {
28
57
  const type = instance.type;
29
58
  if (!type)
30
59
  return 'unknown';
60
+ // 页面层级优先:dd-page 没有 __tagName/__name,且 home 页等无 usingComponents
61
+ // 的页面 type.components 为 undefined,会落到 resolveTemplateNameFromParent
62
+ // 回到 'page'。在那之前直接用 provide('path') 的路径升级 tag 名。
63
+ const pagePath = resolvePagePath(instance);
64
+ if (pagePath)
65
+ return pagePath;
31
66
  if (typeof type.__tagName === 'string')
32
67
  return type.__tagName;
33
68
  const name = (type.__name || type.name);
@@ -42,8 +77,16 @@ function resolveTagName(instance) {
42
77
  return 'page';
43
78
  if (name.startsWith('dd-'))
44
79
  return name.slice(3);
80
+ // Reverse-map Dimina component names back to their miniprogram tag names.
81
+ // The installer (`withInstall`) sets `__tagName = camelCaseToUnderscore(__name)`,
82
+ // but in dev builds, custom registrations, or when the installer hasn't run,
83
+ // we may only see the raw `__name`/`name` (e.g. `View`, `ScrollView`, `DdButton`).
84
+ // Without this fallback the WXML panel would surface the upstream Vue name
85
+ // verbatim, defeating the panel's purpose of showing source-level tags.
45
86
  if (name.startsWith('Dd') && name.length > 2)
46
- return name.slice(2).toLowerCase();
87
+ return pascalToKebab(name.slice(2));
88
+ if (/^[A-Z]/.test(name))
89
+ return pascalToKebab(name);
47
90
  return name;
48
91
  }
49
92
  function extractProps(instance) {
@@ -79,7 +122,12 @@ function isInternalElement(vnode) {
79
122
  function getElementSid(instance) {
80
123
  const subTree = instance.subTree;
81
124
  const el = subTree?.el;
82
- return el?.getAttribute ? el.getAttribute('data-sid') ?? undefined : undefined;
125
+ if (!el?.getAttribute)
126
+ return undefined;
127
+ const sid = el.getAttribute('data-sid');
128
+ if (sid)
129
+ return sid;
130
+ return registerSyntheticSid(el);
83
131
  }
84
132
  function isTransparentComponent(instance, tagName) {
85
133
  if (tagName === 'unknown')
@@ -97,9 +145,29 @@ function isTransparentComponent(instance, tagName) {
97
145
  }
98
146
  return false;
99
147
  }
148
+ /**
149
+ * 检测 Vue 的 Comment vnode(`v-if`/`v-else`/`v-for` 占位锚点)。
150
+ * Vue 在条件不成立时会留下 `<!-- v-if -->` 这样的注释 vnode 用作 DOM 锚点。
151
+ * 这些 vnode 不是用户内容,必须从 wxml 树里剔除。
152
+ *
153
+ * - dev build:type 是 `Symbol('Comment')`,description = 'Comment' 或 'v-cmt'
154
+ * - prod build:description 可能丢失,但 children 一般是 'v-if'/'v-else' 等 marker
155
+ */
156
+ function isCommentVNode(vnode) {
157
+ const type = vnode.type;
158
+ if (typeof type !== 'symbol')
159
+ return false;
160
+ const desc = type.description;
161
+ if (desc === 'Comment' || desc === 'v-cmt')
162
+ return true;
163
+ const children = typeof vnode.children === 'string' ? vnode.children : '';
164
+ return /^v-(if|else|else-if|for)$/.test(children);
165
+ }
100
166
  function extractChildrenFromVNode(vnode, depth) {
101
167
  if (!vnode || depth > 50)
102
168
  return [];
169
+ if (isCommentVNode(vnode))
170
+ return [];
103
171
  if (vnode.component) {
104
172
  const result = walkInstance(vnode.component, depth + 1);
105
173
  return result ? (Array.isArray(result) ? result : [result]) : [];
@@ -157,6 +225,69 @@ function extractChildrenFromVNode(vnode, depth) {
157
225
  }
158
226
  return result;
159
227
  }
228
+ /**
229
+ * Reverse-map Dimina's `<wrapper name="/components/foo/foo">` (used to host
230
+ * every user-defined component) back to the source-level tag the user wrote
231
+ * in WXML (e.g. `<foo>`).
232
+ *
233
+ * The wrapper carries the registered component path in its `name` Vue prop.
234
+ * By convention (also followed by miniprogram tooling), the registered key is
235
+ * the last path segment, optionally stripping a trailing `/index`. We can't
236
+ * see the page's `usingComponents` map from inside the Vue instance, so this
237
+ * convention-based recovery is best-effort: if the user picked a different
238
+ * registration key than the directory name (e.g. `usingComponents:
239
+ * { myCounter: '/components/counter/counter' }`), the panel will show
240
+ * `counter`, not `myCounter`.
241
+ *
242
+ * The path heuristic also resolves a name-collision risk: a user-written
243
+ * `<counter name="x">` would land in the same `attrs.name` slot as the
244
+ * wrapper-internal path. We only unwrap when the value looks like an absolute
245
+ * component path (leading `/`), which dimina always emits but a user would
246
+ * almost never type as a literal attr.
247
+ */
248
+ function unwrapCustomComponent(node) {
249
+ if (node.tagName !== 'wrapper')
250
+ return node;
251
+ const path = node.attrs?.name;
252
+ if (typeof path !== 'string' || !path.startsWith('/'))
253
+ return node;
254
+ // 去掉前导 `/` 后保留原路径形式(保留所有斜杠,不做 kebab/dash 转换)。
255
+ // 仅当路径以 `/index` 结尾且剥离后仍至少剩一段时才剥(`/index` 单独存在则
256
+ // 退回 wrapper,避免出现空 tagName)。
257
+ const stripped = path.replace(/^\//, '');
258
+ const withoutIndex = stripped.endsWith('/index') ? stripped.slice(0, -'/index'.length) : stripped;
259
+ const recovered = withoutIndex || stripped;
260
+ if (!recovered || recovered === 'index')
261
+ return node;
262
+ const nextAttrs = {};
263
+ for (const [k, v] of Object.entries(node.attrs)) {
264
+ if (k === 'name')
265
+ continue;
266
+ nextAttrs[k] = v;
267
+ }
268
+ return { ...node, tagName: recovered, attrs: nextAttrs };
269
+ }
270
+ /**
271
+ * 把"用户授权"层级(页面 / 自定义组件,tagName 以路径形式呈现,含 `/`)
272
+ * 的 children 包一层合成 `#shadow-root`,对齐微信开发者工具:组件本身
273
+ * 与内部实现之间用 shadow-root 边界视觉分隔。
274
+ *
275
+ * 内置组件(view/text/button 等)和合成节点(#text/#fragment)tagName 都
276
+ * 不含 `/`,自然不会被包裹。children 为空也不插入(避免空壳)。重复调用
277
+ * 是幂等的:若 children[0] 已经是 #shadow-root 就跳过。
278
+ */
279
+ function wrapInShadowRoot(node) {
280
+ if (!node.tagName.includes('/'))
281
+ return node;
282
+ if (node.children.length === 0)
283
+ return node;
284
+ if (node.children[0]?.tagName === '#shadow-root')
285
+ return node;
286
+ return {
287
+ ...node,
288
+ children: [{ tagName: '#shadow-root', attrs: {}, children: node.children }],
289
+ };
290
+ }
160
291
  export function walkInstance(instance, depth) {
161
292
  if (depth > 50)
162
293
  return null;
@@ -168,6 +299,6 @@ export function walkInstance(instance, depth) {
168
299
  const sid = getElementSid(instance);
169
300
  if (sid)
170
301
  node.sid = sid;
171
- return node;
302
+ return wrapInShadowRoot(unwrapCustomComponent(node));
172
303
  }
173
304
  //# sourceMappingURL=wxml-extract.js.map
@@ -1,3 +1,4 @@
1
+ import type { ElementInspection } from '../../shared/ipc-channels.js';
1
2
  export interface WxmlNode {
2
3
  tagName: string;
3
4
  attrs: Record<string, string>;
@@ -20,7 +21,8 @@ interface SimulatorBridgeState {
20
21
  wxml: Snapshot<WxmlNode | WxmlNode[] | null>;
21
22
  refreshHandler: ((type: RefreshTarget) => void) | null;
22
23
  }
23
- declare function highlightElement(sid: string): void;
24
+ export declare function registerSyntheticSid(el: HTMLElement): string;
25
+ declare function highlightElement(sid: string): ElementInspection | null;
24
26
  declare function unhighlightElement(): void;
25
27
  export { highlightElement, unhighlightElement };
26
28
  export declare const simulatorBridge: SimulatorBridgeState;
@@ -7,6 +7,38 @@ const state = {
7
7
  };
8
8
  let highlightOverlay = null;
9
9
  let exposedApi = null;
10
+ // 合成 sid 注册表:用 WeakMap 把元素 ↔ sid 双向绑定,避免在源 DOM 上写
11
+ // `data-*` 属性(提取本应只读,且属性形式会污染用户的快照/选择器)。
12
+ // elBySyntheticSid 为反向查找用 WeakRef,元素被 GC 后下次 lookup 自动清理。
13
+ const SYNTHETIC_SID_PREFIX = 'devtools-';
14
+ const syntheticSidByEl = new WeakMap();
15
+ const elBySyntheticSid = new Map();
16
+ let nextSyntheticSid = 1;
17
+ export function registerSyntheticSid(el) {
18
+ const existing = syntheticSidByEl.get(el);
19
+ if (existing)
20
+ return existing;
21
+ const synthetic = `${SYNTHETIC_SID_PREFIX}${nextSyntheticSid++}`;
22
+ syntheticSidByEl.set(el, synthetic);
23
+ elBySyntheticSid.set(synthetic, new WeakRef(el));
24
+ return synthetic;
25
+ }
26
+ function findElementBySid(doc, sid) {
27
+ if (sid.startsWith(SYNTHETIC_SID_PREFIX)) {
28
+ const ref = elBySyntheticSid.get(sid);
29
+ if (!ref)
30
+ return null;
31
+ const el = ref.deref();
32
+ if (!el || !el.isConnected) {
33
+ elBySyntheticSid.delete(sid);
34
+ return null;
35
+ }
36
+ if (el.ownerDocument !== doc)
37
+ return null;
38
+ return el;
39
+ }
40
+ return doc.querySelector(`[data-sid="${CSS.escape(sid)}"]`);
41
+ }
10
42
  function clone(value) {
11
43
  try {
12
44
  return JSON.parse(JSON.stringify(value));
@@ -27,18 +59,20 @@ function ensureOverlay(doc) {
27
59
  highlightOverlay.style.cssText =
28
60
  'position:fixed;pointer-events:none;z-index:999999;' +
29
61
  'border:2px solid #1a73e8;background:rgba(26,115,232,0.12);' +
30
- 'transition:all 0.1s ease;display:none;border-radius:2px;';
62
+ 'transition:all 0.1s ease;display:none;border-radius:2px;box-sizing:border-box;';
31
63
  doc.body.appendChild(highlightOverlay);
32
64
  return highlightOverlay;
33
65
  }
34
66
  function highlightElement(sid) {
67
+ if (!sid)
68
+ return null;
35
69
  const iframe = getPageIframe();
36
70
  if (!iframe?.contentDocument)
37
- return;
71
+ return null;
38
72
  const doc = iframe.contentDocument;
39
- const el = doc.querySelector(`[data-sid="${sid}"]`);
73
+ const el = findElementBySid(doc, sid);
40
74
  if (!el)
41
- return;
75
+ return null;
42
76
  const rect = el.getBoundingClientRect();
43
77
  const overlay = ensureOverlay(doc);
44
78
  overlay.style.left = `${rect.left}px`;
@@ -46,6 +80,23 @@ function highlightElement(sid) {
46
80
  overlay.style.width = `${rect.width}px`;
47
81
  overlay.style.height = `${rect.height}px`;
48
82
  overlay.style.display = 'block';
83
+ const style = el.ownerDocument.defaultView?.getComputedStyle(el);
84
+ if (!style)
85
+ return null;
86
+ return {
87
+ sid,
88
+ rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
89
+ style: {
90
+ display: style.display,
91
+ position: style.position,
92
+ boxSizing: style.boxSizing,
93
+ margin: style.margin,
94
+ padding: style.padding,
95
+ color: style.color,
96
+ backgroundColor: style.backgroundColor,
97
+ fontSize: style.fontSize,
98
+ },
99
+ };
49
100
  }
50
101
  function unhighlightElement() {
51
102
  if (highlightOverlay)
@@ -0,0 +1,6 @@
1
+ export interface DiminaCustomApisBridge {
2
+ list(): Promise<string[]>;
3
+ invoke(name: string, params: unknown): Promise<unknown>;
4
+ }
5
+ export declare function installCustomApisBridge(): void;
6
+ //# sourceMappingURL=custom-apis.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { contextBridge, ipcRenderer } from 'electron';
2
+ import { SimulatorCustomApiChannel } from '../../shared/ipc-channels.js';
3
+ function buildBridge() {
4
+ return {
5
+ list: () => ipcRenderer.invoke(SimulatorCustomApiChannel.List),
6
+ invoke: (name, params) => ipcRenderer.invoke(SimulatorCustomApiChannel.Invoke, name, params),
7
+ };
8
+ }
9
+ export function installCustomApisBridge() {
10
+ const bridge = buildBridge();
11
+ try {
12
+ contextBridge.exposeInMainWorld('__diminaCustomApis', bridge);
13
+ }
14
+ catch {
15
+ window.__diminaCustomApis = bridge;
16
+ }
17
+ }
18
+ //# sourceMappingURL=custom-apis.js.map
@@ -1,3 +1,4 @@
1
1
  export declare function sendToHost(channel: string, data: unknown): void;
2
+ export declare function onHostMessage(channel: string, handler: (...args: unknown[]) => void): () => void;
2
3
  export declare function safeSerialize(val: unknown): unknown;
3
4
  //# sourceMappingURL=host.d.ts.map
@@ -2,6 +2,19 @@ import { ipcRenderer } from 'electron';
2
2
  export function sendToHost(channel, data) {
3
3
  ipcRenderer.sendToHost(channel, data);
4
4
  }
5
+ export function onHostMessage(channel, handler) {
6
+ let active = true;
7
+ const wrapped = (_event, ...args) => {
8
+ if (!active)
9
+ return;
10
+ handler(...args);
11
+ };
12
+ ipcRenderer.on(channel, wrapped);
13
+ return () => {
14
+ active = false;
15
+ ipcRenderer.removeListener(channel, wrapped);
16
+ };
17
+ }
5
18
  export function safeSerialize(val) {
6
19
  if (val === null || val === undefined)
7
20
  return val;
@@ -5,6 +5,7 @@
5
5
  * window by our preload/instrumentation code.
6
6
  */
7
7
  import type { Snapshot, StorageSnapshot, WxmlNode } from '../runtime/bridge.js';
8
+ import type { ElementInspection } from '../../shared/ipc-channels.js';
8
9
  /** The simulator data bridge exposed via contextBridge. */
9
10
  export interface SimulatorDataBridge {
10
11
  getAppdata: () => Record<string, unknown>;
@@ -16,7 +17,7 @@ export interface SimulatorDataBridge {
16
17
  getWxmlSnapshot: () => Snapshot<WxmlNode | WxmlNode[] | null>;
17
18
  getWxmlGen: () => number;
18
19
  refresh: (type: 'wxml' | 'appdata' | 'storage') => void;
19
- highlightElement: (sid: string) => void;
20
+ highlightElement: (sid: string) => ElementInspection | null;
20
21
  unhighlightElement: () => void;
21
22
  }
22
23
  /** Hook interface for app data interception. */
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/preload/windows/main.ts
17
+ var main_exports = {};
18
+ module.exports = __toCommonJS(main_exports);
19
+ var import_electron = require("electron");
20
+ var api = {
21
+ ipc: {
22
+ invoke: (channel, ...args) => import_electron.ipcRenderer.invoke(channel, ...args),
23
+ send: (channel, ...args) => {
24
+ import_electron.ipcRenderer.send(channel, ...args);
25
+ },
26
+ on: (channel, listener) => {
27
+ import_electron.ipcRenderer.on(channel, listener);
28
+ return () => import_electron.ipcRenderer.removeListener(channel, listener);
29
+ },
30
+ once: (channel, listener) => {
31
+ import_electron.ipcRenderer.once(channel, listener);
32
+ },
33
+ removeListener: (channel, listener) => {
34
+ import_electron.ipcRenderer.removeListener(channel, listener);
35
+ }
36
+ }
37
+ };
38
+ import_electron.contextBridge.exposeInMainWorld("devtools", api);
39
+ if (process.env.NODE_ENV === "test") {
40
+ import_electron.contextBridge.exposeInMainWorld("__testIpc", {
41
+ emit: (channel, ...args) => {
42
+ ;
43
+ import_electron.ipcRenderer.emit(channel, ...args);
44
+ }
45
+ });
46
+ }
47
+ //# sourceMappingURL=main.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/preload/windows/main.ts"],
4
+ "sourcesContent": ["/**\n * Preload bridge for the main window, settings window, settings overlay\n * view, and popover overlay view. Exposes a minimal, typed `window.devtools`\n * surface so the renderer never has to call `window.require('electron')`.\n *\n * This file is bundled by esbuild to CJS at `dist/preload/windows/main.js`\n * (see package.json `build:preload`). It must remain a leaf module \u2014 do\n * NOT import other preload files via `.js` ESM specifiers; everything goes\n * through the Electron `contextBridge` / `ipcRenderer` runtime.\n *\n * Channel governance lives in the main process: `sender-policy.ts` plus the\n * per-handler zod schemas decide what's accepted. The preload layer is a\n * thin pass-through \u2014 a renderer inventing channel names will just hit a\n * main-side rejection, so duplicating an allowlist here added no real\n * defense for a devtool that loads user-trusted mini-programs.\n */\nimport { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron'\n\nconst api = {\n ipc: {\n invoke: (channel: string, ...args: unknown[]) => ipcRenderer.invoke(channel, ...args),\n send: (channel: string, ...args: unknown[]) => {\n ipcRenderer.send(channel, ...args)\n },\n on: (\n channel: string,\n listener: (event: IpcRendererEvent, ...args: unknown[]) => void,\n ) => {\n ipcRenderer.on(channel, listener)\n return () => ipcRenderer.removeListener(channel, listener)\n },\n once: (\n channel: string,\n listener: (event: IpcRendererEvent, ...args: unknown[]) => void,\n ) => {\n ipcRenderer.once(channel, listener)\n },\n removeListener: (\n channel: string,\n listener: (...args: unknown[]) => void,\n ) => {\n ipcRenderer.removeListener(channel, listener)\n },\n },\n}\n\n// All host windows that load this preload run with `contextIsolation: true`\n// (main / settings / view-manager), so `contextBridge` is the path that\n// reaches the renderer's `window`. The simulator <webview> uses a separate\n// preload (`simulator.ts`).\ncontextBridge.exposeInMainWorld('devtools', api)\n\n// Test-only hatch: e2e helpers emit synthetic IPC events on the renderer's\n// ipcRenderer instance to trigger handlers registered via `devtools.ipc.on`\n// without round-tripping through the main process. Gated on NODE_ENV so\n// production builds never expose it.\nif (process.env.NODE_ENV === 'test') {\n contextBridge.exposeInMainWorld('__testIpc', {\n emit: (channel: string, ...args: unknown[]) => {\n ;(ipcRenderer as unknown as { emit: (c: string, ...a: unknown[]) => void }).emit(channel, ...args)\n },\n })\n}\n\nexport type DevtoolsApi = typeof api\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;AAgBA,sBAAkE;AAElE,IAAM,MAAM;AAAA,EACV,KAAK;AAAA,IACH,QAAQ,CAAC,YAAoB,SAAoB,4BAAY,OAAO,SAAS,GAAG,IAAI;AAAA,IACpF,MAAM,CAAC,YAAoB,SAAoB;AAC7C,kCAAY,KAAK,SAAS,GAAG,IAAI;AAAA,IACnC;AAAA,IACA,IAAI,CACF,SACA,aACG;AACH,kCAAY,GAAG,SAAS,QAAQ;AAChC,aAAO,MAAM,4BAAY,eAAe,SAAS,QAAQ;AAAA,IAC3D;AAAA,IACA,MAAM,CACJ,SACA,aACG;AACH,kCAAY,KAAK,SAAS,QAAQ;AAAA,IACpC;AAAA,IACA,gBAAgB,CACd,SACA,aACG;AACH,kCAAY,eAAe,SAAS,QAAQ;AAAA,IAC9C;AAAA,EACF;AACF;AAMA,8BAAc,kBAAkB,YAAY,GAAG;AAM/C,IAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,gCAAc,kBAAkB,aAAa;AAAA,IAC3C,MAAM,CAAC,YAAoB,SAAoB;AAC7C;AAAC,MAAC,4BAA0E,KAAK,SAAS,GAAG,IAAI;AAAA,IACnG;AAAA,EACF,CAAC;AACH;",
6
+ "names": []
7
+ }