weapp-vite
Version:
weapp-vite 一个现代化的小程序打包工具
1,184 lines • 227 kB
JavaScript
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