@dimina-kit/devtools 0.4.0-dev.20260702182435 → 0.4.0-dev.20260703101348
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/app/app.js +5 -25
- package/dist/main/app/native-overview.d.ts +27 -0
- package/dist/main/app/native-overview.js +44 -0
- package/dist/main/index.bundle.js +1217 -735
- package/dist/main/ipc/bridge-router.d.ts +8 -0
- package/dist/main/ipc/bridge-router.js +526 -173
- package/dist/main/ipc/project-fs.js +39 -21
- package/dist/main/services/automation/handlers/app.js +143 -109
- package/dist/main/services/console-forward/index.d.ts +12 -1
- package/dist/main/services/console-forward/index.js +99 -1
- package/dist/main/services/diagnostics/index.d.ts +49 -0
- package/dist/main/services/diagnostics/index.js +61 -0
- package/dist/main/services/mcp/tools/simulator-tools.js +118 -97
- package/dist/main/services/network-forward/dispatch-batch.d.ts +22 -0
- package/dist/main/services/network-forward/dispatch-batch.js +21 -0
- package/dist/main/services/network-forward/index.js +37 -20
- package/dist/main/services/notifications/renderer-notifier.d.ts +39 -0
- package/dist/main/services/notifications/renderer-notifier.js +4 -1
- package/dist/main/services/projects/create-project-service.js +54 -34
- package/dist/main/services/projects/project-repository.js +2 -1
- package/dist/main/services/projects/types.d.ts +1 -32
- package/dist/main/services/service-console/console-api.js +36 -27
- package/dist/main/services/simulator-temp-files/disk.js +109 -86
- package/dist/main/services/views/simulator-session-policy.js +17 -0
- package/dist/main/services/workbench-coi-server.js +58 -45
- package/dist/main/services/workbench-context.d.ts +10 -0
- package/dist/main/services/workspace/workspace-service.js +12 -3
- package/dist/main/utils/logger.d.ts +5 -12
- package/dist/main/utils/logger.js +5 -45
- package/dist/main/windows/service-host-window/create.d.ts +15 -1
- package/dist/main/windows/service-host-window/create.js +11 -3
- package/dist/preload/instrumentation/wxml-extract.js +94 -62
- package/dist/preload/runtime/main-api-runner.d.ts +1 -1
- package/dist/preload/shared/api-compat.js +18 -55
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
- package/dist/preload/windows/main.cjs +284 -565
- package/dist/preload/windows/main.cjs.map +2 -2
- package/dist/preload/windows/simulator.cjs +154 -83
- package/dist/preload/windows/simulator.cjs.map +3 -3
- package/dist/preload/windows/simulator.js +154 -83
- package/dist/render-host/render-inspect.js +63 -52
- package/dist/renderer/assets/index-BwzPwCmM.js +49 -0
- package/dist/renderer/assets/{input-Cjk0wSEh.js → input-CTb_le7F.js} +2 -2
- package/dist/renderer/assets/ipc-transport-BPWIV5hA.css +1 -0
- package/dist/renderer/assets/{ipc-transport-OZS-KmOW.js → ipc-transport-Ck5Xa8Tu.js} +2 -2
- package/dist/renderer/assets/popover-B1Zu2UQu.js +2 -0
- package/dist/renderer/assets/{select-BQDXi5ih.js → select-ozSk5Pt6.js} +2 -2
- package/dist/renderer/assets/{settings-tP3Px2KR.js → settings-CNwGOI22.js} +2 -2
- package/dist/renderer/assets/settings-api-DMbMp1Eq.js +2 -0
- package/dist/renderer/assets/workbenchSettings-Cr4EHo5w.js +8 -0
- package/dist/renderer/entries/main/index.html +6 -6
- package/dist/renderer/entries/popover/index.html +5 -5
- package/dist/renderer/entries/settings/index.html +5 -5
- package/dist/renderer/entries/workbench-settings/index.html +4 -4
- package/dist/shared/appdata-accumulator.js +53 -48
- package/dist/shared/bridge-channels.d.ts +22 -0
- package/dist/shared/ipc-channels.d.ts +3 -0
- package/dist/shared/ipc-channels.js +9 -0
- package/dist/shared/open-in-editor-resource-path.d.ts +56 -0
- package/dist/shared/open-in-editor-resource-path.js +133 -0
- package/dist/shared/open-in-editor.d.ts +2 -9
- package/dist/shared/open-in-editor.js +8 -93
- package/dist/shared/request-core.d.ts +84 -0
- package/dist/shared/request-core.js +168 -0
- package/dist/shared/simulator-api-metadata.d.ts +22 -17
- package/dist/shared/simulator-api-metadata.js +36 -0
- package/dist/shared/types.d.ts +20 -4
- package/dist/simulator/assets/{device-shell-CUl0ILfn.js → device-shell-C-wcFq3Z.js} +2 -2
- package/dist/simulator/assets/{jsx-runtime-CDK-o-S0.js → jsx-runtime-BDTY6fEq.js} +2 -2
- package/dist/simulator/assets/simulator-DU3-fS3v.js +10 -0
- package/dist/simulator/assets/simulator-mini-app-DzfMfnVM.js +2 -0
- package/dist/simulator/simulator.html +2 -2
- package/dist/vscode-workbench/assets/__vite-browser-external-CXi6Kz9W.js +1 -0
- package/dist/vscode-workbench/assets/{dist-CS7SQP3K.js → dist-TpGpmMGi.js} +3 -3
- package/dist/vscode-workbench/assets/{iconv-lite-umd-D3q-1-o6.js → iconv-lite-umd-C9tiCAJa.js} +1 -1
- package/dist/vscode-workbench/assets/{index-zjigpZ25.js → index-DJ1HyMZ7.js} +20 -22
- package/dist/vscode-workbench/assets/{jschardet-Cu4ufeHq.js → jschardet-DE1jV513.js} +1 -1
- package/dist/vscode-workbench/index.html +1 -1
- package/package.json +6 -6
- package/dist/renderer/assets/index-B-Dqb7G0.js +0 -49
- package/dist/renderer/assets/ipc-transport-D5dKIti1.css +0 -1
- package/dist/renderer/assets/popover-CQVvOe90.js +0 -2
- package/dist/renderer/assets/settings-api-B6x2mhaD.js +0 -2
- package/dist/renderer/assets/workbenchSettings-Bim9ol9R.js +0 -8
- package/dist/simulator/assets/simulator-Dz8iGcgy.js +0 -10
- package/dist/simulator/assets/simulator-mini-app-Bt639XL_.js +0 -2
- package/dist/vscode-workbench/assets/__vite-browser-external-B0DTNerG.js +0 -1
|
@@ -34,35 +34,25 @@ export function mapConsoleApiType(type) {
|
|
|
34
34
|
}
|
|
35
35
|
const BIGINT_LITERAL = /^-?\d+n$/;
|
|
36
36
|
/**
|
|
37
|
-
* A
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* inline/best-effort fallback.
|
|
37
|
+
* A CDP `unserializableValue` string (specials that can't ride in `value`,
|
|
38
|
+
* e.g. `Infinity`/`NaN`/bigint literals) → the JS value it denotes. Unknown
|
|
39
|
+
* literals pass through as-is (e.g. a bigint literal stays a string).
|
|
41
40
|
*/
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
const u = ro.unserializableValue;
|
|
53
|
-
if (u === 'Infinity')
|
|
54
|
-
return Infinity;
|
|
55
|
-
if (u === '-Infinity')
|
|
56
|
-
return -Infinity;
|
|
57
|
-
if (u === 'NaN')
|
|
58
|
-
return NaN;
|
|
59
|
-
if (u === '-0')
|
|
60
|
-
return -0;
|
|
61
|
-
if (BIGINT_LITERAL.test(u))
|
|
62
|
-
return u;
|
|
41
|
+
function unserializableToValue(u) {
|
|
42
|
+
if (u === 'Infinity')
|
|
43
|
+
return Infinity;
|
|
44
|
+
if (u === '-Infinity')
|
|
45
|
+
return -Infinity;
|
|
46
|
+
if (u === 'NaN')
|
|
47
|
+
return NaN;
|
|
48
|
+
if (u === '-0')
|
|
49
|
+
return -0;
|
|
50
|
+
if (BIGINT_LITERAL.test(u))
|
|
63
51
|
return u;
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
return u;
|
|
53
|
+
}
|
|
54
|
+
/** Fallback serialization by `RemoteObject.type` when neither `value` nor `unserializableValue` is present. */
|
|
55
|
+
function typeFallbackToValue(ro) {
|
|
66
56
|
switch (ro.type) {
|
|
67
57
|
case 'undefined':
|
|
68
58
|
return undefined;
|
|
@@ -78,6 +68,25 @@ export function remoteObjectToValue(ro) {
|
|
|
78
68
|
return ro.description ?? '[Unknown]';
|
|
79
69
|
}
|
|
80
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* A single RemoteObject → a JSON-serializable value WITHOUT a CDP round-trip
|
|
73
|
+
* (shallow). Objects that need their full contents are flagged by
|
|
74
|
+
* {@link needsDeepFetch} and deep-serialized by the caller; this is the
|
|
75
|
+
* inline/best-effort fallback.
|
|
76
|
+
*/
|
|
77
|
+
export function remoteObjectToValue(ro) {
|
|
78
|
+
if (!ro)
|
|
79
|
+
return ro;
|
|
80
|
+
// An inlined value (CDP includes `value` for JSON-serializable primitives
|
|
81
|
+
// and small arrays). Use `in` so falsy values (0, '', false) and an
|
|
82
|
+
// explicit `null` are honoured rather than falling through.
|
|
83
|
+
if ('value' in ro)
|
|
84
|
+
return ro.value;
|
|
85
|
+
if (typeof ro.unserializableValue === 'string') {
|
|
86
|
+
return unserializableToValue(ro.unserializableValue);
|
|
87
|
+
}
|
|
88
|
+
return typeFallbackToValue(ro);
|
|
89
|
+
}
|
|
81
90
|
/**
|
|
82
91
|
* Whether this RemoteObject must be deep-fetched via `Runtime.callFunctionOn`
|
|
83
92
|
* (returnByValue) to be fully serialized — i.e. a real object/array referenced
|
|
@@ -62,49 +62,63 @@ const EXT_MIME = {
|
|
|
62
62
|
'.pdf': 'application/pdf',
|
|
63
63
|
'.zip': 'application/zip',
|
|
64
64
|
};
|
|
65
|
-
function
|
|
66
|
-
|
|
65
|
+
function isPng(head) {
|
|
66
|
+
return head.length >= 8
|
|
67
67
|
&& head[0] === 0x89 && head[1] === 0x50 && head[2] === 0x4e && head[3] === 0x47
|
|
68
|
-
&& head[4] === 0x0d && head[5] === 0x0a && head[6] === 0x1a && head[7] === 0x0a
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
&& head[4] === 0x0d && head[5] === 0x0a && head[6] === 0x1a && head[7] === 0x0a;
|
|
69
|
+
}
|
|
70
|
+
function isJpeg(head) {
|
|
71
|
+
return head.length >= 3 && head[0] === 0xff && head[1] === 0xd8 && head[2] === 0xff;
|
|
72
|
+
}
|
|
73
|
+
function isGif(head) {
|
|
74
|
+
return head.length >= 6
|
|
75
75
|
&& head[0] === 0x47 && head[1] === 0x49 && head[2] === 0x46
|
|
76
|
-
&& head[3] === 0x38 && (head[4] === 0x37 || head[4] === 0x39) && head[5] === 0x61
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
&& head[3] === 0x38 && (head[4] === 0x37 || head[4] === 0x39) && head[5] === 0x61;
|
|
77
|
+
}
|
|
78
|
+
function isWebp(head) {
|
|
79
|
+
return head.length >= 12
|
|
80
80
|
&& head[0] === 0x52 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x46
|
|
81
|
-
&& head[8] === 0x57 && head[9] === 0x45 && head[10] === 0x42 && head[11] === 0x50
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
&& head[0] === 0x25 && head[1] === 0x50 && head[2] === 0x44 && head[3] === 0x46
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
&& head[0] === 0x50 && head[1] === 0x4b && head[2] === 0x03 && head[3] === 0x04
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
81
|
+
&& head[8] === 0x57 && head[9] === 0x45 && head[10] === 0x42 && head[11] === 0x50;
|
|
82
|
+
}
|
|
83
|
+
/** MP4 ftyp box: brand bytes vary, treat all as video/mp4 for now. */
|
|
84
|
+
function isMp4(head) {
|
|
85
|
+
return head.length >= 12
|
|
86
|
+
&& head[4] === 0x66 && head[5] === 0x74 && head[6] === 0x79 && head[7] === 0x70;
|
|
87
|
+
}
|
|
88
|
+
function isPdf(head) {
|
|
89
|
+
return head.length >= 4
|
|
90
|
+
&& head[0] === 0x25 && head[1] === 0x50 && head[2] === 0x44 && head[3] === 0x46;
|
|
91
|
+
}
|
|
92
|
+
function isBmp(head) {
|
|
93
|
+
return head.length >= 2 && head[0] === 0x42 && head[1] === 0x4d;
|
|
94
|
+
}
|
|
95
|
+
function isZip(head) {
|
|
96
|
+
return head.length >= 4
|
|
97
|
+
&& head[0] === 0x50 && head[1] === 0x4b && head[2] === 0x03 && head[3] === 0x04;
|
|
98
|
+
}
|
|
99
|
+
/** ID3-tagged MP3. */
|
|
100
|
+
function isId3Mp3(head) {
|
|
101
|
+
return head.length >= 3 && head[0] === 0x49 && head[1] === 0x44 && head[2] === 0x33;
|
|
102
|
+
}
|
|
103
|
+
function isOgg(head) {
|
|
104
|
+
return head.length >= 4 && head[0] === 0x4f && head[1] === 0x67 && head[2] === 0x67 && head[3] === 0x53;
|
|
105
|
+
}
|
|
106
|
+
/** Magic-byte sniffers in priority order — first match wins. */
|
|
107
|
+
const MAGIC_SNIFFERS = [
|
|
108
|
+
{ mime: 'image/png', match: isPng },
|
|
109
|
+
{ mime: 'image/jpeg', match: isJpeg },
|
|
110
|
+
{ mime: 'image/gif', match: isGif },
|
|
111
|
+
{ mime: 'image/webp', match: isWebp },
|
|
112
|
+
{ mime: 'video/mp4', match: isMp4 },
|
|
113
|
+
{ mime: 'application/pdf', match: isPdf },
|
|
114
|
+
{ mime: 'image/bmp', match: isBmp },
|
|
115
|
+
{ mime: 'application/zip', match: isZip },
|
|
116
|
+
{ mime: 'audio/mpeg', match: isId3Mp3 },
|
|
117
|
+
{ mime: 'audio/ogg', match: isOgg },
|
|
118
|
+
];
|
|
119
|
+
function sniffMime(head) {
|
|
120
|
+
const hit = MAGIC_SNIFFERS.find((sniffer) => sniffer.match(head));
|
|
121
|
+
return hit ? hit.mime : null;
|
|
108
122
|
}
|
|
109
123
|
function extMime(realPath) {
|
|
110
124
|
const ext = path.extname(realPath).toLowerCase();
|
|
@@ -126,6 +140,54 @@ function etagOf(mtimeMs, size) {
|
|
|
126
140
|
return `W/"${Math.floor(mtimeMs)}-${size}"`;
|
|
127
141
|
}
|
|
128
142
|
// -- read -------------------------------------------------------------------
|
|
143
|
+
/** Validate `range` against `totalSize` and resolve it to a byte slice, or throws. */
|
|
144
|
+
function resolveRange(range, totalSize) {
|
|
145
|
+
const { start, end } = range;
|
|
146
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
|
147
|
+
throw new RangeError(`invalid range: ${start}-${end}`);
|
|
148
|
+
}
|
|
149
|
+
if (start < 0)
|
|
150
|
+
throw new RangeError(`range start out of bounds: ${start}`);
|
|
151
|
+
if (start > end)
|
|
152
|
+
throw new RangeError(`range start > end: ${start} > ${end}`);
|
|
153
|
+
if (start >= totalSize) {
|
|
154
|
+
throw new RangeError(`range start beyond file size: ${start} >= ${totalSize}`);
|
|
155
|
+
}
|
|
156
|
+
const clampedEnd = Math.min(end, totalSize - 1);
|
|
157
|
+
return { sliceStart: start, sliceLen: clampedEnd - start + 1 };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Read at most the first 12 bytes for magic sniffing, regardless of whether
|
|
161
|
+
* the caller asked for a range. The head bytes from position 0 are needed to
|
|
162
|
+
* label Content-Type correctly even on a tail Range request.
|
|
163
|
+
*/
|
|
164
|
+
async function readHeadBytes(handle, totalSize) {
|
|
165
|
+
const head = Buffer.alloc(Math.min(12, totalSize));
|
|
166
|
+
if (head.length > 0) {
|
|
167
|
+
await handle.read(head, 0, head.length, 0);
|
|
168
|
+
}
|
|
169
|
+
return head;
|
|
170
|
+
}
|
|
171
|
+
/** Full-file body, re-using `head` (already read) instead of a second read for the leading bytes. */
|
|
172
|
+
async function readFullBody(handle, head, totalSize) {
|
|
173
|
+
const bytes = Buffer.alloc(totalSize);
|
|
174
|
+
if (totalSize === 0)
|
|
175
|
+
return bytes;
|
|
176
|
+
if (totalSize <= head.length) {
|
|
177
|
+
head.copy(bytes, 0, 0, totalSize);
|
|
178
|
+
return bytes;
|
|
179
|
+
}
|
|
180
|
+
head.copy(bytes, 0, 0, head.length);
|
|
181
|
+
await handle.read(bytes, head.length, totalSize - head.length, head.length);
|
|
182
|
+
return bytes;
|
|
183
|
+
}
|
|
184
|
+
async function readRangeBody(handle, sliceStart, sliceLen) {
|
|
185
|
+
const bytes = Buffer.alloc(sliceLen);
|
|
186
|
+
if (sliceLen > 0) {
|
|
187
|
+
await handle.read(bytes, 0, sliceLen, sliceStart);
|
|
188
|
+
}
|
|
189
|
+
return bytes;
|
|
190
|
+
}
|
|
129
191
|
export async function readDiskFile(realPath, opts) {
|
|
130
192
|
const handle = await fs.open(realPath, 'r');
|
|
131
193
|
try {
|
|
@@ -133,52 +195,13 @@ export async function readDiskFile(realPath, opts) {
|
|
|
133
195
|
const totalSize = st.size;
|
|
134
196
|
const mtimeMs = st.mtimeMs;
|
|
135
197
|
const range = opts?.range;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (start < 0)
|
|
144
|
-
throw new RangeError(`range start out of bounds: ${start}`);
|
|
145
|
-
if (start > end)
|
|
146
|
-
throw new RangeError(`range start > end: ${start} > ${end}`);
|
|
147
|
-
if (start >= totalSize) {
|
|
148
|
-
throw new RangeError(`range start beyond file size: ${start} >= ${totalSize}`);
|
|
149
|
-
}
|
|
150
|
-
const clampedEnd = Math.min(end, totalSize - 1);
|
|
151
|
-
sliceStart = start;
|
|
152
|
-
sliceLen = clampedEnd - start + 1;
|
|
153
|
-
}
|
|
154
|
-
// Read at most the first 12 bytes for magic sniffing, regardless of
|
|
155
|
-
// whether the caller asked for a range. We need the head bytes from
|
|
156
|
-
// position 0 to label Content-Type correctly even on a tail Range
|
|
157
|
-
// request. For full reads the head is part of the body so we re-use
|
|
158
|
-
// it without a second read.
|
|
159
|
-
const head = Buffer.alloc(Math.min(12, totalSize));
|
|
160
|
-
if (head.length > 0) {
|
|
161
|
-
await handle.read(head, 0, head.length, 0);
|
|
162
|
-
}
|
|
163
|
-
let bytes;
|
|
164
|
-
if (!range) {
|
|
165
|
-
bytes = Buffer.alloc(totalSize);
|
|
166
|
-
if (totalSize > 0) {
|
|
167
|
-
if (totalSize <= head.length) {
|
|
168
|
-
head.copy(bytes, 0, 0, totalSize);
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
head.copy(bytes, 0, 0, head.length);
|
|
172
|
-
await handle.read(bytes, head.length, totalSize - head.length, head.length);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
bytes = Buffer.alloc(sliceLen);
|
|
178
|
-
if (sliceLen > 0) {
|
|
179
|
-
await handle.read(bytes, 0, sliceLen, sliceStart);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
198
|
+
const { sliceStart, sliceLen } = range
|
|
199
|
+
? resolveRange(range, totalSize)
|
|
200
|
+
: { sliceStart: 0, sliceLen: totalSize };
|
|
201
|
+
const head = await readHeadBytes(handle, totalSize);
|
|
202
|
+
const bytes = range
|
|
203
|
+
? await readRangeBody(handle, sliceStart, sliceLen)
|
|
204
|
+
: await readFullBody(handle, head, totalSize);
|
|
182
205
|
return {
|
|
183
206
|
bytes,
|
|
184
207
|
mime: detectMime(realPath, head),
|
|
@@ -19,6 +19,12 @@ import { session } from 'electron';
|
|
|
19
19
|
import { toDisposable } from '@dimina-kit/electron-deck/main';
|
|
20
20
|
import { getSimulatorServicewechatReferer } from '../simulator/referer.js';
|
|
21
21
|
import { registerMiniappSessionConfigurator, SHARED_MINIAPP_PARTITION, } from './miniapp-partition.js';
|
|
22
|
+
/** The response headers this policy owns outright (compared lowercase). */
|
|
23
|
+
const CORS_OVERRIDE_HEADERS = new Set([
|
|
24
|
+
'access-control-allow-origin',
|
|
25
|
+
'access-control-allow-headers',
|
|
26
|
+
'access-control-allow-methods',
|
|
27
|
+
]);
|
|
22
28
|
/** Apply the simulator runtime's referer + CORS webRequest policy to one
|
|
23
29
|
* session. Each session installs its own listeners (a webRequest listener is
|
|
24
30
|
* per-session), so this runs once per partition. */
|
|
@@ -35,6 +41,17 @@ function applySimulatorWebRequestPolicy(simulatorSession) {
|
|
|
35
41
|
// CORS for the native render/service hosts to fetch compiled app resources
|
|
36
42
|
// cross-origin. (The COOP/COEP cross-origin-isolation headers were only for
|
|
37
43
|
// the removed default-path SharedArrayBuffer sync Worker — dropped.)
|
|
44
|
+
//
|
|
45
|
+
// This policy is the single authority for these three headers: any value
|
|
46
|
+
// the server itself sent must be REPLACED, not shadowed. responseHeaders
|
|
47
|
+
// keys keep the server's original casing, so assigning the lowercase key
|
|
48
|
+
// beside an existing `Access-Control-Allow-Origin` puts BOTH on the wire —
|
|
49
|
+
// Chromium then rejects the response for carrying multiple ACAO values
|
|
50
|
+
// (`*, *`), breaking every fetch against a CORS-compliant backend.
|
|
51
|
+
for (const key of Object.keys(headers)) {
|
|
52
|
+
if (CORS_OVERRIDE_HEADERS.has(key.toLowerCase()))
|
|
53
|
+
delete headers[key];
|
|
54
|
+
}
|
|
38
55
|
headers['access-control-allow-origin'] = ['*'];
|
|
39
56
|
headers['access-control-allow-headers'] = ['*'];
|
|
40
57
|
headers['access-control-allow-methods'] = ['*'];
|
|
@@ -188,6 +188,59 @@ function fsErrorStatus(e) {
|
|
|
188
188
|
return 404;
|
|
189
189
|
return 500;
|
|
190
190
|
}
|
|
191
|
+
async function fsStat(ctx) {
|
|
192
|
+
const st = await statWithin(ctx.projectRoot, ctx.rel);
|
|
193
|
+
jsonRes(ctx.res, 200, { type: st.isDirectory() ? 2 : 1, size: st.size, ctime: st.ctimeMs, mtime: st.mtimeMs });
|
|
194
|
+
}
|
|
195
|
+
async function fsReaddir(ctx) {
|
|
196
|
+
const entries = await readdirWithin(ctx.projectRoot, ctx.rel);
|
|
197
|
+
jsonRes(ctx.res, 200, entries.map((e) => [e.name, e.isDirectory() ? 2 : 1]));
|
|
198
|
+
}
|
|
199
|
+
async function fsRead(ctx) {
|
|
200
|
+
const buf = await readFileBufferWithin(ctx.projectRoot, ctx.rel);
|
|
201
|
+
ctx.res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Length': buf.length });
|
|
202
|
+
ctx.res.end(buf);
|
|
203
|
+
}
|
|
204
|
+
async function fsWrite(ctx) {
|
|
205
|
+
let buf;
|
|
206
|
+
try {
|
|
207
|
+
buf = await readBody(ctx.req);
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
if (e instanceof BodyTooLargeError)
|
|
211
|
+
return jsonRes(ctx.res, 413, { error: e.message });
|
|
212
|
+
throw e;
|
|
213
|
+
}
|
|
214
|
+
await writeFileWithin(ctx.projectRoot, ctx.rel, buf);
|
|
215
|
+
ctx.res.writeHead(204);
|
|
216
|
+
ctx.res.end();
|
|
217
|
+
}
|
|
218
|
+
async function fsMkdir(ctx) {
|
|
219
|
+
await mkdirWithin(ctx.projectRoot, ctx.rel);
|
|
220
|
+
ctx.res.writeHead(204);
|
|
221
|
+
ctx.res.end();
|
|
222
|
+
}
|
|
223
|
+
async function fsDelete(ctx) {
|
|
224
|
+
await deleteWithin(ctx.projectRoot, ctx.rel);
|
|
225
|
+
ctx.res.writeHead(204);
|
|
226
|
+
ctx.res.end();
|
|
227
|
+
}
|
|
228
|
+
async function fsRename(ctx) {
|
|
229
|
+
const toRel = (ctx.url.searchParams.get('to') ?? '').replace(/^\/+/, '');
|
|
230
|
+
await renameWithin(ctx.projectRoot, ctx.rel, toRel);
|
|
231
|
+
ctx.res.writeHead(204);
|
|
232
|
+
ctx.res.end();
|
|
233
|
+
}
|
|
234
|
+
/** One handler per supported `/__fs/<action>` — keyed lookup replaces an if-chain. */
|
|
235
|
+
const FS_ACTIONS = {
|
|
236
|
+
stat: fsStat,
|
|
237
|
+
readdir: fsReaddir,
|
|
238
|
+
read: fsRead,
|
|
239
|
+
write: fsWrite,
|
|
240
|
+
mkdir: fsMkdir,
|
|
241
|
+
delete: fsDelete,
|
|
242
|
+
rename: fsRename,
|
|
243
|
+
};
|
|
191
244
|
/**
|
|
192
245
|
* `/__fs/<action>?p=<rel>` bridge onto the live active project root.
|
|
193
246
|
* `p`/`to` are POSIX paths relative to the project root, resolved through the
|
|
@@ -205,54 +258,14 @@ async function handleFsRequest(req, res, projectRoot, url) {
|
|
|
205
258
|
return jsonRes(res, rejectStatus, { error: 'mutating fs action requires a non-GET same-origin request' });
|
|
206
259
|
}
|
|
207
260
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const st = await statWithin(projectRoot, rel);
|
|
211
|
-
return jsonRes(res, 200, { type: st.isDirectory() ? 2 : 1, size: st.size, ctime: st.ctimeMs, mtime: st.mtimeMs });
|
|
212
|
-
}
|
|
213
|
-
if (action === 'readdir') {
|
|
214
|
-
const entries = await readdirWithin(projectRoot, rel);
|
|
215
|
-
return jsonRes(res, 200, entries.map((e) => [e.name, e.isDirectory() ? 2 : 1]));
|
|
216
|
-
}
|
|
217
|
-
if (action === 'read') {
|
|
218
|
-
const buf = await readFileBufferWithin(projectRoot, rel);
|
|
219
|
-
res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Length': buf.length });
|
|
220
|
-
return void res.end(buf);
|
|
221
|
-
}
|
|
222
|
-
if (action === 'write') {
|
|
223
|
-
let buf;
|
|
224
|
-
try {
|
|
225
|
-
buf = await readBody(req);
|
|
226
|
-
}
|
|
227
|
-
catch (e) {
|
|
228
|
-
if (e instanceof BodyTooLargeError)
|
|
229
|
-
return jsonRes(res, 413, { error: e.message });
|
|
230
|
-
throw e;
|
|
231
|
-
}
|
|
232
|
-
await writeFileWithin(projectRoot, rel, buf);
|
|
233
|
-
res.writeHead(204);
|
|
234
|
-
return void res.end();
|
|
235
|
-
}
|
|
236
|
-
if (action === 'mkdir') {
|
|
237
|
-
await mkdirWithin(projectRoot, rel);
|
|
238
|
-
res.writeHead(204);
|
|
239
|
-
return void res.end();
|
|
240
|
-
}
|
|
241
|
-
if (action === 'delete') {
|
|
242
|
-
await deleteWithin(projectRoot, rel);
|
|
243
|
-
res.writeHead(204);
|
|
244
|
-
return void res.end();
|
|
245
|
-
}
|
|
246
|
-
if (action === 'rename') {
|
|
247
|
-
const toRel = (url.searchParams.get('to') ?? '').replace(/^\/+/, '');
|
|
248
|
-
await renameWithin(projectRoot, rel, toRel);
|
|
249
|
-
res.writeHead(204);
|
|
250
|
-
return void res.end();
|
|
251
|
-
}
|
|
261
|
+
const handler = FS_ACTIONS[action];
|
|
262
|
+
if (!handler)
|
|
252
263
|
return jsonRes(res, 404, { error: 'unknown fs action: ' + action });
|
|
264
|
+
try {
|
|
265
|
+
await handler({ req, res, projectRoot, rel, url });
|
|
253
266
|
}
|
|
254
267
|
catch (e) {
|
|
255
|
-
|
|
268
|
+
jsonRes(res, fsErrorStatus(e), { error: String(e.message), code: e.code });
|
|
256
269
|
}
|
|
257
270
|
}
|
|
258
271
|
/**
|
|
@@ -2,6 +2,7 @@ import type { BrowserWindow } from 'electron';
|
|
|
2
2
|
import type { CompilationAdapter, CustomFileTypes, WorkbenchConfig } from '../../shared/types.js';
|
|
3
3
|
import type { BridgeRouterHandle } from '../ipc/bridge-router.js';
|
|
4
4
|
import type { ConsoleForwarder } from './console-forward/index.js';
|
|
5
|
+
import type { DiagnosticsBus } from './diagnostics/index.js';
|
|
5
6
|
import type { NetworkForwarder } from './network-forward/index.js';
|
|
6
7
|
import type { AppDataTap } from './simulator-appdata/index.js';
|
|
7
8
|
import type { StorageApi } from './simulator-storage/index.js';
|
|
@@ -167,6 +168,15 @@ export interface WorkbenchContext {
|
|
|
167
168
|
* installed.
|
|
168
169
|
*/
|
|
169
170
|
consoleForwarder?: ConsoleForwarder;
|
|
171
|
+
/**
|
|
172
|
+
* Authoritative diagnostics bus for main-synthesized developer diagnostics
|
|
173
|
+
* (page-not-found, logic-bundle-unreachable, service-host-navigation-failed,
|
|
174
|
+
* …), set by `installBridgeRouter`. `consoleForwarder` subscribes to it so
|
|
175
|
+
* every diagnostic also surfaces in the owning session's embedded DevTools
|
|
176
|
+
* Console panel, not just the main-process log. Undefined until the bridge
|
|
177
|
+
* router is installed.
|
|
178
|
+
*/
|
|
179
|
+
diagnostics?: DiagnosticsBus;
|
|
170
180
|
/**
|
|
171
181
|
* Native-host network forwarder, set in app bootstrap. Attaches the CDP
|
|
172
182
|
* debugger to the simulator WCV (where `wx.request`/`downloadFile`/`uploadFile`
|
|
@@ -38,8 +38,8 @@ export function createWorkspaceService(ctx) {
|
|
|
38
38
|
// wins request tokens, so a close mid-open can't disposeAll() the views the open
|
|
39
39
|
// is building or desync currentSession vs the bridge appSessions. See op-lock.ts.
|
|
40
40
|
const opLock = createOpLock();
|
|
41
|
-
function sendStatus(status, message, hotReload) {
|
|
42
|
-
ctx.notify.projectStatus(
|
|
41
|
+
function sendStatus(status, message, hotReload, pages, watcherDead) {
|
|
42
|
+
ctx.notify.projectStatus({ status, message, ...(hotReload ? { hotReload: true } : {}), ...(pages ? { pages } : {}), ...(watcherDead ? { watcher: 'dead' } : {}) });
|
|
43
43
|
}
|
|
44
44
|
function bestEffort(label, fn) {
|
|
45
45
|
try {
|
|
@@ -127,8 +127,17 @@ export function createWorkspaceService(ctx) {
|
|
|
127
127
|
sourcemap: true,
|
|
128
128
|
fileTypes: ctx.fileTypes,
|
|
129
129
|
watch: compile.watch,
|
|
130
|
-
onRebuild: () =>
|
|
130
|
+
onRebuild: () => {
|
|
131
|
+
// getProjectPages never throws — failure yields the empty-pages
|
|
132
|
+
// shape. Empty means "read failed or degenerate project" (a valid
|
|
133
|
+
// app.json names ≥1 page) and is withheld so the renderer keeps its
|
|
134
|
+
// previous dropdown instead of blanking; the push is never blocked.
|
|
135
|
+
const { pages } = repo.getProjectPages(projectPath);
|
|
136
|
+
sendStatus('ready', '编译完成,已重启', true, pages.length ? pages : undefined);
|
|
137
|
+
},
|
|
131
138
|
onBuildError: (err) => sendStatus('error', String(err)),
|
|
139
|
+
// Watcher died mid-session (EMFILE, permission loss, …): non-fatal, so 'ready' stays but `watcher: 'dead'` flags that saves no longer auto-rebuild.
|
|
140
|
+
onWatcherError: () => sendStatus('ready', '文件监听已停止,保存不再触发自动编译', false, undefined, true),
|
|
132
141
|
onLog: (entry) => {
|
|
133
142
|
if (sessionGeneration !== logGeneration)
|
|
134
143
|
return;
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
-
export interface Logger {
|
|
3
|
-
debug(message: string, ...args: unknown[]): void;
|
|
4
|
-
info(message: string, ...args: unknown[]): void;
|
|
5
|
-
warn(message: string, ...args: unknown[]): void;
|
|
6
|
-
error(message: string, ...args: unknown[]): void;
|
|
7
|
-
}
|
|
8
1
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
2
|
+
* Tagged console logger. The implementation lives in
|
|
3
|
+
* `@dimina-kit/electron-deck/main` (single source — the two packages share one
|
|
4
|
+
* logger); this module re-exports it so devtools-internal imports keep their
|
|
5
|
+
* stable `utils/logger` path.
|
|
11
6
|
*/
|
|
12
|
-
export
|
|
13
|
-
export declare function setLogLevel(level: LogLevel): void;
|
|
14
|
-
export {};
|
|
7
|
+
export { createLogger, setLogLevel, type Logger } from '@dimina-kit/electron-deck/main';
|
|
15
8
|
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -1,48 +1,8 @@
|
|
|
1
|
-
const LEVEL_PRIORITY = {
|
|
2
|
-
debug: 0,
|
|
3
|
-
info: 1,
|
|
4
|
-
warn: 2,
|
|
5
|
-
error: 3,
|
|
6
|
-
};
|
|
7
|
-
const LEVEL_PREFIX = {
|
|
8
|
-
debug: '[DEBUG]',
|
|
9
|
-
info: '[INFO]',
|
|
10
|
-
warn: '[WARN]',
|
|
11
|
-
error: '[ERROR]',
|
|
12
|
-
};
|
|
13
|
-
let currentLevel = 'debug';
|
|
14
|
-
function shouldLog(level) {
|
|
15
|
-
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
|
|
16
|
-
}
|
|
17
|
-
function formatMessage(level, tag, message) {
|
|
18
|
-
const ts = new Date().toISOString();
|
|
19
|
-
return `${ts} ${LEVEL_PREFIX[level]} [${tag}] ${message}`;
|
|
20
|
-
}
|
|
21
1
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
2
|
+
* Tagged console logger. The implementation lives in
|
|
3
|
+
* `@dimina-kit/electron-deck/main` (single source — the two packages share one
|
|
4
|
+
* logger); this module re-exports it so devtools-internal imports keep their
|
|
5
|
+
* stable `utils/logger` path.
|
|
24
6
|
*/
|
|
25
|
-
export
|
|
26
|
-
return {
|
|
27
|
-
debug(message, ...args) {
|
|
28
|
-
if (shouldLog('debug'))
|
|
29
|
-
console.debug(formatMessage('debug', tag, message), ...args);
|
|
30
|
-
},
|
|
31
|
-
info(message, ...args) {
|
|
32
|
-
if (shouldLog('info'))
|
|
33
|
-
console.info(formatMessage('info', tag, message), ...args);
|
|
34
|
-
},
|
|
35
|
-
warn(message, ...args) {
|
|
36
|
-
if (shouldLog('warn'))
|
|
37
|
-
console.warn(formatMessage('warn', tag, message), ...args);
|
|
38
|
-
},
|
|
39
|
-
error(message, ...args) {
|
|
40
|
-
if (shouldLog('error'))
|
|
41
|
-
console.error(formatMessage('error', tag, message), ...args);
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
export function setLogLevel(level) {
|
|
46
|
-
currentLevel = level;
|
|
47
|
-
}
|
|
7
|
+
export { createLogger, setLogLevel } from '@dimina-kit/electron-deck/main';
|
|
48
8
|
//# sourceMappingURL=logger.js.map
|
|
@@ -28,6 +28,13 @@ export interface ServiceHostWindowOptions {
|
|
|
28
28
|
* which page logic referencing `qd.*` throws `ReferenceError: qd is not defined`.
|
|
29
29
|
*/
|
|
30
30
|
apiNamespaces?: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Observes a spawn-navigation `loadURL` rejection (NOT encoded into the
|
|
33
|
+
* spawn URL). Without it the fresh-window path swallows the failure entirely
|
|
34
|
+
* — `did-finish-load` never fires, the session hangs, and the only signal is
|
|
35
|
+
* the launch watchdog's late timeout instead of the real cause.
|
|
36
|
+
*/
|
|
37
|
+
onLoadFailed?: (err: unknown) => void;
|
|
31
38
|
}
|
|
32
39
|
/**
|
|
33
40
|
* Construct an (un-navigated) service-host BrowserWindow. Split out from
|
|
@@ -52,8 +59,15 @@ export declare function buildServiceHostSpawnUrl(opts: ServiceHostWindowOptions)
|
|
|
52
59
|
* preload re-evaluates on this navigation, rebuilding `__diminaSpawnContext`.
|
|
53
60
|
* Returns the load promise (resolves on did-finish-load). Attaches the dev-only
|
|
54
61
|
* detached-DevTools hook, matching the original behavior.
|
|
62
|
+
*
|
|
63
|
+
* A `loadURL` rejection is swallowed — the pooled-window recycle path expects
|
|
64
|
+
* a failed navigation not to reject the caller — but `opts.onLoadFailed`, when
|
|
65
|
+
* given, still observes the original rejection reason (routed by the caller
|
|
66
|
+
* into the diagnostics bus) before this resolves.
|
|
55
67
|
*/
|
|
56
|
-
export declare function navigateServiceHost(win: BrowserWindow, url: string
|
|
68
|
+
export declare function navigateServiceHost(win: BrowserWindow, url: string, opts?: {
|
|
69
|
+
onLoadFailed?: (err: unknown) => void;
|
|
70
|
+
}): Promise<void>;
|
|
57
71
|
/**
|
|
58
72
|
* The spec a `ServiceHostPool` uses to pre-warm service-host windows. Keeps the
|
|
59
73
|
* pooled window byte-equivalent to `constructServiceHostWindow`'s output, and
|
|
@@ -72,9 +72,17 @@ export function buildServiceHostSpawnUrl(opts) {
|
|
|
72
72
|
* preload re-evaluates on this navigation, rebuilding `__diminaSpawnContext`.
|
|
73
73
|
* Returns the load promise (resolves on did-finish-load). Attaches the dev-only
|
|
74
74
|
* detached-DevTools hook, matching the original behavior.
|
|
75
|
+
*
|
|
76
|
+
* A `loadURL` rejection is swallowed — the pooled-window recycle path expects
|
|
77
|
+
* a failed navigation not to reject the caller — but `opts.onLoadFailed`, when
|
|
78
|
+
* given, still observes the original rejection reason (routed by the caller
|
|
79
|
+
* into the diagnostics bus) before this resolves.
|
|
75
80
|
*/
|
|
76
|
-
export function navigateServiceHost(win, url) {
|
|
77
|
-
const loaded = Promise.resolve(win.loadURL(url)).then(() => undefined, () =>
|
|
81
|
+
export function navigateServiceHost(win, url, opts) {
|
|
82
|
+
const loaded = Promise.resolve(win.loadURL(url)).then(() => undefined, (err) => {
|
|
83
|
+
opts?.onLoadFailed?.(err);
|
|
84
|
+
return undefined;
|
|
85
|
+
});
|
|
78
86
|
if (!app.isPackaged && process.env.NODE_ENV !== 'test') {
|
|
79
87
|
// Recycled pooled windows get navigated repeatedly. A `once` hook that never
|
|
80
88
|
// fires (the spawn was disposed before its service.html settled) stays on the
|
|
@@ -141,7 +149,7 @@ export function createServiceHostWindow(opts) {
|
|
|
141
149
|
// so its logic-layer localStorage/cookies are shared with the project's render
|
|
142
150
|
// side only, never with other projects.
|
|
143
151
|
const win = constructServiceHostWindow({ appId: opts.appId, partition: miniappPartition(opts.appId, opts.projectPath) });
|
|
144
|
-
void navigateServiceHost(win, buildServiceHostSpawnUrl(opts));
|
|
152
|
+
void navigateServiceHost(win, buildServiceHostSpawnUrl(opts), { onLoadFailed: opts.onLoadFailed });
|
|
145
153
|
return win;
|
|
146
154
|
}
|
|
147
155
|
//# sourceMappingURL=create.js.map
|