@dimina-kit/devtools 0.4.0-dev.20260616102751 → 0.4.0-dev.20260624040247

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 (78) hide show
  1. package/README.md +30 -25
  2. package/dist/main/app/app.js +14 -2
  3. package/dist/main/index.bundle.js +559 -111
  4. package/dist/main/ipc/bridge-router.js +57 -9
  5. package/dist/main/ipc/projects.js +1 -1
  6. package/dist/main/ipc/simulator.js +1 -1
  7. package/dist/main/runtime/devtools-backend.d.ts +1 -1
  8. package/dist/main/runtime/devtools-backend.js +1 -1
  9. package/dist/main/runtime/miniapp-runtime.d.ts +5 -5
  10. package/dist/main/services/console-forward/index.js +8 -1
  11. package/dist/main/services/elements-forward/index.js +36 -12
  12. package/dist/main/services/layout/index.d.ts +12 -0
  13. package/dist/main/services/layout/index.js +14 -2
  14. package/dist/main/services/network-forward/index.d.ts +5 -6
  15. package/dist/main/services/network-forward/index.js +9 -10
  16. package/dist/main/services/notifications/renderer-notifier.d.ts +3 -4
  17. package/dist/main/services/projects/index.js +1 -1
  18. package/dist/main/services/projects/types.d.ts +1 -1
  19. package/dist/main/services/projects/types.js +1 -1
  20. package/dist/main/services/service-console/console-api.d.ts +77 -0
  21. package/dist/main/services/service-console/console-api.js +103 -0
  22. package/dist/main/services/service-console/index.d.ts +47 -0
  23. package/dist/main/services/service-console/index.js +139 -0
  24. package/dist/main/services/service-host-pool/pool.d.ts +2 -3
  25. package/dist/main/services/simulator-storage/index.d.ts +2 -4
  26. package/dist/main/services/simulator-storage/index.js +2 -4
  27. package/dist/main/services/simulator-temp-files/index.js +2 -2
  28. package/dist/main/services/update/update-manager.d.ts +1 -1
  29. package/dist/main/services/update/update-manager.js +1 -1
  30. package/dist/main/services/views/host-toolbar-session-runtime.d.ts +4 -4
  31. package/dist/main/services/views/host-toolbar-session-runtime.js +4 -4
  32. package/dist/main/services/views/view-manager.d.ts +24 -1
  33. package/dist/main/services/views/view-manager.js +120 -41
  34. package/dist/main/services/workbench-context.d.ts +7 -6
  35. package/dist/main/services/workspace/workspace-service.js +11 -0
  36. package/dist/main/windows/service-host-window/create.d.ts +1 -0
  37. package/dist/main/windows/service-host-window/create.js +1 -0
  38. package/dist/preload/runtime/host-toolbar-port.d.ts +1 -1
  39. package/dist/preload/runtime/host-toolbar-port.js +1 -1
  40. package/dist/preload/runtime/host-toolbar-runtime.d.ts +8 -7
  41. package/dist/preload/runtime/host-toolbar-runtime.js +8 -7
  42. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  43. package/dist/preload/windows/main.cjs.map +2 -2
  44. package/dist/preload/windows/simulator.cjs +1 -1
  45. package/dist/preload/windows/simulator.cjs.map +1 -1
  46. package/dist/preload/windows/simulator.js +1 -1
  47. package/dist/renderer/assets/index-PtsdWlGu.js +50 -0
  48. package/dist/renderer/assets/{input-B8NDCYv3.js → input-DiCxYlqY.js} +2 -2
  49. package/dist/renderer/assets/{ipc-transport-DhrajiC5.js → ipc-transport-DSBmaWpJ.js} +1 -1
  50. package/dist/renderer/assets/ipc-transport-DZxf2YoM.css +1 -0
  51. package/dist/renderer/assets/{popover-DbZOg03s.js → popover-CyL_uffj.js} +2 -2
  52. package/dist/renderer/assets/{select-pW3Ysc9T.js → select-BSwvDMtA.js} +2 -2
  53. package/dist/renderer/assets/settings-Ch8XbFxf.js +2 -0
  54. package/dist/renderer/assets/{settings-api-BSKzKiWd.js → settings-api-e9ILe2JO.js} +2 -2
  55. package/dist/renderer/assets/{workbenchSettings-BGxjl25x.js → workbenchSettings-DZsHf1g_.js} +2 -2
  56. package/dist/renderer/entries/main/index.html +6 -6
  57. package/dist/renderer/entries/popover/index.html +5 -5
  58. package/dist/renderer/entries/settings/index.html +11 -5
  59. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  60. package/dist/service-host/preload.cjs +13 -15
  61. package/dist/service-host/sourcemap-rewrite.cjs +21 -12
  62. package/dist/shared/appdata-accumulator.d.ts +2 -2
  63. package/dist/shared/appdata-accumulator.js +3 -3
  64. package/dist/shared/constants.d.ts +1 -1
  65. package/dist/shared/constants.js +1 -1
  66. package/dist/shared/ipc-channels.d.ts +3 -3
  67. package/dist/shared/ipc-channels.js +3 -3
  68. package/dist/shared/open-in-editor.d.ts +20 -1
  69. package/dist/shared/open-in-editor.js +246 -3
  70. package/dist/shared/vpath.d.ts +2 -2
  71. package/dist/shared/vpath.js +2 -2
  72. package/dist/simulator/assets/{device-shell-BWX7Yopg.js → device-shell-TtcSXwhb.js} +2 -2
  73. package/dist/simulator/assets/{simulator-sf-D0mhw.js → simulator-CtyXt_4V.js} +3 -3
  74. package/dist/simulator/simulator.html +1 -1
  75. package/package.json +8 -6
  76. package/dist/renderer/assets/index-Z4Rh52G8.js +0 -50
  77. package/dist/renderer/assets/ipc-transport-9agi76dX.css +0 -1
  78. package/dist/renderer/assets/settings-CZJOHt8b.js +0 -2
