@dimina-kit/devtools 0.3.2-dev.20260524104239 → 0.3.2-dev.20260601115343
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/README.md +20 -7
- package/dist/main/api.d.ts +1 -1
- package/dist/main/app/app.js +30 -2
- package/dist/main/app/bootstrap.d.ts +9 -0
- package/dist/main/app/bootstrap.js +28 -1
- package/dist/main/index.bundle.js +1468 -496
- package/dist/main/ipc/project-fs.d.ts +57 -0
- package/dist/main/ipc/project-fs.js +448 -0
- package/dist/main/ipc/simulator-module.js +2 -0
- package/dist/main/ipc/views.d.ts +19 -0
- package/dist/main/ipc/views.js +27 -0
- package/dist/main/services/layout/index.d.ts +1 -1
- package/dist/main/services/layout/index.js +3 -3
- package/dist/main/services/simulator-temp-files/disk.d.ts +55 -0
- package/dist/main/services/simulator-temp-files/disk.js +215 -0
- package/dist/main/services/simulator-temp-files/fs-channels.d.ts +69 -0
- package/dist/main/services/simulator-temp-files/fs-channels.js +108 -0
- package/dist/main/services/simulator-temp-files/index.d.ts +33 -0
- package/dist/main/services/simulator-temp-files/index.js +160 -0
- package/dist/main/services/simulator-temp-files/request-handler.d.ts +30 -0
- package/dist/main/services/simulator-temp-files/request-handler.js +178 -0
- package/dist/main/services/simulator-temp-files/resolver.d.ts +22 -0
- package/dist/main/services/simulator-temp-files/resolver.js +22 -0
- package/dist/main/services/simulator-temp-files/store.d.ts +14 -0
- package/dist/main/services/simulator-temp-files/store.js +24 -0
- package/dist/main/services/toolbar/toolbar-store.d.ts +3 -3
- package/dist/main/services/toolbar/toolbar-store.js +18 -1
- package/dist/main/services/update/update-manager.d.ts +10 -0
- package/dist/main/services/update/update-manager.js +16 -3
- package/dist/main/services/views/view-manager.d.ts +17 -4
- package/dist/main/services/views/view-manager.js +71 -14
- package/dist/main/services/workbench-context.d.ts +6 -1
- package/dist/main/services/workspace/workspace-service.d.ts +8 -0
- package/dist/main/services/workspace/workspace-service.js +22 -4
- package/dist/main/utils/ipc-registry.d.ts +10 -1
- package/dist/main/utils/ipc-registry.js +43 -0
- package/dist/preload/runtime/temp-files.d.ts +8 -0
- package/dist/preload/runtime/temp-files.js +34 -0
- package/dist/preload/shared/expose.js +2 -2
- package/dist/preload/windows/main.cjs +4 -0
- package/dist/preload/windows/main.cjs.map +2 -2
- package/dist/preload/windows/simulator.js +40 -8
- package/dist/renderer/assets/abap-DiwvWnMr.js +2 -0
- package/dist/renderer/assets/apex-CmtZjKlf.js +2 -0
- package/dist/renderer/assets/azcli-DL2My_i-.js +2 -0
- package/dist/renderer/assets/bat-B-nC98wG.js +2 -0
- package/dist/renderer/assets/bicep-Ju5MwOgh.js +3 -0
- package/dist/renderer/assets/cameligo-8Eu1TyBr.js +2 -0
- package/dist/renderer/assets/chunk-DPI0Y_ll.js +1 -0
- package/dist/renderer/assets/clojure-u-RpMkH3.js +2 -0
- package/dist/renderer/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/renderer/assets/coffee-CdA7bbTe.js +2 -0
- package/dist/renderer/assets/constants-CLAQX6VZ.js +2 -0
- package/dist/renderer/assets/cpp-CzNFP8ks.js +2 -0
- package/dist/renderer/assets/csharp-j1LThmcE.js +2 -0
- package/dist/renderer/assets/csp-CLRC61y6.js +2 -0
- package/dist/renderer/assets/css-r6rC_7P2.js +4 -0
- package/dist/renderer/assets/css.worker-DyDu5ynT.js +90 -0
- package/dist/renderer/assets/cssMode-BCBgxrnA.js +2 -0
- package/dist/renderer/assets/cypher-CW08XVUh.js +2 -0
- package/dist/renderer/assets/dart-Cs9aL5T_.js +2 -0
- package/dist/renderer/assets/dockerfile-BWM0M184.js +2 -0
- package/dist/renderer/assets/ecl-MJJuer5P.js +2 -0
- package/dist/renderer/assets/editor-CWI9TnMM.css +1 -0
- package/dist/renderer/assets/editor.api2-BTaYmRq7.js +873 -0
- package/dist/renderer/assets/editor.worker-Dpt_xPrs.js +27 -0
- package/dist/renderer/assets/elixir-D2AIuXqn.js +2 -0
- package/dist/renderer/assets/flow9-B2H24giC.js +2 -0
- package/dist/renderer/assets/freemarker2-DGDGzimW.js +4 -0
- package/dist/renderer/assets/fsharp-CMk2OIJN.js +2 -0
- package/dist/renderer/assets/go-BrMkuJg0.js +2 -0
- package/dist/renderer/assets/graphql-PSR1UKGv.js +2 -0
- package/dist/renderer/assets/handlebars-bOHhw3EG.js +2 -0
- package/dist/renderer/assets/hcl-DAQrbDOW.js +2 -0
- package/dist/renderer/assets/html-BMyuX0_c.js +2 -0
- package/dist/renderer/assets/html.worker-IWNXhnIC.js +503 -0
- package/dist/renderer/assets/htmlMode-DR4kXJ7l.js +2 -0
- package/dist/renderer/assets/index-BDMxXUfn.js +50 -0
- package/dist/renderer/assets/ini-0TG5BxW0.js +2 -0
- package/dist/renderer/assets/input-CCYS8Ave.js +2 -0
- package/dist/renderer/assets/{ipc-transport-BzPzxPwf.js → ipc-transport-Bn4KXvhu.js} +5 -5
- package/dist/renderer/assets/ipc-transport-Cu7tubyg.css +1 -0
- package/dist/renderer/assets/java-rgorz17v.js +2 -0
- package/dist/renderer/assets/javascript-Bu-tl1P7.js +2 -0
- package/dist/renderer/assets/json.worker-c4h5s80L.js +59 -0
- package/dist/renderer/assets/jsonMode-MQV_ffLR.js +8 -0
- package/dist/renderer/assets/julia-C8VMdHm8.js +2 -0
- package/dist/renderer/assets/kotlin-AhhC4Qhm.js +2 -0
- package/dist/renderer/assets/less-Cgca25AP.js +3 -0
- package/dist/renderer/assets/lexon-D0GHdBaw.js +2 -0
- package/dist/renderer/assets/liquid-ClkfOvhB.js +2 -0
- package/dist/renderer/assets/lspLanguageFeatures-Bke_4Ie9.js +5 -0
- package/dist/renderer/assets/lua-DmRsNG-P.js +2 -0
- package/dist/renderer/assets/m3-BgL5dNKT.js +2 -0
- package/dist/renderer/assets/markdown-BuJfycGS.js +2 -0
- package/dist/renderer/assets/mdx-RZdUskPu.js +2 -0
- package/dist/renderer/assets/mips-C9m_93PR.js +2 -0
- package/dist/renderer/assets/monaco.contribution-B7rUMaEv.js +3 -0
- package/dist/renderer/assets/msdax-CpFHC9OI.js +2 -0
- package/dist/renderer/assets/mysql-qFvltsqN.js +2 -0
- package/dist/renderer/assets/objective-c-Bnmr858J.js +2 -0
- package/dist/renderer/assets/pascal-WP0_D5AO.js +2 -0
- package/dist/renderer/assets/pascaligo-Blom4Rij.js +2 -0
- package/dist/renderer/assets/perl-B-vk8g64.js +2 -0
- package/dist/renderer/assets/pgsql-Cgvz6v67.js +2 -0
- package/dist/renderer/assets/php-8a3Lrw9m.js +2 -0
- package/dist/renderer/assets/pla-DuFqEZ8V.js +2 -0
- package/dist/renderer/assets/popover-CH4rwpdt.js +2 -0
- package/dist/renderer/assets/postiats-DkLtSgkp.js +2 -0
- package/dist/renderer/assets/powerquery-BJ1aNepW.js +2 -0
- package/dist/renderer/assets/powershell-rE98k687.js +2 -0
- package/dist/renderer/assets/protobuf-CUheFacr.js +3 -0
- package/dist/renderer/assets/pug-LDcAMD8w.js +2 -0
- package/dist/renderer/assets/python-Cobrm_-8.js +2 -0
- package/dist/renderer/assets/qsharp-DUKSQoR1.js +2 -0
- package/dist/renderer/assets/r-D-QApv87.js +2 -0
- package/dist/renderer/assets/razor-BEUIQFNJ.js +2 -0
- package/dist/renderer/assets/redis-SXdDyWR9.js +2 -0
- package/dist/renderer/assets/redshift-Y6lsCryn.js +2 -0
- package/dist/renderer/assets/restructuredtext-edObr9a8.js +2 -0
- package/dist/renderer/assets/ruby-CNnUfF-8.js +2 -0
- package/dist/renderer/assets/rust-IHUZWzBr.js +2 -0
- package/dist/renderer/assets/sb-DrUvY44N.js +2 -0
- package/dist/renderer/assets/scala-B4hbXGLM.js +2 -0
- package/dist/renderer/assets/scheme-BGrd12j3.js +2 -0
- package/dist/renderer/assets/scss-x5G1ES4U.js +4 -0
- package/dist/renderer/assets/select-Ct4-sijm.js +2 -0
- package/dist/renderer/assets/settings-CMTB-t0x.js +2 -0
- package/dist/renderer/assets/{settings-api-BXCw9vOZ.js → settings-api-Csdngf9Z.js} +2 -2
- package/dist/renderer/assets/shell-DOehe2Y8.js +2 -0
- package/dist/renderer/assets/solidity-BeRvcwWV.js +2 -0
- package/dist/renderer/assets/sophia-DZbkUNjy.js +2 -0
- package/dist/renderer/assets/sparql-B7_oi5-h.js +2 -0
- package/dist/renderer/assets/sql-CTlsFWVE.js +2 -0
- package/dist/renderer/assets/st-DJVEJdPE.js +2 -0
- package/dist/renderer/assets/swift-CwhT3fYa.js +2 -0
- package/dist/renderer/assets/systemverilog-BQN63pkN.js +2 -0
- package/dist/renderer/assets/tcl-DqwfpskA.js +2 -0
- package/dist/renderer/assets/ts.worker-BwM5Ha3L.js +67720 -0
- package/dist/renderer/assets/tsMode-BHa7dl9v.js +12 -0
- package/dist/renderer/assets/twig-BiyenUgc.js +2 -0
- package/dist/renderer/assets/typescript-15aT0Yj8.js +2 -0
- package/dist/renderer/assets/typespec-CWOJribt.js +2 -0
- package/dist/renderer/assets/vb-Cq5F87m3.js +2 -0
- package/dist/renderer/assets/wgsl-BAvW2lVr.js +299 -0
- package/dist/renderer/assets/workbenchSettings-CODFCzOg.js +8 -0
- package/dist/renderer/assets/workers-CRBS0W-v.js +2 -0
- package/dist/renderer/assets/xml-bR2Zh-HV.js +2 -0
- package/dist/renderer/assets/yaml-Bdk54Qrb.js +2 -0
- package/dist/renderer/entries/main/index.html +29 -6
- package/dist/renderer/entries/popover/index.html +7 -6
- package/dist/renderer/entries/settings/index.html +6 -5
- package/dist/renderer/entries/workbench-settings/index.html +6 -5
- package/dist/shared/constants.d.ts +0 -4
- package/dist/shared/constants.js +0 -4
- package/dist/shared/ipc-channels.d.ts +24 -0
- package/dist/shared/ipc-channels.js +37 -0
- package/dist/shared/ipc-schemas.d.ts +6 -0
- package/dist/shared/ipc-schemas.js +15 -0
- package/dist/shared/types.d.ts +37 -0
- package/dist/shared/vpath.d.ts +42 -0
- package/dist/shared/vpath.js +105 -0
- package/dist/simulator/assets/simulator-B4e3c6_O.js +9 -0
- package/dist/simulator/simulator.html +1 -1
- package/package.json +5 -4
- package/dist/renderer/assets/constants-DI382mTP.js +0 -2
- package/dist/renderer/assets/index-4lkyF644.js +0 -46
- package/dist/renderer/assets/input-D4byFOLS.js +0 -2
- package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +0 -1
- package/dist/renderer/assets/popover-cxKrg_os.js +0 -2
- package/dist/renderer/assets/select-BgSkkdHW.js +0 -2
- package/dist/renderer/assets/settings-oD2K1jdT.js +0 -2
- package/dist/renderer/assets/workbenchSettings-CTNBpTft.js +0 -8
- package/dist/simulator/assets/simulator-DDRBJ6Wa.js +0 -9
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/main/app/bootstrap.ts
|
|
2
|
-
import { app as app2 } from "electron";
|
|
2
|
+
import { app as app2, protocol } from "electron";
|
|
3
3
|
|
|
4
4
|
// src/shared/constants.ts
|
|
5
5
|
var DEFAULT_CDP_PORT = 9222;
|
|
@@ -63,6 +63,24 @@ function applyTheme(theme) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// src/main/app/bootstrap.ts
|
|
66
|
+
var difileSchemeRegistered = false;
|
|
67
|
+
function registerDifileScheme() {
|
|
68
|
+
if (difileSchemeRegistered) return;
|
|
69
|
+
difileSchemeRegistered = true;
|
|
70
|
+
protocol.registerSchemesAsPrivileged([
|
|
71
|
+
{
|
|
72
|
+
scheme: "difile",
|
|
73
|
+
privileges: {
|
|
74
|
+
standard: true,
|
|
75
|
+
secure: true,
|
|
76
|
+
supportFetchAPI: true,
|
|
77
|
+
stream: true,
|
|
78
|
+
bypassCSP: true,
|
|
79
|
+
corsEnabled: true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
66
84
|
function setupCdpPort() {
|
|
67
85
|
if (app2.commandLine.getSwitchValue("remote-debugging-port")) return;
|
|
68
86
|
const envPort = process.env.DIMINA_DEBUG_PORT;
|
|
@@ -78,101 +96,675 @@ function setupCdpPort() {
|
|
|
78
96
|
}
|
|
79
97
|
}
|
|
80
98
|
|
|
99
|
+
// src/main/ipc/project-fs.ts
|
|
100
|
+
import fs2 from "node:fs/promises";
|
|
101
|
+
import nodeFs from "node:fs";
|
|
102
|
+
import path2 from "node:path";
|
|
103
|
+
import { z } from "zod";
|
|
104
|
+
|
|
105
|
+
// src/main/utils/ipc-registry.ts
|
|
106
|
+
import { ipcMain } from "electron";
|
|
107
|
+
|
|
108
|
+
// src/main/utils/disposable.ts
|
|
109
|
+
function toDisposable(fn) {
|
|
110
|
+
let done = false;
|
|
111
|
+
return {
|
|
112
|
+
dispose() {
|
|
113
|
+
if (done) return;
|
|
114
|
+
done = true;
|
|
115
|
+
return fn();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
var DisposableRegistry = class {
|
|
120
|
+
entries = [];
|
|
121
|
+
_disposed = false;
|
|
122
|
+
/**
|
|
123
|
+
* Number of live entries. A wrapper's `dispose` splices its entry out and
|
|
124
|
+
* `disposeAll` clears the array, so `entries.length` is the live count.
|
|
125
|
+
*/
|
|
126
|
+
get size() {
|
|
127
|
+
return this.entries.length;
|
|
128
|
+
}
|
|
129
|
+
add(d) {
|
|
130
|
+
if (this._disposed) {
|
|
131
|
+
throw new Error("cannot add to disposed registry");
|
|
132
|
+
}
|
|
133
|
+
const fn = typeof d === "function" ? d : () => d.dispose();
|
|
134
|
+
const entry = { fn, released: false };
|
|
135
|
+
this.entries.push(entry);
|
|
136
|
+
return {
|
|
137
|
+
dispose: () => {
|
|
138
|
+
if (entry.released) return;
|
|
139
|
+
entry.released = true;
|
|
140
|
+
const i = this.entries.indexOf(entry);
|
|
141
|
+
if (i >= 0) this.entries.splice(i, 1);
|
|
142
|
+
return fn();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async disposeAll() {
|
|
147
|
+
if (this._disposed) return;
|
|
148
|
+
this._disposed = true;
|
|
149
|
+
const items = this.entries.slice().reverse();
|
|
150
|
+
this.entries = [];
|
|
151
|
+
const errors = [];
|
|
152
|
+
for (const entry of items) {
|
|
153
|
+
if (entry.released) continue;
|
|
154
|
+
entry.released = true;
|
|
155
|
+
try {
|
|
156
|
+
await entry.fn();
|
|
157
|
+
} catch (e) {
|
|
158
|
+
errors.push(e);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (errors.length > 0) {
|
|
162
|
+
throw new AggregateError(errors, "DisposableRegistry encountered errors during disposeAll");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
dispose() {
|
|
166
|
+
return this.disposeAll();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/main/utils/ipc-schema.ts
|
|
171
|
+
var IpcValidationError = class extends Error {
|
|
172
|
+
channel;
|
|
173
|
+
paths;
|
|
174
|
+
constructor(channel, issues) {
|
|
175
|
+
const paths = issues.map((i) => i.path.map((p) => String(p)).join(".") || "<root>");
|
|
176
|
+
super(`Invalid IPC arguments for ${channel}`);
|
|
177
|
+
this.name = "IpcValidationError";
|
|
178
|
+
this.channel = channel;
|
|
179
|
+
this.paths = paths;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
function validate(channel, schema, args) {
|
|
183
|
+
const r = schema.safeParse(args);
|
|
184
|
+
if (!r.success) {
|
|
185
|
+
console.warn("[ipc] schema reject", channel, r.error.issues);
|
|
186
|
+
throw new IpcValidationError(channel, r.error.issues);
|
|
187
|
+
}
|
|
188
|
+
return r.data;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/main/utils/logger.ts
|
|
192
|
+
var LEVEL_PRIORITY = {
|
|
193
|
+
debug: 0,
|
|
194
|
+
info: 1,
|
|
195
|
+
warn: 2,
|
|
196
|
+
error: 3
|
|
197
|
+
};
|
|
198
|
+
var LEVEL_PREFIX = {
|
|
199
|
+
debug: "[DEBUG]",
|
|
200
|
+
info: "[INFO]",
|
|
201
|
+
warn: "[WARN]",
|
|
202
|
+
error: "[ERROR]"
|
|
203
|
+
};
|
|
204
|
+
var currentLevel = "debug";
|
|
205
|
+
function shouldLog(level) {
|
|
206
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
|
|
207
|
+
}
|
|
208
|
+
function formatMessage(level, tag, message) {
|
|
209
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
210
|
+
return `${ts} ${LEVEL_PREFIX[level]} [${tag}] ${message}`;
|
|
211
|
+
}
|
|
212
|
+
function createLogger(tag) {
|
|
213
|
+
return {
|
|
214
|
+
debug(message, ...args) {
|
|
215
|
+
if (shouldLog("debug")) console.debug(formatMessage("debug", tag, message), ...args);
|
|
216
|
+
},
|
|
217
|
+
info(message, ...args) {
|
|
218
|
+
if (shouldLog("info")) console.info(formatMessage("info", tag, message), ...args);
|
|
219
|
+
},
|
|
220
|
+
warn(message, ...args) {
|
|
221
|
+
if (shouldLog("warn")) console.warn(formatMessage("warn", tag, message), ...args);
|
|
222
|
+
},
|
|
223
|
+
error(message, ...args) {
|
|
224
|
+
if (shouldLog("error")) console.error(formatMessage("error", tag, message), ...args);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/main/utils/ipc-registry.ts
|
|
230
|
+
var log = createLogger("ipc");
|
|
231
|
+
function reportListenerError(channel, err) {
|
|
232
|
+
if (err instanceof IpcValidationError) {
|
|
233
|
+
log.warn(`schema reject on '${channel}' at [${err.paths.join(", ")}]`);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (err instanceof Error) {
|
|
237
|
+
log.error(`listener threw on '${channel}': ${err.message}`, err.stack);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
log.error(`listener threw on '${channel}'`, err);
|
|
241
|
+
}
|
|
242
|
+
function isThenable(v) {
|
|
243
|
+
return v != null && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
|
|
244
|
+
}
|
|
245
|
+
function summarizeSender(sender) {
|
|
246
|
+
if (sender.isDestroyed()) return "<destroyed>";
|
|
247
|
+
const url = sender.getURL();
|
|
248
|
+
return `id=${sender.id} ${url.slice(0, 120)}`;
|
|
249
|
+
}
|
|
250
|
+
var IpcRegistry = class {
|
|
251
|
+
constructor(policy) {
|
|
252
|
+
this.policy = policy;
|
|
253
|
+
}
|
|
254
|
+
policy;
|
|
255
|
+
registry = new DisposableRegistry();
|
|
256
|
+
handle(channel, fn) {
|
|
257
|
+
const policy = this.policy;
|
|
258
|
+
const guarded = policy ? async (event, ...args) => {
|
|
259
|
+
const sender = event.sender;
|
|
260
|
+
if (!policy(sender)) {
|
|
261
|
+
console.warn(
|
|
262
|
+
`[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
|
|
263
|
+
);
|
|
264
|
+
throw new Error(`IPC sender rejected for channel ${channel}`);
|
|
265
|
+
}
|
|
266
|
+
return fn(event, ...args);
|
|
267
|
+
} : fn;
|
|
268
|
+
ipcMain.handle(channel, guarded);
|
|
269
|
+
this.registry.add(() => ipcMain.removeHandler(channel));
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Register a SYNCHRONOUS `ipcRenderer.sendSync` handler. Unlike {@link on}
|
|
274
|
+
* (fire-and-forget), the renderer is blocked until we set `event.returnValue`,
|
|
275
|
+
* so `fn` MUST be synchronous and return the value to hand back. The sender
|
|
276
|
+
* policy gates it like {@link handle}; a rejected sender or a thrown error
|
|
277
|
+
* yields a structured `{ ok: false, code, message }` returnValue instead of
|
|
278
|
+
* the silent drop `on` uses, so the blocked renderer always gets an answer.
|
|
279
|
+
*/
|
|
280
|
+
handleSync(channel, fn) {
|
|
281
|
+
const policy = this.policy;
|
|
282
|
+
const listener = (event, ...args) => {
|
|
283
|
+
try {
|
|
284
|
+
if (policy && !policy(event.sender)) {
|
|
285
|
+
console.warn(
|
|
286
|
+
`[ipc] sender rejected for channel '${channel}' (${summarizeSender(event.sender)})`
|
|
287
|
+
);
|
|
288
|
+
event.returnValue = {
|
|
289
|
+
ok: false,
|
|
290
|
+
code: "EREJECTED",
|
|
291
|
+
message: `IPC sender rejected for channel ${channel}`
|
|
292
|
+
};
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
event.returnValue = fn(event, ...args);
|
|
296
|
+
} catch (err) {
|
|
297
|
+
reportListenerError(channel, err);
|
|
298
|
+
const code = err?.code;
|
|
299
|
+
event.returnValue = {
|
|
300
|
+
ok: false,
|
|
301
|
+
code: typeof code === "string" ? code : "EUNKNOWN",
|
|
302
|
+
message: err instanceof Error ? err.message : String(err)
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
ipcMain.on(channel, listener);
|
|
307
|
+
this.registry.add(() => {
|
|
308
|
+
ipcMain.removeListener(channel, listener);
|
|
309
|
+
});
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
on(channel, fn) {
|
|
313
|
+
const policy = this.policy;
|
|
314
|
+
const raw = fn;
|
|
315
|
+
const safeInvoke = (event, args) => {
|
|
316
|
+
try {
|
|
317
|
+
const ret = raw(event, ...args);
|
|
318
|
+
if (isThenable(ret)) {
|
|
319
|
+
Promise.resolve(ret).catch((err) => reportListenerError(channel, err));
|
|
320
|
+
}
|
|
321
|
+
} catch (err) {
|
|
322
|
+
reportListenerError(channel, err);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
const guarded = policy ? (event, ...args) => {
|
|
326
|
+
const sender = event.sender;
|
|
327
|
+
if (!policy(sender)) {
|
|
328
|
+
console.warn(
|
|
329
|
+
`[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
|
|
330
|
+
);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
safeInvoke(event, args);
|
|
334
|
+
} : (event, ...args) => safeInvoke(event, args);
|
|
335
|
+
ipcMain.on(channel, guarded);
|
|
336
|
+
this.registry.add(() => {
|
|
337
|
+
ipcMain.removeListener(channel, guarded);
|
|
338
|
+
});
|
|
339
|
+
return this;
|
|
340
|
+
}
|
|
341
|
+
dispose() {
|
|
342
|
+
return this.registry.dispose();
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// src/shared/ipc-channels.ts
|
|
347
|
+
var SimulatorChannel = {
|
|
348
|
+
Attach: "simulator:attach",
|
|
349
|
+
Detach: "simulator:detach",
|
|
350
|
+
Resize: "simulator:resize",
|
|
351
|
+
SetVisible: "simulator:setVisible",
|
|
352
|
+
Console: "simulator:console"
|
|
353
|
+
};
|
|
354
|
+
var SimulatorCustomApiChannel = {
|
|
355
|
+
List: "simulator:custom-apis:list",
|
|
356
|
+
Invoke: "simulator:custom-apis:invoke"
|
|
357
|
+
};
|
|
358
|
+
var SimulatorStorageChannel = {
|
|
359
|
+
GetSnapshot: "simulator:storage:snapshot",
|
|
360
|
+
GetActivePrefix: "simulator:storage:activePrefix",
|
|
361
|
+
Set: "simulator:storage:set",
|
|
362
|
+
Remove: "simulator:storage:remove",
|
|
363
|
+
Clear: "simulator:storage:clear",
|
|
364
|
+
ClearAll: "simulator:storage:clearAll",
|
|
365
|
+
Event: "simulator:storage:event"
|
|
366
|
+
};
|
|
367
|
+
var SimulatorElementChannel = {
|
|
368
|
+
Inspect: "simulator:element:inspect",
|
|
369
|
+
Clear: "simulator:element:clear"
|
|
370
|
+
};
|
|
371
|
+
var WorkbenchSettingsChannel = {
|
|
372
|
+
Get: "workbenchSettings:get",
|
|
373
|
+
Save: "workbenchSettings:save",
|
|
374
|
+
SetTheme: "workbenchSettings:setTheme",
|
|
375
|
+
GetCdpStatus: "workbenchSettings:getCdpStatus",
|
|
376
|
+
GetMcpStatus: "workbenchSettings:getMcpStatus",
|
|
377
|
+
SetVisible: "workbenchSettings:setVisible",
|
|
378
|
+
Init: "workbenchSettings:init"
|
|
379
|
+
};
|
|
380
|
+
var ProjectChannel = {
|
|
381
|
+
Open: "project:open",
|
|
382
|
+
Close: "project:close",
|
|
383
|
+
GetPages: "project:getPages",
|
|
384
|
+
GetCompileConfig: "project:getCompileConfig",
|
|
385
|
+
SaveCompileConfig: "project:saveCompileConfig",
|
|
386
|
+
Status: "project:status",
|
|
387
|
+
CaptureThumbnail: "project:captureThumbnail",
|
|
388
|
+
GetThumbnail: "project:getThumbnail"
|
|
389
|
+
};
|
|
390
|
+
var ProjectFsChannel = {
|
|
391
|
+
GetRoot: "project:fs:getRoot",
|
|
392
|
+
ReadFile: "project:fs:readFile",
|
|
393
|
+
WriteFile: "project:fs:writeFile",
|
|
394
|
+
/**
|
|
395
|
+
* Synchronous (blocking) write, used ONLY by the editor's `beforeunload`
|
|
396
|
+
* flush: a hard window/app close tears the renderer down before an async
|
|
397
|
+
* `WriteFile` IPC can round-trip, so the last in-debounce-window edit would
|
|
398
|
+
* be lost. `sendSync` blocks page teardown until the bytes hit disk. Runs the
|
|
399
|
+
* SAME sandbox as `WriteFile` (see project-fs `writeFileSync`).
|
|
400
|
+
*/
|
|
401
|
+
WriteFileSync: "project:fs:writeFileSync",
|
|
402
|
+
ListFiles: "project:fs:listFiles"
|
|
403
|
+
};
|
|
404
|
+
var ProjectsChannel = {
|
|
405
|
+
List: "projects:list",
|
|
406
|
+
Add: "projects:add",
|
|
407
|
+
Remove: "projects:remove",
|
|
408
|
+
/** Phase 3: merged + sanitized template catalog for the create-project dialog. */
|
|
409
|
+
ListTemplates: "projects:listTemplates",
|
|
410
|
+
/** Phase 3: host-supplied create-project dialog hook (returns input or null). */
|
|
411
|
+
OpenCreateDialog: "projects:openCreateDialog",
|
|
412
|
+
/** Phase 3: server-side scaffold + register (delegates to create-project-service). */
|
|
413
|
+
Create: "projects:create",
|
|
414
|
+
/** Default values used to pre-fill the create-project dialog (baseDir). */
|
|
415
|
+
GetCreateDefaults: "projects:getCreateDefaults"
|
|
416
|
+
};
|
|
417
|
+
var DialogChannel = {
|
|
418
|
+
OpenDirectory: "dialog:openDirectory"
|
|
419
|
+
};
|
|
420
|
+
var PanelChannel = {
|
|
421
|
+
List: "panel:list",
|
|
422
|
+
Eval: "panel:eval",
|
|
423
|
+
Select: "panel:select",
|
|
424
|
+
SelectSimulator: "panel:selectSimulator"
|
|
425
|
+
};
|
|
426
|
+
var ViewChannel = {
|
|
427
|
+
/** Update the simulator DevTools view's bounds (or hide if w/h = 0). */
|
|
428
|
+
SimulatorDevtoolsBounds: "view:simulator:devtools-bounds"
|
|
429
|
+
};
|
|
430
|
+
var PopoverChannel = {
|
|
431
|
+
Show: "popover:show",
|
|
432
|
+
Hide: "popover:hide",
|
|
433
|
+
Relaunch: "popover:relaunch",
|
|
434
|
+
Closed: "popover:closed",
|
|
435
|
+
Init: "popover:init"
|
|
436
|
+
};
|
|
437
|
+
var ToolbarChannel = {
|
|
438
|
+
GetActions: "toolbar:getActions",
|
|
439
|
+
ActionsChanged: "toolbar:actionsChanged",
|
|
440
|
+
/** Invoke a toolbar action by id: `invoke(Invoke, actionId)`. */
|
|
441
|
+
Invoke: "toolbar:invoke"
|
|
442
|
+
};
|
|
443
|
+
var WindowChannel = {
|
|
444
|
+
NavigateBack: "window:navigateBack"
|
|
445
|
+
};
|
|
446
|
+
var AppChannel = {
|
|
447
|
+
GetPreloadPath: "app:getPreloadPath",
|
|
448
|
+
GetBranding: "app:getBranding",
|
|
449
|
+
GetHeaderHeight: "app:getHeaderHeight"
|
|
450
|
+
};
|
|
451
|
+
var AutomationChannel = {
|
|
452
|
+
GetPort: "automation:port"
|
|
453
|
+
};
|
|
454
|
+
var SettingsChannel = {
|
|
455
|
+
SetVisible: "settings:setVisible",
|
|
456
|
+
ConfigChanged: "settings:configChanged",
|
|
457
|
+
ProjectSettingsChanged: "settings:projectSettingsChanged",
|
|
458
|
+
Init: "settings:init",
|
|
459
|
+
Closed: "settings:closed",
|
|
460
|
+
Changed: "settings:changed"
|
|
461
|
+
};
|
|
462
|
+
var UpdateChannel = {
|
|
463
|
+
Check: "updates:check",
|
|
464
|
+
Download: "updates:download",
|
|
465
|
+
Install: "updates:install",
|
|
466
|
+
DownloadProgress: "updates:downloadProgress",
|
|
467
|
+
Available: "updates:available"
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// src/main/ipc/project-fs.ts
|
|
471
|
+
var LIST_FILE_LIMIT = 5e3;
|
|
472
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
473
|
+
"node_modules",
|
|
474
|
+
".git",
|
|
475
|
+
".svn",
|
|
476
|
+
".hg",
|
|
477
|
+
"dist",
|
|
478
|
+
"build",
|
|
479
|
+
".next",
|
|
480
|
+
".cache",
|
|
481
|
+
".turbo",
|
|
482
|
+
".parcel-cache",
|
|
483
|
+
".nuxt",
|
|
484
|
+
".output",
|
|
485
|
+
".vite"
|
|
486
|
+
]);
|
|
487
|
+
var PathArg = z.string().min(1);
|
|
488
|
+
var ReadFileSchema = z.tuple([PathArg]);
|
|
489
|
+
var WriteFileSchema = z.tuple([PathArg, z.string()]);
|
|
490
|
+
var ListFilesSchema = z.tuple([PathArg]);
|
|
491
|
+
function makeError(code, message) {
|
|
492
|
+
const err = new Error(message);
|
|
493
|
+
err.code = code;
|
|
494
|
+
return err;
|
|
495
|
+
}
|
|
496
|
+
function isWithin(parent, child) {
|
|
497
|
+
if (child === parent) return true;
|
|
498
|
+
const rel = path2.relative(parent, child);
|
|
499
|
+
return rel.length > 0 && !rel.startsWith(".." + path2.sep) && rel !== ".." && !path2.isAbsolute(rel);
|
|
500
|
+
}
|
|
501
|
+
function enforceWithinProjectRoot(abs, root) {
|
|
502
|
+
if (root === "") {
|
|
503
|
+
throw makeError("ENOACTIVE", "No active project \u2014 open a project before using the editor file system");
|
|
504
|
+
}
|
|
505
|
+
if (typeof abs !== "string" || abs.length === 0) {
|
|
506
|
+
throw makeError("EINVAL", "path must be a non-empty string");
|
|
507
|
+
}
|
|
508
|
+
if (abs.includes("\0") || root.includes("\0")) {
|
|
509
|
+
throw makeError("EINVAL", "path must not contain NUL bytes");
|
|
510
|
+
}
|
|
511
|
+
const resolvedRoot = path2.resolve(root);
|
|
512
|
+
const resolved = path2.resolve(abs);
|
|
513
|
+
if (!isWithin(resolvedRoot, resolved)) {
|
|
514
|
+
throw makeError("EACCES", `Path escapes project root: ${resolved}`);
|
|
515
|
+
}
|
|
516
|
+
return resolved;
|
|
517
|
+
}
|
|
518
|
+
function pickWriteRoot(absPath, roots) {
|
|
519
|
+
const resolved = path2.resolve(absPath);
|
|
520
|
+
if (roots.current !== "" && isWithin(path2.resolve(roots.current), resolved)) {
|
|
521
|
+
return roots.current;
|
|
522
|
+
}
|
|
523
|
+
if (roots.lastClosed !== "" && isWithin(path2.resolve(roots.lastClosed), resolved)) {
|
|
524
|
+
return roots.lastClosed;
|
|
525
|
+
}
|
|
526
|
+
if (roots.current === "" && roots.lastClosed === "") {
|
|
527
|
+
throw makeError("ENOACTIVE", "No active project \u2014 open a project before using the editor file system");
|
|
528
|
+
}
|
|
529
|
+
throw makeError("EACCES", `Path escapes project root: ${resolved}`);
|
|
530
|
+
}
|
|
531
|
+
async function resolveWithinProjectRoot(abs, root) {
|
|
532
|
+
const lexical = enforceWithinProjectRoot(abs, root);
|
|
533
|
+
const realRoot = await fs2.realpath(path2.resolve(root));
|
|
534
|
+
let realTarget;
|
|
535
|
+
try {
|
|
536
|
+
realTarget = await fs2.realpath(lexical);
|
|
537
|
+
} catch (err) {
|
|
538
|
+
if (err.code !== "ENOENT") throw err;
|
|
539
|
+
const parent = path2.dirname(lexical);
|
|
540
|
+
const realParent = await fs2.realpath(parent);
|
|
541
|
+
if (!isWithin(realRoot, realParent)) {
|
|
542
|
+
throw makeError("EACCES", `Path escapes project root: ${lexical}`);
|
|
543
|
+
}
|
|
544
|
+
return path2.join(realParent, path2.basename(lexical));
|
|
545
|
+
}
|
|
546
|
+
if (!isWithin(realRoot, realTarget)) {
|
|
547
|
+
throw makeError("EACCES", `Path escapes project root: ${realTarget}`);
|
|
548
|
+
}
|
|
549
|
+
return realTarget;
|
|
550
|
+
}
|
|
551
|
+
async function assertOpenedWithinRoot(safe, root) {
|
|
552
|
+
const realRoot = await fs2.realpath(path2.resolve(root));
|
|
553
|
+
const real = await fs2.realpath(safe);
|
|
554
|
+
if (!isWithin(realRoot, real)) {
|
|
555
|
+
throw makeError("EACCES", `Path escapes project root (mid-path race): ${real}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function readFile(root, p) {
|
|
559
|
+
const safe = await resolveWithinProjectRoot(p, root);
|
|
560
|
+
const fh = await fs2.open(safe, fs2.constants.O_RDONLY | fs2.constants.O_NOFOLLOW);
|
|
561
|
+
try {
|
|
562
|
+
await assertOpenedWithinRoot(safe, root);
|
|
563
|
+
return await fh.readFile("utf8");
|
|
564
|
+
} finally {
|
|
565
|
+
await fh.close();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
async function assertWritableAncestor(lexical, root) {
|
|
569
|
+
const realRoot = await fs2.realpath(path2.resolve(root));
|
|
570
|
+
let ancestor = path2.dirname(lexical);
|
|
571
|
+
for (; ; ) {
|
|
572
|
+
let realAncestor;
|
|
573
|
+
try {
|
|
574
|
+
realAncestor = await fs2.realpath(ancestor);
|
|
575
|
+
} catch (err) {
|
|
576
|
+
if (err.code !== "ENOENT") throw err;
|
|
577
|
+
const next = path2.dirname(ancestor);
|
|
578
|
+
if (next === ancestor) {
|
|
579
|
+
throw makeError("EACCES", `Path escapes project root: ${lexical}`);
|
|
580
|
+
}
|
|
581
|
+
ancestor = next;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (!isWithin(realRoot, realAncestor)) {
|
|
585
|
+
throw makeError("EACCES", `Path escapes project root: ${lexical}`);
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async function writeFile(root, p, content) {
|
|
591
|
+
const lexical = enforceWithinProjectRoot(p, root);
|
|
592
|
+
await assertWritableAncestor(lexical, root);
|
|
593
|
+
await fs2.mkdir(path2.dirname(lexical), { recursive: true });
|
|
594
|
+
const safe = await resolveWithinProjectRoot(p, root);
|
|
595
|
+
const fh = await fs2.open(
|
|
596
|
+
safe,
|
|
597
|
+
fs2.constants.O_WRONLY | fs2.constants.O_CREAT | fs2.constants.O_NOFOLLOW,
|
|
598
|
+
438
|
|
599
|
+
);
|
|
600
|
+
try {
|
|
601
|
+
await assertOpenedWithinRoot(safe, root);
|
|
602
|
+
const buf = Buffer.from(content, "utf8");
|
|
603
|
+
let off = 0;
|
|
604
|
+
while (off < buf.length) {
|
|
605
|
+
const { bytesWritten } = await fh.write(buf, off, buf.length - off, off);
|
|
606
|
+
if (bytesWritten <= 0) break;
|
|
607
|
+
off += bytesWritten;
|
|
608
|
+
}
|
|
609
|
+
await fh.truncate(buf.length);
|
|
610
|
+
} finally {
|
|
611
|
+
await fh.close();
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function resolveWithinProjectRootSync(abs, root) {
|
|
615
|
+
const lexical = enforceWithinProjectRoot(abs, root);
|
|
616
|
+
const realRoot = nodeFs.realpathSync(path2.resolve(root));
|
|
617
|
+
let realTarget;
|
|
618
|
+
try {
|
|
619
|
+
realTarget = nodeFs.realpathSync(lexical);
|
|
620
|
+
} catch (err) {
|
|
621
|
+
if (err.code !== "ENOENT") throw err;
|
|
622
|
+
const parent = path2.dirname(lexical);
|
|
623
|
+
const realParent = nodeFs.realpathSync(parent);
|
|
624
|
+
if (!isWithin(realRoot, realParent)) {
|
|
625
|
+
throw makeError("EACCES", `Path escapes project root: ${lexical}`);
|
|
626
|
+
}
|
|
627
|
+
return path2.join(realParent, path2.basename(lexical));
|
|
628
|
+
}
|
|
629
|
+
if (!isWithin(realRoot, realTarget)) {
|
|
630
|
+
throw makeError("EACCES", `Path escapes project root: ${realTarget}`);
|
|
631
|
+
}
|
|
632
|
+
return realTarget;
|
|
633
|
+
}
|
|
634
|
+
function assertWritableAncestorSync(lexical, root) {
|
|
635
|
+
const realRoot = nodeFs.realpathSync(path2.resolve(root));
|
|
636
|
+
let ancestor = path2.dirname(lexical);
|
|
637
|
+
for (; ; ) {
|
|
638
|
+
let realAncestor;
|
|
639
|
+
try {
|
|
640
|
+
realAncestor = nodeFs.realpathSync(ancestor);
|
|
641
|
+
} catch (err) {
|
|
642
|
+
if (err.code !== "ENOENT") throw err;
|
|
643
|
+
const next = path2.dirname(ancestor);
|
|
644
|
+
if (next === ancestor) {
|
|
645
|
+
throw makeError("EACCES", `Path escapes project root: ${lexical}`);
|
|
646
|
+
}
|
|
647
|
+
ancestor = next;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (!isWithin(realRoot, realAncestor)) {
|
|
651
|
+
throw makeError("EACCES", `Path escapes project root: ${lexical}`);
|
|
652
|
+
}
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function assertOpenedWithinRootSync(safe, root) {
|
|
657
|
+
const realRoot = nodeFs.realpathSync(path2.resolve(root));
|
|
658
|
+
const real = nodeFs.realpathSync(safe);
|
|
659
|
+
if (!isWithin(realRoot, real)) {
|
|
660
|
+
throw makeError("EACCES", `Path escapes project root (mid-path race): ${real}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function writeFileSync(root, p, content) {
|
|
664
|
+
const lexical = enforceWithinProjectRoot(p, root);
|
|
665
|
+
assertWritableAncestorSync(lexical, root);
|
|
666
|
+
nodeFs.mkdirSync(path2.dirname(lexical), { recursive: true });
|
|
667
|
+
const safe = resolveWithinProjectRootSync(p, root);
|
|
668
|
+
const fd = nodeFs.openSync(
|
|
669
|
+
safe,
|
|
670
|
+
nodeFs.constants.O_WRONLY | nodeFs.constants.O_CREAT | nodeFs.constants.O_NOFOLLOW,
|
|
671
|
+
438
|
|
672
|
+
);
|
|
673
|
+
try {
|
|
674
|
+
assertOpenedWithinRootSync(safe, root);
|
|
675
|
+
const buf = Buffer.from(content, "utf8");
|
|
676
|
+
let off = 0;
|
|
677
|
+
while (off < buf.length) {
|
|
678
|
+
const bytesWritten = nodeFs.writeSync(fd, buf, off, buf.length - off, off);
|
|
679
|
+
if (bytesWritten <= 0) break;
|
|
680
|
+
off += bytesWritten;
|
|
681
|
+
}
|
|
682
|
+
nodeFs.ftruncateSync(fd, buf.length);
|
|
683
|
+
} finally {
|
|
684
|
+
nodeFs.closeSync(fd);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
async function listFiles(root, rootArg) {
|
|
688
|
+
const safeRoot = await resolveWithinProjectRoot(rootArg, root);
|
|
689
|
+
const out = [];
|
|
690
|
+
const queue = [safeRoot];
|
|
691
|
+
while (queue.length > 0) {
|
|
692
|
+
if (out.length >= LIST_FILE_LIMIT) break;
|
|
693
|
+
const dir = queue.shift();
|
|
694
|
+
let entries;
|
|
695
|
+
try {
|
|
696
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
697
|
+
} catch (err) {
|
|
698
|
+
const code = err.code;
|
|
699
|
+
if (code === "EACCES" || code === "ENOENT" || code === "ENOTDIR") continue;
|
|
700
|
+
throw err;
|
|
701
|
+
}
|
|
702
|
+
for (const e of entries) {
|
|
703
|
+
if (out.length >= LIST_FILE_LIMIT) break;
|
|
704
|
+
const full = path2.join(dir, e.name);
|
|
705
|
+
if (e.isDirectory()) {
|
|
706
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
707
|
+
queue.push(full);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
if (!e.isFile()) continue;
|
|
711
|
+
out.push(path2.relative(safeRoot, full).split(path2.sep).join("/"));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return out;
|
|
715
|
+
}
|
|
716
|
+
function registerProjectFsIpc(ctx) {
|
|
717
|
+
const getRoot = () => ctx.workspace.getProjectPath();
|
|
718
|
+
const getLastClosedRoot = () => ctx.workspace.getLastClosedProjectPath();
|
|
719
|
+
return new IpcRegistry(ctx.senderPolicy).handle(ProjectFsChannel.GetRoot, () => getRoot()).handle(ProjectFsChannel.ReadFile, (_event, ...args) => {
|
|
720
|
+
const [p] = validate(ProjectFsChannel.ReadFile, ReadFileSchema, args);
|
|
721
|
+
return readFile(getRoot(), p);
|
|
722
|
+
}).handle(ProjectFsChannel.WriteFile, (_event, ...args) => {
|
|
723
|
+
const [p, content] = validate(ProjectFsChannel.WriteFile, WriteFileSchema, args);
|
|
724
|
+
const root = pickWriteRoot(p, { current: getRoot(), lastClosed: getLastClosedRoot() });
|
|
725
|
+
return writeFile(root, p, content);
|
|
726
|
+
}).handleSync(ProjectFsChannel.WriteFileSync, (_event, ...args) => {
|
|
727
|
+
const [p, content] = validate(ProjectFsChannel.WriteFileSync, WriteFileSchema, args);
|
|
728
|
+
const root = pickWriteRoot(p, { current: getRoot(), lastClosed: getLastClosedRoot() });
|
|
729
|
+
writeFileSync(root, p, content);
|
|
730
|
+
return { ok: true };
|
|
731
|
+
}).handle(ProjectFsChannel.ListFiles, (_event, ...args) => {
|
|
732
|
+
const [p] = validate(ProjectFsChannel.ListFiles, ListFilesSchema, args);
|
|
733
|
+
return listFiles(getRoot(), p);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
|
|
81
737
|
// src/main/app/app.ts
|
|
82
|
-
import { app as app13, BrowserWindow as BrowserWindow4, nativeImage } from "electron";
|
|
83
|
-
import
|
|
84
|
-
import
|
|
738
|
+
import { app as app13, BrowserWindow as BrowserWindow4, nativeImage, session as session2 } from "electron";
|
|
739
|
+
import fs10 from "fs";
|
|
740
|
+
import path16 from "path";
|
|
85
741
|
|
|
86
742
|
// src/main/utils/paths.ts
|
|
87
|
-
import
|
|
88
|
-
import
|
|
743
|
+
import fs3 from "fs";
|
|
744
|
+
import path3 from "path";
|
|
89
745
|
import { fileURLToPath } from "url";
|
|
90
746
|
function resolveDevtoolsPackageRoot() {
|
|
91
|
-
let dir =
|
|
92
|
-
const root =
|
|
747
|
+
let dir = path3.dirname(fileURLToPath(import.meta.url));
|
|
748
|
+
const root = path3.parse(dir).root;
|
|
93
749
|
while (dir !== root) {
|
|
94
|
-
const pkgPath =
|
|
750
|
+
const pkgPath = path3.join(dir, "package.json");
|
|
95
751
|
try {
|
|
96
|
-
const pkg2 = JSON.parse(
|
|
752
|
+
const pkg2 = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
|
|
97
753
|
if (pkg2.name === "@dimina-kit/devtools") return dir;
|
|
98
754
|
} catch {
|
|
99
755
|
}
|
|
100
|
-
dir =
|
|
756
|
+
dir = path3.dirname(dir);
|
|
101
757
|
}
|
|
102
758
|
throw new Error("@dimina-kit/devtools package root not found from " + fileURLToPath(import.meta.url));
|
|
103
759
|
}
|
|
104
760
|
var devtoolsPackageRoot = resolveDevtoolsPackageRoot();
|
|
105
|
-
var rendererDir =
|
|
106
|
-
var defaultPreloadPath =
|
|
107
|
-
var mainPreloadPath =
|
|
108
|
-
var simulatorDir =
|
|
761
|
+
var rendererDir = path3.join(devtoolsPackageRoot, "dist/renderer");
|
|
762
|
+
var defaultPreloadPath = path3.join(devtoolsPackageRoot, "dist/preload/windows/simulator.js");
|
|
763
|
+
var mainPreloadPath = path3.join(devtoolsPackageRoot, "dist/preload/windows/main.cjs");
|
|
764
|
+
var simulatorDir = path3.join(devtoolsPackageRoot, "dist/simulator");
|
|
109
765
|
|
|
110
766
|
// src/main/utils/theme.ts
|
|
111
767
|
import { BrowserWindow, nativeTheme as nativeTheme2 } from "electron";
|
|
112
|
-
|
|
113
|
-
// src/main/utils/disposable.ts
|
|
114
|
-
function toDisposable(fn) {
|
|
115
|
-
let done = false;
|
|
116
|
-
return {
|
|
117
|
-
dispose() {
|
|
118
|
-
if (done) return;
|
|
119
|
-
done = true;
|
|
120
|
-
return fn();
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
var DisposableRegistry = class {
|
|
125
|
-
entries = [];
|
|
126
|
-
_disposed = false;
|
|
127
|
-
/**
|
|
128
|
-
* Number of live entries. A wrapper's `dispose` splices its entry out and
|
|
129
|
-
* `disposeAll` clears the array, so `entries.length` is the live count.
|
|
130
|
-
*/
|
|
131
|
-
get size() {
|
|
132
|
-
return this.entries.length;
|
|
133
|
-
}
|
|
134
|
-
add(d) {
|
|
135
|
-
if (this._disposed) {
|
|
136
|
-
throw new Error("cannot add to disposed registry");
|
|
137
|
-
}
|
|
138
|
-
const fn = typeof d === "function" ? d : () => d.dispose();
|
|
139
|
-
const entry = { fn, released: false };
|
|
140
|
-
this.entries.push(entry);
|
|
141
|
-
return {
|
|
142
|
-
dispose: () => {
|
|
143
|
-
if (entry.released) return;
|
|
144
|
-
entry.released = true;
|
|
145
|
-
const i = this.entries.indexOf(entry);
|
|
146
|
-
if (i >= 0) this.entries.splice(i, 1);
|
|
147
|
-
return fn();
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
async disposeAll() {
|
|
152
|
-
if (this._disposed) return;
|
|
153
|
-
this._disposed = true;
|
|
154
|
-
const items = this.entries.slice().reverse();
|
|
155
|
-
this.entries = [];
|
|
156
|
-
const errors = [];
|
|
157
|
-
for (const entry of items) {
|
|
158
|
-
if (entry.released) continue;
|
|
159
|
-
entry.released = true;
|
|
160
|
-
try {
|
|
161
|
-
await entry.fn();
|
|
162
|
-
} catch (e) {
|
|
163
|
-
errors.push(e);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (errors.length > 0) {
|
|
167
|
-
throw new AggregateError(errors, "DisposableRegistry encountered errors during disposeAll");
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
dispose() {
|
|
171
|
-
return this.disposeAll();
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// src/main/utils/theme.ts
|
|
176
768
|
function themeBg() {
|
|
177
769
|
return nativeTheme2.shouldUseDarkColors ? "#1a1a1a" : "#fafafa";
|
|
178
770
|
}
|
|
@@ -205,7 +797,7 @@ function registerAppLifecycle() {
|
|
|
205
797
|
|
|
206
798
|
// src/main/windows/main-window/create.ts
|
|
207
799
|
import { app as app4, BrowserWindow as BrowserWindow2, View, session, shell as shell2 } from "electron";
|
|
208
|
-
import
|
|
800
|
+
import path5 from "path";
|
|
209
801
|
|
|
210
802
|
// src/main/services/simulator/referer.ts
|
|
211
803
|
var DEFAULT_VERSION = "develop";
|
|
@@ -228,7 +820,7 @@ function getSimulatorServicewechatReferer() {
|
|
|
228
820
|
|
|
229
821
|
// src/main/windows/navigation-hardening.ts
|
|
230
822
|
import { shell } from "electron";
|
|
231
|
-
import
|
|
823
|
+
import path4 from "path";
|
|
232
824
|
import { pathToFileURL } from "url";
|
|
233
825
|
function handleWindowOpenExternal(url) {
|
|
234
826
|
try {
|
|
@@ -241,7 +833,7 @@ function handleWindowOpenExternal(url) {
|
|
|
241
833
|
return { action: "deny" };
|
|
242
834
|
}
|
|
243
835
|
function applyNavigationHardening(wc, rendererDir2) {
|
|
244
|
-
const rendererPrefix = pathToFileURL(rendererDir2 +
|
|
836
|
+
const rendererPrefix = pathToFileURL(rendererDir2 + path4.sep).href;
|
|
245
837
|
wc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
|
|
246
838
|
wc.on("will-navigate", (event, url) => {
|
|
247
839
|
let parsed;
|
|
@@ -319,7 +911,7 @@ function createMainWindow(opts) {
|
|
|
319
911
|
}
|
|
320
912
|
}
|
|
321
913
|
});
|
|
322
|
-
const rendererDir2 =
|
|
914
|
+
const rendererDir2 = path5.dirname(opts.indexHtml);
|
|
323
915
|
applyNavigationHardening(mainWindow.webContents, rendererDir2);
|
|
324
916
|
mainWindow.webContents.on("will-attach-webview", (event, webPreferences, params) => {
|
|
325
917
|
;
|
|
@@ -408,13 +1000,13 @@ function createWorkbenchSenderPolicy(ctx) {
|
|
|
408
1000
|
}
|
|
409
1001
|
|
|
410
1002
|
// src/main/services/default-adapter.ts
|
|
411
|
-
import
|
|
1003
|
+
import path6 from "path";
|
|
412
1004
|
import { app as app5 } from "electron";
|
|
413
1005
|
var defaultAdapter = {
|
|
414
1006
|
async openProject(opts) {
|
|
415
1007
|
const diminaKit = await import("@dimina-kit/devkit");
|
|
416
1008
|
const openProjectOpts = {
|
|
417
|
-
outputDir:
|
|
1009
|
+
outputDir: path6.join(app5.getPath("userData"), "dimina-fe-output"),
|
|
418
1010
|
...opts,
|
|
419
1011
|
simulatorDir
|
|
420
1012
|
};
|
|
@@ -422,112 +1014,6 @@ var defaultAdapter = {
|
|
|
422
1014
|
}
|
|
423
1015
|
};
|
|
424
1016
|
|
|
425
|
-
// src/shared/ipc-channels.ts
|
|
426
|
-
var SimulatorChannel = {
|
|
427
|
-
Attach: "simulator:attach",
|
|
428
|
-
Detach: "simulator:detach",
|
|
429
|
-
Resize: "simulator:resize",
|
|
430
|
-
SetVisible: "simulator:setVisible",
|
|
431
|
-
Console: "simulator:console"
|
|
432
|
-
};
|
|
433
|
-
var SimulatorCustomApiChannel = {
|
|
434
|
-
List: "simulator:custom-apis:list",
|
|
435
|
-
Invoke: "simulator:custom-apis:invoke"
|
|
436
|
-
};
|
|
437
|
-
var SimulatorStorageChannel = {
|
|
438
|
-
GetSnapshot: "simulator:storage:snapshot",
|
|
439
|
-
GetActivePrefix: "simulator:storage:activePrefix",
|
|
440
|
-
Set: "simulator:storage:set",
|
|
441
|
-
Remove: "simulator:storage:remove",
|
|
442
|
-
Clear: "simulator:storage:clear",
|
|
443
|
-
ClearAll: "simulator:storage:clearAll",
|
|
444
|
-
Event: "simulator:storage:event"
|
|
445
|
-
};
|
|
446
|
-
var SimulatorElementChannel = {
|
|
447
|
-
Inspect: "simulator:element:inspect",
|
|
448
|
-
Clear: "simulator:element:clear"
|
|
449
|
-
};
|
|
450
|
-
var WorkbenchSettingsChannel = {
|
|
451
|
-
Get: "workbenchSettings:get",
|
|
452
|
-
Save: "workbenchSettings:save",
|
|
453
|
-
SetTheme: "workbenchSettings:setTheme",
|
|
454
|
-
GetCdpStatus: "workbenchSettings:getCdpStatus",
|
|
455
|
-
GetMcpStatus: "workbenchSettings:getMcpStatus",
|
|
456
|
-
SetVisible: "workbenchSettings:setVisible",
|
|
457
|
-
Init: "workbenchSettings:init"
|
|
458
|
-
};
|
|
459
|
-
var ProjectChannel = {
|
|
460
|
-
Open: "project:open",
|
|
461
|
-
Close: "project:close",
|
|
462
|
-
GetPages: "project:getPages",
|
|
463
|
-
GetCompileConfig: "project:getCompileConfig",
|
|
464
|
-
SaveCompileConfig: "project:saveCompileConfig",
|
|
465
|
-
Status: "project:status",
|
|
466
|
-
CaptureThumbnail: "project:captureThumbnail",
|
|
467
|
-
GetThumbnail: "project:getThumbnail"
|
|
468
|
-
};
|
|
469
|
-
var ProjectsChannel = {
|
|
470
|
-
List: "projects:list",
|
|
471
|
-
Add: "projects:add",
|
|
472
|
-
Remove: "projects:remove",
|
|
473
|
-
/** Phase 3: merged + sanitized template catalog for the create-project dialog. */
|
|
474
|
-
ListTemplates: "projects:listTemplates",
|
|
475
|
-
/** Phase 3: host-supplied create-project dialog hook (returns input or null). */
|
|
476
|
-
OpenCreateDialog: "projects:openCreateDialog",
|
|
477
|
-
/** Phase 3: server-side scaffold + register (delegates to create-project-service). */
|
|
478
|
-
Create: "projects:create",
|
|
479
|
-
/** Default values used to pre-fill the create-project dialog (baseDir). */
|
|
480
|
-
GetCreateDefaults: "projects:getCreateDefaults"
|
|
481
|
-
};
|
|
482
|
-
var DialogChannel = {
|
|
483
|
-
OpenDirectory: "dialog:openDirectory"
|
|
484
|
-
};
|
|
485
|
-
var PanelChannel = {
|
|
486
|
-
List: "panel:list",
|
|
487
|
-
Eval: "panel:eval",
|
|
488
|
-
Select: "panel:select",
|
|
489
|
-
SelectSimulator: "panel:selectSimulator"
|
|
490
|
-
};
|
|
491
|
-
var PopoverChannel = {
|
|
492
|
-
Show: "popover:show",
|
|
493
|
-
Hide: "popover:hide",
|
|
494
|
-
Relaunch: "popover:relaunch",
|
|
495
|
-
Closed: "popover:closed",
|
|
496
|
-
Init: "popover:init"
|
|
497
|
-
};
|
|
498
|
-
var ToolbarChannel = {
|
|
499
|
-
GetActions: "toolbar:getActions",
|
|
500
|
-
ActionsChanged: "toolbar:actionsChanged",
|
|
501
|
-
/** Invoke a toolbar action by id: `invoke(Invoke, actionId)`. */
|
|
502
|
-
Invoke: "toolbar:invoke"
|
|
503
|
-
};
|
|
504
|
-
var WindowChannel = {
|
|
505
|
-
NavigateBack: "window:navigateBack"
|
|
506
|
-
};
|
|
507
|
-
var AppChannel = {
|
|
508
|
-
GetPreloadPath: "app:getPreloadPath",
|
|
509
|
-
GetBranding: "app:getBranding",
|
|
510
|
-
GetHeaderHeight: "app:getHeaderHeight"
|
|
511
|
-
};
|
|
512
|
-
var AutomationChannel = {
|
|
513
|
-
GetPort: "automation:port"
|
|
514
|
-
};
|
|
515
|
-
var SettingsChannel = {
|
|
516
|
-
SetVisible: "settings:setVisible",
|
|
517
|
-
ConfigChanged: "settings:configChanged",
|
|
518
|
-
ProjectSettingsChanged: "settings:projectSettingsChanged",
|
|
519
|
-
Init: "settings:init",
|
|
520
|
-
Closed: "settings:closed",
|
|
521
|
-
Changed: "settings:changed"
|
|
522
|
-
};
|
|
523
|
-
var UpdateChannel = {
|
|
524
|
-
Check: "updates:check",
|
|
525
|
-
Download: "updates:download",
|
|
526
|
-
Install: "updates:install",
|
|
527
|
-
DownloadProgress: "updates:downloadProgress",
|
|
528
|
-
Available: "updates:available"
|
|
529
|
-
};
|
|
530
|
-
|
|
531
1017
|
// src/main/services/notifications/renderer-notifier.ts
|
|
532
1018
|
function liveWebContents(wc) {
|
|
533
1019
|
if (!wc) return null;
|
|
@@ -584,7 +1070,7 @@ function createRendererNotifier(ctx) {
|
|
|
584
1070
|
|
|
585
1071
|
// src/main/services/views/view-manager.ts
|
|
586
1072
|
import { WebContentsView as WebContentsView2, webContents } from "electron";
|
|
587
|
-
import
|
|
1073
|
+
import path7 from "path";
|
|
588
1074
|
|
|
589
1075
|
// src/main/services/layout/index.ts
|
|
590
1076
|
var SPLITTER_W = 4;
|
|
@@ -608,7 +1094,7 @@ function computeSettingsBounds(contentWidth, contentHeight, headerHeight) {
|
|
|
608
1094
|
return {
|
|
609
1095
|
x: Math.max(0, contentWidth - SETTINGS_W),
|
|
610
1096
|
y: headerHeight,
|
|
611
|
-
width: SETTINGS_W,
|
|
1097
|
+
width: Math.min(SETTINGS_W, Math.max(1, contentWidth)),
|
|
612
1098
|
height: Math.max(1, contentHeight - headerHeight)
|
|
613
1099
|
};
|
|
614
1100
|
}
|
|
@@ -616,7 +1102,7 @@ function computePopoverBounds(contentWidth, contentHeight, headerHeight) {
|
|
|
616
1102
|
return {
|
|
617
1103
|
x: 0,
|
|
618
1104
|
y: headerHeight,
|
|
619
|
-
width: contentWidth,
|
|
1105
|
+
width: Math.max(1, contentWidth),
|
|
620
1106
|
height: contentHeight - headerHeight
|
|
621
1107
|
};
|
|
622
1108
|
}
|
|
@@ -631,6 +1117,7 @@ function createViewManager(ctx) {
|
|
|
631
1117
|
let popoverView = null;
|
|
632
1118
|
let lastSimWidth = 375;
|
|
633
1119
|
let simulatorWebContentsId = null;
|
|
1120
|
+
let simulatorBoundsOverride = null;
|
|
634
1121
|
function destroyViewInternal(view) {
|
|
635
1122
|
if (!view) return;
|
|
636
1123
|
if (!ctx.windows.mainWindow.isDestroyed()) {
|
|
@@ -648,8 +1135,37 @@ function createViewManager(ctx) {
|
|
|
648
1135
|
}
|
|
649
1136
|
function applySimulatorBounds(simWidth) {
|
|
650
1137
|
if (!simulatorView || ctx.windows.mainWindow.isDestroyed()) return;
|
|
1138
|
+
if (simulatorBoundsOverride) {
|
|
1139
|
+
simulatorView.setBounds(simulatorBoundsOverride);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
651
1142
|
const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
|
|
652
|
-
simulatorView.setBounds(
|
|
1143
|
+
simulatorView.setBounds(
|
|
1144
|
+
computeSimulatorBounds(w, h, simWidth, headerHeight)
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
function isHidden(b) {
|
|
1148
|
+
return b.width <= 0 || b.height <= 0;
|
|
1149
|
+
}
|
|
1150
|
+
function setSimulatorDevtoolsBounds(bounds) {
|
|
1151
|
+
simulatorBoundsOverride = bounds;
|
|
1152
|
+
if (!simulatorView || simulatorView.webContents.isDestroyed()) return;
|
|
1153
|
+
if (ctx.windows.mainWindow.isDestroyed()) return;
|
|
1154
|
+
if (isHidden(bounds)) {
|
|
1155
|
+
if (simulatorViewAdded) {
|
|
1156
|
+
try {
|
|
1157
|
+
ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
|
|
1158
|
+
} catch {
|
|
1159
|
+
}
|
|
1160
|
+
simulatorViewAdded = false;
|
|
1161
|
+
}
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (!simulatorViewAdded) {
|
|
1165
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
1166
|
+
simulatorViewAdded = true;
|
|
1167
|
+
}
|
|
1168
|
+
simulatorView.setBounds(bounds);
|
|
653
1169
|
}
|
|
654
1170
|
function applySettingsBounds() {
|
|
655
1171
|
if (!settingsView || ctx.windows.mainWindow.isDestroyed()) return;
|
|
@@ -690,25 +1206,35 @@ function createViewManager(ctx) {
|
|
|
690
1206
|
let tries = 0
|
|
691
1207
|
const timer = setInterval(() => {
|
|
692
1208
|
tries++
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
1209
|
+
try {
|
|
1210
|
+
const UI = globalThis.UI
|
|
1211
|
+
const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
|
|
1212
|
+
? UI.ViewManager.instance()
|
|
1213
|
+
: null
|
|
1214
|
+
if (vm && typeof vm.showView === 'function') {
|
|
1215
|
+
vm.showView('console')
|
|
698
1216
|
clearInterval(timer)
|
|
699
1217
|
return
|
|
700
1218
|
}
|
|
701
|
-
}
|
|
702
|
-
if (tries >
|
|
703
|
-
},
|
|
1219
|
+
} catch {}
|
|
1220
|
+
if (tries > 80) clearInterval(timer)
|
|
1221
|
+
}, 50)
|
|
704
1222
|
})()
|
|
705
1223
|
`).catch(() => {
|
|
706
1224
|
});
|
|
707
1225
|
});
|
|
708
1226
|
if (getDefaultTab(ctx) === "simulator") {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1227
|
+
if (simulatorBoundsOverride) {
|
|
1228
|
+
if (!isHidden(simulatorBoundsOverride)) {
|
|
1229
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
1230
|
+
simulatorViewAdded = true;
|
|
1231
|
+
simulatorView.setBounds(simulatorBoundsOverride);
|
|
1232
|
+
}
|
|
1233
|
+
} else {
|
|
1234
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
1235
|
+
simulatorViewAdded = true;
|
|
1236
|
+
applySimulatorBounds(simWidth);
|
|
1237
|
+
}
|
|
712
1238
|
}
|
|
713
1239
|
}
|
|
714
1240
|
function detachSimulator() {
|
|
@@ -720,6 +1246,7 @@ function createViewManager(ctx) {
|
|
|
720
1246
|
simulatorView = null;
|
|
721
1247
|
simulatorViewAdded = false;
|
|
722
1248
|
simulatorWebContentsId = null;
|
|
1249
|
+
simulatorBoundsOverride = null;
|
|
723
1250
|
}
|
|
724
1251
|
function showSimulator(simWidth) {
|
|
725
1252
|
lastSimWidth = simWidth;
|
|
@@ -752,7 +1279,7 @@ function createViewManager(ctx) {
|
|
|
752
1279
|
});
|
|
753
1280
|
applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
|
|
754
1281
|
await settingsView.webContents.loadFile(
|
|
755
|
-
|
|
1282
|
+
path7.join(ctx.rendererDir, "entries/settings/index.html")
|
|
756
1283
|
);
|
|
757
1284
|
}
|
|
758
1285
|
if (!settingsViewAdded) {
|
|
@@ -789,7 +1316,7 @@ function createViewManager(ctx) {
|
|
|
789
1316
|
ctx.notify.popoverInit(popover, data);
|
|
790
1317
|
});
|
|
791
1318
|
popover.webContents.loadFile(
|
|
792
|
-
|
|
1319
|
+
path7.join(ctx.rendererDir, "entries/popover/index.html")
|
|
793
1320
|
);
|
|
794
1321
|
}
|
|
795
1322
|
function hidePopover() {
|
|
@@ -860,7 +1387,8 @@ function createViewManager(ctx) {
|
|
|
860
1387
|
return popoverView.webContents.id;
|
|
861
1388
|
},
|
|
862
1389
|
resize,
|
|
863
|
-
setVisible
|
|
1390
|
+
setVisible,
|
|
1391
|
+
setSimulatorDevtoolsBounds
|
|
864
1392
|
};
|
|
865
1393
|
}
|
|
866
1394
|
|
|
@@ -892,61 +1420,21 @@ function createWindowService(mainWindow) {
|
|
|
892
1420
|
|
|
893
1421
|
// src/main/services/projects/project-repository.ts
|
|
894
1422
|
import { app as app6 } from "electron";
|
|
895
|
-
import
|
|
896
|
-
import
|
|
897
|
-
|
|
898
|
-
// src/main/utils/logger.ts
|
|
899
|
-
var LEVEL_PRIORITY = {
|
|
900
|
-
debug: 0,
|
|
901
|
-
info: 1,
|
|
902
|
-
warn: 2,
|
|
903
|
-
error: 3
|
|
904
|
-
};
|
|
905
|
-
var LEVEL_PREFIX = {
|
|
906
|
-
debug: "[DEBUG]",
|
|
907
|
-
info: "[INFO]",
|
|
908
|
-
warn: "[WARN]",
|
|
909
|
-
error: "[ERROR]"
|
|
910
|
-
};
|
|
911
|
-
var currentLevel = "debug";
|
|
912
|
-
function shouldLog(level) {
|
|
913
|
-
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
|
|
914
|
-
}
|
|
915
|
-
function formatMessage(level, tag, message) {
|
|
916
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
917
|
-
return `${ts} ${LEVEL_PREFIX[level]} [${tag}] ${message}`;
|
|
918
|
-
}
|
|
919
|
-
function createLogger(tag) {
|
|
920
|
-
return {
|
|
921
|
-
debug(message, ...args) {
|
|
922
|
-
if (shouldLog("debug")) console.debug(formatMessage("debug", tag, message), ...args);
|
|
923
|
-
},
|
|
924
|
-
info(message, ...args) {
|
|
925
|
-
if (shouldLog("info")) console.info(formatMessage("info", tag, message), ...args);
|
|
926
|
-
},
|
|
927
|
-
warn(message, ...args) {
|
|
928
|
-
if (shouldLog("warn")) console.warn(formatMessage("warn", tag, message), ...args);
|
|
929
|
-
},
|
|
930
|
-
error(message, ...args) {
|
|
931
|
-
if (shouldLog("error")) console.error(formatMessage("error", tag, message), ...args);
|
|
932
|
-
}
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// src/main/services/projects/project-repository.ts
|
|
937
|
-
var log = createLogger("projects");
|
|
1423
|
+
import path8 from "path";
|
|
1424
|
+
import fs4 from "fs";
|
|
1425
|
+
var log2 = createLogger("projects");
|
|
938
1426
|
function getProjectsFile() {
|
|
939
|
-
return
|
|
1427
|
+
return path8.join(app6.getPath("userData"), "dimina-projects.json");
|
|
940
1428
|
}
|
|
941
1429
|
function load() {
|
|
942
1430
|
try {
|
|
943
|
-
return JSON.parse(
|
|
1431
|
+
return JSON.parse(fs4.readFileSync(getProjectsFile(), "utf-8"));
|
|
944
1432
|
} catch {
|
|
945
1433
|
return [];
|
|
946
1434
|
}
|
|
947
1435
|
}
|
|
948
1436
|
function save(projects) {
|
|
949
|
-
|
|
1437
|
+
fs4.writeFileSync(getProjectsFile(), JSON.stringify(projects, null, 2));
|
|
950
1438
|
}
|
|
951
1439
|
function listProjects() {
|
|
952
1440
|
return load();
|
|
@@ -955,20 +1443,20 @@ function validateProjectDir(dirPath) {
|
|
|
955
1443
|
if (!dirPath) {
|
|
956
1444
|
return "\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u8DEF\u5F84\u4E3A\u7A7A\uFF0C\u8BF7\u9009\u62E9\u5305\u542B\u5C0F\u7A0B\u5E8F\u6E90\u7801\u7684\u76EE\u5F55";
|
|
957
1445
|
}
|
|
958
|
-
if (!
|
|
1446
|
+
if (!fs4.existsSync(dirPath)) {
|
|
959
1447
|
return `\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${dirPath}`;
|
|
960
1448
|
}
|
|
961
|
-
if (!
|
|
962
|
-
const configPath =
|
|
963
|
-
if (
|
|
1449
|
+
if (!fs4.existsSync(path8.join(dirPath, "app.json"))) {
|
|
1450
|
+
const configPath = path8.join(dirPath, "project.config.json");
|
|
1451
|
+
if (fs4.existsSync(configPath)) {
|
|
964
1452
|
try {
|
|
965
|
-
const cfg = JSON.parse(
|
|
1453
|
+
const cfg = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
966
1454
|
if (cfg.miniprogramRoot) {
|
|
967
|
-
const resolvedRoot =
|
|
1455
|
+
const resolvedRoot = path8.resolve(dirPath, cfg.miniprogramRoot);
|
|
968
1456
|
return `\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0Cproject.config.json \u4E2D\u6307\u5B9A\u4E86 miniprogramRoot: "${cfg.miniprogramRoot}"\uFF0C\u8BF7\u5BFC\u5165 ${resolvedRoot}`;
|
|
969
1457
|
}
|
|
970
1458
|
} catch (err) {
|
|
971
|
-
|
|
1459
|
+
log2.warn("Failed to parse project.config.json", err);
|
|
972
1460
|
}
|
|
973
1461
|
}
|
|
974
1462
|
return "\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0C\u8BF7\u9009\u62E9\u5305\u542B\u5C0F\u7A0B\u5E8F\u6E90\u7801\u7684\u76EE\u5F55";
|
|
@@ -977,15 +1465,15 @@ function validateProjectDir(dirPath) {
|
|
|
977
1465
|
}
|
|
978
1466
|
function addProject(dirPath) {
|
|
979
1467
|
const projects = load();
|
|
980
|
-
let name =
|
|
1468
|
+
let name = path8.basename(dirPath);
|
|
981
1469
|
try {
|
|
982
|
-
const configPath =
|
|
983
|
-
if (
|
|
984
|
-
const cfg = JSON.parse(
|
|
1470
|
+
const configPath = path8.join(dirPath, "project.config.json");
|
|
1471
|
+
if (fs4.existsSync(configPath)) {
|
|
1472
|
+
const cfg = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
985
1473
|
if (cfg.projectname) name = cfg.projectname;
|
|
986
1474
|
}
|
|
987
1475
|
} catch (err) {
|
|
988
|
-
|
|
1476
|
+
log2.warn("Failed to read project name from config", err);
|
|
989
1477
|
}
|
|
990
1478
|
const project = { name, path: dirPath, lastOpened: null };
|
|
991
1479
|
const idx = projects.findIndex((p) => p.path === dirPath);
|
|
@@ -1018,10 +1506,10 @@ function getCompileConfig(dirPath) {
|
|
|
1018
1506
|
};
|
|
1019
1507
|
}
|
|
1020
1508
|
function getProjectPages(dirPath) {
|
|
1021
|
-
const appJsonPath =
|
|
1509
|
+
const appJsonPath = path8.join(dirPath, "app.json");
|
|
1022
1510
|
try {
|
|
1023
1511
|
const appJson = JSON.parse(
|
|
1024
|
-
|
|
1512
|
+
fs4.readFileSync(appJsonPath, "utf-8")
|
|
1025
1513
|
);
|
|
1026
1514
|
return {
|
|
1027
1515
|
pages: appJson.pages || [],
|
|
@@ -1044,8 +1532,8 @@ function getProjectSettings(projectPath) {
|
|
|
1044
1532
|
return { uploadWithSourceMap: false };
|
|
1045
1533
|
}
|
|
1046
1534
|
try {
|
|
1047
|
-
const configPath =
|
|
1048
|
-
const config = JSON.parse(
|
|
1535
|
+
const configPath = path8.join(projectPath, "project.config.json");
|
|
1536
|
+
const config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
1049
1537
|
return {
|
|
1050
1538
|
uploadWithSourceMap: !!config.setting?.uploadWithSourceMap
|
|
1051
1539
|
};
|
|
@@ -1055,17 +1543,17 @@ function getProjectSettings(projectPath) {
|
|
|
1055
1543
|
}
|
|
1056
1544
|
function updateProjectSettings(projectPath, patch) {
|
|
1057
1545
|
if (!projectPath) return;
|
|
1058
|
-
const configPath =
|
|
1546
|
+
const configPath = path8.join(projectPath, "project.config.json");
|
|
1059
1547
|
let config = {};
|
|
1060
1548
|
try {
|
|
1061
|
-
config = JSON.parse(
|
|
1549
|
+
config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
1062
1550
|
} catch {
|
|
1063
1551
|
}
|
|
1064
1552
|
const nextSetting = {
|
|
1065
1553
|
...config.setting ?? {},
|
|
1066
1554
|
...patch.uploadWithSourceMap === void 0 ? {} : { uploadWithSourceMap: patch.uploadWithSourceMap }
|
|
1067
1555
|
};
|
|
1068
|
-
|
|
1556
|
+
fs4.writeFileSync(
|
|
1069
1557
|
configPath,
|
|
1070
1558
|
JSON.stringify({ ...config, setting: nextSetting }, null, 2)
|
|
1071
1559
|
);
|
|
@@ -1082,6 +1570,7 @@ var DEFAULT_COMPILE_CONFIG = {
|
|
|
1082
1570
|
function createWorkspaceService(ctx) {
|
|
1083
1571
|
let currentSession = null;
|
|
1084
1572
|
let currentProjectPath = "";
|
|
1573
|
+
let lastClosedProjectPath = "";
|
|
1085
1574
|
function sendStatus(status2, message, hotReload) {
|
|
1086
1575
|
ctx.notify.projectStatus(hotReload ? { status: status2, message, hotReload: true } : { status: status2, message });
|
|
1087
1576
|
}
|
|
@@ -1094,11 +1583,11 @@ function createWorkspaceService(ctx) {
|
|
|
1094
1583
|
}
|
|
1095
1584
|
async function disposeSession() {
|
|
1096
1585
|
if (!currentSession) return;
|
|
1097
|
-
const
|
|
1586
|
+
const session3 = currentSession;
|
|
1098
1587
|
currentSession = null;
|
|
1099
1588
|
try {
|
|
1100
1589
|
await Promise.race([
|
|
1101
|
-
|
|
1590
|
+
session3.close(),
|
|
1102
1591
|
new Promise(
|
|
1103
1592
|
(_, reject) => setTimeout(() => reject(new Error("session close timed out")), 5e3)
|
|
1104
1593
|
)
|
|
@@ -1107,8 +1596,8 @@ function createWorkspaceService(ctx) {
|
|
|
1107
1596
|
console.error("[workspace] session close failed:", err);
|
|
1108
1597
|
}
|
|
1109
1598
|
}
|
|
1110
|
-
function applyRefererFromSession(
|
|
1111
|
-
const appInfo =
|
|
1599
|
+
function applyRefererFromSession(session3) {
|
|
1600
|
+
const appInfo = session3.appInfo;
|
|
1112
1601
|
if (appInfo && typeof appInfo.appId === "string" && appInfo.appId.length > 0) {
|
|
1113
1602
|
setSimulatorServicewechatReferer(
|
|
1114
1603
|
appInfo.appId,
|
|
@@ -1128,20 +1617,27 @@ function createWorkspaceService(ctx) {
|
|
|
1128
1617
|
return projects.some((p) => p.path === dirPath);
|
|
1129
1618
|
},
|
|
1130
1619
|
validateProjectDir: async (dirPath) => provider.validateProjectDir ? provider.validateProjectDir(dirPath) : null,
|
|
1131
|
-
async openProject(projectPath) {
|
|
1132
|
-
clearSimulatorServicewechatReferer();
|
|
1133
|
-
await disposeSession();
|
|
1134
|
-
currentProjectPath = "";
|
|
1620
|
+
async openProject(projectPath) {
|
|
1135
1621
|
const dirError = provider.validateProjectDir ? await provider.validateProjectDir(projectPath) : null;
|
|
1136
1622
|
if (dirError) {
|
|
1137
1623
|
sendStatus("error", dirError);
|
|
1138
1624
|
return { success: false, error: dirError };
|
|
1139
1625
|
}
|
|
1626
|
+
const gate = ctx.beforeOpenProject ? await ctx.beforeOpenProject(projectPath) : void 0;
|
|
1627
|
+
if (gate && !gate.allow) {
|
|
1628
|
+
const message = gate.userMessage ?? gate.reason;
|
|
1629
|
+
sendStatus("error", message);
|
|
1630
|
+
return { success: false, error: message };
|
|
1631
|
+
}
|
|
1632
|
+
clearSimulatorServicewechatReferer();
|
|
1633
|
+
await disposeSession();
|
|
1634
|
+
currentProjectPath = "";
|
|
1635
|
+
lastClosedProjectPath = "";
|
|
1140
1636
|
sendStatus("compiling", "\u6B63\u5728\u7F16\u8BD1...");
|
|
1141
1637
|
const { compile } = loadWorkbenchSettings();
|
|
1142
|
-
let
|
|
1638
|
+
let session3;
|
|
1143
1639
|
try {
|
|
1144
|
-
|
|
1640
|
+
session3 = await ctx.adapter.openProject({
|
|
1145
1641
|
projectPath,
|
|
1146
1642
|
sourcemap: true,
|
|
1147
1643
|
watch: compile.watch,
|
|
@@ -1153,27 +1649,29 @@ function createWorkspaceService(ctx) {
|
|
|
1153
1649
|
sendStatus("error", String(err));
|
|
1154
1650
|
return { success: false, error: String(err) };
|
|
1155
1651
|
}
|
|
1156
|
-
currentSession =
|
|
1652
|
+
currentSession = session3;
|
|
1157
1653
|
currentProjectPath = projectPath;
|
|
1158
1654
|
bestEffort("updateLastOpened", () => {
|
|
1159
1655
|
if (provider.updateLastOpened) provider.updateLastOpened(projectPath);
|
|
1160
1656
|
});
|
|
1161
|
-
bestEffort("sendStatus", () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210"));
|
|
1162
|
-
bestEffort("applyReferer", () => applyRefererFromSession(
|
|
1657
|
+
bestEffort("sendStatus", () => sendStatus("ready", gate?.statusMessage ?? "\u7F16\u8BD1\u5B8C\u6210"));
|
|
1658
|
+
bestEffort("applyReferer", () => applyRefererFromSession(session3));
|
|
1163
1659
|
return {
|
|
1164
1660
|
success: true,
|
|
1165
|
-
port:
|
|
1166
|
-
appInfo:
|
|
1661
|
+
port: session3.port,
|
|
1662
|
+
appInfo: session3.appInfo
|
|
1167
1663
|
};
|
|
1168
1664
|
},
|
|
1169
1665
|
async closeProject() {
|
|
1170
1666
|
clearSimulatorServicewechatReferer();
|
|
1171
1667
|
await disposeSession();
|
|
1668
|
+
if (currentProjectPath !== "") lastClosedProjectPath = currentProjectPath;
|
|
1172
1669
|
currentProjectPath = "";
|
|
1173
1670
|
ctx.views.disposeAll();
|
|
1174
1671
|
},
|
|
1175
1672
|
getSession: () => currentSession,
|
|
1176
1673
|
getProjectPath: () => currentProjectPath,
|
|
1674
|
+
getLastClosedProjectPath: () => lastClosedProjectPath,
|
|
1177
1675
|
hasActiveSession: () => currentSession !== null,
|
|
1178
1676
|
async captureThumbnail(projectPath) {
|
|
1179
1677
|
const wc = ctx.views.getSimulatorWebContents();
|
|
@@ -1213,29 +1711,29 @@ function createWorkspaceService(ctx) {
|
|
|
1213
1711
|
|
|
1214
1712
|
// src/main/services/projects/thumbnail.ts
|
|
1215
1713
|
import { createHash } from "crypto";
|
|
1216
|
-
import
|
|
1217
|
-
import
|
|
1714
|
+
import fs5 from "fs";
|
|
1715
|
+
import path9 from "path";
|
|
1218
1716
|
import { app as app7 } from "electron";
|
|
1219
1717
|
function getThumbnailDir() {
|
|
1220
|
-
return
|
|
1718
|
+
return path9.join(app7.getPath("userData"), "thumbnails");
|
|
1221
1719
|
}
|
|
1222
1720
|
function hashProjectPath(projectPath) {
|
|
1223
1721
|
return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
|
|
1224
1722
|
}
|
|
1225
1723
|
function getThumbnailPath(projectPath) {
|
|
1226
|
-
return
|
|
1724
|
+
return path9.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
|
|
1227
1725
|
}
|
|
1228
1726
|
var DATA_URL_PNG_PREFIX = "data:image/png;base64,";
|
|
1229
1727
|
function saveThumbnailFromDataUrl(projectPath, imageDataUrl) {
|
|
1230
1728
|
if (!imageDataUrl.startsWith(DATA_URL_PNG_PREFIX)) return;
|
|
1231
1729
|
const png = Buffer.from(imageDataUrl.slice(DATA_URL_PNG_PREFIX.length), "base64");
|
|
1232
|
-
|
|
1233
|
-
|
|
1730
|
+
fs5.mkdirSync(getThumbnailDir(), { recursive: true });
|
|
1731
|
+
fs5.writeFileSync(getThumbnailPath(projectPath), png);
|
|
1234
1732
|
}
|
|
1235
1733
|
function loadThumbnail(projectPath) {
|
|
1236
1734
|
const filePath = getThumbnailPath(projectPath);
|
|
1237
1735
|
try {
|
|
1238
|
-
const buf =
|
|
1736
|
+
const buf = fs5.readFileSync(filePath);
|
|
1239
1737
|
return `${DATA_URL_PNG_PREFIX}${buf.toString("base64")}`;
|
|
1240
1738
|
} catch {
|
|
1241
1739
|
return null;
|
|
@@ -1282,6 +1780,18 @@ function createSimulatorApiRegistry() {
|
|
|
1282
1780
|
}
|
|
1283
1781
|
|
|
1284
1782
|
// src/main/services/toolbar/toolbar-store.ts
|
|
1783
|
+
function toPublicAction(action) {
|
|
1784
|
+
const projected = {
|
|
1785
|
+
id: action.id,
|
|
1786
|
+
label: action.label
|
|
1787
|
+
};
|
|
1788
|
+
if (action.kind) projected.kind = action.kind;
|
|
1789
|
+
if (action.placement) projected.placement = action.placement;
|
|
1790
|
+
if (action.icon) projected.icon = action.icon;
|
|
1791
|
+
if (action.displayInitial) projected.displayInitial = action.displayInitial;
|
|
1792
|
+
if (action.avatarUrl) projected.avatarUrl = action.avatarUrl;
|
|
1793
|
+
return projected;
|
|
1794
|
+
}
|
|
1285
1795
|
function createToolbarStore() {
|
|
1286
1796
|
let actions = [];
|
|
1287
1797
|
return {
|
|
@@ -1296,7 +1806,7 @@ function createToolbarStore() {
|
|
|
1296
1806
|
actions = next.slice();
|
|
1297
1807
|
},
|
|
1298
1808
|
list() {
|
|
1299
|
-
return actions.map(
|
|
1809
|
+
return actions.map(toPublicAction);
|
|
1300
1810
|
},
|
|
1301
1811
|
getHandler(id) {
|
|
1302
1812
|
return actions.find((a) => a.id === id)?.handler;
|
|
@@ -1331,20 +1841,20 @@ function sanitizeTemplates(templates) {
|
|
|
1331
1841
|
}
|
|
1332
1842
|
|
|
1333
1843
|
// src/main/services/projects/builtin-templates.ts
|
|
1334
|
-
import
|
|
1335
|
-
var TEMPLATES_DIR =
|
|
1844
|
+
import path10 from "node:path";
|
|
1845
|
+
var TEMPLATES_DIR = path10.join(devtoolsPackageRoot, "templates");
|
|
1336
1846
|
var BUILTIN_TEMPLATES = [
|
|
1337
1847
|
{
|
|
1338
1848
|
id: "blank",
|
|
1339
1849
|
name: "Blank",
|
|
1340
1850
|
description: "\u6700\u5C0F\u9AA8\u67B6\uFF1Aapp.* + \u4E00\u4E2A index \u9875\u9762",
|
|
1341
|
-
source: { type: "directory", path:
|
|
1851
|
+
source: { type: "directory", path: path10.join(TEMPLATES_DIR, "blank") }
|
|
1342
1852
|
},
|
|
1343
1853
|
{
|
|
1344
1854
|
id: "taro-todo",
|
|
1345
1855
|
name: "Taro Todo",
|
|
1346
1856
|
description: "Taro \u7F16\u8BD1\u4EA7\u7269\u7684 Todo \u793A\u4F8B",
|
|
1347
|
-
source: { type: "directory", path:
|
|
1857
|
+
source: { type: "directory", path: path10.join(TEMPLATES_DIR, "taro-todo") }
|
|
1348
1858
|
}
|
|
1349
1859
|
];
|
|
1350
1860
|
|
|
@@ -1465,113 +1975,10 @@ function installAppMenu(ctx) {
|
|
|
1465
1975
|
}
|
|
1466
1976
|
|
|
1467
1977
|
// src/main/ipc/app.ts
|
|
1468
|
-
import
|
|
1469
|
-
|
|
1470
|
-
// src/main/utils/ipc-registry.ts
|
|
1471
|
-
import { ipcMain } from "electron";
|
|
1472
|
-
|
|
1473
|
-
// src/main/utils/ipc-schema.ts
|
|
1474
|
-
var IpcValidationError = class extends Error {
|
|
1475
|
-
channel;
|
|
1476
|
-
paths;
|
|
1477
|
-
constructor(channel, issues) {
|
|
1478
|
-
const paths = issues.map((i) => i.path.map((p) => String(p)).join(".") || "<root>");
|
|
1479
|
-
super(`Invalid IPC arguments for ${channel}`);
|
|
1480
|
-
this.name = "IpcValidationError";
|
|
1481
|
-
this.channel = channel;
|
|
1482
|
-
this.paths = paths;
|
|
1483
|
-
}
|
|
1484
|
-
};
|
|
1485
|
-
function validate(channel, schema, args) {
|
|
1486
|
-
const r = schema.safeParse(args);
|
|
1487
|
-
if (!r.success) {
|
|
1488
|
-
console.warn("[ipc] schema reject", channel, r.error.issues);
|
|
1489
|
-
throw new IpcValidationError(channel, r.error.issues);
|
|
1490
|
-
}
|
|
1491
|
-
return r.data;
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
// src/main/utils/ipc-registry.ts
|
|
1495
|
-
var log2 = createLogger("ipc");
|
|
1496
|
-
function reportListenerError(channel, err) {
|
|
1497
|
-
if (err instanceof IpcValidationError) {
|
|
1498
|
-
log2.warn(`schema reject on '${channel}' at [${err.paths.join(", ")}]`);
|
|
1499
|
-
return;
|
|
1500
|
-
}
|
|
1501
|
-
if (err instanceof Error) {
|
|
1502
|
-
log2.error(`listener threw on '${channel}': ${err.message}`, err.stack);
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
log2.error(`listener threw on '${channel}'`, err);
|
|
1506
|
-
}
|
|
1507
|
-
function isThenable(v) {
|
|
1508
|
-
return v != null && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
|
|
1509
|
-
}
|
|
1510
|
-
function summarizeSender(sender) {
|
|
1511
|
-
if (sender.isDestroyed()) return "<destroyed>";
|
|
1512
|
-
const url = sender.getURL();
|
|
1513
|
-
return `id=${sender.id} ${url.slice(0, 120)}`;
|
|
1514
|
-
}
|
|
1515
|
-
var IpcRegistry = class {
|
|
1516
|
-
constructor(policy) {
|
|
1517
|
-
this.policy = policy;
|
|
1518
|
-
}
|
|
1519
|
-
policy;
|
|
1520
|
-
registry = new DisposableRegistry();
|
|
1521
|
-
handle(channel, fn) {
|
|
1522
|
-
const policy = this.policy;
|
|
1523
|
-
const guarded = policy ? async (event, ...args) => {
|
|
1524
|
-
const sender = event.sender;
|
|
1525
|
-
if (!policy(sender)) {
|
|
1526
|
-
console.warn(
|
|
1527
|
-
`[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
|
|
1528
|
-
);
|
|
1529
|
-
throw new Error(`IPC sender rejected for channel ${channel}`);
|
|
1530
|
-
}
|
|
1531
|
-
return fn(event, ...args);
|
|
1532
|
-
} : fn;
|
|
1533
|
-
ipcMain.handle(channel, guarded);
|
|
1534
|
-
this.registry.add(() => ipcMain.removeHandler(channel));
|
|
1535
|
-
return this;
|
|
1536
|
-
}
|
|
1537
|
-
on(channel, fn) {
|
|
1538
|
-
const policy = this.policy;
|
|
1539
|
-
const raw = fn;
|
|
1540
|
-
const safeInvoke = (event, args) => {
|
|
1541
|
-
try {
|
|
1542
|
-
const ret = raw(event, ...args);
|
|
1543
|
-
if (isThenable(ret)) {
|
|
1544
|
-
Promise.resolve(ret).catch((err) => reportListenerError(channel, err));
|
|
1545
|
-
}
|
|
1546
|
-
} catch (err) {
|
|
1547
|
-
reportListenerError(channel, err);
|
|
1548
|
-
}
|
|
1549
|
-
};
|
|
1550
|
-
const guarded = policy ? (event, ...args) => {
|
|
1551
|
-
const sender = event.sender;
|
|
1552
|
-
if (!policy(sender)) {
|
|
1553
|
-
console.warn(
|
|
1554
|
-
`[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
|
|
1555
|
-
);
|
|
1556
|
-
return;
|
|
1557
|
-
}
|
|
1558
|
-
safeInvoke(event, args);
|
|
1559
|
-
} : (event, ...args) => safeInvoke(event, args);
|
|
1560
|
-
ipcMain.on(channel, guarded);
|
|
1561
|
-
this.registry.add(() => {
|
|
1562
|
-
ipcMain.removeListener(channel, guarded);
|
|
1563
|
-
});
|
|
1564
|
-
return this;
|
|
1565
|
-
}
|
|
1566
|
-
dispose() {
|
|
1567
|
-
return this.registry.dispose();
|
|
1568
|
-
}
|
|
1569
|
-
};
|
|
1570
|
-
|
|
1571
|
-
// src/main/ipc/app.ts
|
|
1978
|
+
import fs6 from "fs";
|
|
1572
1979
|
function registerAppIpc(ctx) {
|
|
1573
1980
|
return new IpcRegistry(ctx.senderPolicy).handle(AppChannel.GetPreloadPath, () => {
|
|
1574
|
-
return `file://${
|
|
1981
|
+
return `file://${fs6.realpathSync(ctx.preloadPath)}`;
|
|
1575
1982
|
}).handle(AppChannel.GetBranding, async () => {
|
|
1576
1983
|
if (ctx.brandingProvider) return ctx.brandingProvider();
|
|
1577
1984
|
return { appName: ctx.appName };
|
|
@@ -1581,71 +1988,80 @@ function registerAppIpc(ctx) {
|
|
|
1581
1988
|
}
|
|
1582
1989
|
|
|
1583
1990
|
// src/shared/ipc-schemas.ts
|
|
1584
|
-
import { z } from "zod";
|
|
1585
|
-
var AbsolutePath =
|
|
1991
|
+
import { z as z2 } from "zod";
|
|
1992
|
+
var AbsolutePath = z2.string().min(1).refine(
|
|
1586
1993
|
(p) => p.startsWith("/") || /^[a-zA-Z]:\\/.test(p) || /^[a-zA-Z]:\//.test(p),
|
|
1587
1994
|
"must be an absolute path"
|
|
1588
1995
|
);
|
|
1589
|
-
var PanelEvalSchema =
|
|
1590
|
-
var ProjectsAddSchema =
|
|
1591
|
-
var ProjectOpenSchema =
|
|
1592
|
-
var CompileConfigShape =
|
|
1593
|
-
startPage:
|
|
1594
|
-
scene:
|
|
1595
|
-
queryParams:
|
|
1996
|
+
var PanelEvalSchema = z2.tuple([z2.string().min(1).max(5e4)]);
|
|
1997
|
+
var ProjectsAddSchema = z2.tuple([AbsolutePath]);
|
|
1998
|
+
var ProjectOpenSchema = z2.tuple([AbsolutePath]);
|
|
1999
|
+
var CompileConfigShape = z2.looseObject({
|
|
2000
|
+
startPage: z2.string().optional(),
|
|
2001
|
+
scene: z2.number().optional(),
|
|
2002
|
+
queryParams: z2.array(z2.looseObject({ key: z2.string(), value: z2.string() })).optional()
|
|
1596
2003
|
});
|
|
1597
|
-
var ProjectSaveCompileConfigSchema =
|
|
2004
|
+
var ProjectSaveCompileConfigSchema = z2.tuple([
|
|
1598
2005
|
AbsolutePath,
|
|
1599
2006
|
CompileConfigShape
|
|
1600
2007
|
]);
|
|
1601
|
-
var PopoverShowSchema =
|
|
1602
|
-
var SimWidth =
|
|
1603
|
-
var SimulatorAttachSchema =
|
|
1604
|
-
|
|
2008
|
+
var PopoverShowSchema = z2.tuple([z2.looseObject({})]);
|
|
2009
|
+
var SimWidth = z2.number().int().min(100).max(2e3);
|
|
2010
|
+
var SimulatorAttachSchema = z2.tuple([
|
|
2011
|
+
z2.number().int().positive(),
|
|
1605
2012
|
SimWidth
|
|
1606
2013
|
]);
|
|
1607
|
-
var SimulatorResizeSchema =
|
|
1608
|
-
var
|
|
1609
|
-
var
|
|
1610
|
-
|
|
1611
|
-
|
|
2014
|
+
var SimulatorResizeSchema = z2.tuple([SimWidth]);
|
|
2015
|
+
var NonNegInt = z2.number().int().min(0).max(1e5);
|
|
2016
|
+
var ViewBoundsSchema = z2.tuple([
|
|
2017
|
+
z2.object({
|
|
2018
|
+
x: NonNegInt,
|
|
2019
|
+
y: NonNegInt,
|
|
2020
|
+
width: NonNegInt,
|
|
2021
|
+
height: NonNegInt
|
|
2022
|
+
})
|
|
2023
|
+
]);
|
|
2024
|
+
var SimulatorSetVisibleSchema = z2.tuple([z2.boolean(), SimWidth]);
|
|
2025
|
+
var SimulatorCustomApiInvokeSchema = z2.tuple([
|
|
2026
|
+
z2.string().min(1).max(256),
|
|
2027
|
+
z2.unknown()
|
|
1612
2028
|
]);
|
|
1613
|
-
var ToolbarInvokeSchema =
|
|
1614
|
-
var ProjectGetPagesSchema =
|
|
1615
|
-
var ProjectGetCompileConfigSchema =
|
|
1616
|
-
var ProjectsRemoveSchema =
|
|
1617
|
-
var WorkbenchSettingsSaveSchema =
|
|
1618
|
-
|
|
1619
|
-
cdp:
|
|
1620
|
-
enabled:
|
|
1621
|
-
port:
|
|
2029
|
+
var ToolbarInvokeSchema = z2.tuple([z2.string().min(1).max(256)]);
|
|
2030
|
+
var ProjectGetPagesSchema = z2.tuple([AbsolutePath]);
|
|
2031
|
+
var ProjectGetCompileConfigSchema = z2.tuple([AbsolutePath]);
|
|
2032
|
+
var ProjectsRemoveSchema = z2.tuple([AbsolutePath]);
|
|
2033
|
+
var WorkbenchSettingsSaveSchema = z2.tuple([
|
|
2034
|
+
z2.looseObject({
|
|
2035
|
+
cdp: z2.looseObject({
|
|
2036
|
+
enabled: z2.boolean(),
|
|
2037
|
+
port: z2.number().int().min(0).max(65535)
|
|
1622
2038
|
}),
|
|
1623
|
-
mcp:
|
|
1624
|
-
enabled:
|
|
1625
|
-
port:
|
|
2039
|
+
mcp: z2.looseObject({
|
|
2040
|
+
enabled: z2.boolean(),
|
|
2041
|
+
port: z2.number().int().min(0).max(65535)
|
|
1626
2042
|
}),
|
|
1627
|
-
compile:
|
|
1628
|
-
watch:
|
|
2043
|
+
compile: z2.looseObject({
|
|
2044
|
+
watch: z2.boolean()
|
|
1629
2045
|
}),
|
|
1630
|
-
theme:
|
|
1631
|
-
lastCreateBaseDir:
|
|
2046
|
+
theme: z2.enum(["system", "light", "dark"]),
|
|
2047
|
+
lastCreateBaseDir: z2.union([z2.string(), z2.null()])
|
|
1632
2048
|
})
|
|
1633
2049
|
]);
|
|
1634
|
-
var WorkbenchSettingsSetThemeSchema =
|
|
1635
|
-
|
|
2050
|
+
var WorkbenchSettingsSetThemeSchema = z2.tuple([
|
|
2051
|
+
z2.enum(["system", "light", "dark"])
|
|
1636
2052
|
]);
|
|
1637
|
-
var WorkbenchSettingsSetVisibleSchema =
|
|
1638
|
-
var SettingsSetVisibleSchema =
|
|
1639
|
-
var SettingsConfigChangedSchema =
|
|
1640
|
-
var PopoverRelaunchSchema =
|
|
1641
|
-
var SettingsProjectSettingsChangedSchema =
|
|
1642
|
-
|
|
1643
|
-
uploadWithSourceMap:
|
|
2053
|
+
var WorkbenchSettingsSetVisibleSchema = z2.tuple([z2.boolean()]);
|
|
2054
|
+
var SettingsSetVisibleSchema = z2.tuple([z2.boolean()]);
|
|
2055
|
+
var SettingsConfigChangedSchema = z2.tuple([CompileConfigShape]);
|
|
2056
|
+
var PopoverRelaunchSchema = z2.tuple([CompileConfigShape]);
|
|
2057
|
+
var SettingsProjectSettingsChangedSchema = z2.tuple([
|
|
2058
|
+
z2.looseObject({
|
|
2059
|
+
uploadWithSourceMap: z2.boolean().optional()
|
|
1644
2060
|
})
|
|
1645
2061
|
]);
|
|
1646
|
-
var PanelSelectSchema =
|
|
1647
|
-
var ProjectCaptureThumbnailSchema =
|
|
1648
|
-
var ProjectGetThumbnailSchema =
|
|
2062
|
+
var PanelSelectSchema = z2.tuple([z2.string().min(1).max(200)]);
|
|
2063
|
+
var ProjectCaptureThumbnailSchema = z2.tuple([AbsolutePath]);
|
|
2064
|
+
var ProjectGetThumbnailSchema = z2.tuple([AbsolutePath]);
|
|
1649
2065
|
|
|
1650
2066
|
// src/main/ipc/simulator.ts
|
|
1651
2067
|
function registerSimulatorIpc(ctx) {
|
|
@@ -1718,11 +2134,11 @@ var popoverModule = {
|
|
|
1718
2134
|
|
|
1719
2135
|
// src/main/ipc/projects.ts
|
|
1720
2136
|
import { app as app8, dialog } from "electron";
|
|
1721
|
-
import
|
|
2137
|
+
import path12 from "path";
|
|
1722
2138
|
|
|
1723
2139
|
// src/main/services/projects/create-project-service.ts
|
|
1724
|
-
import
|
|
1725
|
-
import
|
|
2140
|
+
import fs7 from "node:fs";
|
|
2141
|
+
import path11 from "node:path";
|
|
1726
2142
|
var DEFAULT_TEMPLATE_ID = "blank";
|
|
1727
2143
|
async function createProject(input, ctx) {
|
|
1728
2144
|
const name = (input.name ?? "").trim();
|
|
@@ -1733,12 +2149,12 @@ async function createProject(input, ctx) {
|
|
|
1733
2149
|
if (!target || typeof target !== "string") {
|
|
1734
2150
|
throw new Error("Project path is required");
|
|
1735
2151
|
}
|
|
1736
|
-
if (
|
|
1737
|
-
const stat =
|
|
2152
|
+
if (fs7.existsSync(target)) {
|
|
2153
|
+
const stat = fs7.statSync(target);
|
|
1738
2154
|
if (!stat.isDirectory()) {
|
|
1739
2155
|
throw new Error(`Project path exists and is not a directory: ${target}`);
|
|
1740
2156
|
}
|
|
1741
|
-
const entries =
|
|
2157
|
+
const entries = fs7.readdirSync(target);
|
|
1742
2158
|
if (entries.length > 0) {
|
|
1743
2159
|
throw new Error(
|
|
1744
2160
|
`Project path is not empty (refusing to overwrite): ${target}`
|
|
@@ -1755,31 +2171,31 @@ async function createProject(input, ctx) {
|
|
|
1755
2171
|
`Template '${templateId}' has neither a source directory nor a generate function`
|
|
1756
2172
|
);
|
|
1757
2173
|
}
|
|
1758
|
-
|
|
2174
|
+
fs7.mkdirSync(target, { recursive: true });
|
|
1759
2175
|
if (template.generate) {
|
|
1760
2176
|
await template.generate(target, { name });
|
|
1761
2177
|
} else if (template.source) {
|
|
1762
|
-
if (!
|
|
2178
|
+
if (!fs7.existsSync(template.source.path)) {
|
|
1763
2179
|
throw new Error(
|
|
1764
2180
|
`Template source missing on disk: ${template.source.path}`
|
|
1765
2181
|
);
|
|
1766
2182
|
}
|
|
1767
|
-
|
|
2183
|
+
fs7.cpSync(template.source.path, target, {
|
|
1768
2184
|
recursive: true,
|
|
1769
2185
|
force: true
|
|
1770
2186
|
});
|
|
1771
2187
|
}
|
|
1772
|
-
const cfgPath =
|
|
2188
|
+
const cfgPath = path11.join(target, "project.config.json");
|
|
1773
2189
|
let cfg = {};
|
|
1774
|
-
if (
|
|
2190
|
+
if (fs7.existsSync(cfgPath)) {
|
|
1775
2191
|
try {
|
|
1776
|
-
cfg = JSON.parse(
|
|
2192
|
+
cfg = JSON.parse(fs7.readFileSync(cfgPath, "utf-8"));
|
|
1777
2193
|
} catch {
|
|
1778
2194
|
cfg = {};
|
|
1779
2195
|
}
|
|
1780
2196
|
}
|
|
1781
2197
|
cfg.projectname = name;
|
|
1782
|
-
|
|
2198
|
+
fs7.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
1783
2199
|
const created = await ctx.projectsProvider.addProject(target);
|
|
1784
2200
|
return created;
|
|
1785
2201
|
}
|
|
@@ -1855,7 +2271,7 @@ function registerProjectsIpc(ctx) {
|
|
|
1855
2271
|
}
|
|
1856
2272
|
try {
|
|
1857
2273
|
const settings = loadWorkbenchSettings();
|
|
1858
|
-
const newBase =
|
|
2274
|
+
const newBase = path12.dirname(input.path);
|
|
1859
2275
|
if (newBase && settings.lastCreateBaseDir !== newBase) {
|
|
1860
2276
|
saveWorkbenchSettings({ ...settings, lastCreateBaseDir: newBase });
|
|
1861
2277
|
}
|
|
@@ -2049,6 +2465,18 @@ function registerToolbarIpc(ctx) {
|
|
|
2049
2465
|
});
|
|
2050
2466
|
}
|
|
2051
2467
|
|
|
2468
|
+
// src/main/ipc/views.ts
|
|
2469
|
+
function registerViewsIpc(ctx) {
|
|
2470
|
+
return new IpcRegistry(ctx.senderPolicy).handle(ViewChannel.SimulatorDevtoolsBounds, (_event, ...args) => {
|
|
2471
|
+
const [bounds] = validate(
|
|
2472
|
+
ViewChannel.SimulatorDevtoolsBounds,
|
|
2473
|
+
ViewBoundsSchema,
|
|
2474
|
+
args
|
|
2475
|
+
);
|
|
2476
|
+
ctx.views.setSimulatorDevtoolsBounds(bounds);
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2052
2480
|
// src/main/ipc/simulator-module.ts
|
|
2053
2481
|
var simulatorModule = {
|
|
2054
2482
|
setup: (ctx) => {
|
|
@@ -2056,6 +2484,7 @@ var simulatorModule = {
|
|
|
2056
2484
|
reg.add(registerSimulatorIpc(ctx));
|
|
2057
2485
|
reg.add(registerPanelsIpc(ctx));
|
|
2058
2486
|
reg.add(registerToolbarIpc(ctx));
|
|
2487
|
+
reg.add(registerViewsIpc(ctx));
|
|
2059
2488
|
return reg;
|
|
2060
2489
|
}
|
|
2061
2490
|
};
|
|
@@ -2749,7 +3178,7 @@ function getClient(kind) {
|
|
|
2749
3178
|
}
|
|
2750
3179
|
|
|
2751
3180
|
// src/main/services/mcp/tool-registry.ts
|
|
2752
|
-
import { z as
|
|
3181
|
+
import { z as z3 } from "zod";
|
|
2753
3182
|
function registerCommonTargetTools(server, kind) {
|
|
2754
3183
|
const descriptions = kind === "simulator" ? {
|
|
2755
3184
|
screenshot: "Take a screenshot of the simulator webview",
|
|
@@ -2770,9 +3199,9 @@ function registerCommonTargetTools(server, kind) {
|
|
|
2770
3199
|
return { content: [{ type: "image", data, mimeType: "image/png" }] };
|
|
2771
3200
|
});
|
|
2772
3201
|
server.tool(`${kind}_console_logs`, descriptions.consoleLogs, {
|
|
2773
|
-
limit:
|
|
2774
|
-
level:
|
|
2775
|
-
sinceTimestamp:
|
|
3202
|
+
limit: z3.number().optional().default(50).describe("Maximum number of log entries"),
|
|
3203
|
+
level: z3.enum(["error", "warning", "warn", "info", "log", "debug"]).optional().describe("Only return entries with this level"),
|
|
3204
|
+
sinceTimestamp: z3.string().optional().describe("ISO timestamp; only return entries at or after this time")
|
|
2776
3205
|
}, async ({ limit, level, sinceTimestamp }) => {
|
|
2777
3206
|
getClient(kind);
|
|
2778
3207
|
let entries = getTargetState(kind).consoleLogs;
|
|
@@ -2787,7 +3216,7 @@ function registerCommonTargetTools(server, kind) {
|
|
|
2787
3216
|
});
|
|
2788
3217
|
if (kind !== "workbench") {
|
|
2789
3218
|
server.tool(`${kind}_evaluate`, descriptions.evaluate, {
|
|
2790
|
-
expression:
|
|
3219
|
+
expression: z3.string().describe("JavaScript expression to evaluate")
|
|
2791
3220
|
}, async ({ expression }) => {
|
|
2792
3221
|
const c = getClient(kind);
|
|
2793
3222
|
const result = await c.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true });
|
|
@@ -2800,15 +3229,15 @@ ${JSON.stringify(result.exceptionDetails, null, 2)}` }], isError: true };
|
|
|
2800
3229
|
});
|
|
2801
3230
|
}
|
|
2802
3231
|
server.tool(`${kind}_get_dom`, descriptions.getDom, {
|
|
2803
|
-
depth:
|
|
3232
|
+
depth: z3.number().optional().default(3).describe("Maximum depth")
|
|
2804
3233
|
}, async ({ depth }) => {
|
|
2805
3234
|
const c = getClient(kind);
|
|
2806
3235
|
const { root } = await c.DOM.getDocument({ depth });
|
|
2807
3236
|
return { content: [{ type: "text", text: JSON.stringify(root, null, 2) }] };
|
|
2808
3237
|
});
|
|
2809
3238
|
server.tool(`${kind}_network_log`, descriptions.networkLog, {
|
|
2810
|
-
limit:
|
|
2811
|
-
minStatus:
|
|
3239
|
+
limit: z3.number().optional().default(20).describe("Maximum number of entries"),
|
|
3240
|
+
minStatus: z3.number().optional().describe("Only return entries with status >= minStatus (status 0 = failed before response is included when minStatus >= 400)")
|
|
2812
3241
|
}, async ({ limit, minStatus }) => {
|
|
2813
3242
|
getClient(kind);
|
|
2814
3243
|
let entries = getTargetState(kind).networkRequests;
|
|
@@ -3051,11 +3480,11 @@ function registerContextTools(server) {
|
|
|
3051
3480
|
}
|
|
3052
3481
|
|
|
3053
3482
|
// src/main/services/mcp/tools/simulator-tools.ts
|
|
3054
|
-
import { z as
|
|
3483
|
+
import { z as z4 } from "zod";
|
|
3055
3484
|
function registerSimulatorTools(server) {
|
|
3056
3485
|
server.tool("simulator_navigate", "Navigate the simulator to a URL, or reload the current page", {
|
|
3057
|
-
url:
|
|
3058
|
-
reload:
|
|
3486
|
+
url: z4.string().optional().describe("URL to navigate to"),
|
|
3487
|
+
reload: z4.boolean().optional().describe("If true, reload current page instead of navigating")
|
|
3059
3488
|
}, async ({ url, reload }) => {
|
|
3060
3489
|
const c = getClient("simulator");
|
|
3061
3490
|
if (reload) {
|
|
@@ -3070,15 +3499,15 @@ function registerSimulatorTools(server) {
|
|
|
3070
3499
|
return { content: [{ type: "text", text: "Error: either url or reload is required" }], isError: true };
|
|
3071
3500
|
});
|
|
3072
3501
|
server.tool("simulator_input", "Dispatch input to the simulator. `tap_coord` requires x and y. `tap_selector` requires selector and optional nth. `type` requires selector and text. `scroll` requires x, y, deltaX, and deltaY. `key` is best-effort for common keys such as Enter, Escape, Tab, Arrow* and Space.", {
|
|
3073
|
-
action:
|
|
3074
|
-
x:
|
|
3075
|
-
y:
|
|
3076
|
-
selector:
|
|
3077
|
-
nth:
|
|
3078
|
-
text:
|
|
3079
|
-
key:
|
|
3080
|
-
deltaX:
|
|
3081
|
-
deltaY:
|
|
3502
|
+
action: z4.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
|
|
3503
|
+
x: z4.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
|
|
3504
|
+
y: z4.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
|
|
3505
|
+
selector: z4.string().optional().describe("CSS selector for `tap_selector` or `type`"),
|
|
3506
|
+
nth: z4.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
|
|
3507
|
+
text: z4.string().optional().describe("Text to insert for `type`"),
|
|
3508
|
+
key: z4.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
|
|
3509
|
+
deltaX: z4.number().optional().describe("Horizontal wheel delta for `scroll`"),
|
|
3510
|
+
deltaY: z4.number().optional().describe("Vertical wheel delta for `scroll`")
|
|
3082
3511
|
}, async ({ action, x, y, selector, nth, text, key, deltaX, deltaY }) => {
|
|
3083
3512
|
const c = getClient("simulator");
|
|
3084
3513
|
const err = (msg) => ({ content: [{ type: "text", text: `Error: ${msg}` }], isError: true });
|
|
@@ -3532,6 +3961,527 @@ async function clearElementInspection() {
|
|
|
3532
3961
|
}
|
|
3533
3962
|
}
|
|
3534
3963
|
|
|
3964
|
+
// src/main/services/simulator-temp-files/store.ts
|
|
3965
|
+
function registerTempFile(store, path18, mime, bytes) {
|
|
3966
|
+
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
|
|
3967
|
+
store.delete(path18);
|
|
3968
|
+
store.set(path18, { bytes: buf, mime });
|
|
3969
|
+
}
|
|
3970
|
+
function revokeTempFile(store, path18) {
|
|
3971
|
+
store.delete(path18);
|
|
3972
|
+
}
|
|
3973
|
+
function revokeAllTempFiles(store) {
|
|
3974
|
+
store.clear();
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
// src/shared/vpath.ts
|
|
3978
|
+
import os from "node:os";
|
|
3979
|
+
import path13 from "node:path";
|
|
3980
|
+
var DIFILE_PREFIX = "difile://";
|
|
3981
|
+
function sandboxBase() {
|
|
3982
|
+
const env = typeof process !== "undefined" && process.env && process.env.DIMINA_HOME || "";
|
|
3983
|
+
if (env) {
|
|
3984
|
+
return path13.join(env, "files");
|
|
3985
|
+
}
|
|
3986
|
+
return path13.join(os.homedir(), ".dimina", "files");
|
|
3987
|
+
}
|
|
3988
|
+
function resolveVPath(url) {
|
|
3989
|
+
if (typeof url !== "string") return null;
|
|
3990
|
+
if (!url.startsWith(DIFILE_PREFIX)) return null;
|
|
3991
|
+
const raw = url.slice(DIFILE_PREFIX.length);
|
|
3992
|
+
if (raw.length === 0) return null;
|
|
3993
|
+
let decoded;
|
|
3994
|
+
try {
|
|
3995
|
+
decoded = decodeURIComponent(raw);
|
|
3996
|
+
} catch {
|
|
3997
|
+
return null;
|
|
3998
|
+
}
|
|
3999
|
+
if (decoded.includes("\0")) return null;
|
|
4000
|
+
decoded = decoded.replace(/^[/\\]+/, "");
|
|
4001
|
+
if (decoded.length === 0) return null;
|
|
4002
|
+
const segments = decoded.split(/[/\\]+/);
|
|
4003
|
+
if (segments.some((s) => s === ".." || s === ".")) return null;
|
|
4004
|
+
let kind;
|
|
4005
|
+
let writable;
|
|
4006
|
+
if (decoded.startsWith("_tmp/")) {
|
|
4007
|
+
kind = "tmp";
|
|
4008
|
+
writable = false;
|
|
4009
|
+
} else if (decoded.startsWith("_store/")) {
|
|
4010
|
+
kind = "store";
|
|
4011
|
+
writable = false;
|
|
4012
|
+
} else {
|
|
4013
|
+
kind = "usr";
|
|
4014
|
+
writable = true;
|
|
4015
|
+
}
|
|
4016
|
+
if (kind === "tmp") {
|
|
4017
|
+
return { kind, writable };
|
|
4018
|
+
}
|
|
4019
|
+
const base = sandboxBase();
|
|
4020
|
+
const joined = path13.join(base, decoded);
|
|
4021
|
+
const normalized = path13.normalize(joined);
|
|
4022
|
+
if (normalized !== base && !normalized.startsWith(base + path13.sep)) return null;
|
|
4023
|
+
return { kind, writable, realPath: normalized };
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
// src/main/services/simulator-temp-files/disk.ts
|
|
4027
|
+
import fs8 from "node:fs/promises";
|
|
4028
|
+
import path14 from "node:path";
|
|
4029
|
+
var EXT_MIME = {
|
|
4030
|
+
".txt": "text/plain",
|
|
4031
|
+
".log": "text/plain",
|
|
4032
|
+
".md": "text/markdown",
|
|
4033
|
+
".html": "text/html",
|
|
4034
|
+
".htm": "text/html",
|
|
4035
|
+
".css": "text/css",
|
|
4036
|
+
".csv": "text/csv",
|
|
4037
|
+
".json": "application/json",
|
|
4038
|
+
".xml": "application/xml",
|
|
4039
|
+
".js": "application/javascript",
|
|
4040
|
+
".mjs": "application/javascript",
|
|
4041
|
+
".svg": "image/svg+xml",
|
|
4042
|
+
".png": "image/png",
|
|
4043
|
+
".jpg": "image/jpeg",
|
|
4044
|
+
".jpeg": "image/jpeg",
|
|
4045
|
+
".gif": "image/gif",
|
|
4046
|
+
".webp": "image/webp",
|
|
4047
|
+
".bmp": "image/bmp",
|
|
4048
|
+
".ico": "image/x-icon",
|
|
4049
|
+
".mp4": "video/mp4",
|
|
4050
|
+
".m4v": "video/mp4",
|
|
4051
|
+
".webm": "video/webm",
|
|
4052
|
+
".mov": "video/quicktime",
|
|
4053
|
+
".mp3": "audio/mpeg",
|
|
4054
|
+
".wav": "audio/wav",
|
|
4055
|
+
".ogg": "audio/ogg",
|
|
4056
|
+
".pdf": "application/pdf",
|
|
4057
|
+
".zip": "application/zip"
|
|
4058
|
+
};
|
|
4059
|
+
function sniffMime(head) {
|
|
4060
|
+
if (head.length >= 8 && head[0] === 137 && head[1] === 80 && head[2] === 78 && head[3] === 71 && head[4] === 13 && head[5] === 10 && head[6] === 26 && head[7] === 10) {
|
|
4061
|
+
return "image/png";
|
|
4062
|
+
}
|
|
4063
|
+
if (head.length >= 3 && head[0] === 255 && head[1] === 216 && head[2] === 255) {
|
|
4064
|
+
return "image/jpeg";
|
|
4065
|
+
}
|
|
4066
|
+
if (head.length >= 6 && head[0] === 71 && head[1] === 73 && head[2] === 70 && head[3] === 56 && (head[4] === 55 || head[4] === 57) && head[5] === 97) {
|
|
4067
|
+
return "image/gif";
|
|
4068
|
+
}
|
|
4069
|
+
if (head.length >= 12 && head[0] === 82 && head[1] === 73 && head[2] === 70 && head[3] === 70 && head[8] === 87 && head[9] === 69 && head[10] === 66 && head[11] === 80) {
|
|
4070
|
+
return "image/webp";
|
|
4071
|
+
}
|
|
4072
|
+
if (head.length >= 12 && head[4] === 102 && head[5] === 116 && head[6] === 121 && head[7] === 112) {
|
|
4073
|
+
return "video/mp4";
|
|
4074
|
+
}
|
|
4075
|
+
if (head.length >= 4 && head[0] === 37 && head[1] === 80 && head[2] === 68 && head[3] === 70) {
|
|
4076
|
+
return "application/pdf";
|
|
4077
|
+
}
|
|
4078
|
+
if (head.length >= 2 && head[0] === 66 && head[1] === 77) {
|
|
4079
|
+
return "image/bmp";
|
|
4080
|
+
}
|
|
4081
|
+
if (head.length >= 4 && head[0] === 80 && head[1] === 75 && head[2] === 3 && head[3] === 4) {
|
|
4082
|
+
return "application/zip";
|
|
4083
|
+
}
|
|
4084
|
+
if (head.length >= 3 && head[0] === 73 && head[1] === 68 && head[2] === 51) {
|
|
4085
|
+
return "audio/mpeg";
|
|
4086
|
+
}
|
|
4087
|
+
if (head.length >= 4 && head[0] === 79 && head[1] === 103 && head[2] === 103 && head[3] === 83) {
|
|
4088
|
+
return "audio/ogg";
|
|
4089
|
+
}
|
|
4090
|
+
return null;
|
|
4091
|
+
}
|
|
4092
|
+
function extMime(realPath) {
|
|
4093
|
+
const ext = path14.extname(realPath).toLowerCase();
|
|
4094
|
+
if (!ext) return null;
|
|
4095
|
+
return EXT_MIME[ext] ?? null;
|
|
4096
|
+
}
|
|
4097
|
+
function detectMime(realPath, head) {
|
|
4098
|
+
const sniffed = sniffMime(head);
|
|
4099
|
+
if (sniffed) return sniffed;
|
|
4100
|
+
const byExt = extMime(realPath);
|
|
4101
|
+
if (byExt) return byExt;
|
|
4102
|
+
return "application/octet-stream";
|
|
4103
|
+
}
|
|
4104
|
+
function etagOf(mtimeMs, size) {
|
|
4105
|
+
return `W/"${Math.floor(mtimeMs)}-${size}"`;
|
|
4106
|
+
}
|
|
4107
|
+
async function readDiskFile(realPath, opts) {
|
|
4108
|
+
const handle = await fs8.open(realPath, "r");
|
|
4109
|
+
try {
|
|
4110
|
+
const st = await handle.stat();
|
|
4111
|
+
const totalSize = st.size;
|
|
4112
|
+
const mtimeMs = st.mtimeMs;
|
|
4113
|
+
const range = opts?.range;
|
|
4114
|
+
let sliceStart = 0;
|
|
4115
|
+
let sliceLen = totalSize;
|
|
4116
|
+
if (range) {
|
|
4117
|
+
const { start, end } = range;
|
|
4118
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
|
4119
|
+
throw new RangeError(`invalid range: ${start}-${end}`);
|
|
4120
|
+
}
|
|
4121
|
+
if (start < 0) throw new RangeError(`range start out of bounds: ${start}`);
|
|
4122
|
+
if (start > end) throw new RangeError(`range start > end: ${start} > ${end}`);
|
|
4123
|
+
if (start >= totalSize) {
|
|
4124
|
+
throw new RangeError(`range start beyond file size: ${start} >= ${totalSize}`);
|
|
4125
|
+
}
|
|
4126
|
+
const clampedEnd = Math.min(end, totalSize - 1);
|
|
4127
|
+
sliceStart = start;
|
|
4128
|
+
sliceLen = clampedEnd - start + 1;
|
|
4129
|
+
}
|
|
4130
|
+
const head = Buffer.alloc(Math.min(12, totalSize));
|
|
4131
|
+
if (head.length > 0) {
|
|
4132
|
+
await handle.read(head, 0, head.length, 0);
|
|
4133
|
+
}
|
|
4134
|
+
let bytes;
|
|
4135
|
+
if (!range) {
|
|
4136
|
+
bytes = Buffer.alloc(totalSize);
|
|
4137
|
+
if (totalSize > 0) {
|
|
4138
|
+
if (totalSize <= head.length) {
|
|
4139
|
+
head.copy(bytes, 0, 0, totalSize);
|
|
4140
|
+
} else {
|
|
4141
|
+
head.copy(bytes, 0, 0, head.length);
|
|
4142
|
+
await handle.read(bytes, head.length, totalSize - head.length, head.length);
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
} else {
|
|
4146
|
+
bytes = Buffer.alloc(sliceLen);
|
|
4147
|
+
if (sliceLen > 0) {
|
|
4148
|
+
await handle.read(bytes, 0, sliceLen, sliceStart);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
return {
|
|
4152
|
+
bytes,
|
|
4153
|
+
mime: detectMime(realPath, head),
|
|
4154
|
+
etag: etagOf(mtimeMs, totalSize),
|
|
4155
|
+
totalSize
|
|
4156
|
+
};
|
|
4157
|
+
} finally {
|
|
4158
|
+
await handle.close();
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
async function writeDiskFile(realPath, bytes) {
|
|
4162
|
+
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
|
|
4163
|
+
await fs8.mkdir(path14.dirname(realPath), { recursive: true });
|
|
4164
|
+
await fs8.writeFile(realPath, buf);
|
|
4165
|
+
}
|
|
4166
|
+
async function readDiskDir(realPath) {
|
|
4167
|
+
const entries = await fs8.readdir(realPath);
|
|
4168
|
+
return entries;
|
|
4169
|
+
}
|
|
4170
|
+
async function statDiskFile(realPath) {
|
|
4171
|
+
const st = await fs8.stat(realPath);
|
|
4172
|
+
return {
|
|
4173
|
+
size: st.size,
|
|
4174
|
+
mtime: Math.floor(st.mtimeMs),
|
|
4175
|
+
mode: st.mode,
|
|
4176
|
+
isFile: st.isFile(),
|
|
4177
|
+
isDirectory: st.isDirectory()
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
// src/main/services/simulator-temp-files/request-handler.ts
|
|
4182
|
+
var CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
4183
|
+
function getHeader(headers, name) {
|
|
4184
|
+
if (!headers) return void 0;
|
|
4185
|
+
const lc = name.toLowerCase();
|
|
4186
|
+
for (const k of Object.keys(headers)) {
|
|
4187
|
+
if (k.toLowerCase() === lc) return headers[k];
|
|
4188
|
+
}
|
|
4189
|
+
return void 0;
|
|
4190
|
+
}
|
|
4191
|
+
function etagsMatch(a, b) {
|
|
4192
|
+
if (!a) return false;
|
|
4193
|
+
const stripped = (s) => s.startsWith("W/") ? s.slice(2) : s;
|
|
4194
|
+
return stripped(a.trim()) === stripped(b);
|
|
4195
|
+
}
|
|
4196
|
+
function parseRange(header, totalSize) {
|
|
4197
|
+
if (!header) return null;
|
|
4198
|
+
const m = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
|
|
4199
|
+
if (!m) return null;
|
|
4200
|
+
const startStr = m[1];
|
|
4201
|
+
const endStr = m[2];
|
|
4202
|
+
if (startStr === "" && endStr === "") return null;
|
|
4203
|
+
if (startStr === "") {
|
|
4204
|
+
const n = Number(endStr);
|
|
4205
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
4206
|
+
const start2 = Math.max(0, totalSize - n);
|
|
4207
|
+
return { start: start2, end: totalSize - 1 };
|
|
4208
|
+
}
|
|
4209
|
+
const start = Number(startStr);
|
|
4210
|
+
if (!Number.isFinite(start) || start < 0) return null;
|
|
4211
|
+
const end = endStr === "" ? totalSize - 1 : Number(endStr);
|
|
4212
|
+
if (!Number.isFinite(end)) return null;
|
|
4213
|
+
return { start, end };
|
|
4214
|
+
}
|
|
4215
|
+
function notFound() {
|
|
4216
|
+
return new Response(null, { status: 404 });
|
|
4217
|
+
}
|
|
4218
|
+
function notModified(etag) {
|
|
4219
|
+
return new Response(null, {
|
|
4220
|
+
status: 304,
|
|
4221
|
+
headers: {
|
|
4222
|
+
ETag: etag,
|
|
4223
|
+
"Cache-Control": CACHE_CONTROL,
|
|
4224
|
+
"Access-Control-Allow-Origin": "*"
|
|
4225
|
+
}
|
|
4226
|
+
});
|
|
4227
|
+
}
|
|
4228
|
+
function bufferToBody(bytes) {
|
|
4229
|
+
const out = new ArrayBuffer(bytes.byteLength);
|
|
4230
|
+
new Uint8Array(out).set(bytes);
|
|
4231
|
+
return out;
|
|
4232
|
+
}
|
|
4233
|
+
function tempBody(bytes, mime) {
|
|
4234
|
+
return new Response(bufferToBody(bytes), {
|
|
4235
|
+
status: 200,
|
|
4236
|
+
headers: {
|
|
4237
|
+
"Content-Type": mime,
|
|
4238
|
+
"Cache-Control": CACHE_CONTROL,
|
|
4239
|
+
"Access-Control-Allow-Origin": "*"
|
|
4240
|
+
}
|
|
4241
|
+
});
|
|
4242
|
+
}
|
|
4243
|
+
async function handleDifileRequest(ctx, req) {
|
|
4244
|
+
const v = resolveVPath(req.url);
|
|
4245
|
+
if (!v) return notFound();
|
|
4246
|
+
if (v.kind === "tmp") {
|
|
4247
|
+
const record = ctx.tempStore.get(req.url);
|
|
4248
|
+
if (!record) return notFound();
|
|
4249
|
+
return tempBody(record.bytes, record.mime || "application/octet-stream");
|
|
4250
|
+
}
|
|
4251
|
+
if (!v.realPath) return notFound();
|
|
4252
|
+
try {
|
|
4253
|
+
const probe = await readDiskFile(v.realPath);
|
|
4254
|
+
const etag = probe.etag;
|
|
4255
|
+
const totalSize = probe.totalSize;
|
|
4256
|
+
const mime = probe.mime;
|
|
4257
|
+
const ifNoneMatch = getHeader(req.headers, "If-None-Match");
|
|
4258
|
+
if (etagsMatch(ifNoneMatch, etag)) {
|
|
4259
|
+
return notModified(etag);
|
|
4260
|
+
}
|
|
4261
|
+
const rangeHeader = getHeader(req.headers, "Range");
|
|
4262
|
+
const range = parseRange(rangeHeader, totalSize);
|
|
4263
|
+
if (range) {
|
|
4264
|
+
if (range.start < 0 || range.start >= totalSize || range.end < range.start) {
|
|
4265
|
+
return new Response(null, {
|
|
4266
|
+
status: 416,
|
|
4267
|
+
headers: {
|
|
4268
|
+
"Content-Range": `bytes */${totalSize}`,
|
|
4269
|
+
"Access-Control-Allow-Origin": "*"
|
|
4270
|
+
}
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
const clampedEnd = Math.min(range.end, totalSize - 1);
|
|
4274
|
+
const sliced = await readDiskFile(v.realPath, { range: { start: range.start, end: clampedEnd } });
|
|
4275
|
+
const { bytes } = sliced;
|
|
4276
|
+
const lastByte = range.start + bytes.length - 1;
|
|
4277
|
+
return new Response(bufferToBody(bytes), {
|
|
4278
|
+
status: 206,
|
|
4279
|
+
headers: {
|
|
4280
|
+
"Content-Type": mime,
|
|
4281
|
+
"Content-Length": String(bytes.length),
|
|
4282
|
+
"Content-Range": `bytes ${range.start}-${lastByte}/${totalSize}`,
|
|
4283
|
+
"Cache-Control": CACHE_CONTROL,
|
|
4284
|
+
ETag: etag,
|
|
4285
|
+
"Access-Control-Allow-Origin": "*"
|
|
4286
|
+
}
|
|
4287
|
+
});
|
|
4288
|
+
}
|
|
4289
|
+
return new Response(bufferToBody(probe.bytes), {
|
|
4290
|
+
status: 200,
|
|
4291
|
+
headers: {
|
|
4292
|
+
"Content-Type": mime,
|
|
4293
|
+
"Cache-Control": CACHE_CONTROL,
|
|
4294
|
+
ETag: etag,
|
|
4295
|
+
"Access-Control-Allow-Origin": "*"
|
|
4296
|
+
}
|
|
4297
|
+
});
|
|
4298
|
+
} catch {
|
|
4299
|
+
return notFound();
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
|
|
4303
|
+
// src/main/services/simulator-temp-files/fs-channels.ts
|
|
4304
|
+
import fs9 from "node:fs/promises";
|
|
4305
|
+
import path15 from "node:path";
|
|
4306
|
+
async function enforceSandbox(realPath) {
|
|
4307
|
+
if (typeof realPath !== "string" || realPath.length === 0) {
|
|
4308
|
+
throw new Error("sandbox: realPath must be a non-empty string");
|
|
4309
|
+
}
|
|
4310
|
+
const base = sandboxBase();
|
|
4311
|
+
const baseReal = await fs9.realpath(base).catch(() => base);
|
|
4312
|
+
const normalized = path15.normalize(realPath);
|
|
4313
|
+
if (normalized !== base && !normalized.startsWith(base + path15.sep)) {
|
|
4314
|
+
throw new Error("sandbox: realPath escapes the user-data base");
|
|
4315
|
+
}
|
|
4316
|
+
let probe = normalized;
|
|
4317
|
+
while (probe !== path15.parse(probe).root) {
|
|
4318
|
+
try {
|
|
4319
|
+
const resolved = await fs9.realpath(probe);
|
|
4320
|
+
if (resolved !== baseReal && !resolved.startsWith(baseReal + path15.sep)) {
|
|
4321
|
+
throw new Error("sandbox: realPath escapes the user-data base via symlink");
|
|
4322
|
+
}
|
|
4323
|
+
break;
|
|
4324
|
+
} catch (err) {
|
|
4325
|
+
if (err.code === "ENOENT") {
|
|
4326
|
+
probe = path15.dirname(probe);
|
|
4327
|
+
continue;
|
|
4328
|
+
}
|
|
4329
|
+
throw err;
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
return normalized;
|
|
4333
|
+
}
|
|
4334
|
+
async function handleFsRead(req) {
|
|
4335
|
+
const safe = await enforceSandbox(req.realPath);
|
|
4336
|
+
const result = await readDiskFile(safe, req.range ? { range: req.range } : void 0);
|
|
4337
|
+
return {
|
|
4338
|
+
bytes: result.bytes,
|
|
4339
|
+
mime: result.mime,
|
|
4340
|
+
etag: result.etag,
|
|
4341
|
+
totalSize: result.totalSize
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4344
|
+
async function handleFsWrite(req) {
|
|
4345
|
+
const safe = await enforceSandbox(req.realPath);
|
|
4346
|
+
await writeDiskFile(safe, req.bytes);
|
|
4347
|
+
return { ok: true };
|
|
4348
|
+
}
|
|
4349
|
+
async function handleFsStat(req) {
|
|
4350
|
+
const safe = await enforceSandbox(req.realPath);
|
|
4351
|
+
return statDiskFile(safe);
|
|
4352
|
+
}
|
|
4353
|
+
async function handleFsReaddir(req) {
|
|
4354
|
+
const safe = await enforceSandbox(req.realPath);
|
|
4355
|
+
return readDiskDir(safe);
|
|
4356
|
+
}
|
|
4357
|
+
async function handleFsUnlink(req) {
|
|
4358
|
+
const safe = await enforceSandbox(req.realPath);
|
|
4359
|
+
await fs9.unlink(safe);
|
|
4360
|
+
return { ok: true };
|
|
4361
|
+
}
|
|
4362
|
+
async function handleFsMkdir(req) {
|
|
4363
|
+
const safe = await enforceSandbox(req.realPath);
|
|
4364
|
+
await fs9.mkdir(safe, { recursive: !!req.recursive });
|
|
4365
|
+
return { ok: true };
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
// src/main/services/simulator-temp-files/index.ts
|
|
4369
|
+
var MAX_STORE_ENTRIES = 200;
|
|
4370
|
+
var PENDING_TIMEOUT_MS = 500;
|
|
4371
|
+
function enforceStoreCap(store) {
|
|
4372
|
+
while (store.size > MAX_STORE_ENTRIES) {
|
|
4373
|
+
const next = store.keys().next();
|
|
4374
|
+
if (next.done) break;
|
|
4375
|
+
store.delete(next.value);
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
function setupSimulatorTempFiles(simSession) {
|
|
4379
|
+
const store = /* @__PURE__ */ new Map();
|
|
4380
|
+
const pendingWaiters = /* @__PURE__ */ new Map();
|
|
4381
|
+
let disposed = false;
|
|
4382
|
+
function drainWaiters(path18) {
|
|
4383
|
+
const list = pendingWaiters.get(path18);
|
|
4384
|
+
if (!list) return;
|
|
4385
|
+
pendingWaiters.delete(path18);
|
|
4386
|
+
for (const fn of list) fn();
|
|
4387
|
+
}
|
|
4388
|
+
function drainAllWaiters() {
|
|
4389
|
+
const lists = Array.from(pendingWaiters.values());
|
|
4390
|
+
pendingWaiters.clear();
|
|
4391
|
+
for (const list of lists) for (const fn of list) fn();
|
|
4392
|
+
}
|
|
4393
|
+
const simulatorOnlyPolicy = (sender) => !sender.isDestroyed() && sender.session === simSession;
|
|
4394
|
+
const registry = new IpcRegistry(simulatorOnlyPolicy);
|
|
4395
|
+
registry.on("simulator:temp-file:write", (_event, payload) => {
|
|
4396
|
+
if (disposed) return;
|
|
4397
|
+
const { path: path18, mime, bytes } = payload;
|
|
4398
|
+
registerTempFile(store, path18, mime, bytes);
|
|
4399
|
+
enforceStoreCap(store);
|
|
4400
|
+
drainWaiters(path18);
|
|
4401
|
+
});
|
|
4402
|
+
registry.on("simulator:temp-file:revoke", (_event, payload) => {
|
|
4403
|
+
if (disposed) return;
|
|
4404
|
+
const { path: path18 } = payload;
|
|
4405
|
+
revokeTempFile(store, path18);
|
|
4406
|
+
});
|
|
4407
|
+
registry.on("simulator:temp-file:revoke-all", () => {
|
|
4408
|
+
if (disposed) return;
|
|
4409
|
+
revokeAllTempFiles(store);
|
|
4410
|
+
});
|
|
4411
|
+
try {
|
|
4412
|
+
simSession.protocol.unhandle("difile");
|
|
4413
|
+
} catch {
|
|
4414
|
+
}
|
|
4415
|
+
simSession.protocol.handle("difile", async (req) => {
|
|
4416
|
+
const url = req.url;
|
|
4417
|
+
const headers = {};
|
|
4418
|
+
try {
|
|
4419
|
+
req.headers.forEach((v, k) => {
|
|
4420
|
+
headers[k] = v;
|
|
4421
|
+
});
|
|
4422
|
+
} catch {
|
|
4423
|
+
}
|
|
4424
|
+
const ctx = { tempStore: store };
|
|
4425
|
+
let res = await handleDifileRequest(ctx, { url, headers });
|
|
4426
|
+
if (res.status === 404 && !disposed && url.startsWith("difile://_tmp/")) {
|
|
4427
|
+
await new Promise((resolve) => {
|
|
4428
|
+
let timer = null;
|
|
4429
|
+
const notify = () => {
|
|
4430
|
+
if (timer) clearTimeout(timer);
|
|
4431
|
+
resolve();
|
|
4432
|
+
};
|
|
4433
|
+
const list = pendingWaiters.get(url) ?? /* @__PURE__ */ new Set();
|
|
4434
|
+
list.add(notify);
|
|
4435
|
+
pendingWaiters.set(url, list);
|
|
4436
|
+
timer = setTimeout(() => {
|
|
4437
|
+
const cur = pendingWaiters.get(url);
|
|
4438
|
+
if (cur) {
|
|
4439
|
+
cur.delete(notify);
|
|
4440
|
+
if (cur.size === 0) pendingWaiters.delete(url);
|
|
4441
|
+
}
|
|
4442
|
+
resolve();
|
|
4443
|
+
}, PENDING_TIMEOUT_MS);
|
|
4444
|
+
});
|
|
4445
|
+
res = await handleDifileRequest(ctx, { url, headers });
|
|
4446
|
+
}
|
|
4447
|
+
return res;
|
|
4448
|
+
});
|
|
4449
|
+
registry.handle(
|
|
4450
|
+
"simulator:fs:read",
|
|
4451
|
+
(_event, payload) => handleFsRead(payload)
|
|
4452
|
+
);
|
|
4453
|
+
registry.handle(
|
|
4454
|
+
"simulator:fs:write",
|
|
4455
|
+
(_event, payload) => handleFsWrite(payload)
|
|
4456
|
+
);
|
|
4457
|
+
registry.handle(
|
|
4458
|
+
"simulator:fs:stat",
|
|
4459
|
+
(_event, payload) => handleFsStat(payload)
|
|
4460
|
+
);
|
|
4461
|
+
registry.handle(
|
|
4462
|
+
"simulator:fs:readdir",
|
|
4463
|
+
(_event, payload) => handleFsReaddir(payload)
|
|
4464
|
+
);
|
|
4465
|
+
registry.handle(
|
|
4466
|
+
"simulator:fs:unlink",
|
|
4467
|
+
(_event, payload) => handleFsUnlink(payload)
|
|
4468
|
+
);
|
|
4469
|
+
registry.handle(
|
|
4470
|
+
"simulator:fs:mkdir",
|
|
4471
|
+
(_event, payload) => handleFsMkdir(payload)
|
|
4472
|
+
);
|
|
4473
|
+
return toDisposable(async () => {
|
|
4474
|
+
disposed = true;
|
|
4475
|
+
drainAllWaiters();
|
|
4476
|
+
store.clear();
|
|
4477
|
+
try {
|
|
4478
|
+
simSession.protocol.unhandle("difile");
|
|
4479
|
+
} catch {
|
|
4480
|
+
}
|
|
4481
|
+
await registry.dispose();
|
|
4482
|
+
});
|
|
4483
|
+
}
|
|
4484
|
+
|
|
3535
4485
|
// src/main/services/update/update-manager.ts
|
|
3536
4486
|
import { app as app11, shell as shell3 } from "electron";
|
|
3537
4487
|
var UpdateManager = class {
|
|
@@ -3540,6 +4490,16 @@ var UpdateManager = class {
|
|
|
3540
4490
|
getCurrentVersion;
|
|
3541
4491
|
latestUpdate = null;
|
|
3542
4492
|
downloadedPath = null;
|
|
4493
|
+
/**
|
|
4494
|
+
* Whether this instance has already emitted `Available` once. The periodic
|
|
4495
|
+
* check fires forever (interval default 1h), and downstream UI (qdmp shell
|
|
4496
|
+
* renders a native toast / Notification) is not idempotent — every event
|
|
4497
|
+
* stacks. We dedupe at the source: at most one Available per instance
|
|
4498
|
+
* lifetime, regardless of how the upstream `info.version` shifts. The user
|
|
4499
|
+
* already knows "an update exists"; tracking minor version bumps mid-
|
|
4500
|
+
* session is not worth duplicating the toast.
|
|
4501
|
+
*/
|
|
4502
|
+
hasNotified = false;
|
|
3543
4503
|
timer = null;
|
|
3544
4504
|
initialTimer = null;
|
|
3545
4505
|
ipc;
|
|
@@ -3613,9 +4573,10 @@ var UpdateManager = class {
|
|
|
3613
4573
|
}
|
|
3614
4574
|
async checkAndNotify() {
|
|
3615
4575
|
const result = await this.check();
|
|
3616
|
-
if (result.hasUpdate
|
|
3617
|
-
|
|
3618
|
-
|
|
4576
|
+
if (!result.hasUpdate || !result.info) return;
|
|
4577
|
+
if (this.hasNotified) return;
|
|
4578
|
+
this.hasNotified = true;
|
|
4579
|
+
this.mainWindow.webContents.send(UpdateChannel.Available, result.info);
|
|
3619
4580
|
}
|
|
3620
4581
|
};
|
|
3621
4582
|
|
|
@@ -3690,7 +4651,7 @@ async function disposeContext(ctx) {
|
|
|
3690
4651
|
function createConfiguredMainWindow(config, rendererDir2) {
|
|
3691
4652
|
const mainWindow = createMainWindow({
|
|
3692
4653
|
title: config.appName ?? "Dimina DevTools",
|
|
3693
|
-
indexHtml:
|
|
4654
|
+
indexHtml: path16.join(rendererDir2, "entries/main/index.html"),
|
|
3694
4655
|
width: config.window?.width,
|
|
3695
4656
|
height: config.window?.height,
|
|
3696
4657
|
minWidth: config.window?.minWidth,
|
|
@@ -3788,7 +4749,7 @@ function enableDevRendererAutoReload(rendererDir2) {
|
|
|
3788
4749
|
});
|
|
3789
4750
|
}
|
|
3790
4751
|
let reloadTimer = null;
|
|
3791
|
-
const watcher =
|
|
4752
|
+
const watcher = fs10.watch(rendererDir2, { recursive: true }, () => {
|
|
3792
4753
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
3793
4754
|
reloadTimer = setTimeout(() => {
|
|
3794
4755
|
for (const win of BrowserWindow4.getAllWindows()) {
|
|
@@ -3807,7 +4768,12 @@ function enableDevRendererAutoReload(rendererDir2) {
|
|
|
3807
4768
|
function createWorkbenchApp(config = {}) {
|
|
3808
4769
|
let setupPromise = null;
|
|
3809
4770
|
let appEventsRegistered = false;
|
|
4771
|
+
try {
|
|
4772
|
+
app13.setName(config.appName ?? "Dimina DevTools");
|
|
4773
|
+
} catch {
|
|
4774
|
+
}
|
|
3810
4775
|
setupCdpPort();
|
|
4776
|
+
registerDifileScheme();
|
|
3811
4777
|
async function setup() {
|
|
3812
4778
|
if (setupPromise) return setupPromise;
|
|
3813
4779
|
setupPromise = app13.whenReady().then(async () => {
|
|
@@ -3816,8 +4782,11 @@ function createWorkbenchApp(config = {}) {
|
|
|
3816
4782
|
const mainWindow = createConfiguredMainWindow(config, rendererDir2);
|
|
3817
4783
|
const context = createContext(config, mainWindow, rendererDir2);
|
|
3818
4784
|
context.registry.add(registerAppIpc(context));
|
|
4785
|
+
context.registry.add(registerProjectFsIpc(context));
|
|
3819
4786
|
context.registry.add(installThemeBackgroundSync());
|
|
3820
4787
|
registerBuiltinModules(config, context);
|
|
4788
|
+
const simSession = session2.fromPartition("persist:simulator");
|
|
4789
|
+
context.registry.add(setupSimulatorTempFiles(simSession));
|
|
3821
4790
|
installMenu(config, mainWindow, context);
|
|
3822
4791
|
const hostIpc = new IpcRegistry(context.senderPolicy);
|
|
3823
4792
|
context.registry.add(hostIpc);
|
|
@@ -3838,6 +4807,9 @@ function createWorkbenchApp(config = {}) {
|
|
|
3838
4807
|
},
|
|
3839
4808
|
dispose: () => disposeContext(context)
|
|
3840
4809
|
};
|
|
4810
|
+
if (config.onBeforeOpenProject) {
|
|
4811
|
+
context.beforeOpenProject = (projectPath) => config.onBeforeOpenProject({ projectPath, instance });
|
|
4812
|
+
}
|
|
3841
4813
|
if (config.onSetup) {
|
|
3842
4814
|
await config.onSetup(instance);
|
|
3843
4815
|
}
|
|
@@ -3864,8 +4836,8 @@ function createWorkbenchApp(config = {}) {
|
|
|
3864
4836
|
// prefixes; this callback lets the storage panel filter the CDP
|
|
3865
4837
|
// snapshot/event stream to the active appId.
|
|
3866
4838
|
getActiveAppId: () => {
|
|
3867
|
-
const
|
|
3868
|
-
const appInfo =
|
|
4839
|
+
const session3 = context.workspace.getSession();
|
|
4840
|
+
const appInfo = session3?.appInfo;
|
|
3869
4841
|
return appInfo?.appId ?? null;
|
|
3870
4842
|
}
|
|
3871
4843
|
}));
|
|
@@ -3890,7 +4862,7 @@ function createWorkbenchApp(config = {}) {
|
|
|
3890
4862
|
|
|
3891
4863
|
// src/main/windows/settings-window/create.ts
|
|
3892
4864
|
import { BrowserWindow as BrowserWindow5 } from "electron";
|
|
3893
|
-
import
|
|
4865
|
+
import path17 from "path";
|
|
3894
4866
|
async function createSettingsWindow(parent, rendererDir2) {
|
|
3895
4867
|
const win = new BrowserWindow5({
|
|
3896
4868
|
width: 420,
|
|
@@ -3908,7 +4880,7 @@ async function createSettingsWindow(parent, rendererDir2) {
|
|
|
3908
4880
|
}
|
|
3909
4881
|
});
|
|
3910
4882
|
applyNavigationHardening(win.webContents, rendererDir2);
|
|
3911
|
-
await win.loadFile(
|
|
4883
|
+
await win.loadFile(path17.join(rendererDir2, "entries/workbench-settings/index.html"));
|
|
3912
4884
|
return win;
|
|
3913
4885
|
}
|
|
3914
4886
|
|