weapp-vite 6.13.4 → 6.14.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/cli.mjs CHANGED
@@ -1,23 +1,27 @@
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-Bb4OSo1-.mjs";
2
- import { r as logger_default, t as colors } from "./logger-gutcwWKE.mjs";
3
- import { p as VERSION } from "./file-Cf3pf5w7.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-DT4tLA1C.mjs";
2
+ import { r as logger_default, t as colors } from "./logger-CgxdNjvb.mjs";
3
+ import { m as VERSION } from "./file-BkvmW6_2.mjs";
4
+ import { a as resolveWeappMcpConfig, o as startWeappViteMcpServer } from "./mcp-DRlj32v4.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { defu, fs } from "@weapp-core/shared";
7
7
  import path, { posix } from "pathe";
8
8
  import process from "node:process";
9
9
  import fs$1 from "node:fs/promises";
10
10
  import { build, createServer } from "vite";
11
+ import os from "node:os";
11
12
  import { execFile } from "node:child_process";
12
13
  import { Buffer } from "node:buffer";
13
14
  import fs$2 from "node:fs";
14
15
  import { cac } from "cac";
15
16
  import { resolveCommand } from "package-manager-detector/commands";
16
17
  import { promisify } from "node:util";
17
- import { formatRetryHotkeyPrompt, formatWechatIdeLoginRequiredError, getConfig, isWeappIdeTopLevelCommand, isWechatIdeLoginRequiredError, parse, startForwardConsole, waitForRetryKeypress } from "weapp-ide-cli";
18
+ import { closeSharedMiniProgram, connectOpenedAutomator, formatRetryHotkeyPrompt, formatWechatIdeLoginRequiredError, getConfig, isWeappIdeTopLevelCommand, isWechatIdeLoginRequiredError, launchAutomator, parse, startForwardConsole, takeScreenshot, waitForRetryKeypress } from "weapp-ide-cli";
18
19
  import { generateJs, generateJson, generateWxml, generateWxss } from "@weapp-core/schematics";
19
20
  import { determineAgent } from "@vercel/detect-agent";
20
21
  import { initConfig } from "@weapp-core/init";
22
+ import { createInterface } from "node:readline/promises";
23
+ import { clearTimeout, setTimeout as setTimeout$1 } from "node:timers";
24
+ import { emitKeypressEvents } from "node:readline";
21
25
  //#region src/analyze/subpackages/classifier.ts
22
26
  const VIRTUAL_MODULE_INDICATOR = "\0";
23
27
  const VIRTUAL_PREFIX = `${SHARED_CHUNK_VIRTUAL_PREFIX}/`;