@@ -5,12 +5,18 @@
5
5
  <!-- See main/index.html for CSP rationale. -->
6
6
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; media-src 'self' blob: http: https:; connect-src 'self' http: https: ws: wss:;" />
7
7
  <title>Settings</title>
8
- <script type="module" crossorigin src="../../assets/settings-CZJOHt8b.js"></script>
8
+ <style>
9
+ /* The view spans the full content area as a transparent backdrop (only the
10
+ panel is opaque) so a click outside the panel reaches the backdrop and
11
+ closes the overlay — mirrors the popover entry. */
12
+ html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: transparent !important; }
13
+ </style>
14
+ <script type="module" crossorigin src="../../assets/settings-Ch8XbFxf.js"></script>
9
15
  <link rel="modulepreload" crossorigin href="../../assets/chunk-DPI0Y_ll.js">
10
- <link rel="modulepreload" crossorigin href="../../assets/ipc-transport-DhrajiC5.js">
11
- <link rel="modulepreload" crossorigin href="../../assets/input-B8NDCYv3.js">
12
- <link rel="modulepreload" crossorigin href="../../assets/settings-api-BSKzKiWd.js">
13
- <link rel="stylesheet" crossorigin href="../../assets/ipc-transport-9agi76dX.css">
16
+ <link rel="modulepreload" crossorigin href="../../assets/ipc-transport-DSBmaWpJ.js">
17
+ <link rel="modulepreload" crossorigin href="../../assets/input-DiCxYlqY.js">
18
+ <link rel="modulepreload" crossorigin href="../../assets/settings-api-e9ILe2JO.js">
19
+ <link rel="stylesheet" crossorigin href="../../assets/ipc-transport-DZxf2YoM.css">
14
20
  </head>
15
21
  <body>
16
22
  <div id="root"></div>
@@ -6,12 +6,12 @@
6
6
  <!-- See entries/main/index.html for CSP rationale. -->
7
7
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; media-src 'self' blob: http: https:; connect-src 'self' http: https: ws: wss:;" />
8
8
  <title>开发工具设置</title>
9
- <script type="module" crossorigin src="../../assets/workbenchSettings-BGxjl25x.js"></script>
9
+ <script type="module" crossorigin src="../../assets/workbenchSettings-DZsHf1g_.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="../../assets/chunk-DPI0Y_ll.js">
11
- <link rel="modulepreload" crossorigin href="../../assets/ipc-transport-DhrajiC5.js">
12
- <link rel="modulepreload" crossorigin href="../../assets/settings-api-BSKzKiWd.js">
11
+ <link rel="modulepreload" crossorigin href="../../assets/ipc-transport-DSBmaWpJ.js">
12
+ <link rel="modulepreload" crossorigin href="../../assets/settings-api-e9ILe2JO.js">
13
13
  <link rel="modulepreload" crossorigin href="../../assets/constants-DI382mTP.js">
