weapp-vite 6.13.3 → 6.14.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/dist/cli.mjs CHANGED
@@ -1,11 +1,10 @@
1
- import { _ as getDefaultIdeProjectRoot, b as shouldPassPlatformArgToIdeOpen, c as SHARED_CHUNK_VIRTUAL_PREFIX, d as checkRuntime, f as getProjectConfigFileName, g as DEFAULT_MP_PLATFORM, h as createCjsConfigLoadError, m as parseCommentJson, n as syncProjectSupportFiles, o as formatBytes, p as loadViteConfigFile, r as syncManagedTsconfigBootstrapFiles, s as createSharedBuildConfig, t as createCompilerContext, u as resolveWeappConfigFile, v as normalizeMiniPlatform, x as isPathInside, y as resolveMiniPlatform } from "./createContext-3UhoQl2A.mjs";
2
- import { r as logger_default, t as colors } from "./logger-gutcwWKE.mjs";
3
- import { p as VERSION } from "./file-BAUXs16l.mjs";
4
- import { resolveWeappMcpConfig, startWeappViteMcpServer } from "./mcp.mjs";
1
+ import { S as isPathInside, _ as DEFAULT_MP_PLATFORM, b as resolveMiniPlatform, c as createSharedBuildConfig, d as resolveWeappConfigFile, f as checkRuntime, g as createCjsConfigLoadError, h as parseCommentJson, l as SHARED_CHUNK_VIRTUAL_PREFIX, m as loadViteConfigFile, n as syncProjectSupportFiles, p as getProjectConfigFileName, r as syncManagedTsconfigBootstrapFiles, s as formatBytes, t as createCompilerContext, v as getDefaultIdeProjectRoot, x as shouldPassPlatformArgToIdeOpen, y as normalizeMiniPlatform } from "./createContext-BolxgJjC.mjs";
2
+ import { r as logger_default, t as colors } from "./logger-CgxdNjvb.mjs";
3
+ import { m as VERSION } from "./file-Ej-4GoYg.mjs";
4
+ import { a as resolveWeappMcpConfig, o as startWeappViteMcpServer } from "./mcp-DRlj32v4.mjs";
5
5
  import { createRequire } from "node:module";
6
- import { defu } from "@weapp-core/shared";
6
+ import { defu, fs } from "@weapp-core/shared";
7
7
  import path, { posix } from "pathe";
8
- import fs from "fs-extra";
9
8
  import process from "node:process";
10
9
  import fs$1 from "node:fs/promises";
11
10
  import { build, createServer } from "vite";
@@ -15,10 +14,11 @@ import fs$2 from "node:fs";
15
14
  import { cac } from "cac";
16
15
  import { resolveCommand } from "package-manager-detector/commands";
17
16
  import { promisify } from "node:util";
18
- import { formatRetryHotkeyPrompt, formatWechatIdeLoginRequiredError, getConfig, isWeappIdeTopLevelCommand, isWechatIdeLoginRequiredError, parse, startForwardConsole, waitForRetryKeypress } from "weapp-ide-cli";
17
+ import { closeSharedMiniProgram, connectOpenedAutomator, formatRetryHotkeyPrompt, formatWechatIdeLoginRequiredError, getConfig, isWeappIdeTopLevelCommand, isWechatIdeLoginRequiredError, launchAutomator, parse, startForwardConsole, takeScreenshot, waitForRetryKeypress } from "weapp-ide-cli";
19
18
  import { generateJs, generateJson, generateWxml, generateWxss } from "@weapp-core/schematics";
20
19
  import { determineAgent } from "@vercel/detect-agent";
21
20
  import { initConfig } from "@weapp-core/init";
21
+ import { emitKeypressEvents } from "node:readline";
22
22
  //#region src/analyze/subpackages/classifier.ts
23
23
  const VIRTUAL_MODULE_INDICATOR = "\0";
24
24
  const VIRTUAL_PREFIX = `${SHARED_CHUNK_VIRTUAL_PREFIX}/`;
@@ -857,6 +857,14 @@ function registerAnalyzeCommand(cli) {
857
857
  });
858
858
  }
859
859
  //#endregion
860
+ //#region src/cli/formatDuration.ts
861
+ /**
862
+ * 将毫秒耗时格式化为适合 CLI 展示的文本。
863
+ */
864
+ function formatDuration(durationMs) {
865
+ return `${durationMs}ms`;
866
+ }
867
+ //#endregion
860
868
  //#region src/cli/logBuildAppFinish.ts
861
869
  let logBuildAppFinishOnlyShowOnce = false;