@@ -950,13 +954,27 @@ function logBuildPackageSizeReport(options) {
950
954
  //#region src/cli/openIde.ts
951
955
  const execFileAsync = promisify(execFile);
952
956
  async function openWechatIdeByAutomator(projectPath) {
953
- const { Launcher } = await import("@weapp-vite/miniprogram-automator");
954
- (await new Launcher().launch({
957
+ (await launchAutomator({
955
958
  projectPath,
956
959
  trustProject: true
957
960
  })).disconnect();
958
961
  }
959
962
  /**
963
+ * @description 若当前项目已在微信开发者工具中打开且自动化可连通,则直接复用现有会话,避免重复拉起 IDE。
964
+ */
965
+ async function tryReuseOpenedWechatIde(projectPath) {
966
+ try {
967
+ (await connectOpenedAutomator({
968
+ projectPath,
969
+ timeout: 3e3
970
+ })).disconnect();
971
+ logger_default.info("目标项目已在微信开发者工具中打开,跳过重复打开。");
972
+ return true;
973
+ } catch {
974
+ return false;
975
+ }
976
+ }
977
+ /**
960
978
  * @description 执行 IDE 打开流程,并在登录失效时允许按键重试。
961
979
  */
962
980
  async function runWechatIdeOpenWithRetry(argv) {
@@ -1017,6 +1035,7 @@ function resolveIdeProjectRoot(mpDistRoot, cwd) {
1017
1035
  }
1018
1036
  async function openIde(platform, projectPath, options = {}) {
1019
1037
  if (platform === "weapp" && projectPath && options.trustProject !== false) try {
1038
+ if (await tryReuseOpenedWechatIde(projectPath)) return;
1020
1039
  await openWechatIdeByAutomator(projectPath);
1021
1040
  return;
1022
1041
  } catch (error) {
@@ -1117,7 +1136,9 @@ function registerBuildCommand(cli) {
1117
1136
  configFile,
1118
1137
  inlineConfig,
1119
1138
  cliPlatform: targets.rawPlatform,
1120
- projectConfigPath: options.projectConfig
1139
+ projectConfigPath: options.projectConfig,
1140
+ syncSupportFiles: false,
1141
+ preloadAppEntry: false
1121
1142
  });
1122
1143
  const { buildService, configService, webService } = ctx;
1123
1144
  logRuntimeTarget(targets, { resolvedConfigPlatform: configService.platform });
@@ -1588,6 +1609,278 @@ function registerInitCommand(cli) {
1588
1609
  });
1589
1610
  }
1590
1611
  //#endregion
1612
+ //#region src/cli/mcpClient.ts
1613
+ const CODEX_BLOCK_PREFIX = "# >>> weapp-vite mcp ";
1614
+ const CODEX_BLOCK_SUFFIX = " >>>";
1615
+ const CODEX_BLOCK_END_PREFIX = "# <<< weapp-vite mcp ";
1616
+ const CODEX_BLOCK_END_SUFFIX = " <<<";
1617
+ const DEFAULT_HTTP_TIMEOUT_MS = 1500;
1618
+ const WORKSPACE_FOLDER_TOKEN = "${workspaceFolder}";
1619
+ const REG_CodexUrl = /^\s*url\s*=\s*"([^"]+)"/m;
1620
+ const REG_CodexCommand = /^\s*command\s*=\s*"([^"]+)"/m;
1621
+ const REG_CodexArgs = /^\s*args\s*=\s*\[([^\]]*)\]/m;
1622
+ const REG_FirstTomlString = /"([^"]+)"/;
1623
+ function resolveTargetWorkspaceRoot(target) {
1624
+ if (target.client === "cursor") return path.dirname(path.dirname(target.configPath));
1625
+ if (target.client === "claude-code") return path.dirname(target.configPath);
1626
+ return process.cwd();
1627
+ }
1628
+ function escapeRegExp(value) {
1629
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1630
+ }
1631
+ function renderTomlString(value) {
1632
+ return JSON.stringify(value);
1633
+ }
1634
+ function sanitizeServerNameSegment(value) {
1635
+ return value.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
1636
+ }
1637
+ function resolveServerName(workspaceRoot) {
1638
+ return `weapp-vite-${sanitizeServerNameSegment(path.basename(workspaceRoot)) || "project"}`;
1639
+ }
1640
+ function resolveNodeBinCommand() {
1641
+ return process.execPath || "node";
1642
+ }
1643
+ function resolveProjectCliBin(workspaceRoot) {
1644
+ return path.join(workspaceRoot, "node_modules", "weapp-vite", "bin", "weapp-vite.js");
1645
+ }
1646
+ function renderCommandArgs(args) {
1647
+ return args.map(renderTomlString).join(", ");
1648
+ }
1649
+ function renderCodexBlock(serverName, entry) {
1650
+ const startMarker = `${CODEX_BLOCK_PREFIX}${serverName}${CODEX_BLOCK_SUFFIX}`;
1651
+ const endMarker = `${CODEX_BLOCK_END_PREFIX}${serverName}${CODEX_BLOCK_END_SUFFIX}`;
1652
+ if (entry.url) return `${startMarker}
1653
+ [mcp_servers.${serverName}]
1654
+ url = ${renderTomlString(entry.url)}
1655
+ ${endMarker}
1656
+ `;
1657
+ return `${startMarker}
1658
+ [mcp_servers.${serverName}]
1659
+ command = ${renderTomlString(entry.command ?? resolveNodeBinCommand())}
1660
+ args = [${renderCommandArgs(entry.args ?? [])}]
1661
+ ${endMarker}
1662
+ `;
1663
+ }
1664
+ function renderJsonPreview(serverName, entry) {
1665
+ return `${JSON.stringify({ mcpServers: { [serverName]: entry } }, null, 2)}
1666
+ `;
1667
+ }
1668
+ function resolveCommandEntry(client, workspaceRoot) {
1669
+ const binPath = resolveProjectCliBin(workspaceRoot);
1670
+ if (client === "cursor") return {
1671
+ command: "node",
1672
+ args: [
1673
+ `${WORKSPACE_FOLDER_TOKEN}/node_modules/weapp-vite/bin/weapp-vite.js`,
1674
+ "mcp",
1675
+ "--workspace-root",
1676
+ WORKSPACE_FOLDER_TOKEN
1677
+ ]
1678
+ };
1679
+ return {
1680
+ command: resolveNodeBinCommand(),
1681
+ args: [
1682
+ binPath,
1683
+ "mcp",
1684
+ "--workspace-root",
1685
+ workspaceRoot
1686
+ ]
1687
+ };
1688
+ }
1689
+ function resolveHttpEntry(client, url) {
1690
+ if (client === "claude-code") return {
1691
+ type: "http",
1692
+ url
1693
+ };
1694
+ return { url };
1695
+ }
1696
+ function resolveCodexBlockPattern(serverName) {
1697
+ const startMarker = `${CODEX_BLOCK_PREFIX}${serverName}${CODEX_BLOCK_SUFFIX}`;
1698
+ const endMarker = `${CODEX_BLOCK_END_PREFIX}${serverName}${CODEX_BLOCK_END_SUFFIX}`;
1699
+ return new RegExp(`${escapeRegExp(startMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}\\n?`, "g");
1700
+ }
1701
+ function upsertCodexManagedBlock(content, serverName, block) {
1702
+ const withoutExisting = content.trimEnd().replace(resolveCodexBlockPattern(serverName), "").trimEnd();
1703
+ if (!withoutExisting) return block;
1704
+ return `${withoutExisting}\n\n${block}`;
1705
+ }
1706
+ function parseJsonConfig(content, configPath) {
1707
+ if (!content.trim()) return {};
1708
+ try {
1709
+ return JSON.parse(content);
1710
+ } catch (error) {
1711
+ const message = error instanceof Error ? error.message : String(error);
1712
+ throw new Error(`无法解析 MCP 配置文件 ${configPath}:${message}`);
1713
+ }
1714
+ }
1715
+ function resolveTarget(client, workspaceRoot) {
1716
+ const serverName = resolveServerName(workspaceRoot);
1717
+ if (client === "codex") return {
1718
+ client,
1719
+ configPath: path.join(os.homedir(), ".codex", "config.toml"),
1720
+ displayName: "Codex",
1721
+ serverName
1722
+ };
1723
+ if (client === "claude-code") return {
1724
+ client,
1725
+ configPath: path.join(workspaceRoot, ".mcp.json"),
1726
+ displayName: "Claude Code",
1727
+ serverName
1728
+ };
1729
+ return {
1730
+ client,
1731
+ configPath: path.join(workspaceRoot, ".cursor", "mcp.json"),
1732
+ displayName: "Cursor",
1733
+ serverName
1734
+ };
1735
+ }
1736
+ function resolveSupportedMcpClient(input) {
1737
+ if (input === "codex" || input === "claude-code" || input === "cursor") return input;
1738
+ throw new Error(`不支持的 MCP 客户端:${input}。当前仅支持 codex、claude-code、cursor。`);
1739
+ }
1740
+ function buildMcpClientConfigPlan(options) {
1741
+ const workspaceRoot = path.resolve(options.workspaceRoot);
1742
+ const target = resolveTarget(options.client, workspaceRoot);
1743
+ const entry = options.transport === "http" ? resolveHttpEntry(options.client, options.url ?? "") : resolveCommandEntry(options.client, workspaceRoot);
1744
+ if (options.transport === "http" && !entry.url) throw new Error("HTTP 模式缺少 MCP 服务地址,请使用 --url 指定或在当前项目中检测到可用地址。");
1745
+ return {
1746
+ entry,
1747
+ preview: target.client === "codex" ? renderCodexBlock(target.serverName, entry) : renderJsonPreview(target.serverName, entry),
1748
+ target,
1749
+ transport: options.transport
1750
+ };
1751
+ }
1752
+ async function writeMcpClientConfig(plan) {
1753
+ await fs.ensureDir(path.dirname(plan.target.configPath));
1754
+ const existing = await fs.readFile(plan.target.configPath, "utf8").catch(() => "");
1755
+ if (plan.target.client === "codex") {
1756
+ const nextContent = upsertCodexManagedBlock(existing, plan.target.serverName, plan.preview);
1757
+ await fs.writeFile(plan.target.configPath, nextContent, "utf8");
1758
+ return;
1759
+ }
1760
+ const parsed = parseJsonConfig(existing, plan.target.configPath);
1761
+ const nextConfig = {
1762
+ ...parsed,
1763
+ mcpServers: {
1764
+ ...parsed.mcpServers ?? {},
1765
+ [plan.target.serverName]: plan.entry
1766
+ }
1767
+ };
1768
+ await fs.writeFile(plan.target.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
1769
+ }
1770
+ async function probeHttpEndpoint(url) {
1771
+ const controller = new AbortController();
1772
+ const timer = setTimeout$1(() => {
1773
+ controller.abort();
1774
+ }, DEFAULT_HTTP_TIMEOUT_MS);
1775
+ try {
1776
+ return (await fetch(url, {
1777
+ method: "GET",
1778
+ signal: controller.signal
1779
+ })).status > 0;
1780
+ } catch {
1781
+ return false;
1782
+ } finally {
1783
+ clearTimeout(timer);
1784
+ controller.abort();
1785
+ }
1786
+ }
1787
+ async function inspectJsonConfig(target) {
1788
+ if (!await fs.pathExists(target.configPath)) return {
1789
+ configExists: false,
1790
+ configPath: target.configPath,
1791
+ displayName: target.displayName,
1792
+ issues: [`未找到配置文件:${target.configPath}`],
1793
+ serverName: target.serverName
1794
+ };
1795
+ const entry = parseJsonConfig(await fs.readFile(target.configPath, "utf8"), target.configPath).mcpServers?.[target.serverName];
1796
+ if (!entry) return {
1797
+ configExists: true,
1798
+ configPath: target.configPath,
1799
+ displayName: target.displayName,
1800
+ issues: [`配置文件中未找到服务器 ${target.serverName}`],
1801
+ serverName: target.serverName
1802
+ };
1803
+ const transport = entry.url ? "http" : "command";
1804
+ const issues = [];
1805
+ let httpReachable;
1806
+ if (transport === "command") {
1807
+ const normalizedBin = (entry.args?.[0])?.replaceAll(WORKSPACE_FOLDER_TOKEN, resolveTargetWorkspaceRoot(target));
1808
+ if (!normalizedBin || !await fs.pathExists(normalizedBin)) issues.push(`未找到本地 CLI 入口:${normalizedBin ?? "缺少 args[0]"}`);
1809
+ } else if (entry.url) {
1810
+ httpReachable = await probeHttpEndpoint(entry.url);
1811
+ if (!httpReachable) issues.push(`HTTP MCP 地址不可达:${entry.url}`);
1812
+ }
1813
+ return {
1814
+ configExists: true,
1815
+ configPath: target.configPath,
1816
+ displayName: target.displayName,
1817
+ httpReachable,
1818
+ issues,
1819
+ serverName: target.serverName,
1820
+ transport
1821
+ };
1822
+ }
1823
+ async function inspectCodexConfig(target) {
1824
+ if (!await fs.pathExists(target.configPath)) return {
1825
+ configExists: false,
1826
+ configPath: target.configPath,
1827
+ displayName: target.displayName,
1828
+ issues: [`未找到配置文件:${target.configPath}`],
1829
+ serverName: target.serverName
1830
+ };
1831
+ const blockMatch = (await fs.readFile(target.configPath, "utf8")).match(resolveCodexBlockPattern(target.serverName));
1832
+ if (!blockMatch?.[0]) return {
1833
+ configExists: true,
1834
+ configPath: target.configPath,
1835
+ displayName: target.displayName,
1836
+ issues: [`未找到 weapp-vite 生成的 Codex 配置区块:${target.serverName}`],
1837
+ serverName: target.serverName
1838
+ };
1839
+ const block = blockMatch[0];
1840
+ const issues = [];
1841
+ const urlMatch = block.match(REG_CodexUrl);
1842
+ const commandMatch = block.match(REG_CodexCommand);
1843
+ const argsMatch = block.match(REG_CodexArgs);
1844
+ if (urlMatch?.[1]) {
1845
+ const reachable = await probeHttpEndpoint(urlMatch[1]);
1846
+ if (!reachable) issues.push(`HTTP MCP 地址不可达:${urlMatch[1]}`);
1847
+ return {
1848
+ configExists: true,
1849
+ configPath: target.configPath,
1850
+ displayName: target.displayName,
1851
+ httpReachable: reachable,
1852
+ issues,
1853
+ serverName: target.serverName,
1854
+ transport: "http"
1855
+ };
1856
+ }
1857
+ const binPath = (argsMatch?.[1]?.match(REG_FirstTomlString))?.[1];
1858
+ if (!commandMatch?.[1]) issues.push("Codex 配置区块缺少 command 字段");
1859
+ if (!binPath || !await fs.pathExists(binPath)) issues.push(`未找到本地 CLI 入口:${binPath ?? "缺少 args[0]"}`);
1860
+ return {
1861
+ configExists: true,
1862
+ configPath: target.configPath,
1863
+ displayName: target.displayName,
1864
+ issues,
1865
+ serverName: target.serverName,
1866
+ transport: "command"
1867
+ };
1868
+ }
1869
+ async function inspectMcpClientConfig(options) {
1870
+ const target = resolveTarget(options.client, path.resolve(options.workspaceRoot));
1871
+ if (target.client === "codex") return await inspectCodexConfig(target);
1872
+ return await inspectJsonConfig(target);
1873
+ }
1874
+ function formatMcpQuickStart(options) {
1875
+ const suffix = options.transport === "http" && options.httpUrl ? ` --transport http --url ${options.httpUrl}` : "";
1876
+ return [
1877
+ "在 AI 工具中接入 weapp-vite MCP:",
1878
+ ` - Codex: wv mcp init codex${suffix}`,
1879
+ ` - Claude Code: wv mcp init claude-code${suffix}`,
1880
+ ` - Cursor: wv mcp init cursor${suffix}`
1881
+ ];
1882
+ }
1883
+ //#endregion
1591
1884
  //#region src/cli/commands/mcp.ts
1592
1885
  function resolvePort(port) {
1593
1886
  if (typeof port === "number" && Number.isInteger(port)) return port;
@@ -1596,17 +1889,109 @@ function resolvePort(port) {
1596
1889
  if (Number.isInteger(parsed)) return parsed;
1597
1890
  }
1598
1891
  }
1892
+ async function resolveHttpUrl(options) {
1893
+ if (options.url?.trim()) return options.url.trim();
1894
+ const workspaceRoot = options.workspaceRoot ? path.resolve(options.workspaceRoot) : process.cwd();
1895
+ const originalCwd = process.cwd();
1896
+ try {
1897
+ if (workspaceRoot !== originalCwd) process.chdir(workspaceRoot);
1898
+ const resolved = resolveWeappMcpConfig((await loadConfig(resolveConfigFile(options)))?.config?.weapp?.mcp);
1899
+ return `http://${resolved.host}:${resolved.port}${resolved.endpoint}`;
1900
+ } catch {
1901
+ const resolved = resolveWeappMcpConfig(void 0);
1902
+ return `http://${resolved.host}:${resolved.port}${resolved.endpoint}`;
1903
+ } finally {
1904
+ if (process.cwd() !== originalCwd) process.chdir(originalCwd);
1905
+ }
1906
+ }
1907
+ async function confirmWrite() {
1908
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
1909
+ const rl = createInterface({
1910
+ input: process.stdin,
1911
+ output: process.stdout
1912
+ });
1913
+ try {
1914
+ const normalized = (await rl.question("是否写入配置文件?(Y/n) ")).trim().toLowerCase();
1915
+ return normalized === "" || normalized === "y" || normalized === "yes";
1916
+ } finally {
1917
+ rl.close();
1918
+ }
1919
+ }
1920
+ function resolveClientTransport(transport) {
1921
+ return transport === "http" ? "http" : "command";
1922
+ }
1923
+ async function handlePrint(clientName, options, write = false) {
1924
+ const client = resolveSupportedMcpClient(clientName);
1925
+ const workspaceRoot = options.workspaceRoot ?? process.cwd();
1926
+ const transport = resolveClientTransport(options.transport);
1927
+ const plan = buildMcpClientConfigPlan({
1928
+ client,
1929
+ transport,
1930
+ url: transport === "http" ? await resolveHttpUrl(options) : void 0,
1931
+ workspaceRoot
1932
+ });
1933
+ logger_default.info(`${plan.target.displayName} 配置文件:${plan.target.configPath}`);
1934
+ logger_default.info(`服务器名称:${plan.target.serverName}`);
1935
+ process.stdout.write(`${plan.preview}\n`);
1936
+ if (!write) return;
1937
+ if (options.yes || await confirmWrite()) {
1938
+ await writeMcpClientConfig(plan);
1939
+ logger_default.success(`已写入 ${plan.target.displayName} MCP 配置。`);
1940
+ logger_default.info(`请重启 ${plan.target.displayName},然后执行:wv mcp doctor ${client}`);
1941
+ return;
1942
+ }
1943
+ logger_default.info("已取消写入。");
1944
+ }
1945
+ async function handleDoctor(clientName, options) {
1946
+ const result = await inspectMcpClientConfig({
1947
+ client: resolveSupportedMcpClient(clientName),
1948
+ workspaceRoot: options.workspaceRoot ?? process.cwd()
1949
+ });
1950
+ logger_default.info(`${result.displayName} 配置文件:${result.configPath}`);
1951
+ logger_default.info(`服务器名称:${result.serverName}`);
1952
+ if (!result.configExists || result.issues.length > 0) {
1953
+ for (const issue of result.issues) logger_default.warn(issue);
1954
+ throw new Error("MCP 客户端配置检查未通过。");
1955
+ }
1956
+ if (result.transport) logger_default.info(`传输模式:${result.transport}`);
1957
+ if (result.httpReachable !== void 0) logger_default.info(`HTTP 服务可达:${result.httpReachable ? "是" : "否"}`);
1958
+ logger_default.success("MCP 客户端配置检查通过。");
1959
+ }
1960
+ async function handleServer(options) {
1961
+ const resolvedTransport = options.transport === "http" ? "streamable-http" : options.transport;
1962
+ await startWeappViteMcpServer({
1963
+ endpoint: options.endpoint,
1964
+ host: options.host,
1965
+ port: resolvePort(options.port),
1966
+ transport: resolvedTransport,
1967
+ unref: options.unref,
1968
+ workspaceRoot: options.workspaceRoot
1969
+ });
1970
+ for (const line of formatMcpQuickStart({
1971
+ httpUrl: resolvedTransport === "streamable-http" ? `http://${options.host ?? "127.0.0.1"}:${resolvePort(options.port) ?? 3088}${options.endpoint ?? "/mcp"}` : void 0,
1972
+ transport: resolvedTransport === "streamable-http" ? "http" : "command"
1973
+ })) logger_default.info(line);
1974
+ }
1599
1975
  function registerMcpCommand(cli) {
1600
- cli.command("mcp", "start weapp-vite MCP server").option("--transport <type>", "[string] stdio | streamable-http", { default: "stdio" }).option("--host <host>", "[string] streamable-http host").option("--port <port>", "[number] streamable-http port").option("--endpoint <path>", "[string] streamable-http endpoint path").option("--unref", "[boolean] unref HTTP server to not block process exit").option("--workspace-root <path>", "[string] workspace root path, defaults to auto detect from cwd").action(async (options) => {
1601
- const { startWeappViteMcpServer } = await import("./mcp.mjs");
1602
- await startWeappViteMcpServer({
1603
- endpoint: options.endpoint,
1604
- host: options.host,
1605
- port: resolvePort(options.port),
1606
- transport: options.transport,
1607
- unref: options.unref,
1608
- workspaceRoot: options.workspaceRoot
1609
- });
1976
+ cli.command("mcp [...args]", "start weapp-vite MCP server or manage MCP client onboarding").option("--transport <type>", "[string] stdio | streamable-http | command | http", { default: "stdio" }).option("--host <host>", "[string] streamable-http host").option("--port <port>", "[number] streamable-http port").option("--endpoint <path>", "[string] streamable-http endpoint path").option("--unref", "[boolean] unref HTTP server to not block process exit").option("--url <url>", "[string] explicit HTTP MCP url").option("--workspace-root <path>", "[string] workspace root path, defaults to cwd").option("-y, --yes", "[boolean] write config without prompt").action(async (args, options) => {
1977
+ const [subcommand, client] = args;
1978
+ if (subcommand === "init") {
1979
+ if (!client) throw new Error("缺少客户端名称,请使用:wv mcp init <codex|claude-code|cursor>");
1980
+ await handlePrint(client, options, true);
1981
+ return;
1982
+ }
1983
+ if (subcommand === "print") {
1984
+ if (!client) throw new Error("缺少客户端名称,请使用:wv mcp print <codex|claude-code|cursor>");
1985
+ await handlePrint(client, options, false);
1986
+ return;
1987
+ }
1988
+ if (subcommand === "doctor") {
1989
+ if (!client) throw new Error("缺少客户端名称,请使用:wv mcp doctor <codex|claude-code|cursor>");
1990
+ await handleDoctor(client, options);
1991
+ return;
1992
+ }
1993
+ if (subcommand) throw new Error(`未知的 mcp 子命令:${subcommand}`);
1994
+ await handleServer(options);
1610
1995
  });
1611
1996
  }
1612
1997
  //#endregion
@@ -1665,6 +2050,343 @@ function registerPrepareCommand(cli) {
1665
2050
  });
1666
2051
  }
1667
2052
  //#endregion
2053
+ //#region package.json
2054
+ var version = "6.14.1";
2055
+ //#endregion
2056
+ //#region src/cli/devHotkeys.ts
2057
+ const DEV_SCREENSHOT_DIR = ".tmp/weapp-vite-dev-screenshots";
2058
+ const DEFAULT_SCREENSHOT_TIMEOUT = 3e4;
2059
+ const REG_PENDING_PREFIX = /^正在/;
2060
+ const FULLWIDTH_ASCII_START = 65281;
2061
+ const FULLWIDTH_ASCII_END = 65374;
2062
+ const FULLWIDTH_ASCII_OFFSET = 65248;
2063
+ const HOTKEY_DEDUP_WINDOW_MS = 32;
2064
+ function formatMcpUrl(host, port, endpoint) {
2065
+ return `http://${host}:${port}${endpoint}`;
2066
+ }
2067
+ function forwardSigint() {
2068
+ process.kill(process.pid, "SIGINT");
2069
+ }
2070
+ function forwardSigtstp() {
2071
+ process.kill(process.pid, "SIGTSTP");
2072
+ }
2073
+ function formatProjectLabel(cwd) {
2074
+ return path.basename(cwd) || cwd;
2075
+ }
2076
+ function formatMcpStatus(state) {
2077
+ if (!state.mcpEnabled) return "已禁用";
2078
+ return state.mcpRunning ? "运行中" : "未启动";
2079
+ }
2080
+ function formatFooterLine(state) {
2081
+ if (state.currentAction) return `执行中 ${state.currentAction}`;
2082
+ return "就绪 等待操作...";
2083
+ }
2084
+ /**
2085
+ * @description 生成带状态的开发态快捷键帮助文本。
2086
+ */
2087
+ function formatDevHotkeyHelpWithState(state) {
2088
+ const key = (value) => colors.bold(colors.green(value));
2089
+ const actionRows = [{
2090
+ key: key("s"),
2091
+ description: "截图当前页面并保存到本地"
2092
+ }, {
2093
+ key: key("m"),
2094
+ description: "开关 MCP 服务"
2095
+ }];
2096
+ const processRows = [
2097
+ {
2098
+ key: key("q"),
2099
+ description: "退出当前 dev"
2100
+ },
2101
+ {
2102
+ key: key("Ctrl+C"),
2103
+ description: "强制中断当前 dev"
2104
+ },
2105
+ {
2106
+ key: key("Ctrl+Z"),
2107
+ description: "暂时挂起当前 dev,恢复终端控制"
2108
+ }
2109
+ ];
2110
+ const helpRows = [{
2111
+ key: key("h"),
2112
+ description: "重新显示这份帮助"
2113
+ }];
2114
+ const keyColumnWidth = Math.max(...[
2115
+ ...actionRows,
2116
+ ...processRows,
2117
+ ...helpRows
2118
+ ].map((row) => row.key.length));
2119
+ const formatRows = (rows) => rows.map(({ key, description }) => `按 ${key.padEnd(keyColumnWidth)} ${description}`);
2120
+ return [
2121
+ `${colors.bold(colors.green("DEV"))} weapp-vite v${version} ${state.projectLabel ?? "weapp"}`,
2122
+ "",
2123
+ "快捷命令",
2124
+ ...formatRows(actionRows),
2125
+ "",
2126
+ "进程控制",
2127
+ ...formatRows(processRows),
2128
+ "",
2129
+ "帮助",
2130
+ ...formatRows(helpRows),
2131
+ "",
2132
+ `当前状态:${state.currentAction ?? "等待操作"} / MCP ${formatMcpStatus(state)}`
2133
+ ].join("\n");
2134
+ }
2135
+ /**
2136
+ * @description 生成带状态的开发态快捷键简短提示。
2137
+ */
2138
+ function formatDevHotkeyHintWithState(state) {
2139
+ const key = (value) => colors.bold(colors.green(value));
2140
+ if (state.currentAction) return `${formatFooterLine(state)},按 ${key("h")} 显示帮助,按 ${key("q")} 退出`;
2141
+ return `开发快捷键已就绪,按 ${key("h")} 显示帮助,按 ${key("q")} 退出`;
2142
+ }
2143
+ /**
2144
+ * @description 生成开发态截图输出路径。
2145
+ */
2146
+ function resolveDevScreenshotOutputPath(cwd, now = /* @__PURE__ */ new Date()) {
2147
+ const stamp = now.toISOString().replace(/[:.]/g, "-");
2148
+ return path.join(cwd, DEV_SCREENSHOT_DIR, `screenshot-${stamp}.png`);
2149
+ }
2150
+ function formatLogPath(cwd, targetPath) {
2151
+ const relativePath = path.relative(cwd, targetPath);
2152
+ if (!relativePath || relativePath.startsWith("..")) return targetPath;
2153
+ return relativePath;
2154
+ }
2155
+ function formatResolvedScreenshotPath(cwd, fallbackPath, result) {
2156
+ return formatLogPath(cwd, result.path ?? fallbackPath);
2157
+ }
2158
+ function normalizeInputChar(input) {
2159
+ if (input.length !== 1) return input;
2160
+ const codePoint = input.codePointAt(0);
2161
+ if (!codePoint) return input;
2162
+ if (codePoint >= FULLWIDTH_ASCII_START && codePoint <= FULLWIDTH_ASCII_END) return String.fromCodePoint(codePoint - FULLWIDTH_ASCII_OFFSET);
2163
+ return input;
2164
+ }
2165
+ /**
2166
+ * @description 执行当前页面截图并输出结果日志。
2167
+ */
2168
+ async function runScreenshotAction(options) {
2169
+ const outputPath = resolveDevScreenshotOutputPath(options.cwd);
2170
+ await fs$1.mkdir(path.dirname(outputPath), { recursive: true });
2171
+ logger_default.info(`[dev action] 正在截图当前页面,输出到 ${colors.cyan(formatLogPath(options.cwd, outputPath))}`);
2172
+ const result = await takeScreenshot({
2173
+ fullPage: true,
2174
+ projectPath: options.projectPath,
2175
+ sharedSession: true,
2176
+ outputPath,
2177
+ timeout: DEFAULT_SCREENSHOT_TIMEOUT
2178
+ });
2179
+ const resolvedPath = formatResolvedScreenshotPath(options.cwd, outputPath, result);
2180
+ logger_default.success(`[dev action] 当前页面截图完成:${colors.cyan(resolvedPath)}`);
2181
+ return resolvedPath;
2182
+ }
2183
+ /**
2184
+ * @description 在开发态启动终端快捷键,并将动作输出到本地日志。
2185
+ */
2186
+ function startDevHotkeys(options) {
2187
+ if (options.platform !== "weapp" || !process.stdin.isTTY) return;
2188
+ emitKeypressEvents(process.stdin);
2189
+ const hasSetRawMode = typeof process.stdin.setRawMode === "function";
2190
+ if (hasSetRawMode) process.stdin.setRawMode(true);
2191
+ process.stdin.resume();
2192
+ let closed = false;
2193
+ let running = false;
2194
+ let mcpHandle;
2195
+ let onData;
2196
+ let onKeypress;
2197
+ let onSigcont;
2198
+ let currentAction;
2199
+ let lastAction;
2200
+ let lastRenderedPanel = "";
2201
+ const recentInputs = /* @__PURE__ */ new Map();
2202
+ const resolvedMcp = resolveWeappMcpConfig(options.mcpConfig);
2203
+ const getState = () => ({
2204
+ currentAction,
2205
+ lastAction,
2206
+ mcpEnabled: resolvedMcp.enabled,
2207
+ mcpRunning: Boolean(mcpHandle?.close),
2208
+ projectLabel: formatProjectLabel(options.cwd)
2209
+ });
2210
+ const detachTerminal = () => {
2211
+ if (hasSetRawMode) process.stdin.setRawMode(false);
2212
+ process.stdin.pause();
2213
+ };
2214
+ const attachTerminal = () => {
2215
+ if (closed) return;
2216
+ if (hasSetRawMode) process.stdin.setRawMode(true);
2217
+ process.stdin.resume();
2218
+ };
2219
+ const ensureTerminalActive = () => {
2220
+ if (closed) return;
2221
+ if (hasSetRawMode) process.stdin.setRawMode(true);
2222
+ process.stdin.resume();
2223
+ };
2224
+ const close = () => {
2225
+ if (closed) return;
2226
+ closed = true;
2227
+ if (onData) process.stdin.off("data", onData);
2228
+ if (onKeypress) process.stdin.off("keypress", onKeypress);
2229
+ if (onSigcont) process.off("SIGCONT", onSigcont);
2230
+ detachTerminal();
2231
+ if (mcpHandle?.close) mcpHandle.close().catch((error) => {
2232
+ logger_default.warn(`[dev action] MCP 服务关闭失败:${error instanceof Error ? error.message : String(error)}`);
2233
+ });
2234
+ closeSharedMiniProgram(options.projectPath).catch((error) => {
2235
+ logger_default.warn(`[dev action] DevTools 会话关闭失败:${error instanceof Error ? error.message : String(error)}`);
2236
+ });
2237
+ };
2238
+ const printPanel = (message, force = false) => {
2239
+ if (!force && message === lastRenderedPanel) return;
2240
+ lastRenderedPanel = message;
2241
+ logger_default.info(message);
2242
+ };
2243
+ const printHelp = () => {
2244
+ printPanel(formatDevHotkeyHelpWithState(getState()), true);
2245
+ ensureTerminalActive();
2246
+ };
2247
+ const printHint = () => {
2248
+ printPanel(formatDevHotkeyHintWithState(getState()));
2249
+ ensureTerminalActive();
2250
+ };
2251
+ const restore = () => {
2252
+ if (closed) return;
2253
+ if (onData) process.stdin.off("data", onData);
2254
+ if (onKeypress) process.stdin.off("keypress", onKeypress);
2255
+ attachTerminal();
2256
+ if (onData) process.stdin.on("data", onData);
2257
+ if (onKeypress) process.stdin.on("keypress", onKeypress);
2258
+ printHint();
2259
+ };
2260
+ const suspend = () => {
2261
+ lastRenderedPanel = "";
2262
+ detachTerminal();
2263
+ forwardSigtstp();
2264
+ };
2265
+ const toggleMcp = async () => {
2266
+ if (!resolvedMcp.enabled) {
2267
+ logger_default.warn("[dev action] MCP 已在配置中禁用,跳过切换。");
2268
+ return "MCP 已禁用";
2269
+ }
2270
+ if (mcpHandle?.close) {
2271
+ const url = formatMcpUrl(resolvedMcp.host, resolvedMcp.port, resolvedMcp.endpoint);
2272
+ logger_default.info(`[dev action] 正在关闭 MCP 服务:${colors.cyan(url)}`);
2273
+ await mcpHandle.close();
2274
+ mcpHandle = void 0;
2275
+ logger_default.success(`[dev action] MCP 服务已关闭:${colors.cyan(url)}`);
2276
+ return `MCP 已关闭 (${url})`;
2277
+ }
2278
+ const url = formatMcpUrl(resolvedMcp.host, resolvedMcp.port, resolvedMcp.endpoint);
2279
+ logger_default.info(`[dev action] 正在启动 MCP 服务:${colors.cyan(url)}`);
2280
+ mcpHandle = await startWeappViteMcpServer({
2281
+ endpoint: resolvedMcp.endpoint,
2282
+ host: resolvedMcp.host,
2283
+ port: resolvedMcp.port,
2284
+ transport: "streamable-http",
2285
+ unref: false,
2286
+ workspaceRoot: options.cwd
2287
+ });
2288
+ logger_default.success(`[dev action] MCP 服务已启动:${colors.cyan(url)}`);
2289
+ for (const line of formatMcpQuickStart({
2290
+ httpUrl: url,
2291
+ transport: "http"
2292
+ })) logger_default.info(line);
2293
+ return `MCP 已启动 (${url})`;
2294
+ };
2295
+ const runAction = (label, pendingLabel, action) => {
2296
+ if (running) {
2297
+ const current = currentAction ?? "已有命令";
2298
+ logger_default.warn(`[dev action] 当前正在${current.replace(REG_PENDING_PREFIX, "")},请稍后再试。`);
2299
+ return;
2300
+ }
2301
+ running = true;
2302
+ currentAction = pendingLabel;
2303
+ printHint();
2304
+ action().then((summary) => {
2305
+ if (summary) lastAction = summary;
2306
+ }).catch((error) => {
2307
+ logger_default.error(`[dev action] ${label}失败:${error instanceof Error ? error.message : String(error)}`);
2308
+ }).finally(() => {
2309
+ running = false;
2310
+ currentAction = void 0;
2311
+ if (!closed) printHint();
2312
+ });
2313
+ };
2314
+ const handleInput = (input) => {
2315
+ if (closed) return;
2316
+ const normalizedInput = normalizeInputChar(input);
2317
+ if (normalizedInput === "") {
2318
+ close();
2319
+ forwardSigint();
2320
+ return;
2321
+ }
2322
+ if (normalizedInput === "") {
2323
+ suspend();
2324
+ return;
2325
+ }
2326
+ const normalized = normalizedInput.toLowerCase();
2327
+ if (normalized === "q") {
2328
+ close();
2329
+ forwardSigint();
2330
+ return;
2331
+ }
2332
+ if (normalized === "h") {
2333
+ printHelp();
2334
+ return;
2335
+ }
2336
+ if (normalized === "s") {
2337
+ runAction("截图", "正在截图当前页面", async () => {
2338
+ return `截图已保存到 ${await runScreenshotAction(options)}`;
2339
+ });
2340
+ return;
2341
+ }
2342
+ if (normalized === "m") runAction("MCP 切换", mcpHandle?.close ? "正在关闭 MCP 服务" : "正在启动 MCP 服务", async () => {
2343
+ return await toggleMcp();
2344
+ });
2345
+ };
2346
+ const handleInputOnce = (input, source) => {
2347
+ const normalizedInput = normalizeInputChar(input);
2348
+ const now = Date.now();
2349
+ for (const [token, timestamp] of recentInputs) if (now - timestamp > HOTKEY_DEDUP_WINDOW_MS) recentInputs.delete(token);
2350
+ const dedupKey = normalizedInput;
2351
+ const recentEntry = recentInputs.get(dedupKey);
2352
+ if (recentEntry !== void 0) {
2353
+ const [recentSource, recentTimestamp] = recentEntry.split(":");
2354
+ if (recentSource !== source && now - Number(recentTimestamp) <= HOTKEY_DEDUP_WINDOW_MS) return;
2355
+ }
2356
+ recentInputs.set(dedupKey, `${source}:${now}`);
2357
+ handleInput(normalizedInput);
2358
+ };
2359
+ onData = (chunk) => {
2360
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
2361
+ for (const char of text) handleInputOnce(char, "data");
2362
+ };
2363
+ process.stdin.on("data", onData);
2364
+ onKeypress = (str, key) => {
2365
+ if (key?.ctrl && key.name === "c") {
2366
+ handleInputOnce("", "keypress");
2367
+ return;
2368
+ }
2369
+ if (key?.ctrl && key.name === "z") {
2370
+ handleInputOnce("", "keypress");
2371
+ return;
2372
+ }
2373
+ if (typeof str === "string" && str) handleInputOnce(str, "keypress");
2374
+ };
2375
+ process.stdin.on("keypress", onKeypress);
2376
+ onSigcont = () => {
2377
+ restore();
2378
+ };
2379
+ process.on("SIGCONT", onSigcont);
2380
+ if (!options.silentStartupHint) printHint();
2381
+ if (resolvedMcp.enabled && resolvedMcp.autoStart) runAction("MCP 自动启动", "正在启动 MCP 服务", async () => {
2382
+ await toggleMcp();
2383
+ });
2384
+ return {
2385
+ close,
2386
+ restore
2387
+ };
2388
+ }
2389
+ //#endregion
1668
2390
  //#region src/cli/commands/serve.ts
1669
2391
  function emitDashboardEvents(handle, events) {
1670
2392
  handle?.emitRuntimeEvents(events);
@@ -1680,6 +2402,17 @@ const REG_DIST_POSIX_SEP = /\\/g;
1680
2402
  function hasAnalyzeData(result) {
1681
2403
  return result.packages.length > 0 || result.modules.length > 0;
1682
2404
  }
2405
+ function waitForServeShutdownSignal() {
2406
+ return new Promise((resolve) => {
2407
+ const onSignal = () => {
2408
+ process.off("SIGINT", onSignal);
2409
+ process.off("SIGTERM", onSignal);
2410
+ resolve();
2411
+ };
2412
+ process.on("SIGINT", onSignal);
2413
+ process.on("SIGTERM", onSignal);
2414
+ });
2415
+ }
1683
2416
  async function collectOutputFiles(root) {
1684
2417
  const entries = await fs$1.readdir(root, { withFileTypes: true });
1685
2418
  const files = [];
@@ -1811,165 +2544,181 @@ function registerServeCommand(cli) {
1811
2544
  const enableAnalyze = Boolean(isUiEnabled(options) && targets.runMini);
1812
2545
  let analyzeHandle;
1813
2546
  let analyzeRunId = 0;
1814
- const runAnalyze = async () => {
1815
- const startedAt = Date.now();
1816
- try {
1817
- const result = await analyzeSubpackages(await createCompilerContext({
1818
- key: `serve-ui-analyze:${process.pid}:${++analyzeRunId}`,
1819
- cwd: configService.cwd,
1820
- mode: configService.mode,
1821
- isDev: false,
1822
- configFile,
1823
- inlineConfig: createInlineConfig(targets.mpPlatform),
1824
- cliPlatform: targets.rawPlatform,
1825
- projectConfigPath: options.projectConfig,
1826
- syncSupportFiles: false
1827
- }));
1828
- if (hasAnalyzeData(result)) return {
1829
- result,
1830
- durationMs: Date.now() - startedAt,
1831
- mode: "full"
1832
- };
1833
- } catch (error) {
1834
- const message = error instanceof Error ? error.message : String(error);
1835
- logger_default.warn(`[ui] 完整分析失败,已回退到 dist 文件扫描:${message}`);
2547
+ const devHotkeysSession = targets.runMini ? startDevHotkeys({
2548
+ cwd: configService.cwd,
2549
+ mcpConfig: configService.weappViteConfig?.weapp?.mcp,
2550
+ platform: configService.platform,
2551
+ projectPath: resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd),
2552
+ silentStartupHint: true
2553
+ }) : void 0;
2554
+ try {
2555
+ const runAnalyze = async () => {
2556
+ const startedAt = Date.now();
2557
+ try {
2558
+ const result = await analyzeSubpackages(await createCompilerContext({
2559
+ key: `serve-ui-analyze:${process.pid}:${++analyzeRunId}`,
2560
+ cwd: configService.cwd,
2561
+ mode: configService.mode,
2562
+ isDev: false,
2563
+ configFile,
2564
+ inlineConfig: createInlineConfig(targets.mpPlatform),
2565
+ cliPlatform: targets.rawPlatform,
2566
+ projectConfigPath: options.projectConfig,
2567
+ syncSupportFiles: false
2568
+ }));
2569
+ if (hasAnalyzeData(result)) return {
2570
+ result,
2571
+ durationMs: Date.now() - startedAt,
2572
+ mode: "full"
2573
+ };
2574
+ } catch (error) {
2575
+ const message = error instanceof Error ? error.message : String(error);
2576
+ logger_default.warn(`[ui] 完整分析失败,已回退到 dist 文件扫描:${message}`);
2577
+ return {
2578
+ result: await analyzeUiFallback(ctx),
2579
+ durationMs: Date.now() - startedAt,
2580
+ mode: "fallback",
2581
+ fallbackReason: message
2582
+ };
2583
+ }
1836
2584
  return {
1837
2585
  result: await analyzeUiFallback(ctx),
1838
2586
  durationMs: Date.now() - startedAt,
1839
2587
  mode: "fallback",
1840
- fallbackReason: message
2588
+ fallbackReason: "完整分析结果为空,已回退到 dist 文件扫描。"
1841
2589
  };
1842
- }
1843
- return {
1844
- result: await analyzeUiFallback(ctx),
1845
- durationMs: Date.now() - startedAt,
1846
- mode: "fallback",
1847
- fallbackReason: "完整分析结果为空,已回退到 dist 文件扫描。"
1848
2590
  };
1849
- };
1850
- const triggerAnalyzeUpdate = async (reason = "watch") => {
1851
- if (!analyzeHandle) return;
1852
- emitDashboardEvents(analyzeHandle, [{
1853
- kind: reason === "watch" ? "hmr" : "build",
1854
- level: "info",
1855
- title: reason === "watch" ? "analyze refresh started" : "initial analyze started",
1856
- detail: reason === "watch" ? "检测到新的构建结束事件,开始刷新 analyze 面板。" : "开发态 UI 已启动,开始生成第一份 analyze 结果。",
1857
- tags: reason === "watch" ? ["watch", "analyze"] : ["initial", "analyze"]
1858
- }]);
1859
- const next = await runAnalyze();
1860
- if (next.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
1861
- kind: "diagnostic",
1862
- level: "warning",
1863
- title: "analyze fallback enabled",
1864
- detail: next.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。",
1865
- durationMs: next.durationMs,
1866
- tags: ["analyze", "fallback"]
1867
- }]);
1868
- await analyzeHandle.update(next.result);
1869
- emitDashboardEvents(analyzeHandle, [{
1870
- kind: next.mode === "fallback" ? "diagnostic" : "build",
1871
- level: next.mode === "fallback" ? "warning" : "success",
1872
- title: reason === "watch" ? "analyze refresh completed" : "initial analyze completed",
1873
- detail: next.mode === "fallback" ? `analyze 已回退到 dist 扫描,当前包含 ${next.result.packages.length} 个包。` : `analyze 已刷新完成,当前包含 ${next.result.packages.length} 个包与 ${next.result.modules.length} 个模块。`,
1874
- durationMs: next.durationMs,
1875
- tags: next.mode === "fallback" ? ["analyze", "fallback"] : ["analyze", reason === "watch" ? "refresh" : "initial"]
1876
- }]);
1877
- };
1878
- if (targets.runMini) {
1879
- const miniBuildStartedAt = Date.now();
1880
- const buildResult = await buildService.build(options);
1881
- logger_default.success(`小程序初次构建完成,耗时:${formatDuration(Date.now() - miniBuildStartedAt)}`);
1882
- if (enableAnalyze) {
1883
- const initialAnalyze = await runAnalyze();
1884
- analyzeHandle = await startAnalyzeDashboard(initialAnalyze.result, {
1885
- watch: true,
1886
- cwd: configService.cwd,
1887
- packageManagerAgent: configService.packageManager.agent,
1888
- silentStartupLog: true
1889
- }) ?? void 0;
2591
+ const triggerAnalyzeUpdate = async (reason = "watch") => {
2592
+ if (!analyzeHandle) return;
1890
2593
  emitDashboardEvents(analyzeHandle, [{
1891
- kind: "command",
1892
- level: "success",
1893
- title: "dev ui session ready",
1894
- detail: `开发态分析面板已启动,当前包含 ${initialAnalyze.result.packages.length} 个包。`,
1895
- durationMs: initialAnalyze.durationMs,
1896
- tags: ["dev", "ui"]
2594
+ kind: reason === "watch" ? "hmr" : "build",
2595
+ level: "info",
2596
+ title: reason === "watch" ? "analyze refresh started" : "initial analyze started",
2597
+ detail: reason === "watch" ? "检测到新的构建结束事件,开始刷新 analyze 面板。" : "开发态 UI 已启动,开始生成第一份 analyze 结果。",
2598
+ tags: reason === "watch" ? ["watch", "analyze"] : ["initial", "analyze"]
1897
2599
  }]);
1898
- if (initialAnalyze.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
2600
+ const next = await runAnalyze();
2601
+ if (next.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
1899
2602
  kind: "diagnostic",
1900
2603
  level: "warning",
1901
- title: "initial analyze fallback enabled",
1902
- detail: initialAnalyze.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。",
1903
- durationMs: initialAnalyze.durationMs,
1904
- tags: [
1905
- "analyze",
1906
- "fallback",
1907
- "initial"
1908
- ]
2604
+ title: "analyze fallback enabled",
2605
+ detail: next.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。",
2606
+ durationMs: next.durationMs,
2607
+ tags: ["analyze", "fallback"]
1909
2608
  }]);
1910
- let updating = false;
1911
- if (analyzeHandle && buildResult && typeof buildResult.on === "function") buildResult.on("event", (event) => {
1912
- if (event.code !== "END" || updating) return;
1913
- updating = true;
1914
- triggerAnalyzeUpdate("watch").finally(() => {
1915
- updating = false;
2609
+ await analyzeHandle.update(next.result);
2610
+ emitDashboardEvents(analyzeHandle, [{
2611
+ kind: next.mode === "fallback" ? "diagnostic" : "build",
2612
+ level: next.mode === "fallback" ? "warning" : "success",
2613
+ title: reason === "watch" ? "analyze refresh completed" : "initial analyze completed",
2614
+ detail: next.mode === "fallback" ? `analyze 已回退到 dist 扫描,当前包含 ${next.result.packages.length} 个包。` : `analyze 已刷新完成,当前包含 ${next.result.packages.length} 个包与 ${next.result.modules.length} 个模块。`,
2615
+ durationMs: next.durationMs,
2616
+ tags: next.mode === "fallback" ? ["analyze", "fallback"] : ["analyze", reason === "watch" ? "refresh" : "initial"]
2617
+ }]);
2618
+ };
2619
+ if (targets.runMini) {
2620
+ const miniBuildStartedAt = Date.now();
2621
+ const buildResult = await buildService.build(options);
2622
+ logger_default.success(`小程序初次构建完成,耗时:${formatDuration(Date.now() - miniBuildStartedAt)}`);
2623
+ if (enableAnalyze) {
2624
+ const initialAnalyze = await runAnalyze();
2625
+ analyzeHandle = await startAnalyzeDashboard(initialAnalyze.result, {
2626
+ watch: true,
2627
+ cwd: configService.cwd,
2628
+ packageManagerAgent: configService.packageManager.agent,
2629
+ silentStartupLog: true
2630
+ }) ?? void 0;
2631
+ emitDashboardEvents(analyzeHandle, [{
2632
+ kind: "command",
2633
+ level: "success",
2634
+ title: "dev ui session ready",
2635
+ detail: `开发态分析面板已启动,当前包含 ${initialAnalyze.result.packages.length} 个包。`,
2636
+ durationMs: initialAnalyze.durationMs,
2637
+ tags: ["dev", "ui"]
2638
+ }]);
2639
+ if (initialAnalyze.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
2640
+ kind: "diagnostic",
2641
+ level: "warning",
2642
+ title: "initial analyze fallback enabled",
2643
+ detail: initialAnalyze.fallbackReason ?? "完整分析不可用,已回退到 dist 文件扫描。",
2644
+ durationMs: initialAnalyze.durationMs,
2645
+ tags: [
2646
+ "analyze",
2647
+ "fallback",
2648
+ "initial"
2649
+ ]
2650
+ }]);
2651
+ let updating = false;
2652
+ if (analyzeHandle && buildResult && typeof buildResult.on === "function") buildResult.on("event", (event) => {
2653
+ if (event.code !== "END" || updating) return;
2654
+ updating = true;
2655
+ triggerAnalyzeUpdate("watch").finally(() => {
2656
+ updating = false;
2657
+ });
1916
2658
  });
1917
- });
1918
- if (analyzeHandle) {
1919
- updating = true;
1920
- await triggerAnalyzeUpdate("initial");
1921
- updating = false;
2659
+ if (analyzeHandle) {
2660
+ updating = true;
2661
+ await triggerAnalyzeUpdate("initial");
2662
+ updating = false;
2663
+ }
1922
2664
  }
1923
2665
  }
1924
- }
1925
- let webServer;
1926
- if (targets.runWeb) {
1927
- const webServerStartedAt = Date.now();
1928
- try {
1929
- webServer = await webService?.startDevServer();
1930
- logger_default.success(`Web 开发服务启动完成,耗时:${formatDuration(Date.now() - webServerStartedAt)}`);
1931
- emitDashboardEvents(analyzeHandle, [{
1932
- kind: "system",
1933
- level: "success",
1934
- title: "web dev server started",
1935
- detail: "Web 开发服务器已启动,可与小程序调试 UI 并行工作。",
1936
- durationMs: Date.now() - webServerStartedAt,
1937
- tags: ["dev", "web"]
1938
- }]);
1939
- } catch (error) {
2666
+ let webServer;
2667
+ if (targets.runWeb) {
2668
+ const webServerStartedAt = Date.now();
2669
+ try {
2670
+ webServer = await webService?.startDevServer();
2671
+ logger_default.success(`Web 开发服务启动完成,耗时:${formatDuration(Date.now() - webServerStartedAt)}`);
2672
+ emitDashboardEvents(analyzeHandle, [{
2673
+ kind: "system",
2674
+ level: "success",
2675
+ title: "web dev server started",
2676
+ detail: "Web 开发服务器已启动,可与小程序调试 UI 并行工作。",
2677
+ durationMs: Date.now() - webServerStartedAt,
2678
+ tags: ["dev", "web"]
2679
+ }]);
2680
+ } catch (error) {
2681
+ emitDashboardEvents(analyzeHandle, [{
2682
+ kind: "diagnostic",
2683
+ level: "error",
2684
+ title: "web dev server failed",
2685
+ detail: error instanceof Error ? error.message : String(error),
2686
+ durationMs: Date.now() - webServerStartedAt,
2687
+ tags: ["dev", "web"]
2688
+ }]);
2689
+ logger_default.error(error);
2690
+ throw error;
2691
+ }
2692
+ }
2693
+ if (targets.runMini) {
2694
+ logBuildAppFinish(configService, webServer, {
2695
+ skipWeb: !targets.runWeb,
2696
+ uiUrls: analyzeHandle?.urls
2697
+ });
2698
+ devHotkeysSession?.restore();
2699
+ } else if (targets.runWeb) logBuildAppFinish(configService, webServer, { skipMini: true });
2700
+ if (options.open && targets.runMini) {
1940
2701
  emitDashboardEvents(analyzeHandle, [{
1941
- kind: "diagnostic",
1942
- level: "error",
1943
- title: "web dev server failed",
1944
- detail: error instanceof Error ? error.message : String(error),
1945
- durationMs: Date.now() - webServerStartedAt,
1946
- tags: ["dev", "web"]
2702
+ kind: "command",
2703
+ level: "info",
2704
+ title: "opening ide",
2705
+ detail: "开发服务已就绪,准备打开 IDE 项目。",
2706
+ tags: ["ide", "open"]
1947
2707
  }]);
1948
- logger_default.error(error);
1949
- throw error;
2708
+ if (!await maybeStartForwardConsole({
2709
+ platform: configService.platform,
2710
+ mpDistRoot: configService.mpDistRoot,
2711
+ cwd: configService.cwd,
2712
+ weappViteConfig: configService.weappViteConfig
2713
+ })) await openIde(configService.platform, resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd), { trustProject: options.trustProject });
2714
+ devHotkeysSession?.restore();
1950
2715
  }
2716
+ if (analyzeHandle) await analyzeHandle.waitForExit();
2717
+ else if (targets.runMini || targets.runWeb) await waitForServeShutdownSignal();
2718
+ } finally {
2719
+ devHotkeysSession?.close();
2720
+ ctx.watcherService?.closeAll();
1951
2721
  }
1952
- if (targets.runMini) logBuildAppFinish(configService, webServer, {
1953
- skipWeb: !targets.runWeb,
1954
- uiUrls: analyzeHandle?.urls
1955
- });
1956
- else if (targets.runWeb) logBuildAppFinish(configService, webServer, { skipMini: true });
1957
- if (options.open && targets.runMini) {
1958
- emitDashboardEvents(analyzeHandle, [{
1959
- kind: "command",
1960
- level: "info",
1961
- title: "opening ide",
1962
- detail: "开发服务已就绪,准备打开 IDE 项目。",
1963
- tags: ["ide", "open"]
1964
- }]);
1965
- if (!await maybeStartForwardConsole({
1966
- platform: configService.platform,
1967
- mpDistRoot: configService.mpDistRoot,
1968
- cwd: configService.cwd,
1969
- weappViteConfig: configService.weappViteConfig
1970
- })) await openIde(configService.platform, resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd), { trustProject: options.trustProject });
1971
- }
1972
- if (analyzeHandle) await analyzeHandle.waitForExit();
1973
2722
  });
1974
2723
  }