14
- <link rel="stylesheet" crossorigin href="../../assets/ipc-transport-9agi76dX.css">
14
+ <link rel="stylesheet" crossorigin href="../../assets/ipc-transport-DZxf2YoM.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -192,14 +192,21 @@ ipcRenderer.on(CHANNELS.TO_SERVICE, (_event, payload) => {
192
192
  deliver(payload && payload.msg)
193
193
  })
194
194
 
195
- // ── Console capture (native-host) ───────────────────────────────────────────
195
+ // ── Uncaught error capture (native-host) ────────────────────────────────────
196
196
  // Placed AFTER the no-bridgeId early-return (pool warming) and AFTER
197
197
  // DiminaServiceBridge is defined, so it only runs for a real spawn and can use
198
- // the bridge. This preload runs with contextIsolation effectively off, so
199
- // patching `console` here captures the service guest's own console (wx.* +
200
- // logic.js). Each entry is forwarded to main as a `consoleLog` container message
201
- // (source:'service') bridge-router routes it to ctx.guestConsole → automation
202
- // rebroadcasts it as App.logAdded. Mirrors src/preload/instrumentation/console.ts.
198
+ // the bridge.
199
+ //
200
+ // NOTE: `console.*` is deliberately NOT patched here. A wrapper adds a stack
201
+ // frame, so the embedded Chrome DevTools (attached natively to this service
202
+ // host) would attribute every log to the wrapper line instead of the
203
+ // developer's source. Service-layer console output is captured in main via CDP
204
+ // `Runtime.consoleAPICalled` (services/service-console) — no extra frame, native
205
+ // attribution + sourcemaps preserved. Only WINDOW error / unhandledrejection
206
+ // events are posted here: they carry no console call site to preserve and CDP's
207
+ // `consoleAPICalled` does not report them. Each is forwarded to main as a
208
+ // `consoleLog` container message (source:'service') → bridge-router →
209
+ // ctx.guestConsole → automation `App.logAdded`.
203
210
 
204
211
  function safeSerializeArg(val) {
205
212
  if (val === null || val === undefined) return val
@@ -230,15 +237,6 @@ function emitConsoleLog(level, args) {
230
237
  } catch (_) { /* never let console capture break the guest */ }
231
238
  }
232
239
 
