weapp-ide-cli 5.4.0 → 5.4.2
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/{automator-session-RrZjw3X5.js → automator-session-CvStbY9I.js} +363 -19
- package/dist/{cli-_qia2--1.js → cli-C9KfpthX.js} +8 -1076
- package/dist/cli.js +2 -2
- package/dist/commands-B4HFVW-Q.js +2 -0
- package/dist/commands-Js_IjVxE.js +1142 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -5
- package/dist/run-mcp-CL909GLy.js +2 -0
- package/dist/{run-mcp-By4qRg8l.js → run-mcp-DvHYYX0m.js} +1 -1
- package/package.json +3 -3
- package/dist/commands-D_j5_0eE.js +0 -2
- package/dist/commands-fWDebiQq.js +0 -335
- package/dist/run-mcp-CTk4KA3y.js +0 -2
|
@@ -0,0 +1,1142 @@
|
|
|
1
|
+
import { $ as readCustomConfig, G as operatingSystemName, I as bootstrapWechatDevtoolsSettings, K as colors, L as detectWechatDevtoolsServicePort, R as resolveCliPath, W as isOperatingSystemSupported, X as createCustomConfig, d as createWechatIdeLoginRequiredExitError, h as isWechatIdeLoginRequiredError, n as closeSharedMiniProgram, nt as defaultCustomConfigFilePath, o as withMiniProgram, q as logger_default, rt as resolvePath, s as runRetryableCommand, v as promptWechatIdeLoginRetry, w as i18nText, x as runWithSuspendedSharedInput } from "./automator-session-CvStbY9I.js";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fs as fs$1 } from "@weapp-core/shared/fs";
|
|
6
|
+
import process, { stdin, stdout } from "node:process";
|
|
7
|
+
import { PNG } from "pngjs";
|
|
8
|
+
import { createInterface } from "node:readline/promises";
|
|
9
|
+
//#region src/cli/automator-argv.ts
|
|
10
|
+
function parsePositiveInt(raw) {
|
|
11
|
+
const value = Number.parseInt(raw, 10);
|
|
12
|
+
if (!Number.isFinite(value) || value <= 0) return;
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function takesValue(optionName) {
|
|
16
|
+
return optionName === "-p" || optionName === "--project" || optionName === "-t" || optionName === "--timeout" || optionName === "--port" || optionName === "--session-id" || optionName === "-o" || optionName === "--output" || optionName === "--page" || optionName === "--login-retry" || optionName === "--login-retry-timeout" || optionName === "--lang" || optionName === "--platform" || optionName === "--runtime-url" || optionName === "--qr-output" || optionName === "-r" || optionName === "--result-output" || optionName === "--info-output" || optionName === "-i";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @description 解析 automator 命令通用参数与位置参数。
|
|
20
|
+
*/
|
|
21
|
+
function parseAutomatorArgs(argv) {
|
|
22
|
+
const positionals = [];
|
|
23
|
+
let projectPath = process.cwd();
|
|
24
|
+
let timeout;
|
|
25
|
+
let port;
|
|
26
|
+
let sessionId;
|
|
27
|
+
let preferOpenedSession;
|
|
28
|
+
let json = false;
|
|
29
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
30
|
+
const token = argv[index];
|
|
31
|
+
if (!token) continue;
|
|
32
|
+
if (token === "-p" || token === "--project") {
|
|
33
|
+
const value = argv[index + 1];
|
|
34
|
+
if (typeof value === "string" && !value.startsWith("-")) {
|
|
35
|
+
projectPath = value;
|
|
36
|
+
index += 1;
|
|
37
|
+
} else projectPath = process.cwd();
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (token.startsWith("--project=")) {
|
|
41
|
+
projectPath = token.slice(10) || process.cwd();
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (token === "-t" || token === "--timeout") {
|
|
45
|
+
const value = argv[index + 1];
|
|
46
|
+
if (typeof value === "string") {
|
|
47
|
+
timeout = parsePositiveInt(value);
|
|
48
|
+
index += 1;
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (token.startsWith("--timeout=")) {
|
|
53
|
+
timeout = parsePositiveInt(token.slice(10));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (token === "--port") {
|
|
57
|
+
const value = argv[index + 1];
|
|
58
|
+
if (typeof value === "string") {
|
|
59
|
+
port = parsePositiveInt(value);
|
|
60
|
+
index += 1;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (token.startsWith("--port=")) {
|
|
65
|
+
port = parsePositiveInt(token.slice(7));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (token === "--session-id") {
|
|
69
|
+
const value = argv[index + 1];
|
|
70
|
+
if (typeof value === "string" && !value.startsWith("-")) {
|
|
71
|
+
sessionId = value.trim() || void 0;
|
|
72
|
+
index += 1;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (token.startsWith("--session-id=")) {
|
|
77
|
+
sessionId = token.slice(13).trim() || void 0;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (token === "--json") {
|
|
81
|
+
json = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (token === "--no-runtime-service") {
|
|
85
|
+
preferOpenedSession = false;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (token.startsWith("-")) {
|
|
89
|
+
if (takesValue(token.includes("=") ? token.slice(0, token.indexOf("=")) : token) && !token.includes("=")) {
|
|
90
|
+
const value = argv[index + 1];
|
|
91
|
+
if (typeof value === "string" && !value.startsWith("-")) index += 1;
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
positionals.push(token);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
projectPath,
|
|
99
|
+
...timeout ? { timeout } : {},
|
|
100
|
+
...port ? { port } : {},
|
|
101
|
+
...sessionId ? { sessionId } : {},
|
|
102
|
+
...preferOpenedSession === false ? { preferOpenedSession } : {},
|
|
103
|
+
json,
|
|
104
|
+
positionals
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* @description 读取选项值,支持多个别名,以及 --option value 与 --option=value。
|
|
109
|
+
*/
|
|
110
|
+
function readOptionValue(argv, ...optionNames) {
|
|
111
|
+
for (const optionName of optionNames) {
|
|
112
|
+
const optionWithEqual = `${optionName}=`;
|
|
113
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
114
|
+
const token = argv[index];
|
|
115
|
+
if (!token) continue;
|
|
116
|
+
if (token === optionName) {
|
|
117
|
+
const value = argv[index + 1];
|
|
118
|
+
if (typeof value !== "string") return;
|
|
119
|
+
return value.trim();
|
|
120
|
+
}
|
|
121
|
+
if (token.startsWith(optionWithEqual)) return token.slice(optionWithEqual.length).trim();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* @description 读取布尔选项,支持裸 flag、--flag=true/false 与 --flag true/false。
|
|
127
|
+
*/
|
|
128
|
+
function readBooleanOption(argv, ...optionNames) {
|
|
129
|
+
for (const optionName of optionNames) {
|
|
130
|
+
const optionWithEqual = `${optionName}=`;
|
|
131
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
132
|
+
const token = argv[index];
|
|
133
|
+
if (!token) continue;
|
|
134
|
+
if (token === optionName) {
|
|
135
|
+
const nextToken = argv[index + 1];
|
|
136
|
+
if (nextToken === "true") return true;
|
|
137
|
+
if (nextToken === "false") return false;
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
if (token.startsWith(optionWithEqual)) {
|
|
141
|
+
const rawValue = token.slice(optionWithEqual.length).trim().toLowerCase();
|
|
142
|
+
if (rawValue === "true") return true;
|
|
143
|
+
if (rawValue === "false") return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* @description 删除参数中的指定选项(同时支持 --opt value 与 --opt=value)。
|
|
150
|
+
*/
|
|
151
|
+
function removeOption(argv, optionName) {
|
|
152
|
+
const optionWithEqual = `${optionName}=`;
|
|
153
|
+
const nextArgv = [];
|
|
154
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
155
|
+
const token = argv[index];
|
|
156
|
+
if (!token) continue;
|
|
157
|
+
if (token === optionName) {
|
|
158
|
+
const nextToken = argv[index + 1];
|
|
159
|
+
if (takesValue(optionName) && typeof nextToken === "string" && !nextToken.startsWith("-")) index += 1;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (token.startsWith(optionWithEqual)) continue;
|
|
163
|
+
nextArgv.push(token);
|
|
164
|
+
}
|
|
165
|
+
return nextArgv;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/cli/fullPageScreenshot.ts
|
|
169
|
+
function decodeScreenshotBuffer(raw) {
|
|
170
|
+
const buffer = typeof raw === "string" ? Buffer.from(raw, "base64") : Buffer.from(raw);
|
|
171
|
+
if (buffer.length === 0) throw new Error("Failed to capture screenshot");
|
|
172
|
+
return buffer;
|
|
173
|
+
}
|
|
174
|
+
function toPositiveNumber(value) {
|
|
175
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
176
|
+
}
|
|
177
|
+
function createScrollPositions(totalHeight, viewportHeight) {
|
|
178
|
+
if (totalHeight <= viewportHeight) return [0];
|
|
179
|
+
const positions = [];
|
|
180
|
+
const lastStart = Math.max(totalHeight - viewportHeight, 0);
|
|
181
|
+
for (let scrollTop = 0; scrollTop < lastStart; scrollTop += viewportHeight) positions.push(scrollTop);
|
|
182
|
+
if (positions[positions.length - 1] !== lastStart) positions.push(lastStart);
|
|
183
|
+
return positions;
|
|
184
|
+
}
|
|
185
|
+
function cropPngRows(source, startRow, rowCount) {
|
|
186
|
+
const cropped = new PNG({
|
|
187
|
+
width: source.width,
|
|
188
|
+
height: rowCount
|
|
189
|
+
});
|
|
190
|
+
const bytesPerRow = source.width * 4;
|
|
191
|
+
for (let row = 0; row < rowCount; row += 1) {
|
|
192
|
+
const sourceStart = (startRow + row) * bytesPerRow;
|
|
193
|
+
const sourceEnd = sourceStart + bytesPerRow;
|
|
194
|
+
cropped.data.set(source.data.subarray(sourceStart, sourceEnd), row * bytesPerRow);
|
|
195
|
+
}
|
|
196
|
+
return cropped;
|
|
197
|
+
}
|
|
198
|
+
async function restoreScrollPosition(miniProgram, page, scrollTop) {
|
|
199
|
+
await miniProgram.pageScrollTo(scrollTop);
|
|
200
|
+
await page.waitFor(150);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* @description 通过多次滚动和拼接生成整页长截图。
|
|
204
|
+
*/
|
|
205
|
+
async function captureFullPageScreenshotBuffer(options) {
|
|
206
|
+
const { miniProgram, timeoutMs, runWithTimeout, screenshotTimeoutMessage } = options;
|
|
207
|
+
const page = await miniProgram.currentPage();
|
|
208
|
+
const pageSize = await page.size();
|
|
209
|
+
const systemInfo = await miniProgram.systemInfo();
|
|
210
|
+
const initialScrollTop = typeof page.scrollTop === "function" ? await page.scrollTop() : 0;
|
|
211
|
+
const pageHeight = toPositiveNumber(pageSize.height);
|
|
212
|
+
const viewportHeight = toPositiveNumber(systemInfo.windowHeight);
|
|
213
|
+
if (!pageHeight || !viewportHeight) return decodeScreenshotBuffer(await runWithTimeout(miniProgram.screenshot(), timeoutMs, screenshotTimeoutMessage, "DEVTOOLS_SCREENSHOT_TIMEOUT"));
|
|
214
|
+
const segments = [];
|
|
215
|
+
const positions = createScrollPositions(pageHeight, viewportHeight);
|
|
216
|
+
let coveredUntil = 0;
|
|
217
|
+
let scale = 1;
|
|
218
|
+
try {
|
|
219
|
+
for (const scrollTop of positions) {
|
|
220
|
+
await miniProgram.pageScrollTo(scrollTop);
|
|
221
|
+
await page.waitFor(150);
|
|
222
|
+
const rawScreenshot = await runWithTimeout(miniProgram.screenshot(), timeoutMs, screenshotTimeoutMessage, "DEVTOOLS_SCREENSHOT_TIMEOUT");
|
|
223
|
+
const png = PNG.sync.read(decodeScreenshotBuffer(rawScreenshot));
|
|
224
|
+
if (viewportHeight > 0) scale = png.height / viewportHeight;
|
|
225
|
+
const visibleEnd = Math.min(scrollTop + viewportHeight, pageHeight);
|
|
226
|
+
const cropTopCss = Math.max(coveredUntil - scrollTop, 0);
|
|
227
|
+
const segmentHeightCss = Math.max(visibleEnd - scrollTop - cropTopCss, 0);
|
|
228
|
+
if (segmentHeightCss <= 0) continue;
|
|
229
|
+
const cropTopRows = Math.min(Math.max(Math.round(cropTopCss * scale), 0), png.height);
|
|
230
|
+
const segmentRows = Math.min(Math.max(Math.round(segmentHeightCss * scale), 1), png.height - cropTopRows);
|
|
231
|
+
segments.push(cropPngRows(png, cropTopRows, segmentRows));
|
|
232
|
+
coveredUntil = visibleEnd;
|
|
233
|
+
}
|
|
234
|
+
} finally {
|
|
235
|
+
await restoreScrollPosition(miniProgram, page, initialScrollTop);
|
|
236
|
+
}
|
|
237
|
+
if (segments.length === 0) throw new Error("Failed to capture screenshot");
|
|
238
|
+
const width = segments[0].width;
|
|
239
|
+
const merged = new PNG({
|
|
240
|
+
width,
|
|
241
|
+
height: segments.reduce((sum, segment) => sum + segment.height, 0)
|
|
242
|
+
});
|
|
243
|
+
const bytesPerRow = width * 4;
|
|
244
|
+
let offsetY = 0;
|
|
245
|
+
for (const segment of segments) {
|
|
246
|
+
if (segment.width !== width) throw new Error("Full-page screenshots have inconsistent widths");
|
|
247
|
+
for (let row = 0; row < segment.height; row += 1) {
|
|
248
|
+
const sourceStart = row * bytesPerRow;
|
|
249
|
+
const sourceEnd = sourceStart + bytesPerRow;
|
|
250
|
+
merged.data.set(segment.data.subarray(sourceStart, sourceEnd), (offsetY + row) * bytesPerRow);
|
|
251
|
+
}
|
|
252
|
+
offsetY += segment.height;
|
|
253
|
+
}
|
|
254
|
+
return PNG.sync.write(merged);
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/cli/http.ts
|
|
258
|
+
const DEFAULT_WECHAT_DEVTOOLS_HTTP_PORT = 9420;
|
|
259
|
+
const ENGINE_BUILD_NOT_START = "NOT_START";
|
|
260
|
+
const ENGINE_BUILD_OPEN_PROJECT = "OPEN_PROJECT";
|
|
261
|
+
const ENGINE_BUILD_BUILDING = "BUILDING";
|
|
262
|
+
const ENGINE_BUILD_END = "END";
|
|
263
|
+
const ENGINE_BUILD_ERROR = "ERROR";
|
|
264
|
+
function createWechatDevtoolsHttpError(message, code) {
|
|
265
|
+
const error = new Error(message);
|
|
266
|
+
error.code = code;
|
|
267
|
+
return error;
|
|
268
|
+
}
|
|
269
|
+
async function resolveWechatDevtoolsHttpPort(port) {
|
|
270
|
+
if (typeof port === "number" && Number.isInteger(port) && port > 0) return port;
|
|
271
|
+
const detected = await detectWechatDevtoolsServicePort();
|
|
272
|
+
if (detected.servicePortEnabled === false) throw createWechatDevtoolsHttpError("WECHAT_DEVTOOLS_SERVICE_PORT_DISABLED", "WECHAT_DEVTOOLS_SERVICE_PORT_DISABLED");
|
|
273
|
+
return detected.servicePort ?? DEFAULT_WECHAT_DEVTOOLS_HTTP_PORT;
|
|
274
|
+
}
|
|
275
|
+
function createWechatDevtoolsHttpUrl(port, pathname, query) {
|
|
276
|
+
const url = new URL(`http://127.0.0.1:${port}${pathname}`);
|
|
277
|
+
for (const [key, value] of Object.entries(query)) url.searchParams.set(key, value);
|
|
278
|
+
return url;
|
|
279
|
+
}
|
|
280
|
+
function parseWechatDevtoolsEngineBuildResult(body) {
|
|
281
|
+
try {
|
|
282
|
+
return JSON.parse(body);
|
|
283
|
+
} catch {
|
|
284
|
+
return {};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function requestWechatDevtoolsHttp(pathname, query, options = {}) {
|
|
288
|
+
const url = createWechatDevtoolsHttpUrl(await resolveWechatDevtoolsHttpPort(options.port), pathname, query);
|
|
289
|
+
const controller = new AbortController();
|
|
290
|
+
const timeout = setTimeout(() => {
|
|
291
|
+
controller.abort();
|
|
292
|
+
}, options.timeoutMs ?? 1e4);
|
|
293
|
+
try {
|
|
294
|
+
const response = await fetch(url, {
|
|
295
|
+
method: "GET",
|
|
296
|
+
signal: controller.signal
|
|
297
|
+
});
|
|
298
|
+
const body = await response.text();
|
|
299
|
+
if (!response.ok) throw createWechatDevtoolsHttpError(body || `HTTP ${response.status}`, "WECHAT_DEVTOOLS_HTTP_REQUEST_FAILED");
|
|
300
|
+
return body;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (error instanceof Error && error.name === "AbortError") throw createWechatDevtoolsHttpError("WECHAT_DEVTOOLS_HTTP_TIMEOUT", "WECHAT_DEVTOOLS_HTTP_TIMEOUT");
|
|
303
|
+
throw error;
|
|
304
|
+
} finally {
|
|
305
|
+
clearTimeout(timeout);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* @description 通过微信开发者工具 HTTP 服务端口重新打开项目;若项目已打开,开发者工具会刷新当前项目。
|
|
310
|
+
*/
|
|
311
|
+
async function openWechatIdeProjectByHttp(projectPath, options = {}) {
|
|
312
|
+
return await requestWechatDevtoolsHttp("/open", { projectpath: path.resolve(projectPath) }, options);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* @description 通过微信开发者工具 HTTP 服务端口重置当前项目的 fileutils 状态。
|
|
316
|
+
*/
|
|
317
|
+
async function resetWechatIdeFileUtilsByHttp(projectPath, options = {}) {
|
|
318
|
+
return await requestWechatDevtoolsHttp("/v2/resetfileutils", { project: path.resolve(projectPath) }, options);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* @description 通过微信开发者工具 HTTP 服务端口触发 engine build。
|
|
322
|
+
*/
|
|
323
|
+
async function startWechatIdeEngineBuildByHttp(projectPath, options = {}) {
|
|
324
|
+
return { body: await requestWechatDevtoolsHttp("/engine/build", { projectpath: path.resolve(projectPath) }, options) };
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* @description 轮询微信开发者工具 engine build 状态。
|
|
328
|
+
*/
|
|
329
|
+
async function pollWechatIdeEngineBuildResultByHttp(options = {}) {
|
|
330
|
+
const body = await requestWechatDevtoolsHttp("/engine/buildResult/", {}, options);
|
|
331
|
+
const parsed = parseWechatDevtoolsEngineBuildResult(body);
|
|
332
|
+
const status = parsed.status;
|
|
333
|
+
return {
|
|
334
|
+
body,
|
|
335
|
+
done: status === ENGINE_BUILD_END,
|
|
336
|
+
failed: status === ENGINE_BUILD_ERROR,
|
|
337
|
+
msg: parsed.msg,
|
|
338
|
+
status
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const WECHAT_DEVTOOLS_ENGINE_BUILD_STATUSES = {
|
|
342
|
+
BUILDING: ENGINE_BUILD_BUILDING,
|
|
343
|
+
END: ENGINE_BUILD_END,
|
|
344
|
+
ERROR: ENGINE_BUILD_ERROR,
|
|
345
|
+
NOT_START: ENGINE_BUILD_NOT_START,
|
|
346
|
+
OPEN_PROJECT: ENGINE_BUILD_OPEN_PROJECT
|
|
347
|
+
};
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/cli/prompt.ts
|
|
350
|
+
/**
|
|
351
|
+
* @description 交互式提示并保存 CLI 路径
|
|
352
|
+
*/
|
|
353
|
+
async function promptForCliPath() {
|
|
354
|
+
return await runWithSuspendedSharedInput(async () => {
|
|
355
|
+
const rl = createInterface({
|
|
356
|
+
input: stdin,
|
|
357
|
+
output: stdout
|
|
358
|
+
});
|
|
359
|
+
try {
|
|
360
|
+
logger_default.info(`请设置 ${colors.bold("微信web开发者工具 CLI")} 的路径`);
|
|
361
|
+
logger_default.info("提示:命令行工具默认所在位置:");
|
|
362
|
+
logger_default.info(`- MacOS: ${colors.green("<安装路径>/Contents/MacOS/cli")}`);
|
|
363
|
+
logger_default.info(`- Windows: ${colors.green("<安装路径>/cli.bat")}`);
|
|
364
|
+
logger_default.info(`- Linux: ${colors.green("<安装路径>/files/bin/bin/wechat-devtools-cli")}`);
|
|
365
|
+
const cliPath = (await rl.question("请输入微信web开发者工具 CLI 路径:")).trim();
|
|
366
|
+
if (!cliPath) {
|
|
367
|
+
logger_default.error("路径不能为空,已取消本次配置。");
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
const normalizedPath = await createCustomConfig({ cliPath });
|
|
372
|
+
logger_default.info(`全局配置存储位置:${colors.green(defaultCustomConfigFilePath)}`);
|
|
373
|
+
if (!await fs$1.pathExists(normalizedPath)) logger_default.warn("在当前路径未找到微信web开发者命令行工具,请确认路径是否正确。");
|
|
374
|
+
return normalizedPath;
|
|
375
|
+
} catch (error) {
|
|
376
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
377
|
+
logger_default.error(`保存配置失败:${reason}`);
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
} finally {
|
|
381
|
+
rl.close();
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/utils/argv.ts
|
|
387
|
+
function ensurePathArgument(argv, optionIndex) {
|
|
388
|
+
const paramIdx = optionIndex + 1;
|
|
389
|
+
const param = argv[paramIdx];
|
|
390
|
+
if (param && !param.startsWith("-")) argv[paramIdx] = resolvePath(param);
|
|
391
|
+
else argv.splice(paramIdx, 0, process.cwd());
|
|
392
|
+
return argv;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* @description 依次应用 argv 处理函数(不修改原始 argv)
|
|
396
|
+
*/
|
|
397
|
+
function transformArgv(argv, transforms) {
|
|
398
|
+
return transforms.reduce((current, transform) => transform(current), [...argv]);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* @description 创建参数别名转换器
|
|
402
|
+
*/
|
|
403
|
+
function createAlias(entry) {
|
|
404
|
+
return (input) => {
|
|
405
|
+
const argv = [...input];
|
|
406
|
+
let optionIndex = argv.indexOf(entry.find);
|
|
407
|
+
if (optionIndex > -1) argv.splice(optionIndex, 1, entry.replacement);
|
|
408
|
+
else optionIndex = argv.indexOf(entry.replacement);
|
|
409
|
+
if (optionIndex === -1) return argv;
|
|
410
|
+
return ensurePathArgument(argv, optionIndex);
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* @description 创建路径参数兼容转换器(补全或规范化路径)
|
|
415
|
+
*/
|
|
416
|
+
function createPathCompat(option) {
|
|
417
|
+
return (input) => {
|
|
418
|
+
const argv = [...input];
|
|
419
|
+
const optionIndex = argv.indexOf(option);
|
|
420
|
+
if (optionIndex === -1) return argv;
|
|
421
|
+
return ensurePathArgument(argv, optionIndex);
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/utils/exec.ts
|
|
426
|
+
/**
|
|
427
|
+
* @description 执行 CLI 命令并透传输出
|
|
428
|
+
*/
|
|
429
|
+
async function execute(cliPath, argv, options = {}) {
|
|
430
|
+
const { pipeStdout = true, pipeStderr = true } = options;
|
|
431
|
+
const { execa } = await import("execa");
|
|
432
|
+
const task = execa(cliPath, argv);
|
|
433
|
+
if (pipeStdout) task?.stdout?.pipe(process.stdout);
|
|
434
|
+
if (pipeStderr) task?.stderr?.pipe(process.stderr);
|
|
435
|
+
return await task;
|
|
436
|
+
}
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/cli/run-login-config.ts
|
|
439
|
+
function normalizeLoginRetryMode(value, nonInteractive) {
|
|
440
|
+
if (value && value !== "never" && value !== "once" && value !== "always") throw new Error(i18nText(`不支持的 --login-retry 值: ${value}(仅支持 never/once/always)`, `Invalid --login-retry value: ${value} (supported: never/once/always)`));
|
|
441
|
+
if (value === "never" || value === "once" || value === "always") return value;
|
|
442
|
+
return nonInteractive ? "never" : "always";
|
|
443
|
+
}
|
|
444
|
+
function normalizeLoginRetryTimeout(value) {
|
|
445
|
+
if (!value) return 3e4;
|
|
446
|
+
const parsed = Number.parseInt(value, 10);
|
|
447
|
+
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(i18nText(`无效的 --login-retry-timeout 值: ${value}(必须为正整数)`, `Invalid --login-retry-timeout value: ${value} (must be a positive integer)`));
|
|
448
|
+
return parsed;
|
|
449
|
+
}
|
|
450
|
+
function isStdinInteractive() {
|
|
451
|
+
return Boolean(process.stdin && process.stdin.isTTY);
|
|
452
|
+
}
|
|
453
|
+
function stripLoginRetryControlFlags(argv) {
|
|
454
|
+
let next = removeOption(argv, "--login-retry");
|
|
455
|
+
next = removeOption(next, "--login-retry-timeout");
|
|
456
|
+
next = removeOption(next, "--non-interactive");
|
|
457
|
+
return next;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* @description 解析登录重试相关控制参数,并产出实际执行微信 CLI 所需的运行时参数。
|
|
461
|
+
*/
|
|
462
|
+
function resolveLoginRetryConfig(argv) {
|
|
463
|
+
const nonInteractiveFlag = argv.includes("--non-interactive");
|
|
464
|
+
const ciMode = process.env.CI === "true";
|
|
465
|
+
const nonTtyStdin = !isStdinInteractive();
|
|
466
|
+
const nonInteractive = nonInteractiveFlag || ciMode || nonTtyStdin;
|
|
467
|
+
const retryModeRaw = readOptionValue(argv, "--login-retry")?.toLowerCase();
|
|
468
|
+
return {
|
|
469
|
+
nonInteractive,
|
|
470
|
+
retryMode: normalizeLoginRetryMode(retryModeRaw, nonInteractive),
|
|
471
|
+
retryTimeoutMs: normalizeLoginRetryTimeout(readOptionValue(argv, "--login-retry-timeout")),
|
|
472
|
+
runtimeArgv: stripLoginRetryControlFlags(argv)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
//#endregion
|
|
476
|
+
//#region src/cli/run-login.ts
|
|
477
|
+
function unwrapWechatCliExecutionError(result) {
|
|
478
|
+
return result.kind === "retryable" ? result.error : result.value;
|
|
479
|
+
}
|
|
480
|
+
async function promptLoginRetry(errorLike, options, retryCount) {
|
|
481
|
+
const { nonInteractive, retryMode, retryTimeoutMs } = options;
|
|
482
|
+
if (nonInteractive) {
|
|
483
|
+
logger_default.error(i18nText("当前为非交互模式,检测到登录失效后直接失败。", "Non-interactive mode enabled, failing immediately on login expiration."));
|
|
484
|
+
return "cancel";
|
|
485
|
+
}
|
|
486
|
+
if (!(retryMode === "always" || retryMode === "once" && retryCount < 1)) {
|
|
487
|
+
await promptWechatIdeLoginRetry({
|
|
488
|
+
allowRetry: false,
|
|
489
|
+
error: errorLike,
|
|
490
|
+
logger: logger_default,
|
|
491
|
+
promptOpenIdeLogin: true,
|
|
492
|
+
retryTimeoutMs
|
|
493
|
+
});
|
|
494
|
+
logger_default.info(i18nText("当前重试策略不允许继续重试。", "Current retry policy does not allow further retries."));
|
|
495
|
+
return "cancel";
|
|
496
|
+
}
|
|
497
|
+
return await promptWechatIdeLoginRetry({
|
|
498
|
+
error: errorLike,
|
|
499
|
+
logger: logger_default,
|
|
500
|
+
promptOpenIdeLogin: true,
|
|
501
|
+
retryTimeoutMs
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
function flushExecutionOutput(result) {
|
|
505
|
+
if (!result || typeof result !== "object") return;
|
|
506
|
+
const candidate = result;
|
|
507
|
+
if (typeof candidate.stdout === "string" && candidate.stdout) process.stdout.write(candidate.stdout);
|
|
508
|
+
if (typeof candidate.stderr === "string" && candidate.stderr) process.stderr.write(candidate.stderr);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* @description 运行微信开发者工具 CLI,并在登录失效时允许按键重试。
|
|
512
|
+
*/
|
|
513
|
+
async function runWechatCliWithRetry(cliPath, argv) {
|
|
514
|
+
const loginRetryOptions = resolveLoginRetryConfig(argv);
|
|
515
|
+
const result = await runWithSuspendedSharedInput(async () => {
|
|
516
|
+
return await runRetryableCommand({
|
|
517
|
+
createCancelError: (result) => createWechatIdeLoginRequiredExitError(unwrapWechatCliExecutionError(result)),
|
|
518
|
+
execute: async () => {
|
|
519
|
+
try {
|
|
520
|
+
return {
|
|
521
|
+
kind: "result",
|
|
522
|
+
value: await execute(cliPath, loginRetryOptions.runtimeArgv, {
|
|
523
|
+
pipeStdout: false,
|
|
524
|
+
pipeStderr: false
|
|
525
|
+
})
|
|
526
|
+
};
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (!isWechatIdeLoginRequiredError(error)) throw error;
|
|
529
|
+
return {
|
|
530
|
+
error,
|
|
531
|
+
kind: "retryable"
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
isRetryableResult: (result) => result.kind === "retryable" || isWechatIdeLoginRequiredError(result.value),
|
|
536
|
+
onRetry: () => {
|
|
537
|
+
logger_default.info(i18nText("正在重试连接微信开发者工具...", "Retrying to connect Wechat DevTools..."));
|
|
538
|
+
},
|
|
539
|
+
promptRetry: async (result, retryCount) => {
|
|
540
|
+
return await promptLoginRetry(unwrapWechatCliExecutionError(result), loginRetryOptions, retryCount);
|
|
541
|
+
},
|
|
542
|
+
shouldRetry: (action) => action === "retry"
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
if (result.kind === "retryable") throw createWechatIdeLoginRequiredExitError(result.error);
|
|
546
|
+
if (isWechatIdeLoginRequiredError(result.value)) throw createWechatIdeLoginRequiredExitError(result.value);
|
|
547
|
+
flushExecutionOutput(result.value);
|
|
548
|
+
}
|
|
549
|
+
//#endregion
|
|
550
|
+
//#region src/cli/run-wechat-cli.ts
|
|
551
|
+
function shouldBootstrapWechatDevtools(command) {
|
|
552
|
+
return command === "open" || command === "auto" || command === "auto-preview";
|
|
553
|
+
}
|
|
554
|
+
function appendOptionValue(argv, sourceArgv, optionName) {
|
|
555
|
+
const value = readOptionValue(sourceArgv, optionName);
|
|
556
|
+
if (value !== void 0) argv.push(optionName, value);
|
|
557
|
+
}
|
|
558
|
+
function createAutoPreviewWakeArgv(argv, trustProject) {
|
|
559
|
+
if (argv[0] !== "auto-preview") return;
|
|
560
|
+
const projectPath = readOptionValue(argv, "--project");
|
|
561
|
+
const appid = readOptionValue(argv, "--appid");
|
|
562
|
+
if (!projectPath && !appid) return;
|
|
563
|
+
const wakeArgv = ["open"];
|
|
564
|
+
appendOptionValue(wakeArgv, argv, "--project");
|
|
565
|
+
appendOptionValue(wakeArgv, argv, "--appid");
|
|
566
|
+
appendOptionValue(wakeArgv, argv, "--ext-appid");
|
|
567
|
+
if (trustProject === true) wakeArgv.push("--trust-project");
|
|
568
|
+
return wakeArgv;
|
|
569
|
+
}
|
|
570
|
+
function resolveBooleanCliOption(argv, optionName) {
|
|
571
|
+
if (argv.includes(optionName)) return true;
|
|
572
|
+
const rawValue = readOptionValue(argv, optionName);
|
|
573
|
+
if (rawValue === void 0) return;
|
|
574
|
+
const normalized = rawValue.trim().toLowerCase();
|
|
575
|
+
if (normalized === "" || normalized === "true" || normalized === "1" || normalized === "on") return true;
|
|
576
|
+
if (normalized === "false" || normalized === "0" || normalized === "off") return false;
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
async function handleMissingCliPath(source) {
|
|
580
|
+
const message = source === "custom" ? i18nText("在当前自定义路径中未找到微信web开发者命令行工具,请重新指定路径。", "Cannot find Wechat Web DevTools CLI in custom path, please reconfigure it.") : i18nText(`未检测到微信web开发者命令行工具,请执行 ${colors.bold(colors.green("weapp-ide-cli config"))} 指定路径。`, `Wechat Web DevTools CLI not found, please run ${colors.bold(colors.green("weapp-ide-cli config"))} to configure it.`);
|
|
581
|
+
logger_default.warn(message);
|
|
582
|
+
await promptForCliPath();
|
|
583
|
+
}
|
|
584
|
+
async function maybeBootstrapWechatDevtoolsSettings(argv) {
|
|
585
|
+
const command = argv[0];
|
|
586
|
+
if (!shouldBootstrapWechatDevtools(command)) return;
|
|
587
|
+
const config = await readCustomConfig();
|
|
588
|
+
if (config.autoBootstrapDevtools === false) return;
|
|
589
|
+
const projectPath = readOptionValue(argv, "--project");
|
|
590
|
+
const trustProjectOption = resolveBooleanCliOption(argv, "--trust-project");
|
|
591
|
+
const trustProject = trustProjectOption === void 0 ? config.autoTrustProject ?? false : trustProjectOption;
|
|
592
|
+
await bootstrapWechatDevtoolsSettings({
|
|
593
|
+
projectPath,
|
|
594
|
+
trustProject
|
|
595
|
+
});
|
|
596
|
+
return { trustProject };
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* @description 执行微信开发者工具 CLI 阶段,包括环境检查、路径解析、bootstrap 与登录重试。
|
|
600
|
+
*/
|
|
601
|
+
async function runWechatCliCommand(argv) {
|
|
602
|
+
if (!isOperatingSystemSupported(operatingSystemName)) {
|
|
603
|
+
logger_default.warn(i18nText(`微信web开发者工具不支持当前平台:${operatingSystemName} !`, `Wechat Web DevTools CLI is not supported on current platform: ${operatingSystemName}!`));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const { cliPath, source } = await resolveCliPath();
|
|
607
|
+
if (!cliPath) {
|
|
608
|
+
await handleMissingCliPath(source);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const wakeArgv = createAutoPreviewWakeArgv(argv, (await maybeBootstrapWechatDevtoolsSettings(argv))?.trustProject);
|
|
612
|
+
if (wakeArgv) await runWechatCliWithRetry(cliPath, wakeArgv);
|
|
613
|
+
await runWechatCliWithRetry(cliPath, argv);
|
|
614
|
+
}
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/cli/wechat-commands.ts
|
|
617
|
+
function appendProjectLocatorArgv(argv, options) {
|
|
618
|
+
if (options.projectPath) argv.push("--project", path.resolve(options.projectPath));
|
|
619
|
+
if (options.appid) argv.push("--appid", options.appid);
|
|
620
|
+
if (options.extAppid) argv.push("--ext-appid", options.extAppid);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* @description 调用微信开发者工具 open 命令。
|
|
624
|
+
*/
|
|
625
|
+
async function openWechatIde(options = {}) {
|
|
626
|
+
const argv = ["open"];
|
|
627
|
+
appendProjectLocatorArgv(argv, options);
|
|
628
|
+
if (options.platform) argv.push("--platform", options.platform);
|
|
629
|
+
if (options.trustProject) argv.push("--trust-project");
|
|
630
|
+
await runWechatCliCommand(argv);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* @description 调用微信开发者工具 login 命令。
|
|
634
|
+
*/
|
|
635
|
+
async function loginWechatIde(options = {}) {
|
|
636
|
+
const argv = ["login"];
|
|
637
|
+
if (options.qrFormat) argv.push("--qr-format", options.qrFormat);
|
|
638
|
+
if (options.qrOutput) argv.push("--qr-output", path.resolve(options.qrOutput));
|
|
639
|
+
if (options.qrSize) argv.push("--qr-size", options.qrSize);
|
|
640
|
+
if (options.resultOutput) argv.push("--result-output", path.resolve(options.resultOutput));
|
|
641
|
+
await runWechatCliCommand(argv);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* @description 调用微信开发者工具 islogin 命令。
|
|
645
|
+
*/
|
|
646
|
+
async function isWechatIdeLoggedIn() {
|
|
647
|
+
await runWechatCliCommand(["islogin"]);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* @description 调用微信开发者工具 build-npm 命令。
|
|
651
|
+
*/
|
|
652
|
+
async function buildWechatIdeNpm(options = {}) {
|
|
653
|
+
const argv = [
|
|
654
|
+
"build-npm",
|
|
655
|
+
"--project",
|
|
656
|
+
path.resolve(options.projectPath ?? process.cwd())
|
|
657
|
+
];
|
|
658
|
+
if (options.compileType) argv.push("--compile-type", options.compileType);
|
|
659
|
+
await runWechatCliCommand(argv);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* @description 调用微信开发者工具 preview 命令。
|
|
663
|
+
*/
|
|
664
|
+
async function previewWechatIde(options = {}) {
|
|
665
|
+
const argv = ["preview"];
|
|
666
|
+
appendProjectLocatorArgv(argv, options);
|
|
667
|
+
if (options.qrFormat) argv.push("--qr-format", options.qrFormat);
|
|
668
|
+
if (options.qrOutput) argv.push("--qr-output", path.resolve(options.qrOutput));
|
|
669
|
+
if (options.qrSize) argv.push("--qr-size", options.qrSize);
|
|
670
|
+
if (options.infoOutput) argv.push("--info-output", path.resolve(options.infoOutput));
|
|
671
|
+
if (options.compileCondition) argv.push("--compile-condition", options.compileCondition);
|
|
672
|
+
await runWechatCliCommand(argv);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* @description 调用微信开发者工具 auto-preview 命令。
|
|
676
|
+
*/
|
|
677
|
+
async function autoPreviewWechatIde(options = {}) {
|
|
678
|
+
const argv = ["auto-preview"];
|
|
679
|
+
appendProjectLocatorArgv(argv, options);
|
|
680
|
+
if (options.infoOutput) argv.push("--info-output", path.resolve(options.infoOutput));
|
|
681
|
+
if (options.compileCondition) argv.push("--compile-condition", options.compileCondition);
|
|
682
|
+
await runWechatCliCommand(argv);
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* @description 调用微信开发者工具 auto 命令。
|
|
686
|
+
*/
|
|
687
|
+
async function autoWechatIde(options = {}) {
|
|
688
|
+
const argv = ["auto"];
|
|
689
|
+
appendProjectLocatorArgv(argv, options);
|
|
690
|
+
if (options.port) argv.push("--auto-port", options.port);
|
|
691
|
+
if (options.account) argv.push("--auto-account", options.account);
|
|
692
|
+
if (options.testTicket) argv.push("--test-ticket", options.testTicket);
|
|
693
|
+
if (options.ticket) argv.push("--ticket", options.ticket);
|
|
694
|
+
if (options.trustProject) argv.push("--trust-project");
|
|
695
|
+
await runWechatCliCommand(argv);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* @description 调用微信开发者工具 auto-replay 命令。
|
|
699
|
+
*/
|
|
700
|
+
async function autoReplayWechatIde(options = {}) {
|
|
701
|
+
const argv = ["auto-replay"];
|
|
702
|
+
appendProjectLocatorArgv(argv, options);
|
|
703
|
+
if (options.port) argv.push("--auto-port", options.port);
|
|
704
|
+
if (options.account) argv.push("--auto-account", options.account);
|
|
705
|
+
if (options.replayAll) argv.push("--replay-all");
|
|
706
|
+
if (options.replayConfigPath) argv.push("--replay-config-path", path.resolve(options.replayConfigPath));
|
|
707
|
+
if (options.testTicket) argv.push("--test-ticket", options.testTicket);
|
|
708
|
+
if (options.ticket) argv.push("--ticket", options.ticket);
|
|
709
|
+
if (options.trustProject) argv.push("--trust-project");
|
|
710
|
+
await runWechatCliCommand(argv);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* @description 调用微信开发者工具 upload 命令。
|
|
714
|
+
*/
|
|
715
|
+
async function uploadWechatIde(options) {
|
|
716
|
+
const argv = ["upload"];
|
|
717
|
+
appendProjectLocatorArgv(argv, options);
|
|
718
|
+
argv.push("--version", options.version, "--desc", options.desc);
|
|
719
|
+
if (options.infoOutput) argv.push("--info-output", path.resolve(options.infoOutput));
|
|
720
|
+
await runWechatCliCommand(argv);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* @description 调用微信开发者工具 close 命令。
|
|
724
|
+
*/
|
|
725
|
+
async function closeWechatIdeProject() {
|
|
726
|
+
await runWechatCliCommand(["close"]);
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* @description 调用微信开发者工具 quit 命令。
|
|
730
|
+
*/
|
|
731
|
+
async function quitWechatIde() {
|
|
732
|
+
await runWechatCliCommand(["quit"]);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* @description 调用微信开发者工具 cache 命令。
|
|
736
|
+
*/
|
|
737
|
+
async function clearWechatIdeCache(options) {
|
|
738
|
+
await runWechatCliCommand([
|
|
739
|
+
"cache",
|
|
740
|
+
"--clean",
|
|
741
|
+
options.clean
|
|
742
|
+
]);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* @description 调用微信开发者工具 open-other 命令。
|
|
746
|
+
*/
|
|
747
|
+
async function openWechatIdeOtherProject(_options = {}) {
|
|
748
|
+
await runWechatCliCommand(["open-other"]);
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* @description 通过微信开发者工具 HTTP 服务端口重置指定项目的 fileutils 状态。
|
|
752
|
+
*/
|
|
753
|
+
async function resetWechatIdeFileUtils(options) {
|
|
754
|
+
await resetWechatIdeFileUtilsByHttp(path.resolve(options.projectPath));
|
|
755
|
+
}
|
|
756
|
+
function createAutomatorSessionOptions(options) {
|
|
757
|
+
return {
|
|
758
|
+
...options.port ? { port: options.port } : {},
|
|
759
|
+
preferOpenedSession: options.preferOpenedSession ?? true,
|
|
760
|
+
projectPath: path.resolve(options.projectPath),
|
|
761
|
+
...options.sessionId ? { sessionId: options.sessionId } : {},
|
|
762
|
+
sharedSession: options.sharedSession ?? true,
|
|
763
|
+
timeout: options.timeout
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* @description 通过已打开或新建的 automator 会话获取开发者工具基础信息。
|
|
768
|
+
*/
|
|
769
|
+
async function getWechatIdeToolInfo(options) {
|
|
770
|
+
return await withMiniProgram(createAutomatorSessionOptions(options), async (miniProgram) => {
|
|
771
|
+
return await miniProgram.toolInfo();
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* @description 通过已打开或新建的 automator 会话执行项目编译。
|
|
776
|
+
*/
|
|
777
|
+
async function compileWechatIdeByAutomator(options) {
|
|
778
|
+
return await withMiniProgram(createAutomatorSessionOptions(options), async (miniProgram) => {
|
|
779
|
+
return await miniProgram.compile({ force: options.force });
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* @description 通过已打开或新建的 automator 会话清理开发者工具缓存。
|
|
784
|
+
*/
|
|
785
|
+
async function clearWechatIdeCacheByAutomator(options) {
|
|
786
|
+
return await withMiniProgram(createAutomatorSessionOptions(options), async (miniProgram) => {
|
|
787
|
+
return await miniProgram.clearCache({ clean: options.clean });
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* @description 通过已打开或新建的 automator 会话获取当前 ticket。
|
|
792
|
+
*/
|
|
793
|
+
async function getWechatIdeTicket(options) {
|
|
794
|
+
return await withMiniProgram(createAutomatorSessionOptions(options), async (miniProgram) => {
|
|
795
|
+
return await miniProgram.getTicket();
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* @description 通过已打开或新建的 automator 会话设置 ticket。
|
|
800
|
+
*/
|
|
801
|
+
async function setWechatIdeTicket(options) {
|
|
802
|
+
return await withMiniProgram(createAutomatorSessionOptions(options), async (miniProgram) => {
|
|
803
|
+
await miniProgram.setTicket(options.ticket);
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* @description 通过已打开或新建的 automator 会话刷新 ticket。
|
|
808
|
+
*/
|
|
809
|
+
async function refreshWechatIdeTicket(options) {
|
|
810
|
+
return await withMiniProgram(createAutomatorSessionOptions(options), async (miniProgram) => {
|
|
811
|
+
await miniProgram.refreshTicket();
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* @description 通过已打开或新建的 automator 会话获取测试账号列表。
|
|
816
|
+
*/
|
|
817
|
+
async function getWechatIdeTestAccounts(options) {
|
|
818
|
+
return await withMiniProgram(createAutomatorSessionOptions(options), async (miniProgram) => {
|
|
819
|
+
return await miniProgram.testAccounts();
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* @description 调用微信开发者工具 build-apk 命令。
|
|
824
|
+
*/
|
|
825
|
+
async function buildWechatIdeApk(options) {
|
|
826
|
+
const argv = [
|
|
827
|
+
"build-apk",
|
|
828
|
+
"--key-store",
|
|
829
|
+
path.resolve(options.keyStore),
|
|
830
|
+
"--key-alias",
|
|
831
|
+
options.keyAlias,
|
|
832
|
+
"--key-pass",
|
|
833
|
+
options.keyPass,
|
|
834
|
+
"--store-pass",
|
|
835
|
+
options.storePass,
|
|
836
|
+
"--output",
|
|
837
|
+
path.resolve(options.output)
|
|
838
|
+
];
|
|
839
|
+
if (options.useAab !== void 0) argv.push("--use-aab", String(options.useAab));
|
|
840
|
+
if (options.desc) argv.push("--desc", options.desc);
|
|
841
|
+
if (options.isUploadResourceBundle) argv.push("--isUploadResourceBundle");
|
|
842
|
+
if (options.resourceBundleVersion) argv.push("--resourceBundleVersion", options.resourceBundleVersion);
|
|
843
|
+
if (options.resourceBundleDesc) argv.push("--resourceBundleDesc", options.resourceBundleDesc);
|
|
844
|
+
await runWechatCliCommand(argv);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* @description 调用微信开发者工具 build-ipa 命令。
|
|
848
|
+
*/
|
|
849
|
+
async function buildWechatIdeIpa(options) {
|
|
850
|
+
const argv = [
|
|
851
|
+
"build-ipa",
|
|
852
|
+
"--output",
|
|
853
|
+
path.resolve(options.output),
|
|
854
|
+
"--isDistribute",
|
|
855
|
+
String(options.isDistribute)
|
|
856
|
+
];
|
|
857
|
+
if (options.isRemoteBuild !== void 0) argv.push("--isRemoteBuild", String(options.isRemoteBuild));
|
|
858
|
+
if (options.profilePath) argv.push("--profilePath", path.resolve(options.profilePath));
|
|
859
|
+
if (options.certificateName) argv.push("--certificateName", options.certificateName);
|
|
860
|
+
if (options.p12Path) argv.push("--p12Path", path.resolve(options.p12Path));
|
|
861
|
+
if (options.p12Password) argv.push("--p12Password", options.p12Password);
|
|
862
|
+
if (options.tpnsProfilePath) argv.push("--tpnsProfilePath", path.resolve(options.tpnsProfilePath));
|
|
863
|
+
if (options.isUploadBeta !== void 0) argv.push("--isUploadBeta", String(options.isUploadBeta));
|
|
864
|
+
if (options.isUploadResourceBundle) argv.push("--isUploadResourceBundle");
|
|
865
|
+
if (options.resourceBundleVersion) argv.push("--resourceBundleVersion", options.resourceBundleVersion);
|
|
866
|
+
if (options.resourceBundleDesc) argv.push("--resourceBundleDesc", options.resourceBundleDesc);
|
|
867
|
+
if (options.versionName) argv.push("--versionName", options.versionName);
|
|
868
|
+
if (options.versionCode !== void 0) argv.push("--versionCode", String(options.versionCode));
|
|
869
|
+
if (options.versionDesc) argv.push("--versionDesc", options.versionDesc);
|
|
870
|
+
await runWechatCliCommand(argv);
|
|
871
|
+
}
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region src/cli/commands.ts
|
|
874
|
+
function createTimeoutError(message, code) {
|
|
875
|
+
const error = new Error(message);
|
|
876
|
+
error.code = code;
|
|
877
|
+
return error;
|
|
878
|
+
}
|
|
879
|
+
function normalizePagePath(page) {
|
|
880
|
+
return page.startsWith("/") ? page : `/${page}`;
|
|
881
|
+
}
|
|
882
|
+
function sleep(ms) {
|
|
883
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
884
|
+
}
|
|
885
|
+
function withCommandTimeout(task, timeoutMs, message, code) {
|
|
886
|
+
return new Promise((resolve, reject) => {
|
|
887
|
+
const timeout = setTimeout(() => {
|
|
888
|
+
reject(createTimeoutError(message, code));
|
|
889
|
+
}, timeoutMs);
|
|
890
|
+
task.then((value) => {
|
|
891
|
+
clearTimeout(timeout);
|
|
892
|
+
resolve(value);
|
|
893
|
+
}).catch((error) => {
|
|
894
|
+
clearTimeout(timeout);
|
|
895
|
+
reject(error);
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
function isDevtoolsProtocolTimeoutError(error) {
|
|
900
|
+
if (!(error instanceof Error)) return false;
|
|
901
|
+
return error.message === "DEVTOOLS_PROTOCOL_TIMEOUT" || error.message.includes("DevTools did not respond to protocol method") || Reflect.get(error, "code") === "DEVTOOLS_PROTOCOL_TIMEOUT";
|
|
902
|
+
}
|
|
903
|
+
function isScreenshotNavigationTimeoutError(error) {
|
|
904
|
+
return error instanceof Error && Reflect.get(error, "code") === "DEVTOOLS_SCREENSHOT_NAVIGATION_TIMEOUT";
|
|
905
|
+
}
|
|
906
|
+
async function reLaunchForScreenshot(miniProgram, page) {
|
|
907
|
+
const routeOptions = { url: page };
|
|
908
|
+
if (typeof miniProgram.callWxMethod === "function") await miniProgram.callWxMethod("reLaunch", routeOptions);
|
|
909
|
+
else await miniProgram.reLaunch(page);
|
|
910
|
+
}
|
|
911
|
+
async function runRouteCommand(options, startMessage, successMessage, action) {
|
|
912
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
913
|
+
logger_default.info(startMessage);
|
|
914
|
+
await action(miniProgram);
|
|
915
|
+
logger_default.success(successMessage);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
function toPageSnapshot(page) {
|
|
919
|
+
return {
|
|
920
|
+
path: page.path ?? "",
|
|
921
|
+
query: page.query
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function printStructuredResult(result, json, title) {
|
|
925
|
+
if (json) {
|
|
926
|
+
console.log(JSON.stringify(result, null, 2));
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
logger_default.info(title);
|
|
930
|
+
console.log(JSON.stringify(result, null, 2));
|
|
931
|
+
}
|
|
932
|
+
async function requireElement(page, selector) {
|
|
933
|
+
const element = await page.$(selector);
|
|
934
|
+
if (!element) throw new Error(i18nText(`未找到元素: ${selector}`, `Element not found: ${selector}`));
|
|
935
|
+
return element;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* @description 执行保留栈页面跳转。
|
|
939
|
+
*/
|
|
940
|
+
async function navigateTo(options) {
|
|
941
|
+
await runRouteCommand(options, i18nText(`正在跳转到 ${colors.cyan(options.url)}...`, `Navigating to ${colors.cyan(options.url)}...`), i18nText(`已跳转到 ${colors.cyan(options.url)}`, `Navigated to ${colors.cyan(options.url)}`), (miniProgram) => miniProgram.navigateTo(options.url));
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* @description 执行关闭当前页的重定向。
|
|
945
|
+
*/
|
|
946
|
+
async function redirectTo(options) {
|
|
947
|
+
await runRouteCommand(options, i18nText(`正在重定向到 ${colors.cyan(options.url)}...`, `Redirecting to ${colors.cyan(options.url)}...`), i18nText(`已重定向到 ${colors.cyan(options.url)}`, `Redirected to ${colors.cyan(options.url)}`), (miniProgram) => miniProgram.redirectTo(options.url));
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* @description 返回上一页。
|
|
951
|
+
*/
|
|
952
|
+
async function navigateBack(options) {
|
|
953
|
+
await runRouteCommand(options, i18nText("正在返回上一页...", "Navigating back..."), i18nText("已返回上一页", "Navigated back"), (miniProgram) => miniProgram.navigateBack());
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* @description 重启到指定页面。
|
|
957
|
+
*/
|
|
958
|
+
async function reLaunch(options) {
|
|
959
|
+
await runRouteCommand(options, i18nText(`正在重启到 ${colors.cyan(options.url)}...`, `Relaunching to ${colors.cyan(options.url)}...`), i18nText(`已重启到 ${colors.cyan(options.url)}`, `Relaunched to ${colors.cyan(options.url)}`), (miniProgram) => miniProgram.reLaunch(options.url));
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* @description 切换到 tabBar 页面。
|
|
963
|
+
*/
|
|
964
|
+
async function switchTab(options) {
|
|
965
|
+
await runRouteCommand(options, i18nText(`正在切换 tab 到 ${colors.cyan(options.url)}...`, `Switching tab to ${colors.cyan(options.url)}...`), i18nText(`已切换 tab 到 ${colors.cyan(options.url)}`, `Switched tab to ${colors.cyan(options.url)}`), (miniProgram) => miniProgram.switchTab(options.url));
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* @description 获取页面栈。
|
|
969
|
+
*/
|
|
970
|
+
async function pageStack(options) {
|
|
971
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
972
|
+
const result = (await miniProgram.pageStack()).map(toPageSnapshot);
|
|
973
|
+
printStructuredResult(result, options.json, i18nText("页面栈:", "Page stack:"));
|
|
974
|
+
return result;
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* @description 获取当前页面信息。
|
|
979
|
+
*/
|
|
980
|
+
async function currentPage(options) {
|
|
981
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
982
|
+
const result = toPageSnapshot(await miniProgram.currentPage());
|
|
983
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
984
|
+
else logger_default.info(i18nText(`当前页面: ${result.path}${result.query ? ` ${JSON.stringify(result.query)}` : ""}`, `Current page: ${result.path}${result.query ? ` ${JSON.stringify(result.query)}` : ""}`));
|
|
985
|
+
return result;
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* @description 获取系统信息。
|
|
990
|
+
*/
|
|
991
|
+
async function systemInfo(options) {
|
|
992
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
993
|
+
const result = await miniProgram.systemInfo();
|
|
994
|
+
printStructuredResult(result, options.json, i18nText("系统信息:", "System info:"));
|
|
995
|
+
return result;
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* @description 获取当前页面数据。
|
|
1000
|
+
*/
|
|
1001
|
+
async function pageData(options) {
|
|
1002
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
1003
|
+
const result = await (await miniProgram.currentPage()).data(options.path);
|
|
1004
|
+
printStructuredResult(result, options.json, i18nText("页面数据:", "Page data:"));
|
|
1005
|
+
return result;
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* @description 点击页面元素。
|
|
1010
|
+
*/
|
|
1011
|
+
async function tap(options) {
|
|
1012
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
1013
|
+
logger_default.info(i18nText(`正在点击元素 ${colors.cyan(options.selector)}...`, `Tapping element ${colors.cyan(options.selector)}...`));
|
|
1014
|
+
await (await requireElement(await miniProgram.currentPage(), options.selector)).tap();
|
|
1015
|
+
logger_default.success(i18nText(`已点击元素 ${colors.cyan(options.selector)}`, `Tapped element ${colors.cyan(options.selector)}`));
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* @description 向页面元素输入文本。
|
|
1020
|
+
*/
|
|
1021
|
+
async function input(options) {
|
|
1022
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
1023
|
+
logger_default.info(i18nText(`正在向 ${colors.cyan(options.selector)} 输入 "${colors.cyan(options.value)}"...`, `Inputting "${colors.cyan(options.value)}" into ${colors.cyan(options.selector)}...`));
|
|
1024
|
+
const element = await requireElement(await miniProgram.currentPage(), options.selector);
|
|
1025
|
+
if (typeof element.input !== "function") throw new TypeError(i18nText(`元素不支持输入: ${options.selector}`, `Element does not support input: ${options.selector}`));
|
|
1026
|
+
await element.input(options.value);
|
|
1027
|
+
logger_default.success(i18nText(`已向 ${colors.cyan(options.selector)} 输入 "${colors.cyan(options.value)}"`, `Inputted "${colors.cyan(options.value)}" into ${colors.cyan(options.selector)}`));
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* @description 滚动页面到指定位置。
|
|
1032
|
+
*/
|
|
1033
|
+
async function scrollTo(options) {
|
|
1034
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
1035
|
+
logger_default.info(i18nText(`正在滚动到位置 ${colors.cyan(String(options.scrollTop))}...`, `Scrolling to position ${colors.cyan(String(options.scrollTop))}...`));
|
|
1036
|
+
await miniProgram.pageScrollTo(options.scrollTop);
|
|
1037
|
+
logger_default.success(i18nText(`已滚动到位置 ${colors.cyan(String(options.scrollTop))}`, `Scrolled to position ${colors.cyan(String(options.scrollTop))}`));
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* @description 执行体验评分审计。
|
|
1042
|
+
*/
|
|
1043
|
+
async function audit(options) {
|
|
1044
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
1045
|
+
logger_default.info(i18nText("正在执行体验审计...", "Running experience audit..."));
|
|
1046
|
+
const result = await miniProgram.stopAudits();
|
|
1047
|
+
if (options.outputPath) {
|
|
1048
|
+
await fs.writeFile(options.outputPath, JSON.stringify(result, null, 2));
|
|
1049
|
+
logger_default.success(i18nText(`审计报告已保存到 ${colors.cyan(options.outputPath)}`, `Audit report saved to ${colors.cyan(options.outputPath)}`));
|
|
1050
|
+
return result;
|
|
1051
|
+
}
|
|
1052
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1053
|
+
return result;
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* @description 捕获当前页面截图并返回二进制内容。
|
|
1058
|
+
*/
|
|
1059
|
+
async function captureScreenshotBuffer(options) {
|
|
1060
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
1061
|
+
const commandTimeout = options.timeout ?? 3e4;
|
|
1062
|
+
const screenshotTimeoutMessage = i18nText(`截图请求在 ${commandTimeout}ms 内未收到 DevTools 回包,请检查当前微信开发者工具是否仍停留在目标项目;若近期执行过其他 e2e / screenshot 任务,关闭多余窗口并清理残留 automator 会话后重试。`, `Screenshot request did not receive a DevTools response within ${commandTimeout}ms. Check that the current Wechat DevTools window is still the target project. If you recently ran other e2e or screenshot tasks, close extra windows and clean up stale automator sessions before retrying.`);
|
|
1063
|
+
if (!options.miniProgram) logger_default.info(i18nText(`正在连接 DevTools:${colors.cyan(options.projectPath)}...`, `Connecting to DevTools at ${colors.cyan(options.projectPath)}...`));
|
|
1064
|
+
if (options.page) {
|
|
1065
|
+
const normalizedPage = normalizePagePath(options.page);
|
|
1066
|
+
logger_default.info(i18nText(`正在跳转页面 ${colors.cyan(normalizedPage)}...`, `Navigating to page ${colors.cyan(normalizedPage)}...`));
|
|
1067
|
+
try {
|
|
1068
|
+
await reLaunchForScreenshot(miniProgram, normalizedPage);
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
if (!isDevtoolsProtocolTimeoutError(error)) throw error;
|
|
1071
|
+
logger_default.warn(i18nText(`截图前跳转页面 ${normalizedPage} 超时,将重建会话后截取当前页面。`, `Timed out navigating to ${normalizedPage} before screenshot. Rebuilding the session and capturing the current page.`));
|
|
1072
|
+
throw createTimeoutError(i18nText(`截图前跳转页面 ${normalizedPage} 超时`, `Timed out navigating to ${normalizedPage} before screenshot`), "DEVTOOLS_SCREENSHOT_NAVIGATION_TIMEOUT");
|
|
1073
|
+
}
|
|
1074
|
+
if (options.fullPage) await sleep(1e3);
|
|
1075
|
+
}
|
|
1076
|
+
if (options.fullPage) {
|
|
1077
|
+
logger_default.info(i18nText("正在生成整页长截图...", "Capturing full-page screenshot..."));
|
|
1078
|
+
return await captureFullPageScreenshotBuffer({
|
|
1079
|
+
miniProgram,
|
|
1080
|
+
timeoutMs: commandTimeout,
|
|
1081
|
+
runWithTimeout: withCommandTimeout,
|
|
1082
|
+
screenshotTimeoutMessage
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
logger_default.info(i18nText("正在截图...", "Taking screenshot..."));
|
|
1086
|
+
const screenshot = await withCommandTimeout(miniProgram.screenshot(), commandTimeout, screenshotTimeoutMessage, "DEVTOOLS_SCREENSHOT_TIMEOUT");
|
|
1087
|
+
const buffer = typeof screenshot === "string" ? Buffer.from(screenshot, "base64") : Buffer.from(screenshot);
|
|
1088
|
+
if (buffer.length === 0) throw new Error(i18nText("截图失败", "Failed to capture screenshot"));
|
|
1089
|
+
return buffer;
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* @description 获取当前小程序截图。
|
|
1094
|
+
*/
|
|
1095
|
+
async function takeScreenshot(options) {
|
|
1096
|
+
let nextOptions = options;
|
|
1097
|
+
let screenshotBuffer;
|
|
1098
|
+
let hasRetriedWithFreshSession = false;
|
|
1099
|
+
while (true) try {
|
|
1100
|
+
screenshotBuffer = await captureScreenshotBuffer(nextOptions);
|
|
1101
|
+
break;
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
const isProtocolTimeout = error instanceof Error && error.message === "DEVTOOLS_PROTOCOL_TIMEOUT";
|
|
1104
|
+
const isNavigationTimeout = isScreenshotNavigationTimeoutError(error);
|
|
1105
|
+
if (!Boolean(!nextOptions.miniProgram && (isProtocolTimeout || isNavigationTimeout) && !hasRetriedWithFreshSession)) throw error;
|
|
1106
|
+
hasRetriedWithFreshSession = true;
|
|
1107
|
+
await closeWechatIdeProject().catch((closeError) => {
|
|
1108
|
+
logger_default.warn(i18nText(`关闭当前微信开发者工具项目窗口失败:${closeError instanceof Error ? closeError.message : String(closeError)},仍将继续尝试重建自动化会话。`, `Failed to close the current Wechat DevTools project window: ${closeError instanceof Error ? closeError.message : String(closeError)}. Continuing to rebuild the automation session.`));
|
|
1109
|
+
});
|
|
1110
|
+
const sessionIdOrPort = nextOptions.sessionId || nextOptions.port;
|
|
1111
|
+
if (sessionIdOrPort) await closeSharedMiniProgram(nextOptions.projectPath, sessionIdOrPort);
|
|
1112
|
+
else await closeSharedMiniProgram(nextOptions.projectPath);
|
|
1113
|
+
logger_default.warn(i18nText("当前 DevTools 会话截图超时,正在改用全新自动化会话重试一次...", "The current DevTools session timed out while capturing screenshot. Retrying once with a fresh automation session..."));
|
|
1114
|
+
const { page: _page, ...optionsWithoutPage } = nextOptions;
|
|
1115
|
+
nextOptions = {
|
|
1116
|
+
...isNavigationTimeout ? optionsWithoutPage : nextOptions,
|
|
1117
|
+
preferOpenedSession: false,
|
|
1118
|
+
sharedSession: false
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
if (!screenshotBuffer) throw new Error(i18nText("截图失败", "Failed to capture screenshot"));
|
|
1122
|
+
const base64 = screenshotBuffer.toString("base64");
|
|
1123
|
+
if (options.outputPath) {
|
|
1124
|
+
await fs.writeFile(options.outputPath, screenshotBuffer);
|
|
1125
|
+
logger_default.success(i18nText(`截图已保存到 ${colors.cyan(options.outputPath)}`, `Screenshot saved to ${colors.cyan(options.outputPath)}`));
|
|
1126
|
+
return { path: options.outputPath };
|
|
1127
|
+
}
|
|
1128
|
+
return { base64 };
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* @description 开关远程调试。
|
|
1132
|
+
*/
|
|
1133
|
+
async function remote(options) {
|
|
1134
|
+
const enable = options.enable ?? true;
|
|
1135
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
1136
|
+
logger_default.info(enable ? i18nText("正在开启远程调试...", "Enabling remote debugging...") : i18nText("正在关闭远程调试...", "Disabling remote debugging..."));
|
|
1137
|
+
await miniProgram.remote(enable);
|
|
1138
|
+
logger_default.success(enable ? i18nText("远程调试已开启", "Remote debugging enabled") : i18nText("远程调试已关闭", "Remote debugging disabled"));
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
//#endregion
|
|
1142
|
+
export { parseAutomatorArgs as $, isWechatIdeLoggedIn as A, runWechatCliCommand as B, clearWechatIdeCache as C, getWechatIdeTestAccounts as D, compileWechatIdeByAutomator as E, quitWechatIde as F, transformArgv as G, execute as H, refreshWechatIdeTicket as I, openWechatIdeProjectByHttp as J, promptForCliPath as K, resetWechatIdeFileUtils as L, openWechatIde as M, openWechatIdeOtherProject as N, getWechatIdeTicket as O, previewWechatIde as P, startWechatIdeEngineBuildByHttp as Q, setWechatIdeTicket as R, buildWechatIdeNpm as S, closeWechatIdeProject as T, createAlias as U, runWechatCliWithRetry as V, createPathCompat as W, requestWechatDevtoolsHttp as X, pollWechatIdeEngineBuildResultByHttp as Y, resetWechatIdeFileUtilsByHttp as Z, autoPreviewWechatIde as _, navigateBack as a, buildWechatIdeApk as b, pageStack as c, remote as d, readBooleanOption as et, scrollTo as f, tap as g, takeScreenshot as h, input as i, loginWechatIde as j, getWechatIdeToolInfo as k, reLaunch as l, systemInfo as m, captureScreenshotBuffer as n, removeOption as nt, navigateTo as o, switchTab as p, WECHAT_DEVTOOLS_ENGINE_BUILD_STATUSES as q, currentPage as r, pageData as s, audit as t, readOptionValue as tt, redirectTo as u, autoReplayWechatIde as v, clearWechatIdeCacheByAutomator as w, buildWechatIdeIpa as x, autoWechatIde as y, uploadWechatIde as z };
|