@dimina-kit/devtools 0.4.0-dev.20260625063832 → 0.4.0-dev.20260630033804

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 (64) hide show
  1. package/dist/main/app/app.js +2 -0
  2. package/dist/main/index.bundle.js +241 -151
  3. package/dist/main/ipc/bridge-router.d.ts +21 -0
  4. package/dist/main/ipc/bridge-router.js +63 -3
  5. package/dist/main/services/automation/index.js +0 -1
  6. package/dist/main/services/elements-forward/index.js +81 -72
  7. package/dist/main/services/mcp/target-manager.d.ts +3 -3
  8. package/dist/main/services/views/miniapp-partition.d.ts +18 -8
  9. package/dist/main/services/views/miniapp-partition.js +34 -13
  10. package/dist/main/services/views/view-manager.js +76 -41
  11. package/dist/main/services/workbench-coi-server.d.ts +7 -0
  12. package/dist/main/services/workbench-coi-server.js +6 -0
  13. package/dist/main/services/workbench-context.d.ts +4 -2
  14. package/dist/main/services/workbench-context.js +1 -0
  15. package/dist/main/services/workspace/op-lock.d.ts +26 -0
  16. package/dist/main/services/workspace/op-lock.js +23 -0
  17. package/dist/main/services/workspace/workspace-service.d.ts +7 -0
  18. package/dist/main/services/workspace/workspace-service.js +197 -103
  19. package/dist/main/windows/service-host-window/create.d.ts +5 -1
  20. package/dist/main/windows/service-host-window/create.js +6 -3
  21. package/dist/native-host/render/render.js +485 -449
  22. package/dist/native-host/service/service.js +1 -1
  23. package/dist/renderer/assets/index-BMwmmaSp.js +46 -0
  24. package/dist/renderer/entries/main/index.html +1 -1
  25. package/dist/shared/types.d.ts +15 -0
  26. package/dist/vscode-workbench/assets/__vite-browser-external-B0DTNerG.js +1 -0
  27. package/dist/vscode-workbench/assets/__vite-browser-external-BuLjVMfN.js +1 -0
  28. package/dist/vscode-workbench/assets/__vite-browser-external-thkx7I19.js +1 -0
  29. package/dist/vscode-workbench/assets/{dist-SFZ2HNEz.js → dist-CS7SQP3K.js} +4 -4
  30. package/dist/vscode-workbench/assets/dist-VSgdjUlW.js +8 -0
  31. package/dist/vscode-workbench/assets/editor.worker-DmMg51ee.js +41 -0
  32. package/dist/vscode-workbench/assets/extensionHost.worker-BMpl1cpB.js +207 -0
  33. package/dist/vscode-workbench/assets/{iconv-lite-umd-izd5l6fa.js → iconv-lite-umd-D3q-1-o6.js} +1 -1
  34. package/dist/vscode-workbench/assets/{iconv-lite-umd-COfjYshr.js → iconv-lite-umd-Jn9RTrsv.js} +2 -2
  35. package/dist/vscode-workbench/assets/index-zjigpZ25.js +2842 -0
  36. package/dist/vscode-workbench/assets/{jschardet-Bf-VMK4F.js → jschardet-CjCVc2Gw.js} +1 -1
  37. package/dist/vscode-workbench/assets/{jschardet-Dh5TyKsQ.js → jschardet-Cu4ufeHq.js} +1 -1
  38. package/dist/vscode-workbench/assets/{layout.contribution.darwin-cz2ttHTW.js → layout.contribution.darwin-CoH1BZtz.js} +1 -1
  39. package/dist/vscode-workbench/assets/{layout.contribution.linux-fEtdzbcY.js → layout.contribution.linux-Di2FF-CG.js} +1 -1
  40. package/dist/vscode-workbench/assets/{layout.contribution.win-BFyOKI8c.js → layout.contribution.win-moiNStil.js} +1 -1
  41. package/dist/vscode-workbench/assets/{main-Bh8TI5LP.js → main-DzHL45xo.js} +4 -4
  42. package/dist/vscode-workbench/assets/{main2-Bds9Fhfn.js → main2-CWVd-Hyp.js} +1 -1
  43. package/dist/vscode-workbench/assets/{worker-D9ti6A0r.js → worker-CRTb-Arc.js} +7 -7
  44. package/dist/vscode-workbench/assets/{worker-DQtXDXIZ.js → worker-CVB8WPuM.js} +2 -2
  45. package/dist/vscode-workbench/index.html +1 -4
  46. package/package.json +8 -38
  47. package/dist/renderer/assets/index-6XUSo6Su.js +0 -46
  48. package/dist/vscode-workbench/assets/__vite-browser-external-BUwiolNy.js +0 -1
  49. package/dist/vscode-workbench/assets/__vite-browser-external-CmG9GjjO.js +0 -1
  50. package/dist/vscode-workbench/assets/__vite-browser-external-DGJPTKLg.js +0 -1
  51. package/dist/vscode-workbench/assets/_commonjsHelpers-qcUHfnrg.js +0 -1
  52. package/dist/vscode-workbench/assets/chunk-B3K2TuZy.js +0 -1
  53. package/dist/vscode-workbench/assets/chunk-DECur_0Z.js +0 -1
  54. package/dist/vscode-workbench/assets/dist-tesJ1Kd2.js +0 -8
  55. package/dist/vscode-workbench/assets/editor.worker-BC1qOyXG.js +0 -41
  56. package/dist/vscode-workbench/assets/extension.api-BWA2aPAU.js +0 -1
  57. package/dist/vscode-workbench/assets/extensionHost.worker-Dvu35anN.js +0 -207
  58. package/dist/vscode-workbench/assets/index-CQ5xP0pp.js +0 -1656
  59. package/dist/vscode-workbench/assets/localExtensionHost-Ccz7QUdA.js +0 -1188
  60. package/dist/vscode-workbench/assets/main-BDgdQmvs.js +0 -4
  61. package/dist/vscode-workbench/assets/main2-DwfDVleQ.js +0 -1
  62. package/dist/vscode-workbench/assets/preload-helper-xBbMyY7u.js +0 -1
  63. /package/dist/vscode-workbench/assets/{_.contribution-C9aS_G-o.js → _.contribution-B46wcGKQ.js} +0 -0
  64. /package/dist/vscode-workbench/assets/{_commonjsHelpers-BPKu-ps5.js → _commonjsHelpers-DMmVv2OA.js} +0 -0