233
- ;['log', 'warn', 'error', 'info', 'debug'].forEach((level) => {
234
- const original = typeof console[level] === 'function' ? console[level].bind(console) : null
235
- if (!original) return
236
- console[level] = (...args) => {
237
- original(...args)
238
- emitConsoleLog(level, args)
239
- }
240
- })
241
-
242
240
  globalThis.addEventListener('error', (event) => {
243
241
  emitConsoleLog('error', [{
244
242
  message: event.message,
@@ -5,18 +5,27 @@
5
5
  * ABSOLUTE URL resolved against the script's own fetch URL.
6
6
  *
7
7
  * Why this exists (native-host console file links):
8
- * The service host loads each compiled `logic.js` via the `importScripts`
9
- * shim in preload.cjs a synchronous XHR + `(0, eval)(...)`. The compiled
10
- * bundle ships a RELATIVE `//# sourceMappingURL=logic.js.map`
11
- * (compiler/core/logic-compiler.js). A real Web Worker `importScripts(url)`
12
- * gives the script the dev-server URL as its base, so DevTools resolves the
13
- * relative map against `…/{appId}/{root}/logic.js` and fetches the right
14
- * `.map`. Under `(0, eval)(...)` the script has NO base URL of its own, so
15
- * DevTools resolves a relative `sourceMappingURL` against the service-host
16
- * DOCUMENT (`file://…/service-host/service.html`) and 404s console
17
- * file:line links then point at the compiled bundle, not the developer's
18
- * original source. Rewriting the directive to an absolute dev-server URL
19
- * restores sourcemapped console frames + Sources links.
8
+ * The compiled `logic.js` ships a RELATIVE `//# sourceMappingURL=logic.js.map`
9
+ * (compiler/core/logic-compiler.js). Whoever injects it must give DevTools a
10
+ * resolvable base for that relative map, or DevTools resolves it against the
11
+ * service-host DOCUMENT (`file://…/service-host/service.html`) and 404s
12
+ * console file:line links then point at the compiled bundle, not the
13
+ * developer's original source. Rewriting the directive to an absolute
14
+ * dev-server URL restores sourcemapped console frames + Sources links.
15
+ *
16
+ * Consumer: the `importScripts` shim in preload.cjs below (this file is COPIED,
17
+ * not bundled, so a plain relative `require('./sourcemap-rewrite.cjs')` works).
18
+ * The main process needs the same rewrite in `bridge-router.injectLogicBundle`
19
+ * but keeps its OWN inlined copy there — the shipped main entry is the flat
20
+ * esbuild bundle and a runtime `require` of this relative path resolved wrong
21
+ * against the bundle location (crashed the packaged app). KEEP THE TWO IN SYNC.
22
+ *
23
+ * Legacy consumer (web-worker render only): the `importScripts` shim in
24
+ * preload.cjs (synchronous XHR + `(0, eval)(...)`). Under native-host the
25
+ * service host is a BrowserWindow, not a Web Worker (`isWebWorker` false), so
26
+ * that shim never runs — kept for the worker render path. A real
27
+ * `importScripts(url)` would base the relative map on the script URL itself,
28
+ * but `(0, eval)(...)` does not, hence the same rewrite applies there too.
20
29
  *
21
30
  * Contract:
22
31
  * - Operates on the LAST `//# sourceMappingURL=` (or legacy `//@`) directive
@@ -11,7 +11,7 @@
11
11
  * Default (dimina-fe) path: the simulator preload sniffs these as Worker
12
12
  * `message` events (app-data.ts). Native-host path: they flow service→render
13
13
  * through bridge-router (SERVICE_PUBLISH). Both feed this one accumulator so the
14
- * decode/merge/page-only/R6 policy can't drift between modes.
14
+ * decode/merge/page-only/init-gate policy can't drift between modes.
15
15
  */
16
16
  /**
17
17
  * The AppData snapshot — the full, cumulative panel state.
@@ -74,7 +74,7 @@ export declare class AppDataAccumulator {
74
74
  private recordBridge;
75
75
  /**
76
76
  * Apply one entry. Returns true if it was accepted (a mutation worth
77
- * republishing), false if dropped (missing ids, or the R6 gate).
77
+ * republishing), false if dropped (missing ids, or the init-gate).
78
78
  */
79
79
  apply(input: AppDataInput): boolean;
80
80
  /** Evict every entry for a bridge (page teardown). */
@@ -11,7 +11,7 @@
11
11
  * Default (dimina-fe) path: the simulator preload sniffs these as Worker
12
12
  * `message` events (app-data.ts). Native-host path: they flow service→render
13
13
  * through bridge-router (SERVICE_PUBLISH). Both feed this one accumulator so the
14
- * decode/merge/page-only/R6 policy can't drift between modes.
14
+ * decode/merge/page-only/init-gate policy can't drift between modes.
15
15
  */
16
16
  /**
17
17
  * Decode a service→render message into AppData entries, or null when it is not
@@ -116,12 +116,12 @@ export class AppDataAccumulator {
116
116
  }
117
117
  /**
118
118
  * Apply one entry. Returns true if it was accepted (a mutation worth
119
- * republishing), false if dropped (missing ids, or the R6 gate).
119
+ * republishing), false if dropped (missing ids, or the init-gate).
120
120
  */
121
121
  apply(input) {
122
122
  if (!input.bridgeId || !input.moduleId)
123
123
  return false;
124
- // R6: drop ub patches whose bridge has never been initialised. dimina
124
+ // Drop ub patches whose bridge has never been initialised. dimina
125
125
  // dispatches pageUnload → onUnload, whose setData produces a late `ub`
126
126
  // arriving AFTER clearBridge; without this gate it would resurrect the
127
127
  // unloaded bridge as a ghost tab.
@@ -18,7 +18,7 @@ export declare const HEADER_H = 40;
18
18
  * defaultSession renderer; its guard activates the height advertiser only when
19
19
  * `process.argv` carries this marker AND `process.isMainFrame` is true (the
20
20
  * marker is process-level, so subframes of the toolbar window see it too —
21
- * both guard wings are required; see .repro/wave3-spike/RESULTS.md items 3/4).
21
+ * both guard wings are required).
22
22
  */
23
23
  export declare const HOST_TOOLBAR_RUNTIME_MARKER = "--dimina-host-toolbar";
24
24
  //# sourceMappingURL=constants.d.ts.map
@@ -18,7 +18,7 @@ export const HEADER_H = 40;
18
18
  * defaultSession renderer; its guard activates the height advertiser only when
19
19
  * `process.argv` carries this marker AND `process.isMainFrame` is true (the
20
20
  * marker is process-level, so subframes of the toolbar window see it too —
21
- * both guard wings are required; see .repro/wave3-spike/RESULTS.md items 3/4).
21
+ * both guard wings are required).
22
22
  */
23
23
  export const HOST_TOOLBAR_RUNTIME_MARKER = '--dimina-host-toolbar';
24
24
  //# sourceMappingURL=constants.js.map
@@ -174,11 +174,11 @@ export declare const ProjectsChannel: {
174
174
  readonly List: "projects:list";
175
175
  readonly Add: "projects:add";
176
176
  readonly Remove: "projects:remove";
177
- /** Phase 3: merged + sanitized template catalog for the create-project dialog. */
177
+ /** Merged + sanitized template catalog for the create-project dialog. */
178
178
  readonly ListTemplates: "projects:listTemplates";
179
- /** Phase 3: host-supplied create-project dialog hook (returns input or null). */
179
+ /** Host-supplied create-project dialog hook (returns input or null). */
180
180
  readonly OpenCreateDialog: "projects:openCreateDialog";
181
- /** Phase 3: server-side scaffold + register (delegates to create-project-service). */
181
+ /** Server-side scaffold + register (delegates to create-project-service). */
182
182
  readonly Create: "projects:create";
183
183
  /** Default values used to pre-fill the create-project dialog (baseDir). */
184
184
  readonly GetCreateDefaults: "projects:getCreateDefaults";
@@ -161,11 +161,11 @@ export const ProjectsChannel = {
161
161
  List: 'projects:list',
162
162
  Add: 'projects:add',
163
163
  Remove: 'projects:remove',
164
- /** Phase 3: merged + sanitized template catalog for the create-project dialog. */
164
+ /** Merged + sanitized template catalog for the create-project dialog. */
165
165
  ListTemplates: 'projects:listTemplates',
166
- /** Phase 3: host-supplied create-project dialog hook (returns input or null). */
166
+ /** Host-supplied create-project dialog hook (returns input or null). */
167
167
  OpenCreateDialog: 'projects:openCreateDialog',
168
- /** Phase 3: server-side scaffold + register (delegates to create-project-service). */
168
+ /** Server-side scaffold + register (delegates to create-project-service). */
169
169
  Create: 'projects:create',
170
170
  /** Default values used to pre-fill the create-project dialog (baseDir). */
171
171
  GetCreateDefaults: 'projects:getCreateDefaults',
@@ -32,6 +32,15 @@ export interface OpenInEditorRequest {
32
32
  /** 0-based column as reported by DevTools (may be undefined). */
33
33
  column?: number;
34
34
  }
35
+ export interface ProjectSourceContext {
36
+ /** Absolute project root on disk. */
37
+ projectRoot: string;
38
+ /** Resource server base URL used by the compiled miniapp. */
39
+ resourceBaseUrl: string;
40
+ appId: string;
41
+ /** Compiler output package root (`main` for the primary package). */
42
+ outputRoot: string;
43
+ }
35
44
  /**
36
45
  * Encode an open-in-editor request as a sentinel URL the DevTools front-end can
37
46
  * hand to `InspectorFrontendHost.openInNewTab(...)`. All fields ride in the
@@ -67,5 +76,15 @@ export declare function decodeOpenInEditorUrl(raw: string): OpenInEditorRequest
67
76
  * @returns the project-relative POSIX path, or null when the URL is not a
68
77
  * mappable dev-server source.
69
78
  */
70
- export declare function resourceUrlToProjectRelativePath(resourceUrl: string, expectedOrigin?: string): string | null;
79
+ export declare function resourceUrlToProjectRelativePath(resourceUrl: string, expectedOriginOrContext?: string | ProjectSourceContext): string | null;
80
+ /** Read the project/source routing metadata carried by a service-host spawn URL. */
81
+ export declare function projectSourceContextFromServiceHostUrl(serviceHostUrl: string, activeProjectRoot?: string): ProjectSourceContext | null;
82
+ /**
83
+ * Build the DevTools-front-end glue for project source links.
84
+ *
85
+ * It keeps the existing `setOpenResourceHandler` transport, but only forwards
86
+ * locations that map to the active project. A DOM observer expands Chromium's
87
+ * basename-only console labels to project-relative paths.
88
+ */
89
+ export declare function buildDevtoolsProjectSourceLinksScript(context: ProjectSourceContext): string;
71
90
  //# sourceMappingURL=open-in-editor.d.ts.map
@@ -96,9 +96,12 @@ export function decodeOpenInEditorUrl(raw) {
96
96
  * @returns the project-relative POSIX path, or null when the URL is not a
97
97
  * mappable dev-server source.
98
98
  */
99
- export function resourceUrlToProjectRelativePath(resourceUrl, expectedOrigin) {
99
+ export function resourceUrlToProjectRelativePath(resourceUrl, expectedOriginOrContext) {
100
100
  if (typeof resourceUrl !== 'string' || !resourceUrl)
101
101
  return null;
102
+ if (expectedOriginOrContext && typeof expectedOriginOrContext === 'object') {
103
+ return projectAwareResourcePath(resourceUrl, expectedOriginOrContext);
104
+ }
102
105
  let resource;
103
106
  try {
104
107
  resource = new URL(resourceUrl);
@@ -110,10 +113,10 @@ export function resourceUrlToProjectRelativePath(resourceUrl, expectedOrigin) {
110
113
  // `webpack://`, `node:`, `data:` are framework/runtime frames with no file.
111
114
  if (resource.protocol !== 'http:' && resource.protocol !== 'https:')
112
115
  return null;
113
- if (expectedOrigin) {
116
+ if (expectedOriginOrContext) {
114
117
  let base;
115
118
  try {
116
- base = new URL(expectedOrigin);
119
+ base = new URL(expectedOriginOrContext);
117
120
  }
118
121
  catch {
119
122
  return null;
@@ -135,4 +138,244 @@ export function resourceUrlToProjectRelativePath(resourceUrl, expectedOrigin) {
135
138
  return null;
136
139
  return segments.slice(1).join('/');
137
140
  }
141
+ function decodePathname(pathname) {
142
+ return pathname.split('/').map((segment) => {
143
+ try {
144
+ return decodeURIComponent(segment);
145
+ }
146
+ catch {
147
+ return segment;
148
+ }
149
+ }).join('/');
150
+ }
151
+ function normalizeSlashPath(value) {
152
+ return decodePathname(value.replace(/\\/g, '/')).replace(/\/+/g, '/');
153
+ }
154
+ function safeRelativePath(segments) {
155
+ if (segments.length === 0 || segments.some((segment) => !segment || segment === '.' || segment === '..')) {
156
+ return null;
157
+ }
158
+ return segments.join('/');
159
+ }
160
+ function projectAwareResourcePath(resourceUrl, context) {
161
+ let base;
162
+ try {
163
+ base = new URL(context.resourceBaseUrl);
164
+ }
165
+ catch {
166
+ return null;
167
+ }
168
+ const projectRoot = normalizeSlashPath(context.projectRoot).replace(/\/$/, '');
169
+ const raw = resourceUrl.trim();
170
+ if (!raw)
171
+ return null;
172
+ // Chromium may hand the open-resource hook a raw absolute filesystem path.
173
+ const normalizedRaw = normalizeSlashPath(raw);
174
+ if (projectRoot && (normalizedRaw === projectRoot || normalizedRaw.startsWith(`${projectRoot}/`))) {
175
+ const relative = normalizedRaw.slice(projectRoot.length).replace(/^\/+/, '');
176
+ return safeRelativePath(relative.split('/').filter(Boolean));
177
+ }
178
+ // Sourcemap sources commonly use virtual root paths such as `/pages/x.js`.
179
+ // Real host filesystem paths use well-known absolute roots; when they are
180
+ // outside the active project they are runtime/devtools frames, not sources.
181
+ if (/^\/(?:Volumes|Users|home|private|tmp|var|opt|Applications)\//.test(normalizedRaw)) {
182
+ return null;
183
+ }
184
+ // Reject relative runtime labels such as `service.js` or
185
+ // `electron/js2c/renderer_init`; project source locations must be an
186
+ // absolute URL/path so they can be scoped to the active project.
187
+ if (!raw.startsWith('/') && !/^[a-z][a-z0-9+.-]*:/i.test(raw))
188
+ return null;
189
+ let resource;
190
+ try {
191
+ resource = raw.startsWith('/') && !raw.startsWith('//')
192
+ ? new URL(raw, base.origin)
193
+ : new URL(raw);
194
+ }
195
+ catch {
196
+ return null;
197
+ }
198
+ if (resource.protocol === 'file:') {
199
+ const filePath = normalizeSlashPath(resource.pathname);
200
+ if (!projectRoot || (filePath !== projectRoot && !filePath.startsWith(`${projectRoot}/`)))
201
+ return null;
202
+ return safeRelativePath(filePath.slice(projectRoot.length).replace(/^\/+/, '').split('/').filter(Boolean));
203
+ }
204
+ if (resource.protocol !== 'http:' && resource.protocol !== 'https:')
205
+ return null;
206
+ if (resource.origin !== base.origin)
207
+ return null;
208
+ let segments = decodePathname(resource.pathname).split('/').filter(Boolean);
209
+ if (context.appId && segments[0] === context.appId) {
210
+ segments = segments.slice(1);
211
+ if (context.outputRoot && segments[0] === context.outputRoot)
212
+ segments = segments.slice(1);
213
+ }
214
+ return safeRelativePath(segments);
215
+ }
216
+ /** Read the project/source routing metadata carried by a service-host spawn URL. */
217
+ export function projectSourceContextFromServiceHostUrl(serviceHostUrl, activeProjectRoot) {
218
+ let url;
219
+ try {
220
+ url = new URL(serviceHostUrl);
221
+ }
222
+ catch {
223
+ return null;
224
+ }
225
+ const projectRoot = url.searchParams.get('pkgRoot') || '';
226
+ const resourceBaseUrl = url.searchParams.get('resourceBaseUrl') ?? '';
227
+ const appId = url.searchParams.get('appId') ?? '';
228
+ const outputRoot = url.searchParams.get('root') ?? 'main';
229
+ if (!projectRoot || !resourceBaseUrl || !appId)
230
+ return null;
231
+ if (activeProjectRoot) {
232
+ const authoritativeRoot = normalizeSlashPath(projectRoot).replace(/\/+$/, '');
233
+ const currentRoot = normalizeSlashPath(activeProjectRoot).replace(/\/+$/, '');
234
+ if (!authoritativeRoot || authoritativeRoot !== currentRoot)
235
+ return null;
236
+ }
237
+ try {
238
+ new URL(resourceBaseUrl);
239
+ }
240
+ catch {
241
+ return null;
242
+ }
243
+ return { projectRoot, resourceBaseUrl, appId, outputRoot };
244
+ }
245
+ /**
246
+ * Build the DevTools-front-end glue for project source links.
247
+ *
248
+ * It keeps the existing `setOpenResourceHandler` transport, but only forwards
249
+ * locations that map to the active project. A DOM observer expands Chromium's
250
+ * basename-only console labels to project-relative paths.
251
+ */
252
+ export function buildDevtoolsProjectSourceLinksScript(context) {
253
+ const config = JSON.stringify(context);
254
+ const scheme = JSON.stringify(OPEN_IN_EDITOR_SCHEME);
255
+ return `
256
+ (function() {
257
+ try {
258
+ const stateKey = '__diminaProjectSourceLinksState__'
259
+ const previousState = globalThis[stateKey]
260
+ if (previousState && typeof previousState.dispose === 'function') {
261
+ try { previousState.dispose() } catch (_) {}
262
+ }
263
+
264
+ const cfg = ${config}
265
+ const diminaProjectSourcePath = ${projectAwareResourcePath.toString()}
266
+ const decodePathname = ${decodePathname.toString()}
267
+ const normalizeSlashPath = ${normalizeSlashPath.toString()}
268
+ const safeRelativePath = ${safeRelativePath.toString()}
269
+ const observedRoots = new Set()
270
+ const observers = []
271
+ let timer = null
272
+ const state = {
273
+ dispose() {
274
+ if (timer !== null) {
275
+ clearInterval(timer)
276
+ timer = null
277
+ }
278
+ for (const observer of observers.splice(0)) {
279
+ try { observer.disconnect() } catch (_) {}
280
+ }
281
+ observedRoots.clear()
282
+ },
283
+ }
284
+ globalThis[stateKey] = state
285
+
286
+ function splitLocation(value) {
287
+ const raw = String(value || '')
288
+ const match = raw.match(/^(.*?)(?::(\\d+))(?::(\\d+))?$/)
289
+ return match
290
+ ? { url: match[1], suffix: ':' + match[2] + (match[3] ? ':' + match[3] : '') }
291
+ : { url: raw, suffix: '' }
292
+ }
293
+
294
+ function rewriteLink(link) {
295
+ if (!link || link.dataset.diminaProjectSourcePath) return
296
+ const candidates = [
297
+ link.getAttribute && link.getAttribute('title'),
298
+ link.getAttribute && link.getAttribute('href'),
299
+ link.getAttribute && link.getAttribute('data-url'),
300
+ ]
301
+ for (const candidate of candidates) {
302
+ if (!candidate) continue
303
+ const location = splitLocation(candidate)
304
+ const relative = diminaProjectSourcePath(location.url, cfg)
305
+ if (!relative) continue
306
+ const textSuffix = location.suffix || ((String(link.textContent || '').match(/(:\\d+(?::\\d+)?)$/) || [])[1] || '')
307
+ link.textContent = relative + textSuffix
308
+ link.dataset.diminaProjectSourcePath = relative
309
+ return
310
+ }
311
+ }
312
+
313
+ function observeRoot(root) {
314
+ if (!root || !root.querySelectorAll || observedRoots.has(root)) return
315
+ observedRoots.add(root)
316
+
317
+ const observer = new MutationObserver((records) => {
318
+ for (const record of records) {
319
+ if (record.type === 'attributes') rewriteLink(record.target)
320
+ for (const node of record.addedNodes || []) visitNode(node)
321
+ }
322
+ })
323
+ observer.observe(root, {
324
+ subtree: true,
325
+ childList: true,
326
+ attributes: true,
327
+ attributeFilter: ['title', 'href', 'data-url'],
328
+ })
329
+ observers.push(observer)
330
+
331
+ for (const link of root.querySelectorAll('.devtools-link')) rewriteLink(link)
332
+ for (const element of root.querySelectorAll('*')) {
333
+ if (element.shadowRoot) observeRoot(element.shadowRoot)
334
+ }
335
+ }
336
+
337
+ function visitNode(node) {
338
+ if (!node) return
339
+ if (node.matches && node.matches('.devtools-link')) rewriteLink(node)
340
+ if (node.shadowRoot) observeRoot(node.shadowRoot)
341
+ if (!node.querySelectorAll) return
342
+ for (const link of node.querySelectorAll('.devtools-link')) rewriteLink(link)
343
+ for (const element of node.querySelectorAll('*')) {
344
+ if (element.shadowRoot) observeRoot(element.shadowRoot)
345
+ }
346
+ }
347
+
348
+ observeRoot(document)
349
+
350
+ let tries = 0
351
+ timer = setInterval(() => {
352
+ tries++
353
+ try {
354
+ const Host = globalThis.Host
355
+ const host = Host && Host.InspectorFrontendHost
356
+ if (host && typeof host.setOpenResourceHandler === 'function'
357
+ && typeof host.openInNewTab === 'function') {
358
+ host.setOpenResourceHandler((url, lineNumber, columnNumber) => {
359
+ try {
360
+ if (!diminaProjectSourcePath(String(url), cfg)) return
361
+ const p = new URLSearchParams()
362
+ p.set('u', String(url))
363
+ if (typeof lineNumber === 'number') p.set('l', String(lineNumber))
364
+ if (typeof columnNumber === 'number') p.set('c', String(columnNumber))
365
+ host.openInNewTab(${scheme} + ':?' + p.toString())
366
+ } catch (_) {}
367
+ })
368
+ clearInterval(timer)
369
+ timer = null
370
+ }
371
+ } catch (_) {}
372
+ if (tries > 80 && timer !== null) {
373
+ clearInterval(timer)
374
+ timer = null
375
+ }
376
+ }, 50)
377
+ } catch (_) {}
378
+ })()
379
+ `;
380
+ }
138
381
  //# sourceMappingURL=open-in-editor.js.map
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Phase 0 vpath resolver.
2
+ * vpath resolver.
3
3
  *
4
4
  * Spec: `docs/file-system.md` §3.3 — single resolver shared by every FSM
5
- * entry, the renderer-side temp store, and (Phase 1) the main-process
5
+ * entry, the renderer-side temp store, and the main-process
6
6
  * protocol.handle / disk reader.
7
7
  *
8
8
  * resolveVPath(url): { kind, writable, realPath? } | null
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Phase 0 vpath resolver.
2
+ * vpath resolver.
3
3
  *
4
4
  * Spec: `docs/file-system.md` §3.3 — single resolver shared by every FSM
5
- * entry, the renderer-side temp store, and (Phase 1) the main-process
5
+ * entry, the renderer-side temp store, and the main-process
6
6
  * protocol.handle / disk reader.
7
7
  *
8
8
  * resolveVPath(url): { kind, writable, realPath? } | null