@dimina-kit/devtools 0.4.0-dev.20260718085557 → 0.4.0-dev.20260718143821
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.
- package/dist/main/app/app.d.ts +10 -1
- package/dist/main/app/app.js +11 -1
- package/dist/main/app/lifecycle.d.ts +12 -1
- package/dist/main/app/lifecycle.js +18 -1
- package/dist/main/index.bundle.js +326 -35
- package/dist/main/index.js +49 -1
- package/dist/main/runtime/devtools-backend-before-quit.harness.d.ts +61 -0
- package/dist/main/runtime/devtools-backend-before-quit.harness.js +356 -0
- package/dist/main/runtime/devtools-backend.js +49 -4
- package/dist/main/services/mcp/server.js +24 -2
- package/dist/main/services/projects/create-project-service.js +10 -3
- package/dist/main/services/update/github-release-checker.js +22 -3
- package/dist/main/services/update/update-manager.d.ts +4 -1
- package/dist/main/services/update/update-manager.js +30 -5
- package/dist/native-host/common/common.js +154 -58
- package/dist/native-host/container/pageFrame.css +1 -1
- package/dist/native-host/render/render.js +6578 -3291
- package/dist/native-host/service/service.js +2 -2
- package/dist/renderer/assets/{index-U9F8MrtV.js → index-DfDIvgvK.js} +2 -2
- package/dist/renderer/entries/main/index.html +1 -1
- package/dist/service-host/sync-impls/menu-button-geometry.js +15 -5
- package/dist/service-host/sync-impls/menu-button.js +5 -3
- package/dist/simulator/assets/device-shell-Cs-rmP1f.js +2 -0
- package/dist/simulator/assets/device-shell-DOy53Y0s.css +1 -0
- package/dist/simulator/assets/{simulator-Cy8c-eA2.js → simulator-BG-5CADK.js} +3 -3
- package/dist/simulator/device-shell/menu-button-geometry.js +15 -5
- package/dist/simulator/simulator.html +1 -1
- package/package.json +6 -6
- package/dist/simulator/assets/device-shell-C-HZY3bL.css +0 -1
- package/dist/simulator/assets/device-shell-Cu1ZPLfe.js +0 -2
package/dist/main/app/app.d.ts
CHANGED
|
@@ -32,5 +32,14 @@ export declare function runDevtoolsBootstrap(config?: WorkbenchAppConfig): void;
|
|
|
32
32
|
* v2 `RuntimeBackend.assemble` reuses the exact same body (parity by shared
|
|
33
33
|
* implementation, not behavioural re-creation).
|
|
34
34
|
*/
|
|
35
|
-
export declare function createDevtoolsRuntime(config?: WorkbenchAppConfig
|
|
35
|
+
export declare function createDevtoolsRuntime(config?: WorkbenchAppConfig,
|
|
36
|
+
/**
|
|
37
|
+
* Fired the instant the `WorkbenchAppInstance` exists — BEFORE `config.onSetup`
|
|
38
|
+
* is awaited below, which may run arbitrarily long host code (including
|
|
39
|
+
* loading the host-toolbar, which opens a live MessagePort). A caller that
|
|
40
|
+
* only learns `instance` from this function's return value would not see it
|
|
41
|
+
* until `onSetup` resolves, leaving a window where an app-quit teardown hook
|
|
42
|
+
* has nothing to dispose yet a live toolbar port already exists.
|
|
43
|
+
*/
|
|
44
|
+
onInstanceCreated?: (instance: WorkbenchAppInstance) => void): Promise<WorkbenchAppInstance>;
|
|
36
45
|
//# sourceMappingURL=app.d.ts.map
|
package/dist/main/app/app.js
CHANGED
|
@@ -357,7 +357,16 @@ export function runDevtoolsBootstrap(config = {}) {
|
|
|
357
357
|
* v2 `RuntimeBackend.assemble` reuses the exact same body (parity by shared
|
|
358
358
|
* implementation, not behavioural re-creation).
|
|
359
359
|
*/
|
|
360
|
-
export async function createDevtoolsRuntime(config = {}
|
|
360
|
+
export async function createDevtoolsRuntime(config = {},
|
|
361
|
+
/**
|
|
362
|
+
* Fired the instant the `WorkbenchAppInstance` exists — BEFORE `config.onSetup`
|
|
363
|
+
* is awaited below, which may run arbitrarily long host code (including
|
|
364
|
+
* loading the host-toolbar, which opens a live MessagePort). A caller that
|
|
365
|
+
* only learns `instance` from this function's return value would not see it
|
|
366
|
+
* until `onSetup` resolves, leaving a window where an app-quit teardown hook
|
|
367
|
+
* has nothing to dispose yet a live toolbar port already exists.
|
|
368
|
+
*/
|
|
369
|
+
onInstanceCreated) {
|
|
361
370
|
// Self-gate on Electron readiness: this builds a BrowserWindow immediately, so
|
|
362
371
|
// it must run after `app.whenReady()`. The framework backend path already
|
|
363
372
|
// awaited it (idempotent no-op here); this guards any direct caller against
|
|
@@ -414,6 +423,7 @@ export async function createDevtoolsRuntime(config = {}) {
|
|
|
414
423
|
registerSimulatorApi: (name, handler) => context.registry.add(toDisposable(context.simulatorApis.register(name, handler))),
|
|
415
424
|
dispose: () => disposeContext(context),
|
|
416
425
|
};
|
|
426
|
+
onInstanceCreated?.(instance);
|
|
417
427
|
// Built-in simulator APIs: devtools-supplied wx.* implementations that run
|
|
418
428
|
// in the main process. Hosts can override any of these in their onSetup by
|
|
419
429
|
// re-registering the same name (last-write-wins on the Map).
|
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
export declare function isAppQuitting(): boolean;
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* `onBeforeQuit`: invoked synchronously, once per `before-quit`, while
|
|
4
|
+
* Electron's main loop is still fully healthy — i.e. BEFORE `will-quit` and
|
|
5
|
+
* any window/WebContentsView destruction. Callers use this to tear down
|
|
6
|
+
* resources (child WebContentsViews, MessagePorts) that are unsafe to let
|
|
7
|
+
* survive into Chromium's native shutdown sequence, where a JS `'destroyed'`
|
|
8
|
+
* handler closing them can hit an already-torn-down native object. Isolated
|
|
9
|
+
* like `host-toolbar-port-channel.ts`'s `invokeReadyHandler`: a throwing
|
|
10
|
+
* callback must not stop `appIsQuitting` from flipping or escape as an
|
|
11
|
+
* uncaught exception out of Electron's event dispatch.
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerAppLifecycle(onBeforeQuit?: () => void): void;
|
|
3
14
|
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -8,7 +8,18 @@ let appIsQuitting = false;
|
|
|
8
8
|
export function isAppQuitting() {
|
|
9
9
|
return appIsQuitting;
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* `onBeforeQuit`: invoked synchronously, once per `before-quit`, while
|
|
13
|
+
* Electron's main loop is still fully healthy — i.e. BEFORE `will-quit` and
|
|
14
|
+
* any window/WebContentsView destruction. Callers use this to tear down
|
|
15
|
+
* resources (child WebContentsViews, MessagePorts) that are unsafe to let
|
|
16
|
+
* survive into Chromium's native shutdown sequence, where a JS `'destroyed'`
|
|
17
|
+
* handler closing them can hit an already-torn-down native object. Isolated
|
|
18
|
+
* like `host-toolbar-port-channel.ts`'s `invokeReadyHandler`: a throwing
|
|
19
|
+
* callback must not stop `appIsQuitting` from flipping or escape as an
|
|
20
|
+
* uncaught exception out of Electron's event dispatch.
|
|
21
|
+
*/
|
|
22
|
+
export function registerAppLifecycle(onBeforeQuit) {
|
|
12
23
|
app.on('window-all-closed', () => {
|
|
13
24
|
globalShortcut.unregisterAll();
|
|
14
25
|
app.quit();
|
|
@@ -16,6 +27,12 @@ export function registerAppLifecycle() {
|
|
|
16
27
|
app.on('before-quit', () => {
|
|
17
28
|
appIsQuitting = true;
|
|
18
29
|
globalShortcut.unregisterAll();
|
|
30
|
+
try {
|
|
31
|
+
onBeforeQuit?.();
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error('[lifecycle] onBeforeQuit handler threw:', err);
|
|
35
|
+
}
|
|
19
36
|
});
|
|
20
37
|
}
|
|
21
38
|
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -893,8 +893,8 @@ function registerProjectFsIpc(ctx) {
|
|
|
893
893
|
|
|
894
894
|
// src/main/app/app.ts
|
|
895
895
|
import { app as app15, BrowserWindow as BrowserWindow8, nativeImage, session as session4 } from "electron";
|
|
896
|
-
import
|
|
897
|
-
import
|
|
896
|
+
import fs13 from "fs";
|
|
897
|
+
import path25 from "path";
|
|
898
898
|
|
|
899
899
|
// src/main/utils/paths.ts
|
|
900
900
|
import fs3 from "fs";
|
|
@@ -1080,7 +1080,7 @@ var appIsQuitting = false;
|
|
|
1080
1080
|
function isAppQuitting() {
|
|
1081
1081
|
return appIsQuitting;
|
|
1082
1082
|
}
|
|
1083
|
-
function registerAppLifecycle() {
|
|
1083
|
+
function registerAppLifecycle(onBeforeQuit) {
|
|
1084
1084
|
app4.on("window-all-closed", () => {
|
|
1085
1085
|
globalShortcut2.unregisterAll();
|
|
1086
1086
|
app4.quit();
|
|
@@ -1088,6 +1088,11 @@ function registerAppLifecycle() {
|
|
|
1088
1088
|
app4.on("before-quit", () => {
|
|
1089
1089
|
appIsQuitting = true;
|
|
1090
1090
|
globalShortcut2.unregisterAll();
|
|
1091
|
+
try {
|
|
1092
|
+
onBeforeQuit?.();
|
|
1093
|
+
} catch (err2) {
|
|
1094
|
+
console.error("[lifecycle] onBeforeQuit handler threw:", err2);
|
|
1095
|
+
}
|
|
1091
1096
|
});
|
|
1092
1097
|
}
|
|
1093
1098
|
|
|
@@ -4068,8 +4073,8 @@ function createHostToolbarView(ctx, reconciler, deps) {
|
|
|
4068
4073
|
hide() {
|
|
4069
4074
|
hideHostToolbar();
|
|
4070
4075
|
},
|
|
4071
|
-
setPreloadPath(
|
|
4072
|
-
hostToolbarPreloadOverride =
|
|
4076
|
+
setPreloadPath(path26) {
|
|
4077
|
+
hostToolbarPreloadOverride = path26;
|
|
4073
4078
|
},
|
|
4074
4079
|
setHeightMode(mode) {
|
|
4075
4080
|
if (mode !== "auto" && !(Number.isFinite(mode.fixed) && mode.fixed >= 0)) {
|
|
@@ -5867,7 +5872,7 @@ async function materializeTemplate(target, template, name) {
|
|
|
5867
5872
|
`Template source missing on disk: ${template.source.path}`
|
|
5868
5873
|
);
|
|
5869
5874
|
}
|
|
5870
|
-
fs7.
|
|
5875
|
+
await fs7.promises.cp(template.source.path, target, {
|
|
5871
5876
|
recursive: true,
|
|
5872
5877
|
force: true
|
|
5873
5878
|
});
|
|
@@ -10250,7 +10255,6 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
|
|
|
10250
10255
|
});
|
|
10251
10256
|
connectTarget("workbench").catch(() => {
|
|
10252
10257
|
});
|
|
10253
|
-
const server = buildServer();
|
|
10254
10258
|
const transports = /* @__PURE__ */ new Map();
|
|
10255
10259
|
const httpServer = createServer(async (req, res) => {
|
|
10256
10260
|
const url = new URL(req.url ?? "/", `http://localhost`);
|
|
@@ -10258,7 +10262,14 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
|
|
|
10258
10262
|
const transport = new SSEServerTransport("/message", res);
|
|
10259
10263
|
transports.set(transport.sessionId, transport);
|
|
10260
10264
|
res.on("close", () => transports.delete(transport.sessionId));
|
|
10261
|
-
|
|
10265
|
+
try {
|
|
10266
|
+
await buildServer().connect(transport);
|
|
10267
|
+
} catch (err2) {
|
|
10268
|
+
transports.delete(transport.sessionId);
|
|
10269
|
+
if (!res.headersSent) res.writeHead(500).end("MCP connect failed");
|
|
10270
|
+
else res.end();
|
|
10271
|
+
console.error("[MCP] SSE connect failed:", err2);
|
|
10272
|
+
}
|
|
10262
10273
|
} else if (req.method === "POST" && url.pathname === "/message") {
|
|
10263
10274
|
const sessionId = url.searchParams.get("sessionId") ?? "";
|
|
10264
10275
|
const transport = transports.get(sessionId);
|
|
@@ -10607,13 +10618,13 @@ function createRenderInspector(options = {}) {
|
|
|
10607
10618
|
import { toDisposable as toDisposable6 } from "@dimina-kit/electron-deck/main";
|
|
10608
10619
|
|
|
10609
10620
|
// src/main/services/simulator-temp-files/store.ts
|
|
10610
|
-
function registerTempFile(store,
|
|
10621
|
+
function registerTempFile(store, path26, mime, bytes) {
|
|
10611
10622
|
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
|
|
10612
|
-
store.delete(
|
|
10613
|
-
store.set(
|
|
10623
|
+
store.delete(path26);
|
|
10624
|
+
store.set(path26, { bytes: buf, mime });
|
|
10614
10625
|
}
|
|
10615
|
-
function revokeTempFile(store,
|
|
10616
|
-
store.delete(
|
|
10626
|
+
function revokeTempFile(store, path26) {
|
|
10627
|
+
store.delete(path26);
|
|
10617
10628
|
}
|
|
10618
10629
|
function revokeAllTempFiles(store) {
|
|
10619
10630
|
store.clear();
|
|
@@ -11041,10 +11052,10 @@ function setupSimulatorTempFiles(simSession) {
|
|
|
11041
11052
|
const store = /* @__PURE__ */ new Map();
|
|
11042
11053
|
const pendingWaiters = /* @__PURE__ */ new Map();
|
|
11043
11054
|
let disposed = false;
|
|
11044
|
-
function drainWaiters(
|
|
11045
|
-
const list = pendingWaiters.get(
|
|
11055
|
+
function drainWaiters(path26) {
|
|
11056
|
+
const list = pendingWaiters.get(path26);
|
|
11046
11057
|
if (!list) return;
|
|
11047
|
-
pendingWaiters.delete(
|
|
11058
|
+
pendingWaiters.delete(path26);
|
|
11048
11059
|
for (const fn of list) fn();
|
|
11049
11060
|
}
|
|
11050
11061
|
function drainAllWaiters() {
|
|
@@ -11057,15 +11068,15 @@ function setupSimulatorTempFiles(simSession) {
|
|
|
11057
11068
|
const registry = new IpcRegistry(simulatorOnlyPolicy);
|
|
11058
11069
|
registry.on("simulator:temp-file:write", (_event, payload) => {
|
|
11059
11070
|
if (disposed) return;
|
|
11060
|
-
const { path:
|
|
11061
|
-
registerTempFile(store,
|
|
11071
|
+
const { path: path26, mime, bytes } = payload;
|
|
11072
|
+
registerTempFile(store, path26, mime, bytes);
|
|
11062
11073
|
enforceStoreCap(store);
|
|
11063
|
-
drainWaiters(
|
|
11074
|
+
drainWaiters(path26);
|
|
11064
11075
|
});
|
|
11065
11076
|
registry.on("simulator:temp-file:revoke", (_event, payload) => {
|
|
11066
11077
|
if (disposed) return;
|
|
11067
|
-
const { path:
|
|
11068
|
-
revokeTempFile(store,
|
|
11078
|
+
const { path: path26 } = payload;
|
|
11079
|
+
revokeTempFile(store, path26);
|
|
11069
11080
|
});
|
|
11070
11081
|
registry.on("simulator:temp-file:revoke-all", () => {
|
|
11071
11082
|
if (disposed) return;
|
|
@@ -11619,8 +11630,9 @@ var UpdateManager = class {
|
|
|
11619
11630
|
const currentVersion = this.getCurrentVersion();
|
|
11620
11631
|
const info = await this.checker.checkForUpdates(currentVersion);
|
|
11621
11632
|
if (info) {
|
|
11633
|
+
const isSameUpdate = this.latestUpdate?.version === info.version && this.latestUpdate?.downloadUrl === info.downloadUrl;
|
|
11622
11634
|
this.latestUpdate = info;
|
|
11623
|
-
this.downloadedPath = null;
|
|
11635
|
+
if (!isSameUpdate) this.downloadedPath = null;
|
|
11624
11636
|
return { hasUpdate: true, info };
|
|
11625
11637
|
}
|
|
11626
11638
|
return { hasUpdate: false };
|
|
@@ -11644,11 +11656,17 @@ var UpdateManager = class {
|
|
|
11644
11656
|
return { success: false, error: String(err2) };
|
|
11645
11657
|
}
|
|
11646
11658
|
}
|
|
11647
|
-
install() {
|
|
11648
|
-
if (this.downloadedPath) {
|
|
11649
|
-
|
|
11650
|
-
app13.quit();
|
|
11659
|
+
async install() {
|
|
11660
|
+
if (!this.downloadedPath) {
|
|
11661
|
+
return { success: false, error: "No update downloaded" };
|
|
11651
11662
|
}
|
|
11663
|
+
const failure = await shell3.openPath(this.downloadedPath);
|
|
11664
|
+
if (failure) {
|
|
11665
|
+
console.warn("[UpdateManager] install failed:", failure);
|
|
11666
|
+
return { success: false, error: failure };
|
|
11667
|
+
}
|
|
11668
|
+
app13.quit();
|
|
11669
|
+
return { success: true };
|
|
11652
11670
|
}
|
|
11653
11671
|
/**
|
|
11654
11672
|
* Tear down timers + IPC handlers. Returns a Promise so callers that need
|
|
@@ -11681,7 +11699,249 @@ var UpdateManager = class {
|
|
|
11681
11699
|
};
|
|
11682
11700
|
|
|
11683
11701
|
// src/main/services/update/github-release-checker.ts
|
|
11702
|
+
import fs12 from "fs";
|
|
11703
|
+
import path24 from "path";
|
|
11704
|
+
import https from "https";
|
|
11684
11705
|
import { app as app14 } from "electron";
|
|
11706
|
+
function createGitHubReleaseChecker(opts) {
|
|
11707
|
+
const releaseUrl = opts.includePrereleases ? `https://api.github.com/repos/${opts.owner}/${opts.repo}/releases` : `https://api.github.com/repos/${opts.owner}/${opts.repo}/releases/latest`;
|
|
11708
|
+
const parseVersion = opts.parseVersion ?? schemeParser(opts.versionScheme ?? "semver");
|
|
11709
|
+
return {
|
|
11710
|
+
async checkForUpdates(currentVersion) {
|
|
11711
|
+
const release = await fetchRelease(releaseUrl, opts);
|
|
11712
|
+
if (!release) return null;
|
|
11713
|
+
const latestVersion = parseVersion(release);
|
|
11714
|
+
if (!latestVersion) return null;
|
|
11715
|
+
if (compareSemver(latestVersion, stripV(currentVersion)) <= 0) return null;
|
|
11716
|
+
const asset = (opts.pickAsset ?? defaultPickAsset)(release.assets, {
|
|
11717
|
+
platform: process.platform,
|
|
11718
|
+
arch: process.arch
|
|
11719
|
+
});
|
|
11720
|
+
if (!asset) return null;
|
|
11721
|
+
return {
|
|
11722
|
+
version: latestVersion,
|
|
11723
|
+
downloadUrl: asset.browser_download_url,
|
|
11724
|
+
releaseNotes: release.body,
|
|
11725
|
+
mandatory: opts.mandatory
|
|
11726
|
+
};
|
|
11727
|
+
},
|
|
11728
|
+
async downloadUpdate(info, onProgress) {
|
|
11729
|
+
const tmpDir = path24.join(app14.getPath("temp"), `${opts.repo}-update`);
|
|
11730
|
+
if (!fs12.existsSync(tmpDir)) fs12.mkdirSync(tmpDir, { recursive: true });
|
|
11731
|
+
const fileName = path24.basename(new URL(info.downloadUrl).pathname);
|
|
11732
|
+
const filePath = path24.join(tmpDir, fileName);
|
|
11733
|
+
await downloadFile(info.downloadUrl, filePath, opts.token, onProgress);
|
|
11734
|
+
return filePath;
|
|
11735
|
+
}
|
|
11736
|
+
};
|
|
11737
|
+
}
|
|
11738
|
+
function defaultPickAsset(assets, ctx) {
|
|
11739
|
+
const candidates = platformMatchers(ctx.platform, ctx.arch);
|
|
11740
|
+
for (const match of candidates) {
|
|
11741
|
+
const hit = assets.find((a) => match(a.name));
|
|
11742
|
+
if (hit) return hit;
|
|
11743
|
+
}
|
|
11744
|
+
return void 0;
|
|
11745
|
+
}
|
|
11746
|
+
function platformMatchers(platform, arch) {
|
|
11747
|
+
const lc = (s) => s.toLowerCase();
|
|
11748
|
+
const archTags = archAliases(arch);
|
|
11749
|
+
const hasArch = (name) => archTags.some((tag) => lc(name).includes(tag));
|
|
11750
|
+
const endsWith = (name, ext) => lc(name).endsWith(ext);
|
|
11751
|
+
switch (platform) {
|
|
11752
|
+
case "darwin":
|
|
11753
|
+
return [
|
|
11754
|
+
(n) => endsWith(n, ".dmg") && hasArch(n),
|
|
11755
|
+
(n) => endsWith(n, ".dmg"),
|
|
11756
|
+
// mac builds are often universal / unarch-tagged
|
|
11757
|
+
(n) => endsWith(n, ".zip") && (lc(n).includes("mac") || lc(n).includes("darwin"))
|
|
11758
|
+
];
|
|
11759
|
+
case "win32":
|
|
11760
|
+
return [
|
|
11761
|
+
(n) => endsWith(n, ".exe") && hasArch(n),
|
|
11762
|
+
(n) => endsWith(n, ".zip") && (lc(n).includes("win") || lc(n).includes("windows")) && hasArch(n),
|
|
11763
|
+
(n) => endsWith(n, ".exe"),
|
|
11764
|
+
(n) => endsWith(n, ".zip") && (lc(n).includes("win") || lc(n).includes("windows"))
|
|
11765
|
+
];
|
|
11766
|
+
case "linux":
|
|
11767
|
+
return [
|
|
11768
|
+
(n) => endsWith(n, ".appimage") && hasArch(n),
|
|
11769
|
+
(n) => endsWith(n, ".tar.gz") && lc(n).includes("linux") && hasArch(n),
|
|
11770
|
+
(n) => endsWith(n, ".appimage"),
|
|
11771
|
+
(n) => endsWith(n, ".tar.gz") && lc(n).includes("linux"),
|
|
11772
|
+
(n) => endsWith(n, ".deb") && hasArch(n)
|
|
11773
|
+
];
|
|
11774
|
+
default:
|
|
11775
|
+
return [];
|
|
11776
|
+
}
|
|
11777
|
+
}
|
|
11778
|
+
function archAliases(arch) {
|
|
11779
|
+
switch (arch) {
|
|
11780
|
+
case "x64":
|
|
11781
|
+
return ["x64", "x86_64", "amd64"];
|
|
11782
|
+
case "arm64":
|
|
11783
|
+
return ["arm64", "aarch64"];
|
|
11784
|
+
case "ia32":
|
|
11785
|
+
return ["ia32", "x86", "i386"];
|
|
11786
|
+
default:
|
|
11787
|
+
return [arch];
|
|
11788
|
+
}
|
|
11789
|
+
}
|
|
11790
|
+
var SEMVER_RE = /(\d+)\.(\d+)\.(\d+)(?:[-+]([0-9A-Za-z.-]+))?/;
|
|
11791
|
+
var TRAILING_NUMBER_RE = /-(\d+)$/;
|
|
11792
|
+
function schemeParser(scheme) {
|
|
11793
|
+
if (scheme === "trailing-number") {
|
|
11794
|
+
return (release) => {
|
|
11795
|
+
const m = release.tag_name.match(TRAILING_NUMBER_RE);
|
|
11796
|
+
return m ? m[1] : null;
|
|
11797
|
+
};
|
|
11798
|
+
}
|
|
11799
|
+
return defaultParseVersion;
|
|
11800
|
+
}
|
|
11801
|
+
function defaultParseVersion(release) {
|
|
11802
|
+
const fromTag = extractSemver(release.tag_name);
|
|
11803
|
+
if (fromTag) return fromTag;
|
|
11804
|
+
if (release.name) {
|
|
11805
|
+
const fromName = extractSemver(release.name);
|
|
11806
|
+
if (fromName) return fromName;
|
|
11807
|
+
}
|
|
11808
|
+
for (const asset of release.assets) {
|
|
11809
|
+
const hit = extractSemver(asset.name);
|
|
11810
|
+
if (hit) return hit;
|
|
11811
|
+
}
|
|
11812
|
+
return null;
|
|
11813
|
+
}
|
|
11814
|
+
function extractSemver(input) {
|
|
11815
|
+
const m = input.match(SEMVER_RE);
|
|
11816
|
+
if (!m) return null;
|
|
11817
|
+
const [, major, minor, patch, pre] = m;
|
|
11818
|
+
return pre ? `${major}.${minor}.${patch}-${pre}` : `${major}.${minor}.${patch}`;
|
|
11819
|
+
}
|
|
11820
|
+
function stripV(version) {
|
|
11821
|
+
return version.replace(/^v/i, "");
|
|
11822
|
+
}
|
|
11823
|
+
function compareSemver(a, b) {
|
|
11824
|
+
const [aCore, aPre] = splitPre(a);
|
|
11825
|
+
const [bCore, bPre] = splitPre(b);
|
|
11826
|
+
const [a1, a2, a3] = parseCore(aCore);
|
|
11827
|
+
const [b1, b2, b3] = parseCore(bCore);
|
|
11828
|
+
if (a1 !== b1) return a1 - b1;
|
|
11829
|
+
if (a2 !== b2) return a2 - b2;
|
|
11830
|
+
if (a3 !== b3) return a3 - b3;
|
|
11831
|
+
if (!aPre && bPre) return 1;
|
|
11832
|
+
if (aPre && !bPre) return -1;
|
|
11833
|
+
if (!aPre && !bPre) return 0;
|
|
11834
|
+
return aPre.localeCompare(bPre);
|
|
11835
|
+
}
|
|
11836
|
+
function splitPre(v) {
|
|
11837
|
+
const i = v.indexOf("-");
|
|
11838
|
+
return i < 0 ? [v, null] : [v.slice(0, i), v.slice(i + 1)];
|
|
11839
|
+
}
|
|
11840
|
+
function parseCore(core) {
|
|
11841
|
+
const parts = core.split(".").map((s) => parseInt(s, 10) || 0);
|
|
11842
|
+
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
11843
|
+
}
|
|
11844
|
+
function ghHeaders(token) {
|
|
11845
|
+
const headers = {
|
|
11846
|
+
"User-Agent": "dimina-kit-updater",
|
|
11847
|
+
"Accept": "application/vnd.github+json",
|
|
11848
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
11849
|
+
};
|
|
11850
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
11851
|
+
return headers;
|
|
11852
|
+
}
|
|
11853
|
+
async function fetchRelease(url, opts) {
|
|
11854
|
+
const body = await httpGetText(url, { headers: ghHeaders(opts.token) });
|
|
11855
|
+
if (!body) return null;
|
|
11856
|
+
if (opts.includePrereleases) {
|
|
11857
|
+
const list = JSON.parse(body);
|
|
11858
|
+
const first = list.find((r) => !r.draft);
|
|
11859
|
+
return first ?? null;
|
|
11860
|
+
}
|
|
11861
|
+
const release = JSON.parse(body);
|
|
11862
|
+
return release.draft ? null : release;
|
|
11863
|
+
}
|
|
11864
|
+
function httpGetText(url, init) {
|
|
11865
|
+
return new Promise((resolve, reject) => {
|
|
11866
|
+
const req = https.get(url, { headers: init.headers }, (res) => {
|
|
11867
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
11868
|
+
res.resume();
|
|
11869
|
+
resolve(httpGetText(res.headers.location, init));
|
|
11870
|
+
return;
|
|
11871
|
+
}
|
|
11872
|
+
if (res.statusCode !== 200) {
|
|
11873
|
+
res.resume();
|
|
11874
|
+
resolve(null);
|
|
11875
|
+
return;
|
|
11876
|
+
}
|
|
11877
|
+
let body = "";
|
|
11878
|
+
res.setEncoding("utf8");
|
|
11879
|
+
res.on("data", (chunk) => {
|
|
11880
|
+
body += chunk;
|
|
11881
|
+
});
|
|
11882
|
+
res.on("end", () => resolve(body));
|
|
11883
|
+
});
|
|
11884
|
+
req.on("error", reject);
|
|
11885
|
+
});
|
|
11886
|
+
}
|
|
11887
|
+
function downloadFile(url, dest, token, onProgress) {
|
|
11888
|
+
return new Promise((resolve, reject) => {
|
|
11889
|
+
const headers = ghHeaders(token);
|
|
11890
|
+
const cleanup = () => fs12.promises.unlink(dest).catch(() => {
|
|
11891
|
+
});
|
|
11892
|
+
const attempt = (target, redirectsLeft) => {
|
|
11893
|
+
const req = https.get(target, { headers }, (res) => {
|
|
11894
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
11895
|
+
res.resume();
|
|
11896
|
+
if (redirectsLeft <= 0) {
|
|
11897
|
+
reject(new Error("Too many redirects"));
|
|
11898
|
+
return;
|
|
11899
|
+
}
|
|
11900
|
+
attempt(res.headers.location, redirectsLeft - 1);
|
|
11901
|
+
return;
|
|
11902
|
+
}
|
|
11903
|
+
if (res.statusCode !== 200) {
|
|
11904
|
+
res.resume();
|
|
11905
|
+
reject(new Error(`HTTP ${res.statusCode} while downloading update`));
|
|
11906
|
+
return;
|
|
11907
|
+
}
|
|
11908
|
+
const total = parseInt(res.headers["content-length"] || "0", 10);
|
|
11909
|
+
let received = 0;
|
|
11910
|
+
const out = fs12.createWriteStream(dest);
|
|
11911
|
+
res.on("data", (chunk) => {
|
|
11912
|
+
received += chunk.length;
|
|
11913
|
+
if (total > 0 && onProgress) {
|
|
11914
|
+
onProgress(received / total * 100);
|
|
11915
|
+
}
|
|
11916
|
+
});
|
|
11917
|
+
res.on("error", async (err2) => {
|
|
11918
|
+
out.destroy();
|
|
11919
|
+
await cleanup();
|
|
11920
|
+
reject(err2);
|
|
11921
|
+
});
|
|
11922
|
+
res.pipe(out);
|
|
11923
|
+
out.on("finish", async () => {
|
|
11924
|
+
out.close();
|
|
11925
|
+
if (total > 0 && received !== total) {
|
|
11926
|
+
await cleanup();
|
|
11927
|
+
reject(new Error(`Downloaded ${received} bytes, expected ${total} \u2014 update download was truncated`));
|
|
11928
|
+
return;
|
|
11929
|
+
}
|
|
11930
|
+
resolve();
|
|
11931
|
+
});
|
|
11932
|
+
out.on("error", async (err2) => {
|
|
11933
|
+
await cleanup();
|
|
11934
|
+
reject(err2);
|
|
11935
|
+
});
|
|
11936
|
+
});
|
|
11937
|
+
req.on("error", async (err2) => {
|
|
11938
|
+
await cleanup();
|
|
11939
|
+
reject(err2);
|
|
11940
|
+
});
|
|
11941
|
+
};
|
|
11942
|
+
attempt(url, 5);
|
|
11943
|
+
});
|
|
11944
|
+
}
|
|
11685
11945
|
|
|
11686
11946
|
// src/main/app/app.ts
|
|
11687
11947
|
import { toDisposable as toDisposable8 } from "@dimina-kit/electron-deck/main";
|
|
@@ -11752,7 +12012,7 @@ async function disposeContext(ctx) {
|
|
|
11752
12012
|
function createConfiguredMainWindow(config, rendererDir2) {
|
|
11753
12013
|
const mainWindow = createMainWindow({
|
|
11754
12014
|
title: config.appName ?? "Dimina DevTools",
|
|
11755
|
-
indexHtml:
|
|
12015
|
+
indexHtml: path25.join(rendererDir2, "entries/main/index.html"),
|
|
11756
12016
|
width: config.window?.width,
|
|
11757
12017
|
height: config.window?.height,
|
|
11758
12018
|
minWidth: config.window?.minWidth,
|
|
@@ -11865,7 +12125,7 @@ function enableDevRendererAutoReload(rendererDir2) {
|
|
|
11865
12125
|
});
|
|
11866
12126
|
}
|
|
11867
12127
|
let reloadTimer = null;
|
|
11868
|
-
const watcher =
|
|
12128
|
+
const watcher = fs13.watch(rendererDir2, { recursive: true }, () => {
|
|
11869
12129
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
11870
12130
|
reloadTimer = setTimeout(() => {
|
|
11871
12131
|
for (const win of BrowserWindow8.getAllWindows()) {
|
|
@@ -11895,7 +12155,7 @@ function runDevtoolsBootstrap(config = {}) {
|
|
|
11895
12155
|
} catch {
|
|
11896
12156
|
}
|
|
11897
12157
|
}
|
|
11898
|
-
async function createDevtoolsRuntime(config = {}) {
|
|
12158
|
+
async function createDevtoolsRuntime(config = {}, onInstanceCreated) {
|
|
11899
12159
|
await app15.whenReady();
|
|
11900
12160
|
applyTheme(loadWorkbenchSettings().theme);
|
|
11901
12161
|
const rendererDir2 = config.rendererDir ?? rendererDir;
|
|
@@ -11924,6 +12184,7 @@ async function createDevtoolsRuntime(config = {}) {
|
|
|
11924
12184
|
registerSimulatorApi: (name, handler) => context.registry.add(toDisposable8(context.simulatorApis.register(name, handler))),
|
|
11925
12185
|
dispose: () => disposeContext(context)
|
|
11926
12186
|
};
|
|
12187
|
+
onInstanceCreated?.(instance);
|
|
11927
12188
|
instance.registerSimulatorApi("login", async () => {
|
|
11928
12189
|
return "hello";
|
|
11929
12190
|
});
|
|
@@ -12036,8 +12297,8 @@ async function createDevtoolsRuntime(config = {}) {
|
|
|
12036
12297
|
bridge: context.bridge
|
|
12037
12298
|
}));
|
|
12038
12299
|
}
|
|
12039
|
-
const bundleDir = config.editorViewConfig?.bundleDir ??
|
|
12040
|
-
if (!
|
|
12300
|
+
const bundleDir = config.editorViewConfig?.bundleDir ?? path25.join(devtoolsPackageRoot, "dist/vscode-workbench");
|
|
12301
|
+
if (!fs13.existsSync(path25.join(bundleDir, "index.html"))) {
|
|
12041
12302
|
console.warn(
|
|
12042
12303
|
`[workbench] editor bundle not found at ${bundleDir} \u2014 skipping embedded editor assembly`
|
|
12043
12304
|
);
|
|
@@ -12074,6 +12335,7 @@ async function createDevtoolsRuntime(config = {}) {
|
|
|
12074
12335
|
// src/main/runtime/devtools-backend.ts
|
|
12075
12336
|
function createDevtoolsBackend(config = {}) {
|
|
12076
12337
|
let instance = null;
|
|
12338
|
+
let assembling = null;
|
|
12077
12339
|
return {
|
|
12078
12340
|
// The backend builds the devtools main window itself (framework skips its own).
|
|
12079
12341
|
// Its only construction-time needs are the host preload + `sandbox:false`
|
|
@@ -12095,15 +12357,33 @@ function createDevtoolsBackend(config = {}) {
|
|
|
12095
12357
|
// Setup (post-whenReady): app lifecycle (window-all-closed → quit) + the
|
|
12096
12358
|
// full devtools assembly.
|
|
12097
12359
|
assemble: async () => {
|
|
12098
|
-
registerAppLifecycle();
|
|
12099
|
-
|
|
12360
|
+
registerAppLifecycle(() => instance?.context.views.disposeAll());
|
|
12361
|
+
assembling = createDevtoolsRuntime(config, (created) => {
|
|
12362
|
+
instance = created;
|
|
12363
|
+
if (isAppQuitting()) instance.context.views.disposeAll();
|
|
12364
|
+
}).then(() => {
|
|
12365
|
+
});
|
|
12366
|
+
await assembling;
|
|
12100
12367
|
},
|
|
12101
12368
|
// Dispose the devtools context during the framework's deterministic shutdown
|
|
12102
12369
|
// (app.on('will-quit') → shutdown() → runShutdownCleanup(), awaited once).
|
|
12103
12370
|
// Without this, the compile session (a child process, torn down by
|
|
12104
12371
|
// closeProject) and the IPC registry would leak on exit. The framework awaits
|
|
12105
12372
|
// this hook, so teardown is no longer a best-effort before-quit reach-around.
|
|
12106
|
-
|
|
12373
|
+
//
|
|
12374
|
+
// Waits for `assembling` first: `instance` can be non-null (published
|
|
12375
|
+
// early, above) while `createDevtoolsRuntime`'s own async body — the
|
|
12376
|
+
// host's `onSetup` and everything scheduled after it — is still running
|
|
12377
|
+
// and still adding entries to `instance.context.registry`. Disposing
|
|
12378
|
+
// that registry out from under an in-flight assembly throws instead of
|
|
12379
|
+
// tearing down cleanly (`DisposableRegistry.add` rejects post-dispose).
|
|
12380
|
+
// `.catch()` here: assembly failing is `assemble`'s problem to surface,
|
|
12381
|
+
// not a reason to skip disposing whatever DID get constructed.
|
|
12382
|
+
onShutdown: async () => {
|
|
12383
|
+
await assembling?.catch(() => {
|
|
12384
|
+
});
|
|
12385
|
+
await instance?.dispose();
|
|
12386
|
+
}
|
|
12107
12387
|
};
|
|
12108
12388
|
}
|
|
12109
12389
|
|
|
@@ -12113,7 +12393,18 @@ function launch(config = {}) {
|
|
|
12113
12393
|
}
|
|
12114
12394
|
|
|
12115
12395
|
// src/main/index.ts
|
|
12116
|
-
|
|
12396
|
+
var updateChecker = app16.isPackaged && !app16.getVersion().includes("-dev.") ? createGitHubReleaseChecker({
|
|
12397
|
+
owner: "EchoTechFE",
|
|
12398
|
+
repo: "dimina-kit",
|
|
12399
|
+
parseVersion: (release) => {
|
|
12400
|
+
for (const asset of release.assets) {
|
|
12401
|
+
const match = /^dimina-devtools-(\d+\.\d+\.\d+)-/.exec(asset.name);
|
|
12402
|
+
if (match) return match[1];
|
|
12403
|
+
}
|
|
12404
|
+
return null;
|
|
12405
|
+
}
|
|
12406
|
+
}) : void 0;
|
|
12407
|
+
launch({ updateChecker }).catch((err2) => {
|
|
12117
12408
|
console.error("[devtools] fatal: launch() failed during boot", err2);
|
|
12118
12409
|
app16.exit(1);
|
|
12119
12410
|
});
|
package/dist/main/index.js
CHANGED
|
@@ -1,10 +1,58 @@
|
|
|
1
1
|
import { app } from 'electron';
|
|
2
2
|
import { launch } from './app/launch.js';
|
|
3
|
+
import { createGitHubReleaseChecker } from './services/update/index.js';
|
|
4
|
+
// Only a packaged RELEASE-channel build self-updates against this repo's
|
|
5
|
+
// GitHub Releases:
|
|
6
|
+
// - `pnpm dev` runs stay quiet (no API calls, no update dialog) — same
|
|
7
|
+
// app.isPackaged gate used elsewhere for dev-only behavior.
|
|
8
|
+
// - A packaged DEV-channel build (release.yml's `channel: dev` — distributed
|
|
9
|
+
// only as a 30-day Actions artifact for QA/branch testing, never a GitHub
|
|
10
|
+
// Release) must NOT get this either: release.yml's dev channel never
|
|
11
|
+
// creates a GitHub Release, so /releases/latest always reflects the
|
|
12
|
+
// RELEASE channel — a dev build would be prompted to "update" to an
|
|
13
|
+
// unrelated stable build, and clicking it would silently swap out the
|
|
14
|
+
// very branch/PR build the person downloaded it to test. dev-channel
|
|
15
|
+
// versions always carry bump-dev-version.js's `-dev.<timestamp>` suffix;
|
|
16
|
+
// release-channel versions never do — use that as the channel signal.
|
|
17
|
+
// - Hosts that embed devtools via `launch(config)` supply their own
|
|
18
|
+
// `updateChecker` (or none) and are unaffected — this default only
|
|
19
|
+
// applies to this entry point.
|
|
20
|
+
//
|
|
21
|
+
// Custom parseVersion, NOT the default asset-name fallback and NOT
|
|
22
|
+
// 'trailing-number':
|
|
23
|
+
// - release.yml's GitHub Release tag (`release-YYYYMMDD-N`) is an unrelated
|
|
24
|
+
// release-sequence label, not this app's version — 'trailing-number' would
|
|
25
|
+
// compare its bare counter against app.getVersion() (e.g. "0.4.0")
|
|
26
|
+
// numerically, which looks "newer" once the counter passes the app's major
|
|
27
|
+
// version, reporting an update on every single check forever.
|
|
28
|
+
// - The built-in asset-name fallback (defaultParseVersion's SEMVER_RE) finds
|
|
29
|
+
// the version inside `dimina-devtools-0.4.0-mac-arm64.dmg` (see release.yml's
|
|
30
|
+
// "Rename macOS dmg" / Archive steps) but its optional prerelease-suffix
|
|
31
|
+
// capture greedily swallows the trailing `-mac-arm64.dmg` as a "prerelease"
|
|
32
|
+
// tag, so the update dialog would show a garbled "0.5.0-mac-arm64.dmg".
|
|
33
|
+
// Extract just the clean `x.y.z` from our own known asset naming instead.
|
|
34
|
+
// Short-circuited: app.getVersion() must only be read once isPackaged is
|
|
35
|
+
// already known true, both because it's meaningless before then and because
|
|
36
|
+
// some minimal test/dev electron mocks don't stub getVersion() at all.
|
|
37
|
+
const updateChecker = app.isPackaged && !app.getVersion().includes('-dev.')
|
|
38
|
+
? createGitHubReleaseChecker({
|
|
39
|
+
owner: 'EchoTechFE',
|
|
40
|
+
repo: 'dimina-kit',
|
|
41
|
+
parseVersion: (release) => {
|
|
42
|
+
for (const asset of release.assets) {
|
|
43
|
+
const match = /^dimina-devtools-(\d+\.\d+\.\d+)-/.exec(asset.name);
|
|
44
|
+
if (match)
|
|
45
|
+
return match[1];
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
: undefined;
|
|
3
51
|
// Fire-and-forget boot: launch() returns the electron-deck whenReady gate
|
|
4
52
|
// promise. A bare call would let a boot failure escape as an unhandledRejection
|
|
5
53
|
// (no diagnostics, can be swallowed). Surface it via a structured failure exit:
|
|
6
54
|
// log the cause and exit non-zero (per workbench-model.md fire-and-forget rule).
|
|
7
|
-
launch().catch((err) => {
|
|
55
|
+
launch({ updateChecker }).catch((err) => {
|
|
8
56
|
console.error('[devtools] fatal: launch() failed during boot', err);
|
|
9
57
|
app.exit(1);
|
|
10
58
|
});
|