@@ -653,12 +653,24 @@ export function createViewManager(ctx) {
653
653
  // interceptor re-emits an encoded sentinel via
654
654
  // `InspectorFrontendHost.openInNewTab` → Electron `devtools-open-url`. (The
655
655
  // legacy `setOpenResourceHandler` hook this used to rely on is gone in
656
- // current Chromium, so the script keeps it only as a fallback.) Best-effort:
657
- // the script is fully try/catch-wrapped so a missing API never throws.
658
- const sourceContext = projectSourceContextFromServiceHostUrl(serviceWc.getURL(), ctx.workspace?.getProjectPath?.());
659
- if (!sourceContext)
656
+ // current Chromium, so the script keeps it only as a fallback.)
657
+ //
658
+ // Destroy-race chokepoint: this runs from a deferred `dom-ready` callback
659
+ // (see wireOpenInEditor), by which time the inspected service-host wc may
660
+ // already be torn down (pool swap / project close / relaunch). Reading
661
+ // `serviceWc.getURL()` on a destroyed wc throws "Object has been destroyed"
662
+ // synchronously, which would escape the event listener. Guard both wc and
663
+ // wrap the body so a teardown mid-call degrades silently (mirrors
664
+ // customizeDevtoolsTabs).
665
+ if (serviceWc.isDestroyed() || devtoolsWc.isDestroyed())
660
666
  return;
661
- devtoolsWc.executeJavaScript(buildDevtoolsProjectSourceLinksScript(sourceContext)).catch(() => { });
667
+ try {
668
+ const sourceContext = projectSourceContextFromServiceHostUrl(serviceWc.getURL(), ctx.workspace?.getProjectPath?.());
669
+ if (!sourceContext)
670
+ return;
671
+ devtoolsWc.executeJavaScript(buildDevtoolsProjectSourceLinksScript(sourceContext)).catch(() => { });
672
+ }
673
+ catch { /* wc torn down mid-call — degrade silently */ }
662
674
  }
663
675
  // ── DevTools tab customization: keep only Elements/Console/Network ──────────
664
676
  // Inject into the same DevTools front-end host wc that the console/network
@@ -877,13 +889,22 @@ export function createViewManager(ctx) {
877
889
  stopElementsForward?.();
878
890
  }
879
891
  catch { /* prior disposer already gone */ }
