weapp-ide-cli 5.3.3 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{automator-session-BZzODsJi.js → automator-session-CvStbY9I.js} +395 -35
- package/dist/{cli-CTVH50qe.js → cli-C9KfpthX.js} +12 -1046
- package/dist/cli.js +2 -2
- package/dist/commands-B4HFVW-Q.js +2 -0
- package/dist/commands-Js_IjVxE.js +1142 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +5 -5
- package/dist/run-mcp-CL909GLy.js +2 -0
- package/dist/{run-mcp-TnooVQe8.js → run-mcp-DvHYYX0m.js} +6 -2
- package/package.json +3 -3
- package/dist/commands-BynZfUJ6.js +0 -333
- package/dist/commands-DG-3Zgd0.js +0 -2
- package/dist/run-mcp-DhujWgfX.js +0 -2
|
@@ -9,6 +9,7 @@ import path$1 from "pathe";
|
|
|
9
9
|
import logger, { colors } from "@weapp-core/logger";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
import { acquireSharedMiniProgram, closeSharedMiniProgram, getSharedMiniProgramSessionCount, releaseSharedMiniProgram, withMiniProgram } from "@weapp-vite/devtools-runtime";
|
|
12
|
+
import { emitKeypressEvents } from "node:readline";
|
|
12
13
|
//#region src/utils/path.ts
|
|
13
14
|
/**
|
|
14
15
|
* @description 解析为绝对路径(基于当前工作目录)
|
|
@@ -532,34 +533,44 @@ function extractErrorText(error) {
|
|
|
532
533
|
candidate.stdout
|
|
533
534
|
].filter((value) => typeof value === "string" && value.trim().length > 0).join("\n");
|
|
534
535
|
}
|
|
535
|
-
function
|
|
536
|
+
function normalizeAutomatorSessionId(sessionId, port) {
|
|
537
|
+
if (sessionId?.trim()) return sessionId.trim();
|
|
538
|
+
return port ? `port-${port}` : "default";
|
|
539
|
+
}
|
|
540
|
+
function resolveAutomatorSessionFilePath(projectPath, sessionId, port) {
|
|
536
541
|
const normalizedProjectPath = path.resolve(projectPath);
|
|
537
|
-
const
|
|
542
|
+
const normalizedSessionId = normalizeAutomatorSessionId(sessionId, port);
|
|
543
|
+
const sessionKey = normalizedSessionId === "default" ? normalizedProjectPath : `${normalizedProjectPath}#${normalizedSessionId}`;
|
|
544
|
+
const encodedProjectPath = Buffer.from(sessionKey).toString("base64url");
|
|
538
545
|
return path.join(AUTOMATOR_SESSION_DIR, `${encodedProjectPath}.json`);
|
|
539
546
|
}
|
|
540
|
-
async function persistAutomatorSession(
|
|
541
|
-
const filePath = resolveAutomatorSessionFilePath(projectPath);
|
|
547
|
+
async function persistAutomatorSession(options) {
|
|
548
|
+
const filePath = resolveAutomatorSessionFilePath(options.projectPath, options.sessionId, options.port);
|
|
542
549
|
const payload = {
|
|
543
|
-
|
|
550
|
+
...options.port ? { port: options.port } : {},
|
|
551
|
+
projectPath: path.resolve(options.projectPath),
|
|
552
|
+
...options.sessionId ? { sessionId: options.sessionId } : {},
|
|
544
553
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
545
|
-
wsEndpoint
|
|
554
|
+
wsEndpoint: options.wsEndpoint
|
|
546
555
|
};
|
|
547
556
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
548
557
|
await fs.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
549
558
|
}
|
|
550
|
-
async function readPersistedAutomatorSession(projectPath) {
|
|
551
|
-
const filePath = resolveAutomatorSessionFilePath(projectPath);
|
|
559
|
+
async function readPersistedAutomatorSession(projectPath, sessionId, port) {
|
|
560
|
+
const filePath = resolveAutomatorSessionFilePath(projectPath, sessionId, port);
|
|
552
561
|
try {
|
|
553
562
|
const raw = await fs.readFile(filePath, "utf8");
|
|
554
563
|
const payload = JSON.parse(raw);
|
|
555
564
|
if (payload.projectPath !== path.resolve(projectPath) || typeof payload.wsEndpoint !== "string" || !payload.wsEndpoint.trim()) return null;
|
|
565
|
+
if (sessionId && payload.sessionId !== sessionId) return null;
|
|
566
|
+
if (port && payload.port !== port) return null;
|
|
556
567
|
return payload;
|
|
557
568
|
} catch {
|
|
558
569
|
return null;
|
|
559
570
|
}
|
|
560
571
|
}
|
|
561
|
-
async function removePersistedAutomatorSession(projectPath) {
|
|
562
|
-
const filePath = resolveAutomatorSessionFilePath(projectPath);
|
|
572
|
+
async function removePersistedAutomatorSession(projectPath, sessionId, port) {
|
|
573
|
+
const filePath = resolveAutomatorSessionFilePath(projectPath, sessionId, port);
|
|
563
574
|
try {
|
|
564
575
|
await fs.rm(filePath, { force: true });
|
|
565
576
|
} catch {}
|
|
@@ -567,7 +578,7 @@ async function removePersistedAutomatorSession(projectPath) {
|
|
|
567
578
|
/**
|
|
568
579
|
* @description 提取登录失效时最适合展示给用户的一行信息。
|
|
569
580
|
*/
|
|
570
|
-
function extractLoginRequiredMessage(text) {
|
|
581
|
+
function extractLoginRequiredMessage$1(text) {
|
|
571
582
|
if (!text) return "";
|
|
572
583
|
if (LOGIN_REQUIRED_CN_RE.test(text)) return "需要重新登录";
|
|
573
584
|
const englishMatch = text.match(LOGIN_REQUIRED_EN_RE);
|
|
@@ -631,7 +642,7 @@ function isAutomatorLoginError(error) {
|
|
|
631
642
|
function formatAutomatorLoginError(error) {
|
|
632
643
|
const text = extractErrorText(error);
|
|
633
644
|
const code = text.match(LOGIN_REQUIRED_CODE_RE)?.[1];
|
|
634
|
-
const message = extractLoginRequiredMessage(text);
|
|
645
|
+
const message = extractLoginRequiredMessage$1(text);
|
|
635
646
|
const lines = ["微信开发者工具返回登录错误:"];
|
|
636
647
|
if (code) lines.push(`- code: ${code}`);
|
|
637
648
|
if (message) lines.push(`- message: ${message}`);
|
|
@@ -642,7 +653,7 @@ function formatAutomatorLoginError(error) {
|
|
|
642
653
|
* @description 基于当前配置解析 CLI 路径,并通过现代化 automator 入口启动会话。
|
|
643
654
|
*/
|
|
644
655
|
async function launchAutomator(options) {
|
|
645
|
-
const { cliPath, projectPath, timeout = 3e4 } = options;
|
|
656
|
+
const { cliPath, port, projectPath, sessionId, timeout = 3e4 } = options;
|
|
646
657
|
const resolvedCliPath = cliPath ?? (await resolveCliPath()).cliPath ?? void 0;
|
|
647
658
|
const config = await readCustomConfig();
|
|
648
659
|
const resolvedTrustProject = options.trustProject ?? config.autoTrustProject ?? false;
|
|
@@ -657,12 +668,18 @@ async function launchAutomator(options) {
|
|
|
657
668
|
for (let attempt = 0; attempt < 2; attempt += 1) try {
|
|
658
669
|
const miniProgram = await launcher.launch({
|
|
659
670
|
cliPath: resolvedCliPath,
|
|
671
|
+
...port ? { port } : {},
|
|
660
672
|
projectPath,
|
|
661
673
|
timeout,
|
|
662
674
|
trustProject: resolvedTrustProject
|
|
663
675
|
});
|
|
664
676
|
const sessionMetadata = Reflect.get(miniProgram, "__WEAPP_VITE_SESSION_METADATA");
|
|
665
|
-
if (typeof sessionMetadata?.wsEndpoint === "string" && sessionMetadata.wsEndpoint) await persistAutomatorSession(
|
|
677
|
+
if (typeof sessionMetadata?.wsEndpoint === "string" && sessionMetadata.wsEndpoint) await persistAutomatorSession({
|
|
678
|
+
port: sessionMetadata.port ?? port,
|
|
679
|
+
projectPath,
|
|
680
|
+
sessionId,
|
|
681
|
+
wsEndpoint: sessionMetadata.wsEndpoint
|
|
682
|
+
});
|
|
666
683
|
return miniProgram;
|
|
667
684
|
} catch (error) {
|
|
668
685
|
lastError = error;
|
|
@@ -674,14 +691,14 @@ async function launchAutomator(options) {
|
|
|
674
691
|
* @description 连接当前项目已打开的开发者工具自动化会话,不触发新的 IDE 拉起。
|
|
675
692
|
*/
|
|
676
693
|
async function connectOpenedAutomator(options) {
|
|
677
|
-
const { projectPath } = options;
|
|
694
|
+
const { port, projectPath, sessionId } = options;
|
|
678
695
|
const launcher = new Launcher();
|
|
679
|
-
const persistedSession = await readPersistedAutomatorSession(projectPath);
|
|
680
|
-
const wsEndpoint = persistedSession?.wsEndpoint ?? DEFAULT_WECHAT_DEVTOOLS_WS_ENDPOINT;
|
|
696
|
+
const persistedSession = await readPersistedAutomatorSession(projectPath, sessionId, port);
|
|
697
|
+
const wsEndpoint = persistedSession?.wsEndpoint ?? (port ? `ws://127.0.0.1:${port}` : DEFAULT_WECHAT_DEVTOOLS_WS_ENDPOINT);
|
|
681
698
|
try {
|
|
682
699
|
return await launcher.connect({ wsEndpoint });
|
|
683
700
|
} catch (error) {
|
|
684
|
-
if (persistedSession) await removePersistedAutomatorSession(projectPath);
|
|
701
|
+
if (persistedSession) await removePersistedAutomatorSession(projectPath, sessionId, port);
|
|
685
702
|
throw error;
|
|
686
703
|
}
|
|
687
704
|
}
|
|
@@ -751,7 +768,316 @@ function readLangOption(argv) {
|
|
|
751
768
|
}
|
|
752
769
|
}
|
|
753
770
|
//#endregion
|
|
771
|
+
//#region src/cli/inputCoordinator.ts
|
|
772
|
+
const sharedSessions = /* @__PURE__ */ new Set();
|
|
773
|
+
const exclusiveKeypressStack = [];
|
|
774
|
+
const EXCLUSIVE_KEYPRESS_CARRYOVER_GUARD_MS = 500;
|
|
775
|
+
let initialized = false;
|
|
776
|
+
let rawModeEnabled = false;
|
|
777
|
+
let dataListenerAttached = false;
|
|
778
|
+
let keypressListenerAttached = false;
|
|
779
|
+
let lastExclusiveResolvedKeypress;
|
|
780
|
+
function hasInteractiveStdin() {
|
|
781
|
+
return Boolean(process.stdin?.isTTY);
|
|
782
|
+
}
|
|
783
|
+
function canSetRawMode() {
|
|
784
|
+
return typeof process.stdin?.setRawMode === "function";
|
|
785
|
+
}
|
|
786
|
+
function updateTerminalState() {
|
|
787
|
+
const shouldKeepInputActive = Array.from(sharedSessions).some((session) => !session.closed && !session.suspended) || exclusiveKeypressStack.length > 0;
|
|
788
|
+
if (canSetRawMode() && rawModeEnabled !== shouldKeepInputActive) {
|
|
789
|
+
process.stdin.setRawMode(shouldKeepInputActive);
|
|
790
|
+
rawModeEnabled = shouldKeepInputActive;
|
|
791
|
+
}
|
|
792
|
+
if (shouldKeepInputActive) {
|
|
793
|
+
process.stdin.resume();
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
process.stdin.pause();
|
|
797
|
+
}
|
|
798
|
+
function handleData(chunk) {
|
|
799
|
+
if (exclusiveKeypressStack.length > 0) return;
|
|
800
|
+
for (const session of sharedSessions) {
|
|
801
|
+
if (session.closed || session.suspended || !session.options.onData) continue;
|
|
802
|
+
session.options.onData(chunk);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function handleKeypress(str, key) {
|
|
806
|
+
const activeExclusive = exclusiveKeypressStack[exclusiveKeypressStack.length - 1];
|
|
807
|
+
if (activeExclusive) {
|
|
808
|
+
const now = Date.now();
|
|
809
|
+
const signature = `${key?.ctrl ? "ctrl+" : ""}${key?.name ?? str}`;
|
|
810
|
+
if (lastExclusiveResolvedKeypress && lastExclusiveResolvedKeypress.signature === signature && now - lastExclusiveResolvedKeypress.timestamp <= EXCLUSIVE_KEYPRESS_CARRYOVER_GUARD_MS) {
|
|
811
|
+
lastExclusiveResolvedKeypress.timestamp = now;
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (now < activeExclusive.ignoreUntil) return;
|
|
815
|
+
const nextValue = activeExclusive.onKeypress(str, key);
|
|
816
|
+
if (nextValue !== void 0) {
|
|
817
|
+
clearTimeout(activeExclusive.timeout);
|
|
818
|
+
exclusiveKeypressStack.pop();
|
|
819
|
+
lastExclusiveResolvedKeypress = {
|
|
820
|
+
signature,
|
|
821
|
+
timestamp: now
|
|
822
|
+
};
|
|
823
|
+
updateTerminalState();
|
|
824
|
+
activeExclusive.resolve(nextValue);
|
|
825
|
+
}
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
for (const session of sharedSessions) {
|
|
829
|
+
if (session.closed || session.suspended || !session.options.onKeypress) continue;
|
|
830
|
+
session.options.onKeypress(str, key);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function ensureCoordinatorInitialized() {
|
|
834
|
+
if (initialized || !hasInteractiveStdin()) return;
|
|
835
|
+
emitKeypressEvents(process.stdin);
|
|
836
|
+
if (!dataListenerAttached) {
|
|
837
|
+
process.stdin.on("data", handleData);
|
|
838
|
+
dataListenerAttached = true;
|
|
839
|
+
}
|
|
840
|
+
if (!keypressListenerAttached) {
|
|
841
|
+
process.stdin.on("keypress", handleKeypress);
|
|
842
|
+
keypressListenerAttached = true;
|
|
843
|
+
}
|
|
844
|
+
initialized = true;
|
|
845
|
+
}
|
|
846
|
+
function createSharedInputSession(options) {
|
|
847
|
+
if (!hasInteractiveStdin()) return;
|
|
848
|
+
ensureCoordinatorInitialized();
|
|
849
|
+
const session = {
|
|
850
|
+
closed: false,
|
|
851
|
+
options,
|
|
852
|
+
suspended: false
|
|
853
|
+
};
|
|
854
|
+
sharedSessions.add(session);
|
|
855
|
+
updateTerminalState();
|
|
856
|
+
return {
|
|
857
|
+
close() {
|
|
858
|
+
if (session.closed) return;
|
|
859
|
+
session.closed = true;
|
|
860
|
+
sharedSessions.delete(session);
|
|
861
|
+
updateTerminalState();
|
|
862
|
+
},
|
|
863
|
+
resume() {
|
|
864
|
+
if (session.closed) return;
|
|
865
|
+
session.suspended = false;
|
|
866
|
+
updateTerminalState();
|
|
867
|
+
},
|
|
868
|
+
suspend() {
|
|
869
|
+
if (session.closed) return;
|
|
870
|
+
session.suspended = true;
|
|
871
|
+
updateTerminalState();
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
async function waitForExclusiveKeypress(options) {
|
|
876
|
+
if (!hasInteractiveStdin()) return "timeout";
|
|
877
|
+
ensureCoordinatorInitialized();
|
|
878
|
+
return await new Promise((resolve) => {
|
|
879
|
+
const normalizedTimeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs && options.timeoutMs > 0 ? options.timeoutMs : 3e4;
|
|
880
|
+
const normalizedIgnoreInitialMs = Number.isFinite(options.ignoreInitialMs) && options.ignoreInitialMs && options.ignoreInitialMs > 0 ? options.ignoreInitialMs : 0;
|
|
881
|
+
const record = {
|
|
882
|
+
ignoreUntil: Date.now() + normalizedIgnoreInitialMs,
|
|
883
|
+
onKeypress: options.onKeypress,
|
|
884
|
+
resolve,
|
|
885
|
+
timeout: setTimeout(() => {
|
|
886
|
+
const index = exclusiveKeypressStack.lastIndexOf(record);
|
|
887
|
+
if (index >= 0) exclusiveKeypressStack.splice(index, 1);
|
|
888
|
+
updateTerminalState();
|
|
889
|
+
resolve("timeout");
|
|
890
|
+
}, normalizedTimeoutMs)
|
|
891
|
+
};
|
|
892
|
+
exclusiveKeypressStack.push(record);
|
|
893
|
+
updateTerminalState();
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
async function runWithSuspendedSharedInput(runner) {
|
|
897
|
+
const previousStates = /* @__PURE__ */ new Map();
|
|
898
|
+
for (const session of sharedSessions) {
|
|
899
|
+
if (session.closed) continue;
|
|
900
|
+
previousStates.set(session, session.suspended);
|
|
901
|
+
session.suspended = true;
|
|
902
|
+
}
|
|
903
|
+
if (canSetRawMode() && rawModeEnabled) {
|
|
904
|
+
process.stdin.setRawMode(false);
|
|
905
|
+
rawModeEnabled = false;
|
|
906
|
+
}
|
|
907
|
+
process.stdin.resume();
|
|
908
|
+
try {
|
|
909
|
+
return await runner();
|
|
910
|
+
} finally {
|
|
911
|
+
for (const [session, suspended] of previousStates) if (!session.closed) session.suspended = suspended;
|
|
912
|
+
updateTerminalState();
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
//#endregion
|
|
916
|
+
//#region src/cli/retry.ts
|
|
917
|
+
const RETRY_PROMPT_INITIAL_IGNORE_MS = 300;
|
|
918
|
+
const RETRY_CONFIRM_KEYS = ["y"];
|
|
919
|
+
const RETRY_CANCEL_KEYS = [
|
|
920
|
+
"q",
|
|
921
|
+
"Esc",
|
|
922
|
+
"Ctrl+C"
|
|
923
|
+
];
|
|
924
|
+
const LOGIN_REQUIRED_PATTERNS = [
|
|
925
|
+
/code\s*[:=]\s*10/i,
|
|
926
|
+
/需要重新登录/,
|
|
927
|
+
/need\s+re-?login/i,
|
|
928
|
+
/re-?login/i
|
|
929
|
+
];
|
|
930
|
+
/**
|
|
931
|
+
* @description 提取执行错误文本,便于统一匹配与提示。
|
|
932
|
+
*/
|
|
933
|
+
function extractExecutionErrorText(error) {
|
|
934
|
+
if (!error || typeof error !== "object") return "";
|
|
935
|
+
const parts = [];
|
|
936
|
+
const candidate = error;
|
|
937
|
+
for (const field of [
|
|
938
|
+
candidate.message,
|
|
939
|
+
candidate.shortMessage,
|
|
940
|
+
candidate.stderr,
|
|
941
|
+
candidate.stdout
|
|
942
|
+
]) if (typeof field === "string" && field.trim()) parts.push(field);
|
|
943
|
+
return parts.join("\n");
|
|
944
|
+
}
|
|
945
|
+
function extractLoginRequiredMessage(text) {
|
|
946
|
+
if (!text) return "";
|
|
947
|
+
if (/需要重新登录/.test(text)) return "需要重新登录";
|
|
948
|
+
const englishMatch = text.match(/need\s+re-?login|re-?login/i);
|
|
949
|
+
if (englishMatch?.[0]) return englishMatch[0].toLowerCase();
|
|
950
|
+
const firstLine = text.split(/\r?\n/).map((line) => line.trim()).find((line) => Boolean(line) && !line.startsWith("at "));
|
|
951
|
+
if (!firstLine) return "";
|
|
952
|
+
return firstLine.replace(/^\[error\]\s*/i, "").replace(/^error\s*:\s*/i, "").slice(0, 120);
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* @description 判断是否为微信开发者工具登录失效错误。
|
|
956
|
+
*/
|
|
957
|
+
function isWechatIdeLoginRequiredError(error) {
|
|
958
|
+
const text = extractExecutionErrorText(error);
|
|
959
|
+
if (!text) return false;
|
|
960
|
+
return LOGIN_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* @description 将登录失效错误格式化为更易读的摘要。
|
|
964
|
+
*/
|
|
965
|
+
function formatWechatIdeLoginRequiredError(error) {
|
|
966
|
+
const text = extractExecutionErrorText(error);
|
|
967
|
+
const code = text.match(/code\s*[:=]\s*(\d+)/i)?.[1];
|
|
968
|
+
const message = extractLoginRequiredMessage(text);
|
|
969
|
+
const lines = ["微信开发者工具返回登录错误:"];
|
|
970
|
+
if (code) lines.push(`- code: ${code}`);
|
|
971
|
+
if (message) lines.push(`- message: ${message}`);
|
|
972
|
+
if (!code && !message) lines.push("- message: 需要重新登录");
|
|
973
|
+
return lines.join("\n");
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* @description 创建登录失效专用错误,并携带退出码语义。
|
|
977
|
+
*/
|
|
978
|
+
function createWechatIdeLoginRequiredExitError(error, reason) {
|
|
979
|
+
const summary = formatWechatIdeLoginRequiredError(error);
|
|
980
|
+
const message = reason ? `${reason}\n${summary}` : summary;
|
|
981
|
+
const loginError = new Error(message);
|
|
982
|
+
loginError.name = "WechatIdeLoginRequiredError";
|
|
983
|
+
loginError.code = 10;
|
|
984
|
+
loginError.exitCode = 10;
|
|
985
|
+
return loginError;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* @description 判断错误是否为登录失效专用退出错误。
|
|
989
|
+
*/
|
|
990
|
+
function isWechatIdeLoginRequiredExitError(error) {
|
|
991
|
+
if (!error || typeof error !== "object") return false;
|
|
992
|
+
const candidate = error;
|
|
993
|
+
return candidate.name === "WechatIdeLoginRequiredError" || candidate.code === 10 || candidate.exitCode === 10;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* @description 交互等待用户按 y 重试,按 q 或 Ctrl+C 取消。
|
|
997
|
+
*/
|
|
998
|
+
async function waitForRetryKeypress(options = {}) {
|
|
999
|
+
const { timeoutMs = 3e4 } = options;
|
|
1000
|
+
if (!process.stdin.isTTY) return "cancel";
|
|
1001
|
+
return await waitForExclusiveKeypress({
|
|
1002
|
+
ignoreInitialMs: 300,
|
|
1003
|
+
onKeypress: (_str, key) => {
|
|
1004
|
+
if (!key) return;
|
|
1005
|
+
if (key.ctrl && key.name === "c") return "cancel";
|
|
1006
|
+
if (key.name === "y") return "retry";
|
|
1007
|
+
if (key.name === "q" || key.name === "escape") return "cancel";
|
|
1008
|
+
},
|
|
1009
|
+
timeoutMs
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
function highlightHotkey(key) {
|
|
1013
|
+
return colors.bold(colors.green(key));
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* @description 生成重试按键提示,并高亮关键热键。
|
|
1017
|
+
*/
|
|
1018
|
+
function formatRetryHotkeyPrompt(timeoutMs = 3e4) {
|
|
1019
|
+
const highlight = (key) => highlightHotkey(key);
|
|
1020
|
+
const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1e3));
|
|
1021
|
+
const confirmKeys = RETRY_CONFIRM_KEYS.map(highlight).join(" / ");
|
|
1022
|
+
const cancelKeys = RETRY_CANCEL_KEYS.map(highlight).join(" / ");
|
|
1023
|
+
return i18nText(`按 ${confirmKeys} 重试,按 ${cancelKeys} 退出(${timeoutSeconds}s 内无输入将自动失败)。`, `Press ${confirmKeys} to retry, ${cancelKeys} to cancel (auto fail in ${timeoutSeconds}s).`);
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* @description 输出重试热键提示,并独占等待当前提示对应的按键输入。
|
|
1027
|
+
*/
|
|
1028
|
+
async function promptRetryKeypress(options) {
|
|
1029
|
+
const { logger, timeoutMs = 3e4 } = options;
|
|
1030
|
+
logger.info(formatRetryHotkeyPrompt(timeoutMs));
|
|
1031
|
+
return await waitForRetryKeypress({ timeoutMs });
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* @description 统一处理微信开发者工具登录失效场景下的提示与重试交互。
|
|
1035
|
+
*/
|
|
1036
|
+
async function promptWechatIdeLoginRetry(options) {
|
|
1037
|
+
const { allowRetry = true, cancelLevel = "info", error, logger, promptOpenIdeLogin = false, retryTimeoutMs = 3e4 } = options;
|
|
1038
|
+
logger.error(i18nText("检测到微信开发者工具登录状态失效,请先登录后重试。", "Wechat DevTools login has expired. Please login and retry."));
|
|
1039
|
+
if (promptOpenIdeLogin) logger.warn(i18nText("请先打开微信开发者工具完成登录。", "Please open Wechat DevTools and complete login first."));
|
|
1040
|
+
logger.warn(formatWechatIdeLoginRequiredError(error));
|
|
1041
|
+
if (!allowRetry) return "cancel";
|
|
1042
|
+
const action = await promptRetryKeypress({
|
|
1043
|
+
logger,
|
|
1044
|
+
timeoutMs: retryTimeoutMs
|
|
1045
|
+
});
|
|
1046
|
+
if (action === "timeout") {
|
|
1047
|
+
logger.error(i18nText(`等待登录重试输入超时(${retryTimeoutMs}ms),已自动取消。`, `Retry prompt timed out (${retryTimeoutMs}ms), canceled automatically.`));
|
|
1048
|
+
return "cancel";
|
|
1049
|
+
}
|
|
1050
|
+
if (action !== "retry") {
|
|
1051
|
+
logger[cancelLevel](i18nText("已取消重试。完成登录后请重新执行当前命令。", "Retry canceled. Please run the command again after login."));
|
|
1052
|
+
return "cancel";
|
|
1053
|
+
}
|
|
1054
|
+
return "retry";
|
|
1055
|
+
}
|
|
1056
|
+
//#endregion
|
|
1057
|
+
//#region src/cli/run-login-executor.ts
|
|
1058
|
+
/**
|
|
1059
|
+
* @description 执行可重试命令循环,并将“是否可重试”与“如何提示重试”交给调用方定义。
|
|
1060
|
+
*/
|
|
1061
|
+
async function runRetryableCommand(options) {
|
|
1062
|
+
const { createCancelError, execute, isRetryableResult, onCancel, onRetry, promptRetry, shouldRetry } = options;
|
|
1063
|
+
let retryCount = 0;
|
|
1064
|
+
while (true) {
|
|
1065
|
+
const result = await execute();
|
|
1066
|
+
if (!isRetryableResult(result)) return result;
|
|
1067
|
+
if (shouldRetry(await promptRetry(result, retryCount))) {
|
|
1068
|
+
retryCount += 1;
|
|
1069
|
+
onRetry?.();
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
onCancel?.(result);
|
|
1073
|
+
throw createCancelError(result);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
//#endregion
|
|
754
1077
|
//#region src/cli/automator-session.ts
|
|
1078
|
+
function unwrapAutomatorConnectionError(result) {
|
|
1079
|
+
return result.kind === "retryable" ? result.error : result.value;
|
|
1080
|
+
}
|
|
755
1081
|
function normalizeMiniProgramConnectionError(error) {
|
|
756
1082
|
if (isAutomatorLoginError(error)) {
|
|
757
1083
|
logger_default.error(i18nText("检测到微信开发者工具登录状态失效,请先登录后重试。", "Wechat DevTools login has expired. Please login and retry."));
|
|
@@ -785,22 +1111,56 @@ function normalizeMiniProgramConnectionError(error) {
|
|
|
785
1111
|
* @description 建立 automator 会话,并统一处理常见连接错误提示。
|
|
786
1112
|
*/
|
|
787
1113
|
async function connectMiniProgram(options) {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1114
|
+
const result = await runRetryableCommand({
|
|
1115
|
+
createCancelError: (result) => createWechatIdeLoginRequiredExitError(unwrapAutomatorConnectionError(result), "cancelled"),
|
|
1116
|
+
execute: async () => {
|
|
1117
|
+
if (options.preferOpenedSession === false) try {
|
|
1118
|
+
return {
|
|
1119
|
+
kind: "result",
|
|
1120
|
+
value: await launchAutomator(options)
|
|
1121
|
+
};
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
if (!isAutomatorLoginError(error)) throw normalizeMiniProgramConnectionError(error);
|
|
1124
|
+
return {
|
|
1125
|
+
error,
|
|
1126
|
+
kind: "retryable"
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
try {
|
|
1130
|
+
return {
|
|
1131
|
+
kind: "result",
|
|
1132
|
+
value: await connectOpenedAutomator(options)
|
|
1133
|
+
};
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
const normalizedOpenSessionError = normalizeMiniProgramConnectionError(error);
|
|
1136
|
+
if (normalizedOpenSessionError instanceof Error && normalizedOpenSessionError.message === "DEVTOOLS_PROTOCOL_TIMEOUT") throw normalizedOpenSessionError;
|
|
1137
|
+
try {
|
|
1138
|
+
return {
|
|
1139
|
+
kind: "result",
|
|
1140
|
+
value: await launchAutomator(options)
|
|
1141
|
+
};
|
|
1142
|
+
} catch (launchError) {
|
|
1143
|
+
if (!isAutomatorLoginError(launchError)) throw normalizeMiniProgramConnectionError(launchError);
|
|
1144
|
+
return {
|
|
1145
|
+
error: launchError,
|
|
1146
|
+
kind: "retryable"
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
isRetryableResult: (result) => result.kind === "retryable",
|
|
1152
|
+
onRetry: () => {
|
|
1153
|
+
logger_default.info(i18nText("正在重试连接微信开发者工具...", "Retrying to connect Wechat DevTools..."));
|
|
1154
|
+
},
|
|
1155
|
+
promptRetry: async (result) => await promptWechatIdeLoginRetry({
|
|
1156
|
+
error: unwrapAutomatorConnectionError(result),
|
|
1157
|
+
logger: logger_default,
|
|
1158
|
+
promptOpenIdeLogin: true
|
|
1159
|
+
}),
|
|
1160
|
+
shouldRetry: (action) => action === "retry"
|
|
1161
|
+
});
|
|
1162
|
+
if (result.kind === "retryable") throw createWechatIdeLoginRequiredExitError(result.error);
|
|
1163
|
+
return result.value;
|
|
804
1164
|
}
|
|
805
1165
|
const runtimeHooks = {
|
|
806
1166
|
connectMiniProgram,
|
|
@@ -819,4 +1179,4 @@ async function withMiniProgram$1(options, runner) {
|
|
|
819
1179
|
return await withMiniProgram(runtimeHooks, options, runner);
|
|
820
1180
|
}
|
|
821
1181
|
//#endregion
|
|
822
|
-
export {
|
|
1182
|
+
export { readCustomConfig as $, isAutomatorProtocolTimeoutError as A, getConfiguredLocale as B, configureLocaleFromArgv as C, formatAutomatorLoginError as D, connectOpenedAutomator as E, launchAutomator as F, operatingSystemName as G, SupportedPlatformsMap as H, bootstrapWechatDevtoolsSettings as I, createAutoBootstrapDevtoolsConfig as J, colors as K, detectWechatDevtoolsServicePort as L, isDevtoolsExtensionContextInvalidatedError as M, isDevtoolsHttpPortError as N, getAutomatorProtocolTimeoutMethod as O, isRetryableAutomatorLaunchError as P, overwriteCustomConfig as Q, resolveCliPath as R, waitForExclusiveKeypress as S, validateLocaleOption as T, getDefaultCliPath as U, resolveDevtoolsAutomationDefaults as V, isOperatingSystemSupported as W, createCustomConfig as X, createAutoTrustProjectConfig as Y, createLocaleConfig as Z, promptRetryKeypress as _, releaseSharedMiniProgram as a, createSharedInputSession as b, RETRY_CANCEL_KEYS as c, createWechatIdeLoginRequiredExitError as d, removeCustomConfigKey as et, extractExecutionErrorText as f, isWechatIdeLoginRequiredExitError as g, isWechatIdeLoginRequiredError as h, getSharedMiniProgramSessionCount as i, isAutomatorWsConnectError as j, isAutomatorLoginError as k, RETRY_CONFIRM_KEYS as l, formatWechatIdeLoginRequiredError as m, closeSharedMiniProgram as n, defaultCustomConfigFilePath as nt, withMiniProgram$1 as o, formatRetryHotkeyPrompt as p, logger_default as q, connectMiniProgram as r, resolvePath as rt, runRetryableCommand as s, acquireSharedMiniProgram$1 as t, defaultCustomConfigDirPath as tt, RETRY_PROMPT_INITIAL_IGNORE_MS as u, promptWechatIdeLoginRetry as v, i18nText as w, runWithSuspendedSharedInput as x, waitForRetryKeypress as y, getConfig as z };
|