weapp-ide-cli 5.0.4 → 5.1.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.
@@ -0,0 +1,1350 @@
1
+ import { S as validateLocaleOption, a as navigateBack, b as configureLocaleFromArgv, c as pageStack, d as remote, f as scrollTo, g as tap, i as input, l as reLaunch, m as systemInfo, o as navigateTo, p as switchTab, r as currentPage, s as pageData, t as audit, u as redirectTo, v as colors, x as i18nText, y as logger_default } from "./commands-9F3ycbXU.js";
2
+ import process, { stdin, stdout } from "node:process";
3
+ import fs from "fs-extra";
4
+ import path from "pathe";
5
+ import os from "node:os";
6
+ import { createInterface } from "node:readline/promises";
7
+ import { emitKeypressEvents } from "node:readline";
8
+ //#region src/cli/automator-argv.ts
9
+ /**
10
+ * @description 解析 automator 命令通用参数与位置参数。
11
+ */
12
+ function parseAutomatorArgs(argv) {
13
+ const positionals = [];
14
+ let projectPath = process.cwd();
15
+ let timeout;
16
+ let json = false;
17
+ for (let index = 0; index < argv.length; index += 1) {
18
+ const token = argv[index];
19
+ if (!token) continue;
20
+ if (token === "-p" || token === "--project") {
21
+ const value = argv[index + 1];
22
+ if (typeof value === "string" && !value.startsWith("-")) {
23
+ projectPath = value;
24
+ index += 1;
25
+ } else projectPath = process.cwd();
26
+ continue;
27
+ }
28
+ if (token.startsWith("--project=")) {
29
+ projectPath = token.slice(10) || process.cwd();
30
+ continue;
31
+ }
32
+ if (token === "-t" || token === "--timeout") {
33
+ const value = argv[index + 1];
34
+ if (typeof value === "string") {
35
+ timeout = parsePositiveInt(value);
36
+ index += 1;
37
+ }
38
+ continue;
39
+ }
40
+ if (token.startsWith("--timeout=")) {
41
+ timeout = parsePositiveInt(token.slice(10));
42
+ continue;
43
+ }
44
+ if (token === "--json") {
45
+ json = true;
46
+ continue;
47
+ }
48
+ if (token.startsWith("-")) {
49
+ if (takesValue(token.includes("=") ? token.slice(0, token.indexOf("=")) : token) && !token.includes("=")) {
50
+ const value = argv[index + 1];
51
+ if (typeof value === "string" && !value.startsWith("-")) index += 1;
52
+ }
53
+ continue;
54
+ }
55
+ positionals.push(token);
56
+ }
57
+ return {
58
+ projectPath,
59
+ timeout,
60
+ json,
61
+ positionals
62
+ };
63
+ }
64
+ /**
65
+ * @description 读取选项值,支持 --option value 与 --option=value。
66
+ */
67
+ function readOptionValue(argv, optionName) {
68
+ const optionWithEqual = `${optionName}=`;
69
+ for (let index = 0; index < argv.length; index += 1) {
70
+ const token = argv[index];
71
+ if (!token) continue;
72
+ if (token === optionName) {
73
+ const value = argv[index + 1];
74
+ if (typeof value !== "string") return;
75
+ return value.trim();
76
+ }
77
+ if (token.startsWith(optionWithEqual)) return token.slice(optionWithEqual.length).trim();
78
+ }
79
+ }
80
+ /**
81
+ * @description 删除参数中的指定选项(同时支持 --opt value 与 --opt=value)。
82
+ */
83
+ function removeOption(argv, optionName) {
84
+ const optionWithEqual = `${optionName}=`;
85
+ const nextArgv = [];
86
+ for (let index = 0; index < argv.length; index += 1) {
87
+ const token = argv[index];
88
+ if (!token) continue;
89
+ if (token === optionName) {
90
+ const nextToken = argv[index + 1];
91
+ if (takesValue(optionName) && typeof nextToken === "string" && !nextToken.startsWith("-")) index += 1;
92
+ continue;
93
+ }
94
+ if (token.startsWith(optionWithEqual)) continue;
95
+ nextArgv.push(token);
96
+ }
97
+ return nextArgv;
98
+ }
99
+ function parsePositiveInt(raw) {
100
+ const value = Number.parseInt(raw, 10);
101
+ if (!Number.isFinite(value) || value <= 0) return;
102
+ return value;
103
+ }
104
+ function takesValue(optionName) {
105
+ 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
+ }
107
+ //#endregion
108
+ //#region src/cli/screenshot.ts
109
+ /**
110
+ * @description Print help for screenshot command
111
+ */
112
+ function printScreenshotHelp() {
113
+ console.log(i18nText(`
114
+ ${colors.bold("Usage:")} weapp screenshot [options]
115
+
116
+ ${colors.bold("参数:")}
117
+ -p, --project <path> 项目路径(默认:当前目录)
118
+ -o, --output <path> 截图输出文件路径
119
+ --page <path> 截图前先跳转页面
120
+ -t, --timeout <ms> 连接超时时间(默认:30000)
121
+ --json 以 JSON 格式输出
122
+ --lang <lang> 语言切换:zh | en(默认:zh)
123
+ -h, --help 显示此帮助信息
124
+
125
+ ${colors.bold("示例:")}
126
+ # 输出 base64 到 stdout
127
+ weapp screenshot -p /path/to/project
128
+
129
+ # 保存到文件
130
+ weapp screenshot -p /path/to/project -o screenshot.png
131
+
132
+ # 先跳转页面再截图
133
+ weapp screenshot -p /path/to/project --page pages/index/index
134
+
135
+ # 以 JSON 输出便于脚本解析
136
+ weapp screenshot -p /path/to/project --json
137
+ `, `
138
+ ${colors.bold("Usage:")} weapp screenshot [options]
139
+
140
+ ${colors.bold("Options:")}
141
+ -p, --project <path> Project path (default: current directory)
142
+ -o, --output <path> Output file path for screenshot
143
+ --page <path> Navigate to page before taking screenshot
144
+ -t, --timeout <ms> Connection timeout in milliseconds (default: 30000)
145
+ --json Output as JSON format
146
+ --lang <lang> Language: zh | en (default: zh)
147
+ -h, --help Show this help message
148
+
149
+ ${colors.bold("Examples:")}
150
+ # Output base64 to stdout
151
+ weapp screenshot -p /path/to/project
152
+
153
+ # Save to file
154
+ weapp screenshot -p /path/to/project -o screenshot.png
155
+
156
+ # Navigate to page first
157
+ weapp screenshot -p /path/to/project --page pages/index/index
158
+
159
+ # JSON output for parsing
160
+ weapp screenshot -p /path/to/project --json
161
+ `));
162
+ }
163
+ /**
164
+ * @description Parse command line arguments for screenshot command
165
+ */
166
+ function parseScreenshotArgs(argv) {
167
+ const parsed = parseAutomatorArgs(argv);
168
+ return {
169
+ projectPath: parsed.projectPath,
170
+ timeout: parsed.timeout,
171
+ outputPath: readOptionValue(argv, "-o") || readOptionValue(argv, "--output"),
172
+ page: readOptionValue(argv, "--page")
173
+ };
174
+ }
175
+ /**
176
+ * @description 运行截图命令并处理输出格式。
177
+ */
178
+ async function runScreenshot(argv) {
179
+ if (argv.includes("-h") || argv.includes("--help")) {
180
+ printScreenshotHelp();
181
+ return;
182
+ }
183
+ const options = parseScreenshotArgs(argv);
184
+ const isJsonOutput = argv.includes("--json");
185
+ const { takeScreenshot } = await import("./commands-9F3ycbXU.js").then((n) => n.n);
186
+ const result = await takeScreenshot(options);
187
+ if (isJsonOutput) {
188
+ console.log(JSON.stringify(result, null, 2));
189
+ return;
190
+ }
191
+ if (result.base64) console.log(result.base64);
192
+ }
193
+ //#endregion
194
+ //#region src/cli/run-automator.ts
195
+ const COMMON_ALLOWED_OPTIONS = new Set([
196
+ "-p",
197
+ "--project",
198
+ "-t",
199
+ "--timeout",
200
+ "--json",
201
+ "--lang",
202
+ "-h",
203
+ "--help"
204
+ ]);
205
+ const COMMAND_DEFINITIONS = {
206
+ "navigate": createDefinition({
207
+ description: {
208
+ zh: "跳转到页面(保留当前页面栈)",
209
+ en: "Navigate to a page (keep current page in stack)"
210
+ },
211
+ usage: "weapp navigate <url> -p <project-path>",
212
+ handler: async ({ args }) => {
213
+ const url = requiredPositional(args.positionals[0], i18nText("navigate 命令缺少 URL 参数", "URL is required for 'navigate' command"));
214
+ await navigateTo({
215
+ ...args,
216
+ url
217
+ });
218
+ }
219
+ }),
220
+ "redirect": createDefinition({
221
+ description: {
222
+ zh: "重定向到页面(关闭当前页面)",
223
+ en: "Redirect to a page (close current page)"
224
+ },
225
+ usage: "weapp redirect <url> -p <project-path>",
226
+ handler: async ({ args }) => {
227
+ const url = requiredPositional(args.positionals[0], i18nText("redirect 命令缺少 URL 参数", "URL is required for 'redirect' command"));
228
+ await redirectTo({
229
+ ...args,
230
+ url
231
+ });
232
+ }
233
+ }),
234
+ "back": createDefinition({
235
+ description: {
236
+ zh: "返回上一页",
237
+ en: "Navigate back to previous page"
238
+ },
239
+ usage: "weapp back -p <project-path>",
240
+ handler: async ({ args }) => {
241
+ await navigateBack(args);
242
+ }
243
+ }),
244
+ "relaunch": createDefinition({
245
+ description: {
246
+ zh: "重启到页面(关闭全部页面)",
247
+ en: "Re-launch to a page (close all pages)"
248
+ },
249
+ usage: "weapp relaunch <url> -p <project-path>",
250
+ handler: async ({ args }) => {
251
+ const url = requiredPositional(args.positionals[0], i18nText("relaunch 命令缺少 URL 参数", "URL is required for 'relaunch' command"));
252
+ await reLaunch({
253
+ ...args,
254
+ url
255
+ });
256
+ }
257
+ }),
258
+ "switch-tab": createDefinition({
259
+ description: {
260
+ zh: "切换到 tabBar 页面",
261
+ en: "Switch to a tab bar page"
262
+ },
263
+ usage: "weapp switch-tab <url> -p <project-path>",
264
+ handler: async ({ args }) => {
265
+ const url = requiredPositional(args.positionals[0], i18nText("switch-tab 命令缺少 URL 参数", "URL is required for 'switch-tab' command"));
266
+ await switchTab({
267
+ ...args,
268
+ url
269
+ });
270
+ }
271
+ }),
272
+ "page-stack": createDefinition({
273
+ description: {
274
+ zh: "获取页面栈",
275
+ en: "Get the page stack"
276
+ },
277
+ usage: "weapp page-stack -p <project-path>",
278
+ handler: async ({ args }) => {
279
+ await pageStack(args);
280
+ }
281
+ }),
282
+ "current-page": createDefinition({
283
+ description: {
284
+ zh: "获取当前页面信息",
285
+ en: "Get current page info"
286
+ },
287
+ usage: "weapp current-page -p <project-path>",
288
+ handler: async ({ args }) => {
289
+ await currentPage(args);
290
+ }
291
+ }),
292
+ "system-info": createDefinition({
293
+ description: {
294
+ zh: "获取系统信息",
295
+ en: "Get system info"
296
+ },
297
+ usage: "weapp system-info -p <project-path>",
298
+ handler: async ({ args }) => {
299
+ await systemInfo(args);
300
+ }
301
+ }),
302
+ "page-data": createDefinition({
303
+ description: {
304
+ zh: "获取页面数据",
305
+ en: "Get page data"
306
+ },
307
+ usage: "weapp page-data [path] -p <project-path>",
308
+ handler: async ({ args }) => {
309
+ await pageData({
310
+ ...args,
311
+ path: args.positionals[0]
312
+ });
313
+ }
314
+ }),
315
+ "tap": createDefinition({
316
+ description: {
317
+ zh: "点击元素",
318
+ en: "Tap an element"
319
+ },
320
+ usage: "weapp tap <selector> -p <project-path>",
321
+ handler: async ({ args }) => {
322
+ const selector = requiredPositional(args.positionals[0], i18nText("tap 命令缺少 selector 参数", "Selector is required for tap command"));
323
+ await tap({
324
+ ...args,
325
+ selector
326
+ });
327
+ }
328
+ }),
329
+ "input": createDefinition({
330
+ description: {
331
+ zh: "向元素输入文本",
332
+ en: "Input text into an element"
333
+ },
334
+ usage: "weapp input <selector> <value> -p <project-path>",
335
+ handler: async ({ args }) => {
336
+ const errorMessage = i18nText("input 命令缺少 selector 或 value 参数", "Selector and value are required for input command");
337
+ const selector = requiredPositional(args.positionals[0], errorMessage);
338
+ const value = requiredPositional(args.positionals[1], errorMessage);
339
+ await input({
340
+ ...args,
341
+ selector,
342
+ value
343
+ });
344
+ }
345
+ }),
346
+ "scroll": createDefinition({
347
+ description: {
348
+ zh: "滚动页面到指定位置",
349
+ en: "Scroll page to position"
350
+ },
351
+ usage: "weapp scroll <scrollTop> -p <project-path>",
352
+ handler: async ({ args }) => {
353
+ const rawScrollTop = requiredPositional(args.positionals[0], i18nText("scroll 命令缺少滚动位置参数", "Scroll position is required for scroll command"));
354
+ const scrollTop = Number.parseInt(rawScrollTop, 10);
355
+ if (!Number.isFinite(scrollTop)) throw new TypeError(i18nText(`无效的滚动位置: ${rawScrollTop}`, `Invalid scroll position: ${rawScrollTop}`));
356
+ await scrollTo({
357
+ ...args,
358
+ scrollTop
359
+ });
360
+ }
361
+ }),
362
+ "audit": createDefinition({
363
+ description: {
364
+ zh: "执行体验评分审计",
365
+ en: "Run experience audit"
366
+ },
367
+ usage: "weapp audit -p <project-path>",
368
+ options: [{
369
+ flag: "-o, --output <path>",
370
+ description: {
371
+ zh: "审计报告输出文件路径",
372
+ en: "Output file path for report"
373
+ }
374
+ }],
375
+ allowedOptions: ["-o", "--output"],
376
+ handler: async ({ args, argv }) => {
377
+ const outputPath = readOptionValue(argv, "-o") || readOptionValue(argv, "--output");
378
+ await audit({
379
+ ...args,
380
+ outputPath
381
+ });
382
+ }
383
+ }),
384
+ "remote": createDefinition({
385
+ description: {
386
+ zh: "开启/关闭远程调试",
387
+ en: "Enable/disable remote debugging"
388
+ },
389
+ usage: "weapp remote -p <project-path>",
390
+ options: [{
391
+ flag: "--disable",
392
+ description: {
393
+ zh: "关闭远程调试",
394
+ en: "Disable remote debugging"
395
+ }
396
+ }],
397
+ allowedOptions: ["--disable"],
398
+ handler: async ({ args, argv }) => {
399
+ await remote({
400
+ ...args,
401
+ enable: !argv.includes("--disable")
402
+ });
403
+ }
404
+ })
405
+ };
406
+ const AUTOMATOR_COMMAND_NAMES = ["screenshot", ...Object.keys(COMMAND_DEFINITIONS)];
407
+ const AUTOMATOR_COMMANDS = new Set(AUTOMATOR_COMMAND_NAMES);
408
+ /**
409
+ * @description 判断是否属于 automator 子命令。
410
+ */
411
+ function isAutomatorCommand(command) {
412
+ return Boolean(command && AUTOMATOR_COMMANDS.has(command));
413
+ }
414
+ /**
415
+ * @description 分发 automator 子命令。
416
+ */
417
+ async function runAutomatorCommand(command, argv) {
418
+ if (command === "screenshot") {
419
+ await runScreenshot(argv);
420
+ return;
421
+ }
422
+ const definition = COMMAND_DEFINITIONS[command];
423
+ if (!definition) throw new Error(i18nText(`未知 automator 命令: ${command}`, `Unknown automator command: ${command}`));
424
+ if (argv.includes("-h") || argv.includes("--help")) {
425
+ printCommandHelp(command);
426
+ return;
427
+ }
428
+ validateUnsupportedOptions(command, argv, definition.allowedOptions);
429
+ const args = parseAutomatorArgs(argv);
430
+ await definition.handler({
431
+ argv,
432
+ args
433
+ });
434
+ }
435
+ /**
436
+ * @description 获取 automator 命令帮助文本。
437
+ */
438
+ function getAutomatorCommandHelp(command) {
439
+ const definition = COMMAND_DEFINITIONS[command];
440
+ if (!definition) return;
441
+ const optionLines = [
442
+ ...definition.options,
443
+ {
444
+ flag: "-p, --project <path>",
445
+ description: {
446
+ zh: "项目路径(默认:当前目录)",
447
+ en: "Project path (default: current directory)"
448
+ }
449
+ },
450
+ {
451
+ flag: "-t, --timeout <ms>",
452
+ description: {
453
+ zh: "连接超时时间(默认:30000)",
454
+ en: "Connection timeout (default: 30000)"
455
+ }
456
+ },
457
+ {
458
+ flag: "--json",
459
+ description: {
460
+ zh: "支持时以 JSON 输出",
461
+ en: "Output as JSON when supported"
462
+ }
463
+ },
464
+ {
465
+ flag: "--lang <lang>",
466
+ description: {
467
+ zh: "语言切换:zh | en(默认:zh)",
468
+ en: "Language: zh | en (default: zh)"
469
+ }
470
+ },
471
+ {
472
+ flag: "-h, --help",
473
+ description: {
474
+ zh: "显示命令帮助",
475
+ en: "Show command help"
476
+ }
477
+ }
478
+ ];
479
+ return [
480
+ i18nText(definition.description.zh, definition.description.en),
481
+ "",
482
+ `Usage: ${definition.usage}`,
483
+ "",
484
+ i18nText("参数:", "Options:"),
485
+ ...optionLines.map((option) => ` ${option.flag.padEnd(24)} ${i18nText(option.description.zh, option.description.en)}`)
486
+ ].join("\n");
487
+ }
488
+ function printCommandHelp(command) {
489
+ const help = getAutomatorCommandHelp(command);
490
+ if (help) {
491
+ console.log(help);
492
+ return;
493
+ }
494
+ logger_default.warn(i18nText(`命令 ${command} 暂无帮助信息`, `No help available for command: ${command}`));
495
+ }
496
+ function createDefinition(input) {
497
+ const options = input.options ?? [];
498
+ const allowedOptions = new Set([...COMMON_ALLOWED_OPTIONS, ...input.allowedOptions ?? []]);
499
+ return {
500
+ description: input.description,
501
+ usage: input.usage,
502
+ options,
503
+ allowedOptions,
504
+ handler: input.handler
505
+ };
506
+ }
507
+ function validateUnsupportedOptions(command, argv, allowedOptions) {
508
+ for (const token of argv) {
509
+ if (!token.startsWith("-")) continue;
510
+ const optionName = token.includes("=") ? token.slice(0, token.indexOf("=")) : token;
511
+ if (allowedOptions.has(optionName)) continue;
512
+ throw new Error(i18nText(`'${command}' 命令不支持参数 '${optionName}'`, `Unknown option '${optionName}' for '${command}' command`));
513
+ }
514
+ }
515
+ function requiredPositional(value, message) {
516
+ if (!value) throw new Error(message);
517
+ return value;
518
+ }
519
+ //#endregion
520
+ //#region src/cli/command-catalog.ts
521
+ const WECHAT_CLI_COMMAND_NAMES = [
522
+ "open",
523
+ "login",
524
+ "islogin",
525
+ "preview",
526
+ "auto-preview",
527
+ "upload",
528
+ "build-npm",
529
+ "auto",
530
+ "auto-replay",
531
+ "reset-fileutils",
532
+ "close",
533
+ "quit",
534
+ "cache",
535
+ "engine",
536
+ "open-other",
537
+ "build-ipa",
538
+ "build-apk",
539
+ "cloud"
540
+ ];
541
+ const MINIDEV_NAMESPACE_COMMAND_NAMES = [
542
+ "alipay",
543
+ "ali",
544
+ "minidev"
545
+ ];
546
+ const CONFIG_COMMAND_NAME = "config";
547
+ const WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES = [
548
+ ...WECHAT_CLI_COMMAND_NAMES,
549
+ ...AUTOMATOR_COMMAND_NAMES,
550
+ ...MINIDEV_NAMESPACE_COMMAND_NAMES,
551
+ CONFIG_COMMAND_NAME
552
+ ];
553
+ const WEAPP_IDE_TOP_LEVEL_COMMAND_SET = new Set(WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES);
554
+ /**
555
+ * @description 判断是否为 weapp-ide-cli 支持的顶层命令。
556
+ */
557
+ function isWeappIdeTopLevelCommand(command) {
558
+ return Boolean(command && WEAPP_IDE_TOP_LEVEL_COMMAND_SET.has(command));
559
+ }
560
+ //#endregion
561
+ //#region src/utils/path.ts
562
+ /**
563
+ * @description 解析为绝对路径(基于当前工作目录)
564
+ */
565
+ function resolvePath(filePath) {
566
+ if (path.isAbsolute(filePath)) return filePath;
567
+ return path.resolve(process.cwd(), filePath);
568
+ }
569
+ //#endregion
570
+ //#region src/config/paths.ts
571
+ const homedir = os.homedir();
572
+ /**
573
+ * @description 默认自定义配置目录
574
+ */
575
+ const defaultCustomConfigDirPath = path.join(homedir, ".weapp-ide-cli");
576
+ /**
577
+ * @description 默认自定义配置文件路径
578
+ */
579
+ const defaultCustomConfigFilePath = path.join(defaultCustomConfigDirPath, "config.json");
580
+ //#endregion
581
+ //#region src/config/custom.ts
582
+ const JSON_OPTIONS = {
583
+ encoding: "utf8",
584
+ spaces: 2
585
+ };
586
+ /**
587
+ * @description 写入自定义 CLI 路径配置
588
+ */
589
+ async function createCustomConfig(params) {
590
+ const trimmedCliPath = params.cliPath.trim();
591
+ if (!trimmedCliPath) throw new Error("cliPath cannot be empty");
592
+ const normalizedCliPath = resolvePath(trimmedCliPath);
593
+ await writeCustomConfig({ cliPath: normalizedCliPath });
594
+ return normalizedCliPath;
595
+ }
596
+ /**
597
+ * @description 写入语言配置(zh / en)。
598
+ */
599
+ async function createLocaleConfig(locale) {
600
+ await writeCustomConfig({ locale });
601
+ return locale;
602
+ }
603
+ /**
604
+ * @description 删除指定配置项。
605
+ */
606
+ async function removeCustomConfigKey(key) {
607
+ const currentConfig = await readCustomConfig();
608
+ if (!(key in currentConfig)) return;
609
+ const nextConfig = { ...currentConfig };
610
+ delete nextConfig[key];
611
+ await writeCustomConfig(nextConfig, { replace: true });
612
+ }
613
+ /**
614
+ * @description 覆盖写入配置内容(会替换原内容)。
615
+ */
616
+ async function overwriteCustomConfig(config) {
617
+ const nextConfig = {};
618
+ if (typeof config.cliPath === "string" && config.cliPath.trim()) nextConfig.cliPath = resolvePath(config.cliPath.trim());
619
+ if (config.locale === "zh" || config.locale === "en") nextConfig.locale = config.locale;
620
+ await writeCustomConfig(nextConfig, { replace: true });
621
+ }
622
+ /**
623
+ * @description 读取原始自定义配置。
624
+ */
625
+ async function readCustomConfig() {
626
+ if (!await fs.pathExists(defaultCustomConfigFilePath)) return {};
627
+ try {
628
+ const config = await fs.readJSON(defaultCustomConfigFilePath);
629
+ if (!config || typeof config !== "object") return {};
630
+ const candidate = config;
631
+ const next = {};
632
+ if (typeof candidate.cliPath === "string" && candidate.cliPath.trim()) next.cliPath = candidate.cliPath.trim();
633
+ if (candidate.locale === "zh" || candidate.locale === "en") next.locale = candidate.locale;
634
+ return next;
635
+ } catch {
636
+ return {};
637
+ }
638
+ }
639
+ async function writeCustomConfig(patch, options = {}) {
640
+ const nextConfig = {
641
+ ...options.replace ? {} : await readCustomConfig(),
642
+ ...patch
643
+ };
644
+ await fs.ensureDir(defaultCustomConfigDirPath);
645
+ await fs.writeJSON(defaultCustomConfigFilePath, nextConfig, JSON_OPTIONS);
646
+ }
647
+ //#endregion
648
+ //#region src/cli/prompt.ts
649
+ /**
650
+ * @description 交互式提示并保存 CLI 路径
651
+ */
652
+ async function promptForCliPath() {
653
+ const rl = createInterface({
654
+ input: stdin,
655
+ output: stdout
656
+ });
657
+ try {
658
+ logger_default.info(`请设置 ${colors.bold("微信web开发者工具 CLI")} 的路径`);
659
+ logger_default.info("提示:命令行工具默认所在位置:");
660
+ logger_default.info(`- MacOS: ${colors.green("<安装路径>/Contents/MacOS/cli")}`);
661
+ logger_default.info(`- Windows: ${colors.green("<安装路径>/cli.bat")}`);
662
+ logger_default.info(`- Linux: ${colors.green("<安装路径>/files/bin/bin/wechat-devtools-cli")}`);
663
+ const cliPath = (await rl.question("请输入微信web开发者工具 CLI 路径:")).trim();
664
+ if (!cliPath) {
665
+ logger_default.error("路径不能为空,已取消本次配置。");
666
+ return null;
667
+ }
668
+ try {
669
+ const normalizedPath = await createCustomConfig({ cliPath });
670
+ logger_default.info(`全局配置存储位置:${colors.green(defaultCustomConfigFilePath)}`);
671
+ if (!await fs.pathExists(normalizedPath)) logger_default.warn("在当前路径未找到微信web开发者命令行工具,请确认路径是否正确。");
672
+ return normalizedPath;
673
+ } catch (error) {
674
+ const reason = error instanceof Error ? error.message : String(error);
675
+ logger_default.error(`保存配置失败:${reason}`);
676
+ return null;
677
+ }
678
+ } finally {
679
+ rl.close();
680
+ }
681
+ }
682
+ //#endregion
683
+ //#region src/runtime/platform.ts
684
+ /**
685
+ * @description 官方微信开发者工具只支持 Windows、macOS,Linux 只有社区版
686
+ * https://github.com/msojocs/wechat-web-devtools-linux
687
+ */
688
+ const SupportedPlatformsMap = {
689
+ Windows_NT: "Windows_NT",
690
+ Darwin: "Darwin",
691
+ Linux: "Linux"
692
+ };
693
+ /**
694
+ * @description 判断当前系统是否支持微信开发者工具
695
+ */
696
+ function isOperatingSystemSupported(osName = os.type()) {
697
+ return osName === SupportedPlatformsMap.Windows_NT || osName === SupportedPlatformsMap.Darwin || osName === SupportedPlatformsMap.Linux;
698
+ }
699
+ /**
700
+ * @description 当前系统名称
701
+ */
702
+ const operatingSystemName = os.type();
703
+ function createLinuxCliResolver() {
704
+ let resolvedPath;
705
+ let attempted = false;
706
+ let pending = null;
707
+ return async () => {
708
+ if (attempted) return resolvedPath;
709
+ if (!pending) pending = (async () => {
710
+ try {
711
+ const envPath = await getFirstBinaryPath("wechat-devtools-cli");
712
+ if (envPath) resolvedPath = envPath;
713
+ } catch (error) {
714
+ const reason = error instanceof Error ? error.message : String(error);
715
+ logger_default.warn(`获取 Linux wechat-devtools-cli 路径失败:${reason}`);
716
+ } finally {
717
+ attempted = true;
718
+ }
719
+ return resolvedPath;
720
+ })();
721
+ return pending;
722
+ };
723
+ }
724
+ const linuxCliResolver = createLinuxCliResolver();
725
+ const WINDOWS_DEFAULT_CLI = "C:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat";
726
+ const DARWIN_DEFAULT_CLI = "/Applications/wechatwebdevtools.app/Contents/MacOS/cli";
727
+ const cliPathResolvers = {
728
+ [SupportedPlatformsMap.Windows_NT]: async () => WINDOWS_DEFAULT_CLI,
729
+ [SupportedPlatformsMap.Darwin]: async () => DARWIN_DEFAULT_CLI,
730
+ [SupportedPlatformsMap.Linux]: linuxCliResolver
731
+ };
732
+ /**
733
+ * @description 获取默认 CLI 路径(按系统)
734
+ */
735
+ async function getDefaultCliPath(targetOs = operatingSystemName) {
736
+ if (!isOperatingSystemSupported(targetOs)) return;
737
+ const resolver = cliPathResolvers[targetOs];
738
+ return await resolver();
739
+ }
740
+ async function getFirstBinaryPath(command) {
741
+ const pathDirs = (process.env.PATH || "").split(path.delimiter);
742
+ for (const dir of pathDirs) {
743
+ const fullPath = path.join(dir, command);
744
+ try {
745
+ await fs.access(fullPath, fs.constants.X_OK);
746
+ return fullPath;
747
+ } catch {
748
+ continue;
749
+ }
750
+ }
751
+ }
752
+ //#endregion
753
+ //#region src/config/resolver.ts
754
+ /**
755
+ * @description 读取并解析 CLI 配置(自定义优先)
756
+ */
757
+ async function getConfig() {
758
+ if (await fs.pathExists(defaultCustomConfigFilePath)) try {
759
+ const config = await fs.readJSON(defaultCustomConfigFilePath);
760
+ const cliPath = typeof config.cliPath === "string" ? config.cliPath.trim() : "";
761
+ const locale = config.locale === "zh" || config.locale === "en" ? config.locale : void 0;
762
+ if (cliPath) {
763
+ logger_default.info(`全局配置文件路径:${colors.green(defaultCustomConfigFilePath)}`);
764
+ logger_default.info(`自定义 CLI 路径:${colors.green(cliPath)}`);
765
+ return {
766
+ cliPath,
767
+ locale,
768
+ source: "custom"
769
+ };
770
+ }
771
+ logger_default.warn("自定义配置文件缺少有效的 CLI 路径,将尝试使用默认路径。");
772
+ } catch (error) {
773
+ const reason = error instanceof Error ? error.message : String(error);
774
+ logger_default.warn(`解析自定义配置失败,将尝试使用默认路径。原因:${reason}`);
775
+ }
776
+ const fallbackPath = await getDefaultCliPath();
777
+ if (fallbackPath) return {
778
+ cliPath: fallbackPath,
779
+ locale: void 0,
780
+ source: "default"
781
+ };
782
+ return {
783
+ cliPath: "",
784
+ locale: void 0,
785
+ source: "missing"
786
+ };
787
+ }
788
+ /**
789
+ * @description 获取用户配置的语言偏好。
790
+ */
791
+ async function getConfiguredLocale() {
792
+ return (await readCustomConfig()).locale;
793
+ }
794
+ //#endregion
795
+ //#region src/cli/resolver.ts
796
+ /**
797
+ * @description 解析 CLI 路径并校验可用性
798
+ */
799
+ async function resolveCliPath() {
800
+ const config = await getConfig();
801
+ if (!config.cliPath) return {
802
+ cliPath: null,
803
+ source: config.source
804
+ };
805
+ return {
806
+ cliPath: await fs.pathExists(config.cliPath) ? config.cliPath : null,
807
+ source: config.source
808
+ };
809
+ }
810
+ //#endregion
811
+ //#region src/cli/config-command.ts
812
+ /**
813
+ * @description 处理 config 子命令。
814
+ */
815
+ async function handleConfigCommand(argv) {
816
+ const action = argv[0];
817
+ if (!action) {
818
+ await promptForCliPath();
819
+ return;
820
+ }
821
+ if (action === "lang" || action === "set-lang") {
822
+ const nextLocale = argv[1];
823
+ if (nextLocale !== "zh" && nextLocale !== "en") throw new Error(i18nText("请使用 weapp config lang <zh|en> 切换语言", "Use weapp config lang <zh|en> to switch language"));
824
+ await createLocaleConfig(nextLocale);
825
+ logger_default.info(i18nText(`语言已切换为:${nextLocale === "zh" ? "中文" : "英文"}`, `Language switched to: ${nextLocale}`));
826
+ return;
827
+ }
828
+ if (action === "show") {
829
+ const config = await readCustomConfig();
830
+ logger_default.info(i18nText(`配置文件路径:${colors.green(defaultCustomConfigFilePath)}`, `Config file: ${colors.green(defaultCustomConfigFilePath)}`));
831
+ console.log(JSON.stringify(config, null, 2));
832
+ return;
833
+ }
834
+ if (action === "get") {
835
+ const key = argv[1];
836
+ if (key !== "cliPath" && key !== "locale") throw new Error(i18nText("仅支持读取配置项:cliPath | locale", "Supported keys: cliPath | locale"));
837
+ const value = (await readCustomConfig())[key];
838
+ if (typeof value === "string") {
839
+ console.log(value);
840
+ return;
841
+ }
842
+ console.log("");
843
+ return;
844
+ }
845
+ if (action === "set") {
846
+ const key = argv[1];
847
+ const value = argv.slice(2).join(" ").trim();
848
+ if (!value) throw new Error(i18nText("请提供配置值,例如:weapp config set locale en", "Please provide a value, e.g. weapp config set locale en"));
849
+ if (key === "cliPath") {
850
+ await createCustomConfig({ cliPath: value });
851
+ logger_default.info(i18nText("CLI 路径已更新。", "CLI path updated."));
852
+ return;
853
+ }
854
+ if (key === "locale") {
855
+ if (value !== "zh" && value !== "en") throw new Error(i18nText("locale 仅支持 zh 或 en", "locale only supports zh or en"));
856
+ await createLocaleConfig(value);
857
+ logger_default.info(i18nText(`语言已切换为:${value === "zh" ? "中文" : "英文"}`, `Language switched to: ${value}`));
858
+ return;
859
+ }
860
+ throw new Error(i18nText("仅支持设置配置项:cliPath | locale", "Supported keys for set: cliPath | locale"));
861
+ }
862
+ if (action === "unset") {
863
+ const key = argv[1];
864
+ if (key !== "cliPath" && key !== "locale") throw new Error(i18nText("仅支持清除配置项:cliPath | locale", "Supported keys for unset: cliPath | locale"));
865
+ await removeCustomConfigKey(key);
866
+ logger_default.info(i18nText(`已清除配置项:${key}`, `Config key cleared: ${key}`));
867
+ return;
868
+ }
869
+ if (action === "doctor") {
870
+ const rawConfig = await readCustomConfig();
871
+ const hasConfigFile = await fs.pathExists(defaultCustomConfigFilePath);
872
+ const resolvedCli = await resolveCliPath();
873
+ const hasCustomCli = typeof rawConfig.cliPath === "string" && rawConfig.cliPath.length > 0;
874
+ const hasValidCli = Boolean(resolvedCli.cliPath);
875
+ const locale = rawConfig.locale ?? "zh";
876
+ const report = {
877
+ configFile: defaultCustomConfigFilePath,
878
+ configFileExists: hasConfigFile,
879
+ cliPath: rawConfig.cliPath ?? null,
880
+ cliPathValid: hasValidCli,
881
+ locale,
882
+ source: resolvedCli.source
883
+ };
884
+ logger_default.info(i18nText("配置诊断结果:", "Configuration diagnostics:"));
885
+ console.log(JSON.stringify(report, null, 2));
886
+ if (!hasConfigFile) logger_default.warn(i18nText("未找到配置文件,可执行 weapp config 进行初始化。", "Config file not found. Run `weapp config` to initialize."));
887
+ if (hasCustomCli && !hasValidCli) logger_default.warn(i18nText("检测到配置的 cliPath 不可用,请执行 weapp config set cliPath <path> 修复。", "Configured cliPath is not available. Run `weapp config set cliPath <path>` to fix it."));
888
+ return;
889
+ }
890
+ if (action === "export") {
891
+ const outputPath = argv[1];
892
+ const config = await readCustomConfig();
893
+ if (outputPath) {
894
+ await fs.writeJSON(outputPath, config, {
895
+ spaces: 2,
896
+ encoding: "utf8"
897
+ });
898
+ logger_default.info(i18nText(`配置已导出到:${colors.green(outputPath)}`, `Config exported to: ${colors.green(outputPath)}`));
899
+ return;
900
+ }
901
+ console.log(JSON.stringify(config, null, 2));
902
+ return;
903
+ }
904
+ if (action === "import") {
905
+ const inputPath = argv[1];
906
+ 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"));
907
+ const imported = await fs.readJSON(inputPath);
908
+ if (!imported || typeof imported !== "object") throw new Error(i18nText("导入文件格式无效:应为 JSON 对象", "Invalid import file format: expected a JSON object"));
909
+ const candidate = imported;
910
+ await overwriteCustomConfig({
911
+ cliPath: typeof candidate.cliPath === "string" ? candidate.cliPath : void 0,
912
+ locale: candidate.locale === "zh" || candidate.locale === "en" ? candidate.locale : void 0
913
+ });
914
+ logger_default.info(i18nText(`配置已从 ${colors.green(inputPath)} 导入`, `Config imported from ${colors.green(inputPath)}`));
915
+ return;
916
+ }
917
+ throw new Error(i18nText("支持的 config 子命令:lang | set-lang | show | get | set | unset | doctor | import | export", "Supported config subcommands: lang | set-lang | show | get | set | unset | doctor | import | export"));
918
+ }
919
+ //#endregion
920
+ //#region src/utils/argv.ts
921
+ function ensurePathArgument(argv, optionIndex) {
922
+ const paramIdx = optionIndex + 1;
923
+ const param = argv[paramIdx];
924
+ if (param && !param.startsWith("-")) argv[paramIdx] = resolvePath(param);
925
+ else argv.splice(paramIdx, 0, process.cwd());
926
+ return argv;
927
+ }
928
+ /**
929
+ * @description 依次应用 argv 处理函数(不修改原始 argv)
930
+ */
931
+ function transformArgv(argv, transforms) {
932
+ return transforms.reduce((current, transform) => transform(current), [...argv]);
933
+ }
934
+ /**
935
+ * @description 创建参数别名转换器
936
+ */
937
+ function createAlias(entry) {
938
+ return (input) => {
939
+ const argv = [...input];
940
+ let optionIndex = argv.indexOf(entry.find);
941
+ if (optionIndex > -1) argv.splice(optionIndex, 1, entry.replacement);
942
+ else optionIndex = argv.indexOf(entry.replacement);
943
+ if (optionIndex === -1) return argv;
944
+ return ensurePathArgument(argv, optionIndex);
945
+ };
946
+ }
947
+ /**
948
+ * @description 创建路径参数兼容转换器(补全或规范化路径)
949
+ */
950
+ function createPathCompat(option) {
951
+ return (input) => {
952
+ const argv = [...input];
953
+ const optionIndex = argv.indexOf(option);
954
+ if (optionIndex === -1) return argv;
955
+ return ensurePathArgument(argv, optionIndex);
956
+ };
957
+ }
958
+ //#endregion
959
+ //#region src/utils/exec.ts
960
+ /**
961
+ * @description 执行 CLI 命令并透传输出
962
+ */
963
+ async function execute(cliPath, argv, options = {}) {
964
+ const { pipeStdout = true, pipeStderr = true } = options;
965
+ const { execa } = await import("execa");
966
+ const task = execa(cliPath, argv);
967
+ if (pipeStdout) task?.stdout?.pipe(process.stdout);
968
+ if (pipeStderr) task?.stderr?.pipe(process.stderr);
969
+ return await task;
970
+ }
971
+ //#endregion
972
+ //#region src/cli/minidev.ts
973
+ const MINIDEV_COMMAND = "minidev";
974
+ function isCommandNotFound(error) {
975
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
976
+ }
977
+ /**
978
+ * @description 运行支付宝小程序 CLI(minidev)
979
+ */
980
+ async function runMinidev(argv) {
981
+ try {
982
+ await execute(MINIDEV_COMMAND, [...argv]);
983
+ } catch (error) {
984
+ if (isCommandNotFound(error)) {
985
+ logger_default.error("未检测到支付宝小程序 CLI:minidev");
986
+ logger_default.warn("请先安装 minidev,可使用以下任一命令:");
987
+ logger_default.info(`- ${colors.green("pnpm add -g minidev")}`);
988
+ logger_default.info(`- ${colors.green("npm install -g minidev")}`);
989
+ logger_default.info(`- ${colors.green("yarn global add minidev")}`);
990
+ return;
991
+ }
992
+ throw error;
993
+ }
994
+ }
995
+ //#endregion
996
+ //#region src/cli/retry.ts
997
+ const LOGIN_REQUIRED_PATTERNS = [
998
+ /code\s*[:=]\s*10/i,
999
+ /需要重新登录/,
1000
+ /need\s+re-?login/i,
1001
+ /re-?login/i
1002
+ ];
1003
+ /**
1004
+ * @description 判断是否为微信开发者工具登录失效错误。
1005
+ */
1006
+ function isWechatIdeLoginRequiredError(error) {
1007
+ const text = extractExecutionErrorText(error);
1008
+ if (!text) return false;
1009
+ return LOGIN_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
1010
+ }
1011
+ /**
1012
+ * @description 提取执行错误文本,便于统一匹配与提示。
1013
+ */
1014
+ function extractExecutionErrorText(error) {
1015
+ if (!error || typeof error !== "object") return "";
1016
+ const parts = [];
1017
+ const candidate = error;
1018
+ for (const field of [
1019
+ candidate.message,
1020
+ candidate.shortMessage,
1021
+ candidate.stderr,
1022
+ candidate.stdout
1023
+ ]) if (typeof field === "string" && field.trim()) parts.push(field);
1024
+ return parts.join("\n");
1025
+ }
1026
+ /**
1027
+ * @description 将登录失效错误格式化为更易读的摘要。
1028
+ */
1029
+ function formatWechatIdeLoginRequiredError(error) {
1030
+ const text = extractExecutionErrorText(error);
1031
+ const code = text.match(/code\s*[:=]\s*(\d+)/i)?.[1];
1032
+ const message = extractLoginRequiredMessage(text);
1033
+ const lines = ["微信开发者工具返回登录错误:"];
1034
+ if (code) lines.push(`- code: ${code}`);
1035
+ if (message) lines.push(`- message: ${message}`);
1036
+ if (!code && !message) lines.push("- message: 需要重新登录");
1037
+ return lines.join("\n");
1038
+ }
1039
+ /**
1040
+ * @description 创建登录失效专用错误,并携带退出码语义。
1041
+ */
1042
+ function createWechatIdeLoginRequiredExitError(error, reason) {
1043
+ const summary = formatWechatIdeLoginRequiredError(error);
1044
+ const message = reason ? `${reason}\n${summary}` : summary;
1045
+ const loginError = new Error(message);
1046
+ loginError.name = "WechatIdeLoginRequiredError";
1047
+ loginError.code = 10;
1048
+ loginError.exitCode = 10;
1049
+ return loginError;
1050
+ }
1051
+ function extractLoginRequiredMessage(text) {
1052
+ if (!text) return "";
1053
+ if (/需要重新登录/.test(text)) return "需要重新登录";
1054
+ const englishMatch = text.match(/need\s+re-?login|re-?login/i);
1055
+ if (englishMatch?.[0]) return englishMatch[0].toLowerCase();
1056
+ const firstLine = text.split(/\r?\n/).map((line) => line.trim()).find((line) => Boolean(line) && !line.startsWith("at "));
1057
+ if (!firstLine) return "";
1058
+ return firstLine.replace(/^\[error\]\s*/i, "").replace(/^error\s*:\s*/i, "").slice(0, 120);
1059
+ }
1060
+ /**
1061
+ * @description 交互等待用户按键重试,按 r 重试,按 q 或 Ctrl+C 取消。
1062
+ */
1063
+ async function waitForRetryKeypress(options = {}) {
1064
+ const { timeoutMs = 3e4 } = options;
1065
+ if (!process.stdin.isTTY) return "cancel";
1066
+ emitKeypressEvents(process.stdin);
1067
+ const hasSetRawMode = typeof process.stdin.setRawMode === "function";
1068
+ if (hasSetRawMode) process.stdin.setRawMode(true);
1069
+ process.stdin.resume();
1070
+ return new Promise((resolve) => {
1071
+ let settled = false;
1072
+ const normalizedTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 3e4;
1073
+ const timeout = setTimeout(() => {
1074
+ done("timeout");
1075
+ }, normalizedTimeoutMs);
1076
+ const cleanup = () => {
1077
+ clearTimeout(timeout);
1078
+ process.stdin.off("keypress", onKeypress);
1079
+ if (hasSetRawMode) process.stdin.setRawMode(false);
1080
+ process.stdin.pause();
1081
+ };
1082
+ const done = (value) => {
1083
+ if (settled) return;
1084
+ settled = true;
1085
+ cleanup();
1086
+ resolve(value);
1087
+ };
1088
+ const onKeypress = (_str, key) => {
1089
+ if (!key) return;
1090
+ if (key.ctrl && key.name === "c") {
1091
+ done("cancel");
1092
+ return;
1093
+ }
1094
+ if (key.name === "r") {
1095
+ done("retry");
1096
+ return;
1097
+ }
1098
+ if (key.name === "q" || key.name === "escape") done("cancel");
1099
+ };
1100
+ process.stdin.on("keypress", onKeypress);
1101
+ });
1102
+ }
1103
+ /**
1104
+ * @description 生成重试按键提示,并高亮关键热键。
1105
+ */
1106
+ function formatRetryHotkeyPrompt(timeoutMs = 3e4) {
1107
+ const highlight = (key) => highlightHotkey(key);
1108
+ const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1e3));
1109
+ return i18nText(`按 ${highlight("r")} 重试,按 ${highlight("q")} / ${highlight("Esc")} / ${highlight("Ctrl+C")} 退出(${timeoutSeconds}s 内无输入将自动失败)。`, `Press ${highlight("r")} to retry, ${highlight("q")} / ${highlight("Esc")} / ${highlight("Ctrl+C")} to cancel (auto fail in ${timeoutSeconds}s).`);
1110
+ }
1111
+ function highlightHotkey(key) {
1112
+ return colors.bold(colors.green(key));
1113
+ }
1114
+ //#endregion
1115
+ //#region src/cli/run-login.ts
1116
+ /**
1117
+ * @description 运行微信开发者工具 CLI,并在登录失效时允许按键重试。
1118
+ */
1119
+ async function runWechatCliWithRetry(cliPath, argv) {
1120
+ const loginRetryOptions = resolveLoginRetryOptions(argv);
1121
+ const runtimeArgv = stripLoginRetryControlFlags(argv);
1122
+ let retryCount = 0;
1123
+ while (true) try {
1124
+ const result = await execute(cliPath, runtimeArgv, {
1125
+ pipeStdout: false,
1126
+ pipeStderr: false
1127
+ });
1128
+ if (!isWechatIdeLoginRequiredError(result)) {
1129
+ flushExecutionOutput(result);
1130
+ return;
1131
+ }
1132
+ if (await promptLoginRetry(result, loginRetryOptions, retryCount) === "retry") {
1133
+ retryCount += 1;
1134
+ logger_default.info(i18nText("正在重试连接微信开发者工具...", "Retrying to connect Wechat DevTools..."));
1135
+ continue;
1136
+ }
1137
+ throw createWechatIdeLoginRequiredExitError(result);
1138
+ } catch (error) {
1139
+ if (!isWechatIdeLoginRequiredError(error)) throw error;
1140
+ if (await promptLoginRetry(error, loginRetryOptions, retryCount) === "retry") {
1141
+ retryCount += 1;
1142
+ logger_default.info(i18nText("正在重试连接微信开发者工具...", "Retrying to connect Wechat DevTools..."));
1143
+ continue;
1144
+ }
1145
+ throw createWechatIdeLoginRequiredExitError(error);
1146
+ }
1147
+ }
1148
+ function resolveLoginRetryOptions(argv) {
1149
+ const nonInteractiveFlag = argv.includes("--non-interactive");
1150
+ const ciMode = process.env.CI === "true";
1151
+ const nonTtyStdin = !isStdinInteractive();
1152
+ const nonInteractive = nonInteractiveFlag || ciMode || nonTtyStdin;
1153
+ const retryModeRaw = readOptionValue(argv, "--login-retry")?.toLowerCase();
1154
+ return {
1155
+ nonInteractive,
1156
+ retryMode: normalizeLoginRetryMode(retryModeRaw, nonInteractive),
1157
+ retryTimeoutMs: normalizeLoginRetryTimeout(readOptionValue(argv, "--login-retry-timeout"))
1158
+ };
1159
+ }
1160
+ function normalizeLoginRetryMode(value, nonInteractive) {
1161
+ if (value && value !== "never" && value !== "once" && value !== "always") throw new Error(i18nText(`不支持的 --login-retry 值: ${value}(仅支持 never/once/always)`, `Invalid --login-retry value: ${value} (supported: never/once/always)`));
1162
+ if (value === "never" || value === "once" || value === "always") return value;
1163
+ return nonInteractive ? "never" : "always";
1164
+ }
1165
+ function normalizeLoginRetryTimeout(value) {
1166
+ if (!value) return 3e4;
1167
+ const parsed = Number.parseInt(value, 10);
1168
+ if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(i18nText(`无效的 --login-retry-timeout 值: ${value}(必须为正整数)`, `Invalid --login-retry-timeout value: ${value} (must be a positive integer)`));
1169
+ return parsed;
1170
+ }
1171
+ function isStdinInteractive() {
1172
+ return Boolean(process.stdin && process.stdin.isTTY);
1173
+ }
1174
+ function stripLoginRetryControlFlags(argv) {
1175
+ let next = removeOption(argv, "--login-retry");
1176
+ next = removeOption(next, "--login-retry-timeout");
1177
+ next = removeOption(next, "--non-interactive");
1178
+ return next;
1179
+ }
1180
+ async function promptLoginRetry(errorLike, options, retryCount) {
1181
+ const { nonInteractive, retryMode, retryTimeoutMs } = options;
1182
+ logger_default.error(i18nText("检测到微信开发者工具登录状态失效,请先登录后重试。", "Wechat DevTools login has expired. Please login and retry."));
1183
+ logger_default.warn(i18nText("请先打开微信开发者工具完成登录。", "Please open Wechat DevTools and complete login first."));
1184
+ logger_default.warn(formatWechatIdeLoginRequiredError(errorLike));
1185
+ if (nonInteractive) {
1186
+ logger_default.error(i18nText("当前为非交互模式,检测到登录失效后直接失败。", "Non-interactive mode enabled, failing immediately on login expiration."));
1187
+ return "cancel";
1188
+ }
1189
+ if (!(retryMode === "always" || retryMode === "once" && retryCount < 1)) {
1190
+ logger_default.info(i18nText("当前重试策略不允许继续重试。", "Current retry policy does not allow further retries."));
1191
+ return "cancel";
1192
+ }
1193
+ logger_default.info(formatRetryHotkeyPrompt(retryTimeoutMs));
1194
+ const action = await waitForRetryKeypress({ timeoutMs: retryTimeoutMs });
1195
+ if (action === "timeout") {
1196
+ logger_default.error(i18nText(`等待登录重试输入超时(${retryTimeoutMs}ms),已自动取消。`, `Retry prompt timed out (${retryTimeoutMs}ms), canceled automatically.`));
1197
+ return "cancel";
1198
+ }
1199
+ if (action !== "retry") {
1200
+ logger_default.info(i18nText("已取消重试。完成登录后请重新执行当前命令。", "Retry canceled. Please run the command again after login."));
1201
+ return "cancel";
1202
+ }
1203
+ return "retry";
1204
+ }
1205
+ function flushExecutionOutput(result) {
1206
+ if (!result || typeof result !== "object") return;
1207
+ const candidate = result;
1208
+ if (typeof candidate.stdout === "string" && candidate.stdout) process.stdout.write(candidate.stdout);
1209
+ if (typeof candidate.stderr === "string" && candidate.stderr) process.stderr.write(candidate.stderr);
1210
+ }
1211
+ //#endregion
1212
+ //#region src/cli/wechat-command-schema.ts
1213
+ /**
1214
+ * @description 在调用官方微信 CLI 前做轻量参数校验。
1215
+ */
1216
+ function validateWechatCliCommandArgs(argv) {
1217
+ const command = argv[0];
1218
+ if (!command) return;
1219
+ validatePortOption(argv);
1220
+ validateExtAppidDependency(argv);
1221
+ if (command === "upload") {
1222
+ const version = readOptionValue(argv, "--version") || readOptionValue(argv, "-v");
1223
+ const desc = readOptionValue(argv, "--desc") || readOptionValue(argv, "-d");
1224
+ if (!isNonEmptyText(version) || !isNonEmptyText(desc)) throw new Error(i18nText("upload 命令缺少必填参数:--version/-v 和 --desc/-d", "upload command requires both --version/-v and --desc/-d"));
1225
+ }
1226
+ if (command === "preview") {
1227
+ validateProjectLocator(command, argv);
1228
+ const qrFormat = readOptionValue(argv, "--qr-format") || readOptionValue(argv, "-f");
1229
+ if (!qrFormat) return;
1230
+ if (![
1231
+ "terminal",
1232
+ "image",
1233
+ "base64"
1234
+ ].includes(qrFormat.toLowerCase())) throw new Error(i18nText(`preview 命令的二维码格式无效: ${qrFormat}(仅支持 terminal/image/base64)`, `Invalid preview qr format: ${qrFormat} (supported: terminal/image/base64)`));
1235
+ }
1236
+ if (command === "upload" || command === "auto" || command === "auto-preview") validateProjectLocator(command, argv);
1237
+ }
1238
+ function validatePortOption(argv) {
1239
+ const port = readOptionValue(argv, "--port");
1240
+ if (!port) return;
1241
+ const parsed = Number.parseInt(port, 10);
1242
+ if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(i18nText(`无效的 --port 值: ${port}(必须为正整数)`, `Invalid --port value: ${port} (must be a positive integer)`));
1243
+ }
1244
+ function validateProjectLocator(command, argv) {
1245
+ const projectPath = readOptionValue(argv, "--project");
1246
+ const appid = readOptionValue(argv, "--appid");
1247
+ if (isNonEmptyText(projectPath) || isNonEmptyText(appid)) return;
1248
+ throw new Error(i18nText(`${command} 命令需要提供 --project 或 --appid`, `${command} command requires --project or --appid`));
1249
+ }
1250
+ function validateExtAppidDependency(argv) {
1251
+ if (!isNonEmptyText(readOptionValue(argv, "--ext-appid"))) return;
1252
+ if (isNonEmptyText(readOptionValue(argv, "--project"))) return;
1253
+ if (isNonEmptyText(readOptionValue(argv, "--appid"))) return;
1254
+ throw new Error(i18nText("--ext-appid 需要和 --appid 一起使用(当未提供 --project 时)", "--ext-appid requires --appid when --project is not provided"));
1255
+ }
1256
+ function isNonEmptyText(value) {
1257
+ return typeof value === "string" && value.trim().length > 0;
1258
+ }
1259
+ //#endregion
1260
+ //#region src/cli/run.ts
1261
+ const MINIDEV_NAMESPACE = new Set([
1262
+ "alipay",
1263
+ "ali",
1264
+ "minidev"
1265
+ ]);
1266
+ const ALIPAY_PLATFORM_ALIASES = new Set([
1267
+ "alipay",
1268
+ "ali",
1269
+ "minidev"
1270
+ ]);
1271
+ const ARG_TRANSFORMS = [
1272
+ createAlias({
1273
+ find: "-p",
1274
+ replacement: "--project"
1275
+ }),
1276
+ createPathCompat("--result-output"),
1277
+ createPathCompat("-r"),
1278
+ createPathCompat("--qr-output"),
1279
+ createPathCompat("-o"),
1280
+ createPathCompat("--info-output"),
1281
+ createPathCompat("-i")
1282
+ ];
1283
+ /**
1284
+ * @description CLI 入口解析与分发。
1285
+ */
1286
+ async function parse(argv) {
1287
+ validateLocaleOption(argv);
1288
+ configureLocaleFromArgv(argv, await getConfiguredLocale());
1289
+ const command = argv[0];
1290
+ if (command && MINIDEV_NAMESPACE.has(command)) {
1291
+ await runMinidev(argv.slice(1));
1292
+ return;
1293
+ }
1294
+ if (command && isAutomatorCommand(command)) {
1295
+ await runAutomatorCommand(command, argv.slice(1));
1296
+ return;
1297
+ }
1298
+ if (command === "help") {
1299
+ const targetCommand = argv[1];
1300
+ if (targetCommand === "screenshot") {
1301
+ printScreenshotHelp();
1302
+ return;
1303
+ }
1304
+ if (isAutomatorCommand(targetCommand)) {
1305
+ await runAutomatorCommand(targetCommand, ["--help"]);
1306
+ return;
1307
+ }
1308
+ }
1309
+ const formattedArgv = transformArgv(argv, ARG_TRANSFORMS);
1310
+ if (shouldDelegateOpenToMinidev(formattedArgv)) {
1311
+ await runMinidev(createMinidevOpenArgv(formattedArgv));
1312
+ return;
1313
+ }
1314
+ if (command === "config") {
1315
+ await handleConfigCommand(argv.slice(1));
1316
+ return;
1317
+ }
1318
+ if (!isOperatingSystemSupported(operatingSystemName)) {
1319
+ logger_default.warn(i18nText(`微信web开发者工具不支持当前平台:${operatingSystemName} !`, `Wechat Web DevTools CLI is not supported on current platform: ${operatingSystemName}!`));
1320
+ return;
1321
+ }
1322
+ validateWechatCliCommandArgs(formattedArgv);
1323
+ const { cliPath, source } = await resolveCliPath();
1324
+ if (!cliPath) {
1325
+ const message = source === "custom" ? i18nText("在当前自定义路径中未找到微信web开发者命令行工具,请重新指定路径。", "Cannot find Wechat Web DevTools CLI in custom path, please reconfigure it.") : i18nText(`未检测到微信web开发者命令行工具,请执行 ${colors.bold(colors.green("weapp-ide-cli config"))} 指定路径。`, `Wechat Web DevTools CLI not found, please run ${colors.bold(colors.green("weapp-ide-cli config"))} to configure it.`);
1326
+ logger_default.warn(message);
1327
+ await promptForCliPath();
1328
+ return;
1329
+ }
1330
+ await runWechatCliWithRetry(cliPath, formattedArgv);
1331
+ }
1332
+ /**
1333
+ * @description 判断 open 指令是否应分发到 minidev。
1334
+ */
1335
+ function shouldDelegateOpenToMinidev(argv) {
1336
+ if (argv[0] !== "open") return false;
1337
+ const platform = readOptionValue(argv, "--platform");
1338
+ if (!platform) return false;
1339
+ return ALIPAY_PLATFORM_ALIASES.has(platform.toLowerCase());
1340
+ }
1341
+ /**
1342
+ * @description 将 open 命令参数转换为 minidev ide 参数。
1343
+ */
1344
+ function createMinidevOpenArgv(argv) {
1345
+ const nextArgv = [...argv];
1346
+ nextArgv[0] = "ide";
1347
+ return removeOption(nextArgv, "--platform");
1348
+ }
1349
+ //#endregion
1350
+ export { defaultCustomConfigFilePath as A, runAutomatorCommand as B, promptForCliPath as C, readCustomConfig as D, overwriteCustomConfig as E, WECHAT_CLI_COMMAND_NAMES as F, removeOption as G, printScreenshotHelp as H, isWeappIdeTopLevelCommand as I, AUTOMATOR_COMMAND_NAMES as L, CONFIG_COMMAND_NAME as M, MINIDEV_NAMESPACE_COMMAND_NAMES as N, removeCustomConfigKey as O, WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES as P, getAutomatorCommandHelp as R, operatingSystemName as S, createLocaleConfig as T, parseAutomatorArgs as U, parseScreenshotArgs as V, readOptionValue as W, getConfig as _, extractExecutionErrorText as a, getDefaultCliPath as b, isWechatIdeLoginRequiredError as c, execute as d, createAlias as f, resolveCliPath as g, handleConfigCommand as h, createWechatIdeLoginRequiredExitError as i, resolvePath as j, defaultCustomConfigDirPath as k, waitForRetryKeypress as l, transformArgv as m, validateWechatCliCommandArgs as n, formatRetryHotkeyPrompt as o, createPathCompat as p, runWechatCliWithRetry as r, formatWechatIdeLoginRequiredError as s, parse as t, runMinidev as u, getConfiguredLocale as v, createCustomConfig as w, isOperatingSystemSupported as x, SupportedPlatformsMap as y, isAutomatorCommand as z };