880
- stopElementsForward = installElementsForward({ devtoolsWc, bridge: ctx.bridge, connections: ctx.connections });
892
+ // Capture THIS instance's disposer in the destroy handler — NOT the mutable
893
+ // `stopElementsForward`. A respawn creates a new simulatorView whose old
894
+ // devtools host wc is destroyed AFTER the new instance is already installed;
895
+ // a handler closing over `stopElementsForward` would then dispose the CURRENT
896
+ // instance (its reconcile loop never gets to install the hook, so Elements
897
+ // falls back to the natively-inspected service host). Stop only this wc's own
898
+ // forward, and clear the module pointer only while it still points here.
899
+ const thisForward = installElementsForward({ devtoolsWc, bridge: ctx.bridge, connections: ctx.connections });
900
+ stopElementsForward = thisForward;
881
901
  devtoolsWc.once('destroyed', () => {
882
902
  try {
883
- stopElementsForward?.();
903
+ thisForward();
884
904
  }
885
905
  catch { /* already stopped */ }
886
- stopElementsForward = null;
906
+ if (stopElementsForward === thisForward)
907
+ stopElementsForward = null;
887
908
  });
888
909
  }
889
910
  devtoolsWc.once('dom-ready', () => {
@@ -934,6 +955,40 @@ export function createViewManager(ctx) {
934
955
  nativeCustomApiBridgeHandler = null;
935
956
  }
936
957
  }
958
+ /**
959
+ * The single teardown path for the live native-host simulator WCV, shared by
960
+ * relaunch (attachNativeSimulator replacing the view) and project close
961
+ * (detachSimulator). Detaches the view from the window, then synchronously
962
+ * clears its bridge sessions (render guests + service host + mappings) BEFORE
963
+ * the WCV's own async close(), so the next project never re-resolves,
964
+ * re-renders, or screenshots the outgoing guest. The sync prefix clears the
965
+ * maps immediately; the async tail (pool / resource-server release) is observed
966
+ * so a rejection is logged rather than swallowed. The simulatorWc 'destroyed'
967
+ * hook in bridge-router stays as an idempotent fallback. No-op when no view is
968
+ * live; idempotent (disposeSessionsForSimulator early-returns once a session is
969
+ * gone), so the eager teardown here and the 'destroyed' fallback never
970
+ * double-dispose. `label` only tags the diagnostic on the async-tail failure.
971
+ */
972
+ function tearDownNativeSimulatorView(label) {
973
+ if (!nativeSimulatorView)
974
+ return;
975
+ if (nativeSimulatorViewAdded && !ctx.windows.mainWindow.isDestroyed()) {
976
+ try {
977
+ ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
978
+ }
979
+ catch { /* already removed */ }
980
+ }
981
+ try {
982
+ if (!nativeSimulatorView.webContents.isDestroyed()) {
983
+ ctx.bridge?.disposeSessionsForSimulator?.(nativeSimulatorView.webContents.id)
984
+ ?.catch((err) => console.warn(`[view-manager] dispose sessions (${label}) failed:`, err));
985
+ nativeSimulatorView.webContents.close();
986
+ }
987
+ }
988
+ catch { /* ignore */ }
989
+ nativeSimulatorView = null;
990
+ nativeSimulatorViewAdded = false;
991
+ }
937
992
  /**
938
993
  * NATIVE-HOST ONLY. The native simulator is a top-level WebContentsView with
939
994
  * NO embedder renderer, so the simulator-side `__diminaCustomApis` bridge
@@ -996,23 +1051,12 @@ export function createViewManager(ctx) {
996
1051
  // that generation cancelled, so this cannot schedule a stale capture.
997
1052
  settleNativeSimulatorReady?.();
998
1053
  settleNativeSimulatorReady = null;
999
- // Tear down any previous native simulator view (relaunch / re-open).
1054
+ // Tear down any previous native simulator view (relaunch / re-open) through
1055
+ // the shared single teardown path. detachNativeCustomApiBridge stays here
1056
+ // (the bridge dispatcher is re-installed per attach below).
1000
1057
  if (nativeSimulatorView) {
1001
1058
  detachNativeCustomApiBridge();
1002
- if (nativeSimulatorViewAdded) {
1003
- try {
1004
- ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
1005
- }
1006
- catch { /* already removed */ }
1007
- nativeSimulatorViewAdded = false;
1008
- }
1009
- try {
1010
- if (!nativeSimulatorView.webContents.isDestroyed()) {
1011
- nativeSimulatorView.webContents.close();
1012
- }
1013
- }
1014
- catch { /* ignore */ }
1015
- nativeSimulatorView = null;
1059
+ tearDownNativeSimulatorView('relaunch');
1016
1060
  }