1975
2724
  //#endregion
@@ -2058,13 +2807,12 @@ const SKIP_COMMANDS = new Set([
2058
2807
  "ide",
2059
2808
  "mcp"
2060
2809
  ]);
2061
- const DEV_COMMANDS = new Set(["dev", "serve"]);
2810
+ const REG_EADDRINUSE = /EADDRINUSE/;
2062
2811
  let started = false;
2063
2812
  function shouldAutoStartMcp(argv) {
2064
2813
  const command = argv[0];
2065
2814
  if (!command || command.startsWith("-")) return true;
2066
2815
  if (SKIP_COMMANDS.has(command)) return false;
2067
- if (DEV_COMMANDS.has(command)) return true;
2068
2816
  return command.includes("/") || command.includes("\\") || command.startsWith(".");
2069
2817
  }
2070
2818
  async function maybeAutoStartMcpServer(argv, cliOptions) {
@@ -2092,9 +2840,13 @@ async function maybeAutoStartMcpServer(argv, cliOptions) {
2092
2840
  const mcpUrl = `http://${resolvedMcp.host}:${resolvedMcp.port}${resolvedMcp.endpoint}`;
2093
2841
  logger_default.success("MCP 服务已自动启动:");
2094
2842
  logger_default.info(` ➜ ${colors.cyan(mcpUrl)}`);
2843
+ for (const line of formatMcpQuickStart({
2844
+ httpUrl: mcpUrl,
2845
+ transport: "http"
2846
+ })) logger_default.info(line);
2095
2847
  } catch (error) {
2096
2848
  const message = error instanceof Error ? error.message : String(error);
2097
- if (/EADDRINUSE/.test(message)) {
2849
+ if (REG_EADDRINUSE.test(message)) {
2098
2850
  logger_default.info(`[mcp] 端口 ${resolvedMcp.port} 已被占用,跳过自动启动。`);
2099
2851
  started = true;
2100
2852
  return;
@@ -2174,22 +2926,19 @@ function resolveManagedTsconfigBootstrapRoot(args) {
2174
2926
  return path.resolve(firstArg);
2175
2927
  }
2176
2928
  try {
2177
- Promise.resolve().then(async () => {
2178
- const args = process.argv.slice(2);
2179
- if (await tryRunIdeCommand(args)) return;
2929
+ const args = process.argv.slice(2);
2930
+ if (!await tryRunIdeCommand(args)) {
2180
2931
  const managedTsconfigBootstrapRoot = resolveManagedTsconfigBootstrapRoot(args);
2181
2932
  if (managedTsconfigBootstrapRoot) await syncManagedTsconfigBootstrapFiles(managedTsconfigBootstrapRoot);
2182
2933
  cli.parse(process.argv, { run: false });
2183
2934
  await maybeAutoStartMcpServer(args, cli.options);
2184
2935
  await cli.runMatchedCommand();
2185
- }).catch((error) => {
2186
- if (handlePrepareLifecycleError(process.argv.slice(2), error)) return;
2936
+ }
2937
+ } catch (error) {
2938
+ if (!handlePrepareLifecycleError(process.argv.slice(2), error)) {
2187
2939
  handleCLIError(error);
2188
2940
  process.exitCode = 1;
2189
- });
2190
- } catch (error) {
2191
- handleCLIError(error);
2192
- process.exitCode = 1;
2941
+ }
2193
2942
  }
2194
2943
  //#endregion
2195
2944
  export {};