weapp-ide-cli 5.2.0 → 5.2.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.
@@ -1,9 +1,9 @@
1
- import { A as createCustomConfig, B as isOperatingSystemSupported, D as resolveCliPath, H as colors, I as defaultCustomConfigFilePath, L as resolvePath, M as overwriteCustomConfig, N as readCustomConfig, P as removeCustomConfigKey, S as validateLocaleOption, U as logger_default, V as operatingSystemName, _ as tap, a as input, b as configureLocaleFromArgv, c as pageData, d as redirectTo, f as remote, h as systemInfo, i as currentPage, j as createLocaleConfig, k as getConfiguredLocale, l as pageStack, m as switchTab, n as captureScreenshotBuffer, o as navigateBack, p as scrollTo, s as navigateTo, t as audit, u as reLaunch, v as connectMiniProgram, x as i18nText } from "./commands-BfdE1eYN.js";
2
- import { fs } from "@weapp-core/shared";
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-38LzU5ML.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";
4
- import fs$1 from "node:fs/promises";
5
- import pixelmatch from "pixelmatch";
6
5
  import { PNG } from "pngjs";
6
+ import pixelmatch from "pixelmatch";
7
7
  import { createInterface } from "node:readline/promises";
8
8
  import { inspect } from "node:util";
9
9
  import { emitKeypressEvents } from "node:readline";
