@weapp-vite/miniprogram-automator 1.2.23 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/index.d.mts +76 -8
- package/dist/index.mjs +996 -212
- package/dist/{launch-CSjvON-f.mjs → launch-ElaGbFxh.mjs} +2373 -621
- package/dist/operation.d.mts +64 -0
- package/dist/operation.mjs +233 -0
- package/package.json +15 -4
package/dist/index.mjs
CHANGED
|
@@ -1,31 +1,49 @@
|
|
|
1
|
+
import { OPERATION_TIMEOUT_CODE, OperationLifecycle, classifyOperationError, isRecoverableOperationError, readWechatLoginState } from "./operation.mjs";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import net from "node:net";
|
|
3
4
|
import process from "node:process";
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
5
|
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";
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
11
13
|
import { decodeQrCodeFromBase64, renderTerminalQrCode } from "@weapp-vite/qr";
|
|
12
14
|
import { Buffer } from "node:buffer";
|
|
13
15
|
//#region src/internal/compat/async.ts
|
|
14
16
|
/**
|
|
15
17
|
* @file 异步控制工具。
|
|
16
18
|
*/
|
|
17
|
-
/**
|
|
18
|
-
function sleep$1(timeout) {
|
|
19
|
-
|
|
19
|
+
/** 可取消的延迟,取消时释放自身计时器。 */
|
|
20
|
+
function sleep$1(timeout, signal) {
|
|
21
|
+
signal?.throwIfAborted();
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
let timer;
|
|
24
|
+
function abort() {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
signal?.removeEventListener("abort", abort);
|
|
27
|
+
reject(signal?.reason);
|
|
28
|
+
}
|
|
29
|
+
timer = setTimeout(() => {
|
|
30
|
+
signal?.removeEventListener("abort", abort);
|
|
31
|
+
resolve();
|
|
32
|
+
}, timeout);
|
|
33
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
34
|
+
});
|
|
20
35
|
}
|
|
21
|
-
/**
|
|
22
|
-
async function waitUntil(condition, timeout = 0, interval = 250) {
|
|
23
|
-
const
|
|
36
|
+
/** 条件轮询受同一个单调时钟 deadline 与取消信号约束。 */
|
|
37
|
+
async function waitUntil(condition, timeout = 0, interval = 250, signal) {
|
|
38
|
+
const deadlineAt = timeout ? performance.now() + timeout : Number.POSITIVE_INFINITY;
|
|
24
39
|
while (true) {
|
|
40
|
+
signal?.throwIfAborted();
|
|
25
41
|
const value = await condition();
|
|
42
|
+
signal?.throwIfAborted();
|
|
26
43
|
if (value) return value;
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
const remaining = deadlineAt - performance.now();
|
|
45
|
+
if (remaining <= 0) throw new Error(`Wait timed out after ${timeout} ms`);
|
|
46
|
+
await sleep$1(Math.min(interval, remaining), signal);
|
|
29
47
|
}
|
|
30
48
|
}
|
|
31
49
|
//#endregion
|
|
@@ -218,6 +236,304 @@ function cmpVersion(versionA, versionB) {
|
|
|
218
236
|
return 0;
|
|
219
237
|
}
|
|
220
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
|
|
221
537
|
//#region src/pageRouteSnapshot.ts
|
|
222
538
|
const ROUTE_ELEMENT_SNAPSHOT_TIMEOUT = 5e3;
|
|
223
539
|
const ROUTE_ELEMENT_SNAPSHOT_EMPTY_RETRIES = 2;
|
|
@@ -437,6 +753,14 @@ async function readRouteElementSnapshot(connection, route, query, selector, inde
|
|
|
437
753
|
}
|
|
438
754
|
//#endregion
|
|
439
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
|
+
}
|
|
440
764
|
const ELEMENT_QUERY_TIMEOUT = 2500;
|
|
441
765
|
const ELEMENT_WXML_TIMEOUT = 8e3;
|
|
442
766
|
/** Element 的实现。 */
|
|
@@ -542,16 +866,26 @@ var Element = class Element {
|
|
|
542
866
|
await this.send("Element.triggerEvent", payload);
|
|
543
867
|
}
|
|
544
868
|
async touchstart(options = {}) {
|
|
545
|
-
await this.send("Element.touchstart", options);
|
|
869
|
+
await this.send("Element.touchstart", normalizeTouchOptions(options));
|
|
546
870
|
}
|
|
547
871
|
async touchmove(options = {}) {
|
|
548
|
-
await this.send("Element.touchmove", options);
|
|
872
|
+
await this.send("Element.touchmove", normalizeTouchOptions(options));
|
|
549
873
|
}
|
|
550
874
|
async touchend(options = {}) {
|
|
551
|
-
await this.send("Element.touchend", options);
|
|
875
|
+
await this.send("Element.touchend", normalizeTouchOptions(options));
|
|
552
876
|
}
|
|
553
877
|
async dispatchEvent(options) {
|
|
554
|
-
|
|
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
|
+
});
|
|
555
889
|
}
|
|
556
890
|
async _property(name) {
|
|
557
891
|
return await this.getter(name, "getProperties", "properties");
|
|
@@ -733,6 +1067,18 @@ var CustomElement = class extends Element {
|
|
|
733
1067
|
return (await this.send("Element.getData", payload)).data;
|
|
734
1068
|
}
|
|
735
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
|
+
}
|
|
736
1082
|
return (await this.send("Element.callMethod", {
|
|
737
1083
|
method,
|
|
738
1084
|
args
|
|
@@ -811,6 +1157,9 @@ var ContextElement = class extends Element {
|
|
|
811
1157
|
/** Transport 的实现。 */
|
|
812
1158
|
var Transport = class extends EventEmitter {
|
|
813
1159
|
ws;
|
|
1160
|
+
closed = false;
|
|
1161
|
+
terminating = false;
|
|
1162
|
+
closing;
|
|
814
1163
|
constructor(ws) {
|
|
815
1164
|
super();
|
|
816
1165
|
this.ws = ws;
|
|
@@ -818,6 +1167,7 @@ var Transport = class extends EventEmitter {
|
|
|
818
1167
|
this.emit("message", typeof data === "string" ? data : data.toString());
|
|
819
1168
|
});
|
|
820
1169
|
ws.on("close", () => {
|
|
1170
|
+
this.closed = true;
|
|
821
1171
|
this.emit("close");
|
|
822
1172
|
});
|
|
823
1173
|
}
|
|
@@ -825,7 +1175,36 @@ var Transport = class extends EventEmitter {
|
|
|
825
1175
|
this.ws.send(message);
|
|
826
1176
|
}
|
|
827
1177
|
close() {
|
|
828
|
-
this.
|
|
1178
|
+
if (this.closing) return this.closing;
|
|
1179
|
+
if (this.closed || this.ws.readyState === 3) return this.closing = Promise.resolve();
|
|
1180
|
+
this.closing = new Promise((resolve, reject) => {
|
|
1181
|
+
let timer;
|
|
1182
|
+
const finish = () => {
|
|
1183
|
+
clearTimeout(timer);
|
|
1184
|
+
this.off("close", finish);
|
|
1185
|
+
resolve();
|
|
1186
|
+
};
|
|
1187
|
+
timer = setTimeout(() => {
|
|
1188
|
+
this.off("close", finish);
|
|
1189
|
+
reject(Object.assign(/* @__PURE__ */ new Error("DevTools websocket did not close within 2000ms"), { code: "DEVTOOLS_DISCONNECT_TIMEOUT" }));
|
|
1190
|
+
}, 2e3);
|
|
1191
|
+
this.once("close", finish);
|
|
1192
|
+
try {
|
|
1193
|
+
if (!this.terminating) {
|
|
1194
|
+
this.terminating = true;
|
|
1195
|
+
this.ws.terminate();
|
|
1196
|
+
}
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
this.terminating = false;
|
|
1199
|
+
clearTimeout(timer);
|
|
1200
|
+
this.off("close", finish);
|
|
1201
|
+
reject(error);
|
|
1202
|
+
}
|
|
1203
|
+
}).catch((error) => {
|
|
1204
|
+
this.closing = void 0;
|
|
1205
|
+
throw error;
|
|
1206
|
+
});
|
|
1207
|
+
return this.closing;
|
|
829
1208
|
}
|
|
830
1209
|
};
|
|
831
1210
|
//#endregion
|
|
@@ -840,13 +1219,22 @@ const CONNECT_TIMEOUT = 3e4;
|
|
|
840
1219
|
/** Page frame 定向消息失效、需要切换到 App-service Page 协议的 DevTools 版本。 */
|
|
841
1220
|
const APP_SERVICE_PAGE_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set(["2.01.2510290"]);
|
|
842
1221
|
/** 仅页面方法调用失效的版本,元素查询仍保留原生组件作用域。 */
|
|
843
|
-
const APP_SERVICE_PAGE_METHOD_VERSIONS = /* @__PURE__ */ new Set([
|
|
1222
|
+
const APP_SERVICE_PAGE_METHOD_VERSIONS = /* @__PURE__ */ new Set([
|
|
1223
|
+
"2.02.2608070",
|
|
1224
|
+
"2.02.2608080",
|
|
1225
|
+
"2.02.2609231",
|
|
1226
|
+
"2.02.2610082",
|
|
1227
|
+
"2.02.2610092"
|
|
1228
|
+
]);
|
|
844
1229
|
/** Connection 的实现。 */
|
|
845
1230
|
var Connection = class Connection extends EventEmitter {
|
|
846
1231
|
transport;
|
|
1232
|
+
disposed = false;
|
|
1233
|
+
disposing;
|
|
847
1234
|
callbacks = /* @__PURE__ */ new Map();
|
|
848
1235
|
useAppServicePageProtocol = false;
|
|
849
1236
|
useAppServicePageMethod = false;
|
|
1237
|
+
useComponentCallerBridge = false;
|
|
850
1238
|
constructor(transport) {
|
|
851
1239
|
super();
|
|
852
1240
|
this.transport = transport;
|
|
@@ -854,6 +1242,7 @@ var Connection = class Connection extends EventEmitter {
|
|
|
854
1242
|
transport.on("close", this.onClose);
|
|
855
1243
|
}
|
|
856
1244
|
send(method, params = {}, options = {}) {
|
|
1245
|
+
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error(closeErrTip));
|
|
857
1246
|
const id = uuid();
|
|
858
1247
|
const payload = stringify({
|
|
859
1248
|
id,
|
|
@@ -877,21 +1266,59 @@ var Connection = class Connection extends EventEmitter {
|
|
|
877
1266
|
});
|
|
878
1267
|
try {
|
|
879
1268
|
this.transport.send(payload);
|
|
880
|
-
} catch {
|
|
1269
|
+
} catch (cause) {
|
|
881
1270
|
clearTimeout(timeout);
|
|
882
1271
|
this.callbacks.delete(id);
|
|
883
|
-
reject(
|
|
1272
|
+
reject(new Error(closeErrTip, { cause }));
|
|
884
1273
|
}
|
|
885
1274
|
});
|
|
886
1275
|
}
|
|
887
1276
|
dispose() {
|
|
888
|
-
this.
|
|
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
|
+
}
|
|
1299
|
+
this.transport.off("message", this.onMessage);
|
|
1300
|
+
this.transport.off("close", this.onClose);
|
|
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
|
+
}
|
|
889
1315
|
}
|
|
890
1316
|
/** 根据 DevTools 版本选择稳定的 Page 协议实现。 */
|
|
891
1317
|
configureToolInfo(info) {
|
|
892
1318
|
const version = String(info.version ?? "");
|
|
893
1319
|
this.useAppServicePageProtocol = APP_SERVICE_PAGE_PROTOCOL_VERSIONS.has(version);
|
|
894
1320
|
this.useAppServicePageMethod = this.useAppServicePageProtocol || APP_SERVICE_PAGE_METHOD_VERSIONS.has(version);
|
|
1321
|
+
this.useComponentCallerBridge = version === "2.02.2608080" && info.SDKVersion === "3.17.5";
|
|
895
1322
|
}
|
|
896
1323
|
get prefersAppServicePageProtocol() {
|
|
897
1324
|
return this.useAppServicePageProtocol;
|
|
@@ -900,6 +1327,10 @@ var Connection = class Connection extends EventEmitter {
|
|
|
900
1327
|
get prefersAppServicePageMethod() {
|
|
901
1328
|
return this.useAppServicePageMethod;
|
|
902
1329
|
}
|
|
1330
|
+
/** 仅为实测存在跨上下文 token 错绑的 IDE 与基础库启用真实 caller 桥接。 */
|
|
1331
|
+
get prefersComponentCallerBridge() {
|
|
1332
|
+
return this.useComponentCallerBridge;
|
|
1333
|
+
}
|
|
903
1334
|
onMessage = (message) => {
|
|
904
1335
|
debugProtocol(`${dateFormat("yyyy-mm-dd HH:MM:ss:l")} ◀ RECV ${message}`);
|
|
905
1336
|
const { id, method, error, result, params } = JSON.parse(message);
|
|
@@ -912,47 +1343,87 @@ var Connection = class Connection extends EventEmitter {
|
|
|
912
1343
|
this.callbacks.delete(id);
|
|
913
1344
|
clearTimeout(callback.timeout);
|
|
914
1345
|
if (error) {
|
|
915
|
-
|
|
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 }));
|
|
916
1348
|
return;
|
|
917
1349
|
}
|
|
918
1350
|
callback.resolve(result);
|
|
919
1351
|
};
|
|
920
1352
|
onClose = () => {
|
|
1353
|
+
if (this.disposed) return;
|
|
1354
|
+
this.disposed = true;
|
|
921
1355
|
for (const callback of this.callbacks.values()) {
|
|
922
1356
|
clearTimeout(callback.timeout);
|
|
923
1357
|
callback.reject(/* @__PURE__ */ new Error(closeErrTip));
|
|
924
1358
|
}
|
|
925
1359
|
this.callbacks.clear();
|
|
1360
|
+
this.emit("close");
|
|
926
1361
|
};
|
|
927
|
-
static create(url, timeout = CONNECT_TIMEOUT) {
|
|
1362
|
+
static create(url, timeout = CONNECT_TIMEOUT, signal) {
|
|
1363
|
+
signal?.throwIfAborted();
|
|
928
1364
|
return new Promise((resolve, reject) => {
|
|
929
1365
|
const ws = new WebSocket(url);
|
|
930
1366
|
let settled = false;
|
|
931
|
-
|
|
1367
|
+
let timer;
|
|
1368
|
+
let onAbort;
|
|
1369
|
+
let onOpen;
|
|
1370
|
+
let onClose;
|
|
1371
|
+
function cleanup() {
|
|
1372
|
+
clearTimeout(timer);
|
|
1373
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1374
|
+
ws.off("open", onOpen);
|
|
1375
|
+
ws.off("close", onClose);
|
|
1376
|
+
}
|
|
1377
|
+
function fail(error) {
|
|
932
1378
|
if (settled) return;
|
|
933
1379
|
settled = true;
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1380
|
+
cleanup();
|
|
1381
|
+
ws.terminate();
|
|
1382
|
+
reject(error);
|
|
1383
|
+
}
|
|
1384
|
+
onAbort = () => {
|
|
1385
|
+
fail(signal?.reason);
|
|
1386
|
+
};
|
|
1387
|
+
onClose = () => {
|
|
1388
|
+
fail(/* @__PURE__ */ new Error("DevTools websocket closed before handshake completed"));
|
|
1389
|
+
};
|
|
1390
|
+
onOpen = () => {
|
|
938
1391
|
if (settled) return;
|
|
939
1392
|
settled = true;
|
|
940
|
-
|
|
1393
|
+
cleanup();
|
|
941
1394
|
resolve(new Connection(new Transport(ws)));
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1395
|
+
};
|
|
1396
|
+
timer = setTimeout(() => fail(/* @__PURE__ */ new Error(`Timed out connecting to DevTools websocket ${url} after ${timeout}ms`)), timeout);
|
|
1397
|
+
ws.on("open", onOpen);
|
|
1398
|
+
ws.on("error", fail);
|
|
1399
|
+
ws.on("close", onClose);
|
|
1400
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1401
|
+
if (signal?.aborted) onAbort();
|
|
949
1402
|
});
|
|
950
1403
|
}
|
|
951
1404
|
};
|
|
952
1405
|
//#endregion
|
|
953
1406
|
//#region src/headless.ts
|
|
954
1407
|
async function launchHeadlessAutomator(options) {
|
|
955
|
-
return await (await import("./launch-
|
|
1408
|
+
return await (await import("./launch-ElaGbFxh.mjs")).launch({ projectPath: options.projectPath });
|
|
1409
|
+
}
|
|
1410
|
+
//#endregion
|
|
1411
|
+
//#region src/launcher/diagnostics.ts
|
|
1412
|
+
/** 将本次 CLI 的有界输出附到原始错误,避免泄露登录票据及账号参数。 */
|
|
1413
|
+
function attachCliDiagnostics(error, state, secrets = []) {
|
|
1414
|
+
const original = error instanceof Error ? error : new Error("Failed to launch WeChat DevTools", { cause: error });
|
|
1415
|
+
if (original.cliOutput !== void 0) return original;
|
|
1416
|
+
let output = stripVTControlCharacters(state.output).trim();
|
|
1417
|
+
for (const secret of secrets.filter(Boolean).sort((a, b) => b.length - a.length)) output = output.split(secret).join("[redacted]");
|
|
1418
|
+
output = output.replace(/(authorization["']?\s*[:=]\s*["']?)(?:Bearer|Basic)\s+[^\s"',;}]+/gi, "$1[redacted]");
|
|
1419
|
+
output = output.replace(/((?:--)?(?:auto-account|account|ticket|access[_-]?token|refresh[_-]?token|token|authorization)["']?\s*(?:[:=]\s*|\s))(["']?)([^\s"',;}]+)\2/gi, "$1[redacted]");
|
|
1420
|
+
if (!output) return original;
|
|
1421
|
+
const truncated = state.outputTruncated || output.length > 32768;
|
|
1422
|
+
output = output.slice(-32768);
|
|
1423
|
+
const target = Object.isExtensible(original) ? original : new Error(original.message, { cause: original });
|
|
1424
|
+
target.cliOutput = output;
|
|
1425
|
+
target.message += `\ncli output${truncated ? " (truncated; last 32768 characters)" : ""}:\n${output}`;
|
|
1426
|
+
return target;
|
|
956
1427
|
}
|
|
957
1428
|
//#endregion
|
|
958
1429
|
//#region src/launcher/portLease.ts
|
|
@@ -978,7 +1449,7 @@ function isProcessAlive(pid) {
|
|
|
978
1449
|
}
|
|
979
1450
|
}
|
|
980
1451
|
async function removePortLock(leasePath) {
|
|
981
|
-
await fs.rm(leasePath, { force: true })
|
|
1452
|
+
await fs.rm(leasePath, { force: true });
|
|
982
1453
|
}
|
|
983
1454
|
async function removeStalePortLock(leasePath) {
|
|
984
1455
|
try {
|
|
@@ -1010,14 +1481,18 @@ async function createPortLock(port) {
|
|
|
1010
1481
|
return leasePath;
|
|
1011
1482
|
}
|
|
1012
1483
|
function createLease(port, leasePath) {
|
|
1013
|
-
let
|
|
1484
|
+
let releasing;
|
|
1014
1485
|
return {
|
|
1015
1486
|
path: leasePath,
|
|
1016
1487
|
port,
|
|
1017
|
-
release:
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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;
|
|
1021
1496
|
}
|
|
1022
1497
|
};
|
|
1023
1498
|
}
|
|
@@ -1064,7 +1539,10 @@ async function enableAutomatorViaHttp(options) {
|
|
|
1064
1539
|
if (options.account) endpoint.searchParams.set("account", options.account);
|
|
1065
1540
|
if (options.ticket) endpoint.searchParams.set("ticket", options.ticket);
|
|
1066
1541
|
if (options.trustProject) endpoint.searchParams.set("trustProject", "true");
|
|
1067
|
-
const response = await fetch(endpoint, {
|
|
1542
|
+
const response = await fetch(endpoint, {
|
|
1543
|
+
redirect: "follow",
|
|
1544
|
+
signal: options.signal
|
|
1545
|
+
});
|
|
1068
1546
|
const body = await response.text();
|
|
1069
1547
|
if (!response.ok) throw new Error(`WeChat DevTools HTTP automator fallback failed with status ${response.status}`);
|
|
1070
1548
|
let result;
|
|
@@ -1073,6 +1551,7 @@ async function enableAutomatorViaHttp(options) {
|
|
|
1073
1551
|
} catch {
|
|
1074
1552
|
throw new Error("WeChat DevTools HTTP automator fallback returned invalid JSON");
|
|
1075
1553
|
}
|
|
1554
|
+
if (result && typeof result === "object" && "code" in result && Number(result.code) === 10) throw Object.assign(/* @__PURE__ */ new Error("DEVTOOLS_LOGIN_REQUIRED: need re-login"), { code: 10 });
|
|
1076
1555
|
if (result && typeof result === "object" && "autoPort" in result) {
|
|
1077
1556
|
const autoPort = Number(result.autoPort);
|
|
1078
1557
|
if (!Number.isInteger(autoPort) || autoPort <= 0 || autoPort > 65535) throw new Error("WeChat DevTools HTTP automator fallback returned invalid autoPort");
|
|
@@ -1106,8 +1585,231 @@ function resolveWechatDevtoolsBootstrapArgs(args) {
|
|
|
1106
1585
|
return bootstrapArgs;
|
|
1107
1586
|
}
|
|
1108
1587
|
//#endregion
|
|
1588
|
+
//#region src/launcher/process.ts
|
|
1589
|
+
const WINDOWS_BATCH_CLI_RE = /\.(?:bat|cmd)$/i;
|
|
1590
|
+
function shouldUseWindowsCommandShell(cliPath) {
|
|
1591
|
+
return isWindows && WINDOWS_BATCH_CLI_RE.test(cliPath);
|
|
1592
|
+
}
|
|
1593
|
+
function escapeWindowsCmdArg(arg) {
|
|
1594
|
+
const escaped = arg.replace(/"/g, "\"\"").replace(/%/g, "%%");
|
|
1595
|
+
return /[\s"&<>^|()]/.test(arg) ? `"${escaped}"` : escaped;
|
|
1596
|
+
}
|
|
1597
|
+
function resolveWindowsBatchSpawn(cliPath, args) {
|
|
1598
|
+
return {
|
|
1599
|
+
file: process.env.ComSpec || "cmd.exe",
|
|
1600
|
+
args: [
|
|
1601
|
+
"/d",
|
|
1602
|
+
"/s",
|
|
1603
|
+
"/c",
|
|
1604
|
+
`"${[cliPath, ...args].map(escapeWindowsCmdArg).join(" ")}"`
|
|
1605
|
+
]
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
/** 只持有本次 CLI 子进程句柄;退出后不通过旧 PID 查找或终止宿主。 */
|
|
1609
|
+
function spawnWechatCli(cliPath, args, cwd, scope) {
|
|
1610
|
+
scope.throwIfAborted();
|
|
1611
|
+
const state = {
|
|
1612
|
+
error: null,
|
|
1613
|
+
success: false,
|
|
1614
|
+
exited: false,
|
|
1615
|
+
output: "",
|
|
1616
|
+
outputTruncated: false
|
|
1617
|
+
};
|
|
1618
|
+
const bootstrapArgs = resolveWechatDevtoolsBootstrapArgs(args);
|
|
1619
|
+
const target = shouldUseWindowsCommandShell(cliPath) ? resolveWindowsBatchSpawn(cliPath, bootstrapArgs) : {
|
|
1620
|
+
file: cliPath,
|
|
1621
|
+
args: bootstrapArgs
|
|
1622
|
+
};
|
|
1623
|
+
const child = spawn(target.file, target.args, {
|
|
1624
|
+
stdio: [
|
|
1625
|
+
"ignore",
|
|
1626
|
+
"pipe",
|
|
1627
|
+
"pipe"
|
|
1628
|
+
],
|
|
1629
|
+
cwd: cwd || void 0,
|
|
1630
|
+
...shouldUseWindowsCommandShell(cliPath) ? {
|
|
1631
|
+
windowsHide: true,
|
|
1632
|
+
windowsVerbatimArguments: true
|
|
1633
|
+
} : {}
|
|
1634
|
+
});
|
|
1635
|
+
let released;
|
|
1636
|
+
const release = () => {
|
|
1637
|
+
if (!released) released = new Promise((resolve) => {
|
|
1638
|
+
if (state.exited || !child.pid || child.exitCode !== null || child.signalCode !== null) {
|
|
1639
|
+
resolve();
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
child.once("close", () => resolve());
|
|
1643
|
+
child.kill("SIGKILL");
|
|
1644
|
+
});
|
|
1645
|
+
return released;
|
|
1646
|
+
};
|
|
1647
|
+
const disown = scope.own(release, "cli-process");
|
|
1648
|
+
child.on("error", (error) => {
|
|
1649
|
+
state.error = error;
|
|
1650
|
+
});
|
|
1651
|
+
const output = (chunk) => {
|
|
1652
|
+
const combined = state.output + String(chunk);
|
|
1653
|
+
state.outputTruncated ||= combined.length > 32768;
|
|
1654
|
+
state.output = combined.slice(-32768);
|
|
1655
|
+
};
|
|
1656
|
+
child.stdout?.on("data", output);
|
|
1657
|
+
child.stderr?.on("data", output);
|
|
1658
|
+
child.on("exit", (code, signal) => {
|
|
1659
|
+
disown();
|
|
1660
|
+
state.exited = true;
|
|
1661
|
+
if (code !== 0 || signal) state.error = /* @__PURE__ */ new Error(`DevTools cli exited unexpectedly with code ${code ?? "null"}${signal ? ` and signal ${signal}` : ""}`);
|
|
1662
|
+
else state.success = true;
|
|
1663
|
+
});
|
|
1664
|
+
child.unref();
|
|
1665
|
+
return Object.assign(state, { release });
|
|
1666
|
+
}
|
|
1667
|
+
//#endregion
|
|
1109
1668
|
//#region package.json
|
|
1110
|
-
var version = "1.
|
|
1669
|
+
var version = "1.4.0";
|
|
1670
|
+
//#endregion
|
|
1671
|
+
//#region src/appServiceHeap.ts
|
|
1672
|
+
function isRecord$1(value) {
|
|
1673
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1674
|
+
}
|
|
1675
|
+
function unsupported$1(reason) {
|
|
1676
|
+
return {
|
|
1677
|
+
status: "unsupported",
|
|
1678
|
+
source: "appservice-cdp-runtime",
|
|
1679
|
+
reason
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
/** 只读取 AppService 当前 V8 堆,不调用 GC,也不把协议故障或空响应当成零内存。 */
|
|
1683
|
+
async function readAppServiceHeapUsage(send, options = {}) {
|
|
1684
|
+
const timeout = options.timeout ?? 2500;
|
|
1685
|
+
if (!Number.isFinite(timeout) || timeout <= 0) throw new TypeError("AppService heap timeout must be finite and positive");
|
|
1686
|
+
let response;
|
|
1687
|
+
try {
|
|
1688
|
+
response = await send("App.CDPCommand", {
|
|
1689
|
+
domain: "Runtime",
|
|
1690
|
+
method: "getHeapUsage",
|
|
1691
|
+
params: {}
|
|
1692
|
+
}, { timeout });
|
|
1693
|
+
} catch (error) {
|
|
1694
|
+
if (error instanceof Error && (error.message === "appservice App.CDPCommand unimplemented" || error.message === "Method not implemented.")) return unsupported$1("protocol-unimplemented");
|
|
1695
|
+
if (error instanceof Error && error.message === "'Runtime.getHeapUsage' wasn't found") return unsupported$1("method-not-found");
|
|
1696
|
+
throw error;
|
|
1697
|
+
}
|
|
1698
|
+
if (isRecord$1(response) && isRecord$1(response.error) && response.error.code === -32601) return unsupported$1("method-not-found");
|
|
1699
|
+
if (!isRecord$1(response) || response.error || response.exceptionDetails || typeof response.usedSize !== "number" || !Number.isFinite(response.usedSize) || response.usedSize < 0 || typeof response.totalSize !== "number" || !Number.isFinite(response.totalSize) || response.totalSize < response.usedSize) throw new Error("Invalid AppService Runtime.getHeapUsage response: expected finite non-negative usedSize <= totalSize");
|
|
1700
|
+
return {
|
|
1701
|
+
status: "available",
|
|
1702
|
+
source: "appservice-cdp-runtime",
|
|
1703
|
+
usedSize: response.usedSize,
|
|
1704
|
+
totalSize: response.totalSize
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
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
|
+
}
|
|
1111
1813
|
//#endregion
|
|
1112
1814
|
//#region src/Native.ts
|
|
1113
1815
|
/** Native 的实现。 */
|
|
@@ -1158,6 +1860,25 @@ var Native = class extends EventEmitter {
|
|
|
1158
1860
|
}
|
|
1159
1861
|
};
|
|
1160
1862
|
//#endregion
|
|
1863
|
+
//#region src/navigation/query.ts
|
|
1864
|
+
/** 仅核对请求显式携带的参数,保留宿主附加参数及其原始编码形式。 */
|
|
1865
|
+
function matchesRouteQuery(actual, expected) {
|
|
1866
|
+
const expectedKeys = Object.keys(expected);
|
|
1867
|
+
if (!expectedKeys.length) return true;
|
|
1868
|
+
if (!actual || typeof actual !== "object" || Array.isArray(actual)) return false;
|
|
1869
|
+
const query = actual;
|
|
1870
|
+
return expectedKeys.every((key) => {
|
|
1871
|
+
if (!Object.prototype.hasOwnProperty.call(query, key) || query[key] == null) return false;
|
|
1872
|
+
const value = String(query[key]);
|
|
1873
|
+
if (value === expected[key]) return true;
|
|
1874
|
+
try {
|
|
1875
|
+
return decodeURIComponent(value) === expected[key];
|
|
1876
|
+
} catch {
|
|
1877
|
+
return false;
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
//#endregion
|
|
1161
1882
|
//#region src/pageRouteFallback.ts
|
|
1162
1883
|
function normalizeFallbackIdPart(value) {
|
|
1163
1884
|
return encodeURIComponent(value).replace(/%/g, "_");
|
|
@@ -2422,7 +3143,7 @@ function isPluginNavigationUrl(value) {
|
|
|
2422
3143
|
}
|
|
2423
3144
|
function matchesRouteChange(options) {
|
|
2424
3145
|
const normalizedCurrentPath = normalizeRoutePath(options.currentPath);
|
|
2425
|
-
if (!options.expectedRoute || normalizedCurrentPath === options.expectedRoute) return
|
|
3146
|
+
if (!options.expectedRoute || normalizedCurrentPath === options.expectedRoute) return matchesRouteQuery(options.currentQuery, options.expectedQuery);
|
|
2426
3147
|
if (!isPluginNavigationUrl(options.requestedUrl) || !isPluginPath(options.currentPath)) return false;
|
|
2427
3148
|
return normalizedCurrentPath !== normalizeRoutePath(options.previousPath);
|
|
2428
3149
|
}
|
|
@@ -2456,16 +3177,21 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2456
3177
|
logEnabling;
|
|
2457
3178
|
structuredLogs = false;
|
|
2458
3179
|
structuredConsole;
|
|
3180
|
+
cdp;
|
|
2459
3181
|
pageMap = /* @__PURE__ */ new Map();
|
|
2460
3182
|
nativeIns;
|
|
3183
|
+
disconnecting;
|
|
3184
|
+
closing;
|
|
2461
3185
|
constructor(connection) {
|
|
2462
3186
|
super();
|
|
2463
3187
|
this.connection = connection;
|
|
3188
|
+
this.cdp = getCDPController(connection, (method, params, options) => this.send(method, params, options));
|
|
2464
3189
|
this.structuredConsole = new StructuredConsole((method, params, options) => this.send(method, params, options), (entry) => this.emit("console", entry));
|
|
2465
3190
|
connection.on("App.CDPEvent", this.structuredConsole.receive);
|
|
2466
3191
|
connection.on("App.logAdded", this.onLogAdded);
|
|
2467
3192
|
connection.on("App.bindingCalled", this.onBindingCalled);
|
|
2468
3193
|
connection.on("App.exceptionThrown", this.onExceptionThrown);
|
|
3194
|
+
connection.on("close", this.stopSubscriptions);
|
|
2469
3195
|
}
|
|
2470
3196
|
async pageStack() {
|
|
2471
3197
|
const { pageStack } = await this.send("App.getPageStack");
|
|
@@ -2600,22 +3326,34 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2600
3326
|
}, sendOptions);
|
|
2601
3327
|
return result;
|
|
2602
3328
|
}
|
|
3329
|
+
/** 探测并读取 AppService JS 堆;明确不支持时返回原因,连接故障与无效响应继续抛出。 */
|
|
3330
|
+
async getAppServiceHeapUsage(options = {}) {
|
|
3331
|
+
return await readAppServiceHeapUsage((method, params, sendOptions) => this.send(method, params, sendOptions), options);
|
|
3332
|
+
}
|
|
2603
3333
|
async pageScrollTo(scrollTop) {
|
|
2604
3334
|
await this.callWxMethod("pageScrollTo", {
|
|
2605
3335
|
scrollTop,
|
|
2606
3336
|
duration: 0
|
|
2607
3337
|
});
|
|
2608
3338
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
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;
|
|
2619
3357
|
}
|
|
2620
3358
|
async remote(auto = false) {
|
|
2621
3359
|
const { qrCode } = await this.send("Tool.enableRemoteDebug", { auto });
|
|
@@ -2628,9 +3366,23 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2628
3366
|
});
|
|
2629
3367
|
}
|
|
2630
3368
|
disconnect() {
|
|
2631
|
-
this.
|
|
2632
|
-
this.
|
|
3369
|
+
if (this.disconnecting) return this.disconnecting;
|
|
3370
|
+
this.stopSubscriptions();
|
|
3371
|
+
this.disconnecting = Promise.resolve(this.connection.dispose()).catch((error) => {
|
|
3372
|
+
this.disconnecting = void 0;
|
|
3373
|
+
throw error;
|
|
3374
|
+
});
|
|
3375
|
+
return this.disconnecting;
|
|
2633
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
|
+
};
|
|
2634
3386
|
/** 等待已接收的结构化日志完成 Error 属性检查并发布。 */
|
|
2635
3387
|
async flushConsole() {
|
|
2636
3388
|
await this.structuredConsole.flush();
|
|
@@ -2655,7 +3407,12 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2655
3407
|
let cdpMethod = this.structuredLogs ? "App.CDPEnable" : "App.CDPCommand";
|
|
2656
3408
|
try {
|
|
2657
3409
|
if (this.structuredLogs) {
|
|
2658
|
-
await
|
|
3410
|
+
if (timeout === void 0) await this.cdp.refresh();
|
|
3411
|
+
else {
|
|
3412
|
+
const remaining = timeout - (Date.now() - startedAt);
|
|
3413
|
+
if (remaining <= 0) throw new Error(`Timed out enabling console logging within ${timeout}ms`);
|
|
3414
|
+
await this.cdp.refresh({ timeout: remaining });
|
|
3415
|
+
}
|
|
2659
3416
|
cdpMethod = "App.CDPCommand";
|
|
2660
3417
|
}
|
|
2661
3418
|
await request("App.CDPCommand", {
|
|
@@ -2695,7 +3452,7 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2695
3452
|
if (sdkVersion !== "dev" && cmpVersion(sdkVersion, "2.7.3") < 0) throw new Error(`SDKVersion is currently ${sdkVersion}, while automator(${version}) requires at least version 2.7.3`);
|
|
2696
3453
|
}
|
|
2697
3454
|
/**
|
|
2698
|
-
* @description
|
|
3455
|
+
* @description 等待原生协议返回真实页面标识和路径,避免返回半就绪自动化会话。
|
|
2699
3456
|
*/
|
|
2700
3457
|
async waitForAppReady(timeout = APP_READY_TIMEOUT) {
|
|
2701
3458
|
const startedAt = Date.now();
|
|
@@ -2703,13 +3460,15 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2703
3460
|
while (Date.now() - startedAt < timeout) {
|
|
2704
3461
|
const remainingTimeout = timeout - (Date.now() - startedAt);
|
|
2705
3462
|
try {
|
|
2706
|
-
await this.send("App.
|
|
2707
|
-
return;
|
|
3463
|
+
const page = await this.send("App.getCurrentPage", {}, { timeout: Math.min(APP_READY_PROBE_TIMEOUT, remainingTimeout) });
|
|
3464
|
+
if (page && typeof page === "object" && "pageId" in page && typeof page.pageId === "number" && Number.isSafeInteger(page.pageId) && page.pageId >= 0 && "path" in page && typeof page.path === "string" && page.path.trim()) return;
|
|
3465
|
+
lastError = /* @__PURE__ */ new Error("App.getCurrentPage returned no ready page with a valid pageId and path");
|
|
2708
3466
|
} catch (error) {
|
|
3467
|
+
if (!isCurrentPageProtocolTimeout(error) && !isPageMetaMissingError(error) && !isCurrentFrameTimedOutError(error)) throw error;
|
|
2709
3468
|
lastError = error;
|
|
2710
|
-
const remainingAfterProbe = timeout - (Date.now() - startedAt);
|
|
2711
|
-
if (remainingAfterProbe > 0) await sleep(Math.min(APP_READY_POLL_DELAY, remainingAfterProbe));
|
|
2712
3469
|
}
|
|
3470
|
+
const remainingAfterProbe = timeout - (Date.now() - startedAt);
|
|
3471
|
+
if (remainingAfterProbe > 0) await sleep(Math.min(APP_READY_POLL_DELAY, remainingAfterProbe));
|
|
2713
3472
|
}
|
|
2714
3473
|
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`Timed out waiting App domain readiness after ${timeout}ms`);
|
|
2715
3474
|
}
|
|
@@ -2800,9 +3559,11 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2800
3559
|
logChangeRouteDebug(`poll method=${method} url=${url ?? "<none>"} current=${page?.path ?? "<none>"}`);
|
|
2801
3560
|
if (matchesRouteChange({
|
|
2802
3561
|
currentPath: page?.path,
|
|
3562
|
+
currentQuery: page?.query,
|
|
2803
3563
|
previousPath: currentPage?.path,
|
|
2804
3564
|
requestedUrl: url,
|
|
2805
|
-
expectedRoute
|
|
3565
|
+
expectedRoute,
|
|
3566
|
+
expectedQuery: expectedRouteQuery
|
|
2806
3567
|
})) {
|
|
2807
3568
|
logChangeRouteDebug(`ready method=${method} url=${url ?? "<none>"} current=${page?.path ?? "<none>"}`);
|
|
2808
3569
|
return page;
|
|
@@ -2819,9 +3580,11 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2819
3580
|
logChangeRouteDebug(`stack method=${method} url=${url ?? "<none>"} current=${stackTop.path}`);
|
|
2820
3581
|
if (matchesRouteChange({
|
|
2821
3582
|
currentPath: stackTop.path,
|
|
3583
|
+
currentQuery: stackTop.query,
|
|
2822
3584
|
previousPath: currentPage?.path,
|
|
2823
3585
|
requestedUrl: url,
|
|
2824
|
-
expectedRoute
|
|
3586
|
+
expectedRoute,
|
|
3587
|
+
expectedQuery: expectedRouteQuery
|
|
2825
3588
|
})) {
|
|
2826
3589
|
logChangeRouteDebug(`stack-ready method=${method} url=${url ?? "<none>"} current=${stackTop.path}`);
|
|
2827
3590
|
return stackTop;
|
|
@@ -2917,6 +3680,11 @@ var MiniProgram = class extends EventEmitter {
|
|
|
2917
3680
|
}
|
|
2918
3681
|
throw lastError;
|
|
2919
3682
|
}
|
|
3683
|
+
/** 返回当前连接共享的 CDP 调试入口。 */
|
|
3684
|
+
CDP() {
|
|
3685
|
+
return this.cdp.session;
|
|
3686
|
+
}
|
|
3687
|
+
/** 发送原始自动化协议,沿用连接的请求超时及清理契约。 */
|
|
2920
3688
|
async send(method, params = {}, options) {
|
|
2921
3689
|
return options ? await this.connection.send(method, params, options) : await this.connection.send(method, params);
|
|
2922
3690
|
}
|
|
@@ -3120,7 +3888,7 @@ var SmartappApp = class {
|
|
|
3120
3888
|
return [];
|
|
3121
3889
|
}
|
|
3122
3890
|
async close() {
|
|
3123
|
-
this.miniProgram?.disconnect();
|
|
3891
|
+
await this.miniProgram?.disconnect();
|
|
3124
3892
|
}
|
|
3125
3893
|
};
|
|
3126
3894
|
//#endregion
|
|
@@ -3198,8 +3966,8 @@ var SmartappDevice = class {
|
|
|
3198
3966
|
return false;
|
|
3199
3967
|
}
|
|
3200
3968
|
async close() {
|
|
3201
|
-
await this.driver.close?.();
|
|
3202
|
-
|
|
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");
|
|
3203
3971
|
}
|
|
3204
3972
|
};
|
|
3205
3973
|
//#endregion
|
|
@@ -3364,40 +4132,36 @@ const VERSION_CHECK_TIMEOUT = 3e4;
|
|
|
3364
4132
|
const AUTOMATOR_LAUNCH_RETRIES = 3;
|
|
3365
4133
|
const DEFAULT_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_AUTOMATOR_RUNTIME_PROVIDER";
|
|
3366
4134
|
const LEGACY_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_E2E_RUNTIME_PROVIDER";
|
|
3367
|
-
const EXTENSION_CONTEXT_INVALIDATED_RE = /Extension context invalidated/i;
|
|
3368
|
-
const RETRYABLE_LAUNCH_PORT_RE = /Wait timed out after \d+ ms|Failed connecting to ws:\/\/127\.0\.0\.1:\d+|Failed connecting to devtools websocket endpoint|Failed to launch wechat web devTools, please make sure cliPath is correctly specified/i;
|
|
3369
|
-
const WINDOWS_BATCH_CLI_RE = /\.(?:bat|cmd)$/i;
|
|
3370
4135
|
let localhostListenPatched = false;
|
|
3371
|
-
function isExtensionContextInvalidatedError(error) {
|
|
3372
|
-
return error instanceof Error && EXTENSION_CONTEXT_INVALIDATED_RE.test(error.message);
|
|
3373
|
-
}
|
|
3374
|
-
function isRetryableAutomatorPortLaunchError(error) {
|
|
3375
|
-
return error instanceof Error && RETRYABLE_LAUNCH_PORT_RE.test(error.message);
|
|
3376
|
-
}
|
|
3377
4136
|
function retainPortLeaseUntilSessionClose(miniProgram, portLease) {
|
|
3378
|
-
let released
|
|
4137
|
+
let released;
|
|
3379
4138
|
const release = async () => {
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
4139
|
+
released ??= portLease.release().catch((error) => {
|
|
4140
|
+
released = void 0;
|
|
4141
|
+
throw error;
|
|
4142
|
+
});
|
|
4143
|
+
await released;
|
|
3383
4144
|
};
|
|
3384
4145
|
const target = miniProgram;
|
|
3385
4146
|
const rawClose = target.close;
|
|
3386
4147
|
const rawDisconnect = target.disconnect;
|
|
3387
4148
|
if (typeof rawClose !== "function" && typeof rawDisconnect !== "function") return false;
|
|
3388
|
-
if (typeof rawDisconnect === "function")
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
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
|
+
}
|
|
3395
4162
|
if (typeof rawClose === "function") target.close = async function closeWithPortLeaseRelease() {
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
} finally {
|
|
3399
|
-
await release();
|
|
3400
|
-
}
|
|
4163
|
+
await rawClose.call(this);
|
|
4164
|
+
await release();
|
|
3401
4165
|
};
|
|
3402
4166
|
return true;
|
|
3403
4167
|
}
|
|
@@ -3418,25 +4182,6 @@ function patchNetListenToLoopback() {
|
|
|
3418
4182
|
return rawListen.apply(this, args);
|
|
3419
4183
|
};
|
|
3420
4184
|
}
|
|
3421
|
-
/** IConnectOptions 的类型定义。 */
|
|
3422
|
-
function shouldUseWindowsCommandShell(cliPath) {
|
|
3423
|
-
return isWindows && WINDOWS_BATCH_CLI_RE.test(cliPath);
|
|
3424
|
-
}
|
|
3425
|
-
function escapeWindowsCmdArg(arg) {
|
|
3426
|
-
const escaped = arg.replace(/"/g, "\"\"").replace(/%/g, "%%");
|
|
3427
|
-
return /[\s"&<>^|()]/.test(arg) ? `"${escaped}"` : escaped;
|
|
3428
|
-
}
|
|
3429
|
-
function resolveWindowsBatchSpawn(cliPath, args) {
|
|
3430
|
-
return {
|
|
3431
|
-
file: process.env.ComSpec || "cmd.exe",
|
|
3432
|
-
args: [
|
|
3433
|
-
"/d",
|
|
3434
|
-
"/s",
|
|
3435
|
-
"/c",
|
|
3436
|
-
`"${[cliPath, ...args].map(escapeWindowsCmdArg).join(" ")}"`
|
|
3437
|
-
]
|
|
3438
|
-
};
|
|
3439
|
-
}
|
|
3440
4185
|
function resolveRuntimeProvider(options) {
|
|
3441
4186
|
return options.runtimeProvider || process.env[DEFAULT_RUNTIME_PROVIDER_ENV] || process.env[LEGACY_RUNTIME_PROVIDER_ENV] || "devtools";
|
|
3442
4187
|
}
|
|
@@ -3449,88 +4194,88 @@ var Launcher = class {
|
|
|
3449
4194
|
return await launchHeadlessAutomator({ projectPath: options.projectPath });
|
|
3450
4195
|
}
|
|
3451
4196
|
patchNetListenToLoopback();
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
4197
|
+
return await new OperationLifecycle(options.timeout ?? DEFAULT_TIMEOUT, "automator launch", options.signal).run(async (scope) => {
|
|
4198
|
+
const attempts = options.port ? 1 : AUTOMATOR_LAUNCH_RETRIES;
|
|
4199
|
+
for (let attempt = 1;; attempt += 1) {
|
|
4200
|
+
scope.attempt();
|
|
4201
|
+
try {
|
|
4202
|
+
return await scope.step(() => this.launchWechatDevtools(options, scope));
|
|
4203
|
+
} catch (error) {
|
|
4204
|
+
scope.recordFailure(error);
|
|
4205
|
+
scope.throwIfAborted();
|
|
4206
|
+
if (error instanceof AggregateError || !isRecoverableOperationError(error) || attempt >= attempts) throw error;
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
});
|
|
3461
4210
|
}
|
|
3462
|
-
async launchWechatDevtools(options) {
|
|
3463
|
-
const { cliPath = await this.resolveCliPath(),
|
|
3464
|
-
|
|
3465
|
-
const portLease = await acquireAutomatorPortLease(options.port)
|
|
4211
|
+
async launchWechatDevtools(options, scope) {
|
|
4212
|
+
const { cliPath = await scope.step(() => this.resolveCliPath(), { stage: "resolve-cli" }), projectConfig = {}, ticket = "", cwd = "", account = "", trustProject = false } = options;
|
|
4213
|
+
const { args = [], projectPath } = options;
|
|
4214
|
+
const portLease = await scope.step(() => acquireAutomatorPortLease(options.port), {
|
|
4215
|
+
stage: "port-lease",
|
|
4216
|
+
disposeLate: (lease) => lease.release()
|
|
4217
|
+
});
|
|
4218
|
+
const rawReleaseLease = portLease.release.bind(portLease);
|
|
4219
|
+
let releasedLease;
|
|
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");
|
|
3466
4243
|
let releasePortLeaseOnExit = true;
|
|
4244
|
+
let releaseChild = async () => {};
|
|
4245
|
+
let diagnoseFailure = (error) => error;
|
|
4246
|
+
let failure;
|
|
4247
|
+
let failed = false;
|
|
4248
|
+
let result;
|
|
3467
4249
|
try {
|
|
3468
4250
|
const port = portLease.port;
|
|
3469
4251
|
if (!cliPath) throw new Error("Wechat web devTools not found, please specify cliPath option");
|
|
3470
4252
|
if (isWindows && endWith(cliPath, ".exe")) throw new Error("cliPath is not correct, it's usually named as 'cli' or 'cli.bat'");
|
|
3471
4253
|
if (!projectPath) throw new Error("projectPath is not provided");
|
|
3472
4254
|
const resolvedProjectPath = isRelative(projectPath) ? path.resolve(projectPath) : projectPath;
|
|
3473
|
-
if (
|
|
3474
|
-
if (!
|
|
3475
|
-
|
|
3476
|
-
let processError = null;
|
|
3477
|
-
let processExitCode = null;
|
|
3478
|
-
let processSignal = null;
|
|
3479
|
-
let successfulCliExitSettled = false;
|
|
4255
|
+
if (!await scope.step(() => import("node:fs/promises").then((fs) => fs.access(resolvedProjectPath).then(() => true).catch(() => false)), { stage: "project" })) throw new Error(`Project path ${resolvedProjectPath} doesn't exist`);
|
|
4256
|
+
if (!isEmpty(projectConfig)) await scope.step(() => this.extendProjectConfig(projectConfig, resolvedProjectPath), { stage: "project-config" });
|
|
4257
|
+
scope.throwIfAborted();
|
|
3480
4258
|
let httpFallbackAttempted = false;
|
|
3481
4259
|
let httpFallbackError = null;
|
|
3482
4260
|
let targetPort = port;
|
|
3483
|
-
const
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
const spawnTarget = shouldUseWindowsCommandShell(cliPath) ? resolveWindowsBatchSpawn(cliPath, args) : {
|
|
3487
|
-
file: cliPath,
|
|
3488
|
-
args
|
|
3489
|
-
};
|
|
3490
|
-
const child = spawn(spawnTarget.file, spawnTarget.args, {
|
|
3491
|
-
stdio: [
|
|
3492
|
-
"ignore",
|
|
3493
|
-
"pipe",
|
|
3494
|
-
"pipe"
|
|
3495
|
-
],
|
|
3496
|
-
cwd: cwd || void 0,
|
|
3497
|
-
...shouldUseWindowsCommandShell(cliPath) ? {
|
|
3498
|
-
windowsHide: true,
|
|
3499
|
-
windowsVerbatimArguments: true
|
|
3500
|
-
} : {}
|
|
3501
|
-
});
|
|
3502
|
-
child.on("error", (error) => {
|
|
3503
|
-
processError = error;
|
|
3504
|
-
});
|
|
3505
|
-
child.stdout?.on("data", (chunk) => {
|
|
3506
|
-
cliOutput.push(String(chunk));
|
|
3507
|
-
});
|
|
3508
|
-
child.stderr?.on("data", (chunk) => {
|
|
3509
|
-
cliOutput.push(String(chunk));
|
|
3510
|
-
});
|
|
3511
|
-
child.on("exit", (code, signal) => {
|
|
3512
|
-
processExitCode = code;
|
|
3513
|
-
processSignal = signal;
|
|
3514
|
-
if (code !== 0 || signal) processError = /* @__PURE__ */ new Error(`DevTools cli exited unexpectedly with code ${code ?? "null"}${signal ? ` and signal ${signal}` : ""}`);
|
|
3515
|
-
else successfulCliExitSettled = true;
|
|
3516
|
-
});
|
|
3517
|
-
child.unref();
|
|
3518
|
-
} catch (error) {
|
|
3519
|
-
processError = error;
|
|
3520
|
-
}
|
|
4261
|
+
const cli = spawnWechatCli(cliPath, args, cwd, scope);
|
|
4262
|
+
diagnoseFailure = (error) => attachCliDiagnostics(error, cli, [ticket, account]);
|
|
4263
|
+
releaseChild = cli.release;
|
|
3521
4264
|
let miniProgram = null;
|
|
3522
4265
|
let lastConnectError = null;
|
|
3523
|
-
const
|
|
3524
|
-
|
|
3525
|
-
|
|
4266
|
+
const resolveRemainingTimeout = () => scope.remainingMs();
|
|
4267
|
+
await scope.step(() => waitUntil(async () => {
|
|
4268
|
+
scope.throwIfAborted();
|
|
3526
4269
|
try {
|
|
3527
|
-
if (
|
|
3528
|
-
if (
|
|
3529
|
-
|
|
4270
|
+
if (cli.error) return true;
|
|
4271
|
+
if (cli.success && readWechatLoginState(cli.output) === false) throw Object.assign(/* @__PURE__ */ new Error("DEVTOOLS_LOGIN_REQUIRED: need re-login"), { code: 10 });
|
|
4272
|
+
if (cli.success && !httpFallbackAttempted) {
|
|
4273
|
+
const servicePort = extractWechatDevtoolsServicePort(cli.output);
|
|
3530
4274
|
if (servicePort) {
|
|
3531
4275
|
httpFallbackAttempted = true;
|
|
3532
4276
|
try {
|
|
3533
4277
|
targetPort = await enableAutomatorViaHttp({
|
|
4278
|
+
signal: scope.signal,
|
|
3534
4279
|
account,
|
|
3535
4280
|
autoPort: port,
|
|
3536
4281
|
projectPath: resolvedProjectPath,
|
|
@@ -3546,43 +4291,68 @@ var Launcher = class {
|
|
|
3546
4291
|
}
|
|
3547
4292
|
const connectTimeout = resolveRemainingTimeout();
|
|
3548
4293
|
if (connectTimeout <= 0) return false;
|
|
3549
|
-
const candidate = await
|
|
3550
|
-
|
|
3551
|
-
|
|
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
|
+
}, {
|
|
4303
|
+
stage: "websocket",
|
|
4304
|
+
disposeLate: disconnectConnection
|
|
3552
4305
|
});
|
|
4306
|
+
let releasedCandidate;
|
|
4307
|
+
const releaseCandidate = () => {
|
|
4308
|
+
return releasedCandidate ??= disconnectConnection(candidate).catch((error) => {
|
|
4309
|
+
releasedCandidate = void 0;
|
|
4310
|
+
throw error;
|
|
4311
|
+
});
|
|
4312
|
+
};
|
|
4313
|
+
const disownCandidate = scope.own(releaseCandidate, "websocket");
|
|
3553
4314
|
try {
|
|
3554
4315
|
const checkVersionTimeout = resolveRemainingTimeout();
|
|
3555
4316
|
if (checkVersionTimeout <= 0) {
|
|
3556
|
-
|
|
4317
|
+
await scope.step(releaseCandidate);
|
|
3557
4318
|
return false;
|
|
3558
4319
|
}
|
|
3559
|
-
await candidate.checkVersion(Math.min(VERSION_CHECK_TIMEOUT, checkVersionTimeout));
|
|
4320
|
+
await scope.step(() => candidate.checkVersion(Math.min(VERSION_CHECK_TIMEOUT, checkVersionTimeout)), { stage: "version" });
|
|
3560
4321
|
if (typeof candidate.waitForAppReady === "function") {
|
|
3561
4322
|
const appReadyTimeout = resolveRemainingTimeout();
|
|
3562
4323
|
if (appReadyTimeout <= 0) {
|
|
3563
|
-
|
|
4324
|
+
await scope.step(releaseCandidate);
|
|
3564
4325
|
return false;
|
|
3565
4326
|
}
|
|
3566
|
-
await candidate.waitForAppReady(appReadyTimeout);
|
|
4327
|
+
await scope.step(() => candidate.waitForAppReady(appReadyTimeout), { stage: "app-ready" });
|
|
3567
4328
|
}
|
|
3568
4329
|
} catch (error) {
|
|
3569
|
-
|
|
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
|
+
}
|
|
4336
|
+
disownCandidate();
|
|
3570
4337
|
lastConnectError = error;
|
|
3571
|
-
if (
|
|
4338
|
+
if (isRecoverableOperationError(error)) return false;
|
|
3572
4339
|
throw error;
|
|
3573
4340
|
}
|
|
3574
4341
|
miniProgram = candidate;
|
|
3575
4342
|
return true;
|
|
3576
4343
|
} catch (error) {
|
|
3577
4344
|
lastConnectError = error;
|
|
4345
|
+
scope.recordFailure(error);
|
|
4346
|
+
scope.throwIfAborted();
|
|
4347
|
+
if (error instanceof AggregateError || !isRecoverableOperationError(error)) throw error;
|
|
3578
4348
|
return false;
|
|
3579
4349
|
}
|
|
3580
|
-
},
|
|
4350
|
+
}, scope.remainingMs(), 1e3, scope.signal), { stage: "readiness" });
|
|
3581
4351
|
if (!miniProgram) {
|
|
3582
4352
|
if (httpFallbackError) throw httpFallbackError;
|
|
3583
|
-
if (
|
|
4353
|
+
if (cli.error) throw new Error("Failed to launch wechat web devTools, please make sure cliPath is correctly specified", { cause: cli.error });
|
|
3584
4354
|
if (lastConnectError) throw lastConnectError;
|
|
3585
|
-
if (
|
|
4355
|
+
if (cli.exited) throw new Error("Failed to launch wechat web devTools, please make sure http port is open");
|
|
3586
4356
|
throw new Error("Failed connecting to devtools websocket endpoint");
|
|
3587
4357
|
}
|
|
3588
4358
|
const resolvedMiniProgram = miniProgram;
|
|
@@ -3592,33 +4362,47 @@ var Launcher = class {
|
|
|
3592
4362
|
wsEndpoint: `ws://127.0.0.1:${targetPort}`
|
|
3593
4363
|
});
|
|
3594
4364
|
releasePortLeaseOnExit = !retainPortLeaseUntilSessionClose(resolvedMiniProgram, portLease);
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
}
|
|
3598
|
-
|
|
4365
|
+
if (!releasePortLeaseOnExit) disownLease();
|
|
4366
|
+
result = resolvedMiniProgram;
|
|
4367
|
+
} catch (error) {
|
|
4368
|
+
failed = true;
|
|
4369
|
+
failure = diagnoseFailure(error);
|
|
3599
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);
|
|
4379
|
+
disownLease();
|
|
4380
|
+
} catch (error) {
|
|
4381
|
+
if ((!failed || error !== failure) && !cleanupErrors.includes(error)) cleanupErrors.push(error);
|
|
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;
|
|
3600
4389
|
}
|
|
3601
4390
|
async connect(options) {
|
|
3602
4391
|
if (normalizePlatform(options.platform) === "swan") return await new SwanLauncher().connect(options);
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
4392
|
+
return await new OperationLifecycle(options.timeout ?? DEFAULT_TIMEOUT, "automator connect", options.signal).run(async (scope) => {
|
|
4393
|
+
scope.attempt();
|
|
4394
|
+
const miniProgram = await scope.step(() => this.connectTool({
|
|
4395
|
+
...options,
|
|
4396
|
+
signal: scope.signal,
|
|
4397
|
+
timeout: scope.remainingMs()
|
|
4398
|
+
}), {
|
|
4399
|
+
stage: "websocket",
|
|
4400
|
+
disposeLate: (program) => program.disconnect()
|
|
4401
|
+
});
|
|
4402
|
+
scope.own(() => miniProgram.disconnect(), "websocket");
|
|
4403
|
+
await scope.step(() => miniProgram.checkVersion(scope.remainingMs()), { stage: "version" });
|
|
3613
4404
|
return miniProgram;
|
|
3614
|
-
}
|
|
3615
|
-
try {
|
|
3616
|
-
miniProgram.disconnect();
|
|
3617
|
-
} catch (cleanupError) {
|
|
3618
|
-
throw new AggregateError([error, cleanupError], "Automator connection and cleanup both failed", { cause: error });
|
|
3619
|
-
}
|
|
3620
|
-
throw error;
|
|
3621
|
-
}
|
|
4405
|
+
});
|
|
3622
4406
|
}
|
|
3623
4407
|
async extendProjectConfig(projectConfig, projectPath) {
|
|
3624
4408
|
const projectConfigPath = path.resolve(projectPath, "project.config.json");
|
|
@@ -3630,9 +4414,9 @@ var Launcher = class {
|
|
|
3630
4414
|
}
|
|
3631
4415
|
async connectTool(options) {
|
|
3632
4416
|
try {
|
|
3633
|
-
return new MiniProgram(await Connection.create(options.wsEndpoint, options.timeout));
|
|
3634
|
-
} catch {
|
|
3635
|
-
throw new Error(`Failed connecting to ${options.wsEndpoint}, check if target project window is opened with automation enabled
|
|
4417
|
+
return new MiniProgram(await Connection.create(options.wsEndpoint, options.timeout, options.signal));
|
|
4418
|
+
} catch (cause) {
|
|
4419
|
+
throw new Error(`Failed connecting to ${options.wsEndpoint}, check if target project window is opened with automation enabled`, { cause });
|
|
3636
4420
|
}
|
|
3637
4421
|
}
|
|
3638
4422
|
async resolveCliPath() {
|
|
@@ -3674,4 +4458,4 @@ var Automator = class {
|
|
|
3674
4458
|
*/
|
|
3675
4459
|
const SmartappAutomator = smartappAutomatorRuntime;
|
|
3676
4460
|
//#endregion
|
|
3677
|
-
export { Automator, Connection, ContextElement, CustomElement, Element, InputElement, Launcher, MiniProgram, MovableViewElement, Native, Page, ScrollViewElement, SliderElement, SmartappAutomator, SwiperElement, SwitchElement, TextareaElement, Transport, decodeQrCode, extractPluginId, isPluginPath, normalizePlatform, printQrCode };
|
|
4461
|
+
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 };
|