@weapp-vite/miniprogram-automator 1.3.0 → 1.4.0
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 +74 -0
- package/dist/index.d.mts +41 -6
- package/dist/index.mjs +705 -77
- package/dist/{launch-anJvfi-6.mjs → launch-ElaGbFxh.mjs} +1387 -328
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import { EventEmitter } from "node:events";
|
|
7
7
|
import { createDebug } from "obug";
|
|
8
8
|
import WebSocket from "ws";
|
|
9
|
+
import { stripVTControlCharacters } from "node:util";
|
|
9
10
|
import fs from "node:fs/promises";
|
|
10
11
|
import os from "node:os";
|
|
11
12
|
import { spawn } from "node:child_process";
|
|
@@ -235,6 +236,304 @@ function cmpVersion(versionA, versionB) {
|
|
|
235
236
|
return 0;
|
|
236
237
|
}
|
|
237
238
|
//#endregion
|
|
239
|
+
//#region src/componentCaller/runtime.ts
|
|
240
|
+
/** 在同一个 Runtime 上下文持有真实 caller,不转用其他 ContextManager 的 token。 */
|
|
241
|
+
function createComponentCallerSession(expected, observerTimeout) {
|
|
242
|
+
const root = this;
|
|
243
|
+
const adapter = root.__glassEaselAdapter__;
|
|
244
|
+
const environment = adapter?.getEnv?.();
|
|
245
|
+
if (typeof environment?.getAdapterPage !== "function") throw new TypeError("Component caller backend selection is unavailable");
|
|
246
|
+
if (environment.getAdapterPage(expected.pageId)) return {
|
|
247
|
+
backend: "glass-easel",
|
|
248
|
+
dispose: () => ({
|
|
249
|
+
state: "disposed",
|
|
250
|
+
added: false,
|
|
251
|
+
removed: false
|
|
252
|
+
})
|
|
253
|
+
};
|
|
254
|
+
const bridge = root.WeixinJSBridge;
|
|
255
|
+
if (!bridge || typeof bridge.addSubscribeListener !== "function" || typeof bridge.removeSubscribeListener !== "function" || !adapter || typeof adapter.getVirtualDOM !== "function") throw new Error("Exact component caller bridge is unavailable");
|
|
256
|
+
const add = bridge.addSubscribeListener;
|
|
257
|
+
const remove = bridge.removeSubscribeListener;
|
|
258
|
+
const subscribe = bridge.subscribe;
|
|
259
|
+
const virtualDOM = adapter.getVirtualDOM();
|
|
260
|
+
const exparser = virtualDOM?.getExparser?.();
|
|
261
|
+
if (typeof virtualDOM?.getNodeById !== "function" || typeof virtualDOM.getNodeId !== "function" || typeof virtualDOM.getPageId !== "function" || typeof exparser?.Element?.getMethodCaller !== "function") throw new TypeError("Exact component caller mapping is unavailable");
|
|
262
|
+
let state = "created";
|
|
263
|
+
let caller;
|
|
264
|
+
let node;
|
|
265
|
+
let virtualId;
|
|
266
|
+
let added = false;
|
|
267
|
+
let removed = false;
|
|
268
|
+
let totalInvocations = 0;
|
|
269
|
+
let matchingInvocations = 0;
|
|
270
|
+
let timer;
|
|
271
|
+
const errors = [];
|
|
272
|
+
const eventName = "exparserComponentInvoker";
|
|
273
|
+
function identitiesMatch() {
|
|
274
|
+
return root.WeixinJSBridge === bridge && root.__glassEaselAdapter__ === adapter && bridge.subscribe === subscribe && bridge.addSubscribeListener === add && bridge.removeSubscribeListener === remove && adapter.getVirtualDOM() === virtualDOM && virtualDOM.getExparser() === exparser;
|
|
275
|
+
}
|
|
276
|
+
function snapshot() {
|
|
277
|
+
return {
|
|
278
|
+
state,
|
|
279
|
+
added,
|
|
280
|
+
removed,
|
|
281
|
+
totalInvocations,
|
|
282
|
+
matchingInvocations,
|
|
283
|
+
errors: [...errors],
|
|
284
|
+
identitiesMatch: identitiesMatch()
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function listener(message, sender) {
|
|
288
|
+
totalInvocations += 1;
|
|
289
|
+
if (state !== "armed" && state !== "resolved") return;
|
|
290
|
+
if (message?.method !== "resolveComponentCaller") return;
|
|
291
|
+
matchingInvocations += 1;
|
|
292
|
+
try {
|
|
293
|
+
const args = message.params;
|
|
294
|
+
if (matchingInvocations !== 1 || !Array.isArray(args) || args.length !== 1 || args[0] === null || typeof args[0] !== "object" || Array.isArray(args[0])) throw new Error("Component caller resolver payload is ambiguous");
|
|
295
|
+
const params = args[0];
|
|
296
|
+
const id = params.nodeId;
|
|
297
|
+
if (!Number.isSafeInteger(message.callbackId) || message.callbackId <= 0 || sender !== expected.pageId || params.webviewId !== expected.pageId || !(typeof id === "string" && id.length > 0 || Number.isSafeInteger(id) && id >= 0)) throw new Error("Component caller resolver identity disagrees");
|
|
298
|
+
if (!identitiesMatch()) throw new Error("Component caller bridge identity changed");
|
|
299
|
+
const resolvedNode = virtualDOM.getNodeById(id, expected.pageId);
|
|
300
|
+
if (!resolvedNode || virtualDOM.getNodeId(resolvedNode, expected.pageId) !== id || virtualDOM.getPageId(resolvedNode) !== expected.pageId) throw new Error("Component caller virtual node identity disagrees");
|
|
301
|
+
const resolvedCaller = exparser.Element.getMethodCaller(resolvedNode);
|
|
302
|
+
if (!resolvedCaller || !["object", "function"].includes(typeof resolvedCaller)) throw new Error("Component caller is unavailable");
|
|
303
|
+
virtualId = id;
|
|
304
|
+
node = resolvedNode;
|
|
305
|
+
caller = resolvedCaller;
|
|
306
|
+
state = "resolved";
|
|
307
|
+
} catch (error) {
|
|
308
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
309
|
+
state = "failed";
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function removeListener() {
|
|
313
|
+
if (added && !removed) {
|
|
314
|
+
remove.call(bridge, eventName, listener);
|
|
315
|
+
removed = true;
|
|
316
|
+
}
|
|
317
|
+
if (timer !== void 0) {
|
|
318
|
+
clearTimeout(timer);
|
|
319
|
+
timer = void 0;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function dispose() {
|
|
323
|
+
try {
|
|
324
|
+
removeListener();
|
|
325
|
+
state = "disposed";
|
|
326
|
+
} finally {
|
|
327
|
+
caller = void 0;
|
|
328
|
+
node = void 0;
|
|
329
|
+
virtualId = void 0;
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
state,
|
|
333
|
+
added,
|
|
334
|
+
removed,
|
|
335
|
+
totalInvocations,
|
|
336
|
+
matchingInvocations,
|
|
337
|
+
errors: [...errors]
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
backend: "exparser",
|
|
342
|
+
arm() {
|
|
343
|
+
if (state !== "created") throw new Error("Component caller observer cannot be armed twice");
|
|
344
|
+
state = "armed";
|
|
345
|
+
timer = setTimeout(() => {
|
|
346
|
+
try {
|
|
347
|
+
dispose();
|
|
348
|
+
} catch (error) {
|
|
349
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
350
|
+
state = "failed";
|
|
351
|
+
}
|
|
352
|
+
}, observerTimeout);
|
|
353
|
+
added = true;
|
|
354
|
+
add.call(bridge, eventName, listener);
|
|
355
|
+
return snapshot();
|
|
356
|
+
},
|
|
357
|
+
snapshot,
|
|
358
|
+
disarm() {
|
|
359
|
+
removeListener();
|
|
360
|
+
if (state !== "resolved" || matchingInvocations !== 1 || errors.length || !identitiesMatch()) throw new Error(`Exact component caller resolution failed: ${JSON.stringify(snapshot())}`);
|
|
361
|
+
state = "ready";
|
|
362
|
+
return snapshot();
|
|
363
|
+
},
|
|
364
|
+
invoke(method, args) {
|
|
365
|
+
if (state !== "ready" || !removed || errors.length || !identitiesMatch()) throw new Error("Component caller is not ready for a single invocation");
|
|
366
|
+
const pages = typeof getCurrentPages === "function" ? getCurrentPages() : [];
|
|
367
|
+
if (pages[pages.length - 1]?.__wxWebviewId__ !== expected.pageId || virtualDOM.getNodeById(virtualId, expected.pageId) !== node || virtualDOM.getNodeId(node, expected.pageId) !== virtualId || virtualDOM.getPageId(node) !== expected.pageId || exparser.Element.getMethodCaller(node) !== caller) throw new Error("Component caller changed before invocation");
|
|
368
|
+
state = "dispatched";
|
|
369
|
+
return caller[method](...args);
|
|
370
|
+
},
|
|
371
|
+
dispose
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/componentCaller/index.ts
|
|
376
|
+
const queues = /* @__PURE__ */ new WeakMap();
|
|
377
|
+
const blocked = /* @__PURE__ */ new WeakMap();
|
|
378
|
+
const owners = /* @__PURE__ */ new WeakMap();
|
|
379
|
+
const TIMEOUT = 2500;
|
|
380
|
+
/** 关闭连接前确认全部自有 listener 已移除;失败时保留 token 与可重试的所有权。 */
|
|
381
|
+
function cleanupComponentCallers(connection) {
|
|
382
|
+
const pending = owners.get(connection);
|
|
383
|
+
if (!pending?.size) return;
|
|
384
|
+
return (async () => {
|
|
385
|
+
const errors = [];
|
|
386
|
+
for (const owner of [...pending]) try {
|
|
387
|
+
await owner.cleanup();
|
|
388
|
+
} catch (error) {
|
|
389
|
+
errors.push(error);
|
|
390
|
+
}
|
|
391
|
+
if (errors.length) throw new AggregateError(errors, "Component caller cleanup remains incomplete", { cause: errors[0] });
|
|
392
|
+
blocked.delete(connection);
|
|
393
|
+
})();
|
|
394
|
+
}
|
|
395
|
+
function result(response) {
|
|
396
|
+
if (response.exceptionDetails) throw new Error(response.exceptionDetails.text ?? "Component caller Runtime exception", { cause: response.exceptionDetails });
|
|
397
|
+
if (!response.result) throw new Error("Component caller Runtime result is missing");
|
|
398
|
+
return response.result;
|
|
399
|
+
}
|
|
400
|
+
function serializeArguments(args) {
|
|
401
|
+
const payload = JSON.parse(stringify({ params: { args } }));
|
|
402
|
+
return JSON.stringify(payload.params.args);
|
|
403
|
+
}
|
|
404
|
+
/** 保持原 SDK 的 returnByValue 解包,不等待业务返回的 Promise。 */
|
|
405
|
+
async function unwrap(connection, remote) {
|
|
406
|
+
if (remote.subtype === "null") return null;
|
|
407
|
+
if (remote.type === "undefined") return;
|
|
408
|
+
if (remote.value !== void 0) return remote.value;
|
|
409
|
+
if (remote.unserializableValue) {
|
|
410
|
+
const value = remote.unserializableValue;
|
|
411
|
+
if (/^-?\d+n$/.test(value)) return BigInt(value.slice(0, -1));
|
|
412
|
+
if ([
|
|
413
|
+
"NaN",
|
|
414
|
+
"Infinity",
|
|
415
|
+
"-Infinity",
|
|
416
|
+
"-0"
|
|
417
|
+
].includes(value)) return Number(value);
|
|
418
|
+
throw new Error(`Unsupported component return value: ${value}`);
|
|
419
|
+
}
|
|
420
|
+
if (remote.objectId) try {
|
|
421
|
+
const converted = await connection.send("App.CDPCommand", {
|
|
422
|
+
domain: "Runtime",
|
|
423
|
+
method: "callFunctionOn",
|
|
424
|
+
params: {
|
|
425
|
+
objectId: remote.objectId,
|
|
426
|
+
functionDeclaration: "function () { try { return JSON.stringify(this) } catch { return String(this) } }",
|
|
427
|
+
returnByValue: true
|
|
428
|
+
}
|
|
429
|
+
}, { timeout: TIMEOUT });
|
|
430
|
+
if (converted.result?.value !== void 0) try {
|
|
431
|
+
return JSON.parse(converted.result.value);
|
|
432
|
+
} catch {
|
|
433
|
+
return converted.result.value;
|
|
434
|
+
}
|
|
435
|
+
} catch {}
|
|
436
|
+
return remote.description ?? `[${remote.type}]`;
|
|
437
|
+
}
|
|
438
|
+
async function invoke(connection, target, method, args, native) {
|
|
439
|
+
let token;
|
|
440
|
+
let owner;
|
|
441
|
+
const errors = [];
|
|
442
|
+
let cleanupFailed = false;
|
|
443
|
+
let nativeBackend = false;
|
|
444
|
+
let value;
|
|
445
|
+
const cdp = async (domain, command, params) => {
|
|
446
|
+
return await connection.send("App.CDPCommand", {
|
|
447
|
+
domain,
|
|
448
|
+
method: command,
|
|
449
|
+
params
|
|
450
|
+
}, { timeout: TIMEOUT });
|
|
451
|
+
};
|
|
452
|
+
const wrapper = async (body) => result(await cdp("Runtime", "callFunctionOn", {
|
|
453
|
+
objectId: token,
|
|
454
|
+
functionDeclaration: `function () { return ${body} }`,
|
|
455
|
+
arguments: [],
|
|
456
|
+
returnByValue: true
|
|
457
|
+
}));
|
|
458
|
+
try {
|
|
459
|
+
if (!Number.isSafeInteger(target.nodeId) || target.nodeId < 0 || !Number.isSafeInteger(target.pageId)) throw new Error("Invalid component caller native target");
|
|
460
|
+
if ((await connection.send("App.getCurrentPage", {}, { timeout: TIMEOUT })).pageId !== target.pageId) throw new Error("Component caller target is not on the current page");
|
|
461
|
+
const created = result(await cdp("Runtime", "evaluate", {
|
|
462
|
+
expression: `(${createComponentCallerSession.toString()})(${JSON.stringify(target)}, 10000)`,
|
|
463
|
+
returnByValue: false
|
|
464
|
+
}));
|
|
465
|
+
if (!created.objectId) throw new Error("Component caller session has no owned Runtime token");
|
|
466
|
+
token = created.objectId;
|
|
467
|
+
let cleaning;
|
|
468
|
+
let disposed = false;
|
|
469
|
+
let released = false;
|
|
470
|
+
owner = { cleanup() {
|
|
471
|
+
cleaning ??= (async () => {
|
|
472
|
+
if (released) return;
|
|
473
|
+
if (!disposed) {
|
|
474
|
+
const snapshot = (await wrapper("this.dispose()")).value;
|
|
475
|
+
if (snapshot?.state !== "disposed" || snapshot.added && !snapshot.removed) throw new Error("Component caller listener removal was not confirmed");
|
|
476
|
+
disposed = true;
|
|
477
|
+
}
|
|
478
|
+
await cdp("Runtime", "releaseObject", { objectId: token });
|
|
479
|
+
released = true;
|
|
480
|
+
owners.get(connection)?.delete(owner);
|
|
481
|
+
})().catch((error) => {
|
|
482
|
+
cleaning = void 0;
|
|
483
|
+
throw error;
|
|
484
|
+
});
|
|
485
|
+
return cleaning;
|
|
486
|
+
} };
|
|
487
|
+
let pending = owners.get(connection);
|
|
488
|
+
if (!pending) {
|
|
489
|
+
pending = /* @__PURE__ */ new Set();
|
|
490
|
+
owners.set(connection, pending);
|
|
491
|
+
}
|
|
492
|
+
pending.add(owner);
|
|
493
|
+
const backend = (await wrapper("this.backend")).value;
|
|
494
|
+
if (backend === "glass-easel") nativeBackend = true;
|
|
495
|
+
else if (backend === "exparser") {
|
|
496
|
+
const argumentSource = serializeArguments(args);
|
|
497
|
+
await wrapper("this.arm()");
|
|
498
|
+
await cdp("WxComponent", "resolveComponentCaller", { nodeId: target.nodeId });
|
|
499
|
+
await wrapper("this.disarm()");
|
|
500
|
+
value = await unwrap(connection, await wrapper(`this.invoke(${JSON.stringify(method)}, ${argumentSource})`));
|
|
501
|
+
} else throw new Error("Unknown component caller backend");
|
|
502
|
+
} catch (error) {
|
|
503
|
+
errors.push(error);
|
|
504
|
+
} finally {
|
|
505
|
+
if (owner) try {
|
|
506
|
+
await owner.cleanup();
|
|
507
|
+
} catch (error) {
|
|
508
|
+
errors.push(error);
|
|
509
|
+
cleanupFailed = true;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (cleanupFailed) blocked.set(connection, new Error("Component caller cleanup failed; reconnect before invoking another component", { cause: errors[0] }));
|
|
513
|
+
if (errors.length === 1) throw errors[0];
|
|
514
|
+
if (errors.length) throw new AggregateError(errors, "Component caller invocation and cleanup failed", { cause: errors[0] });
|
|
515
|
+
if (nativeBackend) {
|
|
516
|
+
if (!native) throw new Error("Native component backend requires the original protocol");
|
|
517
|
+
return await native();
|
|
518
|
+
}
|
|
519
|
+
return value;
|
|
520
|
+
}
|
|
521
|
+
/** 同一连接串行观察 resolver,避免多个组件误认彼此的桥接消息。 */
|
|
522
|
+
async function invokeComponentMethod(connection, target, method, args, native) {
|
|
523
|
+
const pending = (queues.get(connection) ?? Promise.resolve()).then(async () => {
|
|
524
|
+
const failure = blocked.get(connection);
|
|
525
|
+
if (failure) throw failure;
|
|
526
|
+
return await invoke(connection, target, method, args, native);
|
|
527
|
+
});
|
|
528
|
+
const settled = pending.then(() => {}, () => {});
|
|
529
|
+
queues.set(connection, settled);
|
|
530
|
+
try {
|
|
531
|
+
return await pending;
|
|
532
|
+
} finally {
|
|
533
|
+
if (queues.get(connection) === settled) queues.delete(connection);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
//#endregion
|
|
238
537
|
//#region src/pageRouteSnapshot.ts
|
|
239
538
|
const ROUTE_ELEMENT_SNAPSHOT_TIMEOUT = 5e3;
|
|
240
539
|
const ROUTE_ELEMENT_SNAPSHOT_EMPTY_RETRIES = 2;
|
|
@@ -454,6 +753,14 @@ async function readRouteElementSnapshot(connection, route, query, selector, inde
|
|
|
454
753
|
}
|
|
455
754
|
//#endregion
|
|
456
755
|
//#region src/Element.ts
|
|
756
|
+
function normalizeTouchOptions(options) {
|
|
757
|
+
const { changeTouches, ...payload } = options;
|
|
758
|
+
const changedTouches = options.changedTouches ?? changeTouches;
|
|
759
|
+
return changedTouches === void 0 ? payload : {
|
|
760
|
+
...payload,
|
|
761
|
+
changedTouches
|
|
762
|
+
};
|
|
763
|
+
}
|
|
457
764
|
const ELEMENT_QUERY_TIMEOUT = 2500;
|
|
458
765
|
const ELEMENT_WXML_TIMEOUT = 8e3;
|
|
459
766
|
/** Element 的实现。 */
|
|
@@ -559,16 +866,26 @@ var Element = class Element {
|
|
|
559
866
|
await this.send("Element.triggerEvent", payload);
|
|
560
867
|
}
|
|
561
868
|
async touchstart(options = {}) {
|
|
562
|
-
await this.send("Element.touchstart", options);
|
|
869
|
+
await this.send("Element.touchstart", normalizeTouchOptions(options));
|
|
563
870
|
}
|
|
564
871
|
async touchmove(options = {}) {
|
|
565
|
-
await this.send("Element.touchmove", options);
|
|
872
|
+
await this.send("Element.touchmove", normalizeTouchOptions(options));
|
|
566
873
|
}
|
|
567
874
|
async touchend(options = {}) {
|
|
568
|
-
await this.send("Element.touchend", options);
|
|
875
|
+
await this.send("Element.touchend", normalizeTouchOptions(options));
|
|
569
876
|
}
|
|
570
877
|
async dispatchEvent(options) {
|
|
571
|
-
|
|
878
|
+
const payload = normalizeTouchOptions(options);
|
|
879
|
+
if (!payload.$ && !payload.detail && (payload.eventName === "touchstart" || payload.eventName === "touchmove" || payload.eventName === "touchend") && (payload.touches !== void 0 || payload.changedTouches !== void 0)) {
|
|
880
|
+
const { eventName, ...touchOptions } = payload;
|
|
881
|
+
await this.send(`Element.${eventName}`, touchOptions);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const { detail, ...event } = payload;
|
|
885
|
+
await this.send("Element.dispatchEvent", detail === void 0 ? event : {
|
|
886
|
+
...event,
|
|
887
|
+
eventData: detail
|
|
888
|
+
});
|
|
572
889
|
}
|
|
573
890
|
async _property(name) {
|
|
574
891
|
return await this.getter(name, "getProperties", "properties");
|
|
@@ -750,6 +1067,18 @@ var CustomElement = class extends Element {
|
|
|
750
1067
|
return (await this.send("Element.getData", payload)).data;
|
|
751
1068
|
}
|
|
752
1069
|
async callMethod(method, ...args) {
|
|
1070
|
+
if (this.connection.prefersComponentCallerBridge) {
|
|
1071
|
+
const nodeId = this.nodeId === null ? NaN : Number(this.nodeId);
|
|
1072
|
+
return await invokeComponentMethod(this.connection, {
|
|
1073
|
+
nodeId,
|
|
1074
|
+
pageId: this.pageId
|
|
1075
|
+
}, method, args, async () => {
|
|
1076
|
+
return (await this.send("Element.callMethod", {
|
|
1077
|
+
method,
|
|
1078
|
+
args
|
|
1079
|
+
})).result;
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
753
1082
|
return (await this.send("Element.callMethod", {
|
|
754
1083
|
method,
|
|
755
1084
|
args
|
|
@@ -828,6 +1157,9 @@ var ContextElement = class extends Element {
|
|
|
828
1157
|
/** Transport 的实现。 */
|
|
829
1158
|
var Transport = class extends EventEmitter {
|
|
830
1159
|
ws;
|
|
1160
|
+
closed = false;
|
|
1161
|
+
terminating = false;
|
|
1162
|
+
closing;
|
|
831
1163
|
constructor(ws) {
|
|
832
1164
|
super();
|
|
833
1165
|
this.ws = ws;
|
|
@@ -835,6 +1167,7 @@ var Transport = class extends EventEmitter {
|
|
|
835
1167
|
this.emit("message", typeof data === "string" ? data : data.toString());
|
|
836
1168
|
});
|
|
837
1169
|
ws.on("close", () => {
|
|
1170
|
+
this.closed = true;
|
|
838
1171
|
this.emit("close");
|
|
839
1172
|
});
|
|
840
1173
|
}
|
|
@@ -842,7 +1175,36 @@ var Transport = class extends EventEmitter {
|
|
|
842
1175
|
this.ws.send(message);
|
|
843
1176
|
}
|
|
844
1177
|
close() {
|
|
845
|
-
this.
|
|
1178
|
+
if (this.closing) return this.closing;
|
|
1179
|
+
if (this.closed || this.ws.readyState === 3) return this.closing = Promise.resolve();
|
|
1180
|
+
this.closing = new Promise((resolve, reject) => {
|
|
1181
|
+
let timer;
|
|
1182
|
+
const finish = () => {
|
|
1183
|
+
clearTimeout(timer);
|
|
1184
|
+
this.off("close", finish);
|
|
1185
|
+
resolve();
|
|
1186
|
+
};
|
|
1187
|
+
timer = setTimeout(() => {
|
|
1188
|
+
this.off("close", finish);
|
|
1189
|
+
reject(Object.assign(/* @__PURE__ */ new Error("DevTools websocket did not close within 2000ms"), { code: "DEVTOOLS_DISCONNECT_TIMEOUT" }));
|
|
1190
|
+
}, 2e3);
|
|
1191
|
+
this.once("close", finish);
|
|
1192
|
+
try {
|
|
1193
|
+
if (!this.terminating) {
|
|
1194
|
+
this.terminating = true;
|
|
1195
|
+
this.ws.terminate();
|
|
1196
|
+
}
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
this.terminating = false;
|
|
1199
|
+
clearTimeout(timer);
|
|
1200
|
+
this.off("close", finish);
|
|
1201
|
+
reject(error);
|
|
1202
|
+
}
|
|
1203
|
+
}).catch((error) => {
|
|
1204
|
+
this.closing = void 0;
|
|
1205
|
+
throw error;
|
|
1206
|
+
});
|
|
1207
|
+
return this.closing;
|
|
846
1208
|
}
|
|
847
1209
|
};
|
|
848
1210
|
//#endregion
|
|
@@ -868,9 +1230,11 @@ const APP_SERVICE_PAGE_METHOD_VERSIONS = /* @__PURE__ */ new Set([
|
|
|
868
1230
|
var Connection = class Connection extends EventEmitter {
|
|
869
1231
|
transport;
|
|
870
1232
|
disposed = false;
|
|
1233
|
+
disposing;
|
|
871
1234
|
callbacks = /* @__PURE__ */ new Map();
|
|
872
1235
|
useAppServicePageProtocol = false;
|
|
873
1236
|
useAppServicePageMethod = false;
|
|
1237
|
+
useComponentCallerBridge = false;
|
|
874
1238
|
constructor(transport) {
|
|
875
1239
|
super();
|
|
876
1240
|
this.transport = transport;
|
|
@@ -910,18 +1274,51 @@ var Connection = class Connection extends EventEmitter {
|
|
|
910
1274
|
});
|
|
911
1275
|
}
|
|
912
1276
|
dispose() {
|
|
913
|
-
if (this.
|
|
914
|
-
|
|
915
|
-
|
|
1277
|
+
if (this.disposing) return this.disposing;
|
|
1278
|
+
let resolve;
|
|
1279
|
+
let reject;
|
|
1280
|
+
this.disposing = new Promise((done, fail) => {
|
|
1281
|
+
resolve = done;
|
|
1282
|
+
reject = fail;
|
|
1283
|
+
}).catch((error) => {
|
|
1284
|
+
this.disposing = void 0;
|
|
1285
|
+
throw error;
|
|
1286
|
+
});
|
|
1287
|
+
const cleanup = cleanupComponentCallers(this);
|
|
1288
|
+
if (cleanup) cleanup.then(() => this.closeTransport(resolve, reject), reject);
|
|
1289
|
+
else this.closeTransport(resolve, reject);
|
|
1290
|
+
return this.disposing;
|
|
1291
|
+
}
|
|
1292
|
+
closeTransport(resolve, reject) {
|
|
1293
|
+
const errors = [];
|
|
1294
|
+
try {
|
|
1295
|
+
this.onClose();
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
errors.push(error);
|
|
1298
|
+
}
|
|
916
1299
|
this.transport.off("message", this.onMessage);
|
|
917
1300
|
this.transport.off("close", this.onClose);
|
|
918
|
-
|
|
1301
|
+
const finish = () => {
|
|
1302
|
+
if (errors.length === 1) reject(errors[0]);
|
|
1303
|
+
else if (errors.length) reject(new AggregateError(errors, "Connection close notification and transport cleanup failed", { cause: errors[0] }));
|
|
1304
|
+
else resolve();
|
|
1305
|
+
};
|
|
1306
|
+
try {
|
|
1307
|
+
Promise.resolve(this.transport.close()).then(finish, (error) => {
|
|
1308
|
+
errors.push(error);
|
|
1309
|
+
finish();
|
|
1310
|
+
});
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
errors.push(error);
|
|
1313
|
+
finish();
|
|
1314
|
+
}
|
|
919
1315
|
}
|
|
920
1316
|
/** 根据 DevTools 版本选择稳定的 Page 协议实现。 */
|
|
921
1317
|
configureToolInfo(info) {
|
|
922
1318
|
const version = String(info.version ?? "");
|
|
923
1319
|
this.useAppServicePageProtocol = APP_SERVICE_PAGE_PROTOCOL_VERSIONS.has(version);
|
|
924
1320
|
this.useAppServicePageMethod = this.useAppServicePageProtocol || APP_SERVICE_PAGE_METHOD_VERSIONS.has(version);
|
|
1321
|
+
this.useComponentCallerBridge = version === "2.02.2608080" && info.SDKVersion === "3.17.5";
|
|
925
1322
|
}
|
|
926
1323
|
get prefersAppServicePageProtocol() {
|
|
927
1324
|
return this.useAppServicePageProtocol;
|
|
@@ -930,6 +1327,10 @@ var Connection = class Connection extends EventEmitter {
|
|
|
930
1327
|
get prefersAppServicePageMethod() {
|
|
931
1328
|
return this.useAppServicePageMethod;
|
|
932
1329
|
}
|
|
1330
|
+
/** 仅为实测存在跨上下文 token 错绑的 IDE 与基础库启用真实 caller 桥接。 */
|
|
1331
|
+
get prefersComponentCallerBridge() {
|
|
1332
|
+
return this.useComponentCallerBridge;
|
|
1333
|
+
}
|
|
933
1334
|
onMessage = (message) => {
|
|
934
1335
|
debugProtocol(`${dateFormat("yyyy-mm-dd HH:MM:ss:l")} ◀ RECV ${message}`);
|
|
935
1336
|
const { id, method, error, result, params } = JSON.parse(message);
|
|
@@ -942,17 +1343,21 @@ var Connection = class Connection extends EventEmitter {
|
|
|
942
1343
|
this.callbacks.delete(id);
|
|
943
1344
|
clearTimeout(callback.timeout);
|
|
944
1345
|
if (error) {
|
|
945
|
-
|
|
1346
|
+
const message = typeof error.message === "string" ? error.message : error.message === void 0 ? closeErrTip : JSON.stringify(error.message);
|
|
1347
|
+
callback.reject(new Error(message, { cause: error }));
|
|
946
1348
|
return;
|
|
947
1349
|
}
|
|
948
1350
|
callback.resolve(result);
|
|
949
1351
|
};
|
|
950
1352
|
onClose = () => {
|
|
1353
|
+
if (this.disposed) return;
|
|
1354
|
+
this.disposed = true;
|
|
951
1355
|
for (const callback of this.callbacks.values()) {
|
|
952
1356
|
clearTimeout(callback.timeout);
|
|
953
1357
|
callback.reject(/* @__PURE__ */ new Error(closeErrTip));
|
|
954
1358
|
}
|
|
955
1359
|
this.callbacks.clear();
|
|
1360
|
+
this.emit("close");
|
|
956
1361
|
};
|
|
957
1362
|
static create(url, timeout = CONNECT_TIMEOUT, signal) {
|
|
958
1363
|
signal?.throwIfAborted();
|
|
@@ -1000,7 +1405,25 @@ var Connection = class Connection extends EventEmitter {
|
|
|
1000
1405
|
//#endregion
|
|
1001
1406
|
//#region src/headless.ts
|
|
1002
1407
|
async function launchHeadlessAutomator(options) {
|
|
1003
|
-
return await (await import("./launch-
|
|
1408
|
+
return await (await import("./launch-ElaGbFxh.mjs")).launch({ projectPath: options.projectPath });
|
|
1409
|
+
}
|
|
1410
|
+
//#endregion
|
|
1411
|
+
//#region src/launcher/diagnostics.ts
|
|
1412
|
+
/** 将本次 CLI 的有界输出附到原始错误,避免泄露登录票据及账号参数。 */
|
|
1413
|
+
function attachCliDiagnostics(error, state, secrets = []) {
|
|
1414
|
+
const original = error instanceof Error ? error : new Error("Failed to launch WeChat DevTools", { cause: error });
|
|
1415
|
+
if (original.cliOutput !== void 0) return original;
|
|
1416
|
+
let output = stripVTControlCharacters(state.output).trim();
|
|
1417
|
+
for (const secret of secrets.filter(Boolean).sort((a, b) => b.length - a.length)) output = output.split(secret).join("[redacted]");
|
|
1418
|
+
output = output.replace(/(authorization["']?\s*[:=]\s*["']?)(?:Bearer|Basic)\s+[^\s"',;}]+/gi, "$1[redacted]");
|
|
1419
|
+
output = output.replace(/((?:--)?(?:auto-account|account|ticket|access[_-]?token|refresh[_-]?token|token|authorization)["']?\s*(?:[:=]\s*|\s))(["']?)([^\s"',;}]+)\2/gi, "$1[redacted]");
|
|
1420
|
+
if (!output) return original;
|
|
1421
|
+
const truncated = state.outputTruncated || output.length > 32768;
|
|
1422
|
+
output = output.slice(-32768);
|
|
1423
|
+
const target = Object.isExtensible(original) ? original : new Error(original.message, { cause: original });
|
|
1424
|
+
target.cliOutput = output;
|
|
1425
|
+
target.message += `\ncli output${truncated ? " (truncated; last 32768 characters)" : ""}:\n${output}`;
|
|
1426
|
+
return target;
|
|
1004
1427
|
}
|
|
1005
1428
|
//#endregion
|
|
1006
1429
|
//#region src/launcher/portLease.ts
|
|
@@ -1026,7 +1449,7 @@ function isProcessAlive(pid) {
|
|
|
1026
1449
|
}
|
|
1027
1450
|
}
|
|
1028
1451
|
async function removePortLock(leasePath) {
|
|
1029
|
-
await fs.rm(leasePath, { force: true })
|
|
1452
|
+
await fs.rm(leasePath, { force: true });
|
|
1030
1453
|
}
|
|
1031
1454
|
async function removeStalePortLock(leasePath) {
|
|
1032
1455
|
try {
|
|
@@ -1058,14 +1481,18 @@ async function createPortLock(port) {
|
|
|
1058
1481
|
return leasePath;
|
|
1059
1482
|
}
|
|
1060
1483
|
function createLease(port, leasePath) {
|
|
1061
|
-
let
|
|
1484
|
+
let releasing;
|
|
1062
1485
|
return {
|
|
1063
1486
|
path: leasePath,
|
|
1064
1487
|
port,
|
|
1065
|
-
release:
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1488
|
+
release: () => {
|
|
1489
|
+
releasing ??= (async () => {
|
|
1490
|
+
if (leasePath) await removePortLock(leasePath);
|
|
1491
|
+
})().catch((error) => {
|
|
1492
|
+
releasing = void 0;
|
|
1493
|
+
throw error;
|
|
1494
|
+
});
|
|
1495
|
+
return releasing;
|
|
1069
1496
|
}
|
|
1070
1497
|
};
|
|
1071
1498
|
}
|
|
@@ -1185,7 +1612,8 @@ function spawnWechatCli(cliPath, args, cwd, scope) {
|
|
|
1185
1612
|
error: null,
|
|
1186
1613
|
success: false,
|
|
1187
1614
|
exited: false,
|
|
1188
|
-
output: ""
|
|
1615
|
+
output: "",
|
|
1616
|
+
outputTruncated: false
|
|
1189
1617
|
};
|
|
1190
1618
|
const bootstrapArgs = resolveWechatDevtoolsBootstrapArgs(args);
|
|
1191
1619
|
const target = shouldUseWindowsCommandShell(cliPath) ? resolveWindowsBatchSpawn(cliPath, bootstrapArgs) : {
|
|
@@ -1221,7 +1649,9 @@ function spawnWechatCli(cliPath, args, cwd, scope) {
|
|
|
1221
1649
|
state.error = error;
|
|
1222
1650
|
});
|
|
1223
1651
|
const output = (chunk) => {
|
|
1224
|
-
|
|
1652
|
+
const combined = state.output + String(chunk);
|
|
1653
|
+
state.outputTruncated ||= combined.length > 32768;
|
|
1654
|
+
state.output = combined.slice(-32768);
|
|
1225
1655
|
};
|
|
1226
1656
|
child.stdout?.on("data", output);
|
|
1227
1657
|
child.stderr?.on("data", output);
|
|
@@ -1236,7 +1666,7 @@ function spawnWechatCli(cliPath, args, cwd, scope) {
|
|
|
1236
1666
|
}
|
|
1237
1667
|
//#endregion
|
|
1238
1668
|
//#region package.json
|
|
1239
|
-
var version = "1.
|
|
1669
|
+
var version = "1.4.0";
|
|
1240
1670
|
//#endregion
|
|
1241
1671
|
//#region src/appServiceHeap.ts
|
|
1242
1672
|
function isRecord$1(value) {
|
|
@@ -1275,6 +1705,112 @@ async function readAppServiceHeapUsage(send, options = {}) {
|
|
|
1275
1705
|
};
|
|
1276
1706
|
}
|
|
1277
1707
|
//#endregion
|
|
1708
|
+
//#region src/cdp/index.ts
|
|
1709
|
+
/** 单个连接共享 CDP 启用状态与订阅;不创建额外 WebSocket。 */
|
|
1710
|
+
var CDPController = class {
|
|
1711
|
+
connection;
|
|
1712
|
+
send;
|
|
1713
|
+
enabling;
|
|
1714
|
+
enabled = false;
|
|
1715
|
+
stopped = false;
|
|
1716
|
+
subscriptions = /* @__PURE__ */ new Set();
|
|
1717
|
+
session;
|
|
1718
|
+
constructor(connection, send) {
|
|
1719
|
+
this.connection = connection;
|
|
1720
|
+
this.send = send;
|
|
1721
|
+
connection.on("close", this.stop);
|
|
1722
|
+
this.session = {
|
|
1723
|
+
enable: (options) => this.enable(options),
|
|
1724
|
+
sendCommand: (domain, method, params = {}, options) => this.request("App.CDPCommand", {
|
|
1725
|
+
domain,
|
|
1726
|
+
method,
|
|
1727
|
+
params
|
|
1728
|
+
}, options),
|
|
1729
|
+
listProtocol: (options) => this.request("App.CDPListProtocol", {}, options),
|
|
1730
|
+
onEvent: (listener) => this.onEvent(listener)
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
enable(options) {
|
|
1734
|
+
if (this.stopped) return Promise.reject(/* @__PURE__ */ new Error("CDP connection closed"));
|
|
1735
|
+
try {
|
|
1736
|
+
this.validateTimeout(options);
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
return Promise.reject(error);
|
|
1739
|
+
}
|
|
1740
|
+
if (!this.enabling) {
|
|
1741
|
+
let pending;
|
|
1742
|
+
try {
|
|
1743
|
+
pending = this.send("App.CDPEnable", {}, options);
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
pending = Promise.reject(error);
|
|
1746
|
+
}
|
|
1747
|
+
this.enabling = pending.then((result) => {
|
|
1748
|
+
this.enabled = true;
|
|
1749
|
+
return result;
|
|
1750
|
+
}, (error) => {
|
|
1751
|
+
this.enabling = void 0;
|
|
1752
|
+
throw error;
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
if (options?.timeout === void 0) return this.enabling;
|
|
1756
|
+
return new Promise((resolve, reject) => {
|
|
1757
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out enabling CDP within ${options.timeout}ms`)), options.timeout);
|
|
1758
|
+
this.enabling.then(resolve, reject).finally(() => clearTimeout(timer));
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
/** 日志显式刷新时重新注册事件通道,兼容 AppService 重载。 */
|
|
1762
|
+
refresh(options) {
|
|
1763
|
+
if (this.enabled) {
|
|
1764
|
+
this.enabled = false;
|
|
1765
|
+
this.enabling = void 0;
|
|
1766
|
+
}
|
|
1767
|
+
return this.enable(options);
|
|
1768
|
+
}
|
|
1769
|
+
stop = () => {
|
|
1770
|
+
this.stopped = true;
|
|
1771
|
+
this.enabled = false;
|
|
1772
|
+
this.enabling = void 0;
|
|
1773
|
+
this.connection.off("close", this.stop);
|
|
1774
|
+
for (const unsubscribe of this.subscriptions) unsubscribe();
|
|
1775
|
+
};
|
|
1776
|
+
async request(method, params, options) {
|
|
1777
|
+
this.validateTimeout(options);
|
|
1778
|
+
const startedAt = Date.now();
|
|
1779
|
+
await this.enable(options);
|
|
1780
|
+
if (this.stopped) throw new Error("CDP connection closed");
|
|
1781
|
+
if (options?.timeout === void 0) return await this.send(method, params);
|
|
1782
|
+
const timeout = options.timeout - (Date.now() - startedAt);
|
|
1783
|
+
if (timeout <= 0) throw new Error(`Timed out executing ${method} within ${options.timeout}ms`);
|
|
1784
|
+
return await this.send(method, params, { timeout });
|
|
1785
|
+
}
|
|
1786
|
+
validateTimeout(options) {
|
|
1787
|
+
if (options?.timeout !== void 0 && (!Number.isFinite(options.timeout) || options.timeout <= 0)) throw new RangeError("CDP timeout must be finite and positive");
|
|
1788
|
+
}
|
|
1789
|
+
onEvent(listener) {
|
|
1790
|
+
if (this.stopped) throw new Error("CDP connection closed");
|
|
1791
|
+
const receive = (payload) => {
|
|
1792
|
+
listener(payload.domain, payload.event, payload.params);
|
|
1793
|
+
};
|
|
1794
|
+
const unsubscribe = () => {
|
|
1795
|
+
this.connection.off("App.CDPEvent", receive);
|
|
1796
|
+
this.subscriptions.delete(unsubscribe);
|
|
1797
|
+
};
|
|
1798
|
+
this.subscriptions.add(unsubscribe);
|
|
1799
|
+
this.connection.on("App.CDPEvent", receive);
|
|
1800
|
+
return unsubscribe;
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
const controllers = /* @__PURE__ */ new WeakMap();
|
|
1804
|
+
/** 一个协议连接仅拥有一份 CDP 状态,包括被不同 MiniProgram 包装的连接。 */
|
|
1805
|
+
function getCDPController(connection, send) {
|
|
1806
|
+
let controller = controllers.get(connection);
|
|
1807
|
+
if (!controller) {
|
|
1808
|
+
controller = new CDPController(connection, send);
|
|
1809
|
+
controllers.set(connection, controller);
|
|
1810
|
+
}
|
|
1811
|
+
return controller;
|
|
1812
|
+
}
|
|
1813
|
+
//#endregion
|
|
1278
1814
|
//#region src/Native.ts
|
|
1279
1815
|
/** Native 的实现。 */
|
|
1280
1816
|
var Native = class extends EventEmitter {
|
|
@@ -2641,16 +3177,21 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2641
3177
|
logEnabling;
|
|
2642
3178
|
structuredLogs = false;
|
|
2643
3179
|
structuredConsole;
|
|
3180
|
+
cdp;
|
|
2644
3181
|
pageMap = /* @__PURE__ */ new Map();
|
|
2645
3182
|
nativeIns;
|
|
3183
|
+
disconnecting;
|
|
3184
|
+
closing;
|
|
2646
3185
|
constructor(connection) {
|
|
2647
3186
|
super();
|
|
2648
3187
|
this.connection = connection;
|
|
3188
|
+
this.cdp = getCDPController(connection, (method, params, options) => this.send(method, params, options));
|
|
2649
3189
|
this.structuredConsole = new StructuredConsole((method, params, options) => this.send(method, params, options), (entry) => this.emit("console", entry));
|
|
2650
3190
|
connection.on("App.CDPEvent", this.structuredConsole.receive);
|
|
2651
3191
|
connection.on("App.logAdded", this.onLogAdded);
|
|
2652
3192
|
connection.on("App.bindingCalled", this.onBindingCalled);
|
|
2653
3193
|
connection.on("App.exceptionThrown", this.onExceptionThrown);
|
|
3194
|
+
connection.on("close", this.stopSubscriptions);
|
|
2654
3195
|
}
|
|
2655
3196
|
async pageStack() {
|
|
2656
3197
|
const { pageStack } = await this.send("App.getPageStack");
|
|
@@ -2795,16 +3336,24 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2795
3336
|
duration: 0
|
|
2796
3337
|
});
|
|
2797
3338
|
}
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
3339
|
+
close() {
|
|
3340
|
+
this.closing ??= (async () => {
|
|
3341
|
+
try {
|
|
3342
|
+
try {
|
|
3343
|
+
await withTimeout(this.send("App.exit"), CLOSE_STEP_TIMEOUT);
|
|
3344
|
+
} catch {}
|
|
3345
|
+
await sleep(1e3);
|
|
3346
|
+
try {
|
|
3347
|
+
await withTimeout(this.send("Tool.close"), CLOSE_STEP_TIMEOUT);
|
|
3348
|
+
} catch {}
|
|
3349
|
+
} finally {
|
|
3350
|
+
await this.disconnect();
|
|
3351
|
+
}
|
|
3352
|
+
})().catch((error) => {
|
|
3353
|
+
this.closing = void 0;
|
|
3354
|
+
throw error;
|
|
3355
|
+
});
|
|
3356
|
+
return this.closing;
|
|
2808
3357
|
}
|
|
2809
3358
|
async remote(auto = false) {
|
|
2810
3359
|
const { qrCode } = await this.send("Tool.enableRemoteDebug", { auto });
|
|
@@ -2817,9 +3366,23 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2817
3366
|
});
|
|
2818
3367
|
}
|
|
2819
3368
|
disconnect() {
|
|
2820
|
-
this.
|
|
2821
|
-
this.
|
|
3369
|
+
if (this.disconnecting) return this.disconnecting;
|
|
3370
|
+
this.stopSubscriptions();
|
|
3371
|
+
this.disconnecting = Promise.resolve(this.connection.dispose()).catch((error) => {
|
|
3372
|
+
this.disconnecting = void 0;
|
|
3373
|
+
throw error;
|
|
3374
|
+
});
|
|
3375
|
+
return this.disconnecting;
|
|
2822
3376
|
}
|
|
3377
|
+
stopSubscriptions = () => {
|
|
3378
|
+
this.structuredConsole.stop();
|
|
3379
|
+
this.cdp.stop();
|
|
3380
|
+
this.connection.off("App.CDPEvent", this.structuredConsole.receive);
|
|
3381
|
+
this.connection.off("App.logAdded", this.onLogAdded);
|
|
3382
|
+
this.connection.off("App.bindingCalled", this.onBindingCalled);
|
|
3383
|
+
this.connection.off("App.exceptionThrown", this.onExceptionThrown);
|
|
3384
|
+
this.connection.off("close", this.stopSubscriptions);
|
|
3385
|
+
};
|
|
2823
3386
|
/** 等待已接收的结构化日志完成 Error 属性检查并发布。 */
|
|
2824
3387
|
async flushConsole() {
|
|
2825
3388
|
await this.structuredConsole.flush();
|
|
@@ -2844,7 +3407,12 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2844
3407
|
let cdpMethod = this.structuredLogs ? "App.CDPEnable" : "App.CDPCommand";
|
|
2845
3408
|
try {
|
|
2846
3409
|
if (this.structuredLogs) {
|
|
2847
|
-
await
|
|
3410
|
+
if (timeout === void 0) await this.cdp.refresh();
|
|
3411
|
+
else {
|
|
3412
|
+
const remaining = timeout - (Date.now() - startedAt);
|
|
3413
|
+
if (remaining <= 0) throw new Error(`Timed out enabling console logging within ${timeout}ms`);
|
|
3414
|
+
await this.cdp.refresh({ timeout: remaining });
|
|
3415
|
+
}
|
|
2848
3416
|
cdpMethod = "App.CDPCommand";
|
|
2849
3417
|
}
|
|
2850
3418
|
await request("App.CDPCommand", {
|
|
@@ -3112,6 +3680,11 @@ var MiniProgram = class extends EventEmitter {
|
|
|
3112
3680
|
}
|
|
3113
3681
|
throw lastError;
|
|
3114
3682
|
}
|
|
3683
|
+
/** 返回当前连接共享的 CDP 调试入口。 */
|
|
3684
|
+
CDP() {
|
|
3685
|
+
return this.cdp.session;
|
|
3686
|
+
}
|
|
3687
|
+
/** 发送原始自动化协议,沿用连接的请求超时及清理契约。 */
|
|
3115
3688
|
async send(method, params = {}, options) {
|
|
3116
3689
|
return options ? await this.connection.send(method, params, options) : await this.connection.send(method, params);
|
|
3117
3690
|
}
|
|
@@ -3315,7 +3888,7 @@ var SmartappApp = class {
|
|
|
3315
3888
|
return [];
|
|
3316
3889
|
}
|
|
3317
3890
|
async close() {
|
|
3318
|
-
this.miniProgram?.disconnect();
|
|
3891
|
+
await this.miniProgram?.disconnect();
|
|
3319
3892
|
}
|
|
3320
3893
|
};
|
|
3321
3894
|
//#endregion
|
|
@@ -3393,8 +3966,8 @@ var SmartappDevice = class {
|
|
|
3393
3966
|
return false;
|
|
3394
3967
|
}
|
|
3395
3968
|
async close() {
|
|
3396
|
-
await this.driver.close?.();
|
|
3397
|
-
|
|
3969
|
+
const errors = (await Promise.allSettled([Promise.resolve().then(() => this.driver.close?.()), Promise.resolve().then(() => this.driver.miniProgram?.disconnect())])).filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
3970
|
+
if (errors.length) throw new AggregateError(errors, "Smartapp device cleanup failed");
|
|
3398
3971
|
}
|
|
3399
3972
|
};
|
|
3400
3973
|
//#endregion
|
|
@@ -3561,29 +4134,34 @@ const DEFAULT_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_AUTOMATOR_RUNTIME_PROVIDER";
|
|
|
3561
4134
|
const LEGACY_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_E2E_RUNTIME_PROVIDER";
|
|
3562
4135
|
let localhostListenPatched = false;
|
|
3563
4136
|
function retainPortLeaseUntilSessionClose(miniProgram, portLease) {
|
|
3564
|
-
let released
|
|
4137
|
+
let released;
|
|
3565
4138
|
const release = async () => {
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
4139
|
+
released ??= portLease.release().catch((error) => {
|
|
4140
|
+
released = void 0;
|
|
4141
|
+
throw error;
|
|
4142
|
+
});
|
|
4143
|
+
await released;
|
|
3569
4144
|
};
|
|
3570
4145
|
const target = miniProgram;
|
|
3571
4146
|
const rawClose = target.close;
|
|
3572
4147
|
const rawDisconnect = target.disconnect;
|
|
3573
4148
|
if (typeof rawClose !== "function" && typeof rawDisconnect !== "function") return false;
|
|
3574
|
-
if (typeof rawDisconnect === "function")
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
4149
|
+
if (typeof rawDisconnect === "function") {
|
|
4150
|
+
let disconnecting;
|
|
4151
|
+
target.disconnect = function disconnectWithPortLeaseRelease() {
|
|
4152
|
+
disconnecting ??= (async () => {
|
|
4153
|
+
await rawDisconnect.call(this);
|
|
4154
|
+
await release();
|
|
4155
|
+
})().catch((error) => {
|
|
4156
|
+
disconnecting = void 0;
|
|
4157
|
+
throw error;
|
|
4158
|
+
});
|
|
4159
|
+
return disconnecting;
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
3581
4162
|
if (typeof rawClose === "function") target.close = async function closeWithPortLeaseRelease() {
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
} finally {
|
|
3585
|
-
await release();
|
|
3586
|
-
}
|
|
4163
|
+
await rawClose.call(this);
|
|
4164
|
+
await release();
|
|
3587
4165
|
};
|
|
3588
4166
|
return true;
|
|
3589
4167
|
}
|
|
@@ -3625,7 +4203,7 @@ var Launcher = class {
|
|
|
3625
4203
|
} catch (error) {
|
|
3626
4204
|
scope.recordFailure(error);
|
|
3627
4205
|
scope.throwIfAborted();
|
|
3628
|
-
if (!isRecoverableOperationError(error) || attempt >= attempts) throw error;
|
|
4206
|
+
if (error instanceof AggregateError || !isRecoverableOperationError(error) || attempt >= attempts) throw error;
|
|
3629
4207
|
}
|
|
3630
4208
|
}
|
|
3631
4209
|
});
|
|
@@ -3639,10 +4217,35 @@ var Launcher = class {
|
|
|
3639
4217
|
});
|
|
3640
4218
|
const rawReleaseLease = portLease.release.bind(portLease);
|
|
3641
4219
|
let releasedLease;
|
|
3642
|
-
portLease.release = () => releasedLease ??= rawReleaseLease()
|
|
3643
|
-
|
|
4220
|
+
portLease.release = () => releasedLease ??= rawReleaseLease().catch((error) => {
|
|
4221
|
+
releasedLease = void 0;
|
|
4222
|
+
throw error;
|
|
4223
|
+
});
|
|
4224
|
+
const connectionTasks = [];
|
|
4225
|
+
const disconnectingConnections = /* @__PURE__ */ new WeakMap();
|
|
4226
|
+
const disconnectConnection = (program) => {
|
|
4227
|
+
const pending = disconnectingConnections.get(program);
|
|
4228
|
+
if (pending) return pending;
|
|
4229
|
+
const closing = Promise.resolve(program.disconnect?.()).catch((error) => {
|
|
4230
|
+
disconnectingConnections.delete(program);
|
|
4231
|
+
throw error;
|
|
4232
|
+
});
|
|
4233
|
+
disconnectingConnections.set(program, closing);
|
|
4234
|
+
return closing;
|
|
4235
|
+
};
|
|
4236
|
+
const releaseConnectionsAndLease = async () => {
|
|
4237
|
+
const results = await Promise.allSettled(connectionTasks);
|
|
4238
|
+
const errors = (await Promise.allSettled(results.map((result) => result.status === "fulfilled" ? disconnectConnection(result.value) : Promise.resolve()))).filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
4239
|
+
if (errors.length) throw new AggregateError(errors, "Automator connections did not finish closing; port lease retained");
|
|
4240
|
+
await portLease.release();
|
|
4241
|
+
};
|
|
4242
|
+
const disownLease = scope.own(releaseConnectionsAndLease, "port-lease");
|
|
3644
4243
|
let releasePortLeaseOnExit = true;
|
|
3645
4244
|
let releaseChild = async () => {};
|
|
4245
|
+
let diagnoseFailure = (error) => error;
|
|
4246
|
+
let failure;
|
|
4247
|
+
let failed = false;
|
|
4248
|
+
let result;
|
|
3646
4249
|
try {
|
|
3647
4250
|
const port = portLease.port;
|
|
3648
4251
|
if (!cliPath) throw new Error("Wechat web devTools not found, please specify cliPath option");
|
|
@@ -3656,6 +4259,7 @@ var Launcher = class {
|
|
|
3656
4259
|
let httpFallbackError = null;
|
|
3657
4260
|
let targetPort = port;
|
|
3658
4261
|
const cli = spawnWechatCli(cliPath, args, cwd, scope);
|
|
4262
|
+
diagnoseFailure = (error) => attachCliDiagnostics(error, cli, [ticket, account]);
|
|
3659
4263
|
releaseChild = cli.release;
|
|
3660
4264
|
let miniProgram = null;
|
|
3661
4265
|
let lastConnectError = null;
|
|
@@ -3687,40 +4291,49 @@ var Launcher = class {
|
|
|
3687
4291
|
}
|
|
3688
4292
|
const connectTimeout = resolveRemainingTimeout();
|
|
3689
4293
|
if (connectTimeout <= 0) return false;
|
|
3690
|
-
const candidate = await scope.step(() =>
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
4294
|
+
const candidate = await scope.step(() => {
|
|
4295
|
+
const connecting = this.connectTool({
|
|
4296
|
+
signal: scope.signal,
|
|
4297
|
+
timeout: Math.min(3e3, connectTimeout),
|
|
4298
|
+
wsEndpoint: `ws://127.0.0.1:${targetPort}`
|
|
4299
|
+
});
|
|
4300
|
+
connectionTasks.push(connecting);
|
|
4301
|
+
return connecting;
|
|
4302
|
+
}, {
|
|
3695
4303
|
stage: "websocket",
|
|
3696
|
-
disposeLate:
|
|
4304
|
+
disposeLate: disconnectConnection
|
|
3697
4305
|
});
|
|
3698
|
-
let releasedCandidate
|
|
4306
|
+
let releasedCandidate;
|
|
3699
4307
|
const releaseCandidate = () => {
|
|
3700
|
-
|
|
3701
|
-
releasedCandidate =
|
|
3702
|
-
|
|
3703
|
-
}
|
|
4308
|
+
return releasedCandidate ??= disconnectConnection(candidate).catch((error) => {
|
|
4309
|
+
releasedCandidate = void 0;
|
|
4310
|
+
throw error;
|
|
4311
|
+
});
|
|
3704
4312
|
};
|
|
3705
4313
|
const disownCandidate = scope.own(releaseCandidate, "websocket");
|
|
3706
4314
|
try {
|
|
3707
4315
|
const checkVersionTimeout = resolveRemainingTimeout();
|
|
3708
4316
|
if (checkVersionTimeout <= 0) {
|
|
3709
|
-
releaseCandidate
|
|
4317
|
+
await scope.step(releaseCandidate);
|
|
3710
4318
|
return false;
|
|
3711
4319
|
}
|
|
3712
4320
|
await scope.step(() => candidate.checkVersion(Math.min(VERSION_CHECK_TIMEOUT, checkVersionTimeout)), { stage: "version" });
|
|
3713
4321
|
if (typeof candidate.waitForAppReady === "function") {
|
|
3714
4322
|
const appReadyTimeout = resolveRemainingTimeout();
|
|
3715
4323
|
if (appReadyTimeout <= 0) {
|
|
3716
|
-
releaseCandidate
|
|
4324
|
+
await scope.step(releaseCandidate);
|
|
3717
4325
|
return false;
|
|
3718
4326
|
}
|
|
3719
4327
|
await scope.step(() => candidate.waitForAppReady(appReadyTimeout), { stage: "app-ready" });
|
|
3720
4328
|
}
|
|
3721
4329
|
} catch (error) {
|
|
4330
|
+
try {
|
|
4331
|
+
await scope.step(releaseCandidate);
|
|
4332
|
+
} catch (cleanupError) {
|
|
4333
|
+
if (cleanupError !== error) throw new AggregateError([error, cleanupError], "Automator validation and connection cleanup failed", { cause: error });
|
|
4334
|
+
throw error;
|
|
4335
|
+
}
|
|
3722
4336
|
disownCandidate();
|
|
3723
|
-
releaseCandidate();
|
|
3724
4337
|
lastConnectError = error;
|
|
3725
4338
|
if (isRecoverableOperationError(error)) return false;
|
|
3726
4339
|
throw error;
|
|
@@ -3731,7 +4344,7 @@ var Launcher = class {
|
|
|
3731
4344
|
lastConnectError = error;
|
|
3732
4345
|
scope.recordFailure(error);
|
|
3733
4346
|
scope.throwIfAborted();
|
|
3734
|
-
if (!isRecoverableOperationError(error)) throw error;
|
|
4347
|
+
if (error instanceof AggregateError || !isRecoverableOperationError(error)) throw error;
|
|
3735
4348
|
return false;
|
|
3736
4349
|
}
|
|
3737
4350
|
}, scope.remainingMs(), 1e3, scope.signal), { stage: "readiness" });
|
|
@@ -3749,15 +4362,30 @@ var Launcher = class {
|
|
|
3749
4362
|
wsEndpoint: `ws://127.0.0.1:${targetPort}`
|
|
3750
4363
|
});
|
|
3751
4364
|
releasePortLeaseOnExit = !retainPortLeaseUntilSessionClose(resolvedMiniProgram, portLease);
|
|
4365
|
+
if (!releasePortLeaseOnExit) disownLease();
|
|
4366
|
+
result = resolvedMiniProgram;
|
|
4367
|
+
} catch (error) {
|
|
4368
|
+
failed = true;
|
|
4369
|
+
failure = diagnoseFailure(error);
|
|
4370
|
+
}
|
|
4371
|
+
const cleanupErrors = [];
|
|
4372
|
+
try {
|
|
4373
|
+
await scope.step(releaseChild);
|
|
4374
|
+
} catch (error) {
|
|
4375
|
+
if (!failed || error !== failure) cleanupErrors.push(error);
|
|
4376
|
+
}
|
|
4377
|
+
if (releasePortLeaseOnExit) try {
|
|
4378
|
+
await scope.step(releaseConnectionsAndLease);
|
|
3752
4379
|
disownLease();
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
await releaseChild();
|
|
3756
|
-
if (releasePortLeaseOnExit) {
|
|
3757
|
-
disownLease();
|
|
3758
|
-
await portLease.release();
|
|
3759
|
-
}
|
|
4380
|
+
} catch (error) {
|
|
4381
|
+
if ((!failed || error !== failure) && !cleanupErrors.includes(error)) cleanupErrors.push(error);
|
|
3760
4382
|
}
|
|
4383
|
+
if (cleanupErrors.length) {
|
|
4384
|
+
if (failed) throw new AggregateError([failure, ...cleanupErrors], "Automator launch and resource cleanup failed", { cause: failure });
|
|
4385
|
+
throw new AggregateError(cleanupErrors, "Automator launch resource cleanup failed");
|
|
4386
|
+
}
|
|
4387
|
+
if (failed) throw failure;
|
|
4388
|
+
return result;
|
|
3761
4389
|
}
|
|
3762
4390
|
async connect(options) {
|
|
3763
4391
|
if (normalizePlatform(options.platform) === "swan") return await new SwanLauncher().connect(options);
|