@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/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
- await this.send("Element.dispatchEvent", options);
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.ws.terminate();
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.disposed) return;
914
- this.disposed = true;
915
- this.onClose();
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
- this.transport.close();
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
- callback.reject(new Error(error.message || closeErrTip));
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-anJvfi-6.mjs")).launch({ projectPath: options.projectPath });
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 }).catch(() => {});
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 released = false;
1484
+ let releasing;
1062
1485
  return {
1063
1486
  path: leasePath,
1064
1487
  port,
1065
- release: async () => {
1066
- if (released) return;
1067
- released = true;
1068
- if (leasePath) await removePortLock(leasePath);
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
- state.output = (state.output + String(chunk)).slice(-32768);
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.3.0";
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
- 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
- }
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.structuredConsole.stop();
2821
- this.connection.dispose();
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 request(cdpMethod);
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
- this.driver.miniProgram?.disconnect();
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 = false;
4137
+ let released;
3565
4138
  const release = async () => {
3566
- if (released) return;
3567
- released = true;
3568
- await portLease.release();
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") target.disconnect = function disconnectWithPortLeaseRelease() {
3575
- try {
3576
- return rawDisconnect.call(this);
3577
- } finally {
3578
- release();
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
- try {
3583
- return await rawClose.call(this);
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
- const disownLease = scope.own(() => portLease.release(), "port-lease");
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(() => this.connectTool({
3691
- signal: scope.signal,
3692
- timeout: Math.min(3e3, connectTimeout),
3693
- wsEndpoint: `ws://127.0.0.1:${targetPort}`
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: (program) => program.disconnect()
4304
+ disposeLate: disconnectConnection
3697
4305
  });
3698
- let releasedCandidate = false;
4306
+ let releasedCandidate;
3699
4307
  const releaseCandidate = () => {
3700
- if (!releasedCandidate) {
3701
- releasedCandidate = true;
3702
- candidate.disconnect();
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
- return resolvedMiniProgram;
3754
- } finally {
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);