weapp-ide-cli 5.1.1 → 5.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{cli-Dv5EAvYh.js → cli-BkQJ3Aww.js} +294 -355
- package/dist/cli.js +2 -2
- package/dist/commands-CSbpftLN.js +628 -0
- package/dist/index.d.ts +100 -41
- package/dist/index.js +3 -3
- package/package.json +4 -3
- package/dist/commands-9F3ycbXU.js +0 -342
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { H as logger_default } from "./commands-CSbpftLN.js";
|
|
2
|
+
import { n as parse } from "./cli-BkQJ3Aww.js";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
//#region src/cli.ts
|
|
5
5
|
parse(process.argv.slice(2)).catch((err) => {
|
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import { Launcher } from "@weapp-vite/miniprogram-automator";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import logger, { colors } from "@weapp-core/logger";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
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
|
|
21
|
+
//#region src/logger.ts
|
|
22
|
+
var logger_default = logger;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/runtime/platform.ts
|
|
25
|
+
/**
|
|
26
|
+
* @description 官方微信开发者工具只支持 Windows、macOS,Linux 只有社区版
|
|
27
|
+
* https://github.com/msojocs/wechat-web-devtools-linux
|
|
28
|
+
*/
|
|
29
|
+
const SupportedPlatformsMap = {
|
|
30
|
+
Windows_NT: "Windows_NT",
|
|
31
|
+
Darwin: "Darwin",
|
|
32
|
+
Linux: "Linux"
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* @description 判断当前系统是否支持微信开发者工具
|
|
36
|
+
*/
|
|
37
|
+
function isOperatingSystemSupported(osName = os.type()) {
|
|
38
|
+
return osName === SupportedPlatformsMap.Windows_NT || osName === SupportedPlatformsMap.Darwin || osName === SupportedPlatformsMap.Linux;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* @description 当前系统名称
|
|
42
|
+
*/
|
|
43
|
+
const operatingSystemName = os.type();
|
|
44
|
+
function createLinuxCliResolver() {
|
|
45
|
+
let resolvedPath;
|
|
46
|
+
let attempted = false;
|
|
47
|
+
let pending = null;
|
|
48
|
+
return async () => {
|
|
49
|
+
if (attempted) return resolvedPath;
|
|
50
|
+
if (!pending) pending = (async () => {
|
|
51
|
+
try {
|
|
52
|
+
const envPath = await getFirstBinaryPath("wechat-devtools-cli");
|
|
53
|
+
if (envPath) resolvedPath = envPath;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
56
|
+
logger_default.warn(`获取 Linux wechat-devtools-cli 路径失败:${reason}`);
|
|
57
|
+
} finally {
|
|
58
|
+
attempted = true;
|
|
59
|
+
}
|
|
60
|
+
return resolvedPath;
|
|
61
|
+
})();
|
|
62
|
+
return pending;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const linuxCliResolver = createLinuxCliResolver();
|
|
66
|
+
const WINDOWS_DEFAULT_CLI = "C:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat";
|
|
67
|
+
const DARWIN_DEFAULT_CLI = "/Applications/wechatwebdevtools.app/Contents/MacOS/cli";
|
|
68
|
+
const cliPathResolvers = {
|
|
69
|
+
[SupportedPlatformsMap.Windows_NT]: async () => WINDOWS_DEFAULT_CLI,
|
|
70
|
+
[SupportedPlatformsMap.Darwin]: async () => DARWIN_DEFAULT_CLI,
|
|
71
|
+
[SupportedPlatformsMap.Linux]: linuxCliResolver
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* @description 获取默认 CLI 路径(按系统)
|
|
75
|
+
*/
|
|
76
|
+
async function getDefaultCliPath(targetOs = operatingSystemName) {
|
|
77
|
+
if (!isOperatingSystemSupported(targetOs)) return;
|
|
78
|
+
const resolver = cliPathResolvers[targetOs];
|
|
79
|
+
return await resolver();
|
|
80
|
+
}
|
|
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
|
+
//#endregion
|
|
94
|
+
//#region src/utils/path.ts
|
|
95
|
+
/**
|
|
96
|
+
* @description 解析为绝对路径(基于当前工作目录)
|
|
97
|
+
*/
|
|
98
|
+
function resolvePath(filePath) {
|
|
99
|
+
if (path.isAbsolute(filePath)) return filePath;
|
|
100
|
+
return path.resolve(process.cwd(), filePath);
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/config/paths.ts
|
|
104
|
+
const homedir = os.homedir();
|
|
105
|
+
/**
|
|
106
|
+
* @description 默认自定义配置目录
|
|
107
|
+
*/
|
|
108
|
+
const defaultCustomConfigDirPath = path.join(homedir, ".weapp-ide-cli");
|
|
109
|
+
/**
|
|
110
|
+
* @description 默认自定义配置文件路径
|
|
111
|
+
*/
|
|
112
|
+
const defaultCustomConfigFilePath = path.join(defaultCustomConfigDirPath, "config.json");
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/config/custom.ts
|
|
115
|
+
const JSON_OPTIONS = {
|
|
116
|
+
encoding: "utf8",
|
|
117
|
+
spaces: 2
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* @description 写入自定义 CLI 路径配置
|
|
121
|
+
*/
|
|
122
|
+
async function createCustomConfig(params) {
|
|
123
|
+
const trimmedCliPath = params.cliPath.trim();
|
|
124
|
+
if (!trimmedCliPath) throw new Error("cliPath cannot be empty");
|
|
125
|
+
const normalizedCliPath = resolvePath(trimmedCliPath);
|
|
126
|
+
await writeCustomConfig({ cliPath: normalizedCliPath });
|
|
127
|
+
return normalizedCliPath;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* @description 写入语言配置(zh / en)。
|
|
131
|
+
*/
|
|
132
|
+
async function createLocaleConfig(locale) {
|
|
133
|
+
await writeCustomConfig({ locale });
|
|
134
|
+
return locale;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* @description 删除指定配置项。
|
|
138
|
+
*/
|
|
139
|
+
async function removeCustomConfigKey(key) {
|
|
140
|
+
const currentConfig = await readCustomConfig();
|
|
141
|
+
if (!(key in currentConfig)) return;
|
|
142
|
+
const nextConfig = { ...currentConfig };
|
|
143
|
+
delete nextConfig[key];
|
|
144
|
+
await writeCustomConfig(nextConfig, { replace: true });
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* @description 覆盖写入配置内容(会替换原内容)。
|
|
148
|
+
*/
|
|
149
|
+
async function overwriteCustomConfig(config) {
|
|
150
|
+
const nextConfig = {};
|
|
151
|
+
if (typeof config.cliPath === "string" && config.cliPath.trim()) nextConfig.cliPath = resolvePath(config.cliPath.trim());
|
|
152
|
+
if (config.locale === "zh" || config.locale === "en") nextConfig.locale = config.locale;
|
|
153
|
+
await writeCustomConfig(nextConfig, { replace: true });
|
|
154
|
+
}
|
|
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
|
+
//#endregion
|
|
181
|
+
//#region src/config/resolver.ts
|
|
182
|
+
/**
|
|
183
|
+
* @description 读取并解析 CLI 配置(自定义优先)
|
|
184
|
+
*/
|
|
185
|
+
async function getConfig() {
|
|
186
|
+
if (await fs.pathExists(defaultCustomConfigFilePath)) try {
|
|
187
|
+
const config = await fs.readJSON(defaultCustomConfigFilePath);
|
|
188
|
+
const cliPath = typeof config.cliPath === "string" ? config.cliPath.trim() : "";
|
|
189
|
+
const locale = config.locale === "zh" || config.locale === "en" ? config.locale : void 0;
|
|
190
|
+
if (cliPath) {
|
|
191
|
+
logger_default.info(`全局配置文件路径:${colors.green(defaultCustomConfigFilePath)}`);
|
|
192
|
+
logger_default.info(`自定义 CLI 路径:${colors.green(cliPath)}`);
|
|
193
|
+
return {
|
|
194
|
+
cliPath,
|
|
195
|
+
locale,
|
|
196
|
+
source: "custom"
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
logger_default.warn("自定义配置文件缺少有效的 CLI 路径,将尝试使用默认路径。");
|
|
200
|
+
} catch (error) {
|
|
201
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
202
|
+
logger_default.warn(`解析自定义配置失败,将尝试使用默认路径。原因:${reason}`);
|
|
203
|
+
}
|
|
204
|
+
const fallbackPath = await getDefaultCliPath();
|
|
205
|
+
if (fallbackPath) return {
|
|
206
|
+
cliPath: fallbackPath,
|
|
207
|
+
locale: void 0,
|
|
208
|
+
source: "default"
|
|
209
|
+
};
|
|
210
|
+
return {
|
|
211
|
+
cliPath: "",
|
|
212
|
+
locale: void 0,
|
|
213
|
+
source: "missing"
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* @description 获取用户配置的语言偏好。
|
|
218
|
+
*/
|
|
219
|
+
async function getConfiguredLocale() {
|
|
220
|
+
return (await readCustomConfig()).locale;
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/cli/resolver.ts
|
|
224
|
+
/**
|
|
225
|
+
* @description 解析 CLI 路径并校验可用性
|
|
226
|
+
*/
|
|
227
|
+
async function resolveCliPath() {
|
|
228
|
+
const config = await getConfig();
|
|
229
|
+
if (!config.cliPath) return {
|
|
230
|
+
cliPath: null,
|
|
231
|
+
source: config.source
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
cliPath: await fs.pathExists(config.cliPath) ? config.cliPath : null,
|
|
235
|
+
source: config.source
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/cli/automator.ts
|
|
240
|
+
const ERROR_STACK_PREFIX_RE = /^at /;
|
|
241
|
+
const ERROR_PREFIX_RE = /^\[error\]\s*/i;
|
|
242
|
+
const ERROR_LABEL_PREFIX_RE = /^error\s*:\s*/i;
|
|
243
|
+
const ERROR_LINE_SPLIT_RE = /\r?\n/;
|
|
244
|
+
const LOGIN_REQUIRED_CN_RE = /需要重新登录/;
|
|
245
|
+
const LOGIN_REQUIRED_EN_RE = /need\s+re-?login|re-?login/i;
|
|
246
|
+
const LOGIN_REQUIRED_CODE_RE = /code\s*[:=]\s*(\d+)/i;
|
|
247
|
+
const DEVTOOLS_HTTP_PORT_ERROR = "Failed to launch wechat web devTools, please make sure http port is open";
|
|
248
|
+
const DEVTOOLS_INFRA_ERROR_PATTERNS = [
|
|
249
|
+
/listen EPERM/i,
|
|
250
|
+
/operation not permitted 0\.0\.0\.0/i,
|
|
251
|
+
/EACCES/i,
|
|
252
|
+
/ECONNREFUSED/i,
|
|
253
|
+
/connect ECONNREFUSED/i
|
|
254
|
+
];
|
|
255
|
+
const DEVTOOLS_LOGIN_REQUIRED_PATTERNS = [
|
|
256
|
+
/code\s*[:=]\s*10/i,
|
|
257
|
+
/需要重新登录/,
|
|
258
|
+
/need\s+re-?login/i,
|
|
259
|
+
/re-?login/i
|
|
260
|
+
];
|
|
261
|
+
/**
|
|
262
|
+
* @description 从错误对象中提取可读文本。
|
|
263
|
+
*/
|
|
264
|
+
function extractErrorText(error) {
|
|
265
|
+
if (!error || typeof error !== "object") return "";
|
|
266
|
+
const candidate = error;
|
|
267
|
+
return [
|
|
268
|
+
candidate.message,
|
|
269
|
+
candidate.shortMessage,
|
|
270
|
+
candidate.stderr,
|
|
271
|
+
candidate.stdout
|
|
272
|
+
].filter((value) => typeof value === "string" && value.trim().length > 0).join("\n");
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* @description 提取登录失效时最适合展示给用户的一行信息。
|
|
276
|
+
*/
|
|
277
|
+
function extractLoginRequiredMessage(text) {
|
|
278
|
+
if (!text) return "";
|
|
279
|
+
if (LOGIN_REQUIRED_CN_RE.test(text)) return "需要重新登录";
|
|
280
|
+
const englishMatch = text.match(LOGIN_REQUIRED_EN_RE);
|
|
281
|
+
if (englishMatch?.[0]) return englishMatch[0].toLowerCase();
|
|
282
|
+
const firstLine = text.split(ERROR_LINE_SPLIT_RE).map((line) => line.trim()).find((line) => Boolean(line) && !ERROR_STACK_PREFIX_RE.test(line));
|
|
283
|
+
if (!firstLine) return "";
|
|
284
|
+
return firstLine.replace(ERROR_PREFIX_RE, "").replace(ERROR_LABEL_PREFIX_RE, "").slice(0, 120);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* @description 判断错误是否属于开发者工具服务端口不可用。
|
|
288
|
+
*/
|
|
289
|
+
function isDevtoolsHttpPortError(error) {
|
|
290
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
291
|
+
return message.includes(DEVTOOLS_HTTP_PORT_ERROR) || DEVTOOLS_INFRA_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* @description 判断错误是否属于开发者工具登录失效。
|
|
295
|
+
*/
|
|
296
|
+
function isAutomatorLoginError(error) {
|
|
297
|
+
const text = extractErrorText(error);
|
|
298
|
+
if (!text) return false;
|
|
299
|
+
return DEVTOOLS_LOGIN_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* @description 格式化开发者工具登录失效错误,便于终端展示。
|
|
303
|
+
*/
|
|
304
|
+
function formatAutomatorLoginError(error) {
|
|
305
|
+
const text = extractErrorText(error);
|
|
306
|
+
const code = text.match(LOGIN_REQUIRED_CODE_RE)?.[1];
|
|
307
|
+
const message = extractLoginRequiredMessage(text);
|
|
308
|
+
const lines = ["微信开发者工具返回登录错误:"];
|
|
309
|
+
if (code) lines.push(`- code: ${code}`);
|
|
310
|
+
if (message) lines.push(`- message: ${message}`);
|
|
311
|
+
if (!code && !message) lines.push("- message: 需要重新登录");
|
|
312
|
+
return lines.join("\n");
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* @description 基于当前配置解析 CLI 路径,并通过现代化 automator 入口启动会话。
|
|
316
|
+
*/
|
|
317
|
+
async function launchAutomator(options) {
|
|
318
|
+
const { cliPath, projectPath, timeout = 3e4 } = options;
|
|
319
|
+
const resolvedCliPath = cliPath ?? (await resolveCliPath()).cliPath ?? void 0;
|
|
320
|
+
return await new Launcher().launch({
|
|
321
|
+
cliPath: resolvedCliPath,
|
|
322
|
+
projectPath,
|
|
323
|
+
timeout
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/i18n.ts
|
|
328
|
+
let currentLocale = "zh";
|
|
329
|
+
/**
|
|
330
|
+
* @description 设置当前语言。
|
|
331
|
+
*/
|
|
332
|
+
function setLocale(locale) {
|
|
333
|
+
currentLocale = normalizeLocale(locale);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* @description 从 argv 和环境变量中解析语言并设置。
|
|
337
|
+
*/
|
|
338
|
+
function configureLocaleFromArgv(argv, fallbackLocale) {
|
|
339
|
+
const localeFromArgv = readLangOption(argv);
|
|
340
|
+
if (localeFromArgv) {
|
|
341
|
+
setLocale(localeFromArgv);
|
|
342
|
+
return currentLocale;
|
|
343
|
+
}
|
|
344
|
+
if (fallbackLocale) {
|
|
345
|
+
setLocale(fallbackLocale);
|
|
346
|
+
return currentLocale;
|
|
347
|
+
}
|
|
348
|
+
const localeFromEnv = process.env.WEAPP_IDE_CLI_LANG || process.env.LANG;
|
|
349
|
+
if (localeFromEnv) {
|
|
350
|
+
setLocale(localeFromEnv);
|
|
351
|
+
return currentLocale;
|
|
352
|
+
}
|
|
353
|
+
setLocale("zh");
|
|
354
|
+
return currentLocale;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* @description 根据当前语言选择文案。
|
|
358
|
+
*/
|
|
359
|
+
function i18nText(zh, en) {
|
|
360
|
+
return currentLocale === "en" ? en : zh;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* @description 校验 --lang 参数是否合法。
|
|
364
|
+
*/
|
|
365
|
+
function validateLocaleOption(argv) {
|
|
366
|
+
const localeFromArgv = readLangOption(argv);
|
|
367
|
+
if (!localeFromArgv) return;
|
|
368
|
+
if (!isLocaleTokenSupported(localeFromArgv)) throw new Error(`不支持的语言: ${localeFromArgv},仅支持 zh 或 en`);
|
|
369
|
+
}
|
|
370
|
+
function normalizeLocale(raw) {
|
|
371
|
+
if (!raw) return "zh";
|
|
372
|
+
const normalized = raw.trim().toLowerCase();
|
|
373
|
+
if (normalized === "en" || normalized.startsWith("en_") || normalized.startsWith("en-")) return "en";
|
|
374
|
+
return "zh";
|
|
375
|
+
}
|
|
376
|
+
function isLocaleTokenSupported(raw) {
|
|
377
|
+
const normalized = raw.trim().toLowerCase();
|
|
378
|
+
return normalized === "zh" || normalized === "en" || normalized.startsWith("zh_") || normalized.startsWith("zh-") || normalized.startsWith("en_") || normalized.startsWith("en-");
|
|
379
|
+
}
|
|
380
|
+
function readLangOption(argv) {
|
|
381
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
382
|
+
const token = argv[index];
|
|
383
|
+
if (!token) continue;
|
|
384
|
+
if (token === "--lang") {
|
|
385
|
+
const value = argv[index + 1];
|
|
386
|
+
return typeof value === "string" ? value : void 0;
|
|
387
|
+
}
|
|
388
|
+
if (token.startsWith("--lang=")) return token.slice(7);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/cli/automator-session.ts
|
|
393
|
+
function normalizeMiniProgramConnectionError(error) {
|
|
394
|
+
if (isAutomatorLoginError(error)) {
|
|
395
|
+
logger_default.error(i18nText("检测到微信开发者工具登录状态失效,请先登录后重试。", "Wechat DevTools login has expired. Please login and retry."));
|
|
396
|
+
logger_default.warn(formatAutomatorLoginError(error));
|
|
397
|
+
return /* @__PURE__ */ new Error("DEVTOOLS_LOGIN_REQUIRED");
|
|
398
|
+
}
|
|
399
|
+
if (isDevtoolsHttpPortError(error)) {
|
|
400
|
+
logger_default.error(i18nText("无法连接到微信开发者工具,请确保已开启 HTTP 服务端口。", "Cannot connect to Wechat DevTools. Please ensure HTTP service port is enabled."));
|
|
401
|
+
logger_default.warn(i18nText("请在微信开发者工具中:设置 -> 安全设置 -> 开启服务端口", "Please enable service port in Wechat DevTools: Settings -> Security -> Service Port"));
|
|
402
|
+
return /* @__PURE__ */ new Error("DEVTOOLS_HTTP_PORT_ERROR");
|
|
403
|
+
}
|
|
404
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* @description 建立 automator 会话,并统一处理常见连接错误提示。
|
|
408
|
+
*/
|
|
409
|
+
async function connectMiniProgram(options) {
|
|
410
|
+
try {
|
|
411
|
+
return await launchAutomator(options);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
throw normalizeMiniProgramConnectionError(error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* @description 统一管理 automator 会话生命周期。
|
|
418
|
+
*/
|
|
419
|
+
async function withMiniProgram(options, runner) {
|
|
420
|
+
let miniProgram = null;
|
|
421
|
+
try {
|
|
422
|
+
miniProgram = await connectMiniProgram(options);
|
|
423
|
+
return await runner(miniProgram);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
throw normalizeMiniProgramConnectionError(error);
|
|
426
|
+
} finally {
|
|
427
|
+
if (miniProgram) await miniProgram.close();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
//#endregion
|
|
431
|
+
//#region src/cli/commands.ts
|
|
432
|
+
var commands_exports = /* @__PURE__ */ __exportAll({
|
|
433
|
+
audit: () => audit,
|
|
434
|
+
currentPage: () => currentPage,
|
|
435
|
+
input: () => input,
|
|
436
|
+
navigateBack: () => navigateBack,
|
|
437
|
+
navigateTo: () => navigateTo,
|
|
438
|
+
pageData: () => pageData,
|
|
439
|
+
pageStack: () => pageStack,
|
|
440
|
+
reLaunch: () => reLaunch,
|
|
441
|
+
redirectTo: () => redirectTo,
|
|
442
|
+
remote: () => remote,
|
|
443
|
+
scrollTo: () => scrollTo,
|
|
444
|
+
switchTab: () => switchTab,
|
|
445
|
+
systemInfo: () => systemInfo,
|
|
446
|
+
takeScreenshot: () => takeScreenshot,
|
|
447
|
+
tap: () => tap
|
|
448
|
+
});
|
|
449
|
+
async function runRouteCommand(options, startMessage, successMessage, action) {
|
|
450
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
451
|
+
logger_default.info(startMessage);
|
|
452
|
+
await action(miniProgram);
|
|
453
|
+
logger_default.success(successMessage);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
function toPageSnapshot(page) {
|
|
457
|
+
return {
|
|
458
|
+
path: page.path ?? "",
|
|
459
|
+
query: page.query
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function printStructuredResult(result, json, title) {
|
|
463
|
+
if (json) {
|
|
464
|
+
console.log(JSON.stringify(result, null, 2));
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
logger_default.info(title);
|
|
468
|
+
console.log(JSON.stringify(result, null, 2));
|
|
469
|
+
}
|
|
470
|
+
async function requireElement(page, selector) {
|
|
471
|
+
const element = await page.$(selector);
|
|
472
|
+
if (!element) throw new Error(i18nText(`未找到元素: ${selector}`, `Element not found: ${selector}`));
|
|
473
|
+
return element;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* @description 执行保留栈页面跳转。
|
|
477
|
+
*/
|
|
478
|
+
async function navigateTo(options) {
|
|
479
|
+
await runRouteCommand(options, i18nText(`正在跳转到 ${colors.cyan(options.url)}...`, `Navigating to ${colors.cyan(options.url)}...`), i18nText(`已跳转到 ${colors.cyan(options.url)}`, `Navigated to ${colors.cyan(options.url)}`), (miniProgram) => miniProgram.navigateTo(options.url));
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* @description 执行关闭当前页的重定向。
|
|
483
|
+
*/
|
|
484
|
+
async function redirectTo(options) {
|
|
485
|
+
await runRouteCommand(options, i18nText(`正在重定向到 ${colors.cyan(options.url)}...`, `Redirecting to ${colors.cyan(options.url)}...`), i18nText(`已重定向到 ${colors.cyan(options.url)}`, `Redirected to ${colors.cyan(options.url)}`), (miniProgram) => miniProgram.redirectTo(options.url));
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* @description 返回上一页。
|
|
489
|
+
*/
|
|
490
|
+
async function navigateBack(options) {
|
|
491
|
+
await runRouteCommand(options, i18nText("正在返回上一页...", "Navigating back..."), i18nText("已返回上一页", "Navigated back"), (miniProgram) => miniProgram.navigateBack());
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* @description 重启到指定页面。
|
|
495
|
+
*/
|
|
496
|
+
async function reLaunch(options) {
|
|
497
|
+
await runRouteCommand(options, i18nText(`正在重启到 ${colors.cyan(options.url)}...`, `Relaunching to ${colors.cyan(options.url)}...`), i18nText(`已重启到 ${colors.cyan(options.url)}`, `Relaunched to ${colors.cyan(options.url)}`), (miniProgram) => miniProgram.reLaunch(options.url));
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* @description 切换到 tabBar 页面。
|
|
501
|
+
*/
|
|
502
|
+
async function switchTab(options) {
|
|
503
|
+
await runRouteCommand(options, i18nText(`正在切换 tab 到 ${colors.cyan(options.url)}...`, `Switching tab to ${colors.cyan(options.url)}...`), i18nText(`已切换 tab 到 ${colors.cyan(options.url)}`, `Switched tab to ${colors.cyan(options.url)}`), (miniProgram) => miniProgram.switchTab(options.url));
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* @description 获取页面栈。
|
|
507
|
+
*/
|
|
508
|
+
async function pageStack(options) {
|
|
509
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
510
|
+
const result = (await miniProgram.pageStack()).map(toPageSnapshot);
|
|
511
|
+
printStructuredResult(result, options.json, i18nText("页面栈:", "Page stack:"));
|
|
512
|
+
return result;
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* @description 获取当前页面信息。
|
|
517
|
+
*/
|
|
518
|
+
async function currentPage(options) {
|
|
519
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
520
|
+
const result = toPageSnapshot(await miniProgram.currentPage());
|
|
521
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
522
|
+
else logger_default.info(i18nText(`当前页面: ${result.path}${result.query ? ` ${JSON.stringify(result.query)}` : ""}`, `Current page: ${result.path}${result.query ? ` ${JSON.stringify(result.query)}` : ""}`));
|
|
523
|
+
return result;
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* @description 获取系统信息。
|
|
528
|
+
*/
|
|
529
|
+
async function systemInfo(options) {
|
|
530
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
531
|
+
const result = await miniProgram.systemInfo();
|
|
532
|
+
printStructuredResult(result, options.json, i18nText("系统信息:", "System info:"));
|
|
533
|
+
return result;
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* @description 获取当前页面数据。
|
|
538
|
+
*/
|
|
539
|
+
async function pageData(options) {
|
|
540
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
541
|
+
const result = await (await miniProgram.currentPage()).data(options.path);
|
|
542
|
+
printStructuredResult(result, options.json, i18nText("页面数据:", "Page data:"));
|
|
543
|
+
return result;
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* @description 点击页面元素。
|
|
548
|
+
*/
|
|
549
|
+
async function tap(options) {
|
|
550
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
551
|
+
logger_default.info(i18nText(`正在点击元素 ${colors.cyan(options.selector)}...`, `Tapping element ${colors.cyan(options.selector)}...`));
|
|
552
|
+
await (await requireElement(await miniProgram.currentPage(), options.selector)).tap();
|
|
553
|
+
logger_default.success(i18nText(`已点击元素 ${colors.cyan(options.selector)}`, `Tapped element ${colors.cyan(options.selector)}`));
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* @description 向页面元素输入文本。
|
|
558
|
+
*/
|
|
559
|
+
async function input(options) {
|
|
560
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
561
|
+
logger_default.info(i18nText(`正在向 ${colors.cyan(options.selector)} 输入 "${colors.cyan(options.value)}"...`, `Inputting "${colors.cyan(options.value)}" into ${colors.cyan(options.selector)}...`));
|
|
562
|
+
const element = await requireElement(await miniProgram.currentPage(), options.selector);
|
|
563
|
+
if (typeof element.input !== "function") throw new TypeError(i18nText(`元素不支持输入: ${options.selector}`, `Element does not support input: ${options.selector}`));
|
|
564
|
+
await element.input(options.value);
|
|
565
|
+
logger_default.success(i18nText(`已向 ${colors.cyan(options.selector)} 输入 "${colors.cyan(options.value)}"`, `Inputted "${colors.cyan(options.value)}" into ${colors.cyan(options.selector)}`));
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* @description 滚动页面到指定位置。
|
|
570
|
+
*/
|
|
571
|
+
async function scrollTo(options) {
|
|
572
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
573
|
+
logger_default.info(i18nText(`正在滚动到位置 ${colors.cyan(String(options.scrollTop))}...`, `Scrolling to position ${colors.cyan(String(options.scrollTop))}...`));
|
|
574
|
+
await miniProgram.pageScrollTo(options.scrollTop);
|
|
575
|
+
logger_default.success(i18nText(`已滚动到位置 ${colors.cyan(String(options.scrollTop))}`, `Scrolled to position ${colors.cyan(String(options.scrollTop))}`));
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* @description 执行体验评分审计。
|
|
580
|
+
*/
|
|
581
|
+
async function audit(options) {
|
|
582
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
583
|
+
logger_default.info(i18nText("正在执行体验审计...", "Running experience audit..."));
|
|
584
|
+
const result = await miniProgram.stopAudits();
|
|
585
|
+
if (options.outputPath) {
|
|
586
|
+
await fs$1.writeFile(options.outputPath, JSON.stringify(result, null, 2));
|
|
587
|
+
logger_default.success(i18nText(`审计报告已保存到 ${colors.cyan(options.outputPath)}`, `Audit report saved to ${colors.cyan(options.outputPath)}`));
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
console.log(JSON.stringify(result, null, 2));
|
|
591
|
+
return result;
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* @description 获取当前小程序截图。
|
|
596
|
+
*/
|
|
597
|
+
async function takeScreenshot(options) {
|
|
598
|
+
return await withMiniProgram(options, async (miniProgram) => {
|
|
599
|
+
logger_default.info(i18nText(`正在连接 DevTools:${colors.cyan(options.projectPath)}...`, `Connecting to DevTools at ${colors.cyan(options.projectPath)}...`));
|
|
600
|
+
if (options.page) {
|
|
601
|
+
logger_default.info(i18nText(`正在跳转页面 ${colors.cyan(options.page)}...`, `Navigating to page ${colors.cyan(options.page)}...`));
|
|
602
|
+
await miniProgram.reLaunch(options.page);
|
|
603
|
+
}
|
|
604
|
+
logger_default.info(i18nText("正在截图...", "Taking screenshot..."));
|
|
605
|
+
const screenshot = await miniProgram.screenshot();
|
|
606
|
+
const base64 = typeof screenshot === "string" ? screenshot : Buffer$1.from(screenshot).toString("base64");
|
|
607
|
+
if (!base64) throw new Error(i18nText("截图失败", "Failed to capture screenshot"));
|
|
608
|
+
if (options.outputPath) {
|
|
609
|
+
await fs$1.writeFile(options.outputPath, Buffer$1.from(base64, "base64"));
|
|
610
|
+
logger_default.success(i18nText(`截图已保存到 ${colors.cyan(options.outputPath)}`, `Screenshot saved to ${colors.cyan(options.outputPath)}`));
|
|
611
|
+
return { path: options.outputPath };
|
|
612
|
+
}
|
|
613
|
+
return { base64 };
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* @description 开关远程调试。
|
|
618
|
+
*/
|
|
619
|
+
async function remote(options) {
|
|
620
|
+
const enable = options.enable ?? true;
|
|
621
|
+
await withMiniProgram(options, async (miniProgram) => {
|
|
622
|
+
logger_default.info(enable ? i18nText("正在开启远程调试...", "Enabling remote debugging...") : i18nText("正在关闭远程调试...", "Disabling remote debugging..."));
|
|
623
|
+
await miniProgram.remote(enable);
|
|
624
|
+
logger_default.success(enable ? i18nText("远程调试已开启", "Remote debugging enabled") : i18nText("远程调试已关闭", "Remote debugging disabled"));
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
//#endregion
|
|
628
|
+
export { createLocaleConfig as A, operatingSystemName as B, isAutomatorLoginError as C, getConfig as D, resolveCliPath as E, defaultCustomConfigFilePath as F, logger_default as H, resolvePath as I, SupportedPlatformsMap as L, readCustomConfig as M, removeCustomConfigKey as N, getConfiguredLocale as O, defaultCustomConfigDirPath as P, getDefaultCliPath as R, formatAutomatorLoginError as S, launchAutomator as T, colors as V, connectMiniProgram as _, navigateBack as a, i18nText as b, pageStack as c, remote as d, scrollTo as f, tap as g, takeScreenshot as h, input as i, overwriteCustomConfig as j, createCustomConfig as k, reLaunch as l, systemInfo as m, commands_exports as n, navigateTo as o, switchTab as p, currentPage as r, pageData as s, audit as t, redirectTo as u, withMiniProgram as v, isDevtoolsHttpPortError as w, validateLocaleOption as x, configureLocaleFromArgv as y, isOperatingSystemSupported as z };
|