UNPKG

weapp-vite

Version:

weapp-vite 一个现代化的小程序打包工具

1,184 lines 227 kB
import { C as parseCommentJson, D as isPathInside, E as shouldPassPlatformArgToIdeOpen, S as loadViteConfigFile, T as getDefaultIdeProjectRoot, _ as resolveHmrProfileJsonPath, a as formatBytes, b as checkRuntime, d as getBackendForCapability, f as resolveBackendExecution, g as SHARED_CHUNK_VIRTUAL_PREFIX, h as createSharedBuildConfig, m as syncManagedTsconfigBootstrapFiles, p as syncProjectSupportFiles, t as createCompilerContext, w as createCjsConfigLoadError, x as getProjectConfigFileName, y as resolveWeappConfigFile } from "./createContext-D9HCnUmH.mjs"; import { r as logger_default, t as colors } from "./logger-mt4mSTqV.mjs"; import { h as VERSION } from "./file-CFQYYS_3.mjs"; import { c as startWeappViteMcpServer, l as detectAiDevelopmentEnvironment, s as resolveWeappMcpConfig } from "./mcp-BG6TliEg.mjs"; import { createRequire } from "node:module"; import fs from "node:fs"; import path, { posix } from "pathe"; import path$1 from "node:path"; import { defu } from "@weapp-core/shared"; import { fs as fs$1 } from "@weapp-core/shared/fs"; import process from "node:process"; import fs$2, { mkdir, writeFile } from "node:fs/promises"; import { build, createServer } from "vite"; import os from "node:os"; import { execFile, spawn } from "node:child_process"; import { Buffer } from "node:buffer"; import { cac } from "cac"; import { RETRY_CANCEL_KEYS, RETRY_CONFIRM_KEYS, bootstrapWechatDevtoolsSettings, buildWechatIdeNpm, clearWechatIdeCache, clearWechatIdeCacheByAutomator, closeSharedMiniProgram, closeWechatIdeProject, compileWechatIdeByAutomator, connectOpenedAutomator, createSharedInputSession, createWechatIdeLoginRequiredExitError, dispatchWechatCliCommand, formatAutomatorLoginError, getConfig, getWechatIdeTestAccounts, getWechatIdeTicket, getWechatIdeToolInfo, isAutomatorLoginError, isWeappIdeTopLevelCommand, isWechatIdeEngineBuildEndpointMissingError, isWechatIdeLoginRequiredError, isWechatIdeLoginRequiredExitError, launchAutomator, parse, promptRetryKeypress, promptWechatIdeLoginRetry, quitWechatIde, refreshWechatIdeTicket, resetWechatIdeFileUtilsByHttp, resolveProjectAutomatorPort, runRetryableCommand, runWechatIdeEngineBuild, runWithSuspendedSharedInput, setWechatIdeTicket, startForwardConsole, takeScreenshot } from "weapp-ide-cli"; import { promisify } from "node:util"; import { brotliCompressSync, gzipSync } from "node:zlib"; import { resolveCommand } from "package-manager-detector/commands"; import { generateJs, generateJson, generateWxml, generateWxss } from "@weapp-core/schematics"; import { determineAgent } from "@vercel/detect-agent"; import { initConfig } from "@weapp-core/init"; import { createInterface } from "node:readline/promises"; import { clearTimeout as clearTimeout$1, setTimeout as setTimeout$1 } from "node:timers"; import net from "node:net"; //#region src/cli/runtime.ts function logRuntimeTarget(targets, options = {}) { if (options.silent) return; if (targets.label === "config") { const resolvedPlatform = targets.platform ?? options.resolvedConfigPlatform; if (resolvedPlatform) { logger_default.info(`目标平台:${colors.green(resolvedPlatform)}`); return; } logger_default.info(`目标平台:使用配置文件中的 ${colors.bold(colors.green("weapp.platform"))}`); return; } logger_default.info(`目标平台:${colors.green(targets.label)}`); } function resolveRuntimeTargets(options) { const rawPlatform = typeof options.platform === "string" ? options.platform : typeof options.p === "string" ? options.p : void 0; const execution = resolveBackendExecution(rawPlatform, { warn: (message) => logger_default.warn(message) }); const miniBackend = execution.get("miniprogram"); return { ...execution, platform: miniBackend?.platform, rawPlatform }; } function createInlineConfig(execution, options = {}) { const configs = execution.entries.map((entry) => entry.driver.createInlineConfig({ execution, platform: entry.platform, scope: options.scope, host: options.host })).filter((config) => Boolean(config)); if (configs.length === 0) return options.inlineConfig; const merged = configs.slice(1).reduce((merged, config) => defu(merged, config), configs[0]); return options.inlineConfig ? defu(options.inlineConfig, merged) : merged; } //#endregion //#region src/cli/openIde/execute.ts function readArgOption(argv, ...names) { for (let index = 0; index < argv.length; index += 1) { const current = argv[index]; if (!names.includes(current)) continue; const next = argv[index + 1]; if (typeof next === "string" && !next.startsWith("-")) return next; } } async function tryExecuteWechatIdeCliCommandByAutomator(argv, projectPath, options = {}) { if (!projectPath) return false; const command = argv[0]; if (!command) return false; if (command === "compile") { await compileWechatIdeByAutomator({ preserveProjectRoot: options.preserveProjectRoot, projectPath }); return true; } if (command === "cache") { const cleanType = readArgOption(argv, "--clean", "-c"); if (cleanType !== "compile" && cleanType !== "all") return false; await clearWechatIdeCacheByAutomator({ clean: cleanType, preserveProjectRoot: options.preserveProjectRoot, projectPath }); return true; } return false; } async function tryExecuteWechatIdeCliCommandByHttp(argv, projectPath, engineBuildFallbackToCli = false) { const command = argv[0]; if (!command) return false; if (command === "reset-fileutils") { if (!projectPath) return false; await resetWechatIdeFileUtilsByHttp(projectPath); return true; } if (command === "engine" && argv[1] === "build") { const engineProjectPath = argv[2] || projectPath; if (!engineProjectPath) return false; await runWechatIdeEngineBuild(engineProjectPath, { fallbackToCli: engineBuildFallbackToCli, logPath: readArgOption(argv, "--logPath", "-l") }); return true; } return false; } async function tryExecuteWechatIdeCliCommandByHelper(argv) { const command = argv[0]; if (!command) return false; if (command === "close") { await closeWechatIdeProject(); return true; } if (command === "quit") { await quitWechatIde(); return true; } if (command === "cache") { const cleanType = readArgOption(argv, "--clean", "-c"); if (!cleanType) return false; await clearWechatIdeCache({ clean: cleanType }); return true; } return false; } /** * @description 统一执行 weapp-ide-cli 命令,并在登录失效时复用同一套重试交互。 */ async function executeWechatIdeCliCommand(argv, options = {}) { const { automatorMode = "prefer", cancelLevel = "warn", engineBuildFallbackToCli = false, httpMode = "prefer", promptOpenIdeLogin = true, onNonLoginError, onRetry, projectPath, preserveProjectRoot } = options; await runWithSuspendedSharedInput(async () => { if (httpMode !== "skip") try { if (await tryExecuteWechatIdeCliCommandByHttp(argv, projectPath, engineBuildFallbackToCli)) return; } catch (error) { if (httpMode === "require" || isWechatIdeEngineBuildEndpointMissingError(error)) throw error; } if (automatorMode !== "skip") try { if (await tryExecuteWechatIdeCliCommandByAutomator(argv, projectPath, { preserveProjectRoot })) return; } catch (error) { if (automatorMode === "require") throw error; } try { if (await tryExecuteWechatIdeCliCommandByHelper(argv)) return; } catch (error) { if (isWechatIdeLoginRequiredExitError(error)) throw error; if (onNonLoginError) { onNonLoginError(error); return; } throw error; } await runRetryableCommand({ createCancelError: (error) => createWechatIdeLoginRequiredExitError(error, "cancelled"), execute: async () => { try { await parse(argv); return null; } catch (error) { if (isWechatIdeLoginRequiredExitError(error)) throw error; if (!isWechatIdeLoginRequiredError(error)) { if (onNonLoginError) { onNonLoginError(error); return null; } throw error; } return error; } }, isRetryableResult: (result) => result !== null, onCancel: () => {}, onRetry: () => { onRetry?.(); }, promptRetry: async (error) => await promptWechatIdeLoginRetry({ cancelLevel, error, logger: logger_default, promptOpenIdeLogin }), shouldRetry: (action) => action === "retry" }); }); } //#endregion //#region src/cli/openIde/close.ts const execFileAsync = promisify(execFile); async function closeIdeByAppleScript() { if (process.platform !== "darwin") return false; const appName = process.env.WEAPP_DEVTOOLS_APP_NAME || "wechatwebdevtools"; try { await execFileAsync("osascript", ["-e", `tell application "${appName}" to quit`]); return true; } catch { return false; } } async function closeIdeByProcessKill(cliPath) { if (!cliPath) return false; const appContentsRoot = cliPath.includes(".app/") ? cliPath.slice(0, cliPath.indexOf(".app/") + 4) : path.dirname(path.dirname(cliPath)); try { await execFileAsync("pkill", ["-f", appContentsRoot]); return true; } catch { return false; } } /** * @description 关闭微信开发者工具,并在 CLI 不可用时回退到系统级关闭。 */ async function closeIde$1() { const config = await getConfig(); const cliPath = config.cliPath?.trim() ? config.cliPath : null; try { await closeWechatIdeProject(); return true; } catch (error) { if (isWechatIdeLoginRequiredError(error)) try { await executeWechatIdeCliCommand(["close"], { cancelLevel: "warn", onNonLoginError: (retryError) => logger_default.error(retryError), onRetry: () => logger_default.info("正在重试连接微信开发者工具...") }); return true; } catch (retryError) { logger_default.error(retryError); } else { logger_default.warn("微信开发者工具 CLI close 执行失败,尝试回退为系统级关闭。"); logger_default.error(error); } if (await closeIdeByAppleScript()) { logger_default.info("已回退为系统级关闭微信开发者工具。"); return true; } if (await closeIdeByProcessKill(cliPath)) { logger_default.info("已回退为进程级关闭微信开发者工具。"); return true; } return false; } } //#endregion //#region src/cli/openIde/diagnostics.ts /** * @description 输出微信开发者工具打开后的自助恢复步骤。 */ function logWechatIdeRecoveryHint(options) { const lines = [ `微信开发者工具打开后状态可能不稳定:${options.reason}`, "可按下面顺序恢复:", "1. 在微信开发者工具中确认:设置 -> 安全设置 -> 服务端口已开启。", "2. 仅当项目索引刷新失败时,默认会自动关闭并重开一次当前目标项目;如需跳过,传入 `--no-open-recovery` 或设置 `WEAPP_VITE_DISABLE_IDE_OPEN_RECOVERY=1`。", "3. 如果仍然回到项目选择页,手动导入 project.config.json 所在目录,并关闭多余的微信开发者工具窗口后重试。", "4. 需要查看底层错误时,设置 `WEAPP_VITE_DEBUG_AUTOMATOR_OPEN=1` 后重试。" ]; if (options.projectPath) lines.push(`当前目标项目目录:${options.projectPath}`); logger_default.warn(lines.join("\n")); } /** * @description 输出服务端口关闭时的定向恢复提示。 */ function logWechatIdeServicePortDisabledHint(projectPath) { const lines = [ "检测到微信开发者工具服务端口当前处于关闭状态,已保留用户设置并回退到普通 open 流程。", "自动刷新、截图、MCP 和 IDE 联动能力需要服务端口。", "请在微信开发者工具中打开:设置 -> 安全设置 -> 服务端口,然后重新执行当前 dev/open 命令。" ]; if (projectPath) lines.push(`当前目标项目目录:${projectPath}`); logger_default.warn(lines.join("\n")); } //#endregion //#region src/cli/openIde/reuse.ts const OPENED_PROJECT_HEALTH_CHECK_TIMEOUT = 3e3; const OPEN_AUTOMATOR_TIMEOUT = 12e4; function formatReuseOpenedWechatIdePrompt() { return `目标项目已在微信开发者工具中打开,已跳过重复打开。按 ${RETRY_CONFIRM_KEYS.map((key) => colors.bold(colors.green(key))).join(" / ")} 关闭当前窗口后重新打开。`; } function disconnectMiniProgram(miniProgram) { miniProgram.disconnect(); } function withTimeout(task, timeoutMs) { let timer; const timeout = new Promise((_, reject) => { timer = setTimeout(() => { reject(/* @__PURE__ */ new Error(`opened automator health check timed out after ${timeoutMs}ms`)); }, timeoutMs); }); return Promise.race([task, timeout]).finally(() => { if (timer) clearTimeout(timer); }); } async function verifyOpenedProjectHealth(miniProgram) { if (typeof miniProgram.screenshot !== "function") return; await withTimeout(miniProgram.screenshot({ timeout: OPENED_PROJECT_HEALTH_CHECK_TIMEOUT }), OPENED_PROJECT_HEALTH_CHECK_TIMEOUT); } async function openWechatIdeByAutomator(projectPath) { disconnectMiniProgram(await launchAutomator({ persistAsDefaultSession: true, preserveProjectRoot: true, projectPath, port: resolveProjectAutomatorPort(projectPath), timeout: OPEN_AUTOMATOR_TIMEOUT, trustProject: true })); } async function connectOpenedProject(projectPath) { let miniProgram = null; try { miniProgram = await connectOpenedAutomator({ projectPath, port: resolveProjectAutomatorPort(projectPath), timeout: 3e3 }); await verifyOpenedProjectHealth(miniProgram); return miniProgram; } catch { if (miniProgram) disconnectMiniProgram(miniProgram); return null; } } /** * @description 若当前项目已在微信开发者工具中打开且自动化可连通,则直接复用现有会话,避免重复拉起 IDE。 */ async function tryReuseOpenedWechatIde(projectPath, closeIde, options = {}) { const miniProgram = await connectOpenedProject(projectPath); if (!miniProgram) return null; disconnectMiniProgram(miniProgram); if (options.promptReopen === false) { logger_default.info("目标项目已在微信开发者工具中打开,已跳过重复打开。"); return { reopened: false, reused: true }; } logger_default.info(formatReuseOpenedWechatIdePrompt()); if (await promptRetryKeypress({ logger: logger_default }) !== "retry") return { reopened: false, reused: true }; logger_default.info(colors.bold(colors.green("正在关闭当前已打开项目,并重新拉起微信开发者工具..."))); if (!await closeIde()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。"); await openWechatIdeByAutomator(projectPath); return { reopened: true, reused: false }; } /** * @description 对已打开的目标项目执行强制重开,以刷新最新构建产物。 */ async function reopenOpenedWechatIde(projectPath, closeIde) { const miniProgram = await connectOpenedProject(projectPath); if (!miniProgram) return false; disconnectMiniProgram(miniProgram); logger_default.info("目标项目已在微信开发者工具中打开,当前命令将主动重开以刷新最新构建产物。"); if (!await closeIde()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。"); await openWechatIdeByAutomator(projectPath); return true; } //#endregion //#region src/cli/openIde/index.ts function shouldLogAutomatorFallbackError() { const flag = process.env.WEAPP_VITE_DEBUG_AUTOMATOR_OPEN; return flag === "1" || flag === "true"; } const PREPARE_AUTOMATOR_SESSION_TIMEOUT = 8e3; const RESET_FILEUTILS_ENV = "WEAPP_VITE_RESET_IDE_FILEUTILS"; function isWechatIdeOpenRecoveryDisabled(options) { if (options.openRecovery === false) return true; const flag = process.env.WEAPP_VITE_DISABLE_IDE_OPEN_RECOVERY; return flag === "1" || flag === "true"; } function shouldResetWechatIdeFileUtils() { const flag = process.env[RESET_FILEUTILS_ENV]; return flag === "1" || flag === "true"; } /** * @description 执行 IDE 打开流程,并在登录失效时允许按键重试。 */ async function runWechatIdeOpenWithRetry(argv) { await executeWechatIdeCliCommand(argv, { cancelLevel: "warn", onNonLoginError: (error) => logger_default.error(error), onRetry: () => { logger_default.info(colors.bold(colors.green("正在重试连接微信开发者工具..."))); }, promptOpenIdeLogin: true }); } /** * @description 根据 mpDistRoot 推导 IDE 项目目录(目录内应包含 project/mini 配置) */ function resolveIdeProjectPath(mpDistRoot) { if (!mpDistRoot || !mpDistRoot.trim()) return; const parent = path.dirname(mpDistRoot); if (!parent || parent === "." || parent === "/") return; return parent; } /** * @description 结合 mpDistRoot 与配置根目录解析最终 IDE 项目目录。 */ function resolveIdeProjectRoot(mpDistRoot, cwd) { return resolveIdeProjectPath(mpDistRoot) ?? cwd; } async function closeIde() { return await closeIde$1(); } async function tryOpenWechatIdeByAutomator(projectPath, options) { if (options.reuseOpenedProject === false) { if (await reopenOpenedWechatIde(projectPath, closeIde)) return "reopened"; } const reuseResult = await tryReuseOpenedWechatIde(projectPath, closeIde, { promptReopen: options.reuseOpenedProject !== true }); if (reuseResult?.reused) return "reused"; if (reuseResult?.reopened) return "reopened"; await openWechatIdeByAutomator(projectPath); return "opened"; } /** * @description 打开后主动刷新微信开发者工具的项目索引,避免模拟器沿用过期 app 配置。 */ function appendLoginRetryArgv(argv, options) { if (options.nonInteractive) argv.push("--non-interactive"); if (options.loginRetry) argv.push("--login-retry", options.loginRetry); if (options.loginRetryTimeout) argv.push("--login-retry-timeout", options.loginRetryTimeout); return argv; } function createIdeOpenArgv(platform, projectPath, options = {}) { const argv = ["open", "-p"]; if (projectPath) argv.push(projectPath); if (platform === "weapp" && options.trustProject !== false) argv.push("--trust-project"); if (platform && shouldPassPlatformArgToIdeOpen(platform)) argv.push("--platform", platform); if (options.nonInteractive) argv.push("--non-interactive"); if (options.loginRetry) argv.push("--login-retry", options.loginRetry); if (options.loginRetryTimeout) argv.push("--login-retry-timeout", options.loginRetryTimeout); return argv; } async function prepareOpenedWechatIdeAutomatorSession(projectPath, options) { try { (await launchAutomator({ persistAsDefaultSession: true, preserveProjectRoot: true, projectPath, port: resolveProjectAutomatorPort(projectPath), timeout: PREPARE_AUTOMATOR_SESSION_TIMEOUT, trustProject: options.trustProject !== false })).disconnect?.(); return { ok: true }; } catch (error) { logger_default.warn("准备当前项目的微信开发者工具自动化会话失败,截图、MCP 或 IDE 联动命令首次运行时将重新连接。"); logWechatIdeRecoveryHint({ projectPath, reason: "无法建立当前项目的自动化会话,常见原因是 DevTools 服务端口未就绪、窗口停留在项目选择页,或存在残留 DevTools 会话。" }); if (shouldLogAutomatorFallbackError()) logger_default.error(error); return { ok: false, reason: "automator-session-failed", error }; } } async function connectOpenedWechatIdeAutomatorSession(projectPath) { try { (await connectOpenedAutomator({ projectPath, port: resolveProjectAutomatorPort(projectPath), timeout: PREPARE_AUTOMATOR_SESSION_TIMEOUT })).disconnect?.(); return { ok: true }; } catch (error) { logger_default.warn("连接当前项目的微信开发者工具自动化会话失败,截图、MCP 或 IDE 联动命令首次运行时将重新连接。"); logWechatIdeRecoveryHint({ projectPath, reason: "当前项目已完成打开流程,但尚未连接到可复用的自动化会话。" }); if (shouldLogAutomatorFallbackError()) logger_default.error(error); return { ok: false, reason: "automator-session-failed", error }; } } async function prepareWechatIdeAutomatorSession(projectPath, options) { if (options.prepareAutomatorSession === "connect-opened") return await connectOpenedWechatIdeAutomatorSession(projectPath); return await prepareOpenedWechatIdeAutomatorSession(projectPath, options); } async function stabilizeOpenedWechatIdeProject(projectPath, servicePortEnabled, options = {}) { if (servicePortEnabled === false) return { ok: false, reason: "service-port-disabled" }; try { if (shouldResetWechatIdeFileUtils()) await executeWechatIdeCliCommand(appendLoginRetryArgv([ "reset-fileutils", "-p", projectPath ], options), { automatorMode: options.useAutomatorOpen === false ? "skip" : "prefer", httpMode: "require", onNonLoginError: (error) => logger_default.error(error), preserveProjectRoot: options.useAutomatorOpen === false, projectPath }); try { await executeWechatIdeCliCommand(appendLoginRetryArgv([ "engine", "build", projectPath ], options), { automatorMode: options.useAutomatorOpen === false ? "skip" : "prefer", engineBuildFallbackToCli: true, httpMode: "prefer", onNonLoginError: (error) => logger_default.error(error), preserveProjectRoot: options.useAutomatorOpen === false, projectPath }); } catch (error) { if (!isWechatIdeEngineBuildEndpointMissingError(error)) throw error; logger_default.warn("当前微信开发者工具不支持自动 engine build 刷新,已跳过该步骤;如模拟器显示旧状态,可在开发者工具内手动编译。"); } if (options.useAutomatorOpen !== false && options.skipAutomatorCompile !== true) try { await executeWechatIdeCliCommand(appendLoginRetryArgv(["compile"], options), { automatorMode: "require", httpMode: "skip", preserveProjectRoot: true, projectPath }); } catch (error) { if (shouldLogAutomatorFallbackError()) logger_default.error(error); } return { ok: true }; } catch (error) { if (isWechatIdeLoginRequiredExitError(error)) throw error; logger_default.warn("刷新微信开发者工具项目索引失败,已保留当前打开状态;如模拟器仍显示旧状态,可手动刷新一次。"); logWechatIdeRecoveryHint({ projectPath, reason: "打开项目后的文件索引刷新失败,DevTools 可能仍在使用旧项目状态或内部服务未就绪。" }); if (shouldLogAutomatorFallbackError()) logger_default.error(error); return { ok: false, reason: "index-refresh-failed", error }; } } async function verifyOpenedWechatIdeProject(projectPath, servicePortEnabled, options) { const stabilizeResult = await stabilizeOpenedWechatIdeProject(projectPath, servicePortEnabled, options); if (!stabilizeResult.ok) return stabilizeResult; if (options.useAutomatorOpen === false && servicePortEnabled !== false) return await prepareOpenedWechatIdeAutomatorSession(projectPath, options); return stabilizeResult; } function formatWechatIdeOpenHealthReason(result) { if (result.reason === "index-refresh-failed") return "项目索引刷新失败"; return "服务端口未开启"; } async function recoverOpenedWechatIdeProject(platform, projectPath, servicePortEnabled, options, failedResult) { if (failedResult.reason === "service-port-disabled") return failedResult; if (failedResult.reason === "automator-session-failed") { logger_default.warn("已跳过微信开发者工具自动恢复;自动化会话预热失败不影响当前项目打开,截图、MCP 或 IDE 联动命令首次运行时会重新连接。"); return failedResult; } if (isWechatIdeOpenRecoveryDisabled(options)) { logger_default.warn("已跳过微信开发者工具自动恢复;请按上方提示手动关闭并重新打开目标项目。"); return failedResult; } logger_default.info(`检测到微信开发者工具打开后状态不稳定(${formatWechatIdeOpenHealthReason(failedResult)}),正在自动关闭并重新打开目标项目...`); if (!await closeIde()) logger_default.warn("自动恢复时关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。"); await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, options)); const recoveredResult = await verifyOpenedWechatIdeProject(projectPath, servicePortEnabled, options); if (recoveredResult.ok) logger_default.info("微信开发者工具已完成自动恢复。"); else logger_default.warn("微信开发者工具自动恢复未完成;可设置 `WEAPP_VITE_DISABLE_IDE_OPEN_RECOVERY=1` 或传入 `--no-open-recovery` 跳过自动关闭重开,并按提示手动处理。"); return recoveredResult; } async function verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, servicePortEnabled, options) { const healthResult = await verifyOpenedWechatIdeProject(projectPath, servicePortEnabled, options); if (!healthResult.ok) await recoverOpenedWechatIdeProject(platform, projectPath, servicePortEnabled, options, healthResult); } async function openIde(platform, projectPath, options = {}) { let bootstrapResult; const useAutomatorOpen = options.useAutomatorOpen === true; const normalizedOptions = { ...options, useAutomatorOpen }; if (platform === "weapp" && projectPath) try { bootstrapResult = await bootstrapWechatDevtoolsSettings({ projectPath, trustProject: normalizedOptions.trustProject }); } catch (error) { logger_default.warn("检测微信开发者工具服务端口或写入项目信任状态失败,继续执行 open 流程。"); logger_default.error(error); } if (platform === "weapp" && projectPath && bootstrapResult?.servicePortEnabled === false) logWechatIdeServicePortDisabledHint(projectPath); if (platform === "weapp" && projectPath && normalizedOptions.trustProject !== false && bootstrapResult?.servicePortEnabled !== false && useAutomatorOpen) try { const openResult = await tryOpenWechatIdeByAutomator(projectPath, normalizedOptions); if (openResult === "reused") return; if (openResult) { if (!normalizedOptions.skipPostOpenHealthCheck) await verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, bootstrapResult?.servicePortEnabled, normalizedOptions); return; } } catch (error) { if (isAutomatorLoginError(error)) { logger_default.error("检测到微信开发者工具登录状态失效,请先登录后重试。"); logger_default.warn(formatAutomatorLoginError(error)); } logger_default.warn("通过 automator 启动微信开发者工具并自动信任项目失败,回退到普通 open 流程。"); if (shouldLogAutomatorFallbackError()) logger_default.error(error); } else if (platform === "weapp" && projectPath && normalizedOptions.reuseOpenedProject === false) { if (!await closeIde()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试打开目标项目。"); } await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, normalizedOptions)); if (platform === "weapp" && projectPath && normalizedOptions.prepareAutomatorSession) await prepareWechatIdeAutomatorSession(projectPath, normalizedOptions); if (platform === "weapp" && projectPath && !normalizedOptions.skipPostOpenHealthCheck) await verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, bootstrapResult?.servicePortEnabled, normalizedOptions); } /** * @description 解析 IDE 相关命令所需的平台、项目目录与配置上下文。 */ async function resolveIdeCommandContext(options) { const cwd = options.cwd ?? process.cwd(); let platform = options.platform; let projectPath = options.projectPath; if (!platform || !projectPath) try { const targets = resolveRuntimeTargets({ platform }); const ctx = await createCompilerContext({ cwd, mode: options.mode ?? "development", configFile: options.configFile, inlineConfig: createInlineConfig(targets), cliPlatform: options.cliPlatform }); platform ??= ctx.configService.platform; if (!projectPath) projectPath = resolveIdeProjectRoot(ctx.configService.mpDistRoot, ctx.configService.cwd); return { cwd: ctx.configService.cwd, platform, projectPath, weappViteConfig: ctx.configService.weappViteConfig, mpDistRoot: ctx.configService.mpDistRoot }; } catch {} if (!projectPath) { const defaultProjectRoot = getDefaultIdeProjectRoot(platform); if (defaultProjectRoot) projectPath = resolveIdeProjectRoot(defaultProjectRoot, cwd); } return { cwd, platform, projectPath }; } //#endregion //#region src/cli/options.ts function filterDuplicateOptions(options) { for (const [key, value] of Object.entries(options)) if (Array.isArray(value)) options[key] = value[value.length - 1]; } function resolveConfigFile(options) { if (typeof options.config === "string") return options.config; if (typeof options.c === "string") return options.c; } function convertBase(value) { if (value === 0) return ""; return value; } function coerceBooleanOption(value) { if (value === void 0) return; if (typeof value === "boolean") return value; if (typeof value === "string") { const normalized = value.trim().toLowerCase(); if (normalized === "") return true; if (normalized === "false" || normalized === "0" || normalized === "off" || normalized === "no") return false; if (normalized === "true" || normalized === "1" || normalized === "on" || normalized === "yes") return true; return true; } if (typeof value === "number") return value !== 0; return Boolean(value); } function isUiEnabled(options) { return Boolean(options.ui || options.analyze); } //#endregion //#region src/cli/commands/alipayExecute.ts function createSpawnOptions() { return { shell: process.platform === "win32", stdio: "inherit" }; } /** * @description 执行本机 minidev 命令。 */ async function runSpawnMinidev(command, argv, runner) { await new Promise((resolve, reject) => { const child = runner(command, argv, createSpawnOptions()); child.on("error", (error) => { reject(error); }); child.on("exit", (code, signal) => { if (code === 0) { resolve(); return; } reject(/* @__PURE__ */ new Error(signal ? `minidev ${argv[0] ?? ""} exited with signal ${signal}` : `minidev ${argv[0] ?? ""} exited with code ${code ?? "unknown"}`)); }); }); } /** * @description 使用系统进程执行本机 minidev 命令。 */ async function spawnMinidev(command, argv) { return await runSpawnMinidev(command, argv, spawn); } //#endregion //#region src/cli/commands/alipay.ts function normalizePassthroughArgs(args) { return Array.isArray(args) ? args.filter((arg) => typeof arg === "string") : []; } function appendOption(argv, name, value) { if (typeof value === "string" && value.trim()) argv.push(name, value); } function hasOption(argv, ...names) { return argv.some((arg) => names.includes(arg) || names.some((name) => arg.startsWith(`${name}=`))); } function normalizeAlipayAction(action) { if (action === "open") return "ide"; if (action === "ide" || action === "login" || action === "preview" || action === "upload") return action; throw new Error(`未知 alipay 子命令: ${action ?? "(empty)"}`); } function resolveProjectPath(root, resolvedProjectPath, options) { if (typeof options.project === "string" && options.project.trim()) return options.project; return root ?? resolvedProjectPath; } function resolveMinidevCommand(options) { return typeof options.minidev === "string" && options.minidev.trim() ? options.minidev : "minidev"; } function createMinidevArgv(action, root, resolvedProjectPath, options) { const passthroughArgs = normalizePassthroughArgs(options["--"]); const argv = [action]; const projectPath = resolveProjectPath(root, resolvedProjectPath, options); if ((action === "ide" || action === "preview" || action === "upload") && projectPath && !hasOption(passthroughArgs, "--project", "-p")) argv.push("--project", path.normalize(projectPath)); if ((action === "preview" || action === "upload") && !hasOption(passthroughArgs, "--app-id", "-a")) appendOption(argv, "--app-id", options.appId); if ((action === "login" || action === "preview" || action === "upload") && !hasOption(passthroughArgs, "--client-type", "-c")) appendOption(argv, "--client-type", options.clientType); if (action === "upload" && !hasOption(passthroughArgs, "--version", "-v")) appendOption(argv, "--version", options.version); argv.push(...passthroughArgs); return argv; } async function runAlipayCommand(action, root, options) { const normalizedAction = normalizeAlipayAction(action); filterDuplicateOptions(options); const argv = createMinidevArgv(normalizedAction, root, (await resolveIdeCommandContext({ configFile: resolveConfigFile(options), mode: options.mode ?? (normalizedAction === "upload" ? "production" : "development"), platform: "alipay", projectPath: root ?? options.project, cliPlatform: "alipay" })).projectPath, options); const command = resolveMinidevCommand(options); logger_default.info(`执行支付宝小程序 CLI:${command} ${argv.join(" ")}`); await spawnMinidev(command, argv); } function registerAlipayCommand(cli) { cli.command("alipay [action] [root]", "run Alipay minidev ide, login, preview, or upload").option("-a, --app-id <appId>", "[string] Alipay mini program appId").option("-c, --client-type <clientType>", "[string] minidev client type").option("--minidev <command>", "[string] minidev executable path or command name").option("--project <path>", "[string] Alipay mini program project path").option("--version <version>", "[string] upload version").allowUnknownOptions().action(async (action, root, options) => { await runAlipayCommand(action, root, options); }); } //#endregion //#region src/analyze/hmr.ts function createMetricSummary(values) { if (!values.length) return { count: 0 }; const total = values.reduce((sum, value) => sum + value, 0); return { count: values.length, averageMs: total / values.length, maxMs: Math.max(...values) }; } function createOperationSummary(values) { if (!values.length) return { count: 0 }; const total = values.reduce((sum, value) => sum + value, 0); return { count: values.length, average: total / values.length, max: Math.max(...values) }; } function sortCountEntries(map) { return [...map.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([name, count]) => ({ name, count })); } function collectCounts(target, values) { for (const value of values ?? []) { if (!value) continue; target.set(value, (target.get(value) ?? 0) + 1); } } function isFiniteNumber$1(value) { return typeof value === "number" && Number.isFinite(value); } /** * @description 聚合 HMR JSONL profile,为命令行与后续仪表盘复用。 */ async function analyzeHmrProfile(options) { const lines = (await fs$1.readFile(options.profilePath, "utf8")).split(/\r?\n/); const samples = []; let skippedLineCount = 0; for (const line of lines) { const trimmed = line.trim(); if (!trimmed) continue; try { const parsed = JSON.parse(trimmed); if (!isFiniteNumber$1(parsed.totalMs)) { skippedLineCount += 1; continue; } samples.push(parsed); } catch { skippedLineCount += 1; } } const eventCounts = /* @__PURE__ */ new Map(); const dirtyReasonCounts = /* @__PURE__ */ new Map(); const pendingReasonCounts = /* @__PURE__ */ new Map(); const totalValues = []; const buildCoreValues = []; const buildStartValues = []; const pluginResolveValues = []; const transformValues = []; const coreTransformValues = []; const wevuTransformValues = []; const vueTransformValues = []; const vueReadSourceValues = []; const vueCompileValues = []; const vueFinalizeCompiledValues = []; const vueFinalizeCodeValues = []; const coreLoadValues = []; const entryLoadValues = []; const entryCodeReadValues = []; const entrySidecarResolveValues = []; const entryJsonReadValues = []; const entryVueConfigValues = []; const entryTemplateScanValues = []; const entryScriptSetupValues = []; const entryVueSignatureValues = []; const entryAutoImportValues = []; const entryPrepareValues = []; const entryEmitOutputValues = []; const entryStyleScanValues = []; const entryStyleReadValues = []; const entryResolveValues = []; const entryChunkEmitValues = []; const entryChunkLoadValues = []; const entryChunkEmitFileValues = []; const entryLayoutValues = []; const requestGlobalsValues = []; const weapiResolveValues = []; const renderStartValues = []; const generateBundleValues = []; const generateSharedValues = []; const generateRewriteValues = []; const generateModuleGraphValues = []; const snapshotResolveValues = []; const snapshotBuildValues = []; const writeValues = []; const watchToDirtyValues = []; const emitValues = []; const sharedChunkValues = []; const chunkEmitCountValues = []; const loadCountValues = []; const resolveCountValues = []; const skippedLoadedCountValues = []; for (const sample of samples) { totalValues.push(sample.totalMs); if (sample.event) eventCounts.set(sample.event, (eventCounts.get(sample.event) ?? 0) + 1); if (isFiniteNumber$1(sample.buildCoreMs)) buildCoreValues.push(sample.buildCoreMs); if (isFiniteNumber$1(sample.buildStartMs)) buildStartValues.push(sample.buildStartMs); if (isFiniteNumber$1(sample.pluginResolveMs)) pluginResolveValues.push(sample.pluginResolveMs); if (isFiniteNumber$1(sample.transformMs)) transformValues.push(sample.transformMs); if (isFiniteNumber$1(sample.coreTransformMs)) coreTransformValues.push(sample.coreTransformMs); if (isFiniteNumber$1(sample.wevuTransformMs)) wevuTransformValues.push(sample.wevuTransformMs); if (isFiniteNumber$1(sample.vueTransformMs)) vueTransformValues.push(sample.vueTransformMs); if (isFiniteNumber$1(sample.vueReadSourceMs)) vueReadSourceValues.push(sample.vueReadSourceMs); if (isFiniteNumber$1(sample.vueCompileMs)) vueCompileValues.push(sample.vueCompileMs); if (isFiniteNumber$1(sample.vueFinalizeCompiledMs)) vueFinalizeCompiledValues.push(sample.vueFinalizeCompiledMs); if (isFiniteNumber$1(sample.vueFinalizeCodeMs)) vueFinalizeCodeValues.push(sample.vueFinalizeCodeMs); if (isFiniteNumber$1(sample.coreLoadMs)) coreLoadValues.push(sample.coreLoadMs); if (isFiniteNumber$1(sample.entryLoadMs)) entryLoadValues.push(sample.entryLoadMs); if (isFiniteNumber$1(sample.entryCodeReadMs)) entryCodeReadValues.push(sample.entryCodeReadMs); if (isFiniteNumber$1(sample.entrySidecarResolveMs)) entrySidecarResolveValues.push(sample.entrySidecarResolveMs); if (isFiniteNumber$1(sample.entryJsonReadMs)) entryJsonReadValues.push(sample.entryJsonReadMs); if (isFiniteNumber$1(sample.entryVueConfigMs)) entryVueConfigValues.push(sample.entryVueConfigMs); if (isFiniteNumber$1(sample.entryTemplateScanMs)) entryTemplateScanValues.push(sample.entryTemplateScanMs); if (isFiniteNumber$1(sample.entryScriptSetupMs)) entryScriptSetupValues.push(sample.entryScriptSetupMs); if (isFiniteNumber$1(sample.entryVueSignatureMs)) entryVueSignatureValues.push(sample.entryVueSignatureMs); if (isFiniteNumber$1(sample.entryAutoImportMs)) entryAutoImportValues.push(sample.entryAutoImportMs); if (isFiniteNumber$1(sample.entryPrepareMs)) entryPrepareValues.push(sample.entryPrepareMs); if (isFiniteNumber$1(sample.entryEmitOutputMs)) entryEmitOutputValues.push(sample.entryEmitOutputMs); if (isFiniteNumber$1(sample.entryStyleScanMs)) entryStyleScanValues.push(sample.entryStyleScanMs); if (isFiniteNumber$1(sample.entryStyleReadMs)) entryStyleReadValues.push(sample.entryStyleReadMs); if (isFiniteNumber$1(sample.entryResolveMs)) entryResolveValues.push(sample.entryResolveMs); if (isFiniteNumber$1(sample.entryChunkEmitMs)) entryChunkEmitValues.push(sample.entryChunkEmitMs); if (isFiniteNumber$1(sample.entryChunkLoadMs)) entryChunkLoadValues.push(sample.entryChunkLoadMs); if (isFiniteNumber$1(sample.entryChunkEmitFileMs)) entryChunkEmitFileValues.push(sample.entryChunkEmitFileMs); if (isFiniteNumber$1(sample.entryLayoutMs)) entryLayoutValues.push(sample.entryLayoutMs); if (isFiniteNumber$1(sample.requestGlobalsMs)) requestGlobalsValues.push(sample.requestGlobalsMs); if (isFiniteNumber$1(sample.weapiResolveMs)) weapiResolveValues.push(sample.weapiResolveMs); if (isFiniteNumber$1(sample.renderStartMs)) renderStartValues.push(sample.renderStartMs); if (isFiniteNumber$1(sample.generateBundleMs)) generateBundleValues.push(sample.generateBundleMs); if (isFiniteNumber$1(sample.generateSharedMs)) generateSharedValues.push(sample.generateSharedMs); if (isFiniteNumber$1(sample.generateRewriteMs)) generateRewriteValues.push(sample.generateRewriteMs); if (isFiniteNumber$1(sample.generateModuleGraphMs)) generateModuleGraphValues.push(sample.generateModuleGraphMs); if (isFiniteNumber$1(sample.snapshotResolveMs)) snapshotResolveValues.push(sample.snapshotResolveMs); if (isFiniteNumber$1(sample.snapshotBuildMs)) snapshotBuildValues.push(sample.snapshotBuildMs); if (isFiniteNumber$1(sample.writeMs)) writeValues.push(sample.writeMs); if (isFiniteNumber$1(sample.watchToDirtyMs)) watchToDirtyValues.push(sample.watchToDirtyMs); if (isFiniteNumber$1(sample.emitMs)) emitValues.push(sample.emitMs); if (isFiniteNumber$1(sample.sharedChunkResolveMs)) sharedChunkValues.push(sample.sharedChunkResolveMs); if (isFiniteNumber$1(sample.chunkEmitCount)) chunkEmitCountValues.push(sample.chunkEmitCount); if (isFiniteNumber$1(sample.loadCount)) loadCountValues.push(sample.loadCount); if (isFiniteNumber$1(sample.resolveCount)) resolveCountValues.push(sample.resolveCount); if (isFiniteNumber$1(sample.skippedLoadedCount)) skippedLoadedCountValues.push(sample.skippedLoadedCount); collectCounts(dirtyReasonCounts, sample.dirtyReasonSummary); collectCounts(pendingReasonCounts, sample.pendingReasonSummary); } const orderedByTime = [...samples].sort((left, right) => { const leftTime = typeof left.timestamp === "string" ? Date.parse(left.timestamp) : NaN; const rightTime = typeof right.timestamp === "string" ? Date.parse(right.timestamp) : NaN; if (Number.isFinite(leftTime) && Number.isFinite(rightTime)) return leftTime - rightTime; return 0; }); const slowestSamples = [...samples].sort((left, right) => (right.totalMs ?? 0) - (left.totalMs ?? 0)).slice(0, options.topSlowest ?? 5); return { runtime: "mini", kind: "hmr-profile", generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(), profilePath: options.profilePath, sampleCount: samples.length, skippedLineCount, firstTimestamp: orderedByTime[0]?.timestamp, lastTimestamp: orderedByTime[orderedByTime.length - 1]?.timestamp, metrics: { totalMs: createMetricSummary(totalValues), buildCoreMs: createMetricSummary(buildCoreValues), buildStartMs: createMetricSummary(buildStartValues), pluginResolveMs: createMetricSummary(pluginResolveValues), transformMs: createMetricSummary(transformValues), coreTransformMs: createMetricSummary(coreTransformValues), wevuTransformMs: createMetricSummary(wevuTransformValues), vueTransformMs: createMetricSummary(vueTransformValues), vueReadSourceMs: createMetricSummary(vueReadSourceValues), vueCompileMs: createMetricSummary(vueCompileValues), vueFinalizeCompiledMs: createMetricSummary(vueFinalizeCompiledValues), vueFinalizeCodeMs: createMetricSummary(vueFinalizeCodeValues), coreLoadMs: createMetricSummary(coreLoadValues), entryLoadMs: createMetricSummary(entryLoadValues), entryCodeReadMs: createMetricSummary(entryCodeReadValues), entrySidecarResolveMs: createMetricSummary(entrySidecarResolveValues), entryJsonReadMs: createMetricSummary(entryJsonReadValues), entryVueConfigMs: createMetricSummary(entryVueConfigValues), entryTemplateScanMs: createMetricSummary(entryTemplateScanValues), entryScriptSetupMs: createMetricSummary(entryScriptSetupValues), entryVueSignatureMs: createMetricSummary(entryVueSignatureValues), entryAutoImportMs: createMetricSummary(entryAutoImportValues), entryPrepareMs: createMetricSummary(entryPrepareValues), entryEmitOutputMs: createMetricSummary(entryEmitOutputValues), entryStyleScanMs: createMetricSummary(entryStyleScanValues), entryStyleReadMs: createMetricSummary(entryStyleReadValues), entryResolveMs: createMetricSummary(entryResolveValues), entryChunkEmitMs: createMetricSummary(entryChunkEmitValues), entryChunkLoadMs: createMetricSummary(entryChunkLoadValues), entryChunkEmitFileMs: createMetricSummary(entryChunkEmitFileValues), entryLayoutMs: createMetricSummary(entryLayoutValues), requestGlobalsMs: createMetricSummary(requestGlobalsValues), weapiResolveMs: createMetricSummary(weapiResolveValues), renderStartMs: createMetricSummary(renderStartValues), generateBundleMs: createMetricSummary(generateBundleValues), generateSharedMs: createMetricSummary(generateSharedValues), generateRewriteMs: createMetricSummary(generateRewriteValues), generateModuleGraphMs: createMetricSummary(generateModuleGraphValues), snapshotResolveMs: createMetricSummary(snapshotResolveValues), snapshotBuildMs: createMetricSummary(snapshotBuildValues), writeMs: createMetricSummary(writeValues), watchToDirtyMs: createMetricSummary(watchToDirtyValues), emitMs: createMetricSummary(emitValues), sharedChunkResolveMs: createMetricSummary(sharedChunkValues) }, operations: { chunkEmitCount: createOperationSummary(chunkEmitCountValues), loadCount: createOperationSummary(loadCountValues), resolveCount: createOperationSummary(resolveCountValues), skippedLoadedCount: createOperationSummary(skippedLoadedCountValues) }, events: sortCountEntries(eventCounts), dirtyReasons: sortCountEntries(dirtyReasonCounts), pendingReasons: sortCountEntries(pendingReasonCounts), slowestSamples }; } //#endregion //#region src/analyze/components/suggestions.ts function createComponentSuggestion(usage) { if (usage.componentPackage !== "__main__" || usage.crossPackageUsageCount === 0) return; if (usage.crossPackageUsageCount === usage.placeholderCoveredCrossPackageUsageCount) return; const pagePackages = Array.from(new Set(Array.from(usage.pages.values()).map((page) => page.packageId))).sort((left, right) => { if (left === "__main__") return -1; if (right === "__main__") return 1; return left.localeCompare(right); }); const subPackageIds = pagePackages.filter((packageId) => packageId !== "__main__"); const usedByMain = pagePackages.includes("__main__"); if (subPackageIds.length === 1 && !usedByMain) { const targetPackage = subPackageIds[0]; return { kind: "move-to-subpackage", component: usage.component, componentPackage: usage.componentPackage, targetPackage, pagePackages, message: `主包组件 ${usage.component} 仅被分包 ${targetPackage} 使用,建议评估移动到该分包。` }; } if (subPackageIds.length > 1) return { kind: "shared-subpackage-or-placeholder", component: usage.component, componentPackage: usage.componentPackage, pagePackages, message: `主包组件 ${usage.component} 被多个分包使用,建议评估分包归属、共享策略或 componentPlaceholder。` }; if (usedByMain && subPackageIds.length > 0) return { kind: "split-or-async", component: usage.component, componentPackage: usage.componentPackage, pagePackages, message: `主包组件 ${usage.component} 同时被主包和分包使用,建议评估组件拆分、归属或异步化策略。` }; } //#endregion //#region src/analyze/components/index.ts function normalizeRoute(value) { return posix.normalize(value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.json$/, "")); } function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function toStringArray(value) { return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim() !== "") : []; } function readUsingComponents(config) { if (!isRecord(config) || !isRecord(config.usingComponents)) return []; return Object.entries(config.usingComponents).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== ""); } function readComponentPlaceholder(config) { if (!isRecord(config) || !isRecord(config.componentPlaceholder)) return /* @__PURE__ */ new Set(); return new Set(Object.keys(config.componentPlaceholder)); } function resolveComponentRoute(owner, request) { const normalizedRequest = request.replace(/\\/g, "/").trim(); if (!normalizedRequest || normalizedRequest.startsWith("plugin://")) return; if (normalizedRequest.startsWith(".")) return normalizeRoute(posix.join(posix.dirname(owner), normalizedRequest)); if (normalizedRequest.startsWith("/")) return normalizeRoute(normalizedRequest); return normalizeRoute(normalizedRequest); } function resolvePackageId(route, subPackages) { return subPackages.map((item) => item.root).filter(Boolean).sort((left, right) => right.length - left.length).find((root) => route === root || route.startsWith(`${root}/`)) ?? "__main__"; } function collectAppPages(configs) { const appJson = configs.get("app"); if (!isRecord(appJson)) return /* @__PURE__ */ new Set(); const pages = /* @__PURE__ */ new Set(); for (const page of toStringArray(appJson.pages)) pages.add(normalizeRoute(page)); const subPackages = Array.isArray(appJson.subPackages) ? appJson.subPackages : Array.isArray(appJson.subpackages) ? appJson.subpackages : []; for (const item of subPackages) { if (!isRecord(item) || typeof item.root !== "string") continue; for (const page of toStringArray(item.pages)) pages.add(normalizeRoute(posix.join(item.root, page))); } return pages; } function registerUsage(usageMap, edge, page, pagePackage, componentPackage) { const usage = usageMap.get(edge.component) ?? { component: edge.component, componentPackage, totalUsageCount: 0, pages: /* @__PURE__ */ new Map(), crossPackageUsageCount: 0, placeholderCoveredCrossPackageUsageCount: 0 }; usage.totalUsageCount += 1; const pageUsage = usage.pages.get(page) ?? { page, packageId: pagePackage, usageCount: 0 }; pageUsage.usageCount += 1; usage.pages.set(page, pageUsage); if (pagePackage !== componentPackage) { usage.crossPackageUsageCount += 1; if (edge.placeholderCovered) usage.placeholderCoveredCrossPackageUsageCount += 1; } usageMap.set(edge.component, usage); } function collectAnalyzeComponentJsonConfigs(output) { if (!output) return []; const configs = []; for (const item of output.output ?? []) { if (item.type !== "asset" || !item.fileName.endsWith(".json")) continue; const asset = item; if (typeof asset.source !== "string") continue; try { configs.push({ file: normalizeRoute(asset.fileName), config: JSON.parse(asset.source) }); } catch {} } return configs; } function analyzeComponentUsage(options) { const configs = /* @__PURE__ */ new Map(); for (const item of options.jsonConfigs) configs.set(normalizeRoute(item.file), item.config); const pages = collectAppPages(configs); const graph = /* @__PURE__ */ new Map(); const packageMap = /* @__PURE__ */ new Map(); for (const route of configs.keys()) packageMap.set(route, resolvePackageId(route, options.subPackages)); for (const [owner, config] of configs) { const placeholders = readComponentPlaceholder(config); const edges = readUsingComponents(config).map(([name, request]) => { const component = resolveComponentRoute(owner, request); return component && configs.has(component) ? { owner, component, placeholderCovered: placeholders.has(name) } : void 0; }).filter((edge) => Boolean(edge)); if (edges.length > 0) graph.set(owner, edges); } const usageMap = /* @__PURE__ */ new Map(); const visit = (owner, page, stack) => { for (const edge of graph.get(owner) ?? []) { const componentPackage = packageMap.get(edge.component) ?? "__main__"; const pagePackage = packageMap.get(page) ?? "__main__"; registerUsage(usageMap, edge, page, pagePackage, componentPackage); if (stack.has(edge.component)) continue; const nextStack = new Set(stack); nextStack.add(edge.component); visit(edge.component, page, nextStack); } }; for (const page of pages) visit(page, page, /* @__PURE__ */ new Set([page])); return Array.from(usageMap.values()).map((usage) => { const pages = Array.from(usage.pages.values()).sort((left, right) => left.page.localeCompare(right.page)); const suggestions = [createComponentSuggestion(usage)].filter((item) => Boolean(item)); return { component: usage.component, componentPackage: usage.componentPackage, totalUsageCount: usage.totalUsageCount, pageUsageCount: pages.length, pages, suggestions }; }).sort((left, right) => { const usageDelta = right.totalUsageCount - left.totalUsageCount; return usageDelta !== 0 ? usageDelta : left.component.localeCompare(right.component); }); } //#endregion //#region src/analyze/subpackages/metadata.ts const defaultTotalBudgetBytes = 20971520; const defaultPackageBudgetBytes = 2097152; const defaultWarningRatio = .85; const defaultHistoryDir = ".weapp-vite/analyze-history"; co