1017
1061
  nativeSimulatorProjectPath = null;
1018
1062
  const ready = new Promise((resolve) => {
@@ -1025,7 +1069,11 @@ export function createViewManager(ctx) {
1025
1069
  // (protocol handlers + CORS/referer policy) before any project content loads
1026
1070
  // on it — idempotent per partition.
1027
1071
  const route = parseRoute(simulatorUrl);
1028
- const partition = miniappPartition(route?.appId);
1072
+ // Include the project path so two projects that declare the same appId at
1073
+ // different paths get isolated partitions. The service-host window for THIS
1074
+ // project derives its partition from the same (appId, projectPath) pair so
1075
+ // render guests + service host still share one session.
1076
+ const partition = miniappPartition(route?.appId, ctx.workspace?.getProjectPath());
1029
1077
  configureMiniappSession(partition);
1030
1078
  // The simulator preload is a CJS bundle; webPreferences.preload obeys the
1031
1079
  // `.js` + "type":"module" ESM rule (require would be undefined), so hand the
@@ -1221,23 +1269,10 @@ export function createViewManager(ctx) {
1221
1269
  // back to the console.
1222
1270
  ctx.networkForward?.detachSimulator();
1223
1271
  ctx.networkForward?.setDevtoolsHost(null);
1224
- // Native-host simulator content view (no-op in the default path).
1225
- if (nativeSimulatorView) {
1226
- if (nativeSimulatorViewAdded && !ctx.windows.mainWindow.isDestroyed()) {
1227
- try {
1228
- ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
1229
- }
1230
- catch { /* already removed */ }
1231
- }
1232
- try {
1233
- if (!nativeSimulatorView.webContents.isDestroyed()) {
1234
- nativeSimulatorView.webContents.close();
1235
- }
1236
- }
1237
- catch { /* ignore */ }
1238
- nativeSimulatorView = null;
1239
- nativeSimulatorViewAdded = false;
1240
- }
1272
+ // Native-host simulator content view (no-op in the default path) — same
1273
+ // single teardown path as relaunch. detachNativeCustomApiBridge was already
1274
+ // called above.
1275
+ tearDownNativeSimulatorView('close');
1241
1276
  hidePopover();
1242
1277
  // Drop the settings view too — the previous detachAllViews() did.
1243
1278
  destroyViewInternal(settingsView);
@@ -14,6 +14,13 @@ export interface WorkbenchCoiServerOptions {
14
14
  * manifest the workbench reads at boot. Omit for none.
15
15
  */
16
16
  extensionsDir?: string;
17
+ /**
18
+ * Reads the host's custom file types (e.g. `.qdml`/`.qdss`/`.qds`), served at
19
+ * `/__filetypes` so the workbench maps them to `files.associations`. Read
20
+ * per-request so a project switch takes effect on the next editor boot. Omit
21
+ * for none (built-in `wx*` associations only).
22
+ */
23
+ getFileTypes?: () => unknown;
17
24
  }
18
25
  export declare function startWorkbenchCoiServer(options: WorkbenchCoiServerOptions): Promise<WorkbenchCoiServer>;
19
26
  //# sourceMappingURL=workbench-coi-server.d.ts.map
@@ -317,6 +317,12 @@ export async function startWorkbenchCoiServer(options) {
317
317
  });
318
318
  return;
319
319
  }
320
+ // Host custom file types for the editor's `files.associations`. Read-only;
321
+ // empty object when the host configured none.
322
+ if (url.pathname === '/__filetypes') {
323
+ jsonRes(res, 200, options.getFileTypes?.() ?? {});
324
+ return;
325
+ }
320
326
  // Host-contributed web extensions: manifest + static files (same-origin so
321
327
  // they satisfy the workbench's COEP require-corp).
322
328
  if (url.pathname.startsWith('/__contrib/')) {
@@ -1,5 +1,5 @@
1
1
  import type { BrowserWindow } from 'electron';
2
- import type { CompilationAdapter, WorkbenchConfig } from '../../shared/types.js';
2
+ import type { CompilationAdapter, CustomFileTypes, WorkbenchConfig } from '../../shared/types.js';
3
3
  import type { BridgeRouterHandle } from '../ipc/bridge-router.js';
4
4
  import type { ConsoleForwarder } from './console-forward/index.js';
5
5
  import type { NetworkForwarder } from './network-forward/index.js';
@@ -27,6 +27,8 @@ export interface WorkbenchContext {
27
27
  rendererDir: string;
28
28
  /** Custom API namespace names (e.g. ['qd']) passed to the simulator */
29
29
  apiNamespaces: string[];
30
+ /** Custom file types (e.g. .qdml/.qdss/.qds) forwarded to compilation and served to the editor via the COI `/__filetypes` endpoint. */
31
+ fileTypes: CustomFileTypes;
30
32
  /** Branding name shown in title bar and getBranding IPC */
31
33
  appName: string;
32
34
  /** Host-injected provider for branding info (overrides default appName) */
@@ -178,7 +180,7 @@ export interface WorkbenchContext {
178
180
  * - `projectsProvider` / `projectTemplates` / `customCreateProjectDialog` use
179
181
  * the main-process types, not the structural mirrors in `shared/types`.
180
182
  */
181
- export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' | 'apiNamespaces' | 'appName'> {
183
+ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' | 'apiNamespaces' | 'appName' | 'fileTypes'> {
182
184
  mainWindow: BrowserWindow;
183
185
  preloadPath: string;
184
186
  rendererDir: string;
@@ -16,6 +16,7 @@ export function createWorkbenchContext(opts) {
16
16
  preloadPath: opts.preloadPath,
17
17
  rendererDir: opts.rendererDir,
18
18
  apiNamespaces: opts.apiNamespaces ?? [],
19
+ fileTypes: opts.fileTypes ?? {},
19
20
  appName: opts.appName ?? 'Dimina DevTools',
20
21
  brandingProvider: opts.brandingProvider,
21
22
  };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Lifecycle serialization primitive for the workspace's openProject/closeProject.
3
+ *
4
+ * Two parts working together (see workspace-service for the full rationale):
5
+ * - A FIFO async lock so the teardown/commit critical sections of open and
6
+ * close never interleave. The unbounded compile runs between an open's two
7
+ * sections WITHOUT holding the lock, so a queued close is never blocked by a
8
+ * slow/hung compile (close is bounded by disposeSession's own timeout).
9
+ * - A latest-wins request token. `nextSeq()` is claimed at the START of every
10
+ * op (before any await) so it encodes REQUEST order, not hook/compile
11
+ * completion order. `takeOwnership` promotes a seq (highest wins); a section
12
+ * that finds `!isOwner(mySeq)` was superseded while it released the lock and
13
+ * must abort instead of clobbering the newer request's runtime.
14
+ */
15
+ export interface OpLock {
16
+ /** Claim a monotonic request seq. Call once at op entry, before any await. */
17
+ nextSeq(): number;
18
+ /** Promote a seq to the runtime owner. Only the highest seq ever wins. */
19
+ takeOwnership(seq: number): void;
20
+ /** Whether `seq` is still the current owner (false ⇒ superseded). */
21
+ isOwner(seq: number): boolean;
22
+ /** Acquire the FIFO lock; await the result, then call it to release. */
23
+ acquire(): Promise<() => void>;
24
+ }
25
+ export declare function createOpLock(): OpLock;
26
+ //# sourceMappingURL=op-lock.d.ts.map
@@ -0,0 +1,23 @@
1
+ export function createOpLock() {
2
+ let chain = Promise.resolve();
3
+ let requestSeq = 0;
4
+ let ownerSeq = 0;
5
+ return {
6
+ nextSeq: () => ++requestSeq,
7
+ takeOwnership: (seq) => {
8
+ if (seq > ownerSeq)
9
+ ownerSeq = seq;
10
+ },
11
+ isOwner: (seq) => seq === ownerSeq,
12
+ acquire: () => {
13
+ let release;
14
+ const next = new Promise((resolve) => {
15
+ release = resolve;
16
+ });
17
+ const prior = chain;
18
+ chain = prior.then(() => next);
19
+ return prior.then(() => release);
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=op-lock.js.map
@@ -43,6 +43,13 @@ export interface WorkspaceService {
43
43
  */
44
44
  getLastClosedProjectPath(): string;
45
45
  hasActiveSession(): boolean;
46
+ /**
47
+ * True only while `closeProject` is tearing down. Bridge active-content
48
+ * resolution consults this to avoid resolving the dying session's guest during
49
+ * the close's session.close() await (when currentSession is already null but
50
+ * the bridge app session still exists).
51
+ */
52
+ isClosing(): boolean;
46
53
  captureThumbnail(projectPath: string): Promise<string | null>;
47
54
  getThumbnail(projectPath: string): Promise<string | null>;
48
55
  getProjectPages(projectPath: string): ProjectPages;