@@ -122,20 +122,20 @@ function readPng(buffer, label) {
122
122
  async function comparePngWithBaseline(options) {
123
123
  let baselineBuffer;
124
124
  try {
125
- baselineBuffer = await fs$1.readFile(options.baselinePath);
125
+ baselineBuffer = await fs.readFile(options.baselinePath);
126
126
  } catch (error) {
127
127
  throw new Error(i18nText(`无法读取基准图: ${options.baselinePath}`, `Failed to read baseline image: ${options.baselinePath}`), { cause: error });
128
128
  }
129
129
  const baselinePng = readPng(baselineBuffer, i18nText("基准图", "Baseline image"));
130
130
  const currentPng = readPng(options.currentPngBuffer, i18nText("当前截图", "Current screenshot"));
131
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$1.writeFile(options.currentOutputPath, options.currentPngBuffer);
132
+ if (options.currentOutputPath) await fs.writeFile(options.currentOutputPath, options.currentPngBuffer);
133
133
  const diffPng = new PNG({
134
134
  width: currentPng.width,
135
135
  height: currentPng.height
136
136
  });
137
137
  const diffPixels = pixelmatch(baselinePng.data, currentPng.data, diffPng.data, currentPng.width, currentPng.height, { threshold: options.threshold });
138
- if (options.diffOutputPath) await fs$1.writeFile(options.diffOutputPath, PNG.sync.write(diffPng));
138
+ if (options.diffOutputPath) await fs.writeFile(options.diffOutputPath, PNG.sync.write(diffPng));
139
139
  return {
140
140
  baselinePath: options.baselinePath,
141
141
  currentPath: options.currentOutputPath,
@@ -178,6 +178,7 @@ ${colors.bold("参数:")}
178
178
  --current-output <path> 当前截图输出路径
179
179
  --diff-output <path> diff 图片输出路径
180
180
  --page <path> 对比前先跳转页面
181
+ --full-page 对比时使用整页长截图
181
182
  --threshold <number> pixelmatch threshold(默认:0.1)
182
183
  --max-diff-pixels <count> 最大允许差异像素数
183
184
  --max-diff-ratio <number> 最大允许差异占比(0-1)
@@ -192,6 +193,8 @@ ${colors.bold("规则:")}
192
193
  - baseline 与当前截图尺寸不一致时直接失败
193
194
 
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
+
195
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
196
199
 
197
200
  weapp compare -p ./dist/build/mp-weixin --baseline .screenshots/baseline/index.png --max-diff-ratio 0.001
@@ -204,6 +207,7 @@ ${colors.bold("Options:")}
204
207
  --current-output <path> Output file path for current screenshot
205
208
  --diff-output <path> Output file path for diff image
206
209
  --page <path> Navigate to page before comparison
210
+ --full-page Use stitched full-page screenshots for comparison
207
211
  --threshold <number> Pixelmatch threshold (default: 0.1)
208
212
  --max-diff-pixels <count> Maximum allowed diff pixels
209
213
  --max-diff-ratio <number> Maximum allowed diff ratio (0-1)
@@ -218,6 +222,8 @@ ${colors.bold("Rules:")}
218
222
  - Baseline and current screenshot must have identical dimensions
219
223
 
220
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
+
221
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
222
228
 
223
229
  weapp compare -p ./dist/build/mp-weixin --baseline .screenshots/baseline/index.png --max-diff-ratio 0.001
@@ -238,6 +244,7 @@ function parseCompareArgs(argv) {
238
244
  projectPath: parsed.projectPath,
239
245
  timeout: parsed.timeout,
240
246
  page: readOptionValue(argv, "--page"),
247
+ fullPage: argv.includes("--full-page"),
241
248
  baselinePath,
242
249
  currentOutputPath: readOptionValue(argv, "--current-output"),
243
250
  diffOutputPath: readOptionValue(argv, "--diff-output"),
@@ -318,6 +325,7 @@ ${colors.bold("参数:")}
318
325
  -p, --project <path> 项目路径(默认:当前目录)
319
326
  -o, --output <path> 截图输出文件路径
320
327
  --page <path> 截图前先跳转页面
328
+ --full-page 输出整页长截图
321
329
  -t, --timeout <ms> 连接超时时间(默认:30000)
322
330
  --json 以 JSON 格式输出
323
331
  --lang <lang> 语言切换:zh | en(默认:zh)
@@ -333,6 +341,9 @@ ${colors.bold("示例:")}
333
341
  # 先跳转页面再截图
334
342
  weapp screenshot -p /path/to/project --page pages/index/index
335
343
 
344
+ # 输出整页长截图
345
+ weapp screenshot -p /path/to/project --page pages/index/index --full-page -o full.png
346
+
336
347
  # 以 JSON 输出便于脚本解析
337
348
  weapp screenshot -p /path/to/project --json
338
349
  `, `
@@ -342,6 +353,7 @@ ${colors.bold("Options:")}
342
353
  -p, --project <path> Project path (default: current directory)
343
354
  -o, --output <path> Output file path for screenshot
344
355
  --page <path> Navigate to page before taking screenshot
356
+ --full-page Capture a stitched full-page screenshot
345
357
  -t, --timeout <ms> Connection timeout in milliseconds (default: 30000)
346
358
  --json Output as JSON format
347
359
  --lang <lang> Language: zh | en (default: zh)
@@ -357,6 +369,9 @@ ${colors.bold("Examples:")}
357
369
  # Navigate to page first
358
370
  weapp screenshot -p /path/to/project --page pages/index/index
359
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
+
360
375
  # JSON output for parsing
361
376
  weapp screenshot -p /path/to/project --json
362
377
  `));
@@ -366,11 +381,15 @@ ${colors.bold("Examples:")}
366
381
  */
367
382
  function parseScreenshotArgs(argv) {
368
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");
369
387
  return {
370
388
  projectPath: parsed.projectPath,
371
- timeout: parsed.timeout,
372
- outputPath: readOptionValue(argv, "-o") || readOptionValue(argv, "--output"),
373
- page: readOptionValue(argv, "--page")
389
+ ...parsed.timeout ? { timeout: parsed.timeout } : {},
390
+ ...outputPath ? { outputPath } : {},
391
+ ...page ? { page } : {},
392
+ ...fullPage ? { fullPage: true } : {}
374
393
  };
375
394
  }
376
395
  /**
@@ -383,7 +402,7 @@ async function runScreenshot(argv) {
383
402
  }
384
403
  const options = parseScreenshotArgs(argv);
385
404
  const isJsonOutput = argv.includes("--json");
386
- const { takeScreenshot } = await import("./commands-BfdE1eYN.js").then((n) => n.r);
405
+ const { takeScreenshot } = await import("./commands-msx1PSZ8.js");
387
406
  const result = await takeScreenshot(options);
388
407
  if (isJsonOutput) {
389
408
  console.log(JSON.stringify(result, null, 2));
@@ -787,7 +806,7 @@ async function promptForCliPath() {
787
806
  try {
788
807
  const normalizedPath = await createCustomConfig({ cliPath });
789
808
  logger_default.info(`全局配置存储位置:${colors.green(defaultCustomConfigFilePath)}`);
790
- if (!await fs.pathExists(normalizedPath)) logger_default.warn("在当前路径未找到微信web开发者命令行工具,请确认路径是否正确。");
809
+ if (!await fs$1.pathExists(normalizedPath)) logger_default.warn("在当前路径未找到微信web开发者命令行工具,请确认路径是否正确。");
791
810
  return normalizedPath;
792
811
  } catch (error) {
793
812
  const reason = error instanceof Error ? error.message : String(error);
@@ -859,7 +878,7 @@ async function handleConfigCommand(argv) {
859
878
  }
860
879
  if (action === "doctor") {
861
880
  const rawConfig = await readCustomConfig();
862
- const hasConfigFile = await fs.pathExists(defaultCustomConfigFilePath);
881
+ const hasConfigFile = await fs$1.pathExists(defaultCustomConfigFilePath);
863
882
  const resolvedCli = await resolveCliPath();
864
883
  const hasCustomCli = typeof rawConfig.cliPath === "string" && rawConfig.cliPath.length > 0;
865
884
  const hasValidCli = Boolean(resolvedCli.cliPath);
@@ -882,7 +901,7 @@ async function handleConfigCommand(argv) {
882
901
  const outputPath = argv[1];
883
902
  const config = await readCustomConfig();
884
903
  if (outputPath) {
885
- await fs.writeJSON(outputPath, config, {
904
+ await fs$1.writeJSON(outputPath, config, {
886
905
  spaces: 2,
887
906
  encoding: "utf8"
888
907
  });
@@ -895,7 +914,7 @@ async function handleConfigCommand(argv) {
895
914
  if (action === "import") {
896
915
  const inputPath = argv[1];
897
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"));
898
- const imported = await fs.readJSON(inputPath);
917
+ const imported = await fs$1.readJSON(inputPath);
899
918
  if (!imported || typeof imported !== "object") throw new Error(i18nText("导入文件格式无效:应为 JSON 对象", "Invalid import file format: expected a JSON object"));
900
919
  const candidate = imported;
901
920
  await overwriteCustomConfig({
package/dist/cli.js CHANGED
@@ -1,18 +1,15 @@
1
- import { U as logger_default } from "./commands-BfdE1eYN.js";
2
- import { n as parse } from "./cli-DXJCUYYI.js";
1
+ import { Q as logger_default } from "./commands-38LzU5ML.js";
2
+ import { n as parse } from "./cli-1zieDX-6.js";
3
3
  import process from "node:process";
4
4
  //#region src/cli.ts
5
- parse(process.argv.slice(2)).catch((err) => {
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
- process.exitCode = err.exitCode;
9
- return;
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 "@weapp-core/shared";
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 { Buffer as Buffer$1 } from "node:buffer";
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
@@ -42,11 +32,11 @@ function isOperatingSystemSupported(osName = os.type()) {
42
32
  */
43
33
  const operatingSystemName = os.type();
44
34
  async function getFirstBinaryPath(command) {
45
- const pathDirs = (process.env.PATH || "").split(path.delimiter);
35
+ const pathDirs = (process.env.PATH || "").split(path$1.delimiter);
46
36
  for (const dir of pathDirs) {
47
- const fullPath = path.join(dir, command);
37
+ const fullPath = path$1.join(dir, command);
48
38
  try {
49
- await fs.access(fullPath, fs.constants.X_OK);
39
+ await fs$1.access(fullPath, fs$1.constants.X_OK);
50
40
  return fullPath;
51
41
  } catch {
52
42
  continue;
@@ -96,8 +86,8 @@ async function getDefaultCliPath(targetOs = operatingSystemName) {
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,11 +95,11 @@ 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 = {
@@ -117,9 +107,9 @@ const JSON_OPTIONS = {
117
107
  spaces: 2
118
108
  };
119
109
  async function readCustomConfig() {
120
- if (!await fs.pathExists(defaultCustomConfigFilePath)) return {};
110
+ if (!await fs$1.pathExists(defaultCustomConfigFilePath)) return {};
121
111
  try {
122
- const config = await fs.readJSON(defaultCustomConfigFilePath);
112
+ const config = await fs$1.readJSON(defaultCustomConfigFilePath);
123
113
  if (!config || typeof config !== "object") return {};
124
114
  const candidate = config;
125
115
  const next = {};
@@ -135,8 +125,8 @@ async function writeCustomConfig(patch, options = {}) {
135
125
  ...options.replace ? {} : await readCustomConfig(),
136
126
  ...patch
137
127
  };
138
- await fs.ensureDir(defaultCustomConfigDirPath);
139
- await fs.writeJSON(defaultCustomConfigFilePath, nextConfig, JSON_OPTIONS);
128
+ await fs$1.ensureDir(defaultCustomConfigDirPath);
129
+ await fs$1.writeJSON(defaultCustomConfigFilePath, nextConfig, JSON_OPTIONS);
140
130
  }
141
131
  /**
142
132
  * @description 写入自定义 CLI 路径配置
@@ -176,12 +166,16 @@ async function overwriteCustomConfig(config) {
176
166
  }
177
167
  //#endregion
178
168
  //#region src/config/resolver.ts
169
+ function isCustomConfigJson(value) {
170
+ return typeof value === "object" && value !== null;
171
+ }
179
172
  /**
180
173
  * @description 读取并解析 CLI 配置(自定义优先)
181
174
  */
182
175
  async function getConfig() {
183
- if (await fs.pathExists(defaultCustomConfigFilePath)) try {
184
- const config = await fs.readJSON(defaultCustomConfigFilePath);
176
+ if (await fs$1.pathExists(defaultCustomConfigFilePath)) try {
177
+ const rawConfig = await fs$1.readJSON(defaultCustomConfigFilePath);
178
+ const config = isCustomConfigJson(rawConfig) ? rawConfig : {};
185
179
  const cliPath = typeof config.cliPath === "string" ? config.cliPath.trim() : "";
186
180
  const locale = config.locale === "zh" || config.locale === "en" ? config.locale : void 0;
187
181
  if (cliPath) {
@@ -228,7 +222,7 @@ async function resolveCliPath() {
228
222
  source: config.source
229
223
  };
230
224
  return {
231
- cliPath: await fs.pathExists(config.cliPath) ? config.cliPath : null,
225
+ cliPath: await fs$1.pathExists(config.cliPath) ? config.cliPath : null,
232
226
  source: config.source
233
227
  };
234
228
  }
@@ -242,6 +236,10 @@ const LOGIN_REQUIRED_CN_RE = /需要重新登录/;
242
236
  const LOGIN_REQUIRED_EN_RE = /need\s+re-?login|re-?login/i;
243
237
  const LOGIN_REQUIRED_CODE_RE = /code\s*[:=]\s*(\d+)/i;
244
238
  const DEVTOOLS_HTTP_PORT_ERROR = "Failed to launch wechat web devTools, please make sure http port is open";
239
+ const DEVTOOLS_EXTENSION_CONTEXT_INVALIDATED_RE = /Extension context invalidated/i;
240
+ const AUTOMATOR_LAUNCH_TIMEOUT_RE = /Wait timed out after \d+ ms/i;
241
+ const AUTOMATOR_WS_CONNECT_RE = /Failed connecting to ws:\/\/127\.0\.0\.1:\d+/i;
242
+ const DEVTOOLS_PROTOCOL_TIMEOUT_RE = /DevTools did not respond to protocol method (\S+) within \d+ms/i;
245
243
  const DEVTOOLS_INFRA_ERROR_PATTERNS = [
246
244
  /listen EPERM/i,
247
245
  /operation not permitted 0\.0\.0\.0/i,
@@ -249,6 +247,8 @@ const DEVTOOLS_INFRA_ERROR_PATTERNS = [
249
247
  /ECONNREFUSED/i,
250
248
  /connect ECONNREFUSED/i
251
249
  ];
250
+ const DEFAULT_WECHAT_DEVTOOLS_WS_ENDPOINT = "ws://127.0.0.1:9420";
251
+ const AUTOMATOR_SESSION_DIR = path.join(os.tmpdir(), "weapp-vite-automator-sessions");
252
252
  const DEVTOOLS_LOGIN_REQUIRED_PATTERNS = [
253
253
  /code\s*[:=]\s*10/i,
254
254
  /需要重新登录/,
@@ -268,6 +268,38 @@ function extractErrorText(error) {
268
268
  candidate.stdout
269
269
  ].filter((value) => typeof value === "string" && value.trim().length > 0).join("\n");
270
270
  }
271
+ function resolveAutomatorSessionFilePath(projectPath) {
272
+ const normalizedProjectPath = path.resolve(projectPath);
273
+ const encodedProjectPath = Buffer.from(normalizedProjectPath).toString("base64url");
274
+ return path.join(AUTOMATOR_SESSION_DIR, `${encodedProjectPath}.json`);
275
+ }
276
+ async function persistAutomatorSession(projectPath, wsEndpoint) {
277
+ const filePath = resolveAutomatorSessionFilePath(projectPath);
278
+ const payload = {
279
+ projectPath: path.resolve(projectPath),
280
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
281
+ wsEndpoint
282
+ };
283
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
284
+ await fs.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
285
+ }
286
+ async function readPersistedAutomatorSession(projectPath) {
287
+ const filePath = resolveAutomatorSessionFilePath(projectPath);
288
+ try {
289
+ const raw = await fs.readFile(filePath, "utf8");
290
+ const payload = JSON.parse(raw);
291
+ if (payload.projectPath !== path.resolve(projectPath) || typeof payload.wsEndpoint !== "string" || !payload.wsEndpoint.trim()) return null;
292
+ return payload;
293
+ } catch {
294
+ return null;
295
+ }
296
+ }
297
+ async function removePersistedAutomatorSession(projectPath) {
298
+ const filePath = resolveAutomatorSessionFilePath(projectPath);
299
+ try {
300
+ await fs.rm(filePath, { force: true });
301
+ } catch {}
302
+ }
271
303
  /**
272
304
  * @description 提取登录失效时最适合展示给用户的一行信息。
273
305
  */
@@ -288,6 +320,40 @@ function isDevtoolsHttpPortError(error) {
288
320
  return message.includes(DEVTOOLS_HTTP_PORT_ERROR) || DEVTOOLS_INFRA_ERROR_PATTERNS.some((pattern) => pattern.test(message));
289
321
  }
290
322
  /**
323
+ * @description 判断错误是否属于开发者工具 automator 扩展上下文尚未就绪。
324
+ */
325
+ function isDevtoolsExtensionContextInvalidatedError(error) {
326
+ const message = error instanceof Error ? error.message : String(error);
327
+ return DEVTOOLS_EXTENSION_CONTEXT_INVALIDATED_RE.test(message);
328
+ }
329
+ /**
330
+ * @description 判断错误是否属于可重试的 automator 启动抖动。
331
+ */
332
+ function isRetryableAutomatorLaunchError(error) {
333
+ const message = error instanceof Error ? error.message : String(error);
334
+ return DEVTOOLS_EXTENSION_CONTEXT_INVALIDATED_RE.test(message) || AUTOMATOR_LAUNCH_TIMEOUT_RE.test(message) || AUTOMATOR_WS_CONNECT_RE.test(message);
335
+ }
336
+ /**
337
+ * @description 判断错误是否属于开发者工具 websocket 连接失败。
338
+ */
339
+ function isAutomatorWsConnectError(error) {
340
+ const message = error instanceof Error ? error.message : String(error);
341
+ return AUTOMATOR_WS_CONNECT_RE.test(message);
342
+ }
343
+ /**
344
+ * @description 判断错误是否属于开发者工具协议调用超时。
345
+ */
346
+ function isAutomatorProtocolTimeoutError(error) {
347
+ const message = error instanceof Error ? error.message : String(error);
348
+ return DEVTOOLS_PROTOCOL_TIMEOUT_RE.test(message);
349
+ }
350
+ /**
351
+ * @description 提取协议超时的方法名。
352
+ */
353
+ function getAutomatorProtocolTimeoutMethod(error) {
354
+ return (error instanceof Error ? error.message : String(error)).match(DEVTOOLS_PROTOCOL_TIMEOUT_RE)?.[1];
355
+ }
356
+ /**
291
357
  * @description 判断错误是否属于开发者工具登录失效。
292
358
  */
293
359
  function isAutomatorLoginError(error) {
@@ -312,13 +378,40 @@ function formatAutomatorLoginError(error) {
312
378
  * @description 基于当前配置解析 CLI 路径,并通过现代化 automator 入口启动会话。
313
379
  */
314
380
  async function launchAutomator(options) {
315
- const { cliPath, projectPath, timeout = 3e4 } = options;
381
+ const { cliPath, projectPath, timeout = 3e4, trustProject = false } = options;
316
382
  const resolvedCliPath = cliPath ?? (await resolveCliPath()).cliPath ?? void 0;
317
- return await new Launcher().launch({
318
- cliPath: resolvedCliPath,
319
- projectPath,
320
- timeout
321
- });
383
+ const launcher = new Launcher();
384
+ let lastError = null;
385
+ for (let attempt = 0; attempt < 2; attempt += 1) try {
386
+ const miniProgram = await launcher.launch({
387
+ cliPath: resolvedCliPath,
388
+ projectPath,
389
+ timeout,
390
+ trustProject
391
+ });
392
+ const sessionMetadata = Reflect.get(miniProgram, "__WEAPP_VITE_SESSION_METADATA");
393
+ if (typeof sessionMetadata?.wsEndpoint === "string" && sessionMetadata.wsEndpoint) await persistAutomatorSession(projectPath, sessionMetadata.wsEndpoint);
394
+ return miniProgram;
395
+ } catch (error) {
396
+ lastError = error;
397
+ if (!isRetryableAutomatorLaunchError(error) || attempt === 1) throw error;
398
+ }
399
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
400
+ }
401
+ /**
402
+ * @description 连接当前项目已打开的开发者工具自动化会话,不触发新的 IDE 拉起。
403
+ */
404
+ async function connectOpenedAutomator(options) {
405
+ const { projectPath } = options;
406
+ const launcher = new Launcher();
407
+ const persistedSession = await readPersistedAutomatorSession(projectPath);
408
+ const wsEndpoint = persistedSession?.wsEndpoint ?? DEFAULT_WECHAT_DEVTOOLS_WS_ENDPOINT;
409
+ try {
410
+ return await launcher.connect({ wsEndpoint });
411
+ } catch (error) {
412
+ if (persistedSession) await removePersistedAutomatorSession(projectPath);
413
+ throw error;
414
+ }
322
415
  }
323
416
  //#endregion
324
417
  //#region src/i18n.ts
@@ -387,6 +480,7 @@ function readLangOption(argv) {
387
480
  }
388
481
  //#endregion
389
482
  //#region src/cli/automator-session.ts
483
+ const sharedMiniProgramSessions = /* @__PURE__ */ new Map();
390
484
  function normalizeMiniProgramConnectionError(error) {
391
485
  if (isAutomatorLoginError(error)) {
392
486
  logger_default.error(i18nText("检测到微信开发者工具登录状态失效,请先登录后重试。", "Wechat DevTools login has expired. Please login and retry."));
@@ -398,22 +492,106 @@ function normalizeMiniProgramConnectionError(error) {
398
492
  logger_default.warn(i18nText("请在微信开发者工具中:设置 -> 安全设置 -> 开启服务端口", "Please enable service port in Wechat DevTools: Settings -> Security -> Service Port"));
399
493
  return /* @__PURE__ */ new Error("DEVTOOLS_HTTP_PORT_ERROR");
400
494
  }
495
+ if (isDevtoolsExtensionContextInvalidatedError(error)) {
496
+ logger_default.error(i18nText("微信开发者工具自动化上下文尚未就绪,通常是刚启动或正在重载。", "Wechat DevTools automation context is not ready yet, usually because the IDE has just started or is still reloading."));
497
+ logger_default.warn(i18nText("请稍后重试;若持续失败,关闭多余的开发者工具窗口后重试。", "Please retry shortly. If it keeps failing, close extra DevTools windows and try again."));
498
+ return /* @__PURE__ */ new Error("DEVTOOLS_EXTENSION_CONTEXT_INVALIDATED");
499
+ }
500
+ if (isAutomatorWsConnectError(error)) {
501
+ logger_default.error(i18nText("无法连接到当前项目的微信开发者工具自动化 websocket。", "Cannot connect to the Wechat DevTools automation websocket for the current project."));
502
+ 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."));
503
+ return /* @__PURE__ */ new Error("DEVTOOLS_WS_CONNECT_ERROR");
504
+ }
505
+ if (isAutomatorProtocolTimeoutError(error)) {
506
+ const method = getAutomatorProtocolTimeoutMethod(error) ?? "unknown";
507
+ logger_default.error(i18nText(`微信开发者工具在协议调用 ${method} 上超时,未按预期返回结果。`, `Wechat DevTools timed out while executing protocol method ${method} and did not return a result.`));
508
+ 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."));
509
+ return /* @__PURE__ */ new Error("DEVTOOLS_PROTOCOL_TIMEOUT");
510
+ }
401
511
  return error instanceof Error ? error : new Error(String(error));
402
512
  }
403
513
  /**
404
514
  * @description 建立 automator 会话,并统一处理常见连接错误提示。
405
515
  */
406
516
  async function connectMiniProgram(options) {
407
- try {
517
+ if (options.preferOpenedSession === false) try {
408
518
  return await launchAutomator(options);
409
519
  } catch (error) {
410
520
  throw normalizeMiniProgramConnectionError(error);
411
521
  }
522
+ try {
523
+ return await connectOpenedAutomator(options);
524
+ } catch (error) {
525
+ const normalizedOpenSessionError = normalizeMiniProgramConnectionError(error);
526
+ if (normalizedOpenSessionError instanceof Error && normalizedOpenSessionError.message === "DEVTOOLS_PROTOCOL_TIMEOUT") throw normalizedOpenSessionError;
527
+ try {
528
+ return await launchAutomator(options);
529
+ } catch (launchError) {
530
+ throw normalizeMiniProgramConnectionError(launchError);
531
+ }
532
+ }
533
+ }
534
+ /**
535
+ * @description 获取指定项目的共享 automator 会话;若不存在则自动创建。
536
+ */
537
+ async function acquireSharedMiniProgram(options) {
538
+ const existing = sharedMiniProgramSessions.get(options.projectPath);
539
+ if (existing) {
540
+ existing.refs += 1;
541
+ return await existing.session;
542
+ }
543
+ const session = connectMiniProgram(options);
544
+ const entry = {
545
+ refs: 1,
546
+ session
547
+ };
548
+ sharedMiniProgramSessions.set(options.projectPath, entry);
549
+ try {
550
+ return await session;
551
+ } catch (error) {
552
+ sharedMiniProgramSessions.delete(options.projectPath);
553
+ throw error;
554
+ }
555
+ }
556
+ /**
557
+ * @description 释放指定项目的共享会话引用;会话对象会继续缓存,直到显式关闭或重置。
558
+ */
559
+ function releaseSharedMiniProgram(projectPath) {
560
+ const entry = sharedMiniProgramSessions.get(projectPath);
561
+ if (!entry) return;
562
+ entry.refs = Math.max(0, entry.refs - 1);
563
+ }
564
+ /**
565
+ * @description 关闭并移除指定项目的共享 automator 会话。
566
+ */
567
+ async function closeSharedMiniProgram(projectPath) {
568
+ const entry = sharedMiniProgramSessions.get(projectPath);
569
+ if (!entry) return;
570
+ sharedMiniProgramSessions.delete(projectPath);
571
+ (await entry.session.catch(() => null))?.disconnect();
572
+ }
573
+ /**
574
+ * @description 获取当前共享会话数量,供测试断言使用。
575
+ */
576
+ function getSharedMiniProgramSessionCount() {
577
+ return sharedMiniProgramSessions.size;
412
578
  }
413
579
  /**
414
580
  * @description 统一管理 automator 会话生命周期。
415
581
  */
416
582
  async function withMiniProgram(options, runner) {
583
+ if (options.miniProgram) return await runner(options.miniProgram);
584
+ if (options.sharedSession) {
585
+ const miniProgram = await acquireSharedMiniProgram(options);
586
+ try {
587
+ return await runner(miniProgram);
588
+ } catch (error) {
589
+ await closeSharedMiniProgram(options.projectPath);
590
+ throw normalizeMiniProgramConnectionError(error);
591
+ } finally {
592
+ releaseSharedMiniProgram(options.projectPath);
593
+ }
594
+ }
417
595
  let miniProgram = null;
418
596
  try {
419
597
  miniProgram = await connectMiniProgram(options);
@@ -425,25 +603,121 @@ async function withMiniProgram(options, runner) {
425
603
  }
426
604
  }
427
605
  //#endregion
606
+ //#region src/cli/fullPageScreenshot.ts
607
+ function decodeScreenshotBuffer(raw) {
608
+ const buffer = typeof raw === "string" ? Buffer.from(raw, "base64") : Buffer.from(raw);
609
+ if (buffer.length === 0) throw new Error("Failed to capture screenshot");
610
+ return buffer;
611
+ }
612
+ function toPositiveNumber(value) {
613
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
614
+ }
615
+ function createScrollPositions(totalHeight, viewportHeight) {
616
+ if (totalHeight <= viewportHeight) return [0];
617
+ const positions = [];
618
+ const lastStart = Math.max(totalHeight - viewportHeight, 0);
619
+ for (let scrollTop = 0; scrollTop < lastStart; scrollTop += viewportHeight) positions.push(scrollTop);
620
+ if (positions.at(-1) !== lastStart) positions.push(lastStart);
621
+ return positions;
622
+ }
623
+ function cropPngRows(source, startRow, rowCount) {
624
+ const cropped = new PNG({
625
+ width: source.width,
626
+ height: rowCount
627
+ });
628
+ const bytesPerRow = source.width * 4;
629
+ for (let row = 0; row < rowCount; row += 1) {
630
+ const sourceStart = (startRow + row) * bytesPerRow;
631
+ const sourceEnd = sourceStart + bytesPerRow;
632
+ cropped.data.set(source.data.subarray(sourceStart, sourceEnd), row * bytesPerRow);
633
+ }
634
+ return cropped;
635
+ }
636
+ async function restoreScrollPosition(miniProgram, page, scrollTop) {
637
+ await miniProgram.pageScrollTo(scrollTop);
638
+ await page.waitFor(150);
639
+ }
640
+ /**
641
+ * @description 通过多次滚动和拼接生成整页长截图。
642
+ */
643
+ async function captureFullPageScreenshotBuffer(options) {
644
+ const { miniProgram, timeoutMs, runWithTimeout, screenshotTimeoutMessage } = options;
645
+ const page = await miniProgram.currentPage();
646
+ const pageSize = await page.size();
647
+ const systemInfo = await miniProgram.systemInfo();
648
+ const initialScrollTop = typeof page.scrollTop === "function" ? await page.scrollTop() : 0;
649
+ const pageHeight = toPositiveNumber(pageSize.height);
650
+ const viewportHeight = toPositiveNumber(systemInfo.windowHeight);
651
+ if (!pageHeight || !viewportHeight) return decodeScreenshotBuffer(await runWithTimeout(miniProgram.screenshot(), timeoutMs, screenshotTimeoutMessage, "DEVTOOLS_SCREENSHOT_TIMEOUT"));
652
+ const segments = [];
653
+ const positions = createScrollPositions(pageHeight, viewportHeight);
654
+ let coveredUntil = 0;
655
+ let scale = 1;
656
+ try {
657
+ for (const scrollTop of positions) {
658
+ await miniProgram.pageScrollTo(scrollTop);
659
+ await page.waitFor(150);
660
+ const rawScreenshot = await runWithTimeout(miniProgram.screenshot(), timeoutMs, screenshotTimeoutMessage, "DEVTOOLS_SCREENSHOT_TIMEOUT");
661
+ const png = PNG.sync.read(decodeScreenshotBuffer(rawScreenshot));
662
+ if (viewportHeight > 0) scale = png.height / viewportHeight;
663
+ const visibleEnd = Math.min(scrollTop + viewportHeight, pageHeight);
664
+ const cropTopCss = Math.max(coveredUntil - scrollTop, 0);
665
+ const segmentHeightCss = Math.max(visibleEnd - scrollTop - cropTopCss, 0);
666
+ if (segmentHeightCss <= 0) continue;
667
+ const cropTopRows = Math.min(Math.max(Math.round(cropTopCss * scale), 0), png.height);
668
+ const segmentRows = Math.min(Math.max(Math.round(segmentHeightCss * scale), 1), png.height - cropTopRows);
669
+ segments.push(cropPngRows(png, cropTopRows, segmentRows));
670
+ coveredUntil = visibleEnd;
671
+ }
672
+ } finally {
673
+ await restoreScrollPosition(miniProgram, page, initialScrollTop);
674
+ }
675
+ if (segments.length === 0) throw new Error("Failed to capture screenshot");
676
+ const width = segments[0].width;
677
+ const merged = new PNG({
678
+ width,
679
+ height: segments.reduce((sum, segment) => sum + segment.height, 0)
680
+ });
681
+ const bytesPerRow = width * 4;
682
+ let offsetY = 0;
683
+ for (const segment of segments) {
684
+ if (segment.width !== width) throw new Error("Full-page screenshots have inconsistent widths");
685
+ for (let row = 0; row < segment.height; row += 1) {
686
+ const sourceStart = row * bytesPerRow;
687
+ const sourceEnd = sourceStart + bytesPerRow;
688
+ merged.data.set(segment.data.subarray(sourceStart, sourceEnd), (offsetY + row) * bytesPerRow);
689
+ }
690
+ offsetY += segment.height;
691
+ }
692
+ return PNG.sync.write(merged);
693
+ }
694
+ //#endregion
428
695
  //#region src/cli/commands.ts
429
- var commands_exports = /* @__PURE__ */ __exportAll({
430
- audit: () => audit,
431
- captureScreenshotBuffer: () => captureScreenshotBuffer,
432
- currentPage: () => currentPage,
433
- input: () => input,
434
- navigateBack: () => navigateBack,
435
- navigateTo: () => navigateTo,
436
- pageData: () => pageData,
437
- pageStack: () => pageStack,
438
- reLaunch: () => reLaunch,
439
- redirectTo: () => redirectTo,
440
- remote: () => remote,
441
- scrollTo: () => scrollTo,
442
- switchTab: () => switchTab,
443
- systemInfo: () => systemInfo,
444
- takeScreenshot: () => takeScreenshot,
445
- tap: () => tap
446
- });
696
+ function createTimeoutError(message, code) {
697
+ const error = new Error(message);
698
+ error.code = code;
699
+ return error;
700
+ }
701
+ function normalizePagePath(page) {
702
+ return page.startsWith("/") ? page : `/${page}`;
703
+ }
704
+ function sleep(ms) {
705
+ return new Promise((resolve) => setTimeout(resolve, ms));
706
+ }
707
+ function withCommandTimeout(task, timeoutMs, message, code) {
708
+ return new Promise((resolve, reject) => {
709
+ const timeout = setTimeout(() => {
710
+ reject(createTimeoutError(message, code));
711
+ }, timeoutMs);
712
+ task.then((value) => {
713
+ clearTimeout(timeout);
714
+ resolve(value);
715
+ }).catch((error) => {
716
+ clearTimeout(timeout);
717
+ reject(error);
718
+ });
719
+ });
720
+ }
447
721
  async function runRouteCommand(options, startMessage, successMessage, action) {
448
722
  await withMiniProgram(options, async (miniProgram) => {
449
723
  logger_default.info(startMessage);
@@ -581,7 +855,7 @@ async function audit(options) {
581
855
  logger_default.info(i18nText("正在执行体验审计...", "Running experience audit..."));
582
856
  const result = await miniProgram.stopAudits();
583
857
  if (options.outputPath) {
584
- await fs$1.writeFile(options.outputPath, JSON.stringify(result, null, 2));
858
+ await fs.writeFile(options.outputPath, JSON.stringify(result, null, 2));
585
859
  logger_default.success(i18nText(`审计报告已保存到 ${colors.cyan(options.outputPath)}`, `Audit report saved to ${colors.cyan(options.outputPath)}`));
586
860
  return result;
587
861
  }
@@ -594,14 +868,27 @@ async function audit(options) {
594
868
  */
595
869
  async function captureScreenshotBuffer(options) {
596
870
  return await withMiniProgram(options, async (miniProgram) => {
597
- logger_default.info(i18nText(`正在连接 DevTools:${colors.cyan(options.projectPath)}...`, `Connecting to DevTools at ${colors.cyan(options.projectPath)}...`));
871
+ const commandTimeout = options.timeout ?? 3e4;
872
+ 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.`);
873
+ if (!options.miniProgram) logger_default.info(i18nText(`正在连接 DevTools:${colors.cyan(options.projectPath)}...`, `Connecting to DevTools at ${colors.cyan(options.projectPath)}...`));
598
874
  if (options.page) {
599
- logger_default.info(i18nText(`正在跳转页面 ${colors.cyan(options.page)}...`, `Navigating to page ${colors.cyan(options.page)}...`));
600
- await miniProgram.reLaunch(options.page);
875
+ const normalizedPage = normalizePagePath(options.page);
876
+ logger_default.info(i18nText(`正在跳转页面 ${colors.cyan(normalizedPage)}...`, `Navigating to page ${colors.cyan(normalizedPage)}...`));
877
+ await miniProgram.reLaunch(normalizedPage);
878
+ if (options.fullPage) await sleep(1e3);
879
+ }
880
+ if (options.fullPage) {
881
+ logger_default.info(i18nText("正在生成整页长截图...", "Capturing full-page screenshot..."));
882
+ return await captureFullPageScreenshotBuffer({
883
+ miniProgram,
884
+ timeoutMs: commandTimeout,
885
+ runWithTimeout: withCommandTimeout,
886
+ screenshotTimeoutMessage
887
+ });
601
888
  }
602
889
  logger_default.info(i18nText("正在截图...", "Taking screenshot..."));
603
- const screenshot = await miniProgram.screenshot();
604
- const buffer = typeof screenshot === "string" ? Buffer$1.from(screenshot, "base64") : Buffer$1.from(screenshot);
890
+ const screenshot = await withCommandTimeout(miniProgram.screenshot(), commandTimeout, screenshotTimeoutMessage, "DEVTOOLS_SCREENSHOT_TIMEOUT");
891
+ const buffer = typeof screenshot === "string" ? Buffer.from(screenshot, "base64") : Buffer.from(screenshot);
605
892
  if (buffer.length === 0) throw new Error(i18nText("截图失败", "Failed to capture screenshot"));
606
893
  return buffer;
607
894
  });
@@ -610,10 +897,23 @@ async function captureScreenshotBuffer(options) {
610
897
  * @description 获取当前小程序截图。
611
898
  */
612
899
  async function takeScreenshot(options) {
613
- const screenshotBuffer = await captureScreenshotBuffer(options);
900
+ let screenshotBuffer;
901
+ try {
902
+ screenshotBuffer = await captureScreenshotBuffer(options);
903
+ } catch (error) {
904
+ const isProtocolTimeout = error instanceof Error && error.message === "DEVTOOLS_PROTOCOL_TIMEOUT";
905
+ if (!Boolean(options.sharedSession && !options.miniProgram && isProtocolTimeout)) throw error;
906
+ await closeSharedMiniProgram(options.projectPath);
907
+ logger_default.warn(i18nText("当前共享 DevTools 会话截图超时,正在改用全新自动化会话重试一次...", "The current shared DevTools session timed out while capturing screenshot. Retrying once with a fresh automation session..."));
908
+ screenshotBuffer = await captureScreenshotBuffer({
909
+ ...options,
910
+ preferOpenedSession: false,
911
+ sharedSession: false
912
+ });
913
+ }
614
914
  const base64 = screenshotBuffer.toString("base64");
615
915
  if (options.outputPath) {
616
- await fs$1.writeFile(options.outputPath, screenshotBuffer);
916
+ await fs.writeFile(options.outputPath, screenshotBuffer);
617
917
  logger_default.success(i18nText(`截图已保存到 ${colors.cyan(options.outputPath)}`, `Screenshot saved to ${colors.cyan(options.outputPath)}`));
618
918
  return { path: options.outputPath };
619
919
  }
@@ -631,4 +931,4 @@ async function remote(options) {
631
931
  });
632
932
  }
633
933
  //#endregion
634
- export { createCustomConfig as A, isOperatingSystemSupported as B, formatAutomatorLoginError as C, resolveCliPath as D, launchAutomator as E, defaultCustomConfigDirPath as F, colors as H, defaultCustomConfigFilePath as I, resolvePath as L, overwriteCustomConfig as M, readCustomConfig as N, getConfig as O, removeCustomConfigKey as P, SupportedPlatformsMap as R, validateLocaleOption as S, isDevtoolsHttpPortError as T, logger_default as U, operatingSystemName as V, tap as _, input as a, configureLocaleFromArgv as b, pageData as c, redirectTo as d, remote as f, takeScreenshot as g, systemInfo as h, currentPage as i, createLocaleConfig as j, getConfiguredLocale as k, pageStack as l, switchTab as m, captureScreenshotBuffer as n, navigateBack as o, scrollTo as p, commands_exports as r, navigateTo as s, audit as t, reLaunch as u, connectMiniProgram as v, isAutomatorLoginError as w, i18nText as x, withMiniProgram as y, getDefaultCliPath as z };
934
+ 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 };
@@ -0,0 +1,2 @@
1
+ import { h as takeScreenshot } from "./commands-38LzU5ML.js";
2
+ export { takeScreenshot };
package/dist/index.d.ts CHANGED
@@ -1,18 +1,41 @@
1
- import { Element, MiniProgram, Page } from "@weapp-vite/miniprogram-automator";
2
1
  import { Buffer } from "node:buffer";