862
870
  function collectServerUrls(webServer) {
@@ -942,6 +950,27 @@ function logBuildPackageSizeReport(options) {
942
950
  //#endregion
943
951
  //#region src/cli/openIde.ts
944
952
  const execFileAsync = promisify(execFile);
953
+ async function openWechatIdeByAutomator(projectPath) {
954
+ (await launchAutomator({
955
+ projectPath,
956
+ trustProject: true
957
+ })).disconnect();
958
+ }
959
+ /**
960
+ * @description 若当前项目已在微信开发者工具中打开且自动化可连通,则直接复用现有会话,避免重复拉起 IDE。
961
+ */
962
+ async function tryReuseOpenedWechatIde(projectPath) {
963
+ try {
964
+ (await connectOpenedAutomator({
965
+ projectPath,
966
+ timeout: 3e3
967
+ })).disconnect();
968
+ logger_default.info("目标项目已在微信开发者工具中打开,跳过重复打开。");
969
+ return true;
970
+ } catch {
971
+ return false;
972
+ }
973
+ }
945
974
  /**
946
975
  * @description 执行 IDE 打开流程,并在登录失效时允许按键重试。
947
976
  */
@@ -1001,7 +1030,15 @@ function resolveIdeProjectPath(mpDistRoot) {
1001
1030
  function resolveIdeProjectRoot(mpDistRoot, cwd) {
1002
1031
  return resolveIdeProjectPath(mpDistRoot) ?? cwd;
1003
1032
  }
1004
- async function openIde(platform, projectPath) {
1033
+ async function openIde(platform, projectPath, options = {}) {
1034
+ if (platform === "weapp" && projectPath && options.trustProject !== false) try {
1035
+ if (await tryReuseOpenedWechatIde(projectPath)) return;
1036
+ await openWechatIdeByAutomator(projectPath);
1037
+ return;
1038
+ } catch (error) {
1039
+ logger_default.warn("通过 automator 启动微信开发者工具并自动信任项目失败,回退到普通 open 流程。");
1040
+ logger_default.error(error);
1041
+ }
1005
1042
  const argv = ["open", "-p"];
1006
1043
  if (projectPath) argv.push(projectPath);
1007
1044
  if (shouldPassPlatformArgToIdeOpen(platform)) argv.push("--platform", platform);
@@ -1085,7 +1122,7 @@ function emitDashboardEvents$1(handle, events) {
1085
1122
  handle?.emitRuntimeEvents(events);
1086
1123
  }
1087
1124
  function registerBuildCommand(cli) {
1088
- cli.command("build [root]", "build for production").option("--target <target>", `[string] transpile target (default: 'modules')`).option("--outDir <dir>", `[string] output directory (default: dist)`).option("-p, --platform <platform>", `[string] target platform (weapp | h5 | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--sourcemap [output]", `[boolean | "inline" | "hidden"] output source maps for build (default: false)`).option("--minify [minifier]", "[boolean | \"terser\" | \"esbuild\"] enable/disable minification, or specify minifier to use (default: esbuild)").option("--emptyOutDir", `[boolean] force empty outDir when it's outside of root`).option("-w, --watch", `[boolean] rebuilds when modules have changed on disk`).option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--analyze", `[boolean] 输出分包分析仪表盘`, { default: false }).action(async (root, options) => {
1125
+ cli.command("build [root]", "build for production").option("--target <target>", `[string] transpile target (default: 'modules')`).option("--outDir <dir>", `[string] output directory (default: dist)`).option("-p, --platform <platform>", `[string] target platform (weapp | h5 | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--sourcemap [output]", `[boolean | "inline" | "hidden"] output source maps for build (default: false)`).option("--minify [minifier]", "[boolean | \"terser\" | \"esbuild\"] enable/disable minification, or specify minifier to use (default: esbuild)").option("--emptyOutDir", `[boolean] force empty outDir when it's outside of root`).option("-w, --watch", `[boolean] rebuilds when modules have changed on disk`).option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--analyze", `[boolean] 输出分包分析仪表盘`, { default: false }).action(async (root, options) => {
1089
1126
  filterDuplicateOptions(options);
1090
1127
  const configFile = resolveConfigFile(options);
1091
1128
  const targets = resolveRuntimeTargets(options);
@@ -1105,6 +1142,8 @@ function registerBuildCommand(cli) {
1105
1142
  if (targets.runMini) {
1106
1143
  const miniBuildStartedAt = Date.now();
1107
1144
  const output = await buildService.build(options);
1145
+ const miniBuildDurationMs = Date.now() - miniBuildStartedAt;
1146
+ logger_default.success(`小程序构建完成,耗时:${colors.green(formatDuration(miniBuildDurationMs))}`);
1108
1147
  if (!Array.isArray(output) && "output" in output) logBuildPackageSizeReport({
1109
1148
  output,
1110
1149
  subPackageMap: ctx.scanService?.subPackageMap,
@@ -1122,7 +1161,7 @@ function registerBuildCommand(cli) {
1122
1161
  level: "success",
1123
1162
  title: "mini build completed",
1124
1163
  detail: `生产构建已完成,当前 analyze 结果包含 ${analyzeResult.packages.length} 个包。`,
1125
- durationMs: Date.now() - miniBuildStartedAt,
1164
+ durationMs: miniBuildDurationMs,
1126
1165
  tags: ["build", "mini"]
1127
1166
  }]);
1128
1167
  }
@@ -1132,13 +1171,14 @@ function registerBuildCommand(cli) {
1132
1171
  const webBuildStartedAt = Date.now();
1133
1172
  try {
1134
1173
  await webService?.build();
1135
- logger_default.success(`Web 构建完成,输出目录:${colors.green(configService.relativeCwd(webConfig.outDir))}`);
1174
+ const webBuildDurationMs = Date.now() - webBuildStartedAt;
1175
+ logger_default.success(`Web 构建完成,输出目录:${colors.green(configService.relativeCwd(webConfig.outDir))},耗时:${colors.green(formatDuration(webBuildDurationMs))}`);
1136
1176
  emitDashboardEvents$1(analyzeHandle, [{
1137
1177
  kind: "build",
1138
1178
  level: "success",
1139
1179
  title: "web build completed",
1140
1180
  detail: `Web 构建已完成,输出目录 ${configService.relativeCwd(webConfig.outDir)}。`,
1141
- durationMs: Date.now() - webBuildStartedAt,
1181
+ durationMs: webBuildDurationMs,
1142
1182
  tags: ["build", "web"]
1143
1183
  }]);
1144
1184
  } catch (error) {
@@ -1163,7 +1203,7 @@ function registerBuildCommand(cli) {
1163
1203
  detail: "构建完成后准备打开 IDE 项目。",
1164
1204
  tags: ["ide", "open"]
1165
1205
  }]);
1166
- await openIde(configService.platform, resolveIdeProjectPath(configService.mpDistRoot));
1206
+ await openIde(configService.platform, resolveIdeProjectPath(configService.mpDistRoot), { trustProject: options.trustProject });
1167
1207
  }
1168
1208
  if (analyzeHandle) await analyzeHandle.waitForExit();
1169
1209
  ctx.watcherService?.closeAll();
@@ -1486,12 +1526,25 @@ function sleep(ms) {
1486
1526
  }
1487
1527
  //#endregion
1488
1528
  //#region src/cli/commands/ide.ts
1489
- /**
1490
- * @description 注册 IDE 相关子命令。
1491
- */
1492
- function registerIdeCommand(cli) {
1493
- cli.command("ide [action] [root]", "forward Wechat DevTools console logs to terminal").option("-o, --open", "[boolean] open ide before attaching log bridge").option("-p, --platform <platform>", "[string] target platform (weapp | h5)").option("--project-config <path>", "[string] project config path (miniprogram only)").action(async (action, root, options) => {
1494
- await runIdeCommand(action, root, options);
1529
+ async function waitForTermination(cleanup) {
1530
+ await new Promise((resolve) => {
1531
+ const signals = ["SIGINT", "SIGTERM"];
1532
+ let cleaning = false;
1533
+ const handlers = /* @__PURE__ */ new Map();
1534
+ const teardown = async () => {
1535
+ if (cleaning) return;
1536
+ cleaning = true;
1537
+ for (const signal of signals) process.off(signal, handlers.get(signal));
1538
+ await cleanup();
1539
+ resolve();
1540
+ };
1541
+ for (const signal of signals) {
1542
+ const handler = () => {
1543
+ teardown();
1544
+ };
1545
+ handlers.set(signal, handler);
1546
+ process.on(signal, handler);
1547
+ }
1495
1548
  });
1496
1549
  }
1497
1550
  /**
@@ -1511,7 +1564,7 @@ async function runIdeCommand(action, root, options) {
1511
1564
  });
1512
1565
  if (resolved.platform !== "weapp") throw new Error("`weapp-vite ide logs` 当前仅支持微信小程序平台。");
1513
1566
  if (!resolved.projectPath) throw new Error("无法解析微信开发者工具项目目录,请显式传入 root 或检查 project.config.json。");
1514
- if (options.open) await openIde(resolved.platform, resolved.projectPath);
1567
+ if (options.open) await openIde(resolved.platform, resolved.projectPath, { trustProject: options.trustProject });
1515
1568
  const forwardConsoleOptions = await resolveForwardConsoleOptions({
1516
1569
  ...resolved.weappViteConfig,
1517
1570
  forwardConsole: resolved.weappViteConfig?.forwardConsole === false ? { enabled: true } : {
@@ -1531,25 +1584,12 @@ async function runIdeCommand(action, root, options) {
1531
1584
  await session.close();
1532
1585
  });
1533
1586
  }
1534
- async function waitForTermination(cleanup) {
1535
- await new Promise((resolve) => {
1536
- const signals = ["SIGINT", "SIGTERM"];
1537
- let cleaning = false;
1538
- const teardown = async () => {
1539
- if (cleaning) return;
1540
- cleaning = true;
1541
- for (const signal of signals) process.off(signal, handlers.get(signal));
1542
- await cleanup();
1543
- resolve();
1544
- };
1545
- const handlers = /* @__PURE__ */ new Map();
1546
- for (const signal of signals) {
1547
- const handler = () => {
1548
- teardown();
1549
- };
1550
- handlers.set(signal, handler);
1551
- process.on(signal, handler);
1552
- }
1587
+ /**
1588
+ * @description 注册 IDE 相关子命令。
1589
+ */
1590
+ function registerIdeCommand(cli) {
1591
+ cli.command("ide [action] [root]", "forward Wechat DevTools console logs to terminal").option("-o, --open", "[boolean] open ide before attaching log bridge").option("-p, --platform <platform>", "[string] target platform (weapp | h5)").option("--project-config <path>", "[string] project config path (miniprogram only)").option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).action(async (action, root, options) => {
1592
+ await runIdeCommand(action, root, options);
1553
1593
  });
1554
1594
  }
1555
1595
  //#endregion
@@ -1599,7 +1639,7 @@ function registerNpmCommand(cli) {
1599
1639
  //#endregion
1600
1640
  //#region src/cli/commands/open.ts
1601
1641
  function registerOpenCommand(cli) {
1602
- cli.command("open [root]").option("-p, --platform <platform>", `[string] target platform (weapp | h5)`).action(async (root, options) => {
1642
+ cli.command("open [root]").option("-p, --platform <platform>", `[string] target platform (weapp | h5)`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).action(async (root, options) => {
1603
1643
  filterDuplicateOptions(options);
1604
1644
  const configFile = resolveConfigFile(options);
1605
1645
  const targets = resolveRuntimeTargets(options);
@@ -1610,7 +1650,7 @@ function registerOpenCommand(cli) {
1610
1650
  projectPath: root,
1611
1651
  cliPlatform: targets.rawPlatform
1612
1652
  });
1613
- await openIde(platform, projectPath ?? resolveIdeProjectRoot(mpDistRoot, process.cwd()));
1653
+ await openIde(platform, projectPath ?? resolveIdeProjectRoot(mpDistRoot, process.cwd()), { trustProject: options.trustProject });
1614
1654
  });
1615
1655
  }
1616
1656
  //#endregion
@@ -1641,6 +1681,339 @@ function registerPrepareCommand(cli) {
1641
1681
  });
1642
1682
  }
1643
1683
  //#endregion
1684
+ //#region package.json
1685
+ var version = "6.14.0";
1686
+ //#endregion
1687
+ //#region src/cli/devHotkeys.ts
1688
+ const DEV_SCREENSHOT_DIR = ".tmp/weapp-vite-dev-screenshots";
1689
+ const DEFAULT_SCREENSHOT_TIMEOUT = 3e4;
1690
+ const REG_PENDING_PREFIX = /^正在/;
1691
+ const FULLWIDTH_ASCII_START = 65281;
1692
+ const FULLWIDTH_ASCII_END = 65374;
1693
+ const FULLWIDTH_ASCII_OFFSET = 65248;
1694
+ const HOTKEY_DEDUP_WINDOW_MS = 32;
1695
+ function formatMcpUrl(host, port, endpoint) {
1696
+ return `http://${host}:${port}${endpoint}`;
1697
+ }
1698
+ function forwardSigint() {
1699
+ process.kill(process.pid, "SIGINT");
1700
+ }
1701
+ function forwardSigtstp() {
1702
+ process.kill(process.pid, "SIGTSTP");
1703
+ }
1704
+ function formatProjectLabel(cwd) {
1705
+ return path.basename(cwd) || cwd;
1706
+ }
1707
+ function formatMcpStatus(state) {
1708
+ if (!state.mcpEnabled) return "已禁用";
1709
+ return state.mcpRunning ? "运行中" : "未启动";
1710
+ }
1711
+ function formatFooterLine(state) {
1712
+ if (state.currentAction) return `执行中 ${state.currentAction}`;
1713
+ return "就绪 等待操作...";
1714
+ }
1715
+ /**
1716
+ * @description 生成带状态的开发态快捷键帮助文本。
1717
+ */
1718
+ function formatDevHotkeyHelpWithState(state) {
1719
+ const key = (value) => colors.bold(colors.green(value));
1720
+ const actionRows = [{
1721
+ key: key("s"),
1722
+ description: "截图当前页面并保存到本地"
1723
+ }, {
1724
+ key: key("m"),
1725
+ description: "开关 MCP 服务"
1726
+ }];
1727
+ const processRows = [
1728
+ {
1729
+ key: key("q"),
1730
+ description: "退出当前 dev"
1731
+ },
1732
+ {
1733
+ key: key("Ctrl+C"),
1734
+ description: "强制中断当前 dev"
1735
+ },
1736
+ {
1737
+ key: key("Ctrl+Z"),
1738
+ description: "暂时挂起当前 dev,恢复终端控制"
1739
+ }
1740
+ ];
1741
+ const helpRows = [{
1742
+ key: key("h"),
1743
+ description: "重新显示这份帮助"
1744
+ }];
1745
+ const keyColumnWidth = Math.max(...[
1746
+ ...actionRows,
1747
+ ...processRows,
1748
+ ...helpRows
1749
+ ].map((row) => row.key.length));
1750
+ const formatRows = (rows) => rows.map(({ key, description }) => `按 ${key.padEnd(keyColumnWidth)} ${description}`);
1751
+ return [
1752
+ `${colors.bold(colors.green("DEV"))} weapp-vite v${version} ${state.projectLabel ?? "weapp"}`,
1753
+ "",
1754
+ "快捷命令",
1755
+ ...formatRows(actionRows),
1756
+ "",
1757
+ "进程控制",
1758
+ ...formatRows(processRows),
1759
+ "",
1760
+ "帮助",
1761
+ ...formatRows(helpRows),
1762
+ "",
1763
+ `当前状态:${state.currentAction ?? "等待操作"} / MCP ${formatMcpStatus(state)}`
1764
+ ].join("\n");
1765
+ }
1766
+ /**
1767
+ * @description 生成带状态的开发态快捷键简短提示。
1768
+ */
1769
+ function formatDevHotkeyHintWithState(state) {
1770
+ const key = (value) => colors.bold(colors.green(value));
1771
+ if (state.currentAction) return `${formatFooterLine(state)},按 ${key("h")} 显示帮助,按 ${key("q")} 退出`;
1772
+ return `开发快捷键已就绪,按 ${key("h")} 显示帮助,按 ${key("q")} 退出`;
1773
+ }
1774
+ /**
1775
+ * @description 生成开发态截图输出路径。
1776
+ */
1777
+ function resolveDevScreenshotOutputPath(cwd, now = /* @__PURE__ */ new Date()) {
1778
+ const stamp = now.toISOString().replace(/[:.]/g, "-");
1779
+ return path.join(cwd, DEV_SCREENSHOT_DIR, `screenshot-${stamp}.png`);
1780
+ }
1781
+ function formatLogPath(cwd, targetPath) {
1782
+ const relativePath = path.relative(cwd, targetPath);
1783
+ if (!relativePath || relativePath.startsWith("..")) return targetPath;
1784
+ return relativePath;
1785
+ }
1786
+ function formatResolvedScreenshotPath(cwd, fallbackPath, result) {
1787
+ return formatLogPath(cwd, result.path ?? fallbackPath);
1788
+ }
1789
+ function normalizeInputChar(input) {
1790
+ if (input.length !== 1) return input;
1791
+ const codePoint = input.codePointAt(0);
1792
+ if (!codePoint) return input;
1793
+ if (codePoint >= FULLWIDTH_ASCII_START && codePoint <= FULLWIDTH_ASCII_END) return String.fromCodePoint(codePoint - FULLWIDTH_ASCII_OFFSET);
1794
+ return input;
1795
+ }
1796
+ /**
1797
+ * @description 执行当前页面截图并输出结果日志。
1798
+ */
1799
+ async function runScreenshotAction(options) {
1800
+ const outputPath = resolveDevScreenshotOutputPath(options.cwd);
1801
+ await fs$1.mkdir(path.dirname(outputPath), { recursive: true });
1802
+ logger_default.info(`[dev action] 正在截图当前页面,输出到 ${colors.cyan(formatLogPath(options.cwd, outputPath))}`);
1803
+ const result = await takeScreenshot({
1804
+ fullPage: true,
1805
+ projectPath: options.projectPath,
1806
+ sharedSession: true,
1807
+ outputPath,
1808
+ timeout: DEFAULT_SCREENSHOT_TIMEOUT
1809
+ });
1810
+ const resolvedPath = formatResolvedScreenshotPath(options.cwd, outputPath, result);
1811
+ logger_default.success(`[dev action] 当前页面截图完成:${colors.cyan(resolvedPath)}`);
1812
+ return resolvedPath;
1813
+ }
1814
+ /**
1815
+ * @description 在开发态启动终端快捷键,并将动作输出到本地日志。
1816
+ */
1817
+ function startDevHotkeys(options) {
1818
+ if (options.platform !== "weapp" || !process.stdin.isTTY) return;
1819
+ emitKeypressEvents(process.stdin);
1820
+ const hasSetRawMode = typeof process.stdin.setRawMode === "function";
1821
+ if (hasSetRawMode) process.stdin.setRawMode(true);
1822
+ process.stdin.resume();
1823
+ let closed = false;
1824
+ let running = false;
1825
+ let mcpHandle;
1826
+ let onData;
1827
+ let onKeypress;
1828
+ let onSigcont;
1829
+ let currentAction;
1830
+ let lastAction;
1831
+ let lastRenderedPanel = "";
1832
+ const recentInputs = /* @__PURE__ */ new Map();
1833
+ const resolvedMcp = resolveWeappMcpConfig(options.mcpConfig);
1834
+ const getState = () => ({
1835
+ currentAction,
1836
+ lastAction,
1837
+ mcpEnabled: resolvedMcp.enabled,
1838
+ mcpRunning: Boolean(mcpHandle?.close),
1839
+ projectLabel: formatProjectLabel(options.cwd)
1840
+ });
1841
+ const detachTerminal = () => {
1842
+ if (hasSetRawMode) process.stdin.setRawMode(false);
1843
+ process.stdin.pause();
1844
+ };
1845
+ const attachTerminal = () => {
1846
+ if (closed) return;
1847
+ if (hasSetRawMode) process.stdin.setRawMode(true);
1848
+ process.stdin.resume();
1849
+ };
1850
+ const ensureTerminalActive = () => {
1851
+ if (closed) return;
1852
+ if (hasSetRawMode) process.stdin.setRawMode(true);
1853
+ process.stdin.resume();
1854
+ };
1855
+ const close = () => {
1856
+ if (closed) return;
1857
+ closed = true;
1858
+ if (onData) process.stdin.off("data", onData);
1859
+ if (onKeypress) process.stdin.off("keypress", onKeypress);
1860
+ if (onSigcont) process.off("SIGCONT", onSigcont);
1861
+ detachTerminal();
1862
+ if (mcpHandle?.close) mcpHandle.close().catch((error) => {
1863
+ logger_default.warn(`[dev action] MCP 服务关闭失败:${error instanceof Error ? error.message : String(error)}`);
1864
+ });
1865
+ closeSharedMiniProgram(options.projectPath).catch((error) => {
1866
+ logger_default.warn(`[dev action] DevTools 会话关闭失败:${error instanceof Error ? error.message : String(error)}`);
1867
+ });
1868
+ };
1869
+ const printPanel = (message, force = false) => {
1870
+ if (!force && message === lastRenderedPanel) return;
1871
+ lastRenderedPanel = message;
1872
+ logger_default.info(message);
1873
+ };
1874
+ const printHelp = () => {
1875
+ printPanel(formatDevHotkeyHelpWithState(getState()), true);
1876
+ ensureTerminalActive();
1877
+ };
1878
+ const printHint = () => {
1879
+ printPanel(formatDevHotkeyHintWithState(getState()));
1880
+ ensureTerminalActive();
1881
+ };
1882
+ const restore = () => {
1883
+ if (closed) return;
1884
+ if (onData) process.stdin.off("data", onData);
1885
+ if (onKeypress) process.stdin.off("keypress", onKeypress);
1886
+ attachTerminal();
1887
+ if (onData) process.stdin.on("data", onData);
1888
+ if (onKeypress) process.stdin.on("keypress", onKeypress);
1889
+ printHint();
1890
+ };
1891
+ const suspend = () => {
1892
+ lastRenderedPanel = "";
1893
+ detachTerminal();
1894
+ forwardSigtstp();
1895
+ };
1896
+ const toggleMcp = async () => {
1897
+ if (!resolvedMcp.enabled) {
1898
+ logger_default.warn("[dev action] MCP 已在配置中禁用,跳过切换。");
1899
+ return "MCP 已禁用";
1900
+ }
1901
+ if (mcpHandle?.close) {
1902
+ const url = formatMcpUrl(resolvedMcp.host, resolvedMcp.port, resolvedMcp.endpoint);
1903
+ logger_default.info(`[dev action] 正在关闭 MCP 服务:${colors.cyan(url)}`);
1904
+ await mcpHandle.close();
1905
+ mcpHandle = void 0;
1906
+ logger_default.success(`[dev action] MCP 服务已关闭:${colors.cyan(url)}`);
1907
+ return `MCP 已关闭 (${url})`;
1908
+ }
1909
+ const url = formatMcpUrl(resolvedMcp.host, resolvedMcp.port, resolvedMcp.endpoint);
1910
+ logger_default.info(`[dev action] 正在启动 MCP 服务:${colors.cyan(url)}`);
1911
+ mcpHandle = await startWeappViteMcpServer({
1912
+ endpoint: resolvedMcp.endpoint,
1913
+ host: resolvedMcp.host,
1914
+ port: resolvedMcp.port,
1915
+ transport: "streamable-http",
1916
+ unref: false,
1917
+ workspaceRoot: options.cwd
1918
+ });
1919
+ logger_default.success(`[dev action] MCP 服务已启动:${colors.cyan(url)}`);
1920
+ return `MCP 已启动 (${url})`;
1921
+ };
1922
+ const runAction = (label, pendingLabel, action) => {
1923
+ if (running) {
1924
+ const current = currentAction ?? "已有命令";
1925
+ logger_default.warn(`[dev action] 当前正在${current.replace(REG_PENDING_PREFIX, "")},请稍后再试。`);
1926
+ return;
1927
+ }
1928
+ running = true;
1929
+ currentAction = pendingLabel;
1930
+ printHint();
1931
+ action().then((summary) => {
1932
+ if (summary) lastAction = summary;
1933
+ }).catch((error) => {
1934
+ logger_default.error(`[dev action] ${label}失败:${error instanceof Error ? error.message : String(error)}`);
1935
+ }).finally(() => {
1936
+ running = false;
1937
+ currentAction = void 0;
1938
+ if (!closed) printHint();
1939
+ });
1940
+ };
1941
+ const handleInput = (input) => {
1942
+ if (closed) return;
1943
+ const normalizedInput = normalizeInputChar(input);
1944
+ if (normalizedInput === "") {
1945
+ close();
1946
+ forwardSigint();
1947
+ return;
1948
+ }
1949
+ if (normalizedInput === "") {
1950
+ suspend();
1951
+ return;
1952
+ }
1953
+ const normalized = normalizedInput.toLowerCase();
1954
+ if (normalized === "q") {
1955
+ close();
1956
+ forwardSigint();
1957
+ return;
1958
+ }
1959
+ if (normalized === "h") {
1960
+ printHelp();
1961
+ return;
1962
+ }
1963
+ if (normalized === "s") {
1964
+ runAction("截图", "正在截图当前页面", async () => {
1965
+ return `截图已保存到 ${await runScreenshotAction(options)}`;
1966
+ });
1967
+ return;
1968
+ }
1969
+ if (normalized === "m") runAction("MCP 切换", mcpHandle?.close ? "正在关闭 MCP 服务" : "正在启动 MCP 服务", async () => {
1970
+ return await toggleMcp();
1971
+ });
1972
+ };
1973
+ const handleInputOnce = (input, source) => {
1974
+ const normalizedInput = normalizeInputChar(input);
1975
+ const now = Date.now();
1976
+ for (const [token, timestamp] of recentInputs) if (now - timestamp > HOTKEY_DEDUP_WINDOW_MS) recentInputs.delete(token);
1977
+ const dedupKey = normalizedInput;
1978
+ const recentEntry = recentInputs.get(dedupKey);
1979
+ if (recentEntry !== void 0) {
1980
+ const [recentSource, recentTimestamp] = recentEntry.split(":");
1981
+ if (recentSource !== source && now - Number(recentTimestamp) <= HOTKEY_DEDUP_WINDOW_MS) return;
1982
+ }
1983
+ recentInputs.set(dedupKey, `${source}:${now}`);
1984
+ handleInput(normalizedInput);
1985
+ };
1986
+ onData = (chunk) => {
1987
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
1988
+ for (const char of text) handleInputOnce(char, "data");
1989
+ };
1990
+ process.stdin.on("data", onData);
1991
+ onKeypress = (str, key) => {
1992
+ if (key?.ctrl && key.name === "c") {
1993
+ handleInputOnce("", "keypress");
1994
+ return;
1995
+ }
1996
+ if (key?.ctrl && key.name === "z") {
1997
+ handleInputOnce("", "keypress");
1998
+ return;
1999
+ }
2000
+ if (typeof str === "string" && str) handleInputOnce(str, "keypress");
2001
+ };
2002
+ process.stdin.on("keypress", onKeypress);
2003
+ onSigcont = () => {
2004
+ restore();
2005
+ };
2006
+ process.on("SIGCONT", onSigcont);
2007
+ if (!options.silentStartupHint) printHint();
2008
+ if (resolvedMcp.enabled && resolvedMcp.autoStart) runAction("MCP 自动启动", "正在启动 MCP 服务", async () => {
2009
+ await toggleMcp();
2010
+ });
2011
+ return {
2012
+ close,
2013
+ restore
2014
+ };
2015
+ }
2016
+ //#endregion
1644
2017
  //#region src/cli/commands/serve.ts
1645
2018
  function emitDashboardEvents(handle, events) {
1646
2019
  handle?.emitRuntimeEvents(events);
@@ -1656,6 +2029,17 @@ const REG_DIST_POSIX_SEP = /\\/g;
1656
2029
  function hasAnalyzeData(result) {
1657
2030
  return result.packages.length > 0 || result.modules.length > 0;
1658
2031
  }
2032
+ function waitForServeShutdownSignal() {
2033
+ return new Promise((resolve) => {
2034
+ const onSignal = () => {
2035
+ process.off("SIGINT", onSignal);
2036
+ process.off("SIGTERM", onSignal);
2037
+ resolve();
2038
+ };
2039
+ process.on("SIGINT", onSignal);
2040
+ process.on("SIGTERM", onSignal);
2041
+ });
2042
+ }
1659
2043
  async function collectOutputFiles(root) {
1660
2044
  const entries = await fs$1.readdir(root, { withFileTypes: true });
1661
2045
  const files = [];
@@ -1730,7 +2114,7 @@ async function analyzeUiFallback(ctx) {
1730
2114
  };
1731
2115
  }
1732
2116
  function registerServeCommand(cli) {
1733
- cli.command("[root]", "start dev server").alias("serve").alias("dev").option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("-p, --platform <platform>", `[string] target platform (weapp | h5 | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--host [host]", `[string] web dev server host`).option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--analyze", `[boolean] 启动分包分析仪表盘 (实验特性)`, { default: false }).action(async (root, options) => {
2117
+ cli.command("[root]", "start dev server").alias("serve").alias("dev").option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("-p, --platform <platform>", `[string] target platform (weapp | h5 | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).option("--host [host]", `[string] web dev server host`).option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--analyze", `[boolean] 启动分包分析仪表盘 (实验特性)`, { default: false }).action(async (root, options) => {
1734
2118
  filterDuplicateOptions(options);
1735
2119
  const configFile = resolveConfigFile(options);
1736
2120
  const targets = resolveRuntimeTargets(options);
@@ -1787,162 +2171,181 @@ function registerServeCommand(cli) {
1787
2171
  const enableAnalyze = Boolean(isUiEnabled(options) && targets.runMini);
1788
2172
  let analyzeHandle;
1789
2173
  let analyzeRunId = 0;
1790
- const runAnalyze = async () => {
1791
- const startedAt = Date.now();
1792
- try {
1793
- const result = await analyzeSubpackages(await createCompilerContext({
1794
- key: `serve-ui-analyze:${process.pid}:${++analyzeRunId}`,
1795
- cwd: configService.cwd,
1796
- mode: configService.mode,
1797
- isDev: false,
1798
- configFile,
1799
- inlineConfig: createInlineConfig(targets.mpPlatform),
1800
- cliPlatform: targets.rawPlatform,
1801
- projectConfigPath: options.projectConfig,
1802
- syncSupportFiles: false
1803
- }));
1804
- if (hasAnalyzeData(result)) return {
1805
- result,
1806
- durationMs: Date.now() - startedAt,
1807
- mode: "full"
1808
- };
1809
- } catch (error) {
1810
- const message = error instanceof Error ? error.message : String(error);
1811
- logger_default.warn(`[ui] 完整分析失败,已回退到 dist 文件扫描:${message}`);
2174
+ const devHotkeysSession = targets.runMini ? startDevHotkeys({
2175
+ cwd: configService.cwd,
2176
+ mcpConfig: configService.weappViteConfig?.weapp?.mcp,
2177
+ platform: configService.platform,
2178
+ projectPath: resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd),
2179
+ silentStartupHint: true
2180
+ }) : void 0;
2181
+ try {
2182
+ const runAnalyze = async () => {
2183
+ const startedAt = Date.now();
2184
+ try {
2185
+ const result = await analyzeSubpackages(await createCompilerContext({
2186
+ key: `serve-ui-analyze:${process.pid}:${++analyzeRunId}`,
2187
+ cwd: configService.cwd,
2188
+ mode: configService.mode,
2189
+ isDev: false,
2190
+ configFile,
2191
+ inlineConfig: createInlineConfig(targets.mpPlatform),
2192
+ cliPlatform: targets.rawPlatform,
2193
+ projectConfigPath: options.projectConfig,
2194
+ syncSupportFiles: false
2195
+ }));
2196
+ if (hasAnalyzeData(result)) return {
2197
+ result,
2198
+ durationMs: Date.now() - startedAt,
2199
+ mode: "full"
2200
+ };
2201
+ } catch (error) {
2202
+ const message = error instanceof Error ? error.message : String(error);
2203
+ logger_default.warn(`[ui] 完整分析失败,已回退到 dist 文件扫描:${message}`);
2204
+ return {
2205
+ result: await analyzeUiFallback(ctx),
2206
+ durationMs: Date.now() - startedAt,
2207
+ mode: "fallback",
2208
+ fallbackReason: message
2209
+ };
2210
+ }
1812
2211
  return {
1813
2212
  result: await analyzeUiFallback(ctx),
1814
2213
  durationMs: Date.now() - startedAt,
1815
2214
  mode: "fallback",
1816
- fallbackReason: message
2215
+ fallbackReason: "完整分析结果为空,已回退到 dist 文件扫描。"
1817
2216
  };
1818
- }
1819
- return {
1820
- result: await analyzeUiFallback(ctx),
1821
- durationMs: Date.now() - startedAt,
1822
- mode: "fallback",
1823
- fallbackReason: "完整分析结果为空,已回退到 dist 文件扫描。"
1824
2217
  };
1825
- };
1826
- const triggerAnalyzeUpdate = async (reason = "watch") => {
1827
- if (!analyzeHandle) return;
1828
- emitDashboardEvents(analyzeHandle, [{
1829
- kind: reason === "watch" ? "hmr" : "build",
1830
- level: "info",
1831
- title: reason === "watch" ? "analyze refresh started" : "initial analyze started",
1832
- detail: reason === "watch" ? "检测到新的构建结束事件,开始刷新 analyze 面板。" : "开发态 UI 已启动,开始生成第一份 analyze 结果。",
1833
- tags: reason === "watch" ? ["watch", "analyze"] : ["initial", "analyze"]
1834
- }]);
1835
- const next = await runAnalyze();
1836
- if (next.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
1837
- kind: "diagnostic",
1838
- level: "warning",
1839
- title: "analyze fallback enabled",
1840
- detail: next.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。",
1841
- durationMs: next.durationMs,
1842
- tags: ["analyze", "fallback"]
1843
- }]);
1844
- await analyzeHandle.update(next.result);
1845
- emitDashboardEvents(analyzeHandle, [{
1846
- kind: next.mode === "fallback" ? "diagnostic" : "build",
1847
- level: next.mode === "fallback" ? "warning" : "success",
1848
- title: reason === "watch" ? "analyze refresh completed" : "initial analyze completed",
1849
- detail: next.mode === "fallback" ? `analyze 已回退到 dist 扫描,当前包含 ${next.result.packages.length} 个包。` : `analyze 已刷新完成,当前包含 ${next.result.packages.length} 个包与 ${next.result.modules.length} 个模块。`,
1850
- durationMs: next.durationMs,
1851
- tags: next.mode === "fallback" ? ["analyze", "fallback"] : ["analyze", reason === "watch" ? "refresh" : "initial"]
1852
- }]);
1853
- };
1854
- if (targets.runMini) {
1855
- const buildResult = await buildService.build(options);
1856
- if (enableAnalyze) {
1857
- const initialAnalyze = await runAnalyze();
1858
- analyzeHandle = await startAnalyzeDashboard(initialAnalyze.result, {
1859
- watch: true,
1860
- cwd: configService.cwd,
1861
- packageManagerAgent: configService.packageManager.agent,
1862
- silentStartupLog: true
1863
- }) ?? void 0;
2218
+ const triggerAnalyzeUpdate = async (reason = "watch") => {
2219
+ if (!analyzeHandle) return;
1864
2220
  emitDashboardEvents(analyzeHandle, [{
1865
- kind: "command",
1866
- level: "success",
1867
- title: "dev ui session ready",
1868
- detail: `开发态分析面板已启动,当前包含 ${initialAnalyze.result.packages.length} 个包。`,
1869
- durationMs: initialAnalyze.durationMs,
1870
- tags: ["dev", "ui"]
2221
+ kind: reason === "watch" ? "hmr" : "build",
2222
+ level: "info",
2223
+ title: reason === "watch" ? "analyze refresh started" : "initial analyze started",
2224
+ detail: reason === "watch" ? "检测到新的构建结束事件,开始刷新 analyze 面板。" : "开发态 UI 已启动,开始生成第一份 analyze 结果。",
2225
+ tags: reason === "watch" ? ["watch", "analyze"] : ["initial", "analyze"]
1871
2226
  }]);
1872
- if (initialAnalyze.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
2227
+ const next = await runAnalyze();
2228
+ if (next.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
1873
2229
  kind: "diagnostic",
1874
2230
  level: "warning",
1875
- title: "initial analyze fallback enabled",
1876
- detail: initialAnalyze.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。",
1877
- durationMs: initialAnalyze.durationMs,
1878
- tags: [
1879
- "analyze",
1880
- "fallback",
1881
- "initial"
1882
- ]
2231
+ title: "analyze fallback enabled",
2232
+ detail: next.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。",
2233
+ durationMs: next.durationMs,
2234
+ tags: ["analyze", "fallback"]
1883
2235
  }]);
1884
- let updating = false;
1885
- if (analyzeHandle && buildResult && typeof buildResult.on === "function") buildResult.on("event", (event) => {
1886
- if (event.code !== "END" || updating) return;
1887
- updating = true;
1888
- triggerAnalyzeUpdate("watch").finally(() => {
1889
- updating = false;
2236
+ await analyzeHandle.update(next.result);
2237
+ emitDashboardEvents(analyzeHandle, [{
2238
+ kind: next.mode === "fallback" ? "diagnostic" : "build",
2239
+ level: next.mode === "fallback" ? "warning" : "success",
2240
+ title: reason === "watch" ? "analyze refresh completed" : "initial analyze completed",
2241
+ detail: next.mode === "fallback" ? `analyze 已回退到 dist 扫描,当前包含 ${next.result.packages.length} 个包。` : `analyze 已刷新完成,当前包含 ${next.result.packages.length} 个包与 ${next.result.modules.length} 个模块。`,
2242
+ durationMs: next.durationMs,
2243
+ tags: next.mode === "fallback" ? ["analyze", "fallback"] : ["analyze", reason === "watch" ? "refresh" : "initial"]
2244
+ }]);
2245
+ };
2246
+ if (targets.runMini) {
2247
+ const miniBuildStartedAt = Date.now();
2248
+ const buildResult = await buildService.build(options);
2249
+ logger_default.success(`小程序初次构建完成,耗时:${formatDuration(Date.now() - miniBuildStartedAt)}`);
2250
+ if (enableAnalyze) {
2251
+ const initialAnalyze = await runAnalyze();
2252
+ analyzeHandle = await startAnalyzeDashboard(initialAnalyze.result, {
2253
+ watch: true,
2254
+ cwd: configService.cwd,
2255
+ packageManagerAgent: configService.packageManager.agent,
2256
+ silentStartupLog: true
2257
+ }) ?? void 0;
2258
+ emitDashboardEvents(analyzeHandle, [{
2259
+ kind: "command",
2260
+ level: "success",
2261
+ title: "dev ui session ready",
2262
+ detail: `开发态分析面板已启动,当前包含 ${initialAnalyze.result.packages.length} 个包。`,
2263
+ durationMs: initialAnalyze.durationMs,
2264
+ tags: ["dev", "ui"]
2265
+ }]);
2266
+ if (initialAnalyze.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
2267
+ kind: "diagnostic",
2268
+ level: "warning",
2269
+ title: "initial analyze fallback enabled",
2270
+ detail: initialAnalyze.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。",
2271
+ durationMs: initialAnalyze.durationMs,
2272
+ tags: [
2273
+ "analyze",
2274
+ "fallback",
2275
+ "initial"
2276
+ ]
2277
+ }]);
2278
+ let updating = false;
2279
+ if (analyzeHandle && buildResult && typeof buildResult.on === "function") buildResult.on("event", (event) => {
2280
+ if (event.code !== "END" || updating) return;
2281
+ updating = true;
2282
+ triggerAnalyzeUpdate("watch").finally(() => {
2283
+ updating = false;
2284
+ });
1890
2285
  });
1891
- });
1892
- if (analyzeHandle) {
1893
- updating = true;
1894
- await triggerAnalyzeUpdate("initial");
1895
- updating = false;
2286
+ if (analyzeHandle) {
2287
+ updating = true;
2288
+ await triggerAnalyzeUpdate("initial");
2289
+ updating = false;
2290
+ }
1896
2291
  }
1897
2292
  }
1898
- }
1899
- let webServer;
1900
- if (targets.runWeb) {
1901
- const webServerStartedAt = Date.now();
1902
- try {
1903
- webServer = await webService?.startDevServer();
1904
- emitDashboardEvents(analyzeHandle, [{
1905
- kind: "system",
1906
- level: "success",
1907
- title: "web dev server started",
1908
- detail: "Web 开发服务器已启动,可与小程序调试 UI 并行工作。",
1909
- durationMs: Date.now() - webServerStartedAt,
1910
- tags: ["dev", "web"]
1911
- }]);
1912
- } catch (error) {
2293
+ let webServer;
2294
+ if (targets.runWeb) {
2295
+ const webServerStartedAt = Date.now();
2296
+ try {
2297
+ webServer = await webService?.startDevServer();
2298
+ logger_default.success(`Web 开发服务启动完成,耗时:${formatDuration(Date.now() - webServerStartedAt)}`);
2299
+ emitDashboardEvents(analyzeHandle, [{
2300
+ kind: "system",
2301
+ level: "success",
2302
+ title: "web dev server started",
2303
+ detail: "Web 开发服务器已启动,可与小程序调试 UI 并行工作。",
2304
+ durationMs: Date.now() - webServerStartedAt,
2305
+ tags: ["dev", "web"]
2306
+ }]);
2307
+ } catch (error) {
2308
+ emitDashboardEvents(analyzeHandle, [{
2309
+ kind: "diagnostic",
2310
+ level: "error",
2311
+ title: "web dev server failed",
2312
+ detail: error instanceof Error ? error.message : String(error),
2313
+ durationMs: Date.now() - webServerStartedAt,
2314
+ tags: ["dev", "web"]
2315
+ }]);
2316
+ logger_default.error(error);
2317
+ throw error;
2318
+ }
2319
+ }
2320
+ if (targets.runMini) {
2321
+ logBuildAppFinish(configService, webServer, {
2322
+ skipWeb: !targets.runWeb,
2323
+ uiUrls: analyzeHandle?.urls
2324
+ });
2325
+ devHotkeysSession?.restore();
2326
+ } else if (targets.runWeb) logBuildAppFinish(configService, webServer, { skipMini: true });
2327
+ if (options.open && targets.runMini) {
1913
2328
  emitDashboardEvents(analyzeHandle, [{
1914
- kind: "diagnostic",
1915
- level: "error",
1916
- title: "web dev server failed",
1917
- detail: error instanceof Error ? error.message : String(error),
1918
- durationMs: Date.now() - webServerStartedAt,
1919
- tags: ["dev", "web"]
2329
+ kind: "command",
2330
+ level: "info",
2331
+ title: "opening ide",
2332
+ detail: "开发服务已就绪,准备打开 IDE 项目。",
2333
+ tags: ["ide", "open"]
1920
2334
  }]);
1921
- logger_default.error(error);
1922
- throw error;
2335
+ if (!await maybeStartForwardConsole({
2336
+ platform: configService.platform,
2337
+ mpDistRoot: configService.mpDistRoot,
2338
+ cwd: configService.cwd,
2339
+ weappViteConfig: configService.weappViteConfig
2340
+ })) await openIde(configService.platform, resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd), { trustProject: options.trustProject });
2341
+ devHotkeysSession?.restore();
1923
2342
  }
2343
+ if (analyzeHandle) await analyzeHandle.waitForExit();
2344
+ else if (targets.runMini || targets.runWeb) await waitForServeShutdownSignal();
2345
+ } finally {
2346
+ devHotkeysSession?.close();
2347
+ ctx.watcherService?.closeAll();
1924
2348
  }
1925
- if (targets.runMini) logBuildAppFinish(configService, webServer, {
1926
- skipWeb: !targets.runWeb,
1927
- uiUrls: analyzeHandle?.urls
1928
- });
1929
- else if (targets.runWeb) logBuildAppFinish(configService, webServer, { skipMini: true });
1930
- if (options.open && targets.runMini) {
1931
- emitDashboardEvents(analyzeHandle, [{
1932
- kind: "command",
1933
- level: "info",
1934
- title: "opening ide",
1935
- detail: "开发服务已就绪,准备打开 IDE 项目。",
1936
- tags: ["ide", "open"]
1937
- }]);
1938
- if (!await maybeStartForwardConsole({
1939
- platform: configService.platform,
1940
- mpDistRoot: configService.mpDistRoot,
1941
- cwd: configService.cwd,
1942
- weappViteConfig: configService.weappViteConfig
1943
- })) await openIde(configService.platform, resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd));
1944
- }
1945
- if (analyzeHandle) await analyzeHandle.waitForExit();
1946
2349
  });
1947
2350
  }
1948
2351
  //#endregion
@@ -2031,13 +2434,12 @@ const SKIP_COMMANDS = new Set([
2031
2434
  "ide",
2032
2435
  "mcp"
2033
2436
  ]);
2034
- const DEV_COMMANDS = new Set(["dev", "serve"]);
2437
+ const REG_EADDRINUSE = /EADDRINUSE/;
2035
2438
  let started = false;
2036
2439
  function shouldAutoStartMcp(argv) {
2037
2440
  const command = argv[0];
2038
2441
  if (!command || command.startsWith("-")) return true;
2039
2442
  if (SKIP_COMMANDS.has(command)) return false;
2040
- if (DEV_COMMANDS.has(command)) return true;
2041
2443
  return command.includes("/") || command.includes("\\") || command.startsWith(".");
2042
2444
  }
2043
2445
  async function maybeAutoStartMcpServer(argv, cliOptions) {
@@ -2067,7 +2469,7 @@ async function maybeAutoStartMcpServer(argv, cliOptions) {
2067
2469
  logger_default.info(` ➜ ${colors.cyan(mcpUrl)}`);
2068
2470
  } catch (error) {
2069
2471
  const message = error instanceof Error ? error.message : String(error);
2070
- if (/EADDRINUSE/.test(message)) {
2472
+ if (REG_EADDRINUSE.test(message)) {
2071
2473
  logger_default.info(`[mcp] 端口 ${resolvedMcp.port} 已被占用,跳过自动启动。`);
2072
2474
  started = true;
2073
2475
  return;
@@ -2147,22 +2549,19 @@ function resolveManagedTsconfigBootstrapRoot(args) {
2147
2549
  return path.resolve(firstArg);
2148
2550
  }
2149
2551
  try {
2150
- Promise.resolve().then(async () => {
2151
- const args = process.argv.slice(2);
2152
- if (await tryRunIdeCommand(args)) return;
2552
+ const args = process.argv.slice(2);
2553
+ if (!await tryRunIdeCommand(args)) {
2153
2554
  const managedTsconfigBootstrapRoot = resolveManagedTsconfigBootstrapRoot(args);
2154
2555
  if (managedTsconfigBootstrapRoot) await syncManagedTsconfigBootstrapFiles(managedTsconfigBootstrapRoot);
2155
2556
  cli.parse(process.argv, { run: false });
2156
2557
  await maybeAutoStartMcpServer(args, cli.options);
2157
2558
  await cli.runMatchedCommand();
2158
- }).catch((error) => {
2159
- if (handlePrepareLifecycleError(process.argv.slice(2), error)) return;
2559
+ }
2560
+ } catch (error) {
2561
+ if (!handlePrepareLifecycleError(process.argv.slice(2), error)) {
2160
2562
  handleCLIError(error);
2161
2563
  process.exitCode = 1;
2162
- });
2163
- } catch (error) {
2164
- handleCLIError(error);
2165
- process.exitCode = 1;
2564
+ }
2166
2565
  }
2167
2566
  //#endregion
2168
2567
  export {};