@weapp-vite/miniprogram-automator 1.3.0 → 1.5.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/dist/index.mjs CHANGED
@@ -6,12 +6,13 @@ 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";
12
13
  import { decodeQrCodeFromBase64, renderTerminalQrCode } from "@weapp-vite/qr";
13
14
  import { Buffer } from "node:buffer";
14
- //#region src/internal/compat/async.ts
15
+ // src/internal/compat/async.ts
15
16
  /**
16
17
  * @file 异步控制工具。
17
18
  */
@@ -45,8 +46,7 @@ async function waitUntil(condition, timeout = 0, interval = 250, signal) {
45
46
  await sleep$1(Math.min(interval, remaining), signal);
46
47
  }
47
48
  }
48
- //#endregion
49
- //#region src/internal/compat/format.ts
49
+ // src/internal/compat/format.ts
50
50
  /**
51
51
  * @file 标识与格式化工具。
52
52
  */
@@ -62,8 +62,7 @@ function dateFormat(mask, value = /* @__PURE__ */ new Date()) {
62
62
  function uuid() {
63
63
  return randomUUID();
64
64
  }
65
- //#endregion
66
- //#region src/internal/compat/network.ts
65
+ // src/internal/compat/network.ts
67
66
  /**
68
67
  * @file 网络与环境工具。
69
68
  */
@@ -97,8 +96,7 @@ function isRelative(value) {
97
96
  return !absolutePathPattern.test(value);
98
97
  }
99
98
  const isWindows = process.platform === "win32";
100
- //#endregion
101
- //#region src/internal/compat/typeGuards.ts
99
+ // src/internal/compat/typeGuards.ts
102
100
  /**
103
101
  * @file 基础类型守卫工具。
104
102
  */
@@ -126,8 +124,7 @@ function isPlainObject(value) {
126
124
  const prototype = Object.getPrototypeOf(value);
127
125
  return prototype === null || prototype === Object.prototype;
128
126
  }
129
- //#endregion
130
- //#region src/internal/compat/object.ts
127
+ // src/internal/compat/object.ts
131
128
  /**
132
129
  * @file 对象与序列化工具。
133
130
  */
@@ -194,8 +191,7 @@ function isEmpty(value) {
194
191
  function stringify(value) {
195
192
  return stableSerialize(value);
196
193
  }
197
- //#endregion
198
- //#region src/internal/compat/strings.ts
194
+ // src/internal/compat/strings.ts
199
195
  /**
200
196
  * @file 字符串与版本处理工具。
201
197
  */
@@ -234,8 +230,303 @@ function cmpVersion(versionA, versionB) {
234
230
  }
235
231
  return 0;
236
232
  }
237
- //#endregion
238
- //#region src/pageRouteSnapshot.ts
233
+ // src/componentCaller/runtime.ts
234
+ /** 在同一个 Runtime 上下文持有真实 caller,不转用其他 ContextManager 的 token。 */
235
+ function createComponentCallerSession(expected, observerTimeout) {
236
+ const root = this;
237
+ const adapter = root.__glassEaselAdapter__;
238
+ const environment = adapter?.getEnv?.();
239
+ if (typeof environment?.getAdapterPage !== "function") throw new TypeError("Component caller backend selection is unavailable");
240
+ if (environment.getAdapterPage(expected.pageId)) return {
241
+ backend: "glass-easel",
242
+ dispose: () => ({
243
+ state: "disposed",
244
+ added: false,
245
+ removed: false
246
+ })
247
+ };
248
+ const bridge = root.WeixinJSBridge;
249
+ if (!bridge || typeof bridge.addSubscribeListener !== "function" || typeof bridge.removeSubscribeListener !== "function" || !adapter || typeof adapter.getVirtualDOM !== "function") throw new Error("Exact component caller bridge is unavailable");
250
+ const add = bridge.addSubscribeListener;
251
+ const remove = bridge.removeSubscribeListener;
252
+ const subscribe = bridge.subscribe;
253
+ const virtualDOM = adapter.getVirtualDOM();
254
+ const exparser = virtualDOM?.getExparser?.();
255
+ 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");
256
+ let state = "created";
257
+ let caller;
258
+ let node;
259
+ let virtualId;
260
+ let added = false;
261
+ let removed = false;
262
+ let totalInvocations = 0;
263
+ let matchingInvocations = 0;
264
+ let timer;
265
+ const errors = [];
266
+ const eventName = "exparserComponentInvoker";
267
+ function identitiesMatch() {
268
+ return root.WeixinJSBridge === bridge && root.__glassEaselAdapter__ === adapter && bridge.subscribe === subscribe && bridge.addSubscribeListener === add && bridge.removeSubscribeListener === remove && adapter.getVirtualDOM() === virtualDOM && virtualDOM.getExparser() === exparser;
269
+ }
270
+ function snapshot() {
271
+ return {
272
+ state,
273
+ added,
274
+ removed,
275
+ totalInvocations,
276
+ matchingInvocations,
277
+ errors: [...errors],
278
+ identitiesMatch: identitiesMatch()
279
+ };
280
+ }
281
+ function listener(message, sender) {
282
+ totalInvocations += 1;
283
+ if (state !== "armed" && state !== "resolved") return;
284
+ if (message?.method !== "resolveComponentCaller") return;
285
+ matchingInvocations += 1;
286
+ try {
287
+ const args = message.params;
288
+ 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");
289
+ const params = args[0];
290
+ const id = params.nodeId;
291
+ 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");
292
+ if (!identitiesMatch()) throw new Error("Component caller bridge identity changed");
293
+ const resolvedNode = virtualDOM.getNodeById(id, expected.pageId);
294
+ if (!resolvedNode || virtualDOM.getNodeId(resolvedNode, expected.pageId) !== id || virtualDOM.getPageId(resolvedNode) !== expected.pageId) throw new Error("Component caller virtual node identity disagrees");
295
+ const resolvedCaller = exparser.Element.getMethodCaller(resolvedNode);
296
+ if (!resolvedCaller || !["object", "function"].includes(typeof resolvedCaller)) throw new Error("Component caller is unavailable");
297
+ virtualId = id;
298
+ node = resolvedNode;
299
+ caller = resolvedCaller;
300
+ state = "resolved";
301
+ } catch (error) {
302
+ errors.push(error instanceof Error ? error.message : String(error));
303
+ state = "failed";
304
+ }
305
+ }
306
+ function removeListener() {
307
+ if (added && !removed) {
308
+ remove.call(bridge, eventName, listener);
309
+ removed = true;
310
+ }
311
+ if (timer !== void 0) {
312
+ clearTimeout(timer);
313
+ timer = void 0;
314
+ }
315
+ }
316
+ function dispose() {
317
+ try {
318
+ removeListener();
319
+ state = "disposed";
320
+ } finally {
321
+ caller = void 0;
322
+ node = void 0;
323
+ virtualId = void 0;
324
+ }
325
+ return {
326
+ state,
327
+ added,
328
+ removed,
329
+ totalInvocations,
330
+ matchingInvocations,
331
+ errors: [...errors]
332
+ };
333
+ }
334
+ return {
335
+ backend: "exparser",
336
+ arm() {
337
+ if (state !== "created") throw new Error("Component caller observer cannot be armed twice");
338
+ state = "armed";
339
+ timer = setTimeout(() => {
340
+ try {
341
+ dispose();
342
+ } catch (error) {
343
+ errors.push(error instanceof Error ? error.message : String(error));
344
+ state = "failed";
345
+ }
346
+ }, observerTimeout);
347
+ added = true;
348
+ add.call(bridge, eventName, listener);
349
+ return snapshot();
350
+ },
351
+ snapshot,
352
+ disarm() {
353
+ removeListener();
354
+ if (state !== "resolved" || matchingInvocations !== 1 || errors.length || !identitiesMatch()) throw new Error(`Exact component caller resolution failed: ${JSON.stringify(snapshot())}`);
355
+ state = "ready";
356
+ return snapshot();
357
+ },
358
+ invoke(method, args) {
359
+ if (state !== "ready" || !removed || errors.length || !identitiesMatch()) throw new Error("Component caller is not ready for a single invocation");
360
+ const pages = typeof getCurrentPages === "function" ? getCurrentPages() : [];
361
+ 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");
362
+ state = "dispatched";
363
+ return caller[method](...args);
364
+ },
365
+ dispose
366
+ };
367
+ }
368
+ // src/componentCaller/index.ts
369
+ const queues = /* @__PURE__ */ new WeakMap();
370
+ const blocked = /* @__PURE__ */ new WeakMap();
371
+ const owners = /* @__PURE__ */ new WeakMap();
372
+ const TIMEOUT = 2500;
373
+ /** 关闭连接前确认全部自有 listener 已移除;失败时保留 token 与可重试的所有权。 */
374
+ function cleanupComponentCallers(connection) {
375
+ const pending = owners.get(connection);
376
+ if (!pending?.size) return;
377
+ return (async () => {
378
+ const errors = [];
379
+ for (const owner of [...pending]) try {
380
+ await owner.cleanup();
381
+ } catch (error) {
382
+ errors.push(error);
383
+ }
384
+ if (errors.length) throw new AggregateError(errors, "Component caller cleanup remains incomplete", { cause: errors[0] });
385
+ blocked.delete(connection);
386
+ })();
387
+ }
388
+ function result(response) {
389
+ if (response.exceptionDetails) throw new Error(response.exceptionDetails.text ?? "Component caller Runtime exception", { cause: response.exceptionDetails });
390
+ if (!response.result) throw new Error("Component caller Runtime result is missing");
391
+ return response.result;
392
+ }
393
+ function serializeArguments(args) {
394
+ const payload = JSON.parse(stringify({ params: { args } }));
395
+ return JSON.stringify(payload.params.args);
396
+ }
397
+ /** 保持原 SDK 的 returnByValue 解包,不等待业务返回的 Promise。 */
398
+ async function unwrap(connection, remote) {
399
+ if (remote.subtype === "null") return null;
400
+ if (remote.type === "undefined") return;
401
+ if (remote.value !== void 0) return remote.value;
402
+ if (remote.unserializableValue) {
403
+ const value = remote.unserializableValue;
404
+ if (/^-?\d+n$/.test(value)) return BigInt(value.slice(0, -1));
405
+ if ([
406
+ "NaN",
407
+ "Infinity",
408
+ "-Infinity",
409
+ "-0"
410
+ ].includes(value)) return Number(value);
411
+ throw new Error(`Unsupported component return value: ${value}`);
412
+ }
413
+ if (remote.objectId) try {
414
+ const converted = await connection.send("App.CDPCommand", {
415
+ domain: "Runtime",
416
+ method: "callFunctionOn",
417
+ params: {
418
+ objectId: remote.objectId,
419
+ functionDeclaration: "function () { try { return JSON.stringify(this) } catch { return String(this) } }",
420
+ returnByValue: true
421
+ }
422
+ }, { timeout: TIMEOUT });
423
+ if (converted.result?.value !== void 0) try {
424
+ return JSON.parse(converted.result.value);
425
+ } catch {
426
+ return converted.result.value;
427
+ }
428
+ } catch {}
429
+ return remote.description ?? `[${remote.type}]`;
430
+ }
431
+ async function invoke(connection, target, method, args, native) {
432
+ let token;
433
+ let owner;
434
+ const errors = [];
435
+ let cleanupFailed = false;
436
+ let nativeBackend = false;
437
+ let value;
438
+ const cdp = async (domain, command, params) => {
439
+ return await connection.send("App.CDPCommand", {
440
+ domain,
441
+ method: command,
442
+ params
443
+ }, { timeout: TIMEOUT });
444
+ };
445
+ const wrapper = async (body) => result(await cdp("Runtime", "callFunctionOn", {
446
+ objectId: token,
447
+ functionDeclaration: `function () { return ${body} }`,
448
+ arguments: [],
449
+ returnByValue: true
450
+ }));
451
+ try {
452
+ if (!Number.isSafeInteger(target.nodeId) || target.nodeId < 0 || !Number.isSafeInteger(target.pageId)) throw new Error("Invalid component caller native target");
453
+ if ((await connection.send("App.getCurrentPage", {}, { timeout: TIMEOUT })).pageId !== target.pageId) throw new Error("Component caller target is not on the current page");
454
+ const created = result(await cdp("Runtime", "evaluate", {
455
+ expression: `(${createComponentCallerSession.toString()})(${JSON.stringify(target)}, 10000)`,
456
+ returnByValue: false
457
+ }));
458
+ if (!created.objectId) throw new Error("Component caller session has no owned Runtime token");
459
+ token = created.objectId;
460
+ let cleaning;
461
+ let disposed = false;
462
+ let released = false;
463
+ owner = { cleanup() {
464
+ cleaning ??= (async () => {
465
+ if (released) return;
466
+ if (!disposed) {
467
+ const snapshot = (await wrapper("this.dispose()")).value;
468
+ if (snapshot?.state !== "disposed" || snapshot.added && !snapshot.removed) throw new Error("Component caller listener removal was not confirmed");
469
+ disposed = true;
470
+ }
471
+ await cdp("Runtime", "releaseObject", { objectId: token });
472
+ released = true;
473
+ owners.get(connection)?.delete(owner);
474
+ })().catch((error) => {
475
+ cleaning = void 0;
476
+ throw error;
477
+ });
478
+ return cleaning;
479
+ } };
480
+ let pending = owners.get(connection);
481
+ if (!pending) {
482
+ pending = /* @__PURE__ */ new Set();
483
+ owners.set(connection, pending);
484
+ }
485
+ pending.add(owner);
486
+ const backend = (await wrapper("this.backend")).value;
487
+ if (backend === "glass-easel") nativeBackend = true;
488
+ else if (backend === "exparser") {
489
+ const argumentSource = serializeArguments(args);
490
+ await wrapper("this.arm()");
491
+ await cdp("WxComponent", "resolveComponentCaller", { nodeId: target.nodeId });
492
+ await wrapper("this.disarm()");
493
+ value = await unwrap(connection, await wrapper(`this.invoke(${JSON.stringify(method)}, ${argumentSource})`));
494
+ } else throw new Error("Unknown component caller backend");
495
+ } catch (error) {
496
+ errors.push(error);
497
+ } finally {
498
+ if (owner) try {
499
+ await owner.cleanup();
500
+ } catch (error) {
501
+ errors.push(error);
502
+ cleanupFailed = true;
503
+ }
504
+ }
505
+ if (cleanupFailed) blocked.set(connection, new Error("Component caller cleanup failed; reconnect before invoking another component", { cause: errors[0] }));
506
+ if (errors.length === 1) throw errors[0];
507
+ if (errors.length) throw new AggregateError(errors, "Component caller invocation and cleanup failed", { cause: errors[0] });
508
+ if (nativeBackend) {
509
+ if (!native) throw new Error("Native component backend requires the original protocol");
510
+ return await native();
511
+ }
512
+ return value;
513
+ }
514
+ /** 同一连接串行观察 resolver,避免多个组件误认彼此的桥接消息。 */
515
+ async function invokeComponentMethod(connection, target, method, args, native) {
516
+ const pending = (queues.get(connection) ?? Promise.resolve()).then(async () => {
517
+ const failure = blocked.get(connection);
518
+ if (failure) throw failure;
519
+ return await invoke(connection, target, method, args, native);
520
+ });
521
+ const settled = pending.then(() => {}, () => {});
522
+ queues.set(connection, settled);
523
+ try {
524
+ return await pending;
525
+ } finally {
526
+ if (queues.get(connection) === settled) queues.delete(connection);
527
+ }
528
+ }
529
+ // src/pageRouteSnapshot.ts
239
530
  const ROUTE_ELEMENT_SNAPSHOT_TIMEOUT = 5e3;
240
531
  const ROUTE_ELEMENT_SNAPSHOT_EMPTY_RETRIES = 2;
241
532
  const ROUTE_ELEMENT_SNAPSHOT_RETRY_DELAY = 220;
@@ -452,8 +743,15 @@ async function readRouteElementSnapshot(connection, route, query, selector, inde
452
743
  }
453
744
  return null;
454
745
  }