3
- import * as cac$1 from "cac";
4
- import * as execa from "execa";
2
+ import * as _$_weapp_vite_miniprogram_automator0 from "@weapp-vite/miniprogram-automator";
3
+ import { Element, MiniProgram, Page } from "@weapp-vite/miniprogram-automator";
4
+ import * as _$cac from "cac";
5
+ import * as _$execa from "execa";
5
6
 
6
7
  //#region src/cli/automator.d.ts
7
8
  interface AutomatorOptions {
8
9
  projectPath: string;
9
10
  timeout?: number;
10
11
  cliPath?: string;
12
+ trustProject?: boolean;
13
+ preferOpenedSession?: boolean;
11
14
  }
12
15
  /**
13
16
  * @description 判断错误是否属于开发者工具服务端口不可用。
14
17
  */
15
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;
16
39
  /**
17
40
  * @description 判断错误是否属于开发者工具登录失效。
18
41
  */
@@ -25,6 +48,10 @@ declare function formatAutomatorLoginError(error: unknown): string;
25
48
  * @description 基于当前配置解析 CLI 路径,并通过现代化 automator 入口启动会话。
26
49
  */
27
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>;
28
55
  //#endregion
29
56
  //#region src/cli/automator-argv.d.ts
30
57
  interface ParsedAutomatorArgs {
@@ -47,10 +74,6 @@ declare function readOptionValue(argv: readonly string[], optionName: string): s
47
74
  declare function removeOption(argv: readonly string[], optionName: string): string[];
48
75
  //#endregion
49
76
  //#region src/cli/automator-session.d.ts
50
- interface AutomatorSessionOptions {
51
- projectPath: string;
52
- timeout?: number;
53
- }
54
77
  interface MiniProgramEventMap {
55
78
  console: (payload: unknown) => void;
56
79
  exception: (payload: unknown) => void;
@@ -60,10 +83,33 @@ type MiniProgramPage = InstanceType<typeof Page>;
60
83
  type MiniProgramElement = InstanceType<typeof Element> & {
61
84
  input?: (value: string) => Promise<void>;
62
85
  };
86
+ interface AutomatorSessionOptions {
87
+ miniProgram?: MiniProgramLike;
88
+ preferOpenedSession?: boolean;
89
+ projectPath: string;
90
+ sharedSession?: boolean;
91
+ timeout?: number;
92
+ }
63
93
  /**
64
94
  * @description 建立 automator 会话,并统一处理常见连接错误提示。
65
95
  */
66
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;
67
113
  /**
68
114
  * @description 统一管理 automator 会话生命周期。
69
115
  */
@@ -106,6 +152,7 @@ interface AuditOptions extends AutomatorCommandOptions {
106
152
  interface ScreenshotOptions extends AutomatorCommandOptions {
107
153
  outputPath?: string;
108
154
  page?: string;
155
+ fullPage?: boolean;
109
156
  }
110
157
  interface ScreenshotResult {
111
158
  base64?: string;
@@ -286,7 +333,7 @@ declare function formatRetryHotkeyPrompt(timeoutMs?: number): string;
286
333
  /**
287
334
  * @description 基于 cac 注册顶层命令,用于统一识别入口。
288
335
  */
289
- declare function createCli(): cac$1.CAC;
336
+ declare function createCli(): _$cac.CAC;
290
337
  /**
291
338
  * @description CLI 入口解析与分发。
292
339
  */
@@ -452,7 +499,7 @@ interface ExecuteOptions {
452
499
  /**
453
500
  * @description 执行 CLI 命令并透传输出
454
501
  */
455
- declare function execute(cliPath: string, argv: string[], options?: ExecuteOptions): Promise<execa.Result<{}>>;
502
+ declare function execute(cliPath: string, argv: string[], options?: ExecuteOptions): Promise<_$execa.Result<{}>>;
456
503
  //#endregion
457
504
  //#region src/utils/path.d.ts
458
505
  /**
@@ -460,4 +507,4 @@ declare function execute(cliPath: string, argv: string[], options?: ExecuteOptio
460
507
  */
461
508
  declare function resolvePath(filePath: string): string;
462
509
  //#endregion
463
- 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, captureScreenshotBuffer, 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, parseCompareArgs, parseScreenshotArgs, printCompareHelp, 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 createCustomConfig, B as isOperatingSystemSupported, C as formatAutomatorLoginError, D as resolveCliPath, E as launchAutomator, F as defaultCustomConfigDirPath, I as defaultCustomConfigFilePath, L as resolvePath, M as overwriteCustomConfig, N as readCustomConfig, O as getConfig, P as removeCustomConfigKey, R as SupportedPlatformsMap, T as isDevtoolsHttpPortError, V as operatingSystemName, _ as tap, a as input, c as pageData, d as redirectTo, f as remote, g as takeScreenshot, h as systemInfo, i as currentPage, j as createLocaleConfig, k as getConfiguredLocale, l as pageStack, m as switchTab, n as captureScreenshotBuffer, o as navigateBack, p as scrollTo, s as navigateTo, t as audit, u as reLaunch, v as connectMiniProgram, w as isAutomatorLoginError, y as withMiniProgram, z as getDefaultCliPath } from "./commands-BfdE1eYN.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-DXJCUYYI.js";
3
- export { AUTOMATOR_COMMAND_NAMES, CONFIG_COMMAND_NAME, MINIDEV_NAMESPACE_COMMAND_NAMES, SupportedPlatformsMap, WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES, WECHAT_CLI_COMMAND_NAMES, audit, captureScreenshotBuffer, 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, parseCompareArgs, parseScreenshotArgs, printCompareHelp, 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-38LzU5ML.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-1zieDX-6.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.2.0",
4
+ "version": "5.2.2",
5
5
  "description": "让微信开发者工具,用起来更加方便!",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",
@@ -69,7 +69,7 @@
69
69
  "pngjs": "^7.0.0",
70
70
  "@weapp-core/logger": "^3.1.1",
71
71
  "@weapp-core/shared": "^3.0.3",
72
- "@weapp-vite/miniprogram-automator": "1.0.1"
72
+ "@weapp-vite/miniprogram-automator": "1.0.2"
73
73
  },
74
74
  "scripts": {
75
75
  "dev": "tsdown -w --sourcemap",