weapp-ide-cli 5.1.5 → 5.2.1
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 +1 -0
- package/dist/{cli-BZKq6t2k.js → cli-DKrUhSmB.js} +245 -29
- package/dist/cli.js +10 -13
- package/dist/{commands-CSbpftLN.js → commands--vppy2pX.js} +394 -101
- package/dist/commands-Bg8hQbPE.js +2 -0
- package/dist/index.d.ts +76 -9
- package/dist/index.js +3 -3
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -130,6 +130,7 @@ weapp cache --clean all
|
|
|
130
130
|
| 命令 | 说明 |
|
|
131
131
|
| -------------------------------- | ------------------------------ |
|
|
132
132
|
| `weapp screenshot` | 截图(支持 base64 / 文件输出) |
|
|
133
|
+
| `weapp compare` | 截图对比(pixelmatch) |
|
|
133
134
|
| `weapp navigate <url>` | 保留栈跳转页面 |
|
|
134
135
|
| `weapp redirect <url>` | 重定向页面 |
|
|
135
136
|
| `weapp back` | 页面返回 |
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import fs from "fs
|
|
1
|
+
import { B as createLocaleConfig, C as configureLocaleFromArgv, G as defaultCustomConfigFilePath, H as readCustomConfig, I as resolveCliPath, K as resolvePath, Q as logger_default, R as getConfiguredLocale, T as validateLocaleOption, U as removeCustomConfigKey, V as overwriteCustomConfig, X as operatingSystemName, Y as isOperatingSystemSupported, Z as colors, a as navigateBack, c as pageStack, d as remote, f as scrollTo, g as tap, i as input, l as reLaunch, m as systemInfo, n as captureScreenshotBuffer, o as navigateTo, p as switchTab, r as currentPage, s as pageData, t as audit, u as redirectTo, w as i18nText, y as connectMiniProgram, z as createCustomConfig } from "./commands--vppy2pX.js";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { fs as fs$1 } from "@weapp-core/shared";
|
|
3
4
|
import process, { stdin, stdout } from "node:process";
|
|
5
|
+
import { PNG } from "pngjs";
|
|
6
|
+
import pixelmatch from "pixelmatch";
|
|
4
7
|
import { createInterface } from "node:readline/promises";
|
|
5
8
|
import { inspect } from "node:util";
|
|
6
9
|
import { emitKeypressEvents } from "node:readline";
|
|
@@ -105,6 +108,211 @@ function takesValue(optionName) {
|
|
|
105
108
|
return optionName === "-p" || optionName === "--project" || optionName === "-t" || optionName === "--timeout" || optionName === "-o" || optionName === "--output" || optionName === "--page" || optionName === "--login-retry" || optionName === "--login-retry-timeout" || optionName === "--lang" || optionName === "--platform" || optionName === "--qr-output" || optionName === "-r" || optionName === "--result-output" || optionName === "--info-output" || optionName === "-i";
|
|
106
109
|
}
|
|
107
110
|
//#endregion
|
|
111
|
+
//#region src/cli/imageDiff.ts
|
|
112
|
+
function readPng(buffer, label) {
|
|
113
|
+
try {
|
|
114
|
+
return PNG.sync.read(buffer);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw new Error(i18nText(`${label} 不是有效的 PNG 文件`, `${label} is not a valid PNG file`), { cause: error });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* @description 将当前截图与基准图做像素对比,并按需输出当前图与 diff 图。
|
|
121
|
+
*/
|
|
122
|
+
async function comparePngWithBaseline(options) {
|
|
123
|
+
let baselineBuffer;
|
|
124
|
+
try {
|
|
125
|
+
baselineBuffer = await fs.readFile(options.baselinePath);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw new Error(i18nText(`无法读取基准图: ${options.baselinePath}`, `Failed to read baseline image: ${options.baselinePath}`), { cause: error });
|
|
128
|
+
}
|
|
129
|
+
const baselinePng = readPng(baselineBuffer, i18nText("基准图", "Baseline image"));
|
|
130
|
+
const currentPng = readPng(options.currentPngBuffer, i18nText("当前截图", "Current screenshot"));
|
|
131
|
+
if (baselinePng.width !== currentPng.width || baselinePng.height !== currentPng.height) throw new Error(i18nText("基准图与当前截图尺寸不一致", "Baseline image size does not match current screenshot"));
|
|
132
|
+
if (options.currentOutputPath) await fs.writeFile(options.currentOutputPath, options.currentPngBuffer);
|
|
133
|
+
const diffPng = new PNG({
|
|
134
|
+
width: currentPng.width,
|
|
135
|
+
height: currentPng.height
|
|
136
|
+
});
|
|
137
|
+
const diffPixels = pixelmatch(baselinePng.data, currentPng.data, diffPng.data, currentPng.width, currentPng.height, { threshold: options.threshold });
|
|
138
|
+
if (options.diffOutputPath) await fs.writeFile(options.diffOutputPath, PNG.sync.write(diffPng));
|
|
139
|
+
return {
|
|
140
|
+
baselinePath: options.baselinePath,
|
|
141
|
+
currentPath: options.currentOutputPath,
|
|
142
|
+
diffPath: options.diffOutputPath,
|
|
143
|
+
width: currentPng.width,
|
|
144
|
+
height: currentPng.height,
|
|
145
|
+
diffPixels,
|
|
146
|
+
diffRatio: diffPixels / (currentPng.width * currentPng.height)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/cli/compare.ts
|
|
151
|
+
function createCliError(message, exitCode, cause) {
|
|
152
|
+
const error = new Error(message, cause ? { cause } : void 0);
|
|
153
|
+
error.exitCode = exitCode;
|
|
154
|
+
return error;
|
|
155
|
+
}
|
|
156
|
+
function parseNonNegativeInteger(rawValue, optionName) {
|
|
157
|
+
if (rawValue == null) return;
|
|
158
|
+
const value = Number(rawValue);
|
|
159
|
+
if (!Number.isInteger(value) || value < 0) throw createCliError(i18nText(`${optionName} 必须是大于等于 0 的整数`, `${optionName} must be an integer greater than or equal to 0`), 2);
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
function parseRatio(rawValue, optionName) {
|
|
163
|
+
if (rawValue == null) return;
|
|
164
|
+
const value = Number(rawValue);
|
|
165
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) throw createCliError(i18nText(`${optionName} 必须是 0 到 1 之间的数字`, `${optionName} must be a number between 0 and 1`), 2);
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* @description 输出 compare 命令帮助信息。
|
|
170
|
+
*/
|
|
171
|
+
function printCompareHelp() {
|
|
172
|
+
console.log(i18nText(`
|
|
173
|
+
${colors.bold("Usage:")} weapp compare [options]
|
|
174
|
+
|
|
175
|
+
${colors.bold("参数:")}
|
|
176
|
+
-p, --project <path> 项目路径(默认:当前目录)
|
|
177
|
+
--baseline <path> 基准图路径(必填)
|
|
178
|
+
--current-output <path> 当前截图输出路径
|
|
179
|
+
--diff-output <path> diff 图片输出路径
|
|
180
|
+
--page <path> 对比前先跳转页面
|
|
181
|
+
--full-page 对比时使用整页长截图
|
|
182
|
+
--threshold <number> pixelmatch threshold(默认:0.1)
|
|
183
|
+
--max-diff-pixels <count> 最大允许差异像素数
|
|
184
|
+
--max-diff-ratio <number> 最大允许差异占比(0-1)
|
|
185
|
+
-t, --timeout <ms> 连接超时时间(默认:30000)
|
|
186
|
+
--json 以 JSON 格式输出
|
|
187
|
+
--lang <lang> 语言切换:zh | en(默认:zh)
|
|
188
|
+
-h, --help 显示此帮助信息
|
|
189
|
+
|
|
190
|
+
${colors.bold("规则:")}
|
|
191
|
+
- 必须提供 --baseline
|
|
192
|
+
- 必须至少提供 --max-diff-pixels 或 --max-diff-ratio 之一
|
|
193
|
+
- baseline 与当前截图尺寸不一致时直接失败
|
|
194
|
+
|
|
195
|
+
${colors.bold("示例:")}
|
|
196
|
+
weapp compare -p ./dist/build/mp-weixin --page pages/index/index --full-page --baseline .screenshots/baseline/index.full.png --current-output .screenshots/current/index.full.png --diff-output .screenshots/diff/index.full.diff.png --max-diff-pixels 100 --json
|
|
197
|
+
|
|
198
|
+
weapp compare -p ./dist/build/mp-weixin --page pages/index/index --baseline .screenshots/baseline/index.png --current-output .screenshots/current/index.png --diff-output .screenshots/diff/index.diff.png --max-diff-pixels 100 --json
|
|
199
|
+
|
|
200
|
+
weapp compare -p ./dist/build/mp-weixin --baseline .screenshots/baseline/index.png --max-diff-ratio 0.001
|
|
201
|
+
`, `
|
|
202
|
+
${colors.bold("Usage:")} weapp compare [options]
|
|
203
|
+
|
|
204
|
+
${colors.bold("Options:")}
|
|
205
|
+
-p, --project <path> Project path (default: current directory)
|
|
206
|
+
--baseline <path> Baseline image path (required)
|
|
207
|
+
--current-output <path> Output file path for current screenshot
|
|
208
|
+
--diff-output <path> Output file path for diff image
|
|
209
|
+
--page <path> Navigate to page before comparison
|
|
210
|
+
--full-page Use stitched full-page screenshots for comparison
|
|
211
|
+
--threshold <number> Pixelmatch threshold (default: 0.1)
|
|
212
|
+
--max-diff-pixels <count> Maximum allowed diff pixels
|
|
213
|
+
--max-diff-ratio <number> Maximum allowed diff ratio (0-1)
|
|
214
|
+
-t, --timeout <ms> Connection timeout in milliseconds (default: 30000)
|
|
215
|
+
--json Output as JSON format
|
|
216
|
+
--lang <lang> Language: zh | en (default: zh)
|
|
217
|
+
-h, --help Show this help message
|
|
218
|
+
|
|
219
|
+
${colors.bold("Rules:")}
|
|
220
|
+
- --baseline is required
|
|
221
|
+
- At least one of --max-diff-pixels or --max-diff-ratio is required
|
|
222
|
+
- Baseline and current screenshot must have identical dimensions
|
|
223
|
+
|
|
224
|
+
${colors.bold("Examples:")}
|
|
225
|
+
weapp compare -p ./dist/build/mp-weixin --page pages/index/index --full-page --baseline .screenshots/baseline/index.full.png --current-output .screenshots/current/index.full.png --diff-output .screenshots/diff/index.full.diff.png --max-diff-pixels 100 --json
|
|
226
|
+
|
|
227
|
+
weapp compare -p ./dist/build/mp-weixin --page pages/index/index --baseline .screenshots/baseline/index.png --current-output .screenshots/current/index.png --diff-output .screenshots/diff/index.diff.png --max-diff-pixels 100 --json
|
|
228
|
+
|
|
229
|
+
weapp compare -p ./dist/build/mp-weixin --baseline .screenshots/baseline/index.png --max-diff-ratio 0.001
|
|
230
|
+
`));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* @description 解析 compare 命令参数。
|
|
234
|
+
*/
|
|
235
|
+
function parseCompareArgs(argv) {
|
|
236
|
+
const parsed = parseAutomatorArgs(argv);
|
|
237
|
+
const baselinePath = readOptionValue(argv, "--baseline");
|
|
238
|
+
if (!baselinePath) throw createCliError(i18nText("compare 命令缺少 --baseline 参数", "Missing --baseline option for compare command"), 2);
|
|
239
|
+
const threshold = parseRatio(readOptionValue(argv, "--threshold"), "--threshold") ?? .1;
|
|
240
|
+
const maxDiffPixels = parseNonNegativeInteger(readOptionValue(argv, "--max-diff-pixels"), "--max-diff-pixels");
|
|
241
|
+
const maxDiffRatio = parseRatio(readOptionValue(argv, "--max-diff-ratio"), "--max-diff-ratio");
|
|
242
|
+
if (maxDiffPixels == null && maxDiffRatio == null) throw createCliError(i18nText("compare 命令至少需要提供 --max-diff-pixels 或 --max-diff-ratio 之一", "compare command requires at least one of --max-diff-pixels or --max-diff-ratio"), 2);
|
|
243
|
+
return {
|
|
244
|
+
projectPath: parsed.projectPath,
|
|
245
|
+
timeout: parsed.timeout,
|
|
246
|
+
page: readOptionValue(argv, "--page"),
|
|
247
|
+
fullPage: argv.includes("--full-page"),
|
|
248
|
+
baselinePath,
|
|
249
|
+
currentOutputPath: readOptionValue(argv, "--current-output"),
|
|
250
|
+
diffOutputPath: readOptionValue(argv, "--diff-output"),
|
|
251
|
+
threshold,
|
|
252
|
+
maxDiffPixels,
|
|
253
|
+
maxDiffRatio
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function resolveComparePassed(diff, options) {
|
|
257
|
+
if (options.maxDiffPixels != null && diff.diffPixels > options.maxDiffPixels) return false;
|
|
258
|
+
if (options.maxDiffRatio != null && diff.diffRatio > options.maxDiffRatio) return false;
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
function printCompareSummary(result) {
|
|
262
|
+
const summary = result.passed ? i18nText(`compare passed: diffPixels=${result.diffPixels} diffRatio=${result.diffRatio}`, `compare passed: diffPixels=${result.diffPixels} diffRatio=${result.diffRatio}`) : i18nText(`compare failed: diffPixels=${result.diffPixels} diffRatio=${result.diffRatio}`, `compare failed: diffPixels=${result.diffPixels} diffRatio=${result.diffRatio}`);
|
|
263
|
+
console.log(summary);
|
|
264
|
+
const pathSummary = [
|
|
265
|
+
`baseline=${result.baselinePath}`,
|
|
266
|
+
result.currentPath ? `current=${result.currentPath}` : void 0,
|
|
267
|
+
result.diffPath ? `diff=${result.diffPath}` : void 0
|
|
268
|
+
].filter(Boolean).join(" ");
|
|
269
|
+
if (pathSummary) console.log(pathSummary);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* @description 运行 compare 命令并输出对比结果。
|
|
273
|
+
*/
|
|
274
|
+
async function runCompare(argv) {
|
|
275
|
+
if (argv.includes("-h") || argv.includes("--help")) {
|
|
276
|
+
printCompareHelp();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const options = parseCompareArgs(argv);
|
|
280
|
+
let currentPngBuffer;
|
|
281
|
+
try {
|
|
282
|
+
currentPngBuffer = await captureScreenshotBuffer(options);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
throw createCliError(error instanceof Error ? error.message : i18nText("截图失败", "Failed to capture screenshot"), 3, error);
|
|
285
|
+
}
|
|
286
|
+
let diff;
|
|
287
|
+
try {
|
|
288
|
+
diff = await comparePngWithBaseline({
|
|
289
|
+
baselinePath: options.baselinePath,
|
|
290
|
+
currentPngBuffer,
|
|
291
|
+
currentOutputPath: options.currentOutputPath,
|
|
292
|
+
diffOutputPath: options.diffOutputPath,
|
|
293
|
+
threshold: options.threshold
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
throw createCliError(error instanceof Error ? error.message : i18nText("截图对比失败", "Screenshot comparison failed"), 3, error);
|
|
297
|
+
}
|
|
298
|
+
const result = {
|
|
299
|
+
passed: resolveComparePassed(diff, options),
|
|
300
|
+
baselinePath: options.baselinePath,
|
|
301
|
+
currentPath: diff.currentPath,
|
|
302
|
+
diffPath: diff.diffPath,
|
|
303
|
+
width: diff.width,
|
|
304
|
+
height: diff.height,
|
|
305
|
+
diffPixels: diff.diffPixels,
|
|
306
|
+
diffRatio: diff.diffRatio,
|
|
307
|
+
threshold: options.threshold,
|
|
308
|
+
maxDiffPixels: options.maxDiffPixels,
|
|
309
|
+
maxDiffRatio: options.maxDiffRatio
|
|
310
|
+
};
|
|
311
|
+
if (argv.includes("--json")) console.log(JSON.stringify(result, null, 2));
|
|
312
|
+
else printCompareSummary(result);
|
|
313
|
+
if (!result.passed) process.exitCode = 1;
|
|
314
|
+
}
|
|
315
|
+
//#endregion
|
|
108
316
|
//#region src/cli/screenshot.ts
|
|
109
317
|
/**
|
|
110
318
|
* @description Print help for screenshot command
|
|
@@ -117,6 +325,7 @@ ${colors.bold("参数:")}
|
|
|
117
325
|
-p, --project <path> 项目路径(默认:当前目录)
|
|
118
326
|
-o, --output <path> 截图输出文件路径
|
|
119
327
|
--page <path> 截图前先跳转页面
|
|
328
|
+
--full-page 输出整页长截图
|
|
120
329
|
-t, --timeout <ms> 连接超时时间(默认:30000)
|
|
121
330
|
--json 以 JSON 格式输出
|
|
122
331
|
--lang <lang> 语言切换:zh | en(默认:zh)
|
|
@@ -132,6 +341,9 @@ ${colors.bold("示例:")}
|
|
|
132
341
|
# 先跳转页面再截图
|
|
133
342
|
weapp screenshot -p /path/to/project --page pages/index/index
|
|
134
343
|
|
|
344
|
+
# 输出整页长截图
|
|
345
|
+
weapp screenshot -p /path/to/project --page pages/index/index --full-page -o full.png
|
|
346
|
+
|
|
135
347
|
# 以 JSON 输出便于脚本解析
|
|
136
348
|
weapp screenshot -p /path/to/project --json
|
|
137
349
|
`, `
|
|
@@ -141,6 +353,7 @@ ${colors.bold("Options:")}
|
|
|
141
353
|
-p, --project <path> Project path (default: current directory)
|
|
142
354
|
-o, --output <path> Output file path for screenshot
|
|
143
355
|
--page <path> Navigate to page before taking screenshot
|
|
356
|
+
--full-page Capture a stitched full-page screenshot
|
|
144
357
|
-t, --timeout <ms> Connection timeout in milliseconds (default: 30000)
|
|
145
358
|
--json Output as JSON format
|
|
146
359
|
--lang <lang> Language: zh | en (default: zh)
|
|
@@ -156,6 +369,9 @@ ${colors.bold("Examples:")}
|
|
|
156
369
|
# Navigate to page first
|
|
157
370
|
weapp screenshot -p /path/to/project --page pages/index/index
|
|
158
371
|
|
|
372
|
+
# Capture a stitched full-page screenshot
|
|
373
|
+
weapp screenshot -p /path/to/project --page pages/index/index --full-page -o full.png
|
|
374
|
+
|
|
159
375
|
# JSON output for parsing
|
|
160
376
|
weapp screenshot -p /path/to/project --json
|
|
161
377
|
`));
|
|
@@ -165,11 +381,15 @@ ${colors.bold("Examples:")}
|
|
|
165
381
|
*/
|
|
166
382
|
function parseScreenshotArgs(argv) {
|
|
167
383
|
const parsed = parseAutomatorArgs(argv);
|
|
384
|
+
const outputPath = readOptionValue(argv, "-o") || readOptionValue(argv, "--output");
|
|
385
|
+
const page = readOptionValue(argv, "--page");
|
|
386
|
+
const fullPage = argv.includes("--full-page");
|
|
168
387
|
return {
|
|
169
388
|
projectPath: parsed.projectPath,
|
|
170
|
-
timeout: parsed.timeout,
|
|
171
|
-
outputPath
|
|
172
|
-
page:
|
|
389
|
+
...parsed.timeout ? { timeout: parsed.timeout } : {},
|
|
390
|
+
...outputPath ? { outputPath } : {},
|
|
391
|
+
...page ? { page } : {},
|
|
392
|
+
...fullPage ? { fullPage: true } : {}
|
|
173
393
|
};
|
|
174
394
|
}
|
|
175
395
|
/**
|
|
@@ -182,7 +402,7 @@ async function runScreenshot(argv) {
|
|
|
182
402
|
}
|
|
183
403
|
const options = parseScreenshotArgs(argv);
|
|
184
404
|
const isJsonOutput = argv.includes("--json");
|
|
185
|
-
const { takeScreenshot } = await import("./commands-
|
|
405
|
+
const { takeScreenshot } = await import("./commands-Bg8hQbPE.js");
|
|
186
406
|
const result = await takeScreenshot(options);
|
|
187
407
|
if (isJsonOutput) {
|
|
188
408
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -461,7 +681,11 @@ const COMMAND_DEFINITIONS = {
|
|
|
461
681
|
}
|
|
462
682
|
})
|
|
463
683
|
};
|
|
464
|
-
const AUTOMATOR_COMMAND_NAMES = [
|
|
684
|
+
const AUTOMATOR_COMMAND_NAMES = [
|
|
685
|
+
"screenshot",
|
|
686
|
+
"compare",
|
|
687
|
+
...Object.keys(COMMAND_DEFINITIONS)
|
|
688
|
+
];
|
|
465
689
|
const AUTOMATOR_COMMAND_SET = new Set(AUTOMATOR_COMMAND_NAMES);
|
|
466
690
|
/**
|
|
467
691
|
* @description 判断是否属于 automator 子命令。
|
|
@@ -500,6 +724,10 @@ async function runAutomatorCommand(command, argv) {
|
|
|
500
724
|
await runScreenshot(argv);
|
|
501
725
|
return;
|
|
502
726
|
}
|
|
727
|
+
if (command === "compare") {
|
|
728
|
+
await runCompare(argv);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
503
731
|
const definition = COMMAND_DEFINITIONS[command];
|
|
504
732
|
if (!definition) throw new Error(i18nText(`未知 automator 命令: ${command}`, `Unknown automator command: ${command}`));
|
|
505
733
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
@@ -578,7 +806,7 @@ async function promptForCliPath() {
|
|
|
578
806
|
try {
|
|
579
807
|
const normalizedPath = await createCustomConfig({ cliPath });
|
|
580
808
|
logger_default.info(`全局配置存储位置:${colors.green(defaultCustomConfigFilePath)}`);
|
|
581
|
-
if (!await fs.pathExists(normalizedPath)) logger_default.warn("在当前路径未找到微信web开发者命令行工具,请确认路径是否正确。");
|
|
809
|
+
if (!await fs$1.pathExists(normalizedPath)) logger_default.warn("在当前路径未找到微信web开发者命令行工具,请确认路径是否正确。");
|
|
582
810
|
return normalizedPath;
|
|
583
811
|
} catch (error) {
|
|
584
812
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -650,7 +878,7 @@ async function handleConfigCommand(argv) {
|
|
|
650
878
|
}
|
|
651
879
|
if (action === "doctor") {
|
|
652
880
|
const rawConfig = await readCustomConfig();
|
|
653
|
-
const hasConfigFile = await fs.pathExists(defaultCustomConfigFilePath);
|
|
881
|
+
const hasConfigFile = await fs$1.pathExists(defaultCustomConfigFilePath);
|
|
654
882
|
const resolvedCli = await resolveCliPath();
|
|
655
883
|
const hasCustomCli = typeof rawConfig.cliPath === "string" && rawConfig.cliPath.length > 0;
|
|
656
884
|
const hasValidCli = Boolean(resolvedCli.cliPath);
|
|
@@ -673,7 +901,7 @@ async function handleConfigCommand(argv) {
|
|
|
673
901
|
const outputPath = argv[1];
|
|
674
902
|
const config = await readCustomConfig();
|
|
675
903
|
if (outputPath) {
|
|
676
|
-
await fs.writeJSON(outputPath, config, {
|
|
904
|
+
await fs$1.writeJSON(outputPath, config, {
|
|
677
905
|
spaces: 2,
|
|
678
906
|
encoding: "utf8"
|
|
679
907
|
});
|
|
@@ -686,7 +914,7 @@ async function handleConfigCommand(argv) {
|
|
|
686
914
|
if (action === "import") {
|
|
687
915
|
const inputPath = argv[1];
|
|
688
916
|
if (!inputPath) throw new Error(i18nText("请提供导入文件路径,例如:weapp config import ./weapp-config.json", "Please provide import file path, e.g. weapp config import ./weapp-config.json"));
|
|
689
|
-
const imported = await fs.readJSON(inputPath);
|
|
917
|
+
const imported = await fs$1.readJSON(inputPath);
|
|
690
918
|
if (!imported || typeof imported !== "object") throw new Error(i18nText("导入文件格式无效:应为 JSON 对象", "Invalid import file format: expected a JSON object"));
|
|
691
919
|
const candidate = imported;
|
|
692
920
|
await overwriteCustomConfig({
|
|
@@ -1193,23 +1421,7 @@ function createCli() {
|
|
|
1193
1421
|
for (const command of WECHAT_CLI_COMMAND_NAMES) cli.command(command, "微信开发者工具官方命令透传").allowUnknownOptions();
|
|
1194
1422
|
for (const command of MINIDEV_NAMESPACE_COMMAND_NAMES) cli.command(`${command} [...args]`, "支付宝 minidev 命令透传").allowUnknownOptions();
|
|
1195
1423
|
cli.command(`${CONFIG_COMMAND_NAME} [...args]`, "配置 weapp-ide-cli").allowUnknownOptions();
|
|
1196
|
-
for (const command of [
|
|
1197
|
-
"screenshot",
|
|
1198
|
-
"navigate",
|
|
1199
|
-
"redirect",
|
|
1200
|
-
"back",
|
|
1201
|
-
"relaunch",
|
|
1202
|
-
"switch-tab",
|
|
1203
|
-
"page-stack",
|
|
1204
|
-
"current-page",
|
|
1205
|
-
"system-info",
|
|
1206
|
-
"page-data",
|
|
1207
|
-
"tap",
|
|
1208
|
-
"input",
|
|
1209
|
-
"scroll",
|
|
1210
|
-
"audit",
|
|
1211
|
-
"remote"
|
|
1212
|
-
]) cli.command(`${command} [...args]`, "automator 增强命令").allowUnknownOptions();
|
|
1424
|
+
for (const command of AUTOMATOR_COMMAND_NAMES) cli.command(`${command} [...args]`, "automator 增强命令").allowUnknownOptions();
|
|
1213
1425
|
return cli;
|
|
1214
1426
|
}
|
|
1215
1427
|
async function handleHelpCommand(args) {
|
|
@@ -1222,6 +1434,10 @@ async function handleHelpCommand(args) {
|
|
|
1222
1434
|
printScreenshotHelp();
|
|
1223
1435
|
return;
|
|
1224
1436
|
}
|
|
1437
|
+
if (targetCommand === "compare") {
|
|
1438
|
+
printCompareHelp();
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1225
1441
|
if (isAutomatorCommand(targetCommand)) {
|
|
1226
1442
|
const help = getAutomatorCommandHelp(targetCommand);
|
|
1227
1443
|
if (help) {
|
|
@@ -1300,4 +1516,4 @@ async function parse(argv) {
|
|
|
1300
1516
|
await runWechatCliWithRetry(cliPath, formattedArgv);
|
|
1301
1517
|
}
|
|
1302
1518
|
//#endregion
|
|
1303
|
-
export {
|
|
1519
|
+
export { parseCompareArgs as A, isWeappIdeTopLevelCommand as C, runAutomatorCommand as D, isAutomatorCommand as E, parseAutomatorArgs as M, readOptionValue as N, parseScreenshotArgs as O, removeOption as P, WECHAT_CLI_COMMAND_NAMES as S, getAutomatorCommandHelp as T, handleConfigCommand as _, createWechatIdeLoginRequiredExitError as a, MINIDEV_NAMESPACE_COMMAND_NAMES as b, formatWechatIdeLoginRequiredError as c, runMinidev as d, execute as f, startForwardConsole as g, transformArgv as h, runWechatCliWithRetry as i, printCompareHelp as j, printScreenshotHelp as k, isWechatIdeLoginRequiredError as l, createPathCompat as m, parse as n, extractExecutionErrorText as o, createAlias as p, validateWechatCliCommandArgs as r, formatRetryHotkeyPrompt as s, createCli as t, waitForRetryKeypress as u, promptForCliPath as v, AUTOMATOR_COMMAND_NAMES as w, WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES as x, CONFIG_COMMAND_NAME as y };
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as parse } from "./cli-
|
|
1
|
+
import { Q as logger_default } from "./commands--vppy2pX.js";
|
|
2
|
+
import { n as parse } from "./cli-DKrUhSmB.js";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
//#region src/cli.ts
|
|
5
|
-
|
|
5
|
+
const argv = process.argv.slice(2);
|
|
6
|
+
try {
|
|
7
|
+
await parse(argv);
|
|
8
|
+
} catch (err) {
|
|
6
9
|
logger_default.error(err);
|
|
7
|
-
if (typeof err?.exitCode === "number")
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (typeof err?.code === "number") {
|
|
12
|
-
process.exitCode = err.code;
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
process.exitCode = 1;
|
|
16
|
-
});
|
|
10
|
+
if (typeof err?.exitCode === "number") process.exitCode = err.exitCode;
|
|
11
|
+
else if (typeof err?.code === "number") process.exitCode = err.code;
|
|
12
|
+
else process.exitCode = 1;
|
|
13
|
+
}
|
|
17
14
|
//#endregion
|
|
18
15
|
export {};
|
|
@@ -1,23 +1,13 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
1
5
|
import { Launcher } from "@weapp-vite/miniprogram-automator";
|
|
2
|
-
import fs from "
|
|
6
|
+
import { fs as fs$1 } from "@weapp-core/shared";
|
|
3
7
|
import logger, { colors } from "@weapp-core/logger";
|
|
4
|
-
import os from "node:os";
|
|
5
8
|
import process from "node:process";
|
|
6
|
-
import path from "pathe";
|
|
7
|
-
import {
|
|
8
|
-
import fs$1 from "node:fs/promises";
|
|
9
|
-
//#region \0rolldown/runtime.js
|
|
10
|
-
var __defProp = Object.defineProperty;
|
|
11
|
-
var __exportAll = (all, no_symbols) => {
|
|
12
|
-
let target = {};
|
|
13
|
-
for (var name in all) __defProp(target, name, {
|
|
14
|
-
get: all[name],
|
|
15
|
-
enumerable: true
|
|
16
|
-
});
|
|
17
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
18
|
-
return target;
|
|
19
|
-
};
|
|
20
|
-
//#endregion
|
|
9
|
+
import path$1 from "pathe";
|
|
10
|
+
import { PNG } from "pngjs";
|
|
21
11
|
//#region src/logger.ts
|
|
22
12
|
var logger_default = logger;
|
|
23
13
|
//#endregion
|
|
@@ -41,6 +31,18 @@ function isOperatingSystemSupported(osName = os.type()) {
|
|
|
41
31
|
* @description 当前系统名称
|
|
42
32
|
*/
|
|
43
33
|
const operatingSystemName = os.type();
|
|
34
|
+
async function getFirstBinaryPath(command) {
|
|
35
|
+
const pathDirs = (process.env.PATH || "").split(path$1.delimiter);
|
|
36
|
+
for (const dir of pathDirs) {
|
|
37
|
+
const fullPath = path$1.join(dir, command);
|
|
38
|
+
try {
|
|
39
|
+
await fs$1.access(fullPath, fs$1.constants.X_OK);
|
|
40
|
+
return fullPath;
|
|
41
|
+
} catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
44
46
|
function createLinuxCliResolver() {
|
|
45
47
|
let resolvedPath;
|
|
46
48
|
let attempted = false;
|
|
@@ -78,26 +80,14 @@ async function getDefaultCliPath(targetOs = operatingSystemName) {
|
|
|
78
80
|
const resolver = cliPathResolvers[targetOs];
|
|
79
81
|
return await resolver();
|
|
80
82
|
}
|
|
81
|
-
async function getFirstBinaryPath(command) {
|
|
82
|
-
const pathDirs = (process.env.PATH || "").split(path.delimiter);
|
|
83
|
-
for (const dir of pathDirs) {
|
|
84
|
-
const fullPath = path.join(dir, command);
|
|
85
|
-
try {
|
|
86
|
-
await fs.access(fullPath, fs.constants.X_OK);
|
|
87
|
-
return fullPath;
|
|
88
|
-
} catch {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
83
|
//#endregion
|
|
94
84
|
//#region src/utils/path.ts
|
|
95
85
|
/**
|
|
96
86
|
* @description 解析为绝对路径(基于当前工作目录)
|
|
97
87
|
*/
|
|
98
88
|
function resolvePath(filePath) {
|
|
99
|
-
if (path.isAbsolute(filePath)) return filePath;
|
|
100
|
-
return path.resolve(process.cwd(), filePath);
|
|
89
|
+
if (path$1.isAbsolute(filePath)) return filePath;
|
|
90
|
+
return path$1.resolve(process.cwd(), filePath);
|
|
101
91
|
}
|
|
102
92
|
//#endregion
|
|
103
93
|
//#region src/config/paths.ts
|
|
@@ -105,17 +95,39 @@ const homedir = os.homedir();
|
|
|
105
95
|
/**
|
|
106
96
|
* @description 默认自定义配置目录
|
|
107
97
|
*/
|
|
108
|
-
const defaultCustomConfigDirPath = path.join(homedir, ".weapp-ide-cli");
|
|
98
|
+
const defaultCustomConfigDirPath = path$1.join(homedir, ".weapp-ide-cli");
|
|
109
99
|
/**
|
|
110
100
|
* @description 默认自定义配置文件路径
|
|
111
101
|
*/
|
|
112
|
-
const defaultCustomConfigFilePath = path.join(defaultCustomConfigDirPath, "config.json");
|
|
102
|
+
const defaultCustomConfigFilePath = path$1.join(defaultCustomConfigDirPath, "config.json");
|
|
113
103
|
//#endregion
|
|
114
104
|
//#region src/config/custom.ts
|
|
115
105
|
const JSON_OPTIONS = {
|
|
116
106
|
encoding: "utf8",
|
|
117
107
|
spaces: 2
|
|
118
108
|
};
|
|
109
|
+
async function readCustomConfig() {
|
|
110
|
+
if (!await fs$1.pathExists(defaultCustomConfigFilePath)) return {};
|
|
111
|
+
try {
|
|
112
|
+
const config = await fs$1.readJSON(defaultCustomConfigFilePath);
|
|
113
|
+
if (!config || typeof config !== "object") return {};
|
|
114
|
+
const candidate = config;
|
|
115
|
+
const next = {};
|
|
116
|
+
if (typeof candidate.cliPath === "string" && candidate.cliPath.trim()) next.cliPath = candidate.cliPath.trim();
|
|
117
|
+
if (candidate.locale === "zh" || candidate.locale === "en") next.locale = candidate.locale;
|
|
118
|
+
return next;
|
|
119
|
+
} catch {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function writeCustomConfig(patch, options = {}) {
|
|
124
|
+
const nextConfig = {
|
|
125
|
+
...options.replace ? {} : await readCustomConfig(),
|
|
126
|
+
...patch
|
|
127
|
+
};
|
|
128
|
+
await fs$1.ensureDir(defaultCustomConfigDirPath);
|
|
129
|
+
await fs$1.writeJSON(defaultCustomConfigFilePath, nextConfig, JSON_OPTIONS);
|
|
130
|
+
}
|
|
119
131
|
/**
|
|
120
132
|
* @description 写入自定义 CLI 路径配置
|
|
121
133
|
*/
|
|
@@ -152,39 +164,14 @@ async function overwriteCustomConfig(config) {
|
|
|
152
164
|
if (config.locale === "zh" || config.locale === "en") nextConfig.locale = config.locale;
|
|
153
165
|
await writeCustomConfig(nextConfig, { replace: true });
|
|
154
166
|
}
|
|
155
|
-
/**
|
|
156
|
-
* @description 读取原始自定义配置。
|
|
157
|
-
*/
|
|
158
|
-
async function readCustomConfig() {
|
|
159
|
-
if (!await fs.pathExists(defaultCustomConfigFilePath)) return {};
|
|
160
|
-
try {
|
|
161
|
-
const config = await fs.readJSON(defaultCustomConfigFilePath);
|
|
162
|
-
if (!config || typeof config !== "object") return {};
|
|
163
|
-
const candidate = config;
|
|
164
|
-
const next = {};
|
|
165
|
-
if (typeof candidate.cliPath === "string" && candidate.cliPath.trim()) next.cliPath = candidate.cliPath.trim();
|
|
166
|
-
if (candidate.locale === "zh" || candidate.locale === "en") next.locale = candidate.locale;
|
|
167
|
-
return next;
|
|
168
|
-
} catch {
|
|
169
|
-
return {};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
async function writeCustomConfig(patch, options = {}) {
|
|
173
|
-
const nextConfig = {
|
|
174
|
-
...options.replace ? {} : await readCustomConfig(),
|
|
175
|
-
...patch
|
|
176
|
-
};
|
|
177
|
-
await fs.ensureDir(defaultCustomConfigDirPath);
|
|
178
|
-
await fs.writeJSON(defaultCustomConfigFilePath, nextConfig, JSON_OPTIONS);
|
|
179
|
-
}
|
|
180
167
|
//#endregion
|
|
181
168
|
//#region src/config/resolver.ts
|
|
182
169
|
/**
|
|
183
170
|
* @description 读取并解析 CLI 配置(自定义优先)
|
|
184
171
|
*/
|
|
185
172
|
async function getConfig() {
|
|
186
|
-
if (await fs.pathExists(defaultCustomConfigFilePath)) try {
|
|
187
|
-
const config = await fs.readJSON(defaultCustomConfigFilePath);
|
|
173
|
+
if (await fs$1.pathExists(defaultCustomConfigFilePath)) try {
|
|
174
|
+
const config = await fs$1.readJSON(defaultCustomConfigFilePath);
|
|
188
175
|
const cliPath = typeof config.cliPath === "string" ? config.cliPath.trim() : "";
|
|
189
176
|
const locale = config.locale === "zh" || config.locale === "en" ? config.locale : void 0;
|
|
190
177
|
if (cliPath) {
|
|
@@ -231,7 +218,7 @@ async function resolveCliPath() {
|
|
|
231
218
|
source: config.source
|
|
232
219
|
};
|
|
233
220
|
return {
|
|
234
|
-
cliPath: await fs.pathExists(config.cliPath) ? config.cliPath : null,
|
|
221
|
+
cliPath: await fs$1.pathExists(config.cliPath) ? config.cliPath : null,
|
|
235
222
|
source: config.source
|
|
236
223
|
};
|
|
237
224
|
}
|
|
@@ -245,6 +232,10 @@ const LOGIN_REQUIRED_CN_RE = /需要重新登录/;
|
|
|
245
232
|
const LOGIN_REQUIRED_EN_RE = /need\s+re-?login|re-?login/i;
|
|
246
233
|
const LOGIN_REQUIRED_CODE_RE = /code\s*[:=]\s*(\d+)/i;
|
|
247
234
|
const DEVTOOLS_HTTP_PORT_ERROR = "Failed to launch wechat web devTools, please make sure http port is open";
|
|
235
|
+
const DEVTOOLS_EXTENSION_CONTEXT_INVALIDATED_RE = /Extension context invalidated/i;
|
|
236
|
+
const AUTOMATOR_LAUNCH_TIMEOUT_RE = /Wait timed out after \d+ ms/i;
|
|
237
|
+
const AUTOMATOR_WS_CONNECT_RE = /Failed connecting to ws:\/\/127\.0\.0\.1:\d+/i;
|
|
238
|
+
const DEVTOOLS_PROTOCOL_TIMEOUT_RE = /DevTools did not respond to protocol method (\S+) within \d+ms/i;
|
|
248
239
|
const DEVTOOLS_INFRA_ERROR_PATTERNS = [
|
|
249
240
|
/listen EPERM/i,
|
|
250
241
|
/operation not permitted 0\.0\.0\.0/i,
|
|
@@ -252,6 +243,8 @@ const DEVTOOLS_INFRA_ERROR_PATTERNS = [
|
|
|
252
243
|
/ECONNREFUSED/i,
|
|
253
244
|
/connect ECONNREFUSED/i
|
|
254
245
|
];
|
|
246
|
+
const DEFAULT_WECHAT_DEVTOOLS_WS_ENDPOINT = "ws://127.0.0.1:9420";
|
|
247
|
+
const AUTOMATOR_SESSION_DIR = path.join(os.tmpdir(), "weapp-vite-automator-sessions");
|
|
255
248
|
const DEVTOOLS_LOGIN_REQUIRED_PATTERNS = [
|
|
256
249
|
/code\s*[:=]\s*10/i,
|
|
257
250
|
/需要重新登录/,
|
|
@@ -271,6 +264,38 @@ function extractErrorText(error) {
|
|
|
271
264
|
candidate.stdout
|
|
272
265
|
].filter((value) => typeof value === "string" && value.trim().length > 0).join("\n");
|
|
273
266
|
}
|
|
267
|
+
function resolveAutomatorSessionFilePath(projectPath) {
|
|
268
|
+
const normalizedProjectPath = path.resolve(projectPath);
|
|
269
|
+
const encodedProjectPath = Buffer.from(normalizedProjectPath).toString("base64url");
|
|
270
|
+
return path.join(AUTOMATOR_SESSION_DIR, `${encodedProjectPath}.json`);
|
|
271
|
+
}
|
|
272
|
+
async function persistAutomatorSession(projectPath, wsEndpoint) {
|
|
273
|
+
const filePath = resolveAutomatorSessionFilePath(projectPath);
|
|
274
|
+
const payload = {
|
|
275
|
+
projectPath: path.resolve(projectPath),
|
|
276
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
277
|
+
wsEndpoint
|
|
278
|
+
};
|
|
279
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
280
|
+
await fs.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
281
|
+
}
|
|
282
|
+
async function readPersistedAutomatorSession(projectPath) {
|
|
283
|
+
const filePath = resolveAutomatorSessionFilePath(projectPath);
|
|
284
|
+
try {
|
|
285
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
286
|
+
const payload = JSON.parse(raw);
|
|
287
|
+
if (payload.projectPath !== path.resolve(projectPath) || typeof payload.wsEndpoint !== "string" || !payload.wsEndpoint.trim()) return null;
|
|
288
|
+
return payload;
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function removePersistedAutomatorSession(projectPath) {
|
|
294
|
+
const filePath = resolveAutomatorSessionFilePath(projectPath);
|
|
295
|
+
try {
|
|
296
|
+
await fs.rm(filePath, { force: true });
|
|
297
|
+
} catch {}
|
|
298
|
+
}
|
|
274
299
|
/**
|
|
275
300
|
* @description 提取登录失效时最适合展示给用户的一行信息。
|
|
276
301
|
*/
|
|
@@ -291,6 +316,40 @@ function isDevtoolsHttpPortError(error) {
|
|
|
291
316
|
return message.includes(DEVTOOLS_HTTP_PORT_ERROR) || DEVTOOLS_INFRA_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
292
317
|
}
|
|
293
318
|
/**
|
|
319
|
+
* @description 判断错误是否属于开发者工具 automator 扩展上下文尚未就绪。
|
|
320
|
+
*/
|
|
321
|
+
function isDevtoolsExtensionContextInvalidatedError(error) {
|
|
322
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
323
|
+
return DEVTOOLS_EXTENSION_CONTEXT_INVALIDATED_RE.test(message);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* @description 判断错误是否属于可重试的 automator 启动抖动。
|
|
327
|
+
*/
|
|
328
|
+
function isRetryableAutomatorLaunchError(error) {
|
|
329
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
330
|
+
return DEVTOOLS_EXTENSION_CONTEXT_INVALIDATED_RE.test(message) || AUTOMATOR_LAUNCH_TIMEOUT_RE.test(message) || AUTOMATOR_WS_CONNECT_RE.test(message);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* @description 判断错误是否属于开发者工具 websocket 连接失败。
|
|
334
|
+
*/
|
|
335
|
+
function isAutomatorWsConnectError(error) {
|
|
336
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
337
|
+
return AUTOMATOR_WS_CONNECT_RE.test(message);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* @description 判断错误是否属于开发者工具协议调用超时。
|
|
341
|
+
*/
|
|
342
|
+
function isAutomatorProtocolTimeoutError(error) {
|
|
343
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
344
|
+
return DEVTOOLS_PROTOCOL_TIMEOUT_RE.test(message);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* @description 提取协议超时的方法名。
|
|
348
|
+
*/
|
|
349
|
+
function getAutomatorProtocolTimeoutMethod(error) {
|
|
350
|
+
return (error instanceof Error ? error.message : String(error)).match(DEVTOOLS_PROTOCOL_TIMEOUT_RE)?.[1];
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
294
353
|
* @description 判断错误是否属于开发者工具登录失效。
|
|
295
354
|
*/
|
|
296
355
|
function isAutomatorLoginError(error) {
|
|
@@ -315,13 +374,40 @@ function formatAutomatorLoginError(error) {
|
|
|
315
374
|
* @description 基于当前配置解析 CLI 路径,并通过现代化 automator 入口启动会话。
|
|
316
375
|
*/
|
|
317
376
|
async function launchAutomator(options) {
|
|
318
|
-
const { cliPath, projectPath, timeout = 3e4 } = options;
|
|
377
|
+
const { cliPath, projectPath, timeout = 3e4, trustProject = false } = options;
|
|
319
378
|
const resolvedCliPath = cliPath ?? (await resolveCliPath()).cliPath ?? void 0;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
379
|
+
const launcher = new Launcher();
|
|
380
|
+
let lastError = null;
|
|
381
|
+
for (let attempt = 0; attempt < 2; attempt += 1) try {
|
|
382
|
+
const miniProgram = await launcher.launch({
|
|
383
|
+
cliPath: resolvedCliPath,
|
|
384
|
+
projectPath,
|
|
385
|
+
timeout,
|
|
386
|
+
trustProject
|
|
387
|
+
});
|
|
388
|
+
const sessionMetadata = Reflect.get(miniProgram, "__WEAPP_VITE_SESSION_METADATA");
|
|
389
|
+
if (typeof sessionMetadata?.wsEndpoint === "string" && sessionMetadata.wsEndpoint) await persistAutomatorSession(projectPath, sessionMetadata.wsEndpoint);
|
|
390
|
+
return miniProgram;
|
|
391
|
+
} catch (error) {
|
|
392
|
+
lastError = error;
|
|
393
|
+
if (!isRetryableAutomatorLaunchError(error) || attempt === 1) throw error;
|
|
394
|
+
}
|
|
395
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* @description 连接当前项目已打开的开发者工具自动化会话,不触发新的 IDE 拉起。
|
|
399
|
+
*/
|
|
400
|
+
async function connectOpenedAutomator(options) {
|
|
401
|
+
const { projectPath } = options;
|
|
402
|
+
const launcher = new Launcher();
|
|
403
|
+
const persistedSession = await readPersistedAutomatorSession(projectPath);
|
|
404
|
+
const wsEndpoint = persistedSession?.wsEndpoint ?? DEFAULT_WECHAT_DEVTOOLS_WS_ENDPOINT;
|
|
405
|
+
try {
|
|
406
|
+
return await launcher.connect({ wsEndpoint });
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (persistedSession) await removePersistedAutomatorSession(projectPath);
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
325
411
|
}
|
|
326
412
|
//#endregion
|
|
327
413
|
//#region src/i18n.ts
|
|
@@ -390,6 +476,7 @@ function readLangOption(argv) {
|
|
|
390
476
|
}
|
|
391
477
|
//#endregion
|
|
392
478
|
//#region src/cli/automator-session.ts
|
|
479
|
+
const sharedMiniProgramSessions = /* @__PURE__ */ new Map();
|
|
393
480
|
function normalizeMiniProgramConnectionError(error) {
|
|
394
481
|
if (isAutomatorLoginError(error)) {
|
|
395
482
|
logger_default.error(i18nText("检测到微信开发者工具登录状态失效,请先登录后重试。", "Wechat DevTools login has expired. Please login and retry."));
|
|
@@ -401,22 +488,106 @@ function normalizeMiniProgramConnectionError(error) {
|
|
|
401
488
|
logger_default.warn(i18nText("请在微信开发者工具中:设置 -> 安全设置 -> 开启服务端口", "Please enable service port in Wechat DevTools: Settings -> Security -> Service Port"));
|
|
402
489
|
return /* @__PURE__ */ new Error("DEVTOOLS_HTTP_PORT_ERROR");
|
|
403
490
|
}
|
|
491
|
+
if (isDevtoolsExtensionContextInvalidatedError(error)) {
|
|
492
|
+
logger_default.error(i18nText("微信开发者工具自动化上下文尚未就绪,通常是刚启动或正在重载。", "Wechat DevTools automation context is not ready yet, usually because the IDE has just started or is still reloading."));
|
|
493
|
+
logger_default.warn(i18nText("请稍后重试;若持续失败,关闭多余的开发者工具窗口后重试。", "Please retry shortly. If it keeps failing, close extra DevTools windows and try again."));
|
|
494
|
+
return /* @__PURE__ */ new Error("DEVTOOLS_EXTENSION_CONTEXT_INVALIDATED");
|
|
495
|
+
}
|
|
496
|
+
if (isAutomatorWsConnectError(error)) {
|
|
497
|
+
logger_default.error(i18nText("无法连接到当前项目的微信开发者工具自动化 websocket。", "Cannot connect to the Wechat DevTools automation websocket for the current project."));
|
|
498
|
+
logger_default.warn(i18nText("请确认当前打开的是目标项目;若之前跑过其他 e2e / screenshot 任务,关闭多余的微信开发者工具窗口,或结束残留的 `wechatwebdevtools cli auto --project ...` 进程后重试。", "Please confirm the current DevTools window is the target project. If you recently ran other e2e or screenshot tasks, close extra DevTools windows or stop stale `wechatwebdevtools cli auto --project ...` processes and retry."));
|
|
499
|
+
return /* @__PURE__ */ new Error("DEVTOOLS_WS_CONNECT_ERROR");
|
|
500
|
+
}
|
|
501
|
+
if (isAutomatorProtocolTimeoutError(error)) {
|
|
502
|
+
const method = getAutomatorProtocolTimeoutMethod(error) ?? "unknown";
|
|
503
|
+
logger_default.error(i18nText(`微信开发者工具在协议调用 ${method} 上超时,未按预期返回结果。`, `Wechat DevTools timed out while executing protocol method ${method} and did not return a result.`));
|
|
504
|
+
logger_default.warn(i18nText("这通常表示当前 DevTools 自动化会话已卡住、窗口不在目标项目、或当前 DevTools 版本对该协议调用无响应。请重开目标项目窗口后重试;若仍复现,优先记录当前 DevTools 版本与协议方法名继续排查。", "This usually means the current DevTools automation session is stuck, the window is not on the target project, or the current DevTools version is not responding to that protocol method. Reopen the target project window and retry. If it still reproduces, record the current DevTools version and protocol method name for follow-up debugging."));
|
|
505
|
+
return /* @__PURE__ */ new Error("DEVTOOLS_PROTOCOL_TIMEOUT");
|
|
506
|
+
}
|
|
404
507
|
return error instanceof Error ? error : new Error(String(error));
|
|
405
508
|
}
|
|
406
509
|
/**
|
|
407
510
|
* @description 建立 automator 会话,并统一处理常见连接错误提示。
|
|
408
511
|
*/
|
|
409
512
|
async function connectMiniProgram(options) {
|
|
410
|
-
try {
|
|
513
|
+
if (options.preferOpenedSession === false) try {
|
|
411
514
|
return await launchAutomator(options);
|
|
412
515
|
} catch (error) {
|
|
413
516
|
throw normalizeMiniProgramConnectionError(error);
|
|
414
517
|
}
|
|
518
|
+
try {
|
|
519
|
+
return await connectOpenedAutomator(options);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
const normalizedOpenSessionError = normalizeMiniProgramConnectionError(error);
|
|
522
|
+
if (normalizedOpenSessionError instanceof Error && normalizedOpenSessionError.message === "DEVTOOLS_PROTOCOL_TIMEOUT") throw normalizedOpenSessionError;
|
|
523
|
+
try {
|
|
524
|
+
return await launchAutomator(options);
|
|
525
|
+
} catch (launchError) {
|
|
526
|
+
throw normalizeMiniProgramConnectionError(launchError);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* @description 获取指定项目的共享 automator 会话;若不存在则自动创建。
|
|
532
|
+
*/
|
|
533
|
+
async function acquireSharedMiniProgram(options) {
|
|
534
|
+
const existing = sharedMiniProgramSessions.get(options.projectPath);
|
|
535
|
+
if (existing) {
|
|
536
|
+
existing.refs += 1;
|
|
537
|
+
return await existing.session;
|
|
538
|
+
}
|
|
539
|
+
const session = connectMiniProgram(options);
|
|
540
|
+
const entry = {
|
|
541
|
+
refs: 1,
|
|
542
|
+
session
|
|
543
|
+
};
|
|
544
|
+
sharedMiniProgramSessions.set(options.projectPath, entry);
|
|
545
|
+
try {
|
|
546
|
+
return await session;
|
|
547
|
+
} catch (error) {
|
|
548
|
+
sharedMiniProgramSessions.delete(options.projectPath);
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* @description 释放指定项目的共享会话引用;会话对象会继续缓存,直到显式关闭或重置。
|
|
554
|
+
*/
|
|
555
|
+
function releaseSharedMiniProgram(projectPath) {
|
|
556
|
+
const entry = sharedMiniProgramSessions.get(projectPath);
|
|
557
|
+
if (!entry) return;
|
|
558
|
+
entry.refs = Math.max(0, entry.refs - 1);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* @description 关闭并移除指定项目的共享 automator 会话。
|
|
562
|
+
*/
|
|
563
|
+
async function closeSharedMiniProgram(projectPath) {
|
|
564
|
+
const entry = sharedMiniProgramSessions.get(projectPath);
|
|
565
|
+
if (!entry) return;
|
|
566
|
+
sharedMiniProgramSessions.delete(projectPath);
|
|
567
|
+
(await entry.session.catch(() => null))?.disconnect();
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* @description 获取当前共享会话数量,供测试断言使用。
|
|
571
|
+
*/
|
|
572
|
+
function getSharedMiniProgramSessionCount() {
|
|
573
|
+
return sharedMiniProgramSessions.size;
|
|
415
574
|
}
|
|
416
575
|
/**
|
|
417
576
|
* @description 统一管理 automator 会话生命周期。
|
|
418
577
|
*/
|
|
419
578
|
async function withMiniProgram(options, runner) {
|
|
579
|
+
if (options.miniProgram) return await runner(options.miniProgram);
|
|
580
|
+
if (options.sharedSession) {
|
|
581
|
+
const miniProgram = await acquireSharedMiniProgram(options);
|
|
582
|
+
try {
|
|
583
|
+
return await runner(miniProgram);
|
|
584
|
+
} catch (error) {
|
|
585
|
+
await closeSharedMiniProgram(options.projectPath);
|
|
586
|
+
throw normalizeMiniProgramConnectionError(error);
|
|
587
|
+
} finally {
|
|
588
|
+
releaseSharedMiniProgram(options.projectPath);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
420
591
|
let miniProgram = null;
|
|
421
592
|
try {
|
|
422
593
|
miniProgram = await connectMiniProgram(options);
|
|
@@ -428,24 +599,112 @@ async function withMiniProgram(options, runner) {
|
|
|
428
599
|
}
|
|
429
600
|
}
|
|
430
601
|
//#endregion
|
|
602
|
+
//#region src/cli/fullPageScreenshot.ts
|
|
603
|
+
function decodeScreenshotBuffer(raw) {
|
|
604
|
+
const buffer = typeof raw === "string" ? Buffer.from(raw, "base64") : Buffer.from(raw);
|
|
605
|
+
if (buffer.length === 0) throw new Error("Failed to capture screenshot");
|
|
606
|
+
return buffer;
|
|
607
|
+
}
|
|
608
|
+
function toPositiveNumber(value) {
|
|
609
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
610
|
+
}
|
|
611
|
+
function createScrollPositions(totalHeight, viewportHeight) {
|
|
612
|
+
if (totalHeight <= viewportHeight) return [0];
|
|
613
|
+
const positions = [];
|
|
614
|
+
const lastStart = Math.max(totalHeight - viewportHeight, 0);
|
|
615
|
+
for (let scrollTop = 0; scrollTop < lastStart; scrollTop += viewportHeight) positions.push(scrollTop);
|
|
616
|
+
if (positions.at(-1) !== lastStart) positions.push(lastStart);
|
|
617
|
+
return positions;
|
|
618
|
+
}
|
|
619
|
+
function cropPngRows(source, startRow, rowCount) {
|
|
620
|
+
const cropped = new PNG({
|
|
621
|
+
width: source.width,
|
|
622
|
+
height: rowCount
|
|
623
|
+
});
|
|
624
|
+
const bytesPerRow = source.width * 4;
|
|
625
|
+
for (let row = 0; row < rowCount; row += 1) {
|
|
626
|
+
const sourceStart = (startRow + row) * bytesPerRow;
|
|
627
|
+
const sourceEnd = sourceStart + bytesPerRow;
|
|
628
|
+
source.data.copy(cropped.data, row * bytesPerRow, sourceStart, sourceEnd);
|
|
629
|
+
}
|
|
630
|
+
return cropped;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* @description 通过多次滚动和拼接生成整页长截图。
|
|
634
|
+
*/
|
|
635
|
+
async function captureFullPageScreenshotBuffer(options) {
|
|
636
|
+
const { miniProgram, timeoutMs, runWithTimeout, screenshotTimeoutMessage } = options;
|
|
637
|
+
const page = await miniProgram.currentPage();
|
|
638
|
+
const pageSize = await page.size();
|
|
639
|
+
const systemInfo = await miniProgram.systemInfo();
|
|
640
|
+
const pageHeight = toPositiveNumber(pageSize.height);
|
|
641
|
+
const viewportHeight = toPositiveNumber(systemInfo.windowHeight);
|
|
642
|
+
if (!pageHeight || !viewportHeight) return decodeScreenshotBuffer(await runWithTimeout(miniProgram.screenshot(), timeoutMs, screenshotTimeoutMessage, "DEVTOOLS_SCREENSHOT_TIMEOUT"));
|
|
643
|
+
const segments = [];
|
|
644
|
+
const positions = createScrollPositions(pageHeight, viewportHeight);
|
|
645
|
+
let coveredUntil = 0;
|
|
646
|
+
let scale = 1;
|
|
647
|
+
for (const scrollTop of positions) {
|
|
648
|
+
await miniProgram.pageScrollTo(scrollTop);
|
|
649
|
+
await page.waitFor(150);
|
|
650
|
+
const rawScreenshot = await runWithTimeout(miniProgram.screenshot(), timeoutMs, screenshotTimeoutMessage, "DEVTOOLS_SCREENSHOT_TIMEOUT");
|
|
651
|
+
const png = PNG.sync.read(decodeScreenshotBuffer(rawScreenshot));
|
|
652
|
+
if (viewportHeight > 0) scale = png.height / viewportHeight;
|
|
653
|
+
const visibleEnd = Math.min(scrollTop + viewportHeight, pageHeight);
|
|
654
|
+
const cropTopCss = Math.max(coveredUntil - scrollTop, 0);
|
|
655
|
+
const segmentHeightCss = Math.max(visibleEnd - scrollTop - cropTopCss, 0);
|
|
656
|
+
if (segmentHeightCss <= 0) continue;
|
|
657
|
+
const cropTopRows = Math.min(Math.max(Math.round(cropTopCss * scale), 0), png.height);
|
|
658
|
+
const segmentRows = Math.min(Math.max(Math.round(segmentHeightCss * scale), 1), png.height - cropTopRows);
|
|
659
|
+
segments.push(cropPngRows(png, cropTopRows, segmentRows));
|
|
660
|
+
coveredUntil = visibleEnd;
|
|
661
|
+
}
|
|
662
|
+
if (segments.length === 0) throw new Error("Failed to capture screenshot");
|
|
663
|
+
const width = segments[0].width;
|
|
664
|
+
const merged = new PNG({
|
|
665
|
+
width,
|
|
666
|
+
height: segments.reduce((sum, segment) => sum + segment.height, 0)
|
|
667
|
+
});
|
|
668
|
+
const bytesPerRow = width * 4;
|
|
669
|
+
let offsetY = 0;
|
|
670
|
+
for (const segment of segments) {
|
|
671
|
+
if (segment.width !== width) throw new Error("Full-page screenshots have inconsistent widths");
|
|
672
|
+
for (let row = 0; row < segment.height; row += 1) {
|
|
673
|
+
const sourceStart = row * bytesPerRow;
|
|
674
|
+
const sourceEnd = sourceStart + bytesPerRow;
|
|
675
|
+
segment.data.copy(merged.data, (offsetY + row) * bytesPerRow, sourceStart, sourceEnd);
|
|
676
|
+
}
|
|
677
|
+
offsetY += segment.height;
|
|
678
|
+
}
|
|
679
|
+
return PNG.sync.write(merged);
|
|
680
|
+
}
|
|
681
|
+
//#endregion
|
|
431
682
|
//#region src/cli/commands.ts
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
683
|
+
function createTimeoutError(message, code) {
|
|
684
|
+
const error = new Error(message);
|
|
685
|
+
error.code = code;
|
|
686
|
+
return error;
|
|
687
|
+
}
|
|
688
|
+
function normalizePagePath(page) {
|
|
689
|
+
return page.startsWith("/") ? page : `/${page}`;
|
|
690
|
+
}
|
|
691
|
+
function sleep(ms) {
|
|
692
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
693
|
+
}
|
|
694
|
+
function withCommandTimeout(task, timeoutMs, message, code) {
|
|
695
|
+
return new Promise((resolve, reject) => {
|
|
696
|
+
const timeout = setTimeout(() => {
|
|
697
|
+
reject(createTimeoutError(message, code));
|
|
698
|
+
}, timeoutMs);
|
|
699
|
+
task.then((value) => {
|
|
700
|
+
clearTimeout(timeout);
|
|
701
|
+
resolve(value);
|
|
702
|
+
}).catch((error) => {
|
|
703
|
+
clearTimeout(timeout);
|
|
704
|
+
reject(error);
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
}
|
|
449
708
|
async function runRouteCommand(options, startMessage, successMessage, action) {
|
|
450
709
|
await withMiniProgram(options, async (miniProgram) => {
|
|
451
710
|
logger_default.info(startMessage);
|
|
@@ -583,7 +842,7 @@ async function audit(options) {
|
|
|
583
842
|
logger_default.info(i18nText("正在执行体验审计...", "Running experience audit..."));
|
|
584
843
|
const result = await miniProgram.stopAudits();
|
|
585
844
|
if (options.outputPath) {
|
|
586
|
-
await fs
|
|
845
|
+
await fs.writeFile(options.outputPath, JSON.stringify(result, null, 2));
|
|
587
846
|
logger_default.success(i18nText(`审计报告已保存到 ${colors.cyan(options.outputPath)}`, `Audit report saved to ${colors.cyan(options.outputPath)}`));
|
|
588
847
|
return result;
|
|
589
848
|
}
|
|
@@ -592,28 +851,62 @@ async function audit(options) {
|
|
|
592
851
|
});
|
|
593
852
|
}
|
|
594
853
|
/**
|
|
595
|
-
* @description
|
|
854
|
+
* @description 捕获当前页面截图并返回二进制内容。
|
|
596
855
|
*/
|
|
597
|
-
async function
|
|
856
|
+
async function captureScreenshotBuffer(options) {
|
|
598
857
|
return await withMiniProgram(options, async (miniProgram) => {
|
|
599
|
-
|
|
858
|
+
const commandTimeout = options.timeout ?? 3e4;
|
|
859
|
+
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.`);
|
|
860
|
+
if (!options.miniProgram) logger_default.info(i18nText(`正在连接 DevTools:${colors.cyan(options.projectPath)}...`, `Connecting to DevTools at ${colors.cyan(options.projectPath)}...`));
|
|
600
861
|
if (options.page) {
|
|
601
|
-
|
|
602
|
-
|
|
862
|
+
const normalizedPage = normalizePagePath(options.page);
|
|
863
|
+
logger_default.info(i18nText(`正在跳转页面 ${colors.cyan(normalizedPage)}...`, `Navigating to page ${colors.cyan(normalizedPage)}...`));
|
|
864
|
+
await miniProgram.reLaunch(normalizedPage);
|
|
865
|
+
if (options.fullPage) await sleep(1e3);
|
|
603
866
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
867
|
+
if (options.fullPage) {
|
|
868
|
+
logger_default.info(i18nText("正在生成整页长截图...", "Capturing full-page screenshot..."));
|
|
869
|
+
return await captureFullPageScreenshotBuffer({
|
|
870
|
+
miniProgram,
|
|
871
|
+
timeoutMs: commandTimeout,
|
|
872
|
+
runWithTimeout: withCommandTimeout,
|
|
873
|
+
screenshotTimeoutMessage
|
|
874
|
+
});
|
|
612
875
|
}
|
|
613
|
-
|
|
876
|
+
logger_default.info(i18nText("正在截图...", "Taking screenshot..."));
|
|
877
|
+
const screenshot = await withCommandTimeout(miniProgram.screenshot(), commandTimeout, screenshotTimeoutMessage, "DEVTOOLS_SCREENSHOT_TIMEOUT");
|
|
878
|
+
const buffer = typeof screenshot === "string" ? Buffer.from(screenshot, "base64") : Buffer.from(screenshot);
|
|
879
|
+
if (buffer.length === 0) throw new Error(i18nText("截图失败", "Failed to capture screenshot"));
|
|
880
|
+
return buffer;
|
|
614
881
|
});
|
|
615
882
|
}
|
|
616
883
|
/**
|
|
884
|
+
* @description 获取当前小程序截图。
|
|
885
|
+
*/
|
|
886
|
+
async function takeScreenshot(options) {
|
|
887
|
+
let screenshotBuffer;
|
|
888
|
+
try {
|
|
889
|
+
screenshotBuffer = await captureScreenshotBuffer(options);
|
|
890
|
+
} catch (error) {
|
|
891
|
+
const isProtocolTimeout = error instanceof Error && error.message === "DEVTOOLS_PROTOCOL_TIMEOUT";
|
|
892
|
+
if (!Boolean(options.sharedSession && !options.miniProgram && isProtocolTimeout)) throw error;
|
|
893
|
+
await closeSharedMiniProgram(options.projectPath);
|
|
894
|
+
logger_default.warn(i18nText("当前共享 DevTools 会话截图超时,正在改用全新自动化会话重试一次...", "The current shared DevTools session timed out while capturing screenshot. Retrying once with a fresh automation session..."));
|
|
895
|
+
screenshotBuffer = await captureScreenshotBuffer({
|
|
896
|
+
...options,
|
|
897
|
+
preferOpenedSession: false,
|
|
898
|
+
sharedSession: false
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
const base64 = screenshotBuffer.toString("base64");
|
|
902
|
+
if (options.outputPath) {
|
|
903
|
+
await fs.writeFile(options.outputPath, screenshotBuffer);
|
|
904
|
+
logger_default.success(i18nText(`截图已保存到 ${colors.cyan(options.outputPath)}`, `Screenshot saved to ${colors.cyan(options.outputPath)}`));
|
|
905
|
+
return { path: options.outputPath };
|
|
906
|
+
}
|
|
907
|
+
return { base64 };
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
617
910
|
* @description 开关远程调试。
|
|
618
911
|
*/
|
|
619
912
|
async function remote(options) {
|
|
@@ -625,4 +918,4 @@ async function remote(options) {
|
|
|
625
918
|
});
|
|
626
919
|
}
|
|
627
920
|
//#endregion
|
|
628
|
-
export {
|
|
921
|
+
export { isAutomatorProtocolTimeoutError as A, createLocaleConfig as B, configureLocaleFromArgv as C, formatAutomatorLoginError as D, connectOpenedAutomator as E, launchAutomator as F, defaultCustomConfigFilePath as G, readCustomConfig as H, resolveCliPath as I, getDefaultCliPath as J, resolvePath as K, getConfig as L, isDevtoolsExtensionContextInvalidatedError as M, isDevtoolsHttpPortError as N, getAutomatorProtocolTimeoutMethod as O, isRetryableAutomatorLaunchError as P, logger_default as Q, getConfiguredLocale as R, withMiniProgram as S, validateLocaleOption as T, removeCustomConfigKey as U, overwriteCustomConfig as V, defaultCustomConfigDirPath as W, operatingSystemName as X, isOperatingSystemSupported as Y, colors as Z, acquireSharedMiniProgram as _, navigateBack as a, getSharedMiniProgramSessionCount as b, pageStack as c, remote as d, scrollTo as f, tap as g, takeScreenshot as h, input as i, isAutomatorWsConnectError as j, isAutomatorLoginError as k, reLaunch as l, systemInfo as m, captureScreenshotBuffer as n, navigateTo as o, switchTab as p, SupportedPlatformsMap as q, currentPage as r, pageData as s, audit as t, redirectTo as u, closeSharedMiniProgram as v, i18nText as w, releaseSharedMiniProgram as x, connectMiniProgram as y, createCustomConfig as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import * as _weapp_vite_miniprogram_automator0 from "@weapp-vite/miniprogram-automator";
|
|
1
3
|
import { Element, MiniProgram, Page } from "@weapp-vite/miniprogram-automator";
|
|
2
4
|
import * as cac$1 from "cac";
|
|
3
5
|
import * as execa from "execa";
|
|
@@ -7,11 +9,33 @@ interface AutomatorOptions {
|
|
|
7
9
|
projectPath: string;
|
|
8
10
|
timeout?: number;
|
|
9
11
|
cliPath?: string;
|
|
12
|
+
trustProject?: boolean;
|
|
13
|
+
preferOpenedSession?: boolean;
|
|
10
14
|
}
|
|
11
15
|
/**
|
|
12
16
|
* @description 判断错误是否属于开发者工具服务端口不可用。
|
|
13
17
|
*/
|
|
14
18
|
declare function isDevtoolsHttpPortError(error: unknown): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* @description 判断错误是否属于开发者工具 automator 扩展上下文尚未就绪。
|
|
21
|
+
*/
|
|
22
|
+
declare function isDevtoolsExtensionContextInvalidatedError(error: unknown): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* @description 判断错误是否属于可重试的 automator 启动抖动。
|
|
25
|
+
*/
|
|
26
|
+
declare function isRetryableAutomatorLaunchError(error: unknown): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* @description 判断错误是否属于开发者工具 websocket 连接失败。
|
|
29
|
+
*/
|
|
30
|
+
declare function isAutomatorWsConnectError(error: unknown): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* @description 判断错误是否属于开发者工具协议调用超时。
|
|
33
|
+
*/
|
|
34
|
+
declare function isAutomatorProtocolTimeoutError(error: unknown): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* @description 提取协议超时的方法名。
|
|
37
|
+
*/
|
|
38
|
+
declare function getAutomatorProtocolTimeoutMethod(error: unknown): string | undefined;
|
|
15
39
|
/**
|
|
16
40
|
* @description 判断错误是否属于开发者工具登录失效。
|
|
17
41
|
*/
|
|
@@ -24,6 +48,10 @@ declare function formatAutomatorLoginError(error: unknown): string;
|
|
|
24
48
|
* @description 基于当前配置解析 CLI 路径,并通过现代化 automator 入口启动会话。
|
|
25
49
|
*/
|
|
26
50
|
declare function launchAutomator(options: AutomatorOptions): Promise<any>;
|
|
51
|
+
/**
|
|
52
|
+
* @description 连接当前项目已打开的开发者工具自动化会话,不触发新的 IDE 拉起。
|
|
53
|
+
*/
|
|
54
|
+
declare function connectOpenedAutomator(options: AutomatorOptions): Promise<_weapp_vite_miniprogram_automator0.MiniProgram>;
|
|
27
55
|
//#endregion
|
|
28
56
|
//#region src/cli/automator-argv.d.ts
|
|
29
57
|
interface ParsedAutomatorArgs {
|
|
@@ -46,10 +74,6 @@ declare function readOptionValue(argv: readonly string[], optionName: string): s
|
|
|
46
74
|
declare function removeOption(argv: readonly string[], optionName: string): string[];
|
|
47
75
|
//#endregion
|
|
48
76
|
//#region src/cli/automator-session.d.ts
|
|
49
|
-
interface AutomatorSessionOptions {
|
|
50
|
-
projectPath: string;
|
|
51
|
-
timeout?: number;
|
|
52
|
-
}
|
|
53
77
|
interface MiniProgramEventMap {
|
|
54
78
|
console: (payload: unknown) => void;
|
|
55
79
|
exception: (payload: unknown) => void;
|
|
@@ -59,10 +83,33 @@ type MiniProgramPage = InstanceType<typeof Page>;
|
|
|
59
83
|
type MiniProgramElement = InstanceType<typeof Element> & {
|
|
60
84
|
input?: (value: string) => Promise<void>;
|
|
61
85
|
};
|
|
86
|
+
interface AutomatorSessionOptions {
|
|
87
|
+
miniProgram?: MiniProgramLike;
|
|
88
|
+
preferOpenedSession?: boolean;
|
|
89
|
+
projectPath: string;
|
|
90
|
+
sharedSession?: boolean;
|
|
91
|
+
timeout?: number;
|
|
92
|
+
}
|
|
62
93
|
/**
|
|
63
94
|
* @description 建立 automator 会话,并统一处理常见连接错误提示。
|
|
64
95
|
*/
|
|
65
96
|
declare function connectMiniProgram(options: AutomatorSessionOptions): Promise<MiniProgramLike>;
|
|
97
|
+
/**
|
|
98
|
+
* @description 获取指定项目的共享 automator 会话;若不存在则自动创建。
|
|
99
|
+
*/
|
|
100
|
+
declare function acquireSharedMiniProgram(options: AutomatorSessionOptions): Promise<MiniProgramLike>;
|
|
101
|
+
/**
|
|
102
|
+
* @description 释放指定项目的共享会话引用;会话对象会继续缓存,直到显式关闭或重置。
|
|
103
|
+
*/
|
|
104
|
+
declare function releaseSharedMiniProgram(projectPath: string): void;
|
|
105
|
+
/**
|
|
106
|
+
* @description 关闭并移除指定项目的共享 automator 会话。
|
|
107
|
+
*/
|
|
108
|
+
declare function closeSharedMiniProgram(projectPath: string): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* @description 获取当前共享会话数量,供测试断言使用。
|
|
111
|
+
*/
|
|
112
|
+
declare function getSharedMiniProgramSessionCount(): number;
|
|
66
113
|
/**
|
|
67
114
|
* @description 统一管理 automator 会话生命周期。
|
|
68
115
|
*/
|
|
@@ -105,6 +152,7 @@ interface AuditOptions extends AutomatorCommandOptions {
|
|
|
105
152
|
interface ScreenshotOptions extends AutomatorCommandOptions {
|
|
106
153
|
outputPath?: string;
|
|
107
154
|
page?: string;
|
|
155
|
+
fullPage?: boolean;
|
|
108
156
|
}
|
|
109
157
|
interface ScreenshotResult {
|
|
110
158
|
base64?: string;
|
|
@@ -169,6 +217,10 @@ declare function scrollTo(options: ScrollOptions): Promise<void>;
|
|
|
169
217
|
* @description 执行体验评分审计。
|
|
170
218
|
*/
|
|
171
219
|
declare function audit(options: AuditOptions): Promise<any>;
|
|
220
|
+
/**
|
|
221
|
+
* @description 捕获当前页面截图并返回二进制内容。
|
|
222
|
+
*/
|
|
223
|
+
declare function captureScreenshotBuffer(options: ScreenshotOptions): Promise<Buffer>;
|
|
172
224
|
/**
|
|
173
225
|
* @description 获取当前小程序截图。
|
|
174
226
|
*/
|
|
@@ -178,6 +230,24 @@ declare function takeScreenshot(options: ScreenshotOptions): Promise<ScreenshotR
|
|
|
178
230
|
*/
|
|
179
231
|
declare function remote(options: RemoteOptions): Promise<void>;
|
|
180
232
|
//#endregion
|
|
233
|
+
//#region src/cli/compare.d.ts
|
|
234
|
+
interface CompareOptions extends ScreenshotOptions {
|
|
235
|
+
baselinePath: string;
|
|
236
|
+
currentOutputPath?: string;
|
|
237
|
+
diffOutputPath?: string;
|
|
238
|
+
threshold: number;
|
|
239
|
+
maxDiffPixels?: number;
|
|
240
|
+
maxDiffRatio?: number;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* @description 输出 compare 命令帮助信息。
|
|
244
|
+
*/
|
|
245
|
+
declare function printCompareHelp(): void;
|
|
246
|
+
/**
|
|
247
|
+
* @description 解析 compare 命令参数。
|
|
248
|
+
*/
|
|
249
|
+
declare function parseCompareArgs(argv: string[]): CompareOptions;
|
|
250
|
+
//#endregion
|
|
181
251
|
//#region src/cli/config-command.d.ts
|
|
182
252
|
/**
|
|
183
253
|
* @description 处理 config 子命令。
|
|
@@ -338,6 +408,7 @@ interface CustomConfigFile {
|
|
|
338
408
|
cliPath?: string;
|
|
339
409
|
locale?: 'zh' | 'en';
|
|
340
410
|
}
|
|
411
|
+
declare function readCustomConfig(): Promise<CustomConfigFile>;
|
|
341
412
|
/**
|
|
342
413
|
* @description 写入自定义 CLI 路径配置
|
|
343
414
|
*/
|
|
@@ -354,10 +425,6 @@ declare function removeCustomConfigKey(key: keyof CustomConfigFile): Promise<voi
|
|
|
354
425
|
* @description 覆盖写入配置内容(会替换原内容)。
|
|
355
426
|
*/
|
|
356
427
|
declare function overwriteCustomConfig(config: CustomConfigFile): Promise<void>;
|
|
357
|
-
/**
|
|
358
|
-
* @description 读取原始自定义配置。
|
|
359
|
-
*/
|
|
360
|
-
declare function readCustomConfig(): Promise<CustomConfigFile>;
|
|
361
428
|
//#endregion
|
|
362
429
|
//#region src/config/paths.d.ts
|
|
363
430
|
/**
|
|
@@ -440,4 +507,4 @@ declare function execute(cliPath: string, argv: string[], options?: ExecuteOptio
|
|
|
440
507
|
*/
|
|
441
508
|
declare function resolvePath(filePath: string): string;
|
|
442
509
|
//#endregion
|
|
443
|
-
export { AUTOMATOR_COMMAND_NAMES, ArgvTransform, AuditOptions, AutomatorCommandOptions, AutomatorOptions, AutomatorSessionOptions, type BaseConfig, CONFIG_COMMAND_NAME, type ConfigSource, ForwardConsoleEvent, ForwardConsoleLogLevel, ForwardConsoleOptions, ForwardConsoleSession, InputOptions, LoginRetryMode, MINIDEV_NAMESPACE_COMMAND_NAMES, MiniProgramElement, MiniProgramEventMap, MiniProgramLike, MiniProgramPage, NavigateOptions, PageDataOptions, PageInfoOptions, ParsedAutomatorArgs, RemoteOptions, type ResolvedConfig, RetryKeypressOptions, RetryPromptResult, type ScreenshotOptions, type ScreenshotResult, ScrollOptions, SelectorOptions, SupportedPlatform, SupportedPlatformsMap, TapOptions, WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES, WECHAT_CLI_COMMAND_NAMES, audit, connectMiniProgram, createAlias, createCli, createCustomConfig, createLocaleConfig, createPathCompat, createWechatIdeLoginRequiredExitError, currentPage, defaultCustomConfigDirPath, defaultCustomConfigFilePath, execute, extractExecutionErrorText, formatAutomatorLoginError, formatRetryHotkeyPrompt, formatWechatIdeLoginRequiredError, getAutomatorCommandHelp, getConfig, getConfiguredLocale, getDefaultCliPath, handleConfigCommand, input, isAutomatorCommand, isAutomatorLoginError, isDevtoolsHttpPortError, isOperatingSystemSupported, isWeappIdeTopLevelCommand, isWechatIdeLoginRequiredError, launchAutomator, navigateBack, navigateTo, operatingSystemName, overwriteCustomConfig, pageData, pageStack, parse, parseAutomatorArgs, parseScreenshotArgs, printScreenshotHelp, promptForCliPath, reLaunch, readCustomConfig, readOptionValue, redirectTo, remote, removeCustomConfigKey, removeOption, resolveCliPath, resolvePath, runAutomatorCommand, runMinidev, runWechatCliWithRetry, scrollTo, startForwardConsole, switchTab, systemInfo, takeScreenshot, tap, transformArgv, validateWechatCliCommandArgs, waitForRetryKeypress, withMiniProgram };
|
|
510
|
+
export { AUTOMATOR_COMMAND_NAMES, ArgvTransform, AuditOptions, AutomatorCommandOptions, AutomatorOptions, AutomatorSessionOptions, type BaseConfig, CONFIG_COMMAND_NAME, type ConfigSource, ForwardConsoleEvent, ForwardConsoleLogLevel, ForwardConsoleOptions, ForwardConsoleSession, InputOptions, LoginRetryMode, MINIDEV_NAMESPACE_COMMAND_NAMES, MiniProgramElement, MiniProgramEventMap, MiniProgramLike, MiniProgramPage, NavigateOptions, PageDataOptions, PageInfoOptions, ParsedAutomatorArgs, RemoteOptions, type ResolvedConfig, RetryKeypressOptions, RetryPromptResult, type ScreenshotOptions, type ScreenshotResult, ScrollOptions, SelectorOptions, SupportedPlatform, SupportedPlatformsMap, TapOptions, WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES, WECHAT_CLI_COMMAND_NAMES, acquireSharedMiniProgram, audit, captureScreenshotBuffer, closeSharedMiniProgram, connectMiniProgram, connectOpenedAutomator, createAlias, createCli, createCustomConfig, createLocaleConfig, createPathCompat, createWechatIdeLoginRequiredExitError, currentPage, defaultCustomConfigDirPath, defaultCustomConfigFilePath, execute, extractExecutionErrorText, formatAutomatorLoginError, formatRetryHotkeyPrompt, formatWechatIdeLoginRequiredError, getAutomatorCommandHelp, getAutomatorProtocolTimeoutMethod, getConfig, getConfiguredLocale, getDefaultCliPath, getSharedMiniProgramSessionCount, handleConfigCommand, input, isAutomatorCommand, isAutomatorLoginError, isAutomatorProtocolTimeoutError, isAutomatorWsConnectError, isDevtoolsExtensionContextInvalidatedError, isDevtoolsHttpPortError, isOperatingSystemSupported, isRetryableAutomatorLaunchError, isWeappIdeTopLevelCommand, isWechatIdeLoginRequiredError, launchAutomator, navigateBack, navigateTo, operatingSystemName, overwriteCustomConfig, pageData, pageStack, parse, parseAutomatorArgs, parseCompareArgs, parseScreenshotArgs, printCompareHelp, printScreenshotHelp, promptForCliPath, reLaunch, readCustomConfig, readOptionValue, redirectTo, releaseSharedMiniProgram, remote, removeCustomConfigKey, removeOption, resolveCliPath, resolvePath, runAutomatorCommand, runMinidev, runWechatCliWithRetry, scrollTo, startForwardConsole, switchTab, systemInfo, takeScreenshot, tap, transformArgv, validateWechatCliCommandArgs, waitForRetryKeypress, withMiniProgram };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { A as
|
|
3
|
-
export { AUTOMATOR_COMMAND_NAMES, CONFIG_COMMAND_NAME, MINIDEV_NAMESPACE_COMMAND_NAMES, SupportedPlatformsMap, WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES, WECHAT_CLI_COMMAND_NAMES, audit, connectMiniProgram, createAlias, createCli, createCustomConfig, createLocaleConfig, createPathCompat, createWechatIdeLoginRequiredExitError, currentPage, defaultCustomConfigDirPath, defaultCustomConfigFilePath, execute, extractExecutionErrorText, formatAutomatorLoginError, formatRetryHotkeyPrompt, formatWechatIdeLoginRequiredError, getAutomatorCommandHelp, getConfig, getConfiguredLocale, getDefaultCliPath, handleConfigCommand, input, isAutomatorCommand, isAutomatorLoginError, isDevtoolsHttpPortError, isOperatingSystemSupported, isWeappIdeTopLevelCommand, isWechatIdeLoginRequiredError, launchAutomator, navigateBack, navigateTo, operatingSystemName, overwriteCustomConfig, pageData, pageStack, parse, parseAutomatorArgs, parseScreenshotArgs, printScreenshotHelp, promptForCliPath, reLaunch, readCustomConfig, readOptionValue, redirectTo, remote, removeCustomConfigKey, removeOption, resolveCliPath, resolvePath, runAutomatorCommand, runMinidev, runWechatCliWithRetry, scrollTo, startForwardConsole, switchTab, systemInfo, takeScreenshot, tap, transformArgv, validateWechatCliCommandArgs, waitForRetryKeypress, withMiniProgram };
|
|
1
|
+
import { A as isAutomatorProtocolTimeoutError, B as createLocaleConfig, D as formatAutomatorLoginError, E as connectOpenedAutomator, F as launchAutomator, G as defaultCustomConfigFilePath, H as readCustomConfig, I as resolveCliPath, J as getDefaultCliPath, K as resolvePath, L as getConfig, M as isDevtoolsExtensionContextInvalidatedError, N as isDevtoolsHttpPortError, O as getAutomatorProtocolTimeoutMethod, P as isRetryableAutomatorLaunchError, R as getConfiguredLocale, S as withMiniProgram, U as removeCustomConfigKey, V as overwriteCustomConfig, W as defaultCustomConfigDirPath, X as operatingSystemName, Y as isOperatingSystemSupported, _ as acquireSharedMiniProgram, a as navigateBack, b as getSharedMiniProgramSessionCount, c as pageStack, d as remote, f as scrollTo, g as tap, h as takeScreenshot, i as input, j as isAutomatorWsConnectError, k as isAutomatorLoginError, l as reLaunch, m as systemInfo, n as captureScreenshotBuffer, o as navigateTo, p as switchTab, q as SupportedPlatformsMap, r as currentPage, s as pageData, t as audit, u as redirectTo, v as closeSharedMiniProgram, x as releaseSharedMiniProgram, y as connectMiniProgram, z as createCustomConfig } from "./commands--vppy2pX.js";
|
|
2
|
+
import { A as parseCompareArgs, C as isWeappIdeTopLevelCommand, D as runAutomatorCommand, E as isAutomatorCommand, M as parseAutomatorArgs, N as readOptionValue, O as parseScreenshotArgs, P as removeOption, S as WECHAT_CLI_COMMAND_NAMES, T as getAutomatorCommandHelp, _ as handleConfigCommand, a as createWechatIdeLoginRequiredExitError, b as MINIDEV_NAMESPACE_COMMAND_NAMES, c as formatWechatIdeLoginRequiredError, d as runMinidev, f as execute, g as startForwardConsole, h as transformArgv, i as runWechatCliWithRetry, j as printCompareHelp, k as printScreenshotHelp, l as isWechatIdeLoginRequiredError, m as createPathCompat, n as parse, o as extractExecutionErrorText, p as createAlias, r as validateWechatCliCommandArgs, s as formatRetryHotkeyPrompt, t as createCli, u as waitForRetryKeypress, v as promptForCliPath, w as AUTOMATOR_COMMAND_NAMES, x as WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES, y as CONFIG_COMMAND_NAME } from "./cli-DKrUhSmB.js";
|
|
3
|
+
export { AUTOMATOR_COMMAND_NAMES, CONFIG_COMMAND_NAME, MINIDEV_NAMESPACE_COMMAND_NAMES, SupportedPlatformsMap, WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES, WECHAT_CLI_COMMAND_NAMES, acquireSharedMiniProgram, audit, captureScreenshotBuffer, closeSharedMiniProgram, connectMiniProgram, connectOpenedAutomator, createAlias, createCli, createCustomConfig, createLocaleConfig, createPathCompat, createWechatIdeLoginRequiredExitError, currentPage, defaultCustomConfigDirPath, defaultCustomConfigFilePath, execute, extractExecutionErrorText, formatAutomatorLoginError, formatRetryHotkeyPrompt, formatWechatIdeLoginRequiredError, getAutomatorCommandHelp, getAutomatorProtocolTimeoutMethod, getConfig, getConfiguredLocale, getDefaultCliPath, getSharedMiniProgramSessionCount, handleConfigCommand, input, isAutomatorCommand, isAutomatorLoginError, isAutomatorProtocolTimeoutError, isAutomatorWsConnectError, isDevtoolsExtensionContextInvalidatedError, isDevtoolsHttpPortError, isOperatingSystemSupported, isRetryableAutomatorLaunchError, isWeappIdeTopLevelCommand, isWechatIdeLoginRequiredError, launchAutomator, navigateBack, navigateTo, operatingSystemName, overwriteCustomConfig, pageData, pageStack, parse, parseAutomatorArgs, parseCompareArgs, parseScreenshotArgs, printCompareHelp, printScreenshotHelp, promptForCliPath, reLaunch, readCustomConfig, readOptionValue, redirectTo, releaseSharedMiniProgram, remote, removeCustomConfigKey, removeOption, resolveCliPath, resolvePath, runAutomatorCommand, runMinidev, runWechatCliWithRetry, scrollTo, startForwardConsole, switchTab, systemInfo, takeScreenshot, tap, transformArgv, validateWechatCliCommandArgs, waitForRetryKeypress, withMiniProgram };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weapp-ide-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.1
|
|
4
|
+
"version": "5.2.1",
|
|
5
5
|
"description": "让微信开发者工具,用起来更加方便!",
|
|
6
6
|
"author": "ice breaker <1324318532@qq.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -64,10 +64,12 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"cac": "^7.0.0",
|
|
66
66
|
"execa": "9.6.1",
|
|
67
|
-
"fs-extra": "^11.3.4",
|
|
68
67
|
"pathe": "^2.0.3",
|
|
68
|
+
"pixelmatch": "^7.1.0",
|
|
69
|
+
"pngjs": "^7.0.0",
|
|
69
70
|
"@weapp-core/logger": "^3.1.1",
|
|
70
|
-
"@weapp-
|
|
71
|
+
"@weapp-core/shared": "^3.0.3",
|
|
72
|
+
"@weapp-vite/miniprogram-automator": "1.0.2"
|
|
71
73
|
},
|
|
72
74
|
"scripts": {
|
|
73
75
|
"dev": "tsdown -w --sourcemap",
|