455
- //#endregion
456
- //#region src/Element.ts
746
+ // src/Element.ts
747
+ function normalizeTouchOptions(options) {
748
+ const { changeTouches, ...payload } = options;
749
+ const changedTouches = options.changedTouches ?? changeTouches;
750
+ return changedTouches === void 0 ? payload : {
751
+ ...payload,
752
+ changedTouches
753
+ };
754
+ }
457
755
  const ELEMENT_QUERY_TIMEOUT = 2500;
458
756
  const ELEMENT_WXML_TIMEOUT = 8e3;
459
757
  /** Element 的实现。 */
@@ -559,16 +857,26 @@ var Element = class Element {
559
857
  await this.send("Element.triggerEvent", payload);
560
858
  }
561
859
  async touchstart(options = {}) {
562
- await this.send("Element.touchstart", options);
860
+ await this.send("Element.touchstart", normalizeTouchOptions(options));
563
861
  }
564
862
  async touchmove(options = {}) {
565
- await this.send("Element.touchmove", options);
863
+ await this.send("Element.touchmove", normalizeTouchOptions(options));
566
864
  }
567
865
  async touchend(options = {}) {
568
- await this.send("Element.touchend", options);
866
+ await this.send("Element.touchend", normalizeTouchOptions(options));
569
867
  }
