weapp-ide-cli 6.2.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -3
- package/dist/automator-BqhUtMJp.js +1 -0
- package/dist/{automator-CRpB6_pA.js → automator-Esp3BjoT.js} +391 -152
- package/dist/automator-session-Ba27VW0_.js +184 -0
- package/dist/{sessionStore-zdzy2UFM.js → automatorProject-DPU-pEF0.js} +497 -240
- package/dist/{cli-ChMQqi5w.js → cli-Dd1RJ5PH.js} +396 -115
- package/dist/cli.js +3 -4
- package/dist/{commands-C1PF1B4P.js → commands-5entj0Rh.js} +5 -6
- package/dist/commands-DR-8MVFm.js +1 -0
- package/dist/{devtoolsProjectOwnership-yDD6h0dO.js → devtoolsProjectOwnership-C4jj8msU.js} +478 -155
- package/dist/index.d.ts +111 -137
- package/dist/index.js +8 -8
- package/dist/{managed-Sq8y8FzP.js → managed-hSCZXTon.js} +109 -59
- package/dist/{run-mcp-Dl_1TH1x.js → run-mcp-CbmskSgr.js} +14 -7
- package/dist/run-mcp-D1w7-oPp.js +1 -0
- package/package.json +12 -12
- package/dist/automator-BkZZsGtn.js +0 -1
- package/dist/automator-session-gWEsSs_A.js +0 -558
- package/dist/commands-CZVPfkE9.js +0 -1
- package/dist/run-mcp-CPv8cesh.js +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as readPersistedAutomatorSession, i as persistAutomatorSession, j as colors, m as assertWechatDevtoolsPort, p as assertWechatDevtoolsHost, r as resolveAutomatorProjectPath, u as resolveWechatDevtoolsTarget, z as readCustomConfig } from "./automatorProject-DPU-pEF0.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { withMachineE2ELease } from "@weapp-vite/devtools-runtime";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
4
|
import fs from "node:fs/promises";
|
|
6
5
|
import process from "node:process";
|
|
7
|
-
import
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
8
7
|
import { OperationLifecycle } from "@weapp-vite/miniprogram-automator/operation";
|
|
9
|
-
|
|
8
|
+
import { emitKeypressEvents } from "node:readline";
|
|
9
|
+
// src/cli/automator/port.ts
|
|
10
10
|
/** 显式端口不能被截断或作为默认值忽略,避免连接其他项目。 */
|
|
11
11
|
function assertAutomatorPort(port) {
|
|
12
12
|
if (port !== void 0 && (typeof port !== "number" || !Number.isInteger(port) || port < 1 || port > 65535)) throw Object.assign(/* @__PURE__ */ new RangeError("Automator port must be an integer between 1 and 65535."), { code: "DEVTOOLS_INVALID_PORT" });
|
|
@@ -17,8 +17,7 @@ function parseAutomatorPort(raw) {
|
|
|
17
17
|
assertAutomatorPort(port);
|
|
18
18
|
return port;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
//#region src/cli/automator/context.ts
|
|
20
|
+
// src/cli/automator/context.ts
|
|
22
21
|
/** 在连接与共享缓存入口固定安装选择;headless 不读取或修改 IDE 状态。 */
|
|
23
22
|
async function resolveAutomatorSessionOptions(options) {
|
|
24
23
|
assertAutomatorPort(options.port);
|
|
@@ -39,8 +38,376 @@ async function resolveAutomatorSessionOptions(options) {
|
|
|
39
38
|
installationId: target.installationId
|
|
40
39
|
};
|
|
41
40
|
}
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// src/i18n.ts
|
|
42
|
+
let currentLocale = "zh";
|
|
43
|
+
/**
|
|
44
|
+
* @description 设置当前语言。
|
|
45
|
+
*/
|
|
46
|
+
function setLocale(locale) {
|
|
47
|
+
currentLocale = normalizeLocale(locale);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @description 从 argv 和环境变量中解析语言并设置。
|
|
51
|
+
*/
|
|
52
|
+
function configureLocaleFromArgv(argv, fallbackLocale) {
|
|
53
|
+
const localeFromArgv = readLangOption(argv);
|
|
54
|
+
if (localeFromArgv) {
|
|
55
|
+
setLocale(localeFromArgv);
|
|
56
|
+
return currentLocale;
|
|
57
|
+
}
|
|
58
|
+
if (fallbackLocale) {
|
|
59
|
+
setLocale(fallbackLocale);
|
|
60
|
+
return currentLocale;
|
|
61
|
+
}
|
|
62
|
+
const localeFromEnv = process.env.WEAPP_IDE_CLI_LANG || process.env.LANG;
|
|
63
|
+
if (localeFromEnv) {
|
|
64
|
+
setLocale(localeFromEnv);
|
|
65
|
+
return currentLocale;
|
|
66
|
+
}
|
|
67
|
+
setLocale("zh");
|
|
68
|
+
return currentLocale;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @description 根据当前语言选择文案。
|
|
72
|
+
*/
|
|
73
|
+
function i18nText(zh, en) {
|
|
74
|
+
return currentLocale === "en" ? en : zh;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* @description 校验 --lang 参数是否合法。
|
|
78
|
+
*/
|
|
79
|
+
function validateLocaleOption(argv) {
|
|
80
|
+
const localeFromArgv = readLangOption(argv);
|
|
81
|
+
if (!localeFromArgv) return;
|
|
82
|
+
if (!isLocaleTokenSupported(localeFromArgv)) throw new Error(`不支持的语言: ${localeFromArgv},仅支持 zh 或 en`);
|
|
83
|
+
}
|
|
84
|
+
function normalizeLocale(raw) {
|
|
85
|
+
if (!raw) return "zh";
|
|
86
|
+
const normalized = raw.trim().toLowerCase();
|
|
87
|
+
if (normalized === "en" || normalized.startsWith("en_") || normalized.startsWith("en-")) return "en";
|
|
88
|
+
return "zh";
|
|
89
|
+
}
|
|
90
|
+
function isLocaleTokenSupported(raw) {
|
|
91
|
+
const normalized = raw.trim().toLowerCase();
|
|
92
|
+
return normalized === "zh" || normalized === "en" || normalized.startsWith("zh_") || normalized.startsWith("zh-") || normalized.startsWith("en_") || normalized.startsWith("en-");
|
|
93
|
+
}
|
|
94
|
+
function readLangOption(argv) {
|
|
95
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
96
|
+
const token = argv[index];
|
|
97
|
+
if (!token) continue;
|
|
98
|
+
if (token === "--lang") {
|
|
99
|
+
const value = argv[index + 1];
|
|
100
|
+
return typeof value === "string" ? value : void 0;
|
|
101
|
+
}
|
|
102
|
+
if (token.startsWith("--lang=")) return token.slice(7);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// src/cli/inputCoordinator.ts
|
|
106
|
+
const sharedSessions = /* @__PURE__ */ new Set();
|
|
107
|
+
const exclusiveKeypressStack = [];
|
|
108
|
+
const EXCLUSIVE_KEYPRESS_CARRYOVER_GUARD_MS = 500;
|
|
109
|
+
let initialized = false;
|
|
110
|
+
let rawModeEnabled = false;
|
|
111
|
+
let dataListenerAttached = false;
|
|
112
|
+
let keypressListenerAttached = false;
|
|
113
|
+
let lastExclusiveResolvedKeypress;
|
|
114
|
+
function hasInteractiveStdin() {
|
|
115
|
+
return Boolean(process.stdin?.isTTY);
|
|
116
|
+
}
|
|
117
|
+
function canSetRawMode() {
|
|
118
|
+
return typeof process.stdin?.setRawMode === "function";
|
|
119
|
+
}
|
|
120
|
+
function updateTerminalState() {
|
|
121
|
+
const shouldKeepInputActive = Array.from(sharedSessions).some((session) => !session.closed && !session.suspended) || exclusiveKeypressStack.length > 0;
|
|
122
|
+
if (canSetRawMode() && rawModeEnabled !== shouldKeepInputActive) {
|
|
123
|
+
process.stdin.setRawMode(shouldKeepInputActive);
|
|
124
|
+
rawModeEnabled = shouldKeepInputActive;
|
|
125
|
+
}
|
|
126
|
+
if (shouldKeepInputActive) {
|
|
127
|
+
process.stdin.resume();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
process.stdin.pause();
|
|
131
|
+
}
|
|
132
|
+
function handleData(chunk) {
|
|
133
|
+
if (exclusiveKeypressStack.length > 0) return;
|
|
134
|
+
for (const session of sharedSessions) {
|
|
135
|
+
if (session.closed || session.suspended || !session.options.onData) continue;
|
|
136
|
+
session.options.onData(chunk);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function handleKeypress(str, key) {
|
|
140
|
+
const activeExclusive = exclusiveKeypressStack[exclusiveKeypressStack.length - 1];
|
|
141
|
+
if (activeExclusive) {
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const signature = `${key?.ctrl ? "ctrl+" : ""}${key?.name ?? str}`;
|
|
144
|
+
if (lastExclusiveResolvedKeypress && lastExclusiveResolvedKeypress.signature === signature && now - lastExclusiveResolvedKeypress.timestamp <= EXCLUSIVE_KEYPRESS_CARRYOVER_GUARD_MS) {
|
|
145
|
+
lastExclusiveResolvedKeypress.timestamp = now;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (now < activeExclusive.ignoreUntil) return;
|
|
149
|
+
const nextValue = activeExclusive.onKeypress(str, key);
|
|
150
|
+
if (nextValue !== void 0) {
|
|
151
|
+
clearTimeout(activeExclusive.timeout);
|
|
152
|
+
exclusiveKeypressStack.pop();
|
|
153
|
+
lastExclusiveResolvedKeypress = {
|
|
154
|
+
signature,
|
|
155
|
+
timestamp: now
|
|
156
|
+
};
|
|
157
|
+
updateTerminalState();
|
|
158
|
+
activeExclusive.resolve(nextValue);
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
for (const session of sharedSessions) {
|
|
163
|
+
if (session.closed || session.suspended || !session.options.onKeypress) continue;
|
|
164
|
+
session.options.onKeypress(str, key);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function ensureCoordinatorInitialized() {
|
|
168
|
+
if (initialized || !hasInteractiveStdin()) return;
|
|
169
|
+
emitKeypressEvents(process.stdin);
|
|
170
|
+
if (!dataListenerAttached) {
|
|
171
|
+
process.stdin.on("data", handleData);
|
|
172
|
+
dataListenerAttached = true;
|
|
173
|
+
}
|
|
174
|
+
if (!keypressListenerAttached) {
|
|
175
|
+
process.stdin.on("keypress", handleKeypress);
|
|
176
|
+
keypressListenerAttached = true;
|
|
177
|
+
}
|
|
178
|
+
initialized = true;
|
|
179
|
+
}
|
|
180
|
+
function createSharedInputSession(options) {
|
|
181
|
+
if (!hasInteractiveStdin()) return;
|
|
182
|
+
ensureCoordinatorInitialized();
|
|
183
|
+
const session = {
|
|
184
|
+
closed: false,
|
|
185
|
+
options,
|
|
186
|
+
suspended: false
|
|
187
|
+
};
|
|
188
|
+
sharedSessions.add(session);
|
|
189
|
+
updateTerminalState();
|
|
190
|
+
return {
|
|
191
|
+
close() {
|
|
192
|
+
if (session.closed) return;
|
|
193
|
+
session.closed = true;
|
|
194
|
+
sharedSessions.delete(session);
|
|
195
|
+
updateTerminalState();
|
|
196
|
+
},
|
|
197
|
+
resume() {
|
|
198
|
+
if (session.closed) return;
|
|
199
|
+
session.suspended = false;
|
|
200
|
+
updateTerminalState();
|
|
201
|
+
},
|
|
202
|
+
suspend() {
|
|
203
|
+
if (session.closed) return;
|
|
204
|
+
session.suspended = true;
|
|
205
|
+
updateTerminalState();
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function waitForExclusiveKeypress(options) {
|
|
210
|
+
options.signal?.throwIfAborted();
|
|
211
|
+
if (!hasInteractiveStdin()) return "timeout";
|
|
212
|
+
ensureCoordinatorInitialized();
|
|
213
|
+
return await new Promise((resolve, reject) => {
|
|
214
|
+
const normalizedTimeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs && options.timeoutMs > 0 ? options.timeoutMs : 3e4;
|
|
215
|
+
const normalizedIgnoreInitialMs = Number.isFinite(options.ignoreInitialMs) && options.ignoreInitialMs && options.ignoreInitialMs > 0 ? options.ignoreInitialMs : 0;
|
|
216
|
+
let onAbort;
|
|
217
|
+
const record = {
|
|
218
|
+
ignoreUntil: Date.now() + normalizedIgnoreInitialMs,
|
|
219
|
+
onKeypress: options.onKeypress,
|
|
220
|
+
resolve: (value) => {
|
|
221
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
222
|
+
resolve(value);
|
|
223
|
+
},
|
|
224
|
+
timeout: setTimeout(() => {
|
|
225
|
+
const index = exclusiveKeypressStack.lastIndexOf(record);
|
|
226
|
+
if (index >= 0) exclusiveKeypressStack.splice(index, 1);
|
|
227
|
+
updateTerminalState();
|
|
228
|
+
record.resolve("timeout");
|
|
229
|
+
}, normalizedTimeoutMs)
|
|
230
|
+
};
|
|
231
|
+
onAbort = () => {
|
|
232
|
+
clearTimeout(record.timeout);
|
|
233
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
234
|
+
const index = exclusiveKeypressStack.indexOf(record);
|
|
235
|
+
if (index >= 0) exclusiveKeypressStack.splice(index, 1);
|
|
236
|
+
updateTerminalState();
|
|
237
|
+
reject(options.signal?.reason);
|
|
238
|
+
};
|
|
239
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
240
|
+
exclusiveKeypressStack.push(record);
|
|
241
|
+
updateTerminalState();
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async function runWithSuspendedSharedInput(runner) {
|
|
245
|
+
if (!hasInteractiveStdin()) return runner();
|
|
246
|
+
const previousStates = /* @__PURE__ */ new Map();
|
|
247
|
+
for (const session of sharedSessions) {
|
|
248
|
+
if (session.closed) continue;
|
|
249
|
+
previousStates.set(session, session.suspended);
|
|
250
|
+
session.suspended = true;
|
|
251
|
+
}
|
|
252
|
+
if (canSetRawMode() && rawModeEnabled) {
|
|
253
|
+
process.stdin.setRawMode(false);
|
|
254
|
+
rawModeEnabled = false;
|
|
255
|
+
}
|
|
256
|
+
process.stdin.resume();
|
|
257
|
+
try {
|
|
258
|
+
return await runner();
|
|
259
|
+
} finally {
|
|
260
|
+
for (const [session, suspended] of previousStates) if (!session.closed) session.suspended = suspended;
|
|
261
|
+
updateTerminalState();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// src/cli/retry.ts
|
|
265
|
+
const RETRY_PROMPT_INITIAL_IGNORE_MS = 300;
|
|
266
|
+
const RETRY_CONFIRM_KEYS = ["y"];
|
|
267
|
+
const RETRY_CANCEL_KEYS = [
|
|
268
|
+
"q",
|
|
269
|
+
"Esc",
|
|
270
|
+
"Ctrl+C"
|
|
271
|
+
];
|
|
272
|
+
const LOGIN_REQUIRED_PATTERNS = [
|
|
273
|
+
/code(?:\s*[:=]\s*|\s+)13\b/i,
|
|
274
|
+
/需要重新登录/,
|
|
275
|
+
/need\s+re-?login/i,
|
|
276
|
+
/re-?login/i,
|
|
277
|
+
/\blogin(?:\s+again)?\s+(?:is\s+)?required\b/i
|
|
278
|
+
];
|
|
279
|
+
/**
|
|
280
|
+
* @description 提取执行错误文本,便于统一匹配与提示。
|
|
281
|
+
*/
|
|
282
|
+
function extractExecutionErrorText(error) {
|
|
283
|
+
if (!error || typeof error !== "object") return "";
|
|
284
|
+
const parts = [];
|
|
285
|
+
const candidate = error;
|
|
286
|
+
for (const field of [
|
|
287
|
+
candidate.message,
|
|
288
|
+
candidate.shortMessage,
|
|
289
|
+
candidate.stderr,
|
|
290
|
+
candidate.stdout
|
|
291
|
+
]) if (typeof field === "string" && field.trim()) parts.push(field);
|
|
292
|
+
return parts.join("\n");
|
|
293
|
+
}
|
|
294
|
+
function extractLoginRequiredMessage$1(text) {
|
|
295
|
+
if (!text) return "";
|
|
296
|
+
if (/需要重新登录/.test(text)) return "需要重新登录";
|
|
297
|
+
const englishMatch = text.match(/need\s+re-?login|re-?login/i);
|
|
298
|
+
if (englishMatch?.[0]) return englishMatch[0].toLowerCase();
|
|
299
|
+
const firstLine = text.split(/\r?\n/).map((line) => line.trim()).find((line) => Boolean(line) && !line.startsWith("at "));
|
|
300
|
+
if (!firstLine) return "";
|
|
301
|
+
return firstLine.replace(/^\[error\]\s*/i, "").replace(/^error\s*:\s*/i, "").slice(0, 120);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* @description 判断是否为微信开发者工具登录失效错误。
|
|
305
|
+
*/
|
|
306
|
+
function isWechatIdeLoginRequiredError(error) {
|
|
307
|
+
const text = extractExecutionErrorText(error);
|
|
308
|
+
if (!text) return false;
|
|
309
|
+
return LOGIN_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* @description 将登录失效错误格式化为更易读的摘要。
|
|
313
|
+
*/
|
|
314
|
+
function formatWechatIdeLoginRequiredError(error) {
|
|
315
|
+
const text = extractExecutionErrorText(error);
|
|
316
|
+
const code = text.match(/code\s*[:=]\s*(\d+)/i)?.[1];
|
|
317
|
+
const message = extractLoginRequiredMessage$1(text);
|
|
318
|
+
const lines = ["微信开发者工具返回登录错误:"];
|
|
319
|
+
if (code) lines.push(`- code: ${code}`);
|
|
320
|
+
if (message) lines.push(`- message: ${message}`);
|
|
321
|
+
if (!code && !message) lines.push("- message: 需要重新登录");
|
|
322
|
+
return lines.join("\n");
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* @description 创建登录失效专用错误,并携带退出码语义。
|
|
326
|
+
*/
|
|
327
|
+
function createWechatIdeLoginRequiredExitError(error, reason) {
|
|
328
|
+
const summary = formatWechatIdeLoginRequiredError(error);
|
|
329
|
+
const message = reason ? `${reason}\n${summary}` : summary;
|
|
330
|
+
const loginError = new Error(message, { cause: error });
|
|
331
|
+
loginError.name = "WechatIdeLoginRequiredError";
|
|
332
|
+
loginError.code = 10;
|
|
333
|
+
loginError.exitCode = 10;
|
|
334
|
+
return loginError;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* @description 判断错误是否为登录失效专用退出错误。
|
|
338
|
+
*/
|
|
339
|
+
function isWechatIdeLoginRequiredExitError(error) {
|
|
340
|
+
if (!error || typeof error !== "object") return false;
|
|
341
|
+
const candidate = error;
|
|
342
|
+
return candidate.name === "WechatIdeLoginRequiredError" || (candidate.code === 10 || candidate.exitCode === 10) && isWechatIdeLoginRequiredError(error);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* @description 交互等待用户按 y 重试,按 q 或 Ctrl+C 取消。
|
|
346
|
+
*/
|
|
347
|
+
async function waitForRetryKeypress(options = {}) {
|
|
348
|
+
const { timeoutMs = 3e4 } = options;
|
|
349
|
+
if (!process.stdin.isTTY) return "cancel";
|
|
350
|
+
return await waitForExclusiveKeypress({
|
|
351
|
+
signal: options.signal,
|
|
352
|
+
ignoreInitialMs: 300,
|
|
353
|
+
onKeypress: (_str, key) => {
|
|
354
|
+
if (!key) return;
|
|
355
|
+
if (key.ctrl && key.name === "c") return "cancel";
|
|
356
|
+
if (key.name === "y") return "retry";
|
|
357
|
+
if (key.name === "q" || key.name === "escape") return "cancel";
|
|
358
|
+
},
|
|
359
|
+
timeoutMs
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
function highlightHotkey(key) {
|
|
363
|
+
return colors.bold(colors.green(key));
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* @description 生成重试按键提示,并高亮关键热键。
|
|
367
|
+
*/
|
|
368
|
+
function formatRetryHotkeyPrompt(timeoutMs = 3e4) {
|
|
369
|
+
const highlight = (key) => highlightHotkey(key);
|
|
370
|
+
const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1e3));
|
|
371
|
+
const confirmKeys = RETRY_CONFIRM_KEYS.map(highlight).join(" / ");
|
|
372
|
+
const cancelKeys = RETRY_CANCEL_KEYS.map(highlight).join(" / ");
|
|
373
|
+
return i18nText(`按 ${confirmKeys} 重试,按 ${cancelKeys} 退出(${timeoutSeconds}s 内无输入将自动失败)。`, `Press ${confirmKeys} to retry, ${cancelKeys} to cancel (auto fail in ${timeoutSeconds}s).`);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* @description 输出重试热键提示,并独占等待当前提示对应的按键输入。
|
|
377
|
+
*/
|
|
378
|
+
async function promptRetryKeypress(options) {
|
|
379
|
+
const { logger, timeoutMs = 3e4 } = options;
|
|
380
|
+
logger.info(formatRetryHotkeyPrompt(timeoutMs));
|
|
381
|
+
return await waitForRetryKeypress({
|
|
382
|
+
timeoutMs,
|
|
383
|
+
signal: options.signal
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* @description 统一处理微信开发者工具登录失效场景下的提示与重试交互。
|
|
388
|
+
*/
|
|
389
|
+
async function promptWechatIdeLoginRetry(options) {
|
|
390
|
+
const { allowRetry = true, cancelLevel = "info", error, logger, promptOpenIdeLogin = false, retryTimeoutMs = 3e4 } = options;
|
|
391
|
+
logger.error(i18nText("检测到微信开发者工具登录状态失效,请先登录后重试。", "Wechat DevTools login has expired. Please login and retry."));
|
|
392
|
+
if (promptOpenIdeLogin) logger.warn(i18nText("请先打开微信开发者工具完成登录。", "Please open Wechat DevTools and complete login first."));
|
|
393
|
+
logger.warn(formatWechatIdeLoginRequiredError(error));
|
|
394
|
+
if (!allowRetry) return "cancel";
|
|
395
|
+
const action = await promptRetryKeypress({
|
|
396
|
+
logger,
|
|
397
|
+
timeoutMs: retryTimeoutMs,
|
|
398
|
+
signal: options.signal
|
|
399
|
+
});
|
|
400
|
+
if (action === "timeout") {
|
|
401
|
+
logger.error(i18nText(`等待登录重试输入超时(${retryTimeoutMs}ms),已自动取消。`, `Retry prompt timed out (${retryTimeoutMs}ms), canceled automatically.`));
|
|
402
|
+
return "cancel";
|
|
403
|
+
}
|
|
404
|
+
if (action !== "retry") {
|
|
405
|
+
logger[cancelLevel](i18nText("已取消重试。完成登录后请重新执行当前命令。", "Retry canceled. Please run the command again after login."));
|
|
406
|
+
return "cancel";
|
|
407
|
+
}
|
|
408
|
+
return "retry";
|
|
409
|
+
}
|
|
410
|
+
// src/cli/automator/errors.ts
|
|
44
411
|
const ERROR_STACK_PREFIX_RE = /^at /;
|
|
45
412
|
const ERROR_PREFIX_RE = /^\[error\]\s*/i;
|
|
46
413
|
const ERROR_LABEL_PREFIX_RE = /^error\s*:\s*/i;
|
|
@@ -63,12 +430,6 @@ const DEVTOOLS_INFRA_ERROR_PATTERNS = [
|
|
|
63
430
|
/ECONNREFUSED/i,
|
|
64
431
|
/connect ECONNREFUSED/i
|
|
65
432
|
];
|
|
66
|
-
const DEVTOOLS_LOGIN_REQUIRED_PATTERNS = [
|
|
67
|
-
/code\s*[:=]\s*10/i,
|
|
68
|
-
/需要重新登录/,
|
|
69
|
-
/need\s+re-?login/i,
|
|
70
|
-
/re-?login/i
|
|
71
|
-
];
|
|
72
433
|
/**
|
|
73
434
|
* @description 从错误对象中提取可读文本。
|
|
74
435
|
*/
|
|
@@ -146,9 +507,7 @@ function getAutomatorProtocolTimeoutMethod(error) {
|
|
|
146
507
|
* @description 判断错误是否属于开发者工具登录失效。
|
|
147
508
|
*/
|
|
148
509
|
function isAutomatorLoginError(error) {
|
|
149
|
-
|
|
150
|
-
if (!text) return false;
|
|
151
|
-
return DEVTOOLS_LOGIN_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
|
|
510
|
+
return isWechatIdeLoginRequiredError(error);
|
|
152
511
|
}
|
|
153
512
|
/**
|
|
154
513
|
* @description 格式化开发者工具登录失效错误,便于终端展示。
|
|
@@ -163,126 +522,7 @@ function formatAutomatorLoginError(error) {
|
|
|
163
522
|
if (!code && !message) lines.push("- message: 需要重新登录");
|
|
164
523
|
return lines.join("\n");
|
|
165
524
|
}
|
|
166
|
-
|
|
167
|
-
//#region src/cli/automatorProject.ts
|
|
168
|
-
const WRAPPER_ROOT = path.join(os.tmpdir(), "weapp-ide-cli-automator-projects");
|
|
169
|
-
function normalizeProjectRelativeRoot(rawRoot) {
|
|
170
|
-
if (typeof rawRoot !== "string") return;
|
|
171
|
-
const normalized = rawRoot.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
172
|
-
if (!normalized || normalized === ".") return;
|
|
173
|
-
if (normalized.split("/").includes("..")) return;
|
|
174
|
-
return normalized;
|
|
175
|
-
}
|
|
176
|
-
async function pathExists(filePath) {
|
|
177
|
-
try {
|
|
178
|
-
await fs.access(filePath);
|
|
179
|
-
return true;
|
|
180
|
-
} catch {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
async function readJsonObject$1(filePath) {
|
|
185
|
-
try {
|
|
186
|
-
const raw = await fs.readFile(filePath, "utf8");
|
|
187
|
-
const value = JSON.parse(raw);
|
|
188
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
189
|
-
} catch {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
async function writeJsonObject$1(filePath, value) {
|
|
194
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
195
|
-
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
196
|
-
}
|
|
197
|
-
async function copyProjectRoot(sourceProjectPath, wrapperProjectPath, relativeRoot) {
|
|
198
|
-
const sourcePath = path.join(sourceProjectPath, relativeRoot);
|
|
199
|
-
if (!await pathExists(sourcePath)) return;
|
|
200
|
-
await fs.cp(sourcePath, path.join(wrapperProjectPath, relativeRoot), {
|
|
201
|
-
dereference: true,
|
|
202
|
-
force: true,
|
|
203
|
-
recursive: true
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
function createWrapperProjectConfig(source, patch) {
|
|
207
|
-
const { miniprogramRoot: _miniprogramRoot, qcloudRoot: _qcloudRoot, srcMiniprogramRoot: _srcMiniprogramRoot, ...rest } = source;
|
|
208
|
-
const sourceSetting = source.setting && typeof source.setting === "object" ? source.setting : {};
|
|
209
|
-
const patchSetting = patch.setting && typeof patch.setting === "object" ? patch.setting : {};
|
|
210
|
-
return {
|
|
211
|
-
...rest,
|
|
212
|
-
...patch,
|
|
213
|
-
setting: {
|
|
214
|
-
...sourceSetting,
|
|
215
|
-
...patchSetting,
|
|
216
|
-
packNpmManually: false,
|
|
217
|
-
packNpmRelationList: []
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
async function ensureWrapperAppConfig(wrapperProjectPath) {
|
|
222
|
-
const appConfigPath = path.join(wrapperProjectPath, "app.json");
|
|
223
|
-
const appConfig = await readJsonObject$1(appConfigPath);
|
|
224
|
-
if (!appConfig) return;
|
|
225
|
-
await writeJsonObject$1(appConfigPath, {
|
|
226
|
-
...appConfig,
|
|
227
|
-
subPackages: Array.isArray(appConfig.subPackages) ? appConfig.subPackages : []
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* @description 为 DevTools automator 准备稳定的小程序根目录,避免打开瞬间跨 miniprogramRoot 读取 app.json 抖动。
|
|
232
|
-
*/
|
|
233
|
-
async function resolveAutomatorProjectPath(projectPath) {
|
|
234
|
-
const sourceProjectPath = path.resolve(projectPath);
|
|
235
|
-
const projectConfig = await readJsonObject$1(path.join(sourceProjectPath, "project.config.json"));
|
|
236
|
-
const miniprogramRoot = normalizeProjectRelativeRoot(projectConfig?.miniprogramRoot);
|
|
237
|
-
if (!miniprogramRoot) return {
|
|
238
|
-
projectPath: sourceProjectPath,
|
|
239
|
-
sourceProjectPath
|
|
240
|
-
};
|
|
241
|
-
const sourceProjectConfig = projectConfig ?? {};
|
|
242
|
-
const distRoot = path.join(sourceProjectPath, miniprogramRoot);
|
|
243
|
-
if (!await pathExists(path.join(distRoot, "app.json"))) return {
|
|
244
|
-
projectPath: sourceProjectPath,
|
|
245
|
-
sourceProjectPath
|
|
246
|
-
};
|
|
247
|
-
const wrapperHash = createHash("sha1").update(sourceProjectPath).update("\0").update(path.resolve(distRoot)).digest("hex").slice(0, 16);
|
|
248
|
-
const wrapperProjectPath = path.join(WRAPPER_ROOT, wrapperHash);
|
|
249
|
-
await fs.rm(wrapperProjectPath, {
|
|
250
|
-
recursive: true,
|
|
251
|
-
force: true
|
|
252
|
-
});
|
|
253
|
-
await fs.mkdir(wrapperProjectPath, { recursive: true });
|
|
254
|
-
await fs.cp(distRoot, wrapperProjectPath, {
|
|
255
|
-
dereference: true,
|
|
256
|
-
force: true,
|
|
257
|
-
recursive: true
|
|
258
|
-
});
|
|
259
|
-
const pluginRoot = normalizeProjectRelativeRoot(sourceProjectConfig.pluginRoot);
|
|
260
|
-
if (pluginRoot) await copyProjectRoot(sourceProjectPath, wrapperProjectPath, pluginRoot);
|
|
261
|
-
const rootPatch = {
|
|
262
|
-
compileType: sourceProjectConfig.compileType === "plugin" ? "plugin" : "miniprogram",
|
|
263
|
-
miniprogramRoot: "./",
|
|
264
|
-
srcMiniprogramRoot: "./"
|
|
265
|
-
};
|
|
266
|
-
const projectPrivateConfig = await readJsonObject$1(path.join(sourceProjectPath, "project.private.config.json")) ?? {};
|
|
267
|
-
await writeJsonObject$1(path.join(wrapperProjectPath, "project.config.json"), createWrapperProjectConfig(sourceProjectConfig, {
|
|
268
|
-
...projectPrivateConfig,
|
|
269
|
-
...rootPatch
|
|
270
|
-
}));
|
|
271
|
-
await fs.rm(path.join(wrapperProjectPath, "project.private.config.json"), { force: true });
|
|
272
|
-
await ensureWrapperAppConfig(wrapperProjectPath);
|
|
273
|
-
return {
|
|
274
|
-
cleanup: async () => {
|
|
275
|
-
await fs.rm(wrapperProjectPath, {
|
|
276
|
-
recursive: true,
|
|
277
|
-
force: true
|
|
278
|
-
});
|
|
279
|
-
},
|
|
280
|
-
projectPath: wrapperProjectPath,
|
|
281
|
-
sourceProjectPath
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
//#endregion
|
|
285
|
-
//#region src/cli/wechatDevtoolsSettings.ts
|
|
525
|
+
// src/cli/wechatDevtoolsSettings.ts
|
|
286
526
|
function createStorageHash(key) {
|
|
287
527
|
return createHash("md5").update(key).digest("hex");
|
|
288
528
|
}
|
|
@@ -383,8 +623,7 @@ async function bootstrapWechatDevtoolsSettings(options = {}) {
|
|
|
383
623
|
trustedProjectCount: trusted ? 1 : 0
|
|
384
624
|
};
|
|
385
625
|
}
|
|
386
|
-
|
|
387
|
-
//#region src/cli/automator.ts
|
|
626
|
+
// src/cli/automator.ts
|
|
388
627
|
const PROJECT_AUTOMATOR_PORT_BASE = 9620;
|
|
389
628
|
const PROJECT_AUTOMATOR_PORT_RANGE = 2e3;
|
|
390
629
|
/**
|
|
@@ -420,25 +659,26 @@ async function launchSelectedAutomator(options) {
|
|
|
420
659
|
const launcher = new Launcher();
|
|
421
660
|
let lastError = null;
|
|
422
661
|
let bootstrapResult;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
sourceProjectPath: path.resolve(projectPath)
|
|
426
|
-
} : await scope.step(() => resolveAutomatorProjectPath(projectPath), { stage: "project" });
|
|
427
|
-
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL) {
|
|
428
|
-
const { launchManagedAutomator } = await import("./managed-Sq8y8FzP.js");
|
|
662
|
+
if (process.env["WEAPP_IDE_MANAGED_PROJECT_JOURNAL"]?.trim()) {
|
|
663
|
+
const { launchManagedAutomator } = await import("./managed-hSCZXTon.js");
|
|
429
664
|
managedLaunch = launchManagedAutomator({
|
|
430
665
|
launcher,
|
|
431
666
|
scope,
|
|
432
667
|
target,
|
|
433
|
-
projectPath:
|
|
668
|
+
projectPath: path.resolve(projectPath),
|
|
434
669
|
sourceProjectPath: projectPath,
|
|
435
|
-
port: port ?? resolveProjectAutomatorPort(
|
|
670
|
+
port: port ?? resolveProjectAutomatorPort(projectPath),
|
|
436
671
|
sessionId,
|
|
437
672
|
trustProject: resolvedTrustProject,
|
|
438
|
-
persistAsDefaultSession: options.persistAsDefaultSession
|
|
673
|
+
persistAsDefaultSession: options.persistAsDefaultSession,
|
|
674
|
+
preserveProjectRoot: options.preserveProjectRoot
|
|
439
675
|
});
|
|
440
676
|
return await managedLaunch;
|
|
441
677
|
}
|
|
678
|
+
const resolvedProject = options.preserveProjectRoot ? {
|
|
679
|
+
projectPath: path.resolve(projectPath),
|
|
680
|
+
sourceProjectPath: path.resolve(projectPath)
|
|
681
|
+
} : await scope.step(() => resolveAutomatorProjectPath(projectPath), { stage: "project" });
|
|
442
682
|
if (config.autoBootstrapDevtools !== false) bootstrapResult = await scope.step(() => bootstrapWechatDevtoolsSettings({
|
|
443
683
|
target,
|
|
444
684
|
projectPath: resolvedProject.projectPath,
|
|
@@ -563,14 +803,13 @@ async function connectOpenedAutomator(options) {
|
|
|
563
803
|
}), {
|
|
564
804
|
stage: "connect",
|
|
565
805
|
disposeLate: (program) => {
|
|
566
|
-
if (program && typeof program === "object" && "disconnect" in program && typeof program.disconnect === "function") program.disconnect();
|
|
806
|
+
if (program && typeof program === "object" && "disconnect" in program && typeof program.disconnect === "function") return program.disconnect();
|
|
567
807
|
}
|
|
568
808
|
});
|
|
569
809
|
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL) miniProgram.close = async () => {
|
|
570
|
-
miniProgram.disconnect();
|
|
810
|
+
await miniProgram.disconnect();
|
|
571
811
|
};
|
|
572
812
|
return miniProgram;
|
|
573
813
|
});
|
|
574
814
|
}
|
|
575
|
-
|
|
576
|
-
export { parseAutomatorPort as _, bootstrapWechatDevtoolsSettings as a, getAutomatorProtocolTimeoutMethod as c, isAutomatorProtocolTimeoutError as d, isAutomatorWsConnectError as f, resolveAutomatorSessionOptions as g, isRetryableAutomatorLaunchError as h, resolveProjectAutomatorPort as i, isAutomatorLoginError as l, isDevtoolsHttpPortError as m, launchAutomator as n, detectWechatDevtoolsServicePort as o, isDevtoolsExtensionContextInvalidatedError as p, persistOpenedAutomatorSession as r, formatAutomatorLoginError as s, connectOpenedAutomator as t, isAutomatorPortInUseError as u };
|
|
815
|
+
export { waitForExclusiveKeypress as A, isWechatIdeLoginRequiredError as C, waitForRetryKeypress as D, promptWechatIdeLoginRetry as E, parseAutomatorPort as F, i18nText as M, validateLocaleOption as N, createSharedInputSession as O, resolveAutomatorSessionOptions as P, formatWechatIdeLoginRequiredError as S, promptRetryKeypress as T, RETRY_CONFIRM_KEYS as _, bootstrapWechatDevtoolsSettings as a, extractExecutionErrorText as b, getAutomatorProtocolTimeoutMethod as c, isAutomatorProtocolTimeoutError as d, isAutomatorWsConnectError as f, RETRY_CANCEL_KEYS as g, isRetryableAutomatorLaunchError as h, resolveProjectAutomatorPort as i, configureLocaleFromArgv as j, runWithSuspendedSharedInput as k, isAutomatorLoginError as l, isDevtoolsHttpPortError as m, launchAutomator as n, detectWechatDevtoolsServicePort as o, isDevtoolsExtensionContextInvalidatedError as p, persistOpenedAutomatorSession as r, formatAutomatorLoginError as s, connectOpenedAutomator as t, isAutomatorPortInUseError as u, RETRY_PROMPT_INITIAL_IGNORE_MS as v, isWechatIdeLoginRequiredExitError as w, formatRetryHotkeyPrompt as x, createWechatIdeLoginRequiredExitError as y };
|