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

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 +230 -144
  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 +59 -36
  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 +7 -37
  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
@@ -151,6 +151,7 @@ function createContext(config, mainWindow, rendererDir) {
151
151
  rendererDir,
152
152
  appName: config.appName,
153
153
  apiNamespaces: config.apiNamespaces,
154
+ fileTypes: config.fileTypes,
154
155
  brandingProvider: config.brandingProvider,
155
156
  // The host-supplied ProjectsProvider / template types in `shared/types`
156
157
  // are structurally compatible with the main-process equivalents —
@@ -558,6 +559,7 @@ export async function createDevtoolsRuntime(config = {}) {
558
559
  rootDir: bundleDir,
559
560
  getProjectRoot: () => context.workspace.getProjectPath(),
560
561
  extensionsDir: config.editorViewConfig?.extensionsDir,
562
+ getFileTypes: () => context.fileTypes,
561
563
  });
562
564
  // Return the close promise so the registry awaits server shutdown on dispose
563
565
  // instead of fire-and-forgetting it (a dangling http server would keep the
@@ -1653,6 +1653,9 @@ function buildElementsHookScript() {
1653
1653
  return 'partial';
1654
1654
  }catch(e){ return 'error:' + (e && e.message); }})()`;
1655
1655
  }
1656
+ function buildReconcileScript() {
1657
+ return `(function(){var status; try { status = ${buildElementsHookScript()}; } catch(e){ status = 'error:'+(e&&e.message); }var batch = []; try { if (globalThis.__diminaElementsOutbound) batch = globalThis.__diminaElementsOutbound.splice(0); } catch(_){}return { status: status, batch: batch };})()`;
1658
+ }
1656
1659
  var PROBE_DEVTOOLS_API = `(window.DevToolsAPI && typeof window.DevToolsAPI.dispatchMessage === 'function')`;
1657
1660
  var MAX_SINGLE_DISPATCH_CHARS = 1e6;
1658
1661
  var CHUNK_CHARS = 256 * 1024;
@@ -1668,13 +1671,11 @@ function buildDocumentUpdatedScript() {
1668
1671
  return buildDispatchScript(msg);
1669
1672
  }
1670
1673
  var DRAIN_INTERVAL_MS = 150;
1671
- var INSTALL_POLL_TRIES = 200;
1672
- var INSTALL_POLL_INTERVAL_MS = 50;
1673
1674
  function installElementsForward(deps) {
1674
1675
  const { devtoolsWc, bridge } = deps;
1675
1676
  let disposed = false;
1676
- let installTimer = null;
1677
- let drainTimer = null;
1677
+ let reconcileTimer = null;
1678
+ let lateCleanup = null;
1678
1679
  const isActiveWcId = (id) => {
1679
1680
  const a = activeRenderWc();
1680
1681
  return a != null && a.id === id;
@@ -1683,13 +1684,9 @@ function installElementsForward(deps) {
1683
1684
  const selfAttached = /* @__PURE__ */ new Set();
1684
1685
  const stop = () => {
1685
1686
  disposed = true;
1686
- if (installTimer) {
1687
- clearInterval(installTimer);
1688
- installTimer = null;
1689
- }
1690
- if (drainTimer) {
1691
- clearInterval(drainTimer);
1692
- drainTimer = null;
1687
+ if (reconcileTimer) {
1688
+ clearInterval(reconcileTimer);
1689
+ reconcileTimer = null;
1693
1690
  }
1694
1691
  for (const cleanup of wiredGuests.values()) {
1695
1692
  try {
@@ -1699,6 +1696,14 @@ function installElementsForward(deps) {
1699
1696
  }
1700
1697
  wiredGuests.clear();
1701
1698
  detachSelfAttached();
1699
+ if (lateCleanup) {
1700
+ const c = lateCleanup;
1701
+ lateCleanup = null;
1702
+ try {
1703
+ c();
1704
+ } catch {
1705
+ }
1706
+ }
1702
1707
  };
1703
1708
  function detachSelfAttached() {
1704
1709
  for (const wcId of selfAttached) {
@@ -1908,46 +1913,26 @@ function installElementsForward(deps) {
1908
1913
  const unsubscribeRenderEvents = bridge.onRenderEvent(onRenderEvent);
1909
1914
  const onReady = () => {
1910
1915
  if (disposed) return;
1911
- if (installTimer) {
1912
- clearInterval(installTimer);
1913
- installTimer = null;
1916
+ if (reconcileTimer) {
1917
+ clearInterval(reconcileTimer);
1918
+ reconcileTimer = null;
1914
1919
  }
1915
- if (drainTimer) {
1916
- clearInterval(drainTimer);
1917
- drainTimer = null;
1918
- }
1919
- let tries = 0;
1920
- installTimer = setInterval(() => {
1921
- tries++;
1920
+ reconcileTimer = setInterval(() => {
1922
1921
  if (disposed || devtoolsWc.isDestroyed()) {
1923
1922
  stop();
1924
1923
  return;
1925
1924
  }
1926
- devtoolsWc.executeJavaScript(buildElementsHookScript()).then((status2) => {
1925
+ devtoolsWc.executeJavaScript(buildReconcileScript()).then((res) => {
1927
1926
  if (disposed) return;
1928
- if (status2 === "installed" || status2 === "already") {
1929
- if (installTimer) {
1930
- clearInterval(installTimer);
1931
- installTimer = null;
1932
- }
1927
+ const r = res ?? {};
1928
+ if (r.status === "installed") {
1933
1929
  const wc = activeRenderWc();
1934
1930
  if (wc) primeGuest(wc);
1935
1931
  pushDocumentUpdated();
1936
1932
  }
1933
+ handleOutbound(r.batch);
1937
1934
  }).catch(() => {
1938
1935
  });
1939
- if (tries > INSTALL_POLL_TRIES && installTimer) {
1940
- clearInterval(installTimer);
1941
- installTimer = null;
1942
- }
1943
- }, INSTALL_POLL_INTERVAL_MS);
1944
- drainTimer = setInterval(() => {
1945
- if (disposed || devtoolsWc.isDestroyed()) {
1946
- stop();
1947
- return;
1948
- }
1949
- devtoolsWc.executeJavaScript("(globalThis.__diminaElementsOutbound ? __diminaElementsOutbound.splice(0) : [])").then(handleOutbound).catch(() => {
1950
- });
1951
1936
  }, DRAIN_INTERVAL_MS);
1952
1937
  };
1953
1938
  devtoolsWc.on("dom-ready", onReady);
@@ -1958,7 +1943,7 @@ function installElementsForward(deps) {
1958
1943
  else devtoolsWc.once("destroyed", stop);
1959
1944
  } catch {
1960
1945
  }
1961
- return () => {
1946
+ lateCleanup = () => {
1962
1947
  try {
1963
1948
  unsubscribeRenderEvents();
1964
1949
  } catch {
@@ -1971,8 +1956,8 @@ function installElementsForward(deps) {
1971
1956
  devtoolsWc.removeListener("dom-ready", onReady);
1972
1957
  } catch {
1973
1958
  }
1974
- stop();
1975
1959
  };
1960
+ return stop;
1976
1961
  }
1977
1962
 
1978
1963
  // src/main/services/service-console/console-api.ts
@@ -2190,15 +2175,15 @@ function createSimulatorApiRegistry() {
2190
2175
  import * as electron from "electron";
2191
2176
  var SHARED_MINIAPP_PARTITION = "persist:simulator";
2192
2177
  var PARTITION_PREFIX = "persist:miniapp-";
2193
- function miniappPartitionKey(appId) {
2178
+ function miniappPartitionKey(appId, projectPath) {
2194
2179
  const safe = appId.replace(/[^A-Za-z0-9_-]/g, "");
2195
- if (safe === appId && safe.length > 0) return safe;
2196
- const hash = djb2(appId).toString(36);
2197
- return safe.length > 0 ? `${safe}-${hash}` : hash;
2180
+ const base = safe === appId && safe.length > 0 ? safe : safe.length > 0 ? `${safe}-${djb2(appId).toString(36)}` : djb2(appId).toString(36);
2181
+ if (!projectPath) return base;
2182
+ return `${base}-p${djb2(projectPath).toString(36)}`;
2198
2183
  }
2199
- function miniappPartition(appId) {
2184
+ function miniappPartition(appId, projectPath) {
2200
2185
  if (!appId) return SHARED_MINIAPP_PARTITION;
2201
- return `${PARTITION_PREFIX}${miniappPartitionKey(appId)}`;
2186
+ return `${PARTITION_PREFIX}${miniappPartitionKey(appId, projectPath)}`;
2202
2187
  }
2203
2188
  function djb2(input) {
2204
2189
  let h = 5381;
@@ -2938,13 +2923,14 @@ function createViewManager(ctx) {
2938
2923
  stopElementsForward?.();
2939
2924
  } catch {
2940
2925
  }
2941
- stopElementsForward = installElementsForward({ devtoolsWc, bridge: ctx.bridge, connections: ctx.connections });
2926
+ const thisForward = installElementsForward({ devtoolsWc, bridge: ctx.bridge, connections: ctx.connections });
2927
+ stopElementsForward = thisForward;
2942
2928
  devtoolsWc.once("destroyed", () => {
2943
2929
  try {
2944
- stopElementsForward?.();
2930
+ thisForward();
2945
2931
  } catch {
2946
2932
  }
2947
- stopElementsForward = null;
2933
+ if (stopElementsForward === thisForward) stopElementsForward = null;
2948
2934
  });
2949
2935
  }
2950
2936
  devtoolsWc.once("dom-ready", () => {
@@ -2987,6 +2973,24 @@ function createViewManager(ctx) {
2987
2973
  nativeCustomApiBridgeHandler = null;
2988
2974
  }
2989
2975
  }
2976
+ function tearDownNativeSimulatorView(label) {
2977
+ if (!nativeSimulatorView) return;
2978
+ if (nativeSimulatorViewAdded && !ctx.windows.mainWindow.isDestroyed()) {
2979
+ try {
2980
+ ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
2981
+ } catch {
2982
+ }
2983
+ }
2984
+ try {
2985
+ if (!nativeSimulatorView.webContents.isDestroyed()) {
2986
+ ctx.bridge?.disposeSessionsForSimulator?.(nativeSimulatorView.webContents.id)?.catch((err) => console.warn(`[view-manager] dispose sessions (${label}) failed:`, err));
2987
+ nativeSimulatorView.webContents.close();
2988
+ }
2989
+ } catch {
2990
+ }
2991
+ nativeSimulatorView = null;
2992
+ nativeSimulatorViewAdded = false;
2993
+ }
2990
2994
  function attachNativeCustomApiBridge(simWc) {
2991
2995
  detachNativeCustomApiBridge();
2992
2996
  const apis = ctx.simulatorApis;
@@ -3015,27 +3019,14 @@ function createViewManager(ctx) {
3015
3019
  settleNativeSimulatorReady = null;
3016
3020
  if (nativeSimulatorView) {
3017
3021
  detachNativeCustomApiBridge();
3018
- if (nativeSimulatorViewAdded) {
3019
- try {
3020
- ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
3021
- } catch {
3022
- }
3023
- nativeSimulatorViewAdded = false;
3024
- }
3025
- try {
3026
- if (!nativeSimulatorView.webContents.isDestroyed()) {
3027
- nativeSimulatorView.webContents.close();
3028
- }
3029
- } catch {
3030
- }
3031
- nativeSimulatorView = null;
3022
+ tearDownNativeSimulatorView("relaunch");
3032
3023
  }
3033
3024
  nativeSimulatorProjectPath = null;
3034
3025
  const ready = new Promise((resolve) => {
3035
3026
  settleNativeSimulatorReady = resolve;
3036
3027
  });
3037
3028
  const route = parseRoute(simulatorUrl);
3038
- const partition = miniappPartition(route?.appId);
3029
+ const partition = miniappPartition(route?.appId, ctx.workspace?.getProjectPath());
3039
3030
  configureMiniappSession(partition);
3040
3031
  const view = new WebContentsView2({
3041
3032
  webPreferences: {
@@ -3145,22 +3136,7 @@ function createViewManager(ctx) {
3145
3136
  stopElementsForward = null;
3146
3137
  ctx.networkForward?.detachSimulator();
3147
3138
  ctx.networkForward?.setDevtoolsHost(null);
3148
- if (nativeSimulatorView) {
3149
- if (nativeSimulatorViewAdded && !ctx.windows.mainWindow.isDestroyed()) {
3150
- try {
3151
- ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
3152
- } catch {
3153
- }
3154
- }
3155
- try {
3156
- if (!nativeSimulatorView.webContents.isDestroyed()) {
3157
- nativeSimulatorView.webContents.close();
3158
- }
3159
- } catch {
3160
- }
3161
- nativeSimulatorView = null;
3162
- nativeSimulatorViewAdded = false;
3163
- }
3139
+ tearDownNativeSimulatorView("close");
3164
3140
  hidePopover();
3165
3141
  destroyViewInternal(settingsView);
3166
3142
  settingsView = null;
@@ -3616,13 +3592,38 @@ function getSimulatorServicewechatReferer() {
3616
3592
  return cachedRefererUrl;
3617
3593
  }
3618
3594
 
3595
+ // src/main/services/workspace/op-lock.ts
3596
+ function createOpLock() {
3597
+ let chain = Promise.resolve();
3598
+ let requestSeq = 0;
3599
+ let ownerSeq = 0;
3600
+ return {
3601
+ nextSeq: () => ++requestSeq,
3602
+ takeOwnership: (seq) => {
3603
+ if (seq > ownerSeq) ownerSeq = seq;
3604
+ },
3605
+ isOwner: (seq) => seq === ownerSeq,
3606
+ acquire: () => {
3607
+ let release;
3608
+ const next = new Promise((resolve) => {
3609
+ release = resolve;
3610
+ });
3611
+ const prior = chain;
3612
+ chain = prior.then(() => next);
3613
+ return prior.then(() => release);
3614
+ }
3615
+ };
3616
+ }
3617
+
3619
3618
  // src/main/services/workspace/workspace-service.ts
3620
- var SessionAppInfoSchema = z2.looseObject({ appId: z2.string() });
3619
+ var SessionAppInfoSchema = z2.looseObject({ appId: z2.string().min(1) });
3621
3620
  function createWorkspaceService(ctx) {
3622
3621
  let currentSession = null;
3623
3622
  let currentProjectPath = "";
3623
+ let closing = false;
3624
3624
  let lastClosedProjectPath = "";
3625
3625
  let logGeneration = 0;
3626
+ const opLock = createOpLock();
3626
3627
  function sendStatus(status2, message, hotReload) {
3627
3628
  ctx.notify.projectStatus(hotReload ? { status: status2, message, hotReload: true } : { status: status2, message });
3628
3629
  }
@@ -3648,6 +3649,65 @@ function createWorkspaceService(ctx) {
3648
3649
  console.error("[workspace] session close failed:", err);
3649
3650
  }
3650
3651
  }
3652
+ async function runOpenTeardown(mySeq) {
3653
+ const release = await opLock.acquire();
3654
+ try {
3655
+ if (!opLock.isOwner(mySeq)) return true;
3656
+ clearSimulatorServicewechatReferer();
3657
+ if (currentSession !== null) {
3658
+ ctx.views.detachWorkbench();
3659
+ ctx.views.detachSimulator();
3660
+ }
3661
+ logGeneration++;
3662
+ await disposeSession();
3663
+ currentProjectPath = "";
3664
+ lastClosedProjectPath = "";
3665
+ return false;
3666
+ } finally {
3667
+ release();
3668
+ }
3669
+ }
3670
+ async function runOpenCommit(mySeq, session5, projectPath) {
3671
+ const release = await opLock.acquire();
3672
+ try {
3673
+ if (!opLock.isOwner(mySeq)) return false;
3674
+ currentSession = session5;
3675
+ currentProjectPath = projectPath;
3676
+ return true;
3677
+ } finally {
3678
+ release();
3679
+ }
3680
+ }
3681
+ async function runCompile(projectPath, sessionGeneration) {
3682
+ const { compile } = loadWorkbenchSettings();
3683
+ try {
3684
+ const session5 = await ctx.adapter.openProject({
3685
+ projectPath,
3686
+ sourcemap: true,
3687
+ fileTypes: ctx.fileTypes,
3688
+ watch: compile.watch,
3689
+ onRebuild: () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u70ED\u66F4\u65B0", true),
3690
+ onBuildError: (err) => sendStatus("error", String(err)),
3691
+ onLog: (entry) => {
3692
+ if (sessionGeneration !== logGeneration) return;
3693
+ ctx.notify.compileLog({ stream: entry.stream, text: entry.text, at: Date.now() });
3694
+ }
3695
+ });
3696
+ return { session: session5 };
3697
+ } catch (err) {
3698
+ clearSimulatorServicewechatReferer();
3699
+ return { error: String(err) };
3700
+ }
3701
+ }
3702
+ async function rejectInvalidAppId(session5) {
3703
+ if (SessionAppInfoSchema.safeParse(session5.appInfo).success) return null;
3704
+ try {
3705
+ await session5.close();
3706
+ } catch (closeErr) {
3707
+ console.warn("[workspace] closing appId-less adapter session failed (non-fatal):", closeErr);
3708
+ }
3709
+ return "adapter returned session.appInfo without a string appId \u2014 the CompilationAdapter must supply appInfo.appId";
3710
+ }
3651
3711
  function applyRefererFromSession(session5) {
3652
3712
  const appInfo = session5.appInfo;
3653
3713
  if (appInfo && typeof appInfo.appId === "string" && appInfo.appId.length > 0) {
@@ -3670,6 +3730,7 @@ function createWorkspaceService(ctx) {
3670
3730
  },
3671
3731
  validateProjectDir: async (dirPath) => provider.validateProjectDir ? provider.validateProjectDir(dirPath) : null,
3672
3732
  async openProject(projectPath) {
3733
+ const mySeq = opLock.nextSeq();
3673
3734
  if (ctx.onBeforeOpenProject) {
3674
3735
  try {
3675
3736
  await ctx.onBeforeOpenProject(projectPath);
@@ -3679,65 +3740,36 @@ function createWorkspaceService(ctx) {
3679
3740
  return { success: false, error };
3680
3741
  }
3681
3742
  }
3682
- clearSimulatorServicewechatReferer();
3683
- if (currentSession !== null) {
3684
- ctx.views.detachWorkbench();
3743
+ opLock.takeOwnership(mySeq);
3744
+ if (await runOpenTeardown(mySeq)) {
3745
+ return { success: false, error: "superseded by a newer project open/close" };
3685
3746
  }
3686
- logGeneration++;
3687
- await disposeSession();
3688
- currentProjectPath = "";
3689
- lastClosedProjectPath = "";
3690
3747
  const dirError = provider.validateProjectDir ? await provider.validateProjectDir(projectPath) : null;
3691
3748
  if (dirError) {
3692
3749
  sendStatus("error", dirError);
3693
3750
  return { success: false, error: dirError };
3694
3751
  }
3695
3752
  sendStatus("compiling", "\u6B63\u5728\u7F16\u8BD1...");
3696
- const { compile } = loadWorkbenchSettings();
3697
3753
  const sessionGeneration = ++logGeneration;
3698
- let session5;
3699
- try {
3700
- session5 = await ctx.adapter.openProject({
3701
- projectPath,
3702
- sourcemap: true,
3703
- watch: compile.watch,
3704
- onRebuild: () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u70ED\u66F4\u65B0", true),
3705
- onBuildError: (err) => sendStatus("error", String(err)),
3706
- // Per-line dmcc log (already filtered in devkit). Stamp the
3707
- // wall-clock capture time here and push verbatim on the dedicated
3708
- // compile-log channel — never through projectStatus, whose
3709
- // one-event-per-payload contract feeds compileEvents. Stale lines
3710
- // (a closed/replaced session's worker flushing its buffers) are
3711
- // dropped via the generation check.
3712
- onLog: (entry) => {
3713
- if (sessionGeneration !== logGeneration) return;
3714
- ctx.notify.compileLog({
3715
- stream: entry.stream,
3716
- text: entry.text,
3717
- at: Date.now()
3718
- });
3719
- }
3720
- });
3721
- } catch (err) {
3722
- clearSimulatorServicewechatReferer();
3723
- sendStatus("error", String(err));
3724
- return { success: false, error: String(err) };
3725
- }
3726
- if (!SessionAppInfoSchema.safeParse(session5.appInfo).success) {
3754
+ const compiled = await runCompile(projectPath, sessionGeneration);
3755
+ if ("error" in compiled) {
3756
+ sendStatus("error", compiled.error);
3757
+ return { success: false, error: compiled.error };
3758
+ }
3759
+ const session5 = compiled.session;
3760
+ const appIdError = await rejectInvalidAppId(session5);
3761
+ if (appIdError) {
3762
+ sendStatus("error", appIdError);
3763
+ return { success: false, error: appIdError };
3764
+ }
3765
+ if (!await runOpenCommit(mySeq, session5, projectPath)) {
3727
3766
  try {
3728
3767
  await session5.close();
3729
3768
  } catch (closeErr) {
3730
- console.warn(
3731
- "[workspace] closing appId-less adapter session failed (non-fatal):",
3732
- closeErr
3733
- );
3769
+ console.warn("[workspace] closing superseded open session failed (non-fatal):", closeErr);
3734
3770
  }
3735
- const error = "adapter returned session.appInfo without a string appId \u2014 the CompilationAdapter must supply appInfo.appId";
3736
- sendStatus("error", error);
3737
- return { success: false, error };
3771
+ return { success: false, error: "superseded by a newer project open/close" };
3738
3772
  }
3739
- currentSession = session5;
3740
- currentProjectPath = projectPath;
3741
3773
  bestEffort("updateLastOpened", () => {
3742
3774
  if (provider.updateLastOpened) provider.updateLastOpened(projectPath);
3743
3775
  });
@@ -3750,26 +3782,43 @@ function createWorkspaceService(ctx) {
3750
3782
  };
3751
3783
  },
3752
3784
  async closeProject() {
3753
- logGeneration++;
3754
- clearSimulatorServicewechatReferer();
3755
- await disposeSession();
3756
- if (currentProjectPath !== "") lastClosedProjectPath = currentProjectPath;
3757
- currentProjectPath = "";
3758
- ctx.views.disposeAll();
3785
+ const mySeq = opLock.nextSeq();
3786
+ opLock.takeOwnership(mySeq);
3787
+ const release = await opLock.acquire();
3788
+ try {
3789
+ if (!opLock.isOwner(mySeq)) return;
3790
+ closing = true;
3791
+ logGeneration++;
3792
+ clearSimulatorServicewechatReferer();
3793
+ await disposeSession();
3794
+ if (currentProjectPath !== "") lastClosedProjectPath = currentProjectPath;
3795
+ currentProjectPath = "";
3796
+ ctx.views.disposeAll();
3797
+ } finally {
3798
+ closing = false;
3799
+ release();
3800
+ }
3759
3801
  },
3760
3802
  getSession: () => currentSession,
3761
3803
  getProjectPath: () => currentProjectPath,
3762
3804
  getLastClosedProjectPath: () => lastClosedProjectPath,
3763
3805
  hasActiveSession: () => currentSession !== null,
3806
+ isClosing: () => closing,
3764
3807
  async captureThumbnail(projectPath) {
3765
3808
  if (!currentSession || projectPath !== currentProjectPath) return null;
3766
- const wc = ctx.views.getSimulatorWebContents();
3767
- if (!wc) return null;
3809
+ const simulatorWc = ctx.views.getSimulatorWebContents();
3810
+ if (!simulatorWc) return null;
3768
3811
  if (ctx.views.getSimulatorProjectPath() !== projectPath) return null;
3769
3812
  const session5 = currentSession;
3813
+ const bridge = ctx.bridge;
3814
+ const nativeHost = bridge?.isNativeHost() ?? false;
3815
+ const renderGuest = nativeHost ? bridge?.getActiveRenderWc() ?? null : null;
3816
+ if (nativeHost && !renderGuest) return null;
3817
+ const captureTarget = renderGuest ?? simulatorWc;
3818
+ if (captureTarget.isDestroyed()) return null;
3770
3819
  try {
3771
- const image = await wc.capturePage();
3772
- if (currentSession !== session5 || currentProjectPath !== projectPath || ctx.views.getSimulatorWebContents() !== wc || ctx.views.getSimulatorProjectPath() !== projectPath) {
3820
+ const image = await captureTarget.capturePage();
3821
+ if (currentSession !== session5 || currentProjectPath !== projectPath || ctx.views.getSimulatorWebContents() !== simulatorWc || ctx.views.getSimulatorProjectPath() !== projectPath || (nativeHost ? bridge?.getActiveRenderWc() ?? null : simulatorWc) !== captureTarget) {
3773
3822
  return null;
3774
3823
  }
3775
3824
  const dataUrl = `data:image/png;base64,${image.toPNG().toString("base64")}`;
@@ -3900,6 +3949,7 @@ function createWorkbenchContext(opts) {
3900
3949
  preloadPath: opts.preloadPath,
3901
3950
  rendererDir: opts.rendererDir,
3902
3951
  apiNamespaces: opts.apiNamespaces ?? [],
3952
+ fileTypes: opts.fileTypes ?? {},
3903
3953
  appName: opts.appName ?? "Dimina DevTools",
3904
3954
  brandingProvider: opts.brandingProvider
3905
3955
  };
@@ -4702,12 +4752,15 @@ function navigateServiceHost(win, url) {
4702
4752
  }
4703
4753
  return loaded;
4704
4754
  }
4705
- function serviceHostSpec(appId) {
4755
+ function serviceHostSpec(appId, projectPath) {
4706
4756
  return {
4707
4757
  // Per-project partition when an appId is known (so this project's service
4708
4758
  // host shares storage ONLY with its own render side); the shared partition
4709
4759
  // for the pre-warm pool's default spec (no appId — see KNOWN BLOCKER below).
4710
- partition: appId ? miniappPartition(appId) : SERVICE_HOST_PARTITION,
4760
+ // projectPath joins the key so same-appId/different-path projects isolate;
4761
+ // it must match the simulator WCV's partition (view-manager passes the same
4762
+ // (appId, projectPath)).
4763
+ partition: appId ? miniappPartition(appId, projectPath) : SERVICE_HOST_PARTITION,
4711
4764
  preloadPath: serviceHostPreloadPath,
4712
4765
  size: { width: 980, height: 720 },
4713
4766
  contextIsolation: false,
@@ -4717,7 +4770,7 @@ function serviceHostSpec(appId) {
4717
4770
  };
4718
4771
  }
4719
4772
  function createServiceHostWindow(opts) {
4720
- const win = constructServiceHostWindow({ appId: opts.appId, partition: miniappPartition(opts.appId) });
4773
+ const win = constructServiceHostWindow({ appId: opts.appId, partition: miniappPartition(opts.appId, opts.projectPath) });
4721
4774
  void navigateServiceHost(win, buildServiceHostSpawnUrl(opts));
4722
4775
  return win;
4723
4776
  }
@@ -5625,9 +5678,14 @@ function resolveCurrentApp(state, ctx, appId) {
5625
5678
  for (const ap of state.appSessions.values()) if (ap.appId === activeAppId) match = ap;
5626
5679
  if (match) return match;
5627
5680
  }
5681
+ if (ctx.workspace?.isClosing?.()) return void 0;
5628
5682
  let last;
5629
- for (const ap of state.appSessions.values()) last = ap;
5630
- return last;
5683
+ const distinctAppIds = /* @__PURE__ */ new Set();
5684
+ for (const ap of state.appSessions.values()) {
5685
+ last = ap;
5686
+ distinctAppIds.add(ap.appId);
5687
+ }
5688
+ return distinctAppIds.size <= 1 ? last : void 0;
5631
5689
  }
5632
5690
  function installBridgeRouter(ctx) {
5633
5691
  const state = {
@@ -5731,6 +5789,14 @@ function installBridgeRouter(ctx) {
5731
5789
  }
5732
5790
  }
5733
5791
  },
5792
+ disposeSessionsForSimulator: (simulatorWcId) => {
5793
+ const ids = [];
5794
+ for (const [id, ap] of state.appSessions) {
5795
+ if (ap.simulatorWc.id === simulatorWcId) ids.push(id);
5796
+ }
5797
+ return Promise.all(ids.map((id) => disposeAppSession(state, id))).then(() => {
5798
+ });
5799
+ },
5734
5800
  debugTap: state.debugTap
5735
5801
  };
5736
5802
  ctx.bridge = bridgeHandle;
@@ -5944,6 +6010,13 @@ async function handleSpawn(state, ctx, event, opts) {
5944
6010
  serviceWindow = createServiceHostWindow({
5945
6011
  bridgeId,
5946
6012
  appId,
6013
+ // Same (appId, projectPath) pair the simulator WCV uses, so this project's
6014
+ // service host and render guests land on ONE partition while a different
6015
+ // project with the same appId is isolated. Use the path captured at spawn
6016
+ // ENTRY (workspaceProjectPath), not a re-read: a project switch during the
6017
+ // awaits above would otherwise give the service host a different path than
6018
+ // the simulator WCV was built with, splitting the partition.
6019
+ projectPath: workspaceProjectPath || void 0,
5947
6020
  pagePath,
5948
6021
  pkgRoot,
5949
6022
  root,
@@ -6004,6 +6077,9 @@ async function handleSpawn(state, ctx, event, opts) {
6004
6077
  void disposeAppSession(state, appSessionId);
6005
6078
  };
6006
6079
  simulatorWc.once("destroyed", onSimulatorDestroyed);
6080
+ if (simulatorWc.isDestroyed()) {
6081
+ void disposeAppSession(state, appSessionId);
6082
+ }
6007
6083
  if (usedPool) {
6008
6084
  const bootOnServiceLoad = () => {
6009
6085
  if (serviceWindow.isDestroyed()) return;
@@ -6518,6 +6594,10 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
6518
6594
  for (const page of ap.pages.values()) {
6519
6595
  if (page.renderWc && !page.renderWc.isDestroyed()) {
6520
6596
  state.wcIdToBridgeId.delete(page.renderWc.id);
6597
+ try {
6598
+ page.renderWc.close();
6599
+ } catch {
6600
+ }
6521
6601
  }
6522
6602
  state.pageSessions.delete(page.bridgeId);
6523
6603
  }
@@ -9792,6 +9872,10 @@ async function startWorkbenchCoiServer(options) {
9792
9872
  });
9793
9873
  return;
9794
9874
  }
9875
+ if (url.pathname === "/__filetypes") {
9876
+ jsonRes(res, 200, options.getFileTypes?.() ?? {});
9877
+ return;
9878
+ }
9795
9879
  if (url.pathname.startsWith("/__contrib/")) {
9796
9880
  if (!contribRoot) {
9797
9881
  res.writeHead(404);
@@ -10017,6 +10101,7 @@ function createContext(config, mainWindow, rendererDir2) {
10017
10101
  rendererDir: rendererDir2,
10018
10102
  appName: config.appName,
10019
10103
  apiNamespaces: config.apiNamespaces,
10104
+ fileTypes: config.fileTypes,
10020
10105
  brandingProvider: config.brandingProvider,
10021
10106
  // The host-supplied ProjectsProvider / template types in `shared/types`
10022
10107
  // are structurally compatible with the main-process equivalents —
@@ -10291,7 +10376,8 @@ async function createDevtoolsRuntime(config = {}) {
10291
10376
  const coiServer = await startWorkbenchCoiServer({
10292
10377
  rootDir: bundleDir,
10293
10378
  getProjectRoot: () => context.workspace.getProjectPath(),
10294
- extensionsDir: config.editorViewConfig?.extensionsDir
10379
+ extensionsDir: config.editorViewConfig?.extensionsDir,
10380
+ getFileTypes: () => context.fileTypes
10295
10381
  });
10296
10382
  context.registry.add(() => coiServer.close());
10297
10383
  context.registry.add(() => context.views.detachWorkbench());
@@ -49,6 +49,27 @@ export interface BridgeRouterHandle {
49
49
  getDevice(): NativeDeviceInfo | null;
50
50
  /** Cache the selected device + push DEVICE_CHANGE to the live simulator WC(s). */
51
51
  setDevice(device: NativeDeviceInfo): void;
52
+ /**
53
+ * Deterministically tear down every app session bound to the given simulator
54
+ * WebContentsView id (project close / DeviceShell respawn). The session
55
+ * mappings are cleared, the render-host guest webContents are closed, and the
56
+ * service-host window is closed/released — synchronously, instead of waiting
57
+ * on the WCV's async `'destroyed'` cascade — so the next project never
58
+ * re-resolves or screenshots the outgoing project's render guest. Idempotent
59
+ * with the `simulatorWc.once('destroyed')` hook (`disposeAppSession`
60
+ * early-returns once a session is gone). Optional so partial test mocks of the
61
+ * handle need not stub it.
62
+ *
63
+ * The synchronous prefix of the underlying disposeAppSession clears every map
64
+ * + closes the render guests + the service window before it awaits, so the
65
+ * bridge is clean the moment this is called. The returned promise resolves
66
+ * after the async tail (pool.release / resourceServer.close) so the lifecycle
67
+ * owner can sequence a reopen after full teardown. NOTE: disposeAppSession
68
+ * catches and logs pool/resource-release failures internally, so the promise
69
+ * resolves rather than rejecting on those — it is a completion signal, not an
70
+ * error channel.
71
+ */
72
+ disposeSessionsForSimulator?(simulatorWcId: number): Promise<void>;
52
73
  /**
53
74
  * The flag-gated debugTap (§7) over the bridge message stream. Exposed so a
54
75
  * hidden devtools panel / automation can read `.entries()` when