570
868
  async dispatchEvent(options) {
571
- await this.send("Element.dispatchEvent", options);
869
+ const payload = normalizeTouchOptions(options);
870
+ if (!payload.$ && !payload.detail && (payload.eventName === "touchstart" || payload.eventName === "touchmove" || payload.eventName === "touchend") && (payload.touches !== void 0 || payload.changedTouches !== void 0)) {
871
+ const { eventName, ...touchOptions } = payload;
872
+ await this.send(`Element.${eventName}`, touchOptions);
873
+ return;
874
+ }
875
+ const { detail, ...event } = payload;
876
+ await this.send("Element.dispatchEvent", detail === void 0 ? event : {
877
+ ...event,
878
+ eventData: detail
879
+ });
572
880
  }
573
881
  async _property(name) {
574
882
  return await this.getter(name, "getProperties", "properties");
@@ -750,6 +1058,18 @@ var CustomElement = class extends Element {
750
1058
  return (await this.send("Element.getData", payload)).data;
751
1059
  }
752
1060
  async callMethod(method, ...args) {
1061
+ if (this.connection.prefersComponentCallerBridge) {
1062
+ const nodeId = this.nodeId === null ? NaN : Number(this.nodeId);
1063
+ return await invokeComponentMethod(this.connection, {
1064
+ nodeId,
1065
+ pageId: this.pageId
1066
+ }, method, args, async () => {
1067
+ return (await this.send("Element.callMethod", {
1068
+ method,
1069
+ args
1070
+ })).result;
1071
+ });
1072
+ }
753
1073
  return (await this.send("Element.callMethod", {
754
1074
  method,
755
1075
  args
@@ -823,11 +1143,13 @@ var ContextElement = class extends Element {
823
1143
  })).result;
824
1144
  }
825
1145
  };
826
- //#endregion
827
- //#region src/Transport.ts
1146
+ // src/Transport.ts
828
1147
  /** Transport 的实现。 */
829
1148
  var Transport = class extends EventEmitter {
830
1149
  ws;
1150
+ closed = false;
1151
+ terminating = false;
1152
+ closing;
831
1153
  constructor(ws) {
832
1154
  super();
833
1155
  this.ws = ws;
@@ -835,6 +1157,7 @@ var Transport = class extends EventEmitter {
835
1157
  this.emit("message", typeof data === "string" ? data : data.toString());
836
1158
  });
837
1159
  ws.on("close", () => {
1160
+ this.closed = true;
838
1161
  this.emit("close");
839
1162
  });
840
1163
  }
@@ -842,11 +1165,39 @@ var Transport = class extends EventEmitter {
842
1165
  this.ws.send(message);
843
1166
  }
844
1167
  close() {
845
- this.ws.terminate();
1168
+ if (this.closing) return this.closing;
1169
+ if (this.closed || this.ws.readyState === 3) return this.closing = Promise.resolve();
1170
+ this.closing = new Promise((resolve, reject) => {
1171
+ let timer;
1172
+ const finish = () => {
1173
+ clearTimeout(timer);
1174
+ this.off("close", finish);
1175
+ resolve();
1176
+ };
1177
+ timer = setTimeout(() => {
1178
+ this.off("close", finish);
1179
+ reject(Object.assign(/* @__PURE__ */ new Error("DevTools websocket did not close within 2000ms"), { code: "DEVTOOLS_DISCONNECT_TIMEOUT" }));
1180
+ }, 2e3);
1181
+ this.once("close", finish);
1182
+ try {
1183
+ if (!this.terminating) {
1184
+ this.terminating = true;
1185
+ this.ws.terminate();
1186
+ }
1187
+ } catch (error) {
1188
+ this.terminating = false;
1189
+ clearTimeout(timer);
1190
+ this.off("close", finish);
1191
+ reject(error);
1192
+ }
1193
+ }).catch((error) => {
1194
+ this.closing = void 0;
1195
+ throw error;
1196
+ });
1197
+ return this.closing;
846
1198
  }
847
1199
  };
848
- //#endregion
849
- //#region src/Connection.ts
1200
+ // src/Connection.ts
850
1201
  /**
851
1202
  * @file 自动化协议连接实现。
852
1203
  */
@@ -864,13 +1215,17 @@ const APP_SERVICE_PAGE_METHOD_VERSIONS = /* @__PURE__ */ new Set([
864
1215
  "2.02.2610082",
865
1216
  "2.02.2610092"
866
1217
  ]);
1218
+ /** 已确认 Exparser caller token 与 Runtime 对象表错绑的 IDE 与 SDK 精确组合。 */
1219
+ const COMPONENT_CALLER_BRIDGE_COMBINATIONS = /* @__PURE__ */ new Map([["2.02.2608080", /* @__PURE__ */ new Set(["3.17.5"])], ["2.02.2609231", /* @__PURE__ */ new Set(["3.17.5", "3.17.6"])]]);
867
1220
  /** Connection 的实现。 */
868
1221
  var Connection = class Connection extends EventEmitter {
869
1222
  transport;
870
1223
  disposed = false;
1224
+ disposing;
871
1225
  callbacks = /* @__PURE__ */ new Map();
872
1226
  useAppServicePageProtocol = false;
873
1227
  useAppServicePageMethod = false;
1228
+ useComponentCallerBridge = false;
874
1229
  constructor(transport) {
875
1230
  super();
876
1231
  this.transport = transport;
@@ -910,18 +1265,51 @@ var Connection = class Connection extends EventEmitter {
910
1265
  });
911
1266
  }
912
1267
  dispose() {
913
- if (this.disposed) return;
914
- this.disposed = true;
915
- this.onClose();
1268
+ if (this.disposing) return this.disposing;
1269
+ let resolve;
1270
+ let reject;
1271
+ this.disposing = new Promise((done, fail) => {
1272
+ resolve = done;
1273
+ reject = fail;
1274
+ }).catch((error) => {
1275
+ this.disposing = void 0;
1276
+ throw error;
1277
+ });
1278
+ const cleanup = cleanupComponentCallers(this);
1279
+ if (cleanup) cleanup.then(() => this.closeTransport(resolve, reject), reject);
1280
+ else this.closeTransport(resolve, reject);
1281
+ return this.disposing;
1282
+ }
1283
+ closeTransport(resolve, reject) {
1284
+ const errors = [];
1285
+ try {
1286
+ this.onClose();
1287
+ } catch (error) {
1288
+ errors.push(error);
1289
+ }
916
1290
  this.transport.off("message", this.onMessage);
917
1291
  this.transport.off("close", this.onClose);
918
- this.transport.close();
1292
+ const finish = () => {
1293
+ if (errors.length === 1) reject(errors[0]);
1294
+ else if (errors.length) reject(new AggregateError(errors, "Connection close notification and transport cleanup failed", { cause: errors[0] }));
1295
+ else resolve();
1296
+ };
1297
+ try {
1298
+ Promise.resolve(this.transport.close()).then(finish, (error) => {
1299
+ errors.push(error);
1300
+ finish();
1301
+ });
1302
+ } catch (error) {
1303
+ errors.push(error);
1304
+ finish();
1305
+ }
919
1306
  }
920
1307
  /** 根据 DevTools 版本选择稳定的 Page 协议实现。 */
921
1308
  configureToolInfo(info) {
922
1309
  const version = String(info.version ?? "");
923
1310
  this.useAppServicePageProtocol = APP_SERVICE_PAGE_PROTOCOL_VERSIONS.has(version);
924
1311
  this.useAppServicePageMethod = this.useAppServicePageProtocol || APP_SERVICE_PAGE_METHOD_VERSIONS.has(version);
1312
+ this.useComponentCallerBridge = COMPONENT_CALLER_BRIDGE_COMBINATIONS.get(version)?.has(info.SDKVersion ?? "") ?? false;
925
1313
  }
926
1314
  get prefersAppServicePageProtocol() {
927
1315
  return this.useAppServicePageProtocol;
@@ -930,6 +1318,10 @@ var Connection = class Connection extends EventEmitter {
930
1318
  get prefersAppServicePageMethod() {
931
1319
  return this.useAppServicePageMethod;
932
1320
  }
1321
+ /** 仅为实测存在跨上下文 token 错绑的 IDE 与基础库启用真实 caller 桥接。 */
1322
+ get prefersComponentCallerBridge() {
1323
+ return this.useComponentCallerBridge;
1324
+ }
933
1325
  onMessage = (message) => {
934
1326
  debugProtocol(`${dateFormat("yyyy-mm-dd HH:MM:ss:l")} ◀ RECV ${message}`);
935
1327
  const { id, method, error, result, params } = JSON.parse(message);
@@ -942,17 +1334,21 @@ var Connection = class Connection extends EventEmitter {
942
1334
  this.callbacks.delete(id);
943
1335
  clearTimeout(callback.timeout);
944
1336
  if (error) {
945
- callback.reject(new Error(error.message || closeErrTip));
1337
+ const message = typeof error.message === "string" ? error.message : error.message === void 0 ? closeErrTip : JSON.stringify(error.message);
1338
+ callback.reject(new Error(message, { cause: error }));
946
1339
  return;
947
1340
  }
948
1341
  callback.resolve(result);
949
1342
  };
950
1343
  onClose = () => {
1344
+ if (this.disposed) return;
1345
+ this.disposed = true;
951
1346
  for (const callback of this.callbacks.values()) {
952
1347
  clearTimeout(callback.timeout);
953
1348
  callback.reject(/* @__PURE__ */ new Error(closeErrTip));
954
1349
  }
955
1350
  this.callbacks.clear();
1351
+ this.emit("close");
956
1352
  };
957
1353
  static create(url, timeout = CONNECT_TIMEOUT, signal) {
958
1354
  signal?.throwIfAborted();
@@ -997,13 +1393,28 @@ var Connection = class Connection extends EventEmitter {
997
1393
  });
998
1394
  }
999
1395
  };
1000
- //#endregion
1001
- //#region src/headless.ts
1396
+ // src/headless.ts
1002
1397
  async function launchHeadlessAutomator(options) {
1003
- return await (await import("./launch-anJvfi-6.mjs")).launch({ projectPath: options.projectPath });
1398
+ return await (await import("./launch-DuW7r68J.mjs")).launch({ projectPath: options.projectPath });
1399
+ }
1400
+ // src/launcher/diagnostics.ts
1401
+ /** 将本次 CLI 的有界输出附到原始错误,避免泄露登录票据及账号参数。 */
1402
+ function attachCliDiagnostics(error, state, secrets = []) {
1403
+ const original = error instanceof Error ? error : new Error("Failed to launch WeChat DevTools", { cause: error });
1404
+ if (original.cliOutput !== void 0) return original;
1405
+ let output = stripVTControlCharacters(state.output).trim();
1406
+ for (const secret of secrets.filter(Boolean).sort((a, b) => b.length - a.length)) output = output.split(secret).join("[redacted]");
1407
+ output = output.replace(/(authorization["']?\s*[:=]\s*["']?)(?:Bearer|Basic)\s+[^\s"',;}]+/gi, "$1[redacted]");
1408
+ output = output.replace(/((?:--)?(?:auto-account|account|ticket|access[_-]?token|refresh[_-]?token|token|authorization)["']?\s*(?:[:=]\s*|\s))(["']?)([^\s"',;}]+)\2/gi, "$1[redacted]");
1409
+ if (!output) return original;
1410
+ const truncated = state.outputTruncated || output.length > 32768;
1411
+ output = output.slice(-32768);
1412
+ const target = Object.isExtensible(original) ? original : new Error(original.message, { cause: original });
1413
+ target.cliOutput = output;
1414
+ target.message += `\ncli output${truncated ? " (truncated; last 32768 characters)" : ""}:\n${output}`;
1415
+ return target;
1004
1416
  }
1005
- //#endregion
1006
- //#region src/launcher/portLease.ts
1417
+ // src/launcher/portLease.ts
1007
1418
  /**
1008
1419
  * @file DevTools 自动化端口租约。
1009
1420
  */
@@ -1026,7 +1437,7 @@ function isProcessAlive(pid) {
1026
1437
  }
1027
1438
  }
1028
1439
  async function removePortLock(leasePath) {
1029
- await fs.rm(leasePath, { force: true }).catch(() => {});
1440
+ await fs.rm(leasePath, { force: true });
1030
1441
  }
1031
1442
  async function removeStalePortLock(leasePath) {
1032
1443
  try {
@@ -1058,14 +1469,18 @@ async function createPortLock(port) {
1058
1469
  return leasePath;
1059
1470
  }
1060
1471
  function createLease(port, leasePath) {
1061
- let released = false;
1472
+ let releasing;
1062
1473
  return {
1063
1474
  path: leasePath,
1064
1475
  port,
1065
- release: async () => {
1066
- if (released) return;
1067
- released = true;
1068
- if (leasePath) await removePortLock(leasePath);
1476
+ release: () => {
1477
+ releasing ??= (async () => {
1478
+ if (leasePath) await removePortLock(leasePath);
1479
+ })().catch((error) => {
1480
+ releasing = void 0;
1481
+ throw error;
1482
+ });
1483
+ return releasing;
1069
1484
  }
1070
1485
  };
1071
1486
  }
@@ -1097,8 +1512,7 @@ async function acquireAutomatorPortLease(preferredPort) {
1097
1512
  }
1098
1513
  return createLease(await getPort(0, "127.0.0.1"));
1099
1514
  }
1100
- //#endregion
1101
- //#region src/launcher/wechatCliFallback.ts
1515
+ // src/launcher/wechatCliFallback.ts
1102
1516
  const WECHAT_DEVTOOLS_SERVICE_PORT_RE = /listening\s+on\s+http:\/\/127\.0\.0\.1:(\d{2,5})/i;
1103
1517
  function extractWechatDevtoolsServicePort(output) {
1104
1518
  const port = Number(output.match(WECHAT_DEVTOOLS_SERVICE_PORT_RE)?.[1]);
@@ -1157,8 +1571,7 @@ function resolveWechatDevtoolsBootstrapArgs(args) {
1157
1571
  bootstrapArgs.push("islogin");
1158
1572
  return bootstrapArgs;
1159
1573
  }
1160
- //#endregion
1161
- //#region src/launcher/process.ts
1574
+ // src/launcher/process.ts
1162
1575
  const WINDOWS_BATCH_CLI_RE = /\.(?:bat|cmd)$/i;
1163
1576
  function shouldUseWindowsCommandShell(cliPath) {
1164
1577
  return isWindows && WINDOWS_BATCH_CLI_RE.test(cliPath);
@@ -1185,7 +1598,8 @@ function spawnWechatCli(cliPath, args, cwd, scope) {
1185
1598
  error: null,
1186
1599
  success: false,
1187
1600
  exited: false,
1188
- output: ""
1601
+ output: "",
1602
+ outputTruncated: false
1189
1603
  };
1190
1604
  const bootstrapArgs = resolveWechatDevtoolsBootstrapArgs(args);
1191
1605
  const target = shouldUseWindowsCommandShell(cliPath) ? resolveWindowsBatchSpawn(cliPath, bootstrapArgs) : {
@@ -1221,7 +1635,9 @@ function spawnWechatCli(cliPath, args, cwd, scope) {
1221
1635
  state.error = error;
1222
1636
  });
1223
1637
  const output = (chunk) => {
1224
- state.output = (state.output + String(chunk)).slice(-32768);
1638
+ const combined = state.output + String(chunk);
1639
+ state.outputTruncated ||= combined.length > 32768;
1640
+ state.output = combined.slice(-32768);
1225
1641
  };
1226
1642
  child.stdout?.on("data", output);
1227
1643
  child.stderr?.on("data", output);
@@ -1234,11 +1650,9 @@ function spawnWechatCli(cliPath, args, cwd, scope) {
1234
1650
  child.unref();
1235
1651
  return Object.assign(state, { release });
1236
1652
  }
1237
- //#endregion
1238
- //#region package.json
1239
- var version = "1.3.0";
1240
- //#endregion
1241
- //#region src/appServiceHeap.ts
1653
+ // package.json
1654
+ var version = "1.5.0";
1655
+ // src/appServiceHeap.ts
1242
1656
  function isRecord$1(value) {
1243
1657
  return value !== null && typeof value === "object" && !Array.isArray(value);
1244
1658
  }
@@ -1274,8 +1688,112 @@ async function readAppServiceHeapUsage(send, options = {}) {
1274
1688
  totalSize: response.totalSize
1275
1689
  };
1276
1690
  }
1277
- //#endregion
1278
- //#region src/Native.ts
1691
+ // src/cdp/index.ts
1692
+ /** 单个连接共享 CDP 启用状态与订阅;不创建额外 WebSocket。 */
1693
+ var CDPController = class {
1694
+ connection;
1695
+ send;
1696
+ enabling;
1697
+ enabled = false;
1698
+ stopped = false;
1699
+ subscriptions = /* @__PURE__ */ new Set();
1700
+ session;
1701
+ constructor(connection, send) {
1702
+ this.connection = connection;
1703
+ this.send = send;
1704
+ connection.on("close", this.stop);
1705
+ this.session = {
1706
+ enable: (options) => this.enable(options),
1707
+ sendCommand: (domain, method, params = {}, options) => this.request("App.CDPCommand", {
1708
+ domain,
1709
+ method,
1710
+ params
1711
+ }, options),
1712
+ listProtocol: (options) => this.request("App.CDPListProtocol", {}, options),
1713
+ onEvent: (listener) => this.onEvent(listener)
1714
+ };
1715
+ }
1716
+ enable(options) {
1717
+ if (this.stopped) return Promise.reject(/* @__PURE__ */ new Error("CDP connection closed"));
1718
+ try {
1719
+ this.validateTimeout(options);
1720
+ } catch (error) {
1721
+ return Promise.reject(error);
1722
+ }
1723
+ if (!this.enabling) {
1724
+ let pending;
1725
+ try {
1726
+ pending = this.send("App.CDPEnable", {}, options);
1727
+ } catch (error) {
1728
+ pending = Promise.reject(error);
1729
+ }
1730
+ this.enabling = pending.then((result) => {
1731
+ this.enabled = true;
1732
+ return result;
1733
+ }, (error) => {
1734
+ this.enabling = void 0;
1735
+ throw error;
1736
+ });
1737
+ }
1738
+ if (options?.timeout === void 0) return this.enabling;
1739
+ return new Promise((resolve, reject) => {
1740
+ const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out enabling CDP within ${options.timeout}ms`)), options.timeout);
1741
+ this.enabling.then(resolve, reject).finally(() => clearTimeout(timer));
1742
+ });
1743
+ }
1744
+ /** 日志显式刷新时重新注册事件通道,兼容 AppService 重载。 */
1745
+ refresh(options) {
1746
+ if (this.enabled) {
1747
+ this.enabled = false;
1748
+ this.enabling = void 0;
1749
+ }
1750
+ return this.enable(options);
1751
+ }
1752
+ stop = () => {
1753
+ this.stopped = true;
1754
+ this.enabled = false;
1755
+ this.enabling = void 0;
1756
+ this.connection.off("close", this.stop);
1757
+ for (const unsubscribe of this.subscriptions) unsubscribe();
1758
+ };
1759
+ async request(method, params, options) {
1760
+ this.validateTimeout(options);
1761
+ const startedAt = Date.now();
1762
+ await this.enable(options);
1763
+ if (this.stopped) throw new Error("CDP connection closed");
1764
+ if (options?.timeout === void 0) return await this.send(method, params);
1765
+ const timeout = options.timeout - (Date.now() - startedAt);
1766
+ if (timeout <= 0) throw new Error(`Timed out executing ${method} within ${options.timeout}ms`);
1767
+ return await this.send(method, params, { timeout });
1768
+ }
1769
+ validateTimeout(options) {
1770
+ if (options?.timeout !== void 0 && (!Number.isFinite(options.timeout) || options.timeout <= 0)) throw new RangeError("CDP timeout must be finite and positive");
1771
+ }
1772
+ onEvent(listener) {
1773
+ if (this.stopped) throw new Error("CDP connection closed");
1774
+ const receive = (payload) => {
1775
+ listener(payload.domain, payload.event, payload.params);
1776
+ };
1777
+ const unsubscribe = () => {
1778
+ this.connection.off("App.CDPEvent", receive);
1779
+ this.subscriptions.delete(unsubscribe);
1780
+ };
1781
+ this.subscriptions.add(unsubscribe);
1782
+ this.connection.on("App.CDPEvent", receive);
1783
+ return unsubscribe;
1784
+ }
1785
+ };
1786
+ const controllers = /* @__PURE__ */ new WeakMap();
1787
+ /** 一个协议连接仅拥有一份 CDP 状态,包括被不同 MiniProgram 包装的连接。 */
1788
+ function getCDPController(connection, send) {
1789
+ let controller = controllers.get(connection);
1790
+ if (!controller) {
1791
+ controller = new CDPController(connection, send);
1792
+ controllers.set(connection, controller);
1793
+ }
1794
+ return controller;
1795
+ }
1796
+ // src/Native.ts
1279
1797
  /** Native 的实现。 */
1280
1798
  var Native = class extends EventEmitter {
1281
1799
  connection;
@@ -1323,8 +1841,7 @@ var Native = class extends EventEmitter {
1323
1841
  });
1324
1842
  }
1325
1843
  };
1326
- //#endregion
1327
- //#region src/navigation/query.ts
1844
+ // src/navigation/query.ts
1328
1845
  /** 仅核对请求显式携带的参数,保留宿主附加参数及其原始编码形式。 */
1329
1846
  function matchesRouteQuery(actual, expected) {
1330
1847
  const expectedKeys = Object.keys(expected);
@@ -1342,8 +1859,7 @@ function matchesRouteQuery(actual, expected) {
1342
1859
  }
1343
1860
  });
1344
1861
  }
1345
- //#endregion
1346
- //#region src/pageRouteFallback.ts
1862
+ // src/pageRouteFallback.ts
1347
1863
  function normalizeFallbackIdPart(value) {
1348
1864
  return encodeURIComponent(value).replace(/%/g, "_");
1349
1865
  }
@@ -1368,8 +1884,7 @@ function createRouteFallbackElement(connection, elementMap, pageId, selector, no
1368
1884
  routeFallbackScopeSelectors: [...scopeSelectors]
1369
1885
  }, elementMap);
1370
1886
  }
1371
- //#endregion
1372
- //#region src/pageRouteMethod.ts
1887
+ // src/pageRouteMethod.ts
1373
1888
  const PAGE_CALL_METHOD_FALLBACK_RETRIES$1 = 3;
1374
1889
  const PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY$1 = 300;
1375
1890
  const ROUTE_METHOD_CALL_STORE = "__weappViteAutomatorMethodCalls__";
@@ -1520,8 +2035,7 @@ async function callRouteMethodViaAppService(connection, route, query, method, ar
1520
2035
  await sleep$1(PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY$1);
1521
2036
  }
1522
2037
  }
1523
- //#endregion
1524
- //#region src/Page.ts
2038
+ // src/Page.ts
1525
2039
  const PAGE_QUERY_TIMEOUT = 2500;
1526
2040
  const PAGE_DATA_TIMEOUT = 12e3;
1527
2041
  const PAGE_DATA_PROTOCOL_TIMEOUT = 2500;
@@ -2397,8 +2911,7 @@ var Page = class Page {
2397
2911
  return page;
2398
2912
  }
2399
2913
  };
2400
- //#endregion
2401
- //#region src/structuredConsole.ts
2914
+ // src/structuredConsole.ts
2402
2915
  function isRecord(value) {
2403
2916
  return value !== null && typeof value === "object";
2404
2917
  }
@@ -2491,8 +3004,7 @@ var StructuredConsole = class {
2491
3004
  }
2492
3005
  }
2493
3006
  };
2494
- //#endregion
2495
- //#region src/util.ts
3007
+ // src/util.ts
2496
3008
  /**
2497
3009
  * @file 二维码与插件路径工具。
2498
3010
  */
@@ -2519,8 +3031,7 @@ function extractPluginId(p) {
2519
3031
  const match = p.match(regPluginId);
2520
3032
  return match ? match[1] : "";
2521
3033
  }
2522
- //#endregion
2523
- //#region src/MiniProgram.ts
3034
+ // src/MiniProgram.ts
2524
3035
  const CLOSE_STEP_TIMEOUT = 2e3;
2525
3036
  const CURRENT_PAGE_RETRIES = 3;
2526
3037
  const CURRENT_PAGE_RETRY_DELAY = 400;
@@ -2641,16 +3152,21 @@ var MiniProgram = class extends EventEmitter {
2641
3152
  logEnabling;
2642
3153
  structuredLogs = false;
2643
3154
  structuredConsole;
3155
+ cdp;
2644
3156
  pageMap = /* @__PURE__ */ new Map();
2645
3157
  nativeIns;
3158
+ disconnecting;
3159
+ closing;
2646
3160
  constructor(connection) {
2647
3161
  super();
2648
3162
  this.connection = connection;
3163
+ this.cdp = getCDPController(connection, (method, params, options) => this.send(method, params, options));
2649
3164
  this.structuredConsole = new StructuredConsole((method, params, options) => this.send(method, params, options), (entry) => this.emit("console", entry));
2650
3165
  connection.on("App.CDPEvent", this.structuredConsole.receive);
2651
3166
  connection.on("App.logAdded", this.onLogAdded);
2652
3167
  connection.on("App.bindingCalled", this.onBindingCalled);
2653
3168
  connection.on("App.exceptionThrown", this.onExceptionThrown);
3169
+ connection.on("close", this.stopSubscriptions);
2654
3170
  }
2655
3171
  async pageStack() {
2656
3172
  const { pageStack } = await this.send("App.getPageStack");
@@ -2795,16 +3311,24 @@ var MiniProgram = class extends EventEmitter {
2795
3311
  duration: 0
2796
3312
  });
2797
3313
  }
2798
- async close() {
2799
- try {
2800
- await withTimeout(this.send("App.exit"), CLOSE_STEP_TIMEOUT);
2801
- } catch {}
2802
- await sleep(1e3);
2803
- try {
2804
- await withTimeout(this.send("Tool.close"), CLOSE_STEP_TIMEOUT);
2805
- } catch {} finally {
2806
- this.disconnect();
2807
- }
3314
+ close() {
3315
+ this.closing ??= (async () => {
3316
+ try {
3317
+ try {
3318
+ await withTimeout(this.send("App.exit"), CLOSE_STEP_TIMEOUT);
3319
+ } catch {}
3320
+ await sleep(1e3);
3321
+ try {
3322
+ await withTimeout(this.send("Tool.close"), CLOSE_STEP_TIMEOUT);
3323
+ } catch {}
3324
+ } finally {
3325
+ await this.disconnect();
3326
+ }
3327
+ })().catch((error) => {
3328
+ this.closing = void 0;
3329
+ throw error;
3330
+ });
3331
+ return this.closing;
2808
3332
  }
2809
3333
  async remote(auto = false) {
2810
3334
  const { qrCode } = await this.send("Tool.enableRemoteDebug", { auto });
@@ -2817,9 +3341,23 @@ var MiniProgram = class extends EventEmitter {
2817
3341
  });
2818
3342
  }
2819
3343
  disconnect() {
2820
- this.structuredConsole.stop();
2821
- this.connection.dispose();
3344
+ if (this.disconnecting) return this.disconnecting;
3345
+ this.stopSubscriptions();
3346
+ this.disconnecting = Promise.resolve(this.connection.dispose()).catch((error) => {
3347
+ this.disconnecting = void 0;
3348
+ throw error;
3349
+ });
3350
+ return this.disconnecting;
2822
3351
  }
3352
+ stopSubscriptions = () => {
3353
+ this.structuredConsole.stop();
3354
+ this.cdp.stop();
3355
+ this.connection.off("App.CDPEvent", this.structuredConsole.receive);
3356
+ this.connection.off("App.logAdded", this.onLogAdded);
3357
+ this.connection.off("App.bindingCalled", this.onBindingCalled);
3358
+ this.connection.off("App.exceptionThrown", this.onExceptionThrown);
3359
+ this.connection.off("close", this.stopSubscriptions);
3360
+ };
2823
3361
  /** 等待已接收的结构化日志完成 Error 属性检查并发布。 */
2824
3362
  async flushConsole() {
2825
3363
  await this.structuredConsole.flush();
@@ -2844,7 +3382,12 @@ var MiniProgram = class extends EventEmitter {
2844
3382
  let cdpMethod = this.structuredLogs ? "App.CDPEnable" : "App.CDPCommand";
2845
3383
  try {
2846
3384
  if (this.structuredLogs) {
2847
- await request(cdpMethod);
3385
+ if (timeout === void 0) await this.cdp.refresh();
3386
+ else {
3387
+ const remaining = timeout - (Date.now() - startedAt);
3388
+ if (remaining <= 0) throw new Error(`Timed out enabling console logging within ${timeout}ms`);
3389
+ await this.cdp.refresh({ timeout: remaining });
3390
+ }
2848
3391
  cdpMethod = "App.CDPCommand";
2849
3392
  }
2850
3393
  await request("App.CDPCommand", {
@@ -3112,6 +3655,11 @@ var MiniProgram = class extends EventEmitter {
3112
3655
  }
3113
3656
  throw lastError;
3114
3657
  }
3658
+ /** 返回当前连接共享的 CDP 调试入口。 */
3659
+ CDP() {
3660
+ return this.cdp.session;
3661
+ }
3662
+ /** 发送原始自动化协议,沿用连接的请求超时及清理契约。 */
3115
3663
  async send(method, params = {}, options) {
3116
3664
  return options ? await this.connection.send(method, params, options) : await this.connection.send(method, params);
3117
3665
  }
@@ -3129,13 +3677,11 @@ var MiniProgram = class extends EventEmitter {
3129
3677
  this.emit("exception", payload);
3130
3678
  };
3131
3679
  };
3132
- //#endregion
3133
- //#region src/platform.ts
3680
+ // src/platform.ts
3134
3681
  function normalizePlatform(platform = "wechat") {
3135
3682
  return platform === "baidu" ? "swan" : platform;
3136
3683
  }
3137
- //#endregion
3138
- //#region src/smartapp/errors.ts
3684
+ // src/smartapp/errors.ts
3139
3685
  /**
3140
3686
  * @file 百度智能小程序自动化错误定义。
3141
3687
  */
@@ -3148,8 +3694,7 @@ var SmartappUnsupportedError = class extends Error {
3148
3694
  function unsupported(feature) {
3149
3695
  throw new SmartappUnsupportedError(feature);
3150
3696
  }
3151
- //#endregion
3152
- //#region src/smartapp/time.ts
3697
+ // src/smartapp/time.ts
3153
3698
  /**
3154
3699
  * @file 百度智能小程序自动化时间工具。
3155
3700
  */
@@ -3163,8 +3708,7 @@ const time = {
3163
3708
  currentTimestamp,
3164
3709
  delay
3165
3710
  };
3166
- //#endregion
3167
- //#region src/smartapp/Element.ts
3711
+ // src/smartapp/Element.ts
3168
3712
  function getCoreElementId(element, selectors, index) {
3169
3713
  if (!element) return `${selectors}:${index}`;
3170
3714
  const elementId = Reflect.get(element, "id");
@@ -3231,8 +3775,7 @@ var SmartappElement = class {
3231
3775
  return Buffer.alloc(0);
3232
3776
  }
3233
3777
  };
3234
- //#endregion
3235
- //#region src/smartapp/Page.ts
3778
+ // src/smartapp/Page.ts
3236
3779
  var SmartappPage = class {
3237
3780
  page;
3238
3781
  path;
@@ -3255,8 +3798,7 @@ var SmartappPage = class {
3255
3798
  return await this.page?.callMethod(method, ...args);
3256
3799
  }
3257
3800
  };
3258
- //#endregion
3259
- //#region src/smartapp/App.ts
3801
+ // src/smartapp/App.ts
3260
3802
  var SmartappApp = class {
3261
3803
  miniProgram;
3262
3804
  appKey;
@@ -3315,11 +3857,10 @@ var SmartappApp = class {
3315
3857
  return [];
3316
3858
  }
3317
3859
  async close() {
3318
- this.miniProgram?.disconnect();
3860
+ await this.miniProgram?.disconnect();
3319
3861
  }
3320
3862
  };
3321
- //#endregion
3322
- //#region src/smartapp/Device.ts
3863
+ // src/smartapp/Device.ts
3323
3864
  var SmartappDevice = class {
3324
3865
  type;
3325
3866
  connectType;
@@ -3393,12 +3934,11 @@ var SmartappDevice = class {
3393
3934
  return false;
3394
3935
  }
3395
3936
  async close() {
3396
- await this.driver.close?.();
3397
- this.driver.miniProgram?.disconnect();
3937
+ 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);
3938
+ if (errors.length) throw new AggregateError(errors, "Smartapp device cleanup failed");
3398
3939
  }
3399
3940
  };
3400
- //#endregion
3401
- //#region src/smartapp/file.ts
3941
+ // src/smartapp/file.ts
3402
3942
  /**
3403
3943
  * @file 百度智能小程序自动化文件工具。
3404
3944
  */
@@ -3433,8 +3973,7 @@ const file = {
3433
3973
  rmdir,
3434
3974
  wget
3435
3975
  };
3436
- //#endregion
3437
- //#region src/smartapp/image.ts
3976
+ // src/smartapp/image.ts
3438
3977
  async function drawBorder(imageBuffer) {
3439
3978
  return imageBuffer;
3440
3979
  }
@@ -3449,8 +3988,7 @@ const image = {
3449
3988
  drawBorder,
3450
3989
  extract
3451
3990
  };
3452
- //#endregion
3453
- //#region src/smartapp/log.ts
3991
+ // src/smartapp/log.ts
3454
3992
  const rawConsole = console;
3455
3993
  const loggers = /* @__PURE__ */ new Map();
3456
3994
  function createLogger(label) {
@@ -3474,8 +4012,7 @@ const log = {
3474
4012
  get,
3475
4013
  set
3476
4014
  };
3477
- //#endregion
3478
- //#region src/smartapp/index.ts
4015
+ // src/smartapp/index.ts
3479
4016
  const DEFAULT_CONNECT_TYPE = "usb";
3480
4017
  function resolveDeviceType(deviceType) {
3481
4018
  switch (deviceType) {
@@ -3525,8 +4062,7 @@ const SmartappAutomator$1 = {
3525
4062
  log,
3526
4063
  time
3527
4064
  };
3528
- //#endregion
3529
- //#region src/SwanLauncher.ts
4065
+ // src/SwanLauncher.ts
3530
4066
  const smartappAutomatorRuntime = SmartappAutomator$1;
3531
4067
  function toSmartappLaunchOptions(options) {
3532
4068
  const { cliPath, platform: _platform, runtimeProvider: _runtimeProvider, ...rest } = options;
@@ -3549,8 +4085,7 @@ var SwanLauncher = class {
3549
4085
  return await this.automator.connect({ ...options });
3550
4086
  }
3551
4087
  };
3552
- //#endregion
3553
- //#region src/Launcher.ts
4088
+ // src/Launcher.ts
3554
4089
  /**
3555
4090
  * @file 开发者工具启动与连接流程。
3556
4091
  */
@@ -3561,29 +4096,34 @@ const DEFAULT_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_AUTOMATOR_RUNTIME_PROVIDER";
3561
4096
  const LEGACY_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_E2E_RUNTIME_PROVIDER";
3562
4097
  let localhostListenPatched = false;
3563
4098
  function retainPortLeaseUntilSessionClose(miniProgram, portLease) {
3564
- let released = false;
4099
+ let released;
3565
4100
  const release = async () => {
3566
- if (released) return;
3567
- released = true;
3568
- await portLease.release();
4101
+ released ??= portLease.release().catch((error) => {
4102
+ released = void 0;
4103
+ throw error;
4104
+ });
4105
+ await released;
3569
4106
  };
3570
4107
  const target = miniProgram;
3571
4108
  const rawClose = target.close;
3572
4109
  const rawDisconnect = target.disconnect;
3573
4110
  if (typeof rawClose !== "function" && typeof rawDisconnect !== "function") return false;
3574
- if (typeof rawDisconnect === "function") target.disconnect = function disconnectWithPortLeaseRelease() {
3575
- try {
3576
- return rawDisconnect.call(this);
3577
- } finally {
3578
- release();
3579
- }
3580
- };
4111
+ if (typeof rawDisconnect === "function") {
4112
+ let disconnecting;
4113
+ target.disconnect = function disconnectWithPortLeaseRelease() {
4114
+ disconnecting ??= (async () => {
4115
+ await rawDisconnect.call(this);
4116
+ await release();
4117
+ })().catch((error) => {
4118
+ disconnecting = void 0;
4119
+ throw error;
4120
+ });
4121
+ return disconnecting;
4122
+ };
4123
+ }
3581
4124
  if (typeof rawClose === "function") target.close = async function closeWithPortLeaseRelease() {
3582
- try {
3583
- return await rawClose.call(this);
3584
- } finally {
3585
- await release();
3586
- }
4125
+ await rawClose.call(this);
4126
+ await release();
3587
4127
  };
3588
4128
  return true;
3589
4129
  }
@@ -3625,7 +4165,7 @@ var Launcher = class {
3625
4165
  } catch (error) {
3626
4166
  scope.recordFailure(error);
3627
4167
  scope.throwIfAborted();
3628
- if (!isRecoverableOperationError(error) || attempt >= attempts) throw error;
4168
+ if (error instanceof AggregateError || !isRecoverableOperationError(error) || attempt >= attempts) throw error;
3629
4169
  }
3630
4170
  }
3631
4171
  });
@@ -3639,10 +4179,35 @@ var Launcher = class {
3639
4179
  });
3640
4180
  const rawReleaseLease = portLease.release.bind(portLease);
3641
4181
  let releasedLease;
3642
- portLease.release = () => releasedLease ??= rawReleaseLease();
3643
- const disownLease = scope.own(() => portLease.release(), "port-lease");
4182
+ portLease.release = () => releasedLease ??= rawReleaseLease().catch((error) => {
4183
+ releasedLease = void 0;
4184
+ throw error;
4185
+ });
4186
+ const connectionTasks = [];
4187
+ const disconnectingConnections = /* @__PURE__ */ new WeakMap();
4188
+ const disconnectConnection = (program) => {
4189
+ const pending = disconnectingConnections.get(program);
4190
+ if (pending) return pending;
4191
+ const closing = Promise.resolve(program.disconnect?.()).catch((error) => {
4192
+ disconnectingConnections.delete(program);
4193
+ throw error;
4194
+ });
4195
+ disconnectingConnections.set(program, closing);
4196
+ return closing;
4197
+ };
4198
+ const releaseConnectionsAndLease = async () => {
4199
+ const results = await Promise.allSettled(connectionTasks);
4200
+ 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);
4201
+ if (errors.length) throw new AggregateError(errors, "Automator connections did not finish closing; port lease retained");
4202
+ await portLease.release();
4203
+ };
4204
+ const disownLease = scope.own(releaseConnectionsAndLease, "port-lease");
3644
4205
  let releasePortLeaseOnExit = true;
3645
4206
  let releaseChild = async () => {};
4207
+ let diagnoseFailure = (error) => error;
4208
+ let failure;
4209
+ let failed = false;
4210
+ let result;
3646
4211
  try {
3647
4212
  const port = portLease.port;
3648
4213
  if (!cliPath) throw new Error("Wechat web devTools not found, please specify cliPath option");
@@ -3656,6 +4221,7 @@ var Launcher = class {
3656
4221
  let httpFallbackError = null;
3657
4222
  let targetPort = port;
3658
4223
  const cli = spawnWechatCli(cliPath, args, cwd, scope);
4224
+ diagnoseFailure = (error) => attachCliDiagnostics(error, cli, [ticket, account]);
3659
4225
  releaseChild = cli.release;
3660
4226
  let miniProgram = null;
3661
4227
  let lastConnectError = null;
@@ -3687,40 +4253,49 @@ var Launcher = class {
3687
4253
  }
3688
4254
  const connectTimeout = resolveRemainingTimeout();
3689
4255
  if (connectTimeout <= 0) return false;
3690
- const candidate = await scope.step(() => this.connectTool({
3691
- signal: scope.signal,
3692
- timeout: Math.min(3e3, connectTimeout),
3693
- wsEndpoint: `ws://127.0.0.1:${targetPort}`
3694
- }), {
4256
+ const candidate = await scope.step(() => {
4257
+ const connecting = this.connectTool({
4258
+ signal: scope.signal,
4259
+ timeout: Math.min(3e3, connectTimeout),
4260
+ wsEndpoint: `ws://127.0.0.1:${targetPort}`
4261
+ });
4262
+ connectionTasks.push(connecting);
4263
+ return connecting;
4264
+ }, {
3695
4265
  stage: "websocket",
3696
- disposeLate: (program) => program.disconnect()
4266
+ disposeLate: disconnectConnection
3697
4267
  });
3698
- let releasedCandidate = false;
4268
+ let releasedCandidate;
3699
4269
  const releaseCandidate = () => {
3700
- if (!releasedCandidate) {
3701
- releasedCandidate = true;
3702
- candidate.disconnect();
3703
- }
4270
+ return releasedCandidate ??= disconnectConnection(candidate).catch((error) => {
4271
+ releasedCandidate = void 0;
4272
+ throw error;
4273
+ });
3704
4274
  };
3705
4275
  const disownCandidate = scope.own(releaseCandidate, "websocket");
3706
4276
  try {
3707
4277
  const checkVersionTimeout = resolveRemainingTimeout();
3708
4278
  if (checkVersionTimeout <= 0) {
3709
- releaseCandidate();
4279
+ await scope.step(releaseCandidate);
3710
4280
  return false;
3711
4281
  }
3712
4282
  await scope.step(() => candidate.checkVersion(Math.min(VERSION_CHECK_TIMEOUT, checkVersionTimeout)), { stage: "version" });
3713
4283
  if (typeof candidate.waitForAppReady === "function") {
3714
4284
  const appReadyTimeout = resolveRemainingTimeout();
3715
4285
  if (appReadyTimeout <= 0) {
3716
- releaseCandidate();
4286
+ await scope.step(releaseCandidate);
3717
4287
  return false;
3718
4288
  }
3719
4289
  await scope.step(() => candidate.waitForAppReady(appReadyTimeout), { stage: "app-ready" });
3720
4290
  }
3721
4291
  } catch (error) {
4292
+ try {
4293
+ await scope.step(releaseCandidate);
4294
+ } catch (cleanupError) {
4295
+ if (cleanupError !== error) throw new AggregateError([error, cleanupError], "Automator validation and connection cleanup failed", { cause: error });
4296
+ throw error;
4297
+ }
3722
4298
  disownCandidate();
3723
- releaseCandidate();
3724
4299
  lastConnectError = error;
3725
4300
  if (isRecoverableOperationError(error)) return false;
3726
4301
  throw error;
@@ -3731,7 +4306,7 @@ var Launcher = class {
3731
4306
  lastConnectError = error;
3732
4307
  scope.recordFailure(error);
3733
4308
  scope.throwIfAborted();
3734
- if (!isRecoverableOperationError(error)) throw error;
4309
+ if (error instanceof AggregateError || !isRecoverableOperationError(error)) throw error;
3735
4310
  return false;
3736
4311
  }
3737
4312
  }, scope.remainingMs(), 1e3, scope.signal), { stage: "readiness" });
@@ -3749,15 +4324,30 @@ var Launcher = class {
3749
4324
  wsEndpoint: `ws://127.0.0.1:${targetPort}`
3750
4325
  });
3751
4326
  releasePortLeaseOnExit = !retainPortLeaseUntilSessionClose(resolvedMiniProgram, portLease);
4327
+ if (!releasePortLeaseOnExit) disownLease();
4328
+ result = resolvedMiniProgram;
4329
+ } catch (error) {
4330
+ failed = true;
4331
+ failure = diagnoseFailure(error);
4332
+ }
4333
+ const cleanupErrors = [];
4334
+ try {
4335
+ await scope.step(releaseChild);
4336
+ } catch (error) {
4337
+ if (!failed || error !== failure) cleanupErrors.push(error);
4338
+ }
4339
+ if (releasePortLeaseOnExit) try {
4340
+ await scope.step(releaseConnectionsAndLease);
3752
4341
  disownLease();
3753
- return resolvedMiniProgram;
3754
- } finally {
3755
- await releaseChild();
3756
- if (releasePortLeaseOnExit) {
3757
- disownLease();
3758
- await portLease.release();
3759
- }
4342
+ } catch (error) {
4343
+ if ((!failed || error !== failure) && !cleanupErrors.includes(error)) cleanupErrors.push(error);
3760
4344
  }
4345
+ if (cleanupErrors.length) {
4346
+ if (failed) throw new AggregateError([failure, ...cleanupErrors], "Automator launch and resource cleanup failed", { cause: failure });
4347
+ throw new AggregateError(cleanupErrors, "Automator launch resource cleanup failed");
4348
+ }
4349
+ if (failed) throw failure;
4350
+ return result;
3761
4351
  }
3762
4352
  async connect(options) {
3763
4353
  if (normalizePlatform(options.platform) === "swan") return await new SwanLauncher().connect(options);
@@ -3802,8 +4392,7 @@ var Launcher = class {
3802
4392
  }
3803
4393
  }
3804
4394
  };
3805
- //#endregion
3806
- //#region src/Automator.ts
4395
+ // src/Automator.ts
3807
4396
  /** Automator 的实现。 */
3808
4397
  var Automator = class {
3809
4398
  Element = Element;
@@ -3823,11 +4412,9 @@ var Automator = class {
3823
4412
  return await this.launcher.launch(options);
3824
4413
  }
3825
4414
  };
3826
- //#endregion
3827
- //#region src/index.ts
4415
+ // src/index.ts
3828
4416
  /**
3829
4417
  * @file miniprogram-automator 对外导出入口。
3830
4418
  */
3831
4419
  const SmartappAutomator = smartappAutomatorRuntime;
3832
- //#endregion
3833
4420
  export { Automator, Connection, ContextElement, CustomElement, Element, InputElement, Launcher, MiniProgram, MovableViewElement, Native, OPERATION_TIMEOUT_CODE, OperationLifecycle, Page, ScrollViewElement, SliderElement, SmartappAutomator, SwiperElement, SwitchElement, TextareaElement, Transport, acquireAutomatorPortLease, classifyOperationError, decodeQrCode, extractPluginId, isPluginPath, isRecoverableOperationError, normalizePlatform, printQrCode, readWechatLoginState };