weapp-vite 7.5.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/THIRD_PARTY_NOTICES.md +60 -0
- package/dist/action-B3YZMA-h.mjs +255 -0
- package/dist/action-BELcDNNv.mjs +139 -0
- package/dist/action-BOk3vGI7.mjs +1110 -0
- package/dist/action-DlC6JYpt.mjs +95 -0
- package/dist/action-zEDY7oh_.mjs +247 -0
- package/dist/{analyze-ZJUvEdaY.mjs → analyze-DN79YOAH.mjs} +1 -65
- package/dist/artifacts-BOyC9Fy-.mjs +106 -0
- package/dist/assets-C5_YYXNk.mjs +637 -0
- package/dist/auto-routes.mjs +2 -2
- package/dist/{budget-BD7VqLCl.d.mts → budget-CTLOHwgI.d.mts} +1 -2
- package/dist/budget-EOeEycc_.mjs +65 -0
- package/dist/builtProject-C29udgXY.mjs +541 -0
- package/dist/{chunkStrategy-DRA5WpB-.mjs → chunkStrategy-DY_LZIIe.mjs} +15 -3
- package/dist/{package-7swhyLmf.mjs → classifier-DBJY__kg.mjs} +4 -7
- package/dist/cli-acceptance.mjs +1 -1
- package/dist/cli.mjs +110 -4506
- package/dist/{config-V0L-IAJf.d.mts → config-Bf6DWhyb.d.mts} +136 -49
- package/dist/config.d.mts +2 -2
- package/dist/{createCompilerContextInstance-DJH_P_nZ.mjs → createCompilerContextInstance-aYwi8wQt.mjs} +15235 -9523
- package/dist/{createContext-BqYzHhY9.mjs → createContext-tApLtII-.mjs} +6 -3
- package/dist/dashboard/analyze.d.mts +1 -1
- package/dist/dashboard/analyze.mjs +3 -1
- package/dist/dashboard/vite.d.mts +1 -1
- package/dist/dashboard.d.mts +2 -2
- package/dist/dashboard.mjs +1749 -1
- package/dist/dashboardTransport-C_Y6jMp-.mjs +16 -0
- package/dist/{dashboardViteBridge-Dy3nAD4m.mjs → dashboardViteBridge-DCGR43aM.mjs} +8 -5
- package/dist/{dev-fR5GiJRO.mjs → dev-C2fgr_Xd.mjs} +5 -1
- package/dist/devProvider-D-hTfsEO.mjs +3027 -0
- package/dist/docs/README.md +8 -2
- package/dist/docs/mcp.md +10 -0
- package/dist/docs/troubleshooting.md +8 -0
- package/dist/docs/vue-sfc.md +25 -2
- package/dist/doctor-worker.mjs +3 -2
- package/dist/doctor.d.mts +2 -2
- package/dist/doctor.mjs +15 -13
- package/dist/engine-C66J0HLz.mjs +54 -0
- package/dist/{file-sC3jD7Fh.mjs → file-CcIOmqhC.mjs} +47 -19
- package/dist/file-DtcteQ4G.mjs +1 -0
- package/dist/forwardConsole-B0Reo8Lf.mjs +511 -0
- package/dist/getInstance-BAIaU2y9.mjs +1 -0
- package/dist/{getInstance-DiGSzBNI.mjs → getInstance-Dt9IQDKr.mjs} +1 -1
- package/dist/history-BIiRCzbq.mjs +784 -0
- package/dist/i18n.d.mts +1 -1
- package/dist/{index-C9HjUX5c.d.mts → index-D5rgBzYZ.d.mts} +1 -2
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/json.d.mts +1 -1
- package/dist/lib-DjccSI1B.mjs +143 -0
- package/dist/{lifecycle-BROTlwSL.mjs → lifecycle-C36WSbQS.mjs} +120 -27
- package/dist/{loadConfig-C3BQhz7F.mjs → loadConfig-BWIsDXR4.mjs} +1 -1
- package/dist/loadConfig-D-BpBgsk.mjs +1 -0
- package/dist/logBuildAppFinish-ChjwATMv.mjs +57 -0
- package/dist/mcp-CYgX9HpO.mjs +86 -0
- package/dist/mcp.d.mts +1 -1
- package/dist/mcpClient-DVLvFLZv.mjs +287 -0
- package/dist/{processCleanup-B6KA30Zc.mjs → metadata-hwF7QRcS.mjs} +1 -19
- package/dist/nodeEnv-BmH8UZPR.mjs +7 -0
- package/dist/openIde-CroqOgk5.mjs +648 -0
- package/dist/options-jqFwfD1G.mjs +34 -0
- package/dist/package-Cmakyy3J.mjs +4 -0
- package/dist/path-CYYIQGS7.mjs +44 -0
- package/dist/processCleanup-BQn2kTcK.mjs +19 -0
- package/dist/{protocol-vg8HGJ4n.mjs → protocol-Cg6Jdlvb.mjs} +42 -39
- package/dist/reader-sh_POQ9-.mjs +93 -0
- package/dist/realpathScope-Tf9mz-fh.mjs +23 -0
- package/dist/report-hyonPGbZ.mjs +107 -0
- package/dist/{path-doV773b7.mjs → resolvedId-CiDl7SC1.mjs} +3 -46
- package/dist/runtime-CLUhQsdA.mjs +43 -0
- package/dist/{runtime-Ee2HY6gR.d.mts → runtime-DpVf714f.d.mts} +1 -1
- package/dist/runtime.d.mts +1 -1
- package/dist/schematics-kO26eQRT.mjs +91 -0
- package/dist/{session-PskhD-nZ.mjs → session-D3zsdzIy.mjs} +71 -9
- package/dist/test.mjs +10 -8
- package/dist/{types-CgZn5X6j.d.mts → types-D-kfuyVa.d.mts} +1 -2
- package/dist/types-D0FFexRF.d.mts +39 -0
- package/dist/types.d.mts +2 -2
- package/dist/{upload-D4msblBf.mjs → upload-DozK8UdN.mjs} +109 -5
- package/dist/upload-worker.mjs +1 -1
- package/dist/{utils-CJSCgEj6.mjs → utils-Dy51PYAC.mjs} +56 -116
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +51 -12
- package/dist/wxs-BT8btflA.mjs +112 -0
- package/dist/wxs-Z4VQDeJ_.mjs +1 -0
- package/package.json +52 -46
- package/dist/dashboard-DvqF3Key.mjs +0 -1842
- package/dist/devProvider-BGBkw28J.mjs +0 -1050
- package/dist/file-DRb4eWqw.mjs +0 -1
- package/dist/getInstance-SiE6WZRh.mjs +0 -1
- package/dist/loadConfig-VGtBF4qm.mjs +0 -1
- package/dist/mcp-D3Yh24Cj.mjs +0 -529
package/dist/cli.mjs
CHANGED
|
@@ -1,717 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { E as createSharedBuildConfig, S as createGlassEaselAnalyzeResult, c as getBackendForCapability, f as syncProjectSupportFiles, l as resolveBackendExecution, n as formatBytes, p as syncManagedTsconfigBootstrapFiles, u as CompilerSession, x as createDevViteServer } from "./createCompilerContextInstance-DJH_P_nZ.mjs";
|
|
4
|
-
import { l as resolveHmrProfileJsonPath } from "./devProvider-BGBkw28J.mjs";
|
|
5
|
-
import { l as getProjectPlatformOptions, n as getDefaultIdeProjectRoot, w as shouldPassPlatformArgToIdeOpen } from "./platform-CnkySBKz.mjs";
|
|
6
|
-
import { i as findJsEntry, o as findTemplateEntry, s as findVueEntry } from "./file-sC3jD7Fh.mjs";
|
|
1
|
+
import { o as createDevShutdownScope } from "./lifecycle-C36WSbQS.mjs";
|
|
2
|
+
import { M as syncManagedTsconfigBootstrapFiles, R as getHmrRuntimeSession, c as getBackendForCapability, j as syncProjectSupportFiles, w as createGlassEaselAnalyzeResult } from "./createCompilerContextInstance-aYwi8wQt.mjs";
|
|
7
3
|
import { t as VERSION } from "./constants-DEZdcsMn.mjs";
|
|
8
|
-
import { C as resolvePreloadPackageIdentifier, S as findPreloadRuleKey, c as getProjectConfigFileName, w as suggestPreloadRules, x as collectPreloadPages } from "./utils-CJSCgEj6.mjs";
|
|
9
4
|
import { r as logger_default, t as colors } from "./logger-DGn5iEVN.mjs";
|
|
10
|
-
import { t as createCompilerContext } from "./createContext-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { a as
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { t as loadConfig } from "./loadConfig-C3BQhz7F.mjs";
|
|
5
|
+
import { t as createCompilerContext } from "./createContext-tApLtII-.mjs";
|
|
6
|
+
import { n as registerAcceptCommand, t as registerMcpCommand } from "./mcp-CYgX9HpO.mjs";
|
|
7
|
+
import { i as resolveRuntimeTargets, n as logRuntimeTarget, r as resolveConfiguredRuntimeTargets, t as createInlineConfig } from "./runtime-CLUhQsdA.mjs";
|
|
8
|
+
import { a as resolveIdeProjectRoot, n as openIde, r as resolveIdeCommandContext, s as executeWechatIdeCliCommand, t as closeIde } from "./openIde-CroqOgk5.mjs";
|
|
9
|
+
import { a as parseDashboardUiHost, i as isUiEnabled, n as convertBase, o as resolveConfigFile, r as filterDuplicateOptions } from "./options-jqFwfD1G.mjs";
|
|
10
|
+
import { n as formatDuration, r as scheduleCompletedProductionBuildExit, t as logBuildAppFinish } from "./logBuildAppFinish-ChjwATMv.mjs";
|
|
11
|
+
import { t as loadConfig } from "./loadConfig-BWIsDXR4.mjs";
|
|
18
12
|
import { c as startWeappViteMcpServer, l as detectAiDevelopmentEnvironment, s as resolveWeappMcpConfig } from "./mcp-Cj8Maqo6.mjs";
|
|
13
|
+
import { n as maybeStartForwardConsole, o as readLatestHmrProfileSummary, r as pauseActiveForwardConsole, t as closeActiveForwardConsole } from "./forwardConsole-B0Reo8Lf.mjs";
|
|
14
|
+
import { r as formatMcpQuickStart } from "./mcpClient-DVLvFLZv.mjs";
|
|
15
|
+
import { n as createDashboardArtifactSnapshot } from "./artifacts-BOyC9Fy-.mjs";
|
|
16
|
+
import { i as startAnalyzeDashboard, n as writeAnalyzeHistorySnapshot, r as analyzeSubpackages, t as readLatestAnalyzeHistorySnapshot } from "./history-BIiRCzbq.mjs";
|
|
17
|
+
import { t as version } from "./package-Cmakyy3J.mjs";
|
|
18
|
+
import { t as setCommandNodeEnv } from "./nodeEnv-BmH8UZPR.mjs";
|
|
19
|
+
import { t as createAnalyzeMetadata } from "./metadata-hwF7QRcS.mjs";
|
|
20
|
+
import { i as readUploadMetadata, n as outputUploadReport } from "./report-hyonPGbZ.mjs";
|
|
19
21
|
import process from "node:process";
|
|
20
|
-
import path
|
|
21
|
-
import fs, { readFileSync } from "node:fs";
|
|
22
|
-
import { fileURLToPath } from "node:url";
|
|
22
|
+
import path from "pathe";
|
|
23
23
|
import path$1 from "node:path";
|
|
24
|
-
import {
|
|
25
|
-
import { fs as fs$1 } from "@weapp-core/shared/fs";
|
|
26
|
-
import { parse } from "vue/compiler-sfc";
|
|
27
|
-
import fs$2, { access, mkdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
28
|
-
import { build } from "vite";
|
|
29
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
30
|
-
import { parseArgs } from "node:util";
|
|
24
|
+
import fs, { mkdir, writeFile } from "node:fs/promises";
|
|
31
25
|
import { Buffer } from "node:buffer";
|
|
32
|
-
import { fork, spawn } from "node:child_process";
|
|
33
26
|
import { cac } from "cac";
|
|
34
|
-
import { RETRY_CANCEL_KEYS, RETRY_CONFIRM_KEYS,
|
|
35
|
-
import {
|
|
36
|
-
import { buildOtpAuthUrl, refreshTempAuthCode } from "devframe/node/auth";
|
|
37
|
-
import { resolveCommand } from "package-manager-detector/commands";
|
|
38
|
-
import semverInc from "semver/functions/inc.js";
|
|
39
|
-
import { x } from "tinyexec";
|
|
40
|
-
import { parse as parse$2 } from "dotenv";
|
|
41
|
-
import { expand } from "dotenv-expand";
|
|
42
|
-
import { generateJs, generateJson, generateWxml, generateWxss } from "@weapp-core/schematics";
|
|
43
|
-
import { determineAgent } from "@vercel/detect-agent";
|
|
44
|
-
import { initConfig } from "@weapp-core/init";
|
|
27
|
+
import { MANAGED_PROJECT_JOURNAL_ENV, RETRY_CANCEL_KEYS, RETRY_CONFIRM_KEYS, buildWechatIdeNpm, closeSharedMiniProgram, createSharedInputSession, dispatchWechatCliCommand, getActiveWechatDevtoolsTarget, isWeappIdeTopLevelCommand, resolveProjectAutomatorPort, takeScreenshot, withWechatDevtoolsSelection, withWechatDevtoolsSelectionArgs } from "weapp-ide-cli";
|
|
28
|
+
import { spawn } from "node:child_process";
|
|
45
29
|
import net from "node:net";
|
|
46
|
-
|
|
47
|
-
function logRuntimeTarget(targets, options = {}) {
|
|
48
|
-
if (options.silent) return;
|
|
49
|
-
if (targets.label === "config") {
|
|
50
|
-
const resolvedPlatform = targets.platform ?? options.resolvedConfigPlatform;
|
|
51
|
-
if (resolvedPlatform) {
|
|
52
|
-
logger_default.info(`目标平台:${colors.green(resolvedPlatform)}`);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
logger_default.info(`目标平台:使用配置文件中的 ${colors.bold(colors.green("weapp.platform"))}`);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
logger_default.info(`目标平台:${colors.green(targets.label)}`);
|
|
59
|
-
}
|
|
60
|
-
function resolveRuntimeTargets(options) {
|
|
61
|
-
const rawPlatform = typeof options.platform === "string" ? options.platform : typeof options.p === "string" ? options.p : void 0;
|
|
62
|
-
const execution = resolveBackendExecution(rawPlatform, { warn: (message) => logger_default.warn(message) });
|
|
63
|
-
const miniBackend = execution.get("miniprogram");
|
|
64
|
-
return {
|
|
65
|
-
...execution,
|
|
66
|
-
platform: miniBackend?.platform,
|
|
67
|
-
rawPlatform
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function resolveConfiguredRuntimeTargets(targets, platform) {
|
|
71
|
-
return targets.rawPlatform === void 0 && platform === "web" ? resolveRuntimeTargets({ platform: "web" }) : targets;
|
|
72
|
-
}
|
|
73
|
-
function createInlineConfig(execution, options = {}) {
|
|
74
|
-
const configs = execution.entries.map((entry) => entry.driver.createInlineConfig({
|
|
75
|
-
execution,
|
|
76
|
-
platform: entry.platform,
|
|
77
|
-
scope: options.scope,
|
|
78
|
-
host: options.host
|
|
79
|
-
})).filter((config) => Boolean(config));
|
|
80
|
-
if (configs.length === 0) return options.inlineConfig;
|
|
81
|
-
const merged = configs.slice(1).reduce((merged, config) => defu(merged, config), configs[0]);
|
|
82
|
-
return options.inlineConfig ? defu(options.inlineConfig, merged) : merged;
|
|
83
|
-
}
|
|
84
|
-
//#endregion
|
|
85
|
-
//#region src/cli/openIde/execute.ts
|
|
86
|
-
function readArgOption(argv, ...names) {
|
|
87
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
88
|
-
const current = argv[index];
|
|
89
|
-
if (!names.includes(current)) continue;
|
|
90
|
-
const next = argv[index + 1];
|
|
91
|
-
if (typeof next === "string" && !next.startsWith("-")) return next;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async function tryExecuteWechatIdeCliCommandByAutomator(argv, projectPath, options = {}) {
|
|
95
|
-
if (!projectPath) return false;
|
|
96
|
-
const command = argv[0];
|
|
97
|
-
if (!command) return false;
|
|
98
|
-
if (command === "compile") {
|
|
99
|
-
await compileWechatIdeByAutomator({
|
|
100
|
-
preserveProjectRoot: options.preserveProjectRoot,
|
|
101
|
-
projectPath
|
|
102
|
-
});
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
if (command === "cache") {
|
|
106
|
-
const cleanType = readArgOption(argv, "--clean", "-c");
|
|
107
|
-
if (cleanType !== "compile" && cleanType !== "all") return false;
|
|
108
|
-
await clearWechatIdeCacheByAutomator({
|
|
109
|
-
clean: cleanType,
|
|
110
|
-
preserveProjectRoot: options.preserveProjectRoot,
|
|
111
|
-
projectPath
|
|
112
|
-
});
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
async function tryExecuteWechatIdeCliCommandByHttp(argv, projectPath, engineBuildFallbackToCli = false) {
|
|
118
|
-
const command = argv[0];
|
|
119
|
-
if (!command) return false;
|
|
120
|
-
if (command === "reset-fileutils") {
|
|
121
|
-
if (!projectPath) return false;
|
|
122
|
-
await resetWechatIdeFileUtilsByHttp(projectPath);
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
if (command === "engine" && argv[1] === "build") {
|
|
126
|
-
const engineProjectPath = argv[2] || projectPath;
|
|
127
|
-
if (!engineProjectPath) return false;
|
|
128
|
-
await runWechatIdeEngineBuild(engineProjectPath, {
|
|
129
|
-
fallbackToCli: engineBuildFallbackToCli,
|
|
130
|
-
logPath: readArgOption(argv, "--logPath", "-l")
|
|
131
|
-
});
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
async function tryExecuteWechatIdeCliCommandByHelper(argv) {
|
|
137
|
-
const command = argv[0];
|
|
138
|
-
if (!command) return false;
|
|
139
|
-
if (command === "close") {
|
|
140
|
-
await closeWechatIdeProject();
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
if (command === "quit") {
|
|
144
|
-
await quitWechatIde();
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
if (command === "cache") {
|
|
148
|
-
const cleanType = readArgOption(argv, "--clean", "-c");
|
|
149
|
-
if (!cleanType) return false;
|
|
150
|
-
await clearWechatIdeCache({ clean: cleanType });
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* @description 统一执行 weapp-ide-cli 命令,并在登录失效时复用同一套重试交互。
|
|
157
|
-
*/
|
|
158
|
-
async function executeWechatIdeCliCommand(argv, options = {}) {
|
|
159
|
-
const { automatorMode = "prefer", cancelLevel = "warn", engineBuildFallbackToCli = false, httpMode = "prefer", promptOpenIdeLogin = true, onNonLoginError, onRetry, projectPath, preserveProjectRoot } = options;
|
|
160
|
-
await runWithSuspendedSharedInput(async () => {
|
|
161
|
-
if (httpMode !== "skip") try {
|
|
162
|
-
if (await tryExecuteWechatIdeCliCommandByHttp(argv, projectPath, engineBuildFallbackToCli)) return;
|
|
163
|
-
} catch (error) {
|
|
164
|
-
if (httpMode === "require" || isWechatIdeEngineBuildEndpointMissingError(error)) throw error;
|
|
165
|
-
}
|
|
166
|
-
if (automatorMode !== "skip") try {
|
|
167
|
-
if (await tryExecuteWechatIdeCliCommandByAutomator(argv, projectPath, { preserveProjectRoot })) return;
|
|
168
|
-
} catch (error) {
|
|
169
|
-
if (automatorMode === "require") throw error;
|
|
170
|
-
}
|
|
171
|
-
try {
|
|
172
|
-
if (await tryExecuteWechatIdeCliCommandByHelper(argv)) return;
|
|
173
|
-
} catch (error) {
|
|
174
|
-
if (isWechatIdeLoginRequiredExitError(error)) throw error;
|
|
175
|
-
if (onNonLoginError) {
|
|
176
|
-
onNonLoginError(error);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
throw error;
|
|
180
|
-
}
|
|
181
|
-
await runRetryableCommand({
|
|
182
|
-
createCancelError: (error) => createWechatIdeLoginRequiredExitError(error, "cancelled"),
|
|
183
|
-
execute: async () => {
|
|
184
|
-
try {
|
|
185
|
-
await parse$1(argv);
|
|
186
|
-
return null;
|
|
187
|
-
} catch (error) {
|
|
188
|
-
if (isWechatIdeLoginRequiredExitError(error)) throw error;
|
|
189
|
-
if (!isWechatIdeLoginRequiredError(error)) {
|
|
190
|
-
if (onNonLoginError) {
|
|
191
|
-
onNonLoginError(error);
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
196
|
-
return error;
|
|
197
|
-
}
|
|
198
|
-
},
|
|
199
|
-
isRetryableResult: (result) => result !== null,
|
|
200
|
-
onCancel: () => {},
|
|
201
|
-
onRetry: () => {
|
|
202
|
-
onRetry?.();
|
|
203
|
-
},
|
|
204
|
-
promptRetry: async (error) => await promptWechatIdeLoginRetry({
|
|
205
|
-
cancelLevel,
|
|
206
|
-
error,
|
|
207
|
-
logger: logger_default,
|
|
208
|
-
promptOpenIdeLogin
|
|
209
|
-
}),
|
|
210
|
-
shouldRetry: (action) => action === "retry"
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
//#endregion
|
|
215
|
-
//#region src/cli/openIde/close.ts
|
|
216
|
-
/**
|
|
217
|
-
* @description 执行用户显式请求的 CLI close;失败时保留宿主,不升级为系统级退出。
|
|
218
|
-
*/
|
|
219
|
-
async function closeIde$1() {
|
|
220
|
-
try {
|
|
221
|
-
await closeWechatIdeProject();
|
|
222
|
-
return true;
|
|
223
|
-
} catch (error) {
|
|
224
|
-
if (isWechatIdeLoginRequiredError(error)) try {
|
|
225
|
-
await executeWechatIdeCliCommand(["close"], {
|
|
226
|
-
cancelLevel: "warn",
|
|
227
|
-
onNonLoginError: (retryError) => logger_default.error(retryError),
|
|
228
|
-
onRetry: () => logger_default.info("正在重试连接微信开发者工具...")
|
|
229
|
-
});
|
|
230
|
-
return true;
|
|
231
|
-
} catch (retryError) {
|
|
232
|
-
logger_default.error(retryError);
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
logger_default.warn("微信开发者工具 CLI close 执行失败,已保留现有宿主;请在开发者工具中手动关闭目标项目。");
|
|
236
|
-
logger_default.error(error);
|
|
237
|
-
}
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
//#endregion
|
|
242
|
-
//#region src/cli/openIde/diagnostics.ts
|
|
243
|
-
/**
|
|
244
|
-
* @description 将 automator 启动错误归类为用户可执行的恢复提示。
|
|
245
|
-
*/
|
|
246
|
-
function formatWechatIdeAutomatorFailure(error) {
|
|
247
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
248
|
-
if (/需要重新登录|need\s+re-?login|code\s*[:=]\s*10/i.test(message)) return "登录状态失效,请先登录微信开发者工具。";
|
|
249
|
-
if (/service port|http port|ECONNREFUSED|listen EPERM|EACCES/i.test(message)) return "服务端口不可用,请在设置 -> 安全设置中开启服务端口。";
|
|
250
|
-
if (/protocol method|protocol timeout|automator.*timeout|Wait timed out|websocket|ws:\/\//i.test(message)) return "自动化 websocket 或协议尚未就绪,已保留普通 CLI 打开的项目。";
|
|
251
|
-
if (/cliPath|not found|spawn/i.test(message)) return "微信开发者工具 CLI 路径不可用,请执行 `wv ide doctor` 检查配置。";
|
|
252
|
-
return "当前 DevTools 自动化能力不可用,已保留普通 CLI 打开的项目。";
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* @description 输出微信开发者工具打开后的自助恢复步骤。
|
|
256
|
-
*/
|
|
257
|
-
function logWechatIdeRecoveryHint(options) {
|
|
258
|
-
const lines = [
|
|
259
|
-
`微信开发者工具打开后状态可能不稳定:${options.reason}`,
|
|
260
|
-
"可按下面顺序恢复:",
|
|
261
|
-
"1. 在微信开发者工具中确认:设置 -> 安全设置 -> 服务端口已开启。",
|
|
262
|
-
"2. 仅当项目索引刷新失败时,默认会自动关闭并重开一次当前目标项目;如需跳过,传入 `--no-open-recovery` 或设置 `WEAPP_VITE_DISABLE_IDE_OPEN_RECOVERY=1`。",
|
|
263
|
-
"3. 如果仍然回到项目选择页,手动导入 project.config.json 所在目录,并关闭多余的微信开发者工具窗口后重试。",
|
|
264
|
-
"4. 需要查看底层错误时,设置 `WEAPP_VITE_DEBUG_AUTOMATOR_OPEN=1` 后重试。"
|
|
265
|
-
];
|
|
266
|
-
if (options.projectPath) lines.push(`当前目标项目目录:${options.projectPath}`);
|
|
267
|
-
logger_default.warn(lines.join("\n"));
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* @description 输出服务端口关闭时的定向恢复提示。
|
|
271
|
-
*/
|
|
272
|
-
function logWechatIdeServicePortDisabledHint(projectPath) {
|
|
273
|
-
const lines = [
|
|
274
|
-
"检测到微信开发者工具服务端口当前处于关闭状态,已保留用户设置并回退到普通 open 流程。",
|
|
275
|
-
"自动刷新、截图、MCP 和 IDE 联动能力需要服务端口。",
|
|
276
|
-
"请在微信开发者工具中打开:设置 -> 安全设置 -> 服务端口,然后重新执行当前 dev/open 命令。"
|
|
277
|
-
];
|
|
278
|
-
if (projectPath) lines.push(`当前目标项目目录:${projectPath}`);
|
|
279
|
-
logger_default.warn(lines.join("\n"));
|
|
280
|
-
}
|
|
281
|
-
//#endregion
|
|
282
|
-
//#region src/cli/openIde/reuse.ts
|
|
283
|
-
const OPENED_PROJECT_HEALTH_CHECK_TIMEOUT = 3e3;
|
|
284
|
-
const OPEN_AUTOMATOR_TIMEOUT = 12e4;
|
|
285
|
-
function formatReuseOpenedWechatIdePrompt() {
|
|
286
|
-
return `目标项目已在微信开发者工具中打开,已跳过重复打开。按 ${RETRY_CONFIRM_KEYS.map((key) => colors.bold(colors.green(key))).join(" / ")} 重新连接目标项目(保留现有窗口)。`;
|
|
287
|
-
}
|
|
288
|
-
function disconnectMiniProgram(miniProgram) {
|
|
289
|
-
miniProgram.disconnect();
|
|
290
|
-
}
|
|
291
|
-
function withTimeout(task, timeoutMs) {
|
|
292
|
-
let timer;
|
|
293
|
-
const timeout = new Promise((_, reject) => {
|
|
294
|
-
timer = setTimeout(() => {
|
|
295
|
-
reject(/* @__PURE__ */ new Error(`opened automator health check timed out after ${timeoutMs}ms`));
|
|
296
|
-
}, timeoutMs);
|
|
297
|
-
});
|
|
298
|
-
return Promise.race([task, timeout]).finally(() => {
|
|
299
|
-
if (timer) clearTimeout(timer);
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
async function verifyOpenedProjectHealth(miniProgram) {
|
|
303
|
-
if (typeof miniProgram.screenshot !== "function") return;
|
|
304
|
-
await withTimeout(miniProgram.screenshot({ timeout: OPENED_PROJECT_HEALTH_CHECK_TIMEOUT }), OPENED_PROJECT_HEALTH_CHECK_TIMEOUT);
|
|
305
|
-
}
|
|
306
|
-
async function openWechatIdeByAutomator(projectPath, options = {}) {
|
|
307
|
-
disconnectMiniProgram(await launchAutomator({
|
|
308
|
-
persistAsDefaultSession: true,
|
|
309
|
-
preserveProjectRoot: options.preserveProjectRoot ?? true,
|
|
310
|
-
projectPath,
|
|
311
|
-
port: resolveProjectAutomatorPort(projectPath),
|
|
312
|
-
timeout: OPEN_AUTOMATOR_TIMEOUT,
|
|
313
|
-
trustProject: options.trustProject !== false
|
|
314
|
-
}));
|
|
315
|
-
}
|
|
316
|
-
async function connectOpenedProject(projectPath) {
|
|
317
|
-
let miniProgram;
|
|
318
|
-
try {
|
|
319
|
-
miniProgram = await connectOpenedAutomator({
|
|
320
|
-
projectPath,
|
|
321
|
-
port: resolveProjectAutomatorPort(projectPath),
|
|
322
|
-
timeout: 3e3
|
|
323
|
-
});
|
|
324
|
-
} catch {
|
|
325
|
-
return { status: "not-connected" };
|
|
326
|
-
}
|
|
327
|
-
try {
|
|
328
|
-
await verifyOpenedProjectHealth(miniProgram);
|
|
329
|
-
return {
|
|
330
|
-
status: "connected",
|
|
331
|
-
miniProgram
|
|
332
|
-
};
|
|
333
|
-
} catch {
|
|
334
|
-
disconnectMiniProgram(miniProgram);
|
|
335
|
-
return { status: "unhealthy" };
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* @description 若当前项目已在微信开发者工具中打开且自动化可连通,则直接复用现有会话,避免重复拉起 IDE。
|
|
340
|
-
*/
|
|
341
|
-
async function tryReuseOpenedWechatIde(projectPath, options = {}) {
|
|
342
|
-
const connection = await connectOpenedProject(projectPath);
|
|
343
|
-
if (connection.status === "unhealthy") return {
|
|
344
|
-
reopened: false,
|
|
345
|
-
reused: false,
|
|
346
|
-
unhealthy: true
|
|
347
|
-
};
|
|
348
|
-
if (connection.status === "not-connected") return null;
|
|
349
|
-
const { miniProgram } = connection;
|
|
350
|
-
disconnectMiniProgram(miniProgram);
|
|
351
|
-
if (options.promptReopen === false) {
|
|
352
|
-
logger_default.info("目标项目已在微信开发者工具中打开,已跳过重复打开。");
|
|
353
|
-
return {
|
|
354
|
-
reopened: false,
|
|
355
|
-
reused: true
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
logger_default.info(formatReuseOpenedWechatIdePrompt());
|
|
359
|
-
if (await promptRetryKeypress({ logger: logger_default }) !== "retry") return {
|
|
360
|
-
reopened: false,
|
|
361
|
-
reused: true
|
|
362
|
-
};
|
|
363
|
-
logger_default.info(colors.bold(colors.green("正在重新连接目标项目,保留现有微信开发者工具窗口...")));
|
|
364
|
-
await openWechatIdeByAutomator(projectPath, {
|
|
365
|
-
preserveProjectRoot: options.preserveProjectRoot,
|
|
366
|
-
trustProject: options.trustProject
|
|
367
|
-
});
|
|
368
|
-
return {
|
|
369
|
-
reopened: true,
|
|
370
|
-
reused: false
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* @description 重新连接目标项目以刷新构建产物;连接失败不授予关闭共享宿主的权限。
|
|
375
|
-
*/
|
|
376
|
-
async function reopenOpenedWechatIde(projectPath, options = {}) {
|
|
377
|
-
const connection = await connectOpenedProject(projectPath);
|
|
378
|
-
if (connection.status === "connected") {
|
|
379
|
-
disconnectMiniProgram(connection.miniProgram);
|
|
380
|
-
logger_default.info("目标项目已在微信开发者工具中打开,当前命令将重新连接以刷新最新构建产物,保留现有窗口。");
|
|
381
|
-
} else if (connection.status === "unhealthy") logger_default.info("目标项目的 automator 会话未通过健康检查,将重试连接目标项目,保留现有窗口。");
|
|
382
|
-
else logger_default.info("未检测到可复用的 automator 会话,将尝试打开目标项目,保留其他窗口。");
|
|
383
|
-
await openWechatIdeByAutomator(projectPath, options);
|
|
384
|
-
return true;
|
|
385
|
-
}
|
|
386
|
-
//#endregion
|
|
387
|
-
//#region src/cli/openIde/index.ts
|
|
388
|
-
function shouldLogAutomatorFallbackError() {
|
|
389
|
-
const flag = process.env.WEAPP_VITE_DEBUG_AUTOMATOR_OPEN;
|
|
390
|
-
return flag === "1" || flag === "true";
|
|
391
|
-
}
|
|
392
|
-
const PREPARE_AUTOMATOR_SESSION_TIMEOUT = 8e3;
|
|
393
|
-
const RESET_FILEUTILS_ENV = "WEAPP_VITE_RESET_IDE_FILEUTILS";
|
|
394
|
-
function readProjectConfigObject(projectPath) {
|
|
395
|
-
try {
|
|
396
|
-
const raw = fs.readFileSync(path.join(projectPath, "project.config.json"), "utf8");
|
|
397
|
-
const value = JSON.parse(raw);
|
|
398
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
399
|
-
} catch {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
function shouldUseAutomatorProjectWrapper(projectPath) {
|
|
404
|
-
const projectConfig = readProjectConfigObject(projectPath);
|
|
405
|
-
return projectConfig?.compileType === "plugin" && typeof projectConfig.miniprogramRoot === "string" && projectConfig.miniprogramRoot.trim().length > 0;
|
|
406
|
-
}
|
|
407
|
-
function isWechatIdeOpenRecoveryDisabled(options) {
|
|
408
|
-
if (options.openRecovery === false) return true;
|
|
409
|
-
const flag = process.env.WEAPP_VITE_DISABLE_IDE_OPEN_RECOVERY;
|
|
410
|
-
return flag === "1" || flag === "true";
|
|
411
|
-
}
|
|
412
|
-
function shouldResetWechatIdeFileUtils() {
|
|
413
|
-
const flag = process.env[RESET_FILEUTILS_ENV];
|
|
414
|
-
return flag === "1" || flag === "true";
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* @description 执行 IDE 打开流程,并在登录失效时允许按键重试。
|
|
418
|
-
*/
|
|
419
|
-
async function runWechatIdeOpenWithRetry(argv) {
|
|
420
|
-
await executeWechatIdeCliCommand(argv, {
|
|
421
|
-
cancelLevel: "warn",
|
|
422
|
-
onNonLoginError: (error) => {
|
|
423
|
-
logger_default.error(error);
|
|
424
|
-
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL?.trim()) throw error;
|
|
425
|
-
},
|
|
426
|
-
onRetry: () => {
|
|
427
|
-
logger_default.info(colors.bold(colors.green("正在重试连接微信开发者工具...")));
|
|
428
|
-
},
|
|
429
|
-
promptOpenIdeLogin: true
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* @description 根据代码输出目录推导 IDE 项目目录;内联项目配置与 app.json 同目录。
|
|
434
|
-
*/
|
|
435
|
-
function resolveIdeProjectPath(mpDistRoot, generatedProjectConfig = false) {
|
|
436
|
-
if (!mpDistRoot || !mpDistRoot.trim()) return;
|
|
437
|
-
if (generatedProjectConfig) return mpDistRoot;
|
|
438
|
-
const parent = path.dirname(mpDistRoot);
|
|
439
|
-
if (!parent || parent === "." || parent === "/") return;
|
|
440
|
-
return parent;
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* @description 结合 mpDistRoot 与配置根目录解析最终 IDE 项目目录。
|
|
444
|
-
*/
|
|
445
|
-
function resolveIdeProjectRoot(mpDistRoot, cwd, generatedProjectConfig = false) {
|
|
446
|
-
const projectPath = resolveIdeProjectPath(mpDistRoot, generatedProjectConfig);
|
|
447
|
-
return generatedProjectConfig && projectPath && cwd ? path.resolve(cwd, projectPath) : projectPath ?? cwd;
|
|
448
|
-
}
|
|
449
|
-
async function closeIde() {
|
|
450
|
-
return await closeIde$1();
|
|
451
|
-
}
|
|
452
|
-
async function tryOpenWechatIdeByAutomator(projectPath, options) {
|
|
453
|
-
const preserveProjectRoot = !shouldUseAutomatorProjectWrapper(projectPath);
|
|
454
|
-
if (options.reuseOpenedProject === false) {
|
|
455
|
-
if (await reopenOpenedWechatIde(projectPath, {
|
|
456
|
-
preserveProjectRoot,
|
|
457
|
-
trustProject: options.trustProject
|
|
458
|
-
})) return "reopened";
|
|
459
|
-
}
|
|
460
|
-
const reuseResult = await tryReuseOpenedWechatIde(projectPath, {
|
|
461
|
-
preserveProjectRoot,
|
|
462
|
-
promptReopen: options.reuseOpenedProject !== true,
|
|
463
|
-
trustProject: options.trustProject
|
|
464
|
-
});
|
|
465
|
-
if (reuseResult?.reused) return "reused";
|
|
466
|
-
if (reuseResult?.reopened) return "reopened";
|
|
467
|
-
if (reuseResult?.unhealthy) return "unhealthy";
|
|
468
|
-
await openWechatIdeByAutomator(projectPath, {
|
|
469
|
-
preserveProjectRoot,
|
|
470
|
-
trustProject: options.trustProject
|
|
471
|
-
});
|
|
472
|
-
return "opened";
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* @description 打开后主动刷新微信开发者工具的项目索引,避免模拟器沿用过期 app 配置。
|
|
476
|
-
*/
|
|
477
|
-
function appendLoginRetryArgv(argv, options) {
|
|
478
|
-
if (options.nonInteractive) argv.push("--non-interactive");
|
|
479
|
-
if (options.loginRetry) argv.push("--login-retry", options.loginRetry);
|
|
480
|
-
if (options.loginRetryTimeout) argv.push("--login-retry-timeout", options.loginRetryTimeout);
|
|
481
|
-
return argv;
|
|
482
|
-
}
|
|
483
|
-
function createIdeOpenArgv(platform, projectPath, options = {}) {
|
|
484
|
-
const argv = ["open", "-p"];
|
|
485
|
-
if (projectPath) argv.push(projectPath);
|
|
486
|
-
if (platform === "weapp" && options.trustProject !== false) argv.push("--trust-project");
|
|
487
|
-
if (platform && shouldPassPlatformArgToIdeOpen(platform)) argv.push("--platform", platform);
|
|
488
|
-
if (options.nonInteractive) argv.push("--non-interactive");
|
|
489
|
-
if (options.loginRetry) argv.push("--login-retry", options.loginRetry);
|
|
490
|
-
if (options.loginRetryTimeout) argv.push("--login-retry-timeout", options.loginRetryTimeout);
|
|
491
|
-
return argv;
|
|
492
|
-
}
|
|
493
|
-
async function prepareOpenedWechatIdeAutomatorSession(projectPath, options) {
|
|
494
|
-
const preserveProjectRoot = !shouldUseAutomatorProjectWrapper(projectPath);
|
|
495
|
-
try {
|
|
496
|
-
(await launchAutomator({
|
|
497
|
-
persistAsDefaultSession: true,
|
|
498
|
-
preserveProjectRoot,
|
|
499
|
-
projectPath,
|
|
500
|
-
port: resolveProjectAutomatorPort(projectPath),
|
|
501
|
-
timeout: PREPARE_AUTOMATOR_SESSION_TIMEOUT,
|
|
502
|
-
trustProject: options.trustProject !== false
|
|
503
|
-
})).disconnect?.();
|
|
504
|
-
return { ok: true };
|
|
505
|
-
} catch (error) {
|
|
506
|
-
logger_default.warn("准备当前项目的微信开发者工具自动化会话失败,截图、MCP 或 IDE 联动命令首次运行时将重新连接。");
|
|
507
|
-
logWechatIdeRecoveryHint({
|
|
508
|
-
projectPath,
|
|
509
|
-
reason: "无法建立当前项目的自动化会话,常见原因是 DevTools 服务端口未就绪、窗口停留在项目选择页,或存在残留 DevTools 会话。"
|
|
510
|
-
});
|
|
511
|
-
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
512
|
-
return {
|
|
513
|
-
ok: false,
|
|
514
|
-
reason: "automator-session-failed",
|
|
515
|
-
error
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
async function connectOpenedWechatIdeAutomatorSession(projectPath) {
|
|
520
|
-
try {
|
|
521
|
-
(await connectOpenedAutomator({
|
|
522
|
-
projectPath,
|
|
523
|
-
port: resolveProjectAutomatorPort(projectPath),
|
|
524
|
-
timeout: PREPARE_AUTOMATOR_SESSION_TIMEOUT
|
|
525
|
-
})).disconnect?.();
|
|
526
|
-
return { ok: true };
|
|
527
|
-
} catch (error) {
|
|
528
|
-
logger_default.warn("连接当前项目的微信开发者工具自动化会话失败,截图、MCP 或 IDE 联动命令首次运行时将重新连接。");
|
|
529
|
-
logWechatIdeRecoveryHint({
|
|
530
|
-
projectPath,
|
|
531
|
-
reason: "当前项目已完成打开流程,但尚未连接到可复用的自动化会话。"
|
|
532
|
-
});
|
|
533
|
-
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
534
|
-
return {
|
|
535
|
-
ok: false,
|
|
536
|
-
reason: "automator-session-failed",
|
|
537
|
-
error
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
async function prepareWechatIdeAutomatorSession(projectPath, options) {
|
|
542
|
-
if (options.prepareAutomatorSession === "connect-opened") return await connectOpenedWechatIdeAutomatorSession(projectPath);
|
|
543
|
-
return await prepareOpenedWechatIdeAutomatorSession(projectPath, options);
|
|
544
|
-
}
|
|
545
|
-
async function stabilizeOpenedWechatIdeProject(projectPath, servicePortEnabled, options = {}) {
|
|
546
|
-
if (servicePortEnabled === false) return {
|
|
547
|
-
ok: false,
|
|
548
|
-
reason: "service-port-disabled"
|
|
549
|
-
};
|
|
550
|
-
try {
|
|
551
|
-
if (shouldResetWechatIdeFileUtils()) await executeWechatIdeCliCommand(appendLoginRetryArgv([
|
|
552
|
-
"reset-fileutils",
|
|
553
|
-
"-p",
|
|
554
|
-
projectPath
|
|
555
|
-
], options), {
|
|
556
|
-
automatorMode: options.useAutomatorOpen === false ? "skip" : "prefer",
|
|
557
|
-
httpMode: "require",
|
|
558
|
-
onNonLoginError: (error) => logger_default.error(error),
|
|
559
|
-
preserveProjectRoot: options.useAutomatorOpen === false,
|
|
560
|
-
projectPath
|
|
561
|
-
});
|
|
562
|
-
try {
|
|
563
|
-
await executeWechatIdeCliCommand(appendLoginRetryArgv([
|
|
564
|
-
"engine",
|
|
565
|
-
"build",
|
|
566
|
-
projectPath
|
|
567
|
-
], options), {
|
|
568
|
-
automatorMode: options.useAutomatorOpen === false ? "skip" : "prefer",
|
|
569
|
-
engineBuildFallbackToCli: true,
|
|
570
|
-
httpMode: "prefer",
|
|
571
|
-
onNonLoginError: (error) => logger_default.error(error),
|
|
572
|
-
preserveProjectRoot: options.useAutomatorOpen === false,
|
|
573
|
-
projectPath
|
|
574
|
-
});
|
|
575
|
-
} catch (error) {
|
|
576
|
-
if (!isWechatIdeEngineBuildEndpointMissingError(error)) throw error;
|
|
577
|
-
logger_default.warn("当前微信开发者工具不支持自动 engine build 刷新,已跳过该步骤;如模拟器显示旧状态,可在开发者工具内手动编译。");
|
|
578
|
-
}
|
|
579
|
-
if (options.useAutomatorOpen !== false && options.skipAutomatorCompile !== true) try {
|
|
580
|
-
await executeWechatIdeCliCommand(appendLoginRetryArgv(["compile"], options), {
|
|
581
|
-
automatorMode: "require",
|
|
582
|
-
httpMode: "skip",
|
|
583
|
-
preserveProjectRoot: !shouldUseAutomatorProjectWrapper(projectPath),
|
|
584
|
-
projectPath
|
|
585
|
-
});
|
|
586
|
-
} catch (error) {
|
|
587
|
-
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
588
|
-
}
|
|
589
|
-
return { ok: true };
|
|
590
|
-
} catch (error) {
|
|
591
|
-
if (isWechatIdeLoginRequiredExitError(error)) throw error;
|
|
592
|
-
logger_default.warn("刷新微信开发者工具项目索引失败,已保留当前打开状态;如模拟器仍显示旧状态,可手动刷新一次。");
|
|
593
|
-
logWechatIdeRecoveryHint({
|
|
594
|
-
projectPath,
|
|
595
|
-
reason: "打开项目后的文件索引刷新失败,DevTools 可能仍在使用旧项目状态或内部服务未就绪。"
|
|
596
|
-
});
|
|
597
|
-
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
598
|
-
return {
|
|
599
|
-
ok: false,
|
|
600
|
-
reason: "index-refresh-failed",
|
|
601
|
-
error
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
async function verifyOpenedWechatIdeProject(projectPath, servicePortEnabled, options) {
|
|
606
|
-
const stabilizeResult = await stabilizeOpenedWechatIdeProject(projectPath, servicePortEnabled, options);
|
|
607
|
-
if (!stabilizeResult.ok) return stabilizeResult;
|
|
608
|
-
if (options.useAutomatorOpen === false && servicePortEnabled !== false) return await connectOpenedWechatIdeAutomatorSession(projectPath);
|
|
609
|
-
return stabilizeResult;
|
|
610
|
-
}
|
|
611
|
-
function formatWechatIdeOpenHealthReason(result) {
|
|
612
|
-
if (result.reason === "index-refresh-failed") return "项目索引刷新失败";
|
|
613
|
-
return "服务端口未开启";
|
|
614
|
-
}
|
|
615
|
-
async function recoverOpenedWechatIdeProject(platform, projectPath, servicePortEnabled, options, failedResult) {
|
|
616
|
-
if (failedResult.reason === "service-port-disabled") return failedResult;
|
|
617
|
-
if (failedResult.reason === "automator-session-failed") {
|
|
618
|
-
logger_default.warn("已跳过微信开发者工具自动恢复;自动化会话预热失败不影响当前项目打开,截图、MCP 或 IDE 联动命令首次运行时会重新连接。");
|
|
619
|
-
return failedResult;
|
|
620
|
-
}
|
|
621
|
-
if (isWechatIdeOpenRecoveryDisabled(options)) {
|
|
622
|
-
logger_default.warn("已跳过微信开发者工具自动恢复;请按上方提示手动关闭并重新打开目标项目。");
|
|
623
|
-
return failedResult;
|
|
624
|
-
}
|
|
625
|
-
logger_default.info(`检测到微信开发者工具打开后状态不稳定(${formatWechatIdeOpenHealthReason(failedResult)}),正在重试打开目标项目(保留现有窗口)...`);
|
|
626
|
-
await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, options));
|
|
627
|
-
const recoveredResult = await verifyOpenedWechatIdeProject(projectPath, servicePortEnabled, options);
|
|
628
|
-
if (recoveredResult.ok) logger_default.info("微信开发者工具已完成自动恢复。");
|
|
629
|
-
else logger_default.warn("微信开发者工具自动恢复未完成;可设置 `WEAPP_VITE_DISABLE_IDE_OPEN_RECOVERY=1` 或传入 `--no-open-recovery` 跳过自动重试,并按提示手动处理。");
|
|
630
|
-
return recoveredResult;
|
|
631
|
-
}
|
|
632
|
-
async function verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, servicePortEnabled, options) {
|
|
633
|
-
const healthResult = await verifyOpenedWechatIdeProject(projectPath, servicePortEnabled, options);
|
|
634
|
-
if (!healthResult.ok) await recoverOpenedWechatIdeProject(platform, projectPath, servicePortEnabled, options, healthResult);
|
|
635
|
-
}
|
|
636
|
-
async function openIde(platform, projectPath, options = {}) {
|
|
637
|
-
let bootstrapResult;
|
|
638
|
-
const openStrategy = options.openStrategy ?? (options.useAutomatorOpen === true ? "automator" : "cli");
|
|
639
|
-
const useAutomatorOpen = openStrategy === "automator";
|
|
640
|
-
const normalizedOptions = {
|
|
641
|
-
...options,
|
|
642
|
-
openStrategy,
|
|
643
|
-
useAutomatorOpen
|
|
644
|
-
};
|
|
645
|
-
if (platform === "weapp" && projectPath) try {
|
|
646
|
-
bootstrapResult = await bootstrapWechatDevtoolsSettings({
|
|
647
|
-
projectPath,
|
|
648
|
-
trustProject: normalizedOptions.trustProject
|
|
649
|
-
});
|
|
650
|
-
} catch (error) {
|
|
651
|
-
logger_default.warn("检测微信开发者工具服务端口或写入项目信任状态失败,继续执行 open 流程。");
|
|
652
|
-
logger_default.error(error);
|
|
653
|
-
}
|
|
654
|
-
if (platform === "weapp" && projectPath && bootstrapResult?.servicePortEnabled === false) logWechatIdeServicePortDisabledHint(projectPath);
|
|
655
|
-
if (platform === "weapp" && projectPath && bootstrapResult?.servicePortEnabled !== false && useAutomatorOpen) try {
|
|
656
|
-
const openResult = await tryOpenWechatIdeByAutomator(projectPath, normalizedOptions);
|
|
657
|
-
if (openResult === "reused") return;
|
|
658
|
-
if (openResult === "unhealthy") {
|
|
659
|
-
logger_default.warn("已连接到目标微信开发者工具项目,但协议健康检查暂未就绪;已保留当前窗口并跳过重复启动。");
|
|
660
|
-
return;
|
|
661
|
-
} else if (openResult) {
|
|
662
|
-
if (!normalizedOptions.skipPostOpenHealthCheck) await verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, bootstrapResult?.servicePortEnabled, normalizedOptions);
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
} catch (error) {
|
|
666
|
-
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL?.trim()) throw error;
|
|
667
|
-
if (isAutomatorLoginError(error)) {
|
|
668
|
-
logger_default.error("检测到微信开发者工具登录状态失效,请先登录后重试。");
|
|
669
|
-
logger_default.warn(formatAutomatorLoginError(error));
|
|
670
|
-
}
|
|
671
|
-
logger_default.warn(`通过 automator 启动微信开发者工具失败,回退到普通 open 流程:${formatWechatIdeAutomatorFailure(error)}`);
|
|
672
|
-
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
673
|
-
}
|
|
674
|
-
await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, normalizedOptions));
|
|
675
|
-
if (platform === "weapp" && projectPath && normalizedOptions.prepareAutomatorSession) await prepareWechatIdeAutomatorSession(projectPath, normalizedOptions);
|
|
676
|
-
if (platform === "weapp" && projectPath && !normalizedOptions.skipPostOpenHealthCheck) await verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, bootstrapResult?.servicePortEnabled, normalizedOptions);
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* @description 解析 IDE 相关命令所需的平台、项目目录与配置上下文。
|
|
680
|
-
*/
|
|
681
|
-
async function resolveIdeCommandContext(options) {
|
|
682
|
-
const cwd = options.cwd ?? process.cwd();
|
|
683
|
-
let platform = options.platform;
|
|
684
|
-
let projectPath = options.projectPath;
|
|
685
|
-
if (!platform || !projectPath) try {
|
|
686
|
-
const targets = resolveRuntimeTargets({ platform });
|
|
687
|
-
const ctx = await createCompilerContext({
|
|
688
|
-
cwd,
|
|
689
|
-
mode: options.mode ?? "development",
|
|
690
|
-
configFile: options.configFile,
|
|
691
|
-
inlineConfig: createInlineConfig(targets),
|
|
692
|
-
cliPlatform: options.cliPlatform
|
|
693
|
-
});
|
|
694
|
-
platform ??= ctx.configService.platform;
|
|
695
|
-
if (!projectPath) projectPath = resolveIdeProjectRoot(ctx.configService.mpDistRoot, ctx.configService.cwd, ctx.configService.multiPlatform.projectConfigs !== void 0);
|
|
696
|
-
return {
|
|
697
|
-
cwd: ctx.configService.cwd,
|
|
698
|
-
platform,
|
|
699
|
-
projectPath,
|
|
700
|
-
weappViteConfig: ctx.configService.weappViteConfig,
|
|
701
|
-
mpDistRoot: ctx.configService.mpDistRoot
|
|
702
|
-
};
|
|
703
|
-
} catch {}
|
|
704
|
-
if (!projectPath) {
|
|
705
|
-
const defaultProjectRoot = getDefaultIdeProjectRoot(platform);
|
|
706
|
-
if (defaultProjectRoot) projectPath = resolveIdeProjectRoot(defaultProjectRoot, cwd);
|
|
707
|
-
}
|
|
708
|
-
return {
|
|
709
|
-
cwd,
|
|
710
|
-
platform,
|
|
711
|
-
projectPath
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
//#endregion
|
|
30
|
+
import { brotliCompressSync, gzipSync } from "node:zlib";
|
|
715
31
|
//#region src/cli/commands/alipayExecute.ts
|
|
716
32
|
function createSpawnOptions() {
|
|
717
33
|
return {
|
|
@@ -780,8 +96,9 @@ function createMinidevArgv(action, root, resolvedProjectPath, options) {
|
|
|
780
96
|
async function runAlipayCommand(action, root, options) {
|
|
781
97
|
const normalizedAction = normalizeAlipayAction(action);
|
|
782
98
|
filterDuplicateOptions(options);
|
|
99
|
+
const configFile = resolveConfigFile(options);
|
|
783
100
|
const argv = createMinidevArgv(normalizedAction, root, (await resolveIdeCommandContext({
|
|
784
|
-
configFile
|
|
101
|
+
configFile,
|
|
785
102
|
mode: options.mode ?? (normalizedAction === "upload" ? "production" : "development"),
|
|
786
103
|
platform: "alipay",
|
|
787
104
|
projectPath: root ?? options.project,
|
|
@@ -793,2863 +110,23 @@ async function runAlipayCommand(action, root, options) {
|
|
|
793
110
|
}
|
|
794
111
|
function registerAlipayCommand(cli) {
|
|
795
112
|
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) => {
|
|
796
|
-
await runAlipayCommand(action, root, options);
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
//#endregion
|
|
800
|
-
//#region src/analyze/
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
"
|
|
812
|
-
|
|
813
|
-
"commitMs",
|
|
814
|
-
"publishMs"
|
|
815
|
-
] : [
|
|
816
|
-
"sourceToBatchMs",
|
|
817
|
-
"snapshotBuildMs",
|
|
818
|
-
"snapshotPublishMs"
|
|
819
|
-
]).map((key) => [key, value(key)])) : {
|
|
820
|
-
batchWaitMs: value("batchWaitMs"),
|
|
821
|
-
queueWaitMs: value("queueWaitMs"),
|
|
822
|
-
otherBeforeBuildMs: difference(sample.watchToDirtyMs, sample.batchWaitMs, sample.queueWaitMs),
|
|
823
|
-
bundlerMs: value("bundlerMs")
|
|
824
|
-
};
|
|
825
|
-
const profileResidualMs = sample.pipeline === "stateful" ? difference(sample.totalMs, ...Object.values(phases)) : difference(sample.totalMs, sample.watchToDirtyMs, sample.bundlerMs);
|
|
826
|
-
let externalStatus = external ? "unknown" : "not-observed";
|
|
827
|
-
let sourceToWatcherMs = null;
|
|
828
|
-
let afterProfileToVisibleMs = null;
|
|
829
|
-
let eventId = null;
|
|
830
|
-
if (external && sample.schemaVersion === 1 && sample.correlation === "known" && sample.clock?.durations === "performance.now" && nonnegative(sample.clock.timeOrigin) && nonnegative(external.writtenAtEpochMs) && external.visibleAtEpochMs >= external.writtenAtEpochMs) {
|
|
831
|
-
const events = (sample.sourceEvents ?? []).filter((event) => event.file?.replaceAll("\\", "/") === external.sourceFile.replaceAll("\\", "/") && sample.clock.timeOrigin + event.receivedAtMs >= external.writtenAtEpochMs && sample.clock.timeOrigin + event.receivedAtMs <= external.visibleAtEpochMs);
|
|
832
|
-
if (events.length === 1 && sample.sourceEvents?.length === 1) {
|
|
833
|
-
const received = sample.clock.timeOrigin + events[0].receivedAtMs;
|
|
834
|
-
sourceToWatcherMs = received - external.writtenAtEpochMs;
|
|
835
|
-
afterProfileToVisibleMs = difference(external.visibleAtEpochMs - received, sample.totalMs);
|
|
836
|
-
eventId = events[0].eventId;
|
|
837
|
-
externalStatus = afterProfileToVisibleMs === null ? "unknown" : "matched";
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
return {
|
|
841
|
-
schemaVersion: 1,
|
|
842
|
-
pipeline: sample.pipeline ?? "standard",
|
|
843
|
-
status: Object.values(phases).every(nonnegative) && profileResidualMs !== null ? "complete" : "partial",
|
|
844
|
-
phases,
|
|
845
|
-
profileResidualMs,
|
|
846
|
-
external: {
|
|
847
|
-
status: externalStatus,
|
|
848
|
-
eventId,
|
|
849
|
-
sourceToWatcherMs,
|
|
850
|
-
afterProfileToVisibleMs
|
|
851
|
-
},
|
|
852
|
-
nestedMetrics: "hook durations overlap; do not add to outer phases"
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
//#endregion
|
|
856
|
-
//#region src/analyze/hmr/reader.ts
|
|
857
|
-
const stringFields = [
|
|
858
|
-
"timestamp",
|
|
859
|
-
"eventId",
|
|
860
|
-
"event",
|
|
861
|
-
"file",
|
|
862
|
-
"relativeFile",
|
|
863
|
-
"sourceRootFile",
|
|
864
|
-
"sessionId",
|
|
865
|
-
"buildId",
|
|
866
|
-
"batchId"
|
|
867
|
-
];
|
|
868
|
-
const collectionFields = ["dirtyReasonSummary", "pendingReasonSummary"];
|
|
869
|
-
function isSourceEvent(value) {
|
|
870
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
871
|
-
const event = value;
|
|
872
|
-
return typeof event.eventId === "string" && (event.file === void 0 || typeof event.file === "string") && (event.event === void 0 || typeof event.event === "string" && [
|
|
873
|
-
"update",
|
|
874
|
-
"create",
|
|
875
|
-
"delete"
|
|
876
|
-
].includes(event.event)) && typeof event.receivedAtMs === "number" && Number.isFinite(event.receivedAtMs) && event.receivedAtMs >= 0;
|
|
877
|
-
}
|
|
878
|
-
function isSample(value) {
|
|
879
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
880
|
-
const sample = value;
|
|
881
|
-
for (const [key, values] of Object.entries({
|
|
882
|
-
pipeline: ["standard", "stateful"],
|
|
883
|
-
profileMode: [
|
|
884
|
-
"delivery",
|
|
885
|
-
"full",
|
|
886
|
-
"refresh"
|
|
887
|
-
],
|
|
888
|
-
completionBoundary: ["delivery-acknowledged", "output-published"]
|
|
889
|
-
})) if (sample[key] !== void 0 && (typeof sample[key] !== "string" || !values.includes(sample[key]))) return false;
|
|
890
|
-
if (typeof sample.totalMs !== "number" || !Number.isFinite(sample.totalMs) || sample.totalMs < 0) return false;
|
|
891
|
-
if (stringFields.some((key) => sample[key] !== void 0 && typeof sample[key] !== "string")) return false;
|
|
892
|
-
if (collectionFields.some((key) => {
|
|
893
|
-
const items = sample[key];
|
|
894
|
-
return items !== void 0 && (!Array.isArray(items) || items.some((item) => typeof item !== "string"));
|
|
895
|
-
})) return false;
|
|
896
|
-
if (sample.sourceEvents !== void 0 && (!Array.isArray(sample.sourceEvents) || !sample.sourceEvents.every(isSourceEvent))) return false;
|
|
897
|
-
return Object.entries(sample).every(([key, value]) => {
|
|
898
|
-
if (!key.endsWith("Ms") && !key.endsWith("Count")) return true;
|
|
899
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0 && (!key.endsWith("Count") || Number.isInteger(value));
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
/** 旧版仍可读取;不兼容、失败和未完成记录不参与正常耗时统计,缺失阶段不补零。 */
|
|
903
|
-
function readHmrProfileLines(content) {
|
|
904
|
-
const samples = [];
|
|
905
|
-
const coverage = {
|
|
906
|
-
legacy: 0,
|
|
907
|
-
compatible: 0,
|
|
908
|
-
incompatible: 0,
|
|
909
|
-
incomplete: 0,
|
|
910
|
-
invalid: 0
|
|
911
|
-
};
|
|
912
|
-
for (const line of content.split(/\r?\n/)) {
|
|
913
|
-
if (!line.trim()) continue;
|
|
914
|
-
let value;
|
|
915
|
-
try {
|
|
916
|
-
value = JSON.parse(line);
|
|
917
|
-
} catch {
|
|
918
|
-
coverage.invalid += 1;
|
|
919
|
-
continue;
|
|
920
|
-
}
|
|
921
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
922
|
-
coverage.invalid += 1;
|
|
923
|
-
continue;
|
|
924
|
-
}
|
|
925
|
-
const record = value;
|
|
926
|
-
if (record.schemaVersion !== void 0 && record.schemaVersion !== 1) {
|
|
927
|
-
coverage.incompatible += 1;
|
|
928
|
-
continue;
|
|
929
|
-
}
|
|
930
|
-
if (record.status === "incomplete" || record.status === "failed") {
|
|
931
|
-
coverage.incomplete += 1;
|
|
932
|
-
continue;
|
|
933
|
-
}
|
|
934
|
-
if (record.status !== void 0 && record.status !== "complete" || record.schemaVersion === 1 && record.status !== "complete" || !isSample(record)) {
|
|
935
|
-
coverage.invalid += 1;
|
|
936
|
-
continue;
|
|
937
|
-
}
|
|
938
|
-
coverage[record.schemaVersion === void 0 ? "legacy" : "compatible"] += 1;
|
|
939
|
-
samples.push(record);
|
|
940
|
-
}
|
|
941
|
-
return {
|
|
942
|
-
samples,
|
|
943
|
-
coverage,
|
|
944
|
-
skippedLineCount: coverage.incompatible + coverage.incomplete + coverage.invalid
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
//#endregion
|
|
948
|
-
//#region src/analyze/hmr.ts
|
|
949
|
-
function createMetricSummary(values) {
|
|
950
|
-
if (!values.length) return { count: 0 };
|
|
951
|
-
const total = values.reduce((sum, value) => sum + value, 0);
|
|
952
|
-
return {
|
|
953
|
-
count: values.length,
|
|
954
|
-
averageMs: total / values.length,
|
|
955
|
-
maxMs: Math.max(...values)
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
function createOperationSummary(values) {
|
|
959
|
-
if (!values.length) return { count: 0 };
|
|
960
|
-
const total = values.reduce((sum, value) => sum + value, 0);
|
|
961
|
-
return {
|
|
962
|
-
count: values.length,
|
|
963
|
-
average: total / values.length,
|
|
964
|
-
max: Math.max(...values)
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
function sortCountEntries(map) {
|
|
968
|
-
return [...map.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([name, count]) => ({
|
|
969
|
-
name,
|
|
970
|
-
count
|
|
971
|
-
}));
|
|
972
|
-
}
|
|
973
|
-
function collectCounts(target, values) {
|
|
974
|
-
for (const value of values ?? []) {
|
|
975
|
-
if (!value) continue;
|
|
976
|
-
target.set(value, (target.get(value) ?? 0) + 1);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
function isFiniteNumber$1(value) {
|
|
980
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
981
|
-
}
|
|
982
|
-
/**
|
|
983
|
-
* @description 聚合 HMR JSONL profile,为命令行与后续仪表盘复用。
|
|
984
|
-
*/
|
|
985
|
-
async function analyzeHmrProfile(options) {
|
|
986
|
-
const { samples, coverage: inputCoverage, skippedLineCount } = readHmrProfileLines(await fs$1.readFile(options.profilePath, "utf8"));
|
|
987
|
-
const eventCounts = /* @__PURE__ */ new Map();
|
|
988
|
-
const dirtyReasonCounts = /* @__PURE__ */ new Map();
|
|
989
|
-
const pendingReasonCounts = /* @__PURE__ */ new Map();
|
|
990
|
-
const batchWaitValues = [];
|
|
991
|
-
const queueWaitValues = [];
|
|
992
|
-
const bundlerValues = [];
|
|
993
|
-
const finalizePrepareValues = [];
|
|
994
|
-
const finalizeTemplateValues = [];
|
|
995
|
-
const finalizePublishValues = [];
|
|
996
|
-
const publicationValidateValues = [];
|
|
997
|
-
const publicationIndependentValues = [];
|
|
998
|
-
const publicationPruneValues = [];
|
|
999
|
-
const totalValues = [];
|
|
1000
|
-
const buildCoreValues = [];
|
|
1001
|
-
const buildStartValues = [];
|
|
1002
|
-
const pluginResolveValues = [];
|
|
1003
|
-
const transformValues = [];
|
|
1004
|
-
const coreTransformValues = [];
|
|
1005
|
-
const wevuTransformValues = [];
|
|
1006
|
-
const vueTransformValues = [];
|
|
1007
|
-
const vueReadSourceValues = [];
|
|
1008
|
-
const vueCompileValues = [];
|
|
1009
|
-
const vueFinalizeCompiledValues = [];
|
|
1010
|
-
const vueFinalizeCodeValues = [];
|
|
1011
|
-
const coreLoadValues = [];
|
|
1012
|
-
const entryLoadValues = [];
|
|
1013
|
-
const entryCodeReadValues = [];
|
|
1014
|
-
const entrySidecarResolveValues = [];
|
|
1015
|
-
const entryJsonReadValues = [];
|
|
1016
|
-
const entryVueConfigValues = [];
|
|
1017
|
-
const entryTemplateScanValues = [];
|
|
1018
|
-
const entryScriptSetupValues = [];
|
|
1019
|
-
const entryVueSignatureValues = [];
|
|
1020
|
-
const entryAutoImportValues = [];
|
|
1021
|
-
const entryPrepareValues = [];
|
|
1022
|
-
const entryEmitOutputValues = [];
|
|
1023
|
-
const entryStyleScanValues = [];
|
|
1024
|
-
const entryStyleReadValues = [];
|
|
1025
|
-
const entryResolveValues = [];
|
|
1026
|
-
const entryChunkEmitValues = [];
|
|
1027
|
-
const entryChunkLoadValues = [];
|
|
1028
|
-
const entryChunkEmitFileValues = [];
|
|
1029
|
-
const entryLayoutValues = [];
|
|
1030
|
-
const requestGlobalsValues = [];
|
|
1031
|
-
const weapiResolveValues = [];
|
|
1032
|
-
const renderStartValues = [];
|
|
1033
|
-
const generateBundleValues = [];
|
|
1034
|
-
const generateSharedValues = [];
|
|
1035
|
-
const generateRewriteValues = [];
|
|
1036
|
-
const generateModuleGraphValues = [];
|
|
1037
|
-
const snapshotResolveValues = [];
|
|
1038
|
-
const snapshotBuildValues = [];
|
|
1039
|
-
const writeValues = [];
|
|
1040
|
-
const watchToDirtyValues = [];
|
|
1041
|
-
const emitValues = [];
|
|
1042
|
-
const sharedChunkValues = [];
|
|
1043
|
-
const chunkEmitCountValues = [];
|
|
1044
|
-
const loadCountValues = [];
|
|
1045
|
-
const resolveCountValues = [];
|
|
1046
|
-
const skippedLoadedCountValues = [];
|
|
1047
|
-
for (const sample of samples) {
|
|
1048
|
-
if (isFiniteNumber$1(sample.batchWaitMs)) batchWaitValues.push(sample.batchWaitMs);
|
|
1049
|
-
if (isFiniteNumber$1(sample.queueWaitMs)) queueWaitValues.push(sample.queueWaitMs);
|
|
1050
|
-
if (isFiniteNumber$1(sample.bundlerMs)) bundlerValues.push(sample.bundlerMs);
|
|
1051
|
-
if (isFiniteNumber$1(sample.finalizePrepareMs)) finalizePrepareValues.push(sample.finalizePrepareMs);
|
|
1052
|
-
if (isFiniteNumber$1(sample.finalizeTemplateMs)) finalizeTemplateValues.push(sample.finalizeTemplateMs);
|
|
1053
|
-
if (isFiniteNumber$1(sample.finalizePublishMs)) finalizePublishValues.push(sample.finalizePublishMs);
|
|
1054
|
-
if (isFiniteNumber$1(sample.publicationValidateMs)) publicationValidateValues.push(sample.publicationValidateMs);
|
|
1055
|
-
if (isFiniteNumber$1(sample.publicationIndependentMs)) publicationIndependentValues.push(sample.publicationIndependentMs);
|
|
1056
|
-
if (isFiniteNumber$1(sample.publicationPruneMs)) publicationPruneValues.push(sample.publicationPruneMs);
|
|
1057
|
-
totalValues.push(sample.totalMs);
|
|
1058
|
-
if (sample.event) eventCounts.set(sample.event, (eventCounts.get(sample.event) ?? 0) + 1);
|
|
1059
|
-
if (isFiniteNumber$1(sample.buildCoreMs)) buildCoreValues.push(sample.buildCoreMs);
|
|
1060
|
-
if (isFiniteNumber$1(sample.buildStartMs)) buildStartValues.push(sample.buildStartMs);
|
|
1061
|
-
if (isFiniteNumber$1(sample.pluginResolveMs)) pluginResolveValues.push(sample.pluginResolveMs);
|
|
1062
|
-
if (isFiniteNumber$1(sample.transformMs)) transformValues.push(sample.transformMs);
|
|
1063
|
-
if (isFiniteNumber$1(sample.coreTransformMs)) coreTransformValues.push(sample.coreTransformMs);
|
|
1064
|
-
if (isFiniteNumber$1(sample.wevuTransformMs)) wevuTransformValues.push(sample.wevuTransformMs);
|
|
1065
|
-
if (isFiniteNumber$1(sample.vueTransformMs)) vueTransformValues.push(sample.vueTransformMs);
|
|
1066
|
-
if (isFiniteNumber$1(sample.vueReadSourceMs)) vueReadSourceValues.push(sample.vueReadSourceMs);
|
|
1067
|
-
if (isFiniteNumber$1(sample.vueCompileMs)) vueCompileValues.push(sample.vueCompileMs);
|
|
1068
|
-
if (isFiniteNumber$1(sample.vueFinalizeCompiledMs)) vueFinalizeCompiledValues.push(sample.vueFinalizeCompiledMs);
|
|
1069
|
-
if (isFiniteNumber$1(sample.vueFinalizeCodeMs)) vueFinalizeCodeValues.push(sample.vueFinalizeCodeMs);
|
|
1070
|
-
if (isFiniteNumber$1(sample.coreLoadMs)) coreLoadValues.push(sample.coreLoadMs);
|
|
1071
|
-
if (isFiniteNumber$1(sample.entryLoadMs)) entryLoadValues.push(sample.entryLoadMs);
|
|
1072
|
-
if (isFiniteNumber$1(sample.entryCodeReadMs)) entryCodeReadValues.push(sample.entryCodeReadMs);
|
|
1073
|
-
if (isFiniteNumber$1(sample.entrySidecarResolveMs)) entrySidecarResolveValues.push(sample.entrySidecarResolveMs);
|
|
1074
|
-
if (isFiniteNumber$1(sample.entryJsonReadMs)) entryJsonReadValues.push(sample.entryJsonReadMs);
|
|
1075
|
-
if (isFiniteNumber$1(sample.entryVueConfigMs)) entryVueConfigValues.push(sample.entryVueConfigMs);
|
|
1076
|
-
if (isFiniteNumber$1(sample.entryTemplateScanMs)) entryTemplateScanValues.push(sample.entryTemplateScanMs);
|
|
1077
|
-
if (isFiniteNumber$1(sample.entryScriptSetupMs)) entryScriptSetupValues.push(sample.entryScriptSetupMs);
|
|
1078
|
-
if (isFiniteNumber$1(sample.entryVueSignatureMs)) entryVueSignatureValues.push(sample.entryVueSignatureMs);
|
|
1079
|
-
if (isFiniteNumber$1(sample.entryAutoImportMs)) entryAutoImportValues.push(sample.entryAutoImportMs);
|
|
1080
|
-
if (isFiniteNumber$1(sample.entryPrepareMs)) entryPrepareValues.push(sample.entryPrepareMs);
|
|
1081
|
-
if (isFiniteNumber$1(sample.entryEmitOutputMs)) entryEmitOutputValues.push(sample.entryEmitOutputMs);
|
|
1082
|
-
if (isFiniteNumber$1(sample.entryStyleScanMs)) entryStyleScanValues.push(sample.entryStyleScanMs);
|
|
1083
|
-
if (isFiniteNumber$1(sample.entryStyleReadMs)) entryStyleReadValues.push(sample.entryStyleReadMs);
|
|
1084
|
-
if (isFiniteNumber$1(sample.entryResolveMs)) entryResolveValues.push(sample.entryResolveMs);
|
|
1085
|
-
if (isFiniteNumber$1(sample.entryChunkEmitMs)) entryChunkEmitValues.push(sample.entryChunkEmitMs);
|
|
1086
|
-
if (isFiniteNumber$1(sample.entryChunkLoadMs)) entryChunkLoadValues.push(sample.entryChunkLoadMs);
|
|
1087
|
-
if (isFiniteNumber$1(sample.entryChunkEmitFileMs)) entryChunkEmitFileValues.push(sample.entryChunkEmitFileMs);
|
|
1088
|
-
if (isFiniteNumber$1(sample.entryLayoutMs)) entryLayoutValues.push(sample.entryLayoutMs);
|
|
1089
|
-
if (isFiniteNumber$1(sample.requestGlobalsMs)) requestGlobalsValues.push(sample.requestGlobalsMs);
|
|
1090
|
-
if (isFiniteNumber$1(sample.weapiResolveMs)) weapiResolveValues.push(sample.weapiResolveMs);
|
|
1091
|
-
if (isFiniteNumber$1(sample.renderStartMs)) renderStartValues.push(sample.renderStartMs);
|
|
1092
|
-
if (isFiniteNumber$1(sample.generateBundleMs)) generateBundleValues.push(sample.generateBundleMs);
|
|
1093
|
-
if (isFiniteNumber$1(sample.generateSharedMs)) generateSharedValues.push(sample.generateSharedMs);
|
|
1094
|
-
if (isFiniteNumber$1(sample.generateRewriteMs)) generateRewriteValues.push(sample.generateRewriteMs);
|
|
1095
|
-
if (isFiniteNumber$1(sample.generateModuleGraphMs)) generateModuleGraphValues.push(sample.generateModuleGraphMs);
|
|
1096
|
-
if (isFiniteNumber$1(sample.snapshotResolveMs)) snapshotResolveValues.push(sample.snapshotResolveMs);
|
|
1097
|
-
if (isFiniteNumber$1(sample.snapshotBuildMs)) snapshotBuildValues.push(sample.snapshotBuildMs);
|
|
1098
|
-
if (isFiniteNumber$1(sample.writeMs)) writeValues.push(sample.writeMs);
|
|
1099
|
-
if (isFiniteNumber$1(sample.watchToDirtyMs)) watchToDirtyValues.push(sample.watchToDirtyMs);
|
|
1100
|
-
if (isFiniteNumber$1(sample.emitMs)) emitValues.push(sample.emitMs);
|
|
1101
|
-
if (isFiniteNumber$1(sample.sharedChunkResolveMs)) sharedChunkValues.push(sample.sharedChunkResolveMs);
|
|
1102
|
-
if (isFiniteNumber$1(sample.chunkEmitCount)) chunkEmitCountValues.push(sample.chunkEmitCount);
|
|
1103
|
-
if (isFiniteNumber$1(sample.loadCount)) loadCountValues.push(sample.loadCount);
|
|
1104
|
-
if (isFiniteNumber$1(sample.resolveCount)) resolveCountValues.push(sample.resolveCount);
|
|
1105
|
-
if (isFiniteNumber$1(sample.skippedLoadedCount)) skippedLoadedCountValues.push(sample.skippedLoadedCount);
|
|
1106
|
-
collectCounts(dirtyReasonCounts, sample.dirtyReasonSummary);
|
|
1107
|
-
collectCounts(pendingReasonCounts, sample.pendingReasonSummary);
|
|
1108
|
-
}
|
|
1109
|
-
const orderedByTime = [...samples].sort((left, right) => {
|
|
1110
|
-
const leftTime = typeof left.timestamp === "string" ? Date.parse(left.timestamp) : NaN;
|
|
1111
|
-
const rightTime = typeof right.timestamp === "string" ? Date.parse(right.timestamp) : NaN;
|
|
1112
|
-
if (Number.isFinite(leftTime) && Number.isFinite(rightTime)) return leftTime - rightTime;
|
|
1113
|
-
return 0;
|
|
1114
|
-
});
|
|
1115
|
-
const slowestSamples = [...samples].sort((left, right) => (right.totalMs ?? 0) - (left.totalMs ?? 0)).slice(0, options.topSlowest ?? 5);
|
|
1116
|
-
return {
|
|
1117
|
-
runtime: "mini",
|
|
1118
|
-
kind: "hmr-profile",
|
|
1119
|
-
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
1120
|
-
profilePath: options.profilePath,
|
|
1121
|
-
sampleCount: samples.length,
|
|
1122
|
-
skippedLineCount,
|
|
1123
|
-
inputCoverage,
|
|
1124
|
-
timelines: samples.map((sample) => ({
|
|
1125
|
-
batchId: sample.batchId,
|
|
1126
|
-
completionBoundary: sample.completionBoundary,
|
|
1127
|
-
...attributeHmrProfile(sample)
|
|
1128
|
-
})),
|
|
1129
|
-
firstTimestamp: orderedByTime[0]?.timestamp,
|
|
1130
|
-
lastTimestamp: orderedByTime[orderedByTime.length - 1]?.timestamp,
|
|
1131
|
-
metrics: {
|
|
1132
|
-
batchWaitMs: createMetricSummary(batchWaitValues),
|
|
1133
|
-
queueWaitMs: createMetricSummary(queueWaitValues),
|
|
1134
|
-
bundlerMs: createMetricSummary(bundlerValues),
|
|
1135
|
-
finalizePrepareMs: createMetricSummary(finalizePrepareValues),
|
|
1136
|
-
finalizeTemplateMs: createMetricSummary(finalizeTemplateValues),
|
|
1137
|
-
finalizePublishMs: createMetricSummary(finalizePublishValues),
|
|
1138
|
-
publicationValidateMs: createMetricSummary(publicationValidateValues),
|
|
1139
|
-
publicationIndependentMs: createMetricSummary(publicationIndependentValues),
|
|
1140
|
-
publicationPruneMs: createMetricSummary(publicationPruneValues),
|
|
1141
|
-
totalMs: createMetricSummary(totalValues),
|
|
1142
|
-
buildCoreMs: createMetricSummary(buildCoreValues),
|
|
1143
|
-
buildStartMs: createMetricSummary(buildStartValues),
|
|
1144
|
-
pluginResolveMs: createMetricSummary(pluginResolveValues),
|
|
1145
|
-
transformMs: createMetricSummary(transformValues),
|
|
1146
|
-
coreTransformMs: createMetricSummary(coreTransformValues),
|
|
1147
|
-
wevuTransformMs: createMetricSummary(wevuTransformValues),
|
|
1148
|
-
vueTransformMs: createMetricSummary(vueTransformValues),
|
|
1149
|
-
vueReadSourceMs: createMetricSummary(vueReadSourceValues),
|
|
1150
|
-
vueCompileMs: createMetricSummary(vueCompileValues),
|
|
1151
|
-
vueFinalizeCompiledMs: createMetricSummary(vueFinalizeCompiledValues),
|
|
1152
|
-
vueFinalizeCodeMs: createMetricSummary(vueFinalizeCodeValues),
|
|
1153
|
-
coreLoadMs: createMetricSummary(coreLoadValues),
|
|
1154
|
-
entryLoadMs: createMetricSummary(entryLoadValues),
|
|
1155
|
-
entryCodeReadMs: createMetricSummary(entryCodeReadValues),
|
|
1156
|
-
entrySidecarResolveMs: createMetricSummary(entrySidecarResolveValues),
|
|
1157
|
-
entryJsonReadMs: createMetricSummary(entryJsonReadValues),
|
|
1158
|
-
entryVueConfigMs: createMetricSummary(entryVueConfigValues),
|
|
1159
|
-
entryTemplateScanMs: createMetricSummary(entryTemplateScanValues),
|
|
1160
|
-
entryScriptSetupMs: createMetricSummary(entryScriptSetupValues),
|
|
1161
|
-
entryVueSignatureMs: createMetricSummary(entryVueSignatureValues),
|
|
1162
|
-
entryAutoImportMs: createMetricSummary(entryAutoImportValues),
|
|
1163
|
-
entryPrepareMs: createMetricSummary(entryPrepareValues),
|
|
1164
|
-
entryEmitOutputMs: createMetricSummary(entryEmitOutputValues),
|
|
1165
|
-
entryStyleScanMs: createMetricSummary(entryStyleScanValues),
|
|
1166
|
-
entryStyleReadMs: createMetricSummary(entryStyleReadValues),
|
|
1167
|
-
entryResolveMs: createMetricSummary(entryResolveValues),
|
|
1168
|
-
entryChunkEmitMs: createMetricSummary(entryChunkEmitValues),
|
|
1169
|
-
entryChunkLoadMs: createMetricSummary(entryChunkLoadValues),
|
|
1170
|
-
entryChunkEmitFileMs: createMetricSummary(entryChunkEmitFileValues),
|
|
1171
|
-
entryLayoutMs: createMetricSummary(entryLayoutValues),
|
|
1172
|
-
requestGlobalsMs: createMetricSummary(requestGlobalsValues),
|
|
1173
|
-
weapiResolveMs: createMetricSummary(weapiResolveValues),
|
|
1174
|
-
renderStartMs: createMetricSummary(renderStartValues),
|
|
1175
|
-
generateBundleMs: createMetricSummary(generateBundleValues),
|
|
1176
|
-
generateSharedMs: createMetricSummary(generateSharedValues),
|
|
1177
|
-
generateRewriteMs: createMetricSummary(generateRewriteValues),
|
|
1178
|
-
generateModuleGraphMs: createMetricSummary(generateModuleGraphValues),
|
|
1179
|
-
snapshotResolveMs: createMetricSummary(snapshotResolveValues),
|
|
1180
|
-
snapshotBuildMs: createMetricSummary(snapshotBuildValues),
|
|
1181
|
-
writeMs: createMetricSummary(writeValues),
|
|
1182
|
-
watchToDirtyMs: createMetricSummary(watchToDirtyValues),
|
|
1183
|
-
emitMs: createMetricSummary(emitValues),
|
|
1184
|
-
sharedChunkResolveMs: createMetricSummary(sharedChunkValues)
|
|
1185
|
-
},
|
|
1186
|
-
operations: {
|
|
1187
|
-
chunkEmitCount: createOperationSummary(chunkEmitCountValues),
|
|
1188
|
-
loadCount: createOperationSummary(loadCountValues),
|
|
1189
|
-
resolveCount: createOperationSummary(resolveCountValues),
|
|
1190
|
-
skippedLoadedCount: createOperationSummary(skippedLoadedCountValues)
|
|
1191
|
-
},
|
|
1192
|
-
events: sortCountEntries(eventCounts),
|
|
1193
|
-
dirtyReasons: sortCountEntries(dirtyReasonCounts),
|
|
1194
|
-
pendingReasons: sortCountEntries(pendingReasonCounts),
|
|
1195
|
-
slowestSamples
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
//#endregion
|
|
1199
|
-
//#region src/analyze/preload/budget.ts
|
|
1200
|
-
const PRELOAD_LIMIT_BYTES = 2097152;
|
|
1201
|
-
function createPackageSizeMap(result) {
|
|
1202
|
-
return new Map((result?.packages ?? []).map((pkg) => [pkg.id, pkg.files.reduce((total, file) => total + (file.size ?? 0), 0)]));
|
|
1203
|
-
}
|
|
1204
|
-
function getConfiguredPackages(rule, appJson) {
|
|
1205
|
-
if (!rule || typeof rule !== "object" || !("packages" in rule)) return [];
|
|
1206
|
-
const packages = rule.packages;
|
|
1207
|
-
if (!Array.isArray(packages)) return [];
|
|
1208
|
-
return packages.flatMap((item) => typeof item === "string" ? [resolvePreloadPackageIdentifier(item, appJson)] : []).filter((item) => Boolean(item));
|
|
1209
|
-
}
|
|
1210
|
-
function createPreloadBudgets(appJson, result, configuredRules, packageAnalysis) {
|
|
1211
|
-
const packageSizes = createPackageSizeMap(packageAnalysis);
|
|
1212
|
-
const bySourcePackage = /* @__PURE__ */ new Map();
|
|
1213
|
-
const ensureSourcePackage = (page) => {
|
|
1214
|
-
const sourcePackage = page.packageRoot ?? "__main__";
|
|
1215
|
-
let entry = bySourcePackage.get(sourcePackage);
|
|
1216
|
-
if (!entry) {
|
|
1217
|
-
entry = {
|
|
1218
|
-
sourceType: page.packageRoot ? page.independent ? "independent" : "subpackage" : "main",
|
|
1219
|
-
targets: /* @__PURE__ */ new Map()
|
|
1220
|
-
};
|
|
1221
|
-
bySourcePackage.set(sourcePackage, entry);
|
|
1222
|
-
}
|
|
1223
|
-
return entry;
|
|
1224
|
-
};
|
|
1225
|
-
for (const page of result.pages) {
|
|
1226
|
-
const configuredKey = findPreloadRuleKey(page.route, configuredRules);
|
|
1227
|
-
if (configuredKey === void 0) continue;
|
|
1228
|
-
const entry = ensureSourcePackage(page);
|
|
1229
|
-
for (const packageRoot of getConfiguredPackages(configuredRules[configuredKey], appJson)) {
|
|
1230
|
-
const target = entry.targets.get(packageRoot) ?? {
|
|
1231
|
-
configured: false,
|
|
1232
|
-
suggested: false
|
|
1233
|
-
};
|
|
1234
|
-
target.configured = true;
|
|
1235
|
-
entry.targets.set(packageRoot, target);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
const pageByRoute = new Map(result.pages.map((page) => [page.route, page]));
|
|
1239
|
-
for (const suggestion of result.suggestions) {
|
|
1240
|
-
const page = pageByRoute.get(suggestion.page);
|
|
1241
|
-
if (!page) continue;
|
|
1242
|
-
const entry = ensureSourcePackage(page);
|
|
1243
|
-
const target = entry.targets.get(suggestion.packageRoot) ?? {
|
|
1244
|
-
configured: false,
|
|
1245
|
-
suggested: false
|
|
1246
|
-
};
|
|
1247
|
-
target.suggested = true;
|
|
1248
|
-
entry.targets.set(suggestion.packageRoot, target);
|
|
1249
|
-
}
|
|
1250
|
-
return [...bySourcePackage.entries()].map(([sourcePackage, entry]) => {
|
|
1251
|
-
const targets = [...entry.targets.entries()].map(([packageRoot, state]) => {
|
|
1252
|
-
const packageId = packageRoot === "__APP__" ? "__main__" : packageRoot;
|
|
1253
|
-
return {
|
|
1254
|
-
packageRoot,
|
|
1255
|
-
bytes: packageSizes.get(packageId),
|
|
1256
|
-
...state
|
|
1257
|
-
};
|
|
1258
|
-
}).sort((left, right) => left.packageRoot.localeCompare(right.packageRoot));
|
|
1259
|
-
const unknownPackages = targets.filter((target) => target.bytes === void 0).map((target) => target.packageRoot);
|
|
1260
|
-
const estimatedBytes = targets.reduce((total, target) => total + (target.bytes ?? 0), 0);
|
|
1261
|
-
const status = estimatedBytes > PRELOAD_LIMIT_BYTES ? "exceeded" : unknownPackages.length > 0 ? "unknown" : "ok";
|
|
1262
|
-
return {
|
|
1263
|
-
sourcePackage,
|
|
1264
|
-
sourceType: entry.sourceType,
|
|
1265
|
-
limitBytes: PRELOAD_LIMIT_BYTES,
|
|
1266
|
-
estimatedBytes,
|
|
1267
|
-
remainingBytes: unknownPackages.length === 0 ? Math.max(0, PRELOAD_LIMIT_BYTES - estimatedBytes) : void 0,
|
|
1268
|
-
status,
|
|
1269
|
-
targets,
|
|
1270
|
-
unknownPackages
|
|
1271
|
-
};
|
|
1272
|
-
}).sort((left, right) => left.sourcePackage.localeCompare(right.sourcePackage));
|
|
1273
|
-
}
|
|
1274
|
-
//#endregion
|
|
1275
|
-
//#region src/analyze/preload/index.ts
|
|
1276
|
-
async function readFileIfExists(filePath) {
|
|
1277
|
-
if (!filePath) return;
|
|
1278
|
-
try {
|
|
1279
|
-
return await fs$1.readFile(filePath, "utf8");
|
|
1280
|
-
} catch {
|
|
1281
|
-
return;
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
function splitVueSource(source, filename) {
|
|
1285
|
-
try {
|
|
1286
|
-
const { descriptor } = parse(source, { filename });
|
|
1287
|
-
return {
|
|
1288
|
-
template: descriptor.template?.content,
|
|
1289
|
-
script: [descriptor.script?.content, descriptor.scriptSetup?.content].filter((content) => Boolean(content)).join("\n")
|
|
1290
|
-
};
|
|
1291
|
-
} catch {
|
|
1292
|
-
return {
|
|
1293
|
-
template: source,
|
|
1294
|
-
script: source
|
|
1295
|
-
};
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
async function collectPageSources(ctx, route) {
|
|
1299
|
-
const base = path.resolve(ctx.configService.absoluteSrcRoot, route);
|
|
1300
|
-
const [templateEntry, scriptEntry, vueEntry] = await Promise.all([
|
|
1301
|
-
findTemplateEntry(base, ctx.configService.platform),
|
|
1302
|
-
findJsEntry(base),
|
|
1303
|
-
findVueEntry(base)
|
|
1304
|
-
]);
|
|
1305
|
-
if (vueEntry) {
|
|
1306
|
-
const source = await readFileIfExists(vueEntry);
|
|
1307
|
-
return source ? splitVueSource(source, vueEntry) : {};
|
|
1308
|
-
}
|
|
1309
|
-
const [template, script] = await Promise.all([readFileIfExists(templateEntry.path), readFileIfExists(scriptEntry.path)]);
|
|
1310
|
-
return {
|
|
1311
|
-
template,
|
|
1312
|
-
script
|
|
1313
|
-
};
|
|
1314
|
-
}
|
|
1315
|
-
function aggregateSuggestions(result, configuredRules, appJson) {
|
|
1316
|
-
const byPage = /* @__PURE__ */ new Map();
|
|
1317
|
-
for (const suggestion of result.suggestions) {
|
|
1318
|
-
const current = byPage.get(suggestion.page) ?? {
|
|
1319
|
-
page: suggestion.page,
|
|
1320
|
-
packages: [],
|
|
1321
|
-
evidence: [],
|
|
1322
|
-
alreadyConfigured: []
|
|
1323
|
-
};
|
|
1324
|
-
if (!current.packages.includes(suggestion.packageRoot)) current.packages.push(suggestion.packageRoot);
|
|
1325
|
-
current.evidence.push({
|
|
1326
|
-
target: suggestion.target,
|
|
1327
|
-
packageRoot: suggestion.packageRoot,
|
|
1328
|
-
source: suggestion.source
|
|
1329
|
-
});
|
|
1330
|
-
const configuredKey = findPreloadRuleKey(suggestion.page, configuredRules);
|
|
1331
|
-
const existingRule = configuredKey === void 0 ? void 0 : configuredRules[configuredKey];
|
|
1332
|
-
const existingPackages = existingRule && typeof existingRule === "object" && "packages" in existingRule ? existingRule.packages : void 0;
|
|
1333
|
-
if ((Array.isArray(existingPackages) ? existingPackages.flatMap((item) => typeof item === "string" ? [resolvePreloadPackageIdentifier(item, appJson)] : []) : []).includes(suggestion.packageRoot)) current.alreadyConfigured.push(suggestion.packageRoot);
|
|
1334
|
-
byPage.set(suggestion.page, current);
|
|
1335
|
-
}
|
|
1336
|
-
return [...byPage.values()].map((suggestion) => ({
|
|
1337
|
-
...suggestion,
|
|
1338
|
-
packages: suggestion.packages.sort(),
|
|
1339
|
-
alreadyConfigured: [...new Set(suggestion.alreadyConfigured)].sort(),
|
|
1340
|
-
evidence: suggestion.evidence.sort((left, right) => left.target.localeCompare(right.target))
|
|
1341
|
-
}));
|
|
1342
|
-
}
|
|
1343
|
-
async function analyzePreloadRules(ctx, options = {}) {
|
|
1344
|
-
const appJson = (await ctx.scanService.loadAppEntry()).json;
|
|
1345
|
-
const pages = collectPreloadPages(appJson);
|
|
1346
|
-
const pageSources = /* @__PURE__ */ new Map();
|
|
1347
|
-
const sourceEntries = await Promise.all(pages.map(async (page) => [page.route, await collectPageSources(ctx, page.route)]));
|
|
1348
|
-
for (const [route, source] of sourceEntries) pageSources.set(route, source);
|
|
1349
|
-
const configuredRules = appJson.preloadRule && typeof appJson.preloadRule === "object" ? appJson.preloadRule : {};
|
|
1350
|
-
const suggestions = suggestPreloadRules(appJson, pageSources);
|
|
1351
|
-
return {
|
|
1352
|
-
runtime: "mini",
|
|
1353
|
-
kind: "preload",
|
|
1354
|
-
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
1355
|
-
platform: ctx.configService.platform,
|
|
1356
|
-
pages: pages.map((page) => page.route),
|
|
1357
|
-
configuredRules,
|
|
1358
|
-
suggestions: aggregateSuggestions(suggestions, configuredRules, appJson),
|
|
1359
|
-
budgets: createPreloadBudgets(appJson, suggestions, configuredRules, options.packageAnalysis),
|
|
1360
|
-
uncoveredPages: suggestions.uncovered,
|
|
1361
|
-
limitations: [
|
|
1362
|
-
"仅分析静态路由字面量;动态路由、后端配置和运行时守卫不会被推断。",
|
|
1363
|
-
"建议使用分包 root 作为 packages 值;预算按触发页所属包聚合,未知体积目标仍需人工复核。",
|
|
1364
|
-
"静态跳转只能证明可达性,不能代表真实访问频率,正式启用前仍需结合业务数据确认。",
|
|
1365
|
-
"该命令只提供可审计建议,不会自动修改源码或覆盖手写的 app.json.preloadRule。"
|
|
1366
|
-
]
|
|
1367
|
-
};
|
|
1368
|
-
}
|
|
1369
|
-
//#endregion
|
|
1370
|
-
//#region src/analyze/components/suggestions.ts
|
|
1371
|
-
function createComponentSuggestion(usage) {
|
|
1372
|
-
if (usage.componentPackage !== "__main__" || usage.crossPackageUsageCount === 0) return;
|
|
1373
|
-
if (usage.crossPackageUsageCount === usage.placeholderCoveredCrossPackageUsageCount) return;
|
|
1374
|
-
const pagePackages = Array.from(new Set(Array.from(usage.pages.values()).map((page) => page.packageId))).sort((left, right) => {
|
|
1375
|
-
if (left === "__main__") return -1;
|
|
1376
|
-
if (right === "__main__") return 1;
|
|
1377
|
-
return left.localeCompare(right);
|
|
1378
|
-
});
|
|
1379
|
-
const subPackageIds = pagePackages.filter((packageId) => packageId !== "__main__");
|
|
1380
|
-
const usedByMain = pagePackages.includes("__main__");
|
|
1381
|
-
if (subPackageIds.length === 1 && !usedByMain) {
|
|
1382
|
-
const targetPackage = subPackageIds[0];
|
|
1383
|
-
return {
|
|
1384
|
-
kind: "move-to-subpackage",
|
|
1385
|
-
component: usage.component,
|
|
1386
|
-
componentPackage: usage.componentPackage,
|
|
1387
|
-
targetPackage,
|
|
1388
|
-
pagePackages,
|
|
1389
|
-
message: `主包组件 ${usage.component} 仅被分包 ${targetPackage} 使用,建议评估移动到该分包。`
|
|
1390
|
-
};
|
|
1391
|
-
}
|
|
1392
|
-
if (subPackageIds.length > 1) return {
|
|
1393
|
-
kind: "shared-subpackage-or-placeholder",
|
|
1394
|
-
component: usage.component,
|
|
1395
|
-
componentPackage: usage.componentPackage,
|
|
1396
|
-
pagePackages,
|
|
1397
|
-
message: `主包组件 ${usage.component} 被多个分包使用,建议评估分包归属、共享策略或 componentPlaceholder。`
|
|
1398
|
-
};
|
|
1399
|
-
if (usedByMain && subPackageIds.length > 0) return {
|
|
1400
|
-
kind: "split-or-async",
|
|
1401
|
-
component: usage.component,
|
|
1402
|
-
componentPackage: usage.componentPackage,
|
|
1403
|
-
pagePackages,
|
|
1404
|
-
message: `主包组件 ${usage.component} 同时被主包和分包使用,建议评估组件拆分、归属或异步化策略。`
|
|
1405
|
-
};
|
|
1406
|
-
}
|
|
1407
|
-
//#endregion
|
|
1408
|
-
//#region src/analyze/components/index.ts
|
|
1409
|
-
function normalizeRoute(value) {
|
|
1410
|
-
return posix.normalize(value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.json$/, ""));
|
|
1411
|
-
}
|
|
1412
|
-
function isRecord(value) {
|
|
1413
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1414
|
-
}
|
|
1415
|
-
function toStringArray(value) {
|
|
1416
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim() !== "") : [];
|
|
1417
|
-
}
|
|
1418
|
-
function readUsingComponents(config) {
|
|
1419
|
-
if (!isRecord(config) || !isRecord(config.usingComponents)) return [];
|
|
1420
|
-
return Object.entries(config.usingComponents).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "");
|
|
1421
|
-
}
|
|
1422
|
-
function readComponentGenericDefaults(config) {
|
|
1423
|
-
if (!isRecord(config) || !isRecord(config.componentGenerics)) return [];
|
|
1424
|
-
return Object.entries(config.componentGenerics).map(([name, value]) => isRecord(value) && typeof value.default === "string" && value.default.trim() !== "" ? [name, value.default] : void 0).filter((entry) => Boolean(entry));
|
|
1425
|
-
}
|
|
1426
|
-
function readComponentPlaceholder(config) {
|
|
1427
|
-
if (!isRecord(config) || !isRecord(config.componentPlaceholder)) return /* @__PURE__ */ new Set();
|
|
1428
|
-
return new Set(Object.keys(config.componentPlaceholder));
|
|
1429
|
-
}
|
|
1430
|
-
function resolveComponentRoute(owner, request) {
|
|
1431
|
-
const normalizedRequest = request.replace(/\\/g, "/").trim();
|
|
1432
|
-
if (!normalizedRequest || normalizedRequest.startsWith("plugin://")) return;
|
|
1433
|
-
if (normalizedRequest.startsWith(".")) return normalizeRoute(posix.join(posix.dirname(owner), normalizedRequest));
|
|
1434
|
-
if (normalizedRequest.startsWith("/")) return normalizeRoute(normalizedRequest);
|
|
1435
|
-
return normalizeRoute(normalizedRequest);
|
|
1436
|
-
}
|
|
1437
|
-
function resolvePackageId(route, subPackages) {
|
|
1438
|
-
return subPackages.map((item) => item.root).filter(Boolean).sort((left, right) => right.length - left.length).find((root) => route === root || route.startsWith(`${root}/`)) ?? "__main__";
|
|
1439
|
-
}
|
|
1440
|
-
function collectAppPages(configs) {
|
|
1441
|
-
const appJson = configs.get("app");
|
|
1442
|
-
if (!isRecord(appJson)) return /* @__PURE__ */ new Set();
|
|
1443
|
-
const pages = /* @__PURE__ */ new Set();
|
|
1444
|
-
for (const page of toStringArray(appJson.pages)) pages.add(normalizeRoute(page));
|
|
1445
|
-
const subPackages = Array.isArray(appJson.subPackages) ? appJson.subPackages : Array.isArray(appJson.subpackages) ? appJson.subpackages : [];
|
|
1446
|
-
for (const item of subPackages) {
|
|
1447
|
-
if (!isRecord(item) || typeof item.root !== "string") continue;
|
|
1448
|
-
for (const page of toStringArray(item.pages)) pages.add(normalizeRoute(posix.join(item.root, page)));
|
|
1449
|
-
}
|
|
1450
|
-
return pages;
|
|
1451
|
-
}
|
|
1452
|
-
function registerUsage(usageMap, edge, page, pagePackage, componentPackage) {
|
|
1453
|
-
const usage = usageMap.get(edge.component) ?? {
|
|
1454
|
-
component: edge.component,
|
|
1455
|
-
componentPackage,
|
|
1456
|
-
totalUsageCount: 0,
|
|
1457
|
-
pages: /* @__PURE__ */ new Map(),
|
|
1458
|
-
crossPackageUsageCount: 0,
|
|
1459
|
-
placeholderCoveredCrossPackageUsageCount: 0
|
|
1460
|
-
};
|
|
1461
|
-
usage.totalUsageCount += 1;
|
|
1462
|
-
const pageUsage = usage.pages.get(page) ?? {
|
|
1463
|
-
page,
|
|
1464
|
-
packageId: pagePackage,
|
|
1465
|
-
usageCount: 0
|
|
1466
|
-
};
|
|
1467
|
-
pageUsage.usageCount += 1;
|
|
1468
|
-
usage.pages.set(page, pageUsage);
|
|
1469
|
-
if (pagePackage !== componentPackage) {
|
|
1470
|
-
usage.crossPackageUsageCount += 1;
|
|
1471
|
-
if (edge.placeholderCovered) usage.placeholderCoveredCrossPackageUsageCount += 1;
|
|
1472
|
-
}
|
|
1473
|
-
usageMap.set(edge.component, usage);
|
|
1474
|
-
}
|
|
1475
|
-
function collectAnalyzeComponentJsonConfigs(output) {
|
|
1476
|
-
if (!output) return [];
|
|
1477
|
-
const configs = [];
|
|
1478
|
-
for (const item of output.output ?? []) {
|
|
1479
|
-
if (item.type !== "asset" || !item.fileName.endsWith(".json")) continue;
|
|
1480
|
-
const asset = item;
|
|
1481
|
-
if (typeof asset.source !== "string") continue;
|
|
1482
|
-
try {
|
|
1483
|
-
configs.push({
|
|
1484
|
-
file: normalizeRoute(asset.fileName),
|
|
1485
|
-
config: JSON.parse(asset.source)
|
|
1486
|
-
});
|
|
1487
|
-
} catch {}
|
|
1488
|
-
}
|
|
1489
|
-
return configs;
|
|
1490
|
-
}
|
|
1491
|
-
function analyzeComponentUsage(options) {
|
|
1492
|
-
const configs = /* @__PURE__ */ new Map();
|
|
1493
|
-
for (const item of options.jsonConfigs) configs.set(normalizeRoute(item.file), item.config);
|
|
1494
|
-
const pages = collectAppPages(configs);
|
|
1495
|
-
const graph = /* @__PURE__ */ new Map();
|
|
1496
|
-
const packageMap = /* @__PURE__ */ new Map();
|
|
1497
|
-
for (const route of configs.keys()) packageMap.set(route, resolvePackageId(route, options.subPackages));
|
|
1498
|
-
for (const [owner, config] of configs) {
|
|
1499
|
-
const placeholders = readComponentPlaceholder(config);
|
|
1500
|
-
const edges = [...readUsingComponents(config), ...readComponentGenericDefaults(config)].map(([name, request]) => {
|
|
1501
|
-
const component = resolveComponentRoute(owner, request);
|
|
1502
|
-
return component && configs.has(component) ? {
|
|
1503
|
-
owner,
|
|
1504
|
-
component,
|
|
1505
|
-
placeholderCovered: placeholders.has(name)
|
|
1506
|
-
} : void 0;
|
|
1507
|
-
}).filter((edge) => Boolean(edge)).filter((edge, index, all) => all.findIndex((item) => item.component === edge.component) === index);
|
|
1508
|
-
if (edges.length > 0) graph.set(owner, edges);
|
|
1509
|
-
}
|
|
1510
|
-
const usageMap = /* @__PURE__ */ new Map();
|
|
1511
|
-
const visit = (owner, page, stack) => {
|
|
1512
|
-
for (const edge of graph.get(owner) ?? []) {
|
|
1513
|
-
const componentPackage = packageMap.get(edge.component) ?? "__main__";
|
|
1514
|
-
const pagePackage = packageMap.get(page) ?? "__main__";
|
|
1515
|
-
registerUsage(usageMap, edge, page, pagePackage, componentPackage);
|
|
1516
|
-
if (stack.has(edge.component)) continue;
|
|
1517
|
-
const nextStack = new Set(stack);
|
|
1518
|
-
nextStack.add(edge.component);
|
|
1519
|
-
visit(edge.component, page, nextStack);
|
|
1520
|
-
}
|
|
1521
|
-
};
|
|
1522
|
-
for (const page of pages) visit(page, page, /* @__PURE__ */ new Set([page]));
|
|
1523
|
-
return Array.from(usageMap.values()).map((usage) => {
|
|
1524
|
-
const pages = Array.from(usage.pages.values()).sort((left, right) => left.page.localeCompare(right.page));
|
|
1525
|
-
const suggestions = [createComponentSuggestion(usage)].filter((item) => Boolean(item));
|
|
1526
|
-
return {
|
|
1527
|
-
component: usage.component,
|
|
1528
|
-
componentPackage: usage.componentPackage,
|
|
1529
|
-
totalUsageCount: usage.totalUsageCount,
|
|
1530
|
-
pageUsageCount: pages.length,
|
|
1531
|
-
pages,
|
|
1532
|
-
suggestions
|
|
1533
|
-
};
|
|
1534
|
-
}).sort((left, right) => {
|
|
1535
|
-
const usageDelta = right.totalUsageCount - left.totalUsageCount;
|
|
1536
|
-
return usageDelta !== 0 ? usageDelta : left.component.localeCompare(right.component);
|
|
1537
|
-
});
|
|
1538
|
-
}
|
|
1539
|
-
//#endregion
|
|
1540
|
-
//#region src/analyze/subpackages/artifacts/owner.ts
|
|
1541
|
-
/** 每次分析独立缓存包边界,兼容 pnpm、工作区和普通 node_modules。 */
|
|
1542
|
-
function createModuleOwnerResolver() {
|
|
1543
|
-
const cache = /* @__PURE__ */ new Map();
|
|
1544
|
-
const resolveDirectory = (directory) => {
|
|
1545
|
-
if (cache.has(directory)) return cache.get(directory);
|
|
1546
|
-
let owner;
|
|
1547
|
-
try {
|
|
1548
|
-
const value = JSON.parse(readFileSync(path.join(directory, "package.json"), "utf8"));
|
|
1549
|
-
if (value && typeof value === "object" && "name" in value && typeof value.name === "string") owner = {
|
|
1550
|
-
name: value.name,
|
|
1551
|
-
version: "version" in value && typeof value.version === "string" ? value.version : void 0
|
|
1552
|
-
};
|
|
1553
|
-
} catch (error) {
|
|
1554
|
-
if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
|
|
1555
|
-
}
|
|
1556
|
-
const parent = path.dirname(directory);
|
|
1557
|
-
if (!owner && parent !== directory && path.basename(directory) !== "node_modules") owner = resolveDirectory(parent);
|
|
1558
|
-
cache.set(directory, owner);
|
|
1559
|
-
return owner;
|
|
1560
|
-
};
|
|
1561
|
-
return (id) => id === "\0rolldown/runtime.js" ? { name: "rolldown" } : path.isAbsolute(id) && !id.includes("\0") ? resolveDirectory(path.dirname(id.replace(/[?#].*$/, ""))) : void 0;
|
|
1562
|
-
}
|
|
1563
|
-
function classifyOwnedModule(owner, application) {
|
|
1564
|
-
if (application) return "application";
|
|
1565
|
-
if (owner?.name === "@vue/reactivity") return "reactivity";
|
|
1566
|
-
if (owner?.name === "@vue/shared" || owner?.name === "@weapp-core/constants" || owner?.name === "rolldown") return "helper";
|
|
1567
|
-
if (owner && [
|
|
1568
|
-
"weapi",
|
|
1569
|
-
"@wevu/api",
|
|
1570
|
-
"@wevu/web-apis",
|
|
1571
|
-
"@weapp-vite/web",
|
|
1572
|
-
"@weapp-vite/web-apis"
|
|
1573
|
-
].includes(owner.name)) return "host";
|
|
1574
|
-
if (owner && ["wevu", "@weapp-vite/react"].includes(owner.name)) return "runtime";
|
|
1575
|
-
return owner ? "dependency" : "unknown";
|
|
1576
|
-
}
|
|
1577
|
-
//#endregion
|
|
1578
|
-
//#region src/analyze/subpackages/artifacts/index.ts
|
|
1579
|
-
function createArtifactAnalysis(packages, resolveOwner = createModuleOwnerResolver()) {
|
|
1580
|
-
const files = /* @__PURE__ */ new Map();
|
|
1581
|
-
const moduleCopies = /* @__PURE__ */ new Map();
|
|
1582
|
-
for (const pkg of packages) for (const file of pkg.files) {
|
|
1583
|
-
if (files.has(file.file)) throw new Error(`产物重复归属:${file.file}`);
|
|
1584
|
-
if (typeof file.size !== "number" || !Number.isFinite(file.size) || file.size < 0) throw new Error(`产物缺少有效字节数:${file.file}`);
|
|
1585
|
-
const retained = (file.modules ?? []).filter((module) => module.bytes !== 0);
|
|
1586
|
-
const length = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
1587
|
-
const totalLength = retained.reduce((sum, module) => sum + length(module.bytes), 0);
|
|
1588
|
-
const scale = Math.max(totalLength, file.moduleRenderedLength ?? 0, file.size);
|
|
1589
|
-
const modules = retained.map((module) => {
|
|
1590
|
-
const owner = resolveOwner(module.id);
|
|
1591
|
-
const category = classifyOwnedModule(owner, module.sourceType === "src" || module.sourceType === "plugin");
|
|
1592
|
-
const estimatedBytes = scale > 0 ? file.size * length(module.bytes) / scale : 0;
|
|
1593
|
-
const copies = moduleCopies.get(module.id) ?? [];
|
|
1594
|
-
copies.push(estimatedBytes);
|
|
1595
|
-
moduleCopies.set(module.id, copies);
|
|
1596
|
-
return {
|
|
1597
|
-
source: module.source,
|
|
1598
|
-
package: owner,
|
|
1599
|
-
category,
|
|
1600
|
-
renderedLength: module.bytes,
|
|
1601
|
-
estimatedBytes
|
|
1602
|
-
};
|
|
1603
|
-
});
|
|
1604
|
-
const runtime = modules.filter((module) => isRuntimeCategory(module.category));
|
|
1605
|
-
const roles = new Set(modules.map((module) => isRuntimeCategory(module.category) ? "runtime" : module.category === "application" ? "application" : module.category === "dependency" ? "dependency" : "unknown"));
|
|
1606
|
-
const isAsset = file.type === "asset" && !file.modules && !/\.[cm]?js$/i.test(file.file);
|
|
1607
|
-
const role = isAsset ? "asset" : roles.size > 1 ? "mixed" : [...roles][0] ?? "unknown";
|
|
1608
|
-
files.set(file.file, {
|
|
1609
|
-
file: file.file,
|
|
1610
|
-
packageId: pkg.id,
|
|
1611
|
-
origin: file.from,
|
|
1612
|
-
type: file.type,
|
|
1613
|
-
bytes: file.size,
|
|
1614
|
-
sha256: file.sha256,
|
|
1615
|
-
role,
|
|
1616
|
-
classification: isAsset ? "asset" : modules.length ? "module-ownership" : "unavailable",
|
|
1617
|
-
estimation: "rendered-length-proportional",
|
|
1618
|
-
modules,
|
|
1619
|
-
runtimeEstimatedBytes: runtime.reduce((sum, module) => sum + module.estimatedBytes, 0),
|
|
1620
|
-
unattributedBytes: Math.max(0, file.size - modules.reduce((sum, module) => sum + module.estimatedBytes, 0))
|
|
1621
|
-
});
|
|
1622
|
-
}
|
|
1623
|
-
const artifacts = [...files.values()].sort((a, b) => a.file.localeCompare(b.file));
|
|
1624
|
-
if (artifacts.length === 0) throw new Error("未生成可分析产物,不能把缺失输出计为零字节。");
|
|
1625
|
-
return {
|
|
1626
|
-
files: artifacts,
|
|
1627
|
-
totalBytes: artifacts.reduce((sum, file) => sum + file.bytes, 0),
|
|
1628
|
-
duplicateEstimatedBytes: [...moduleCopies.values()].reduce((sum, copies) => sum + copies.reduce((a, b) => a + b, 0) - Math.max(...copies), 0),
|
|
1629
|
-
runtime: {
|
|
1630
|
-
estimatedBytes: artifacts.reduce((sum, file) => sum + file.runtimeEstimatedBytes, 0),
|
|
1631
|
-
upperBoundBytes: artifacts.filter((file) => file.modules.some((module) => isRuntimeCategory(module.category))).reduce((sum, file) => sum + file.bytes, 0),
|
|
1632
|
-
unknownFiles: artifacts.filter((file) => file.role === "unknown" || file.modules.some((module) => module.category === "unknown")).map((file) => file.file)
|
|
1633
|
-
}
|
|
1634
|
-
};
|
|
1635
|
-
}
|
|
1636
|
-
//#endregion
|
|
1637
|
-
//#region src/analyze/subpackages/registry.ts
|
|
1638
|
-
function ensurePackage(packages, classification) {
|
|
1639
|
-
const existing = packages.get(classification.id);
|
|
1640
|
-
if (existing) return existing;
|
|
1641
|
-
const created = {
|
|
1642
|
-
...classification,
|
|
1643
|
-
files: /* @__PURE__ */ new Map()
|
|
1644
|
-
};
|
|
1645
|
-
packages.set(classification.id, created);
|
|
1646
|
-
return created;
|
|
1647
|
-
}
|
|
1648
|
-
function ensureModule(modules, id, source, sourceType) {
|
|
1649
|
-
const existing = modules.get(id);
|
|
1650
|
-
if (existing) return existing;
|
|
1651
|
-
const created = {
|
|
1652
|
-
id,
|
|
1653
|
-
source,
|
|
1654
|
-
sourceType,
|
|
1655
|
-
packages: /* @__PURE__ */ new Map()
|
|
1656
|
-
};
|
|
1657
|
-
modules.set(id, created);
|
|
1658
|
-
return created;
|
|
1659
|
-
}
|
|
1660
|
-
function registerModuleInPackage(modules, moduleId, source, sourceType, packageId, fileName) {
|
|
1661
|
-
const moduleEntry = ensureModule(modules, moduleId, source, sourceType);
|
|
1662
|
-
const files = moduleEntry.packages.get(packageId) ?? /* @__PURE__ */ new Set();
|
|
1663
|
-
files.add(fileName);
|
|
1664
|
-
moduleEntry.packages.set(packageId, files);
|
|
1665
|
-
}
|
|
1666
|
-
//#endregion
|
|
1667
|
-
//#region src/analyze/subpackages/output.ts
|
|
1668
|
-
function getAssetBuffer(asset) {
|
|
1669
|
-
if (typeof asset.source === "string") return Buffer.from(asset.source, "utf8");
|
|
1670
|
-
if (asset.source instanceof Uint8Array) return Buffer.from(asset.source);
|
|
1671
|
-
}
|
|
1672
|
-
function getCompressedSizes$1(content) {
|
|
1673
|
-
if (content === void 0) return {};
|
|
1674
|
-
const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
|
|
1675
|
-
return {
|
|
1676
|
-
gzipSize: gzipSync(buffer).byteLength,
|
|
1677
|
-
brotliSize: brotliCompressSync(buffer).byteLength
|
|
1678
|
-
};
|
|
1679
|
-
}
|
|
1680
|
-
function processChunk(chunk, origin, ctx, classifierContext, packages, modules) {
|
|
1681
|
-
const classification = classifyPackage(chunk.fileName, origin, classifierContext);
|
|
1682
|
-
const packageEntry = ensurePackage(packages, classification);
|
|
1683
|
-
const chunkEntry = {
|
|
1684
|
-
file: chunk.fileName,
|
|
1685
|
-
type: "chunk",
|
|
1686
|
-
from: origin,
|
|
1687
|
-
size: typeof chunk.code === "string" ? Buffer.byteLength(chunk.code, "utf8") : void 0,
|
|
1688
|
-
sha256: createHash("sha256").update(chunk.code).digest("hex"),
|
|
1689
|
-
...getCompressedSizes$1(chunk.code),
|
|
1690
|
-
isEntry: chunk.isEntry,
|
|
1691
|
-
modules: []
|
|
1692
|
-
};
|
|
1693
|
-
if (chunk.imports.length > 0) chunkEntry.imports = [...chunk.imports].sort((a, b) => a.localeCompare(b));
|
|
1694
|
-
if (chunk.dynamicImports.length > 0) chunkEntry.dynamicImports = [...chunk.dynamicImports].sort((a, b) => a.localeCompare(b));
|
|
1695
|
-
const moduleEntries = Object.entries(chunk.modules ?? {});
|
|
1696
|
-
chunkEntry.moduleRenderedLength = moduleEntries.reduce((sum, [, info]) => sum + (typeof info.renderedLength === "number" && Number.isFinite(info.renderedLength) && info.renderedLength > 0 ? info.renderedLength : 0), 0);
|
|
1697
|
-
for (const [rawModuleId, info] of moduleEntries) {
|
|
1698
|
-
const generatedRuntime = rawModuleId === "\0rolldown/runtime.js";
|
|
1699
|
-
const absoluteId = generatedRuntime ? rawModuleId : normalizeModuleId(rawModuleId);
|
|
1700
|
-
if (!absoluteId) continue;
|
|
1701
|
-
const { source, sourceType } = generatedRuntime ? {
|
|
1702
|
-
source: "rolldown:runtime",
|
|
1703
|
-
sourceType: "workspace"
|
|
1704
|
-
} : resolveModuleSourceType(absoluteId, ctx);
|
|
1705
|
-
const moduleEntry = {
|
|
1706
|
-
id: absoluteId,
|
|
1707
|
-
source,
|
|
1708
|
-
sourceType,
|
|
1709
|
-
bytes: info?.renderedLength
|
|
1710
|
-
};
|
|
1711
|
-
if (typeof info?.code === "string") moduleEntry.originalBytes = Buffer.byteLength(info.code, "utf8");
|
|
1712
|
-
chunkEntry.modules.push(moduleEntry);
|
|
1713
|
-
registerModuleInPackage(modules, absoluteId, source, sourceType, classification.id, chunk.fileName);
|
|
1714
|
-
}
|
|
1715
|
-
if (chunkEntry.modules) chunkEntry.modules.sort((a, b) => a.source.localeCompare(b.source));
|
|
1716
|
-
packageEntry.files.set(chunk.fileName, chunkEntry);
|
|
1717
|
-
}
|
|
1718
|
-
function processAsset(asset, origin, ctx, classifierContext, packages, modules) {
|
|
1719
|
-
const classification = classifyPackage(asset.fileName, origin, classifierContext);
|
|
1720
|
-
const packageEntry = ensurePackage(packages, classification);
|
|
1721
|
-
const assetBuffer = getAssetBuffer(asset);
|
|
1722
|
-
const entry = {
|
|
1723
|
-
file: asset.fileName,
|
|
1724
|
-
type: "asset",
|
|
1725
|
-
from: origin,
|
|
1726
|
-
size: assetBuffer?.byteLength,
|
|
1727
|
-
sha256: assetBuffer ? createHash("sha256").update(assetBuffer).digest("hex") : void 0,
|
|
1728
|
-
...getCompressedSizes$1(assetBuffer)
|
|
1729
|
-
};
|
|
1730
|
-
let assetSource;
|
|
1731
|
-
for (const sourceFile of [...asset.originalFileNames ?? [], asset.fileName]) {
|
|
1732
|
-
assetSource = resolveAssetSource(sourceFile, ctx);
|
|
1733
|
-
if (assetSource) break;
|
|
1734
|
-
}
|
|
1735
|
-
if (assetSource) {
|
|
1736
|
-
entry.source = assetSource.source;
|
|
1737
|
-
entry.sourceType = assetSource.sourceType;
|
|
1738
|
-
registerModuleInPackage(modules, assetSource.absolute, assetSource.source, assetSource.sourceType, classification.id, asset.fileName);
|
|
1739
|
-
}
|
|
1740
|
-
packageEntry.files.set(asset.fileName, entry);
|
|
1741
|
-
}
|
|
1742
|
-
function processOutput(output, origin, ctx, classifierContext, packages, modules, onArtifact) {
|
|
1743
|
-
if (!output) return;
|
|
1744
|
-
for (const item of output.output ?? []) if (item.type === "chunk") {
|
|
1745
|
-
processChunk(item, origin, ctx, classifierContext, packages, modules);
|
|
1746
|
-
onArtifact?.(item.fileName, item.code);
|
|
1747
|
-
} else if (item.type === "asset") {
|
|
1748
|
-
const originalChunk = ctx.runtimeState?.build?.output?.analysisChunks?.get(item.fileName);
|
|
1749
|
-
if (originalChunk) {
|
|
1750
|
-
processChunk({
|
|
1751
|
-
...originalChunk,
|
|
1752
|
-
fileName: item.fileName,
|
|
1753
|
-
code: Buffer.from(item.source).toString("utf8")
|
|
1754
|
-
}, origin, ctx, classifierContext, packages, modules);
|
|
1755
|
-
const classification = classifyPackage(item.fileName, origin, classifierContext);
|
|
1756
|
-
packages.get(classification.id).files.get(item.fileName).type = "asset";
|
|
1757
|
-
} else processAsset(item, origin, ctx, classifierContext, packages, modules);
|
|
1758
|
-
onArtifact?.(item.fileName, item.source);
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
//#endregion
|
|
1762
|
-
//#region src/analyze/subpackages/summary.ts
|
|
1763
|
-
function toArray(value) {
|
|
1764
|
-
return Array.from(value);
|
|
1765
|
-
}
|
|
1766
|
-
function summarizePackages(packages) {
|
|
1767
|
-
const order = {
|
|
1768
|
-
main: 0,
|
|
1769
|
-
subPackage: 1,
|
|
1770
|
-
independent: 2,
|
|
1771
|
-
virtual: 3
|
|
1772
|
-
};
|
|
1773
|
-
const reports = toArray(packages.values()).map((pkg) => {
|
|
1774
|
-
const files = toArray(pkg.files.values());
|
|
1775
|
-
files.sort((a, b) => a.file.localeCompare(b.file));
|
|
1776
|
-
return {
|
|
1777
|
-
id: pkg.id,
|
|
1778
|
-
label: pkg.label,
|
|
1779
|
-
type: pkg.type,
|
|
1780
|
-
files
|
|
1781
|
-
};
|
|
1782
|
-
});
|
|
1783
|
-
reports.sort((a, b) => {
|
|
1784
|
-
const delta = order[a.type] - order[b.type];
|
|
1785
|
-
if (delta !== 0) return delta;
|
|
1786
|
-
if (a.id === "__main__") return -1;
|
|
1787
|
-
if (b.id === "__main__") return 1;
|
|
1788
|
-
return a.id.localeCompare(b.id);
|
|
1789
|
-
});
|
|
1790
|
-
return reports;
|
|
1791
|
-
}
|
|
1792
|
-
function summarizeModules(modules) {
|
|
1793
|
-
const usage = toArray(modules.values()).map((module) => {
|
|
1794
|
-
const packages = toArray(module.packages.entries()).map(([packageId, files]) => {
|
|
1795
|
-
return {
|
|
1796
|
-
packageId,
|
|
1797
|
-
files: toArray(files).sort((a, b) => a.localeCompare(b))
|
|
1798
|
-
};
|
|
1799
|
-
}).sort((a, b) => {
|
|
1800
|
-
if (a.packageId === b.packageId) return 0;
|
|
1801
|
-
if (a.packageId === "__main__") return -1;
|
|
1802
|
-
if (b.packageId === "__main__") return 1;
|
|
1803
|
-
return a.packageId.localeCompare(b.packageId);
|
|
1804
|
-
});
|
|
1805
|
-
return {
|
|
1806
|
-
id: module.id,
|
|
1807
|
-
source: module.source,
|
|
1808
|
-
sourceType: module.sourceType,
|
|
1809
|
-
packages
|
|
1810
|
-
};
|
|
1811
|
-
});
|
|
1812
|
-
usage.sort((a, b) => a.source.localeCompare(b.source));
|
|
1813
|
-
return usage;
|
|
1814
|
-
}
|
|
1815
|
-
function expandVirtualModulePlacements(modules, packages, context) {
|
|
1816
|
-
for (const moduleEntry of modules.values()) {
|
|
1817
|
-
const virtualEntries = Array.from(moduleEntry.packages.entries()).filter(([packageId]) => packageId.startsWith("virtual:"));
|
|
1818
|
-
if (!virtualEntries.length) continue;
|
|
1819
|
-
const virtualFileBases = /* @__PURE__ */ new Map();
|
|
1820
|
-
for (const [virtualPackageId, files] of virtualEntries) {
|
|
1821
|
-
const combination = virtualPackageId.slice(8);
|
|
1822
|
-
if (!combination) continue;
|
|
1823
|
-
const segments = combination.split(/[_+]/).map((segment) => segment.trim()).filter(Boolean);
|
|
1824
|
-
if (!segments.length) continue;
|
|
1825
|
-
let matchingBases = virtualFileBases.get(virtualPackageId);
|
|
1826
|
-
if (!matchingBases) {
|
|
1827
|
-
matchingBases = Array.from(files).map((file) => posix.basename(file));
|
|
1828
|
-
virtualFileBases.set(virtualPackageId, matchingBases);
|
|
1829
|
-
}
|
|
1830
|
-
for (const root of segments) {
|
|
1831
|
-
if (!context.subPackageRoots.has(root)) continue;
|
|
1832
|
-
const targetPackage = packages.get(root);
|
|
1833
|
-
if (!targetPackage) continue;
|
|
1834
|
-
const moduleFiles = moduleEntry.packages.get(root) ?? /* @__PURE__ */ new Set();
|
|
1835
|
-
const targetFiles = Array.from(targetPackage.files.values()).filter((fileEntry) => {
|
|
1836
|
-
if (!matchingBases?.length) return true;
|
|
1837
|
-
const base = posix.basename(fileEntry.file);
|
|
1838
|
-
return matchingBases.includes(base);
|
|
1839
|
-
}).map((fileEntry) => fileEntry.file);
|
|
1840
|
-
if (targetFiles.length === 0) {
|
|
1841
|
-
const fallback = targetPackage.files.values().next().value;
|
|
1842
|
-
if (fallback) moduleFiles.add(fallback.file);
|
|
1843
|
-
} else for (const fileName of targetFiles) moduleFiles.add(fileName);
|
|
1844
|
-
if (moduleFiles.size > 0) moduleEntry.packages.set(root, moduleFiles);
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
function summarizeSubPackages(metas) {
|
|
1850
|
-
const descriptors = metas.map((meta) => {
|
|
1851
|
-
return {
|
|
1852
|
-
root: meta.subPackage.root ?? "",
|
|
1853
|
-
independent: Boolean(meta.subPackage.independent),
|
|
1854
|
-
name: meta.subPackage.name
|
|
1855
|
-
};
|
|
1856
|
-
}).filter((descriptor) => descriptor.root);
|
|
1857
|
-
descriptors.sort((a, b) => a.root.localeCompare(b.root));
|
|
1858
|
-
return descriptors;
|
|
1859
|
-
}
|
|
1860
|
-
//#endregion
|
|
1861
|
-
//#region src/analyze/subpackages/index.ts
|
|
1862
|
-
async function analyzeBuild(ctx, options) {
|
|
1863
|
-
const { configService, scanService, buildService } = ctx;
|
|
1864
|
-
if (!configService || !scanService || !buildService) throw new Error("analyzeSubpackages 需要先初始化 configService、scanService 和 buildService。");
|
|
1865
|
-
await scanService.loadAppEntry();
|
|
1866
|
-
const subPackageMetas = scanService.loadSubPackages();
|
|
1867
|
-
const subPackageRoots = /* @__PURE__ */ new Set();
|
|
1868
|
-
const independentRoots = /* @__PURE__ */ new Set();
|
|
1869
|
-
for (const meta of subPackageMetas) {
|
|
1870
|
-
const root = meta.subPackage.root;
|
|
1871
|
-
if (root) {
|
|
1872
|
-
subPackageRoots.add(root);
|
|
1873
|
-
if (meta.subPackage.independent) independentRoots.add(root);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
const classifierContext = {
|
|
1877
|
-
subPackageRoots,
|
|
1878
|
-
independentRoots
|
|
1879
|
-
};
|
|
1880
|
-
const analysisConfig = configService.merge(void 0, createSharedBuildConfig(configService, scanService), { build: {
|
|
1881
|
-
write: false,
|
|
1882
|
-
watch: null
|
|
1883
|
-
} });
|
|
1884
|
-
const mainResult = await build(analysisConfig);
|
|
1885
|
-
const mainOutputs = Array.isArray(mainResult) ? mainResult : [mainResult];
|
|
1886
|
-
const packages = /* @__PURE__ */ new Map();
|
|
1887
|
-
const modules = /* @__PURE__ */ new Map();
|
|
1888
|
-
const componentJsonConfigs = [];
|
|
1889
|
-
for (const output of mainOutputs) {
|
|
1890
|
-
processOutput(output, "main", ctx, classifierContext, packages, modules, options?.onArtifact);
|
|
1891
|
-
componentJsonConfigs.push(...collectAnalyzeComponentJsonConfigs(output));
|
|
1892
|
-
}
|
|
1893
|
-
for (const root of independentRoots) {
|
|
1894
|
-
const output = buildService.getIndependentOutput(root);
|
|
1895
|
-
processOutput(output, "independent", ctx, classifierContext, packages, modules, options?.onArtifact);
|
|
1896
|
-
componentJsonConfigs.push(...collectAnalyzeComponentJsonConfigs(output));
|
|
1897
|
-
}
|
|
1898
|
-
expandVirtualModulePlacements(modules, packages, classifierContext);
|
|
1899
|
-
const subPackages = summarizeSubPackages(subPackageMetas);
|
|
1900
|
-
const packageReports = summarizePackages(packages);
|
|
1901
|
-
const artifacts = createArtifactAnalysis(packageReports);
|
|
1902
|
-
const result = {
|
|
1903
|
-
schemaVersion: 2,
|
|
1904
|
-
build: {
|
|
1905
|
-
id: createHash("sha256").update(JSON.stringify([
|
|
1906
|
-
configService.platform,
|
|
1907
|
-
configService.mode,
|
|
1908
|
-
artifacts.files.map((file) => [file.file, file.sha256])
|
|
1909
|
-
])).digest("hex"),
|
|
1910
|
-
platform: configService.platform,
|
|
1911
|
-
mode: configService.mode
|
|
1912
|
-
},
|
|
1913
|
-
artifacts,
|
|
1914
|
-
metadata: createAnalyzeMetadata(ctx.configService),
|
|
1915
|
-
packages: packageReports,
|
|
1916
|
-
modules: summarizeModules(modules),
|
|
1917
|
-
subPackages,
|
|
1918
|
-
components: analyzeComponentUsage({
|
|
1919
|
-
jsonConfigs: componentJsonConfigs,
|
|
1920
|
-
subPackages
|
|
1921
|
-
}),
|
|
1922
|
-
glassEasel: createGlassEaselAnalyzeResult(ctx)
|
|
1923
|
-
};
|
|
1924
|
-
result.budgetChecks = createAnalyzeBudgetCheck(result);
|
|
1925
|
-
return result;
|
|
1926
|
-
}
|
|
1927
|
-
async function analyzeSubpackages(ctx, options) {
|
|
1928
|
-
const previous = ctx.runtimeState.build.output.analysisChunks;
|
|
1929
|
-
ctx.runtimeState.build.output.analysisChunks = /* @__PURE__ */ new Map();
|
|
1930
|
-
try {
|
|
1931
|
-
return await analyzeBuild(ctx, options);
|
|
1932
|
-
} finally {
|
|
1933
|
-
ctx.runtimeState.build.output.analysisChunks = previous;
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
//#endregion
|
|
1937
|
-
//#region src/analyze/subpackages/history.ts
|
|
1938
|
-
const jsonExtension = ".json";
|
|
1939
|
-
function createSnapshotFileName(date = /* @__PURE__ */ new Date()) {
|
|
1940
|
-
return `${date.toISOString().replace(/[:.]/g, "-")}${jsonExtension}`;
|
|
1941
|
-
}
|
|
1942
|
-
function isAnalyzeResult(value) {
|
|
1943
|
-
return Boolean(value && typeof value === "object" && Array.isArray(value.packages) && Array.isArray(value.modules) && Array.isArray(value.subPackages));
|
|
1944
|
-
}
|
|
1945
|
-
async function listSnapshotFiles(dir) {
|
|
1946
|
-
try {
|
|
1947
|
-
return (await fs$2.readdir(dir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(jsonExtension)).map((entry) => path.join(dir, entry.name)).sort((a, b) => b.localeCompare(a));
|
|
1948
|
-
} catch {
|
|
1949
|
-
return [];
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
async function trimSnapshotFiles(dir, limit) {
|
|
1953
|
-
const staleFiles = (await listSnapshotFiles(dir)).slice(limit);
|
|
1954
|
-
await Promise.all(staleFiles.map(async (file) => {
|
|
1955
|
-
try {
|
|
1956
|
-
await fs$2.unlink(file);
|
|
1957
|
-
} catch {}
|
|
1958
|
-
}));
|
|
1959
|
-
}
|
|
1960
|
-
async function readLatestAnalyzeHistorySnapshot(configService) {
|
|
1961
|
-
const history = resolveAnalyzeHistoryMetadata(configService);
|
|
1962
|
-
if (!history.enabled) return null;
|
|
1963
|
-
const files = await listSnapshotFiles(history.dir);
|
|
1964
|
-
for (const file of files) try {
|
|
1965
|
-
const parsed = JSON.parse(await fs$2.readFile(file, "utf8"));
|
|
1966
|
-
if (isAnalyzeResult(parsed)) return parsed;
|
|
1967
|
-
} catch {}
|
|
1968
|
-
return null;
|
|
1969
|
-
}
|
|
1970
|
-
async function writeAnalyzeHistorySnapshot(result, configService, now = /* @__PURE__ */ new Date()) {
|
|
1971
|
-
const history = resolveAnalyzeHistoryMetadata(configService);
|
|
1972
|
-
if (!history.enabled) return;
|
|
1973
|
-
await fs$2.mkdir(history.dir, { recursive: true });
|
|
1974
|
-
const snapshotPath = path.join(history.dir, createSnapshotFileName(now));
|
|
1975
|
-
if (result.metadata) result.metadata = {
|
|
1976
|
-
...result.metadata,
|
|
1977
|
-
history: {
|
|
1978
|
-
...history,
|
|
1979
|
-
dir: configService.relativeCwd(history.dir),
|
|
1980
|
-
latestSnapshot: configService.relativeCwd(snapshotPath)
|
|
1981
|
-
}
|
|
1982
|
-
};
|
|
1983
|
-
await fs$2.writeFile(snapshotPath, `${JSON.stringify(result, null, 2)}\n`, "utf8");
|
|
1984
|
-
await trimSnapshotFiles(history.dir, history.limit);
|
|
1985
|
-
return snapshotPath;
|
|
1986
|
-
}
|
|
1987
|
-
//#endregion
|
|
1988
|
-
//#region src/analyze/subpackages/report.ts
|
|
1989
|
-
function formatAnalyzeBytes(bytes) {
|
|
1990
|
-
if (!bytes || Number.isNaN(bytes)) return "0 B";
|
|
1991
|
-
const units = [
|
|
1992
|
-
"B",
|
|
1993
|
-
"KB",
|
|
1994
|
-
"MB",
|
|
1995
|
-
"GB"
|
|
1996
|
-
];
|
|
1997
|
-
let value = bytes;
|
|
1998
|
-
let unitIndex = 0;
|
|
1999
|
-
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
2000
|
-
value /= 1024;
|
|
2001
|
-
unitIndex++;
|
|
2002
|
-
}
|
|
2003
|
-
return `${value.toFixed(value >= 100 || unitIndex === 0 ? 0 : 2)} ${units[unitIndex]}`;
|
|
2004
|
-
}
|
|
2005
|
-
function formatDelta(bytes) {
|
|
2006
|
-
if (bytes === null) return "缺少体积测量";
|
|
2007
|
-
if (typeof bytes !== "number" || Number.isNaN(bytes) || bytes === 0) return "无变化";
|
|
2008
|
-
return `${bytes > 0 ? "+" : "-"}${formatAnalyzeBytes(Math.abs(bytes))}`;
|
|
2009
|
-
}
|
|
2010
|
-
function getFileSize(file) {
|
|
2011
|
-
return file.size ?? 0;
|
|
2012
|
-
}
|
|
2013
|
-
function getCompressedSize(file) {
|
|
2014
|
-
return file.brotliSize ?? file.gzipSize ?? 0;
|
|
2015
|
-
}
|
|
2016
|
-
function formatBudgetStatus(item) {
|
|
2017
|
-
if (item.status === "unknown") return "归因或产物字节不足,无法验收";
|
|
2018
|
-
if (item.status === "ok") return "正常";
|
|
2019
|
-
return `${item.status === "exceeded" ? "超预算" : "接近预算"} ${(item.ratio * 100).toFixed(1)}%`;
|
|
2020
|
-
}
|
|
2021
|
-
function createActionItems(options) {
|
|
2022
|
-
const actions = [];
|
|
2023
|
-
const exceededItems = options.budgetItems.filter((item) => item.status === "exceeded");
|
|
2024
|
-
const warningItems = options.budgetItems.filter((item) => item.status === "warning");
|
|
2025
|
-
const topDuplicate = options.duplicateInsights.find((item) => item.estimatedSavingBytes > 0);
|
|
2026
|
-
if (exceededItems.length > 0) actions.push(`处理 ${exceededItems[0].label} 预算超限:当前 ${formatAnalyzeBytes(exceededItems[0].currentBytes)},限制 ${formatAnalyzeBytes(exceededItems[0].limitBytes)}。`);
|
|
2027
|
-
else if (warningItems.length > 0) actions.push(`关注 ${warningItems[0].label} 预算接近阈值:当前 ${(warningItems[0].ratio * 100).toFixed(1)}%。`);
|
|
2028
|
-
if (topDuplicate) actions.push(`优先处理重复模块 ${topDuplicate.source},估算可节省 ${formatAnalyzeBytes(topDuplicate.estimatedSavingBytes)}。`);
|
|
2029
|
-
if (actions.length === 0) actions.push("当前没有预算超限或高收益重复模块,保持观察即可。");
|
|
2030
|
-
return actions;
|
|
2031
|
-
}
|
|
2032
|
-
function createReportGrowth(comparison) {
|
|
2033
|
-
if (!comparison) return [];
|
|
2034
|
-
return [...comparison.files, ...comparison.modules].filter((item) => item.deltaBytes !== null && item.deltaBytes > 0).sort((a, b) => b.deltaBytes - a.deltaBytes || (a.category ?? "").localeCompare(b.category ?? "") || a.label.localeCompare(b.label));
|
|
2035
|
-
}
|
|
2036
|
-
function createAnalyzeIncrementCategorySummary(items) {
|
|
2037
|
-
const map = /* @__PURE__ */ new Map();
|
|
2038
|
-
for (const item of items) {
|
|
2039
|
-
const category = item.category ?? "";
|
|
2040
|
-
const entry = map.get(category) ?? {
|
|
2041
|
-
category,
|
|
2042
|
-
count: 0,
|
|
2043
|
-
deltaBytes: 0
|
|
2044
|
-
};
|
|
2045
|
-
entry.count += 1;
|
|
2046
|
-
entry.deltaBytes += item.deltaBytes;
|
|
2047
|
-
map.set(category, entry);
|
|
2048
|
-
}
|
|
2049
|
-
return [...map.values()].sort((a, b) => b.deltaBytes - a.deltaBytes || b.count - a.count || a.category.localeCompare(b.category));
|
|
2050
|
-
}
|
|
2051
|
-
function createAnalyzePrMarkdownReport(result, previousResult) {
|
|
2052
|
-
const files = result.packages.flatMap((pkg) => pkg.files.map((file) => ({
|
|
2053
|
-
pkg,
|
|
2054
|
-
file
|
|
2055
|
-
})));
|
|
2056
|
-
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
2057
|
-
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
2058
|
-
const comparison = previousResult ? createAnalyzeComparison(result, previousResult) : void 0;
|
|
2059
|
-
const incrementItems = createReportGrowth(comparison);
|
|
2060
|
-
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
2061
|
-
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
2062
|
-
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
2063
|
-
const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
|
|
2064
|
-
const actionItems = createActionItems({
|
|
2065
|
-
budgetItems,
|
|
2066
|
-
duplicateInsights
|
|
2067
|
-
}).slice(0, 3);
|
|
2068
|
-
const budgetRows = budgetIssues.slice(0, 5).map((item) => `| ${item.label} | ${formatAnalyzeBytes(item.currentBytes)} | ${formatAnalyzeBytes(item.limitBytes)} | ${formatBudgetStatus(item)} |`).join("\n");
|
|
2069
|
-
const incrementRows = incrementItems.slice(0, 8).map((item) => `| ${item.label} | ${item.category} | ${item.packageLabel} | ${formatAnalyzeBytes(item.deltaBytes)} | ${item.advice} |`).join("\n");
|
|
2070
|
-
const sourceRows = incrementSummary.slice(0, 6).map((item) => `| ${item.category} | ${item.count} | ${formatAnalyzeBytes(item.deltaBytes)} |`).join("\n");
|
|
2071
|
-
const duplicateRows = duplicateInsights.slice(0, 5).map((module) => `| ${module.source} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
|
|
2072
|
-
return [
|
|
2073
|
-
"## weapp-vite analyze PR 摘要",
|
|
2074
|
-
"",
|
|
2075
|
-
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}(较上次 ${formatDelta(comparison?.deltaBytes)})`,
|
|
2076
|
-
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
2077
|
-
`- 预算告警:${budgetIssues.length}`,
|
|
2078
|
-
`- 增量归因:${incrementItems.length > 0 ? `${incrementItems.length} 项正向增长` : "无正向增长"}`,
|
|
2079
|
-
`- 跨包复用:${duplicateInsights.length}`,
|
|
2080
|
-
"",
|
|
2081
|
-
"### 建议动作",
|
|
2082
|
-
"",
|
|
2083
|
-
...actionItems.map((item) => `- ${item}`),
|
|
2084
|
-
"",
|
|
2085
|
-
"### 预算状态",
|
|
2086
|
-
"",
|
|
2087
|
-
"| 对象 | 当前体积 | 预算 | 状态 |",
|
|
2088
|
-
"| --- | ---: | ---: | --- |",
|
|
2089
|
-
budgetRows || "| - | 0 B | 0 B | 正常 |",
|
|
2090
|
-
"",
|
|
2091
|
-
"### 增量来源",
|
|
2092
|
-
"",
|
|
2093
|
-
"| 来源 | 项数 | 增量 |",
|
|
2094
|
-
"| --- | ---: | ---: |",
|
|
2095
|
-
sourceRows || "| - | 0 | 0 B |",
|
|
2096
|
-
"",
|
|
2097
|
-
"### Top 增量",
|
|
2098
|
-
"",
|
|
2099
|
-
"| 文件/模块 | 来源 | 包 | 增量 | 建议 |",
|
|
2100
|
-
"| --- | --- | --- | ---: | --- |",
|
|
2101
|
-
incrementRows || "| - | - | - | 0 B | - |",
|
|
2102
|
-
"",
|
|
2103
|
-
"### 重复模块",
|
|
2104
|
-
"",
|
|
2105
|
-
"| 模块 | 包数量 | 估算可节省 | 建议 |",
|
|
2106
|
-
"| --- | ---: | ---: | --- |",
|
|
2107
|
-
duplicateRows || "| - | 0 | 0 B | - |",
|
|
2108
|
-
""
|
|
2109
|
-
].join("\n");
|
|
2110
|
-
}
|
|
2111
|
-
function createAnalyzeMarkdownReport(result, previousResult) {
|
|
2112
|
-
const files = result.packages.flatMap((pkg) => pkg.files.map((file) => ({
|
|
2113
|
-
pkg,
|
|
2114
|
-
file
|
|
2115
|
-
})));
|
|
2116
|
-
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
2117
|
-
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
2118
|
-
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
2119
|
-
const comparison = previousResult ? createAnalyzeComparison(result, previousResult) : void 0;
|
|
2120
|
-
const incrementItems = createReportGrowth(comparison);
|
|
2121
|
-
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
2122
|
-
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
2123
|
-
const packageDeltas = new Map(comparison?.packages.map((item) => [item.packageId, item.deltaBytes]));
|
|
2124
|
-
const budgets = result.metadata?.budgets;
|
|
2125
|
-
const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
|
|
2126
|
-
const actionItems = createActionItems({
|
|
2127
|
-
budgetItems,
|
|
2128
|
-
duplicateInsights
|
|
2129
|
-
});
|
|
2130
|
-
const packageRows = result.packages.map((pkg) => {
|
|
2131
|
-
const size = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
|
|
2132
|
-
const compressed = pkg.files.reduce((sum, file) => sum + getCompressedSize(file), 0);
|
|
2133
|
-
const delta = packageDeltas.has(pkg.id) ? packageDeltas.get(pkg.id) : 0;
|
|
2134
|
-
const budgetStatus = budgetItems.find((item) => item.id === pkg.id);
|
|
2135
|
-
return `| ${pkg.label} | ${pkg.type} | ${formatAnalyzeBytes(size)} | ${formatAnalyzeBytes(compressed)} | ${formatDelta(delta)} | ${budgetStatus ? formatBudgetStatus(budgetStatus) : "正常"} |`;
|
|
2136
|
-
}).join("\n");
|
|
2137
|
-
const topFileRows = files.sort((a, b) => getFileSize(b.file) - getFileSize(a.file) || a.file.file.localeCompare(b.file.file)).slice(0, 10).map((item) => `| ${item.file.file} | ${item.pkg.label} | ${item.file.type} | ${formatAnalyzeBytes(getFileSize(item.file))} | ${formatAnalyzeBytes(getCompressedSize(item.file))} |`).join("\n");
|
|
2138
|
-
const duplicateRows = duplicateInsights.slice(0, 10).map((module) => `| ${module.source} | ${module.sourceType} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
|
|
2139
|
-
const budgetRows = budgetIssues.map((item) => `| ${item.label} | ${item.scope} | ${formatAnalyzeBytes(item.currentBytes)} | ${formatAnalyzeBytes(item.limitBytes)} | ${formatBudgetStatus(item)} |`).join("\n");
|
|
2140
|
-
const incrementRows = incrementItems.slice(0, 10).map((item) => `| ${item.label} | ${item.category} | ${item.packageLabel} | ${formatAnalyzeBytes(item.deltaBytes)} | ${item.advice} |`).join("\n");
|
|
2141
|
-
const incrementSummaryRows = incrementSummary.slice(0, 8).map((item) => `| ${item.category} | ${item.count} | ${formatAnalyzeBytes(item.deltaBytes)} |`).join("\n");
|
|
2142
|
-
return [
|
|
2143
|
-
"# weapp-vite analyze 报告",
|
|
2144
|
-
"",
|
|
2145
|
-
`生成时间:${result.metadata?.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2146
|
-
"",
|
|
2147
|
-
"## 本次变化摘要",
|
|
2148
|
-
"",
|
|
2149
|
-
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}`,
|
|
2150
|
-
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
2151
|
-
`- 较上次:${formatDelta(comparison?.deltaBytes)}`,
|
|
2152
|
-
`- 包体数量:${result.packages.length}`,
|
|
2153
|
-
`- 源码模块:${result.modules.length}`,
|
|
2154
|
-
`- 跨包复用:${duplicateInsights.length}`,
|
|
2155
|
-
`- 预算来源:${budgets?.source === "config" ? "配置" : "默认"}`,
|
|
2156
|
-
"",
|
|
2157
|
-
"## 预算告警",
|
|
2158
|
-
"",
|
|
2159
|
-
"| 对象 | 范围 | 当前体积 | 预算 | 状态 |",
|
|
2160
|
-
"| --- | --- | ---: | ---: | --- |",
|
|
2161
|
-
budgetRows || "| - | - | 0 B | 0 B | 正常 |",
|
|
2162
|
-
"",
|
|
2163
|
-
"## 建议动作",
|
|
2164
|
-
"",
|
|
2165
|
-
...actionItems.map((item) => `- ${item}`),
|
|
2166
|
-
"",
|
|
2167
|
-
"## 增量归因",
|
|
2168
|
-
"",
|
|
2169
|
-
"| 来源 | 项数 | 增量 |",
|
|
2170
|
-
"| --- | ---: | ---: |",
|
|
2171
|
-
incrementSummaryRows || "| - | 0 | 0 B |",
|
|
2172
|
-
"",
|
|
2173
|
-
"| 文件/模块 | 来源 | 包 | 增量 | 建议 |",
|
|
2174
|
-
"| --- | --- | --- | ---: | --- |",
|
|
2175
|
-
incrementRows || "| - | - | - | 0 B | - |",
|
|
2176
|
-
"",
|
|
2177
|
-
"## 包体预算",
|
|
2178
|
-
"",
|
|
2179
|
-
"| 包 | 类型 | 体积 | 压缩后 | 较上次 | 预算 |",
|
|
2180
|
-
"| --- | --- | ---: | ---: | ---: | --- |",
|
|
2181
|
-
packageRows || "| - | - | 0 B | 0 B | 无变化 | 正常 |",
|
|
2182
|
-
"",
|
|
2183
|
-
"## Top 文件",
|
|
2184
|
-
"",
|
|
2185
|
-
"| 文件 | 包 | 类型 | 体积 | 压缩后 |",
|
|
2186
|
-
"| --- | --- | --- | ---: | ---: |",
|
|
2187
|
-
topFileRows || "| - | - | - | 0 B | 0 B |",
|
|
2188
|
-
"",
|
|
2189
|
-
"## 重复模块",
|
|
2190
|
-
"",
|
|
2191
|
-
"| 模块 | 来源 | 包数量 | 估算可节省 | 建议 |",
|
|
2192
|
-
"| --- | --- | ---: | ---: | --- |",
|
|
2193
|
-
duplicateRows || "| - | - | 0 | 0 B | - |",
|
|
2194
|
-
""
|
|
2195
|
-
].join("\n");
|
|
2196
|
-
}
|
|
2197
|
-
//#endregion
|
|
2198
|
-
//#region src/cli/analyze/dashboard.ts
|
|
2199
|
-
function createInstallCommand(agent) {
|
|
2200
|
-
const resolved = resolveCommand(agent ?? "npm", "install", [ANALYZE_DASHBOARD_PACKAGE_NAME]);
|
|
2201
|
-
if (!resolved) return `npm install ${ANALYZE_DASHBOARD_PACKAGE_NAME}`;
|
|
2202
|
-
return `${resolved.command} ${resolved.args.join(" ")}`;
|
|
2203
|
-
}
|
|
2204
|
-
async function startAnalyzeDashboard(result, options) {
|
|
2205
|
-
const shutdown = getDevShutdownScope();
|
|
2206
|
-
const resolved = resolveDashboardRoot(options);
|
|
2207
|
-
if (!resolved) {
|
|
2208
|
-
logger_default.warn(`[weapp-vite ui] 未安装可选仪表盘包 ${colors.bold(colors.green(ANALYZE_DASHBOARD_PACKAGE_NAME))},已自动降级关闭 dashboard 能力。`);
|
|
2209
|
-
logger_default.info(`如需启用,请执行 ${colors.bold(colors.green(createInstallCommand(options.packageManagerAgent)))}`);
|
|
2210
|
-
return;
|
|
2211
|
-
}
|
|
2212
|
-
const { root, configFile } = resolved;
|
|
2213
|
-
const { ANALYZE_DASHBOARD_DEVFRAME_BASE, ANALYZE_DASHBOARD_HUB_BASE, createAnalyzeDashboardViteBridge } = await import("./dashboardViteBridge-Dy3nAD4m.mjs");
|
|
2214
|
-
const devframe = createAnalyzeDashboardDevframe({
|
|
2215
|
-
clientAssets: options.uiHost === "hub" ? root : void 0,
|
|
2216
|
-
snapshot: {
|
|
2217
|
-
current: result,
|
|
2218
|
-
previous: options.previousResult ?? null,
|
|
2219
|
-
artifacts: options.artifacts
|
|
2220
|
-
},
|
|
2221
|
-
initialEvents: [{
|
|
2222
|
-
kind: "command",
|
|
2223
|
-
level: "success",
|
|
2224
|
-
title: options.watch ? "dashboard watch session started" : "dashboard static session started",
|
|
2225
|
-
detail: options.watch ? "weapp-vite UI 已进入实时分析模式,后续 analyze 结果会继续推送到 dashboard。" : "weapp-vite UI 已进入静态分析模式,当前页面展示的是一次性分析结果。",
|
|
2226
|
-
tags: options.watch ? ["watch", "analyze"] : ["static", "analyze"]
|
|
2227
|
-
}, ...options.initialEvents ?? []],
|
|
2228
|
-
roots: {
|
|
2229
|
-
pluginRoot: options.pluginRoot,
|
|
2230
|
-
projectRoot: options.cwd,
|
|
2231
|
-
srcRoot: options.srcRoot ?? (options.cwd ? path.resolve(options.cwd, "src") : void 0)
|
|
2232
|
-
}
|
|
2233
|
-
});
|
|
2234
|
-
const bridge = createAnalyzeDashboardViteBridge(devframe, {
|
|
2235
|
-
projectRoot: options.cwd ?? process.cwd(),
|
|
2236
|
-
uiHost: options.uiHost
|
|
2237
|
-
});
|
|
2238
|
-
let server;
|
|
2239
|
-
let onHostClose;
|
|
2240
|
-
const closeResources = async () => {
|
|
2241
|
-
return (await Promise.allSettled([
|
|
2242
|
-
Promise.resolve().then(() => devframe.dispose()),
|
|
2243
|
-
Promise.resolve().then(() => bridge.close()),
|
|
2244
|
-
Promise.resolve().then(() => shutdown ? shutdown.run("cleanup", () => server?.close()) : server?.close())
|
|
2245
|
-
])).filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
2246
|
-
};
|
|
2247
|
-
try {
|
|
2248
|
-
server = await createDevViteServer({
|
|
2249
|
-
root,
|
|
2250
|
-
base: ANALYZE_DASHBOARD_DEVFRAME_BASE,
|
|
2251
|
-
configFile: configFile ?? false,
|
|
2252
|
-
clearScreen: false,
|
|
2253
|
-
appType: "spa",
|
|
2254
|
-
publicDir: false,
|
|
2255
|
-
plugins: [{
|
|
2256
|
-
name: "weapp-vite:dashboard-lifetime",
|
|
2257
|
-
enforce: "pre",
|
|
2258
|
-
configureServer(createdServer) {
|
|
2259
|
-
server ??= createdServer;
|
|
2260
|
-
const record = getDevServerCloseRecord(createdServer);
|
|
2261
|
-
const close = record.close;
|
|
2262
|
-
replaceDevServerClose(createdServer, async () => {
|
|
2263
|
-
try {
|
|
2264
|
-
await close();
|
|
2265
|
-
} finally {
|
|
2266
|
-
if (server && getDevServerCloseRecord(server) === record) onHostClose?.();
|
|
2267
|
-
}
|
|
2268
|
-
});
|
|
2269
|
-
}
|
|
2270
|
-
}, bridge],
|
|
2271
|
-
server: {
|
|
2272
|
-
host: "127.0.0.1",
|
|
2273
|
-
port: 0,
|
|
2274
|
-
watch: { ignored: ["**/*"] }
|
|
2275
|
-
},
|
|
2276
|
-
logLevel: "error"
|
|
2277
|
-
});
|
|
2278
|
-
if (!shutdown?.stopping) await server.listen(0);
|
|
2279
|
-
} catch (error) {
|
|
2280
|
-
const cleanupErrors = await closeResources();
|
|
2281
|
-
if (cleanupErrors.length) throw new AggregateError([error, ...cleanupErrors], "Dashboard 启动及资源清理失败", { cause: error });
|
|
2282
|
-
throw error;
|
|
2283
|
-
}
|
|
2284
|
-
const activeServer = server;
|
|
2285
|
-
const urls = [...activeServer.resolvedUrls?.local ?? [], ...activeServer.resolvedUrls?.network ?? []];
|
|
2286
|
-
const authCode = refreshTempAuthCode();
|
|
2287
|
-
const authenticatedUrls = urls.map((url) => {
|
|
2288
|
-
const entryUrl = options.uiHost === "hub" ? new URL(ANALYZE_DASHBOARD_HUB_BASE, url).href : url;
|
|
2289
|
-
return buildOtpAuthUrl(entryUrl, authCode);
|
|
2290
|
-
});
|
|
2291
|
-
let closing;
|
|
2292
|
-
let resolveExit;
|
|
2293
|
-
const waitPromise = new Promise((resolve) => {
|
|
2294
|
-
resolveExit = resolve;
|
|
2295
|
-
});
|
|
2296
|
-
const signals = ["SIGINT", "SIGTERM"];
|
|
2297
|
-
let onExit;
|
|
2298
|
-
let releaseOwnership;
|
|
2299
|
-
const finish = () => {
|
|
2300
|
-
for (const signal of signals) process.removeListener(signal, onExit);
|
|
2301
|
-
releaseOwnership?.();
|
|
2302
|
-
resolveExit();
|
|
2303
|
-
};
|
|
2304
|
-
onHostClose = () => {
|
|
2305
|
-
if (!closing) finish();
|
|
2306
|
-
};
|
|
2307
|
-
const close = () => {
|
|
2308
|
-
if (!closing) closing = closeResources().then((errors) => {
|
|
2309
|
-
if (errors.length === 1) throw errors[0];
|
|
2310
|
-
if (errors.length > 1) throw new AggregateError(errors, "Dashboard 资源清理失败");
|
|
2311
|
-
}).finally(finish);
|
|
2312
|
-
return closing;
|
|
2313
|
-
};
|
|
2314
|
-
onExit = async () => {
|
|
2315
|
-
await close().catch((error) => logger_default.error(error));
|
|
2316
|
-
};
|
|
2317
|
-
if (!shutdown) for (const signal of signals) process.once(signal, onExit);
|
|
2318
|
-
const handle = {
|
|
2319
|
-
update: devframe.update,
|
|
2320
|
-
emitRuntimeEvents: devframe.emitRuntimeEvents,
|
|
2321
|
-
waitForExit: () => waitPromise,
|
|
2322
|
-
close,
|
|
2323
|
-
urls: authenticatedUrls
|
|
2324
|
-
};
|
|
2325
|
-
releaseOwnership = shutdown?.own(close);
|
|
2326
|
-
if (shutdown?.stopping) {
|
|
2327
|
-
await close();
|
|
2328
|
-
return;
|
|
2329
|
-
}
|
|
2330
|
-
if (options.watch) {
|
|
2331
|
-
if (!options.silentStartupLog) {
|
|
2332
|
-
logger_default.info("weapp-vite UI 已启动(分析视图,实时模式),按 Ctrl+C 退出。");
|
|
2333
|
-
for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
|
|
2334
|
-
}
|
|
2335
|
-
return handle;
|
|
2336
|
-
}
|
|
2337
|
-
if (!options.silentStartupLog) {
|
|
2338
|
-
logger_default.info("weapp-vite UI 已启动(分析视图,静态模式),按 Ctrl+C 退出。");
|
|
2339
|
-
for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
|
|
2340
|
-
}
|
|
2341
|
-
await waitPromise;
|
|
2342
|
-
}
|
|
2343
|
-
//#endregion
|
|
2344
|
-
//#region src/cli/analyze/output.ts
|
|
2345
|
-
/** CLI 单次分析期间保留 JSON 专用写入入口,配置、插件和清理日志走 stderr。 */
|
|
2346
|
-
async function withAnalyzeOutput(json, run) {
|
|
2347
|
-
const stdoutWrite = process.stdout.write;
|
|
2348
|
-
const writeJson = (value) => stdoutWrite.call(process.stdout, `${JSON.stringify(value, null, 2)}\n`);
|
|
2349
|
-
if (json) process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
2350
|
-
try {
|
|
2351
|
-
await run(writeJson);
|
|
2352
|
-
} finally {
|
|
2353
|
-
process.stdout.write = stdoutWrite;
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
//#endregion
|
|
2357
|
-
//#region src/cli/commands/analyze.ts
|
|
2358
|
-
function normalizeDisplayPath(value) {
|
|
2359
|
-
return value || ".";
|
|
2360
|
-
}
|
|
2361
|
-
function getDefaultWebAnalyzeScopes() {
|
|
2362
|
-
return {
|
|
2363
|
-
supported: [
|
|
2364
|
-
"weapp.web 配置解析(enable/root/srcDir/outDir)",
|
|
2365
|
-
"runtime.executionMode 静态解析(compat/safe/strict)",
|
|
2366
|
-
"JSON 报告输出(--json/--output)"
|
|
2367
|
-
],
|
|
2368
|
-
unsupported: [
|
|
2369
|
-
"分包产物体积分析(仅小程序)",
|
|
2370
|
-
"源码模块包体映射(仅小程序)",
|
|
2371
|
-
"分析仪表盘(dashboard)"
|
|
2372
|
-
]
|
|
2373
|
-
};
|
|
2374
|
-
}
|
|
2375
|
-
function createWebAnalyzeResult(configService, options) {
|
|
2376
|
-
const webConfig = configService.weappWebConfig;
|
|
2377
|
-
const executionMode = webConfig?.pluginOptions.runtime?.executionMode ?? "compat";
|
|
2378
|
-
const scope = getDefaultWebAnalyzeScopes();
|
|
2379
|
-
const limitations = ["当前仅提供静态配置分析,不执行 Web 产物扫描。"];
|
|
2380
|
-
if (!webConfig?.enabled) limitations.push("未检测到启用的 weapp.web 配置。");
|
|
2381
|
-
return {
|
|
2382
|
-
runtime: "web",
|
|
2383
|
-
platform: options.platform,
|
|
2384
|
-
mode: configService.mode,
|
|
2385
|
-
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
2386
|
-
experimental: true,
|
|
2387
|
-
configFile: configService.configFilePath ? normalizeDisplayPath(configService.relativeCwd(configService.configFilePath)) : void 0,
|
|
2388
|
-
web: {
|
|
2389
|
-
enabled: Boolean(webConfig?.enabled),
|
|
2390
|
-
root: webConfig?.root ? normalizeDisplayPath(configService.relativeCwd(webConfig.root)) : void 0,
|
|
2391
|
-
srcDir: webConfig?.srcDir,
|
|
2392
|
-
outDir: webConfig?.outDir ? normalizeDisplayPath(configService.relativeCwd(webConfig.outDir)) : void 0,
|
|
2393
|
-
executionMode
|
|
2394
|
-
},
|
|
2395
|
-
supportedScopes: scope.supported,
|
|
2396
|
-
unsupportedScopes: scope.unsupported,
|
|
2397
|
-
limitations
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
function printAnalysisSummary(result) {
|
|
2401
|
-
const packageLabelMap = /* @__PURE__ */ new Map();
|
|
2402
|
-
const packageModuleSet = /* @__PURE__ */ new Map();
|
|
2403
|
-
for (const pkg of result.packages) packageLabelMap.set(pkg.id, pkg.label);
|
|
2404
|
-
for (const module of result.modules) for (const pkgRef of module.packages) {
|
|
2405
|
-
const set = packageModuleSet.get(pkgRef.packageId) ?? /* @__PURE__ */ new Set();
|
|
2406
|
-
set.add(module.id);
|
|
2407
|
-
packageModuleSet.set(pkgRef.packageId, set);
|
|
2408
|
-
}
|
|
2409
|
-
logger_default.success("分包分析完成");
|
|
2410
|
-
for (const pkg of result.packages) {
|
|
2411
|
-
const chunkCount = pkg.files.filter((file) => file.type === "chunk").length;
|
|
2412
|
-
const assetCount = pkg.files.length - chunkCount;
|
|
2413
|
-
const moduleCount = packageModuleSet.get(pkg.id)?.size ?? 0;
|
|
2414
|
-
logger_default.info(`- ${pkg.label}:${chunkCount} 个模块产物,${assetCount} 个资源,覆盖 ${moduleCount} 个源码模块`);
|
|
2415
|
-
}
|
|
2416
|
-
if (result.subPackages.length > 0) {
|
|
2417
|
-
logger_default.info("分包配置:");
|
|
2418
|
-
for (const descriptor of result.subPackages) {
|
|
2419
|
-
const segments = [descriptor.root];
|
|
2420
|
-
if (descriptor.name) segments.push(`别名:${descriptor.name}`);
|
|
2421
|
-
if (descriptor.independent) segments.push("独立构建");
|
|
2422
|
-
logger_default.info(`- ${segments.join(",")}`);
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
const componentUsages = result.components ?? [];
|
|
2426
|
-
if (componentUsages.length > 0) {
|
|
2427
|
-
const suggestions = componentUsages.flatMap((component) => component.suggestions);
|
|
2428
|
-
logger_default.info(`组件依赖:${componentUsages.length} 个组件,${suggestions.length} 条分包优化建议`);
|
|
2429
|
-
for (const suggestion of suggestions.slice(0, 5)) logger_default.info(`- ${suggestion.message}`);
|
|
2430
|
-
if (suggestions.length > 5) logger_default.info(`- …其余 ${suggestions.length - 5} 条组件建议请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
|
|
2431
|
-
}
|
|
2432
|
-
const duplicates = result.modules.filter((module) => module.packages.length > 1);
|
|
2433
|
-
if (duplicates.length === 0) {
|
|
2434
|
-
logger_default.info("未检测到跨包复用的源码模块。");
|
|
2435
|
-
return;
|
|
2436
|
-
}
|
|
2437
|
-
logger_default.info(`跨包复用/复制源码共 ${duplicates.length} 项:`);
|
|
2438
|
-
const limit = 10;
|
|
2439
|
-
const entries = duplicates.slice(0, limit);
|
|
2440
|
-
for (const module of entries) {
|
|
2441
|
-
const placements = module.packages.map((pkgRef) => {
|
|
2442
|
-
return `${packageLabelMap.get(pkgRef.packageId) ?? pkgRef.packageId} → ${pkgRef.files.join(", ")}`;
|
|
2443
|
-
}).join(";");
|
|
2444
|
-
logger_default.info(`- ${module.source} (${module.sourceType}):${placements}`);
|
|
2445
|
-
}
|
|
2446
|
-
if (duplicates.length > limit) logger_default.info(`- …其余 ${duplicates.length - limit} 项请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
|
|
2447
|
-
}
|
|
2448
|
-
function printBudgetCheckSummary(result) {
|
|
2449
|
-
const exceededItems = createAnalyzeBudgetCheck(result).filter((item) => item.status === "exceeded" || item.status === "unknown");
|
|
2450
|
-
if (exceededItems.length === 0) {
|
|
2451
|
-
logger_default.success("包体预算检查通过");
|
|
2452
|
-
return false;
|
|
2453
|
-
}
|
|
2454
|
-
logger_default.error(`包体预算检查失败:${exceededItems.length} 项超限`);
|
|
2455
|
-
for (const item of exceededItems) {
|
|
2456
|
-
logger_default.error(`- ${item.label}:${formatAnalyzeBytes(item.currentBytes)} / ${formatAnalyzeBytes(item.limitBytes)} (${(item.ratio * 100).toFixed(1)}%)`);
|
|
2457
|
-
if (item.status === "unknown") logger_default.error(" 产物字节或 runtime 归因不足,无法验收预算。");
|
|
2458
|
-
logger_default.error(` 文件:${item.files.join(", ") || "未知"}`);
|
|
2459
|
-
}
|
|
2460
|
-
return true;
|
|
2461
|
-
}
|
|
2462
|
-
function printGlassEaselCheckSummary(result) {
|
|
2463
|
-
const { glassEasel } = result;
|
|
2464
|
-
if (!glassEasel.detected) {
|
|
2465
|
-
logger_default.info("未检测到 glass-easel 配置。");
|
|
2466
|
-
return false;
|
|
2467
|
-
}
|
|
2468
|
-
if (glassEasel.diagnostics.length === 0) {
|
|
2469
|
-
logger_default.success("glass-easel 迁移检查通过");
|
|
2470
|
-
return false;
|
|
2471
|
-
}
|
|
2472
|
-
logger_default.info(`glass-easel 迁移检查:${glassEasel.summary.errors} 个错误,${glassEasel.summary.warnings} 个警告`);
|
|
2473
|
-
for (const diagnostic of glassEasel.diagnostics) {
|
|
2474
|
-
const location = diagnostic.line ? `:${diagnostic.line}:${diagnostic.column}` : "";
|
|
2475
|
-
const message = `[${diagnostic.code}] ${diagnostic.file}${location} ${diagnostic.message}`;
|
|
2476
|
-
if (diagnostic.severity === "error") logger_default.error(message);
|
|
2477
|
-
else logger_default.warn(message);
|
|
2478
|
-
}
|
|
2479
|
-
return glassEasel.summary.errors > 0;
|
|
2480
|
-
}
|
|
2481
|
-
function printWebAnalysisSummary(result) {
|
|
2482
|
-
logger_default.success("Web 静态分析完成");
|
|
2483
|
-
logger_default.info(`- 配置状态:${result.web.enabled ? "已启用 weapp.web" : "未启用 weapp.web"}`);
|
|
2484
|
-
if (result.web.enabled) {
|
|
2485
|
-
logger_default.info(`- root:${result.web.root ?? "."}`);
|
|
2486
|
-
logger_default.info(`- srcDir:${result.web.srcDir ?? "."}`);
|
|
2487
|
-
logger_default.info(`- outDir:${result.web.outDir ?? "dist/web"}`);
|
|
2488
|
-
}
|
|
2489
|
-
logger_default.info(`- executionMode:${result.web.executionMode}`);
|
|
2490
|
-
logger_default.info(`- 支持范围:${result.supportedScopes.join(";")}`);
|
|
2491
|
-
logger_default.warn(`- 未支持范围:${result.unsupportedScopes.join(";")}`);
|
|
2492
|
-
for (const limitation of result.limitations) logger_default.warn(`- 限制:${limitation}`);
|
|
2493
|
-
}
|
|
2494
|
-
function printPreloadAnalysisSummary(result) {
|
|
2495
|
-
logger_default.success("preloadRule 静态分析完成");
|
|
2496
|
-
if (result.suggestions.length === 0) logger_default.info("未检测到可以证明的跨分包静态跳转。");
|
|
2497
|
-
for (const suggestion of result.suggestions) {
|
|
2498
|
-
const packages = suggestion.packages.join("、");
|
|
2499
|
-
const covered = suggestion.alreadyConfigured.length > 0 ? `,已配置:${suggestion.alreadyConfigured.join("、")}` : "";
|
|
2500
|
-
logger_default.info(`- ${suggestion.page} -> ${packages}${covered}`);
|
|
2501
|
-
for (const evidence of suggestion.evidence.slice(0, 5)) logger_default.info(` - ${evidence.source}:${evidence.target}`);
|
|
2502
|
-
}
|
|
2503
|
-
if (result.uncoveredPages.length > 0) logger_default.warn(`- 未找到可扫描源码的页面:${result.uncoveredPages.join("、")}`);
|
|
2504
|
-
if (result.budgets.length > 0) {
|
|
2505
|
-
logger_default.info("预下载额度:");
|
|
2506
|
-
for (const budget of result.budgets) {
|
|
2507
|
-
const size = `${formatAnalyzeBytes(budget.estimatedBytes)} / ${formatAnalyzeBytes(budget.limitBytes)}`;
|
|
2508
|
-
const unknown = budget.unknownPackages.length > 0 ? `,未知:${budget.unknownPackages.join("、")}` : "";
|
|
2509
|
-
const status = budget.status === "exceeded" ? "超限" : budget.status === "unknown" ? "待确认" : "正常";
|
|
2510
|
-
logger_default.info(`- ${budget.sourcePackage}:${size},${status}${unknown}`);
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
for (const limitation of result.limitations) logger_default.warn(`- 限制:${limitation}`);
|
|
2514
|
-
}
|
|
2515
|
-
async function writeAnalyzeResult(result, outputOption, configService, format = "json", previousResult) {
|
|
2516
|
-
if (!outputOption) return;
|
|
2517
|
-
const baseDir = configService.cwd;
|
|
2518
|
-
const resolvedOutputPath = path.isAbsolute(outputOption) ? outputOption : path.resolve(baseDir, outputOption);
|
|
2519
|
-
await fs$1.ensureDir(path.dirname(resolvedOutputPath));
|
|
2520
|
-
const content = format === "markdown" && "packages" in result ? createAnalyzeMarkdownReport(result, previousResult) : format === "pr" && "packages" in result ? createAnalyzePrMarkdownReport(result, previousResult) : JSON.stringify(result, null, 2);
|
|
2521
|
-
await fs$1.writeFile(resolvedOutputPath, `${content}\n`, "utf8");
|
|
2522
|
-
const relativeOutput = configService.relativeCwd(resolvedOutputPath);
|
|
2523
|
-
logger_default.success(`分析结果已写入 ${colors.green(relativeOutput)}`);
|
|
2524
|
-
return resolvedOutputPath;
|
|
2525
|
-
}
|
|
2526
|
-
function formatMetricSummary(label, metric) {
|
|
2527
|
-
if (!metric.count || metric.averageMs === void 0 || metric.maxMs === void 0) return;
|
|
2528
|
-
return `${label} avg ${metric.averageMs.toFixed(2)} ms,max ${metric.maxMs.toFixed(2)} ms`;
|
|
2529
|
-
}
|
|
2530
|
-
function formatOperationSummary(label, operation) {
|
|
2531
|
-
if (!operation.count || operation.average === void 0 || operation.max === void 0) return;
|
|
2532
|
-
return `${label} avg ${operation.average.toFixed(1)},max ${operation.max.toFixed(0)}`;
|
|
2533
|
-
}
|
|
2534
|
-
function formatCountItems(items, limit = 5) {
|
|
2535
|
-
return items.slice(0, limit).map((item) => `${item.name} x${item.count}`).join(",");
|
|
2536
|
-
}
|
|
2537
|
-
function printHmrProfileAnalysisSummary(result, configService) {
|
|
2538
|
-
logger_default.success("HMR profile 分析完成");
|
|
2539
|
-
logger_default.info(`- profile:${colors.green(configService.relativeCwd(result.profilePath))}`);
|
|
2540
|
-
logger_default.info(`- 样本:${result.sampleCount} 条`);
|
|
2541
|
-
if (result.firstTimestamp && result.lastTimestamp) logger_default.info(`- 时间范围:${result.firstTimestamp} -> ${result.lastTimestamp}`);
|
|
2542
|
-
const totalSummary = formatMetricSummary("total", result.metrics.totalMs);
|
|
2543
|
-
const buildStartSummary = formatMetricSummary("build-start", result.metrics.buildStartMs);
|
|
2544
|
-
const pluginResolveSummary = formatMetricSummary("plugin-resolve", result.metrics.pluginResolveMs);
|
|
2545
|
-
const coreLoadSummary = formatMetricSummary("core-load", result.metrics.coreLoadMs);
|
|
2546
|
-
const entryLoadSummary = formatMetricSummary("entry-load", result.metrics.entryLoadMs);
|
|
2547
|
-
const vueReadSourceSummary = formatMetricSummary("vue-read", result.metrics.vueReadSourceMs);
|
|
2548
|
-
const vueCompileSummary = formatMetricSummary("vue-compile", result.metrics.vueCompileMs);
|
|
2549
|
-
const vueFinalizeCompiledSummary = formatMetricSummary("vue-finalize-compiled", result.metrics.vueFinalizeCompiledMs);
|
|
2550
|
-
const vueFinalizeCodeSummary = formatMetricSummary("vue-finalize-code", result.metrics.vueFinalizeCodeMs);
|
|
2551
|
-
const entryEmitOutputSummary = formatMetricSummary("entry-emit-output", result.metrics.entryEmitOutputMs);
|
|
2552
|
-
const entryTemplateScanSummary = formatMetricSummary("entry-template-scan", result.metrics.entryTemplateScanMs);
|
|
2553
|
-
const entryAutoImportSummary = formatMetricSummary("entry-auto-import", result.metrics.entryAutoImportMs);
|
|
2554
|
-
const entryScriptSetupSummary = formatMetricSummary("entry-script-setup", result.metrics.entryScriptSetupMs);
|
|
2555
|
-
const entryVueSignatureSummary = formatMetricSummary("entry-vue-signature", result.metrics.entryVueSignatureMs);
|
|
2556
|
-
const entrySidecarResolveSummary = formatMetricSummary("entry-sidecar-resolve", result.metrics.entrySidecarResolveMs);
|
|
2557
|
-
const entryJsonReadSummary = formatMetricSummary("entry-json-read", result.metrics.entryJsonReadMs);
|
|
2558
|
-
const entryVueConfigSummary = formatMetricSummary("entry-vue-config", result.metrics.entryVueConfigMs);
|
|
2559
|
-
const entryPrepareSummary = formatMetricSummary("entry-prepare", result.metrics.entryPrepareMs);
|
|
2560
|
-
const entryResolveSummary = formatMetricSummary("entry-resolve", result.metrics.entryResolveMs);
|
|
2561
|
-
const entryStyleScanSummary = formatMetricSummary("entry-style-scan", result.metrics.entryStyleScanMs);
|
|
2562
|
-
const entryStyleReadSummary = formatMetricSummary("entry-style-read", result.metrics.entryStyleReadMs);
|
|
2563
|
-
const entryCodeReadSummary = formatMetricSummary("entry-code-read", result.metrics.entryCodeReadMs);
|
|
2564
|
-
const entryChunkEmitSummary = formatMetricSummary("entry-chunk-emit", result.metrics.entryChunkEmitMs);
|
|
2565
|
-
const entryChunkLoadSummary = formatMetricSummary("entry-chunk-load", result.metrics.entryChunkLoadMs);
|
|
2566
|
-
const entryChunkEmitFileSummary = formatMetricSummary("entry-chunk-emit-file", result.metrics.entryChunkEmitFileMs);
|
|
2567
|
-
const entryLayoutSummary = formatMetricSummary("entry-layout", result.metrics.entryLayoutMs);
|
|
2568
|
-
const requestGlobalsSummary = formatMetricSummary("request-globals", result.metrics.requestGlobalsMs);
|
|
2569
|
-
const weapiResolveSummary = formatMetricSummary("weapi-resolve", result.metrics.weapiResolveMs);
|
|
2570
|
-
const snapshotResolveSummary = formatMetricSummary("snapshot-resolve", result.metrics.snapshotResolveMs);
|
|
2571
|
-
const snapshotBuildSummary = formatMetricSummary("snapshot-build", result.metrics.snapshotBuildMs);
|
|
2572
|
-
const writeSummary = formatMetricSummary("write", result.metrics.writeMs);
|
|
2573
|
-
const watchSummary = formatMetricSummary("watch->dirty", result.metrics.watchToDirtyMs);
|
|
2574
|
-
const emitSummary = formatMetricSummary("emit", result.metrics.emitMs);
|
|
2575
|
-
const sharedSummary = formatMetricSummary("shared", result.metrics.sharedChunkResolveMs);
|
|
2576
|
-
const chunkEmitSummary = formatOperationSummary("chunk emit", result.operations.chunkEmitCount);
|
|
2577
|
-
const loadSummary = formatOperationSummary("load calls", result.operations.loadCount);
|
|
2578
|
-
const resolveSummary = formatOperationSummary("resolve calls", result.operations.resolveCount);
|
|
2579
|
-
const skippedLoadedSummary = formatOperationSummary("loaded skips", result.operations.skippedLoadedCount);
|
|
2580
|
-
for (const summary of [
|
|
2581
|
-
totalSummary,
|
|
2582
|
-
buildStartSummary,
|
|
2583
|
-
pluginResolveSummary,
|
|
2584
|
-
coreLoadSummary,
|
|
2585
|
-
entryLoadSummary,
|
|
2586
|
-
vueReadSourceSummary,
|
|
2587
|
-
vueCompileSummary,
|
|
2588
|
-
vueFinalizeCompiledSummary,
|
|
2589
|
-
vueFinalizeCodeSummary,
|
|
2590
|
-
entryEmitOutputSummary,
|
|
2591
|
-
entryTemplateScanSummary,
|
|
2592
|
-
entryAutoImportSummary,
|
|
2593
|
-
entryScriptSetupSummary,
|
|
2594
|
-
entryVueSignatureSummary,
|
|
2595
|
-
entrySidecarResolveSummary,
|
|
2596
|
-
entryJsonReadSummary,
|
|
2597
|
-
entryVueConfigSummary,
|
|
2598
|
-
entryPrepareSummary,
|
|
2599
|
-
entryResolveSummary,
|
|
2600
|
-
entryStyleScanSummary,
|
|
2601
|
-
entryStyleReadSummary,
|
|
2602
|
-
entryCodeReadSummary,
|
|
2603
|
-
entryChunkEmitSummary,
|
|
2604
|
-
entryChunkLoadSummary,
|
|
2605
|
-
entryChunkEmitFileSummary,
|
|
2606
|
-
entryLayoutSummary,
|
|
2607
|
-
requestGlobalsSummary,
|
|
2608
|
-
weapiResolveSummary,
|
|
2609
|
-
snapshotResolveSummary,
|
|
2610
|
-
snapshotBuildSummary,
|
|
2611
|
-
writeSummary,
|
|
2612
|
-
watchSummary,
|
|
2613
|
-
emitSummary,
|
|
2614
|
-
sharedSummary
|
|
2615
|
-
]) if (summary) logger_default.info(`- ${summary}`);
|
|
2616
|
-
for (const summary of [
|
|
2617
|
-
loadSummary,
|
|
2618
|
-
resolveSummary,
|
|
2619
|
-
chunkEmitSummary,
|
|
2620
|
-
skippedLoadedSummary
|
|
2621
|
-
]) if (summary) logger_default.info(`- ${summary}`);
|
|
2622
|
-
if (result.events.length) logger_default.info(`- 事件分布:${formatCountItems(result.events)}`);
|
|
2623
|
-
if (result.dirtyReasons.length) logger_default.info(`- 主要 dirty 原因:${formatCountItems(result.dirtyReasons)}`);
|
|
2624
|
-
if (result.pendingReasons.length) logger_default.info(`- 主要 pending 原因:${formatCountItems(result.pendingReasons)}`);
|
|
2625
|
-
if (result.skippedLineCount > 0) logger_default.warn(`- 跳过 ${result.skippedLineCount} 条无法解析的 profile 记录`);
|
|
2626
|
-
if (result.slowestSamples.length) {
|
|
2627
|
-
logger_default.info("- 最慢样本:");
|
|
2628
|
-
for (const sample of result.slowestSamples.slice(0, 3)) {
|
|
2629
|
-
const fileLabel = sample.file ? configService.relativeCwd(sample.file) : "(unknown)";
|
|
2630
|
-
logger_default.info(` - ${sample.totalMs?.toFixed(2) ?? "0.00"} ms,${sample.event ?? "unknown"},${fileLabel}`);
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
function registerAnalyzeCommand(cli) {
|
|
2635
|
-
cli.command("analyze [root]", "analyze 两端包体与源码映射").option("--ui-host <host>", `[string] 启动调试 UI 并选择宿主(standalone | hub)`).option("--hmr-profile [file]", `[string | boolean] 分析 HMR JSONL profile,省略值时优先读取配置,否则回退到默认路径`).option("--json", `[boolean] 输出 JSON 结果`).option("--markdown", `[boolean] 输出 Markdown 报告`).option("--report <type>", `[string] 输出指定报告类型(pr)`).option("--budget-check", `[boolean] 检查 analyze 预算,超过预算时返回非 0 退出码`).option("--glass-easel-check", `[boolean] 检查 glass-easel 配置、模板和 SelectorQuery 迁移兼容性`).option("--preload", `[boolean] 分析静态页面跳转、实际分包体积和 preloadRule 额度`).option("--output <file>", `[string] 将分析结果写入指定文件(JSON 或 Markdown)`).option("-p, --platform <platform>", `[string] target platform (weapp | web)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).action(async (root, options) => {
|
|
2636
|
-
filterDuplicateOptions(options);
|
|
2637
|
-
options.uiHost = parseDashboardUiHost(options.uiHost);
|
|
2638
|
-
const configFile = resolveConfigFile(options);
|
|
2639
|
-
const outputJson = coerceBooleanOption(options.json);
|
|
2640
|
-
return withAnalyzeOutput(Boolean(outputJson), async (writeJson) => {
|
|
2641
|
-
const outputMarkdown = coerceBooleanOption(options.markdown);
|
|
2642
|
-
const reportType = typeof options.report === "string" ? options.report.trim() : "";
|
|
2643
|
-
const outputPrReport = reportType === "pr";
|
|
2644
|
-
if (reportType && !outputPrReport) throw new Error(`不支持的 analyze report 类型:${reportType}`);
|
|
2645
|
-
const budgetCheck = coerceBooleanOption(options.budgetCheck);
|
|
2646
|
-
const glassEaselCheck = coerceBooleanOption(options.glassEaselCheck);
|
|
2647
|
-
const launchDashboard = !outputJson && !outputMarkdown && !outputPrReport && !budgetCheck && !glassEaselCheck;
|
|
2648
|
-
const targets = resolveRuntimeTargets(options);
|
|
2649
|
-
const inlineConfig = createInlineConfig(targets);
|
|
2650
|
-
let ctx;
|
|
2651
|
-
try {
|
|
2652
|
-
ctx = await createCompilerContext({
|
|
2653
|
-
cwd: root,
|
|
2654
|
-
mode: options.mode ?? "production",
|
|
2655
|
-
configFile,
|
|
2656
|
-
inlineConfig,
|
|
2657
|
-
cliPlatform: targets.rawPlatform,
|
|
2658
|
-
projectConfigPath: options.projectConfig
|
|
2659
|
-
});
|
|
2660
|
-
logRuntimeTarget(targets, {
|
|
2661
|
-
silent: outputJson || outputMarkdown,
|
|
2662
|
-
resolvedConfigPlatform: ctx.configService.platform
|
|
2663
|
-
});
|
|
2664
|
-
const outputOption = typeof options.output === "string" ? options.output.trim() : "";
|
|
2665
|
-
if (options.hmrProfile !== void 0 && options.hmrProfile !== false) {
|
|
2666
|
-
const profileOption = typeof options.hmrProfile === "string" && options.hmrProfile.trim() ? options.hmrProfile.trim() : ctx.configService.weappViteConfig.hmr?.profileJson;
|
|
2667
|
-
const profilePath = resolveHmrProfileJsonPath({
|
|
2668
|
-
cwd: ctx.configService.cwd,
|
|
2669
|
-
option: profileOption,
|
|
2670
|
-
fallbackToDefault: true
|
|
2671
|
-
});
|
|
2672
|
-
if (!profilePath) throw new Error("未找到可用的 HMR profile 文件路径");
|
|
2673
|
-
const hmrProfileResult = await analyzeHmrProfile({ profilePath });
|
|
2674
|
-
const writtenPath = await writeAnalyzeResult(hmrProfileResult, outputOption, ctx.configService);
|
|
2675
|
-
if (outputJson) {
|
|
2676
|
-
if (!writtenPath) writeJson(hmrProfileResult);
|
|
2677
|
-
} else printHmrProfileAnalysisSummary(hmrProfileResult, ctx.configService);
|
|
2678
|
-
return;
|
|
2679
|
-
}
|
|
2680
|
-
if (coerceBooleanOption(options.preload)) {
|
|
2681
|
-
if (targets.kind !== "miniprogram" || ctx.configService.platform !== "weapp") throw new Error("preloadRule 分析目前仅支持微信小程序平台。");
|
|
2682
|
-
const packageAnalysis = await analyzeSubpackages(ctx);
|
|
2683
|
-
const preloadResult = await analyzePreloadRules(ctx, { packageAnalysis });
|
|
2684
|
-
const writtenPath = await writeAnalyzeResult(preloadResult, outputOption, ctx.configService);
|
|
2685
|
-
if (outputJson && !writtenPath) writeJson(preloadResult);
|
|
2686
|
-
if (!outputJson && !writtenPath) printPreloadAnalysisSummary(preloadResult);
|
|
2687
|
-
return;
|
|
2688
|
-
}
|
|
2689
|
-
if (getBackendForCapability(targets, "web", "analyze")) {
|
|
2690
|
-
const webResult = createWebAnalyzeResult(ctx.configService, { platform: "web" });
|
|
2691
|
-
const writtenPath = await writeAnalyzeResult(webResult, outputOption, ctx.configService);
|
|
2692
|
-
if (outputJson) {
|
|
2693
|
-
if (!writtenPath) writeJson(webResult);
|
|
2694
|
-
} else printWebAnalysisSummary(webResult);
|
|
2695
|
-
return;
|
|
2696
|
-
}
|
|
2697
|
-
if (!getBackendForCapability(targets, "miniprogram", "analyze")) {
|
|
2698
|
-
logger_default.warn("当前命令不支持该平台,请通过 --platform weapp 或 --platform web 指定目标。");
|
|
2699
|
-
return;
|
|
2700
|
-
}
|
|
2701
|
-
const previousResult = await readLatestAnalyzeHistorySnapshot(ctx.configService);
|
|
2702
|
-
ctx.runtimeState.glassEasel.silent = Boolean(outputJson || outputMarkdown || outputPrReport || glassEaselCheck);
|
|
2703
|
-
const artifactSnapshot = launchDashboard ? createDashboardArtifactSnapshot() : void 0;
|
|
2704
|
-
const result = artifactSnapshot ? await analyzeSubpackages(ctx, { onArtifact: artifactSnapshot.capture }) : await analyzeSubpackages(ctx);
|
|
2705
|
-
await writeAnalyzeHistorySnapshot(result, ctx.configService);
|
|
2706
|
-
const writtenPath = await writeAnalyzeResult(result, outputOption, ctx.configService, outputPrReport ? "pr" : outputMarkdown ? "markdown" : "json", previousResult);
|
|
2707
|
-
if (outputPrReport) {
|
|
2708
|
-
if (!writtenPath) process.stdout.write(`${createAnalyzePrMarkdownReport(result, previousResult)}\n`);
|
|
2709
|
-
} else if (outputMarkdown) {
|
|
2710
|
-
if (!writtenPath) process.stdout.write(`${createAnalyzeMarkdownReport(result, previousResult)}\n`);
|
|
2711
|
-
} else if (outputJson) {
|
|
2712
|
-
if (!writtenPath) writeJson(result);
|
|
2713
|
-
}
|
|
2714
|
-
const budgetFailed = budgetCheck ? outputJson ? createAnalyzeBudgetCheck(result).some((item) => item.status === "exceeded" || item.status === "unknown") : printBudgetCheckSummary(result) : false;
|
|
2715
|
-
const glassEaselFailed = glassEaselCheck && !outputJson ? printGlassEaselCheckSummary(result) : glassEaselCheck && result.glassEasel.summary.errors > 0;
|
|
2716
|
-
if (budgetFailed || glassEaselFailed) process.exitCode = 1;
|
|
2717
|
-
if (budgetCheck || glassEaselCheck) return;
|
|
2718
|
-
if (artifactSnapshot) {
|
|
2719
|
-
printAnalysisSummary(result);
|
|
2720
|
-
await startAnalyzeDashboard(result, {
|
|
2721
|
-
uiHost: options.uiHost,
|
|
2722
|
-
artifacts: artifactSnapshot.files,
|
|
2723
|
-
cwd: ctx.configService.cwd,
|
|
2724
|
-
packageManagerAgent: ctx.configService.packageManager.agent,
|
|
2725
|
-
pluginRoot: ctx.configService.absolutePluginRoot,
|
|
2726
|
-
srcRoot: ctx.configService.absoluteSrcRoot,
|
|
2727
|
-
previousResult
|
|
2728
|
-
});
|
|
2729
|
-
}
|
|
2730
|
-
} catch (error) {
|
|
2731
|
-
logger_default.error(error);
|
|
2732
|
-
process.exitCode = 1;
|
|
2733
|
-
} finally {
|
|
2734
|
-
if (ctx) {
|
|
2735
|
-
for (const backend of [...targets.entries].reverse()) if (backend.descriptor.capabilities.analyze) await backend.driver?.close?.(ctx);
|
|
2736
|
-
}
|
|
2737
|
-
terminateStaleSassEmbeddedProcess();
|
|
2738
|
-
}
|
|
2739
|
-
});
|
|
2740
|
-
});
|
|
2741
|
-
}
|
|
2742
|
-
//#endregion
|
|
2743
|
-
//#region src/cli/formatDuration.ts
|
|
2744
|
-
/**
|
|
2745
|
-
* 将毫秒耗时格式化为适合 CLI 展示的文本。
|
|
2746
|
-
*/
|
|
2747
|
-
function formatDuration(durationMs) {
|
|
2748
|
-
return `${durationMs}ms`;
|
|
2749
|
-
}
|
|
2750
|
-
//#endregion
|
|
2751
|
-
//#region src/cli/logBuildAppFinish.ts
|
|
2752
|
-
let logBuildAppFinishOnlyShowOnce = false;
|
|
2753
|
-
function collectServerUrls(webServer) {
|
|
2754
|
-
const urls = webServer?.resolvedUrls;
|
|
2755
|
-
if (!urls) return [];
|
|
2756
|
-
return [...urls.local ?? [], ...urls.network ?? []];
|
|
2757
|
-
}
|
|
2758
|
-
function logBuildAppFinish(configService, webServer, options = {}) {
|
|
2759
|
-
if (logBuildAppFinishOnlyShowOnce) return;
|
|
2760
|
-
const { skipMini = false, skipWeb = false, uiUrls = [] } = options;
|
|
2761
|
-
const webUrls = skipWeb ? [] : collectServerUrls(webServer);
|
|
2762
|
-
if (skipMini) {
|
|
2763
|
-
logger_default.success("开发服务已就绪:");
|
|
2764
|
-
if (webUrls.length > 0) logger_default.info(`Web:${colors.cyan(webUrls[0])}`);
|
|
2765
|
-
else logger_default.info("Web:已启动");
|
|
2766
|
-
logBuildAppFinishOnlyShowOnce = true;
|
|
2767
|
-
return;
|
|
2768
|
-
}
|
|
2769
|
-
const { command, args } = resolveCommand(configService.packageManager.agent, "run", ["open"]) ?? {
|
|
2770
|
-
command: "npm",
|
|
2771
|
-
args: ["run", "open"]
|
|
2772
|
-
};
|
|
2773
|
-
const devCommand = `${command} ${args.join(" ")}`;
|
|
2774
|
-
logger_default.success("开发服务已就绪:");
|
|
2775
|
-
logger_default.info(`小程序:执行 ${colors.bold(colors.green(devCommand))},或手动导入 ${colors.green(getProjectConfigFileName(configService.platform))}`);
|
|
2776
|
-
if (uiUrls.length > 0) logger_default.info(`UI:${colors.cyan(uiUrls[0])}`);
|
|
2777
|
-
else if (!skipMini) logger_default.info("UI:未启用");
|
|
2778
|
-
if (webUrls.length > 0) logger_default.info(`Web:${colors.cyan(webUrls[0])}`);
|
|
2779
|
-
const projectConfigFileName = getProjectConfigFileName(configService.platform);
|
|
2780
|
-
if (!uiUrls.length && !webUrls.length) logger_default.info(`提示:手动打开对应平台开发者工具,导入根目录(${colors.green(projectConfigFileName)} 文件所在目录)`);
|
|
2781
|
-
logBuildAppFinishOnlyShowOnce = true;
|
|
2782
|
-
}
|
|
2783
|
-
const WINDOWS_SEPARATOR_RE = /\\/g;
|
|
2784
|
-
function normalizeFileName(fileName) {
|
|
2785
|
-
return fileName.replace(WINDOWS_SEPARATOR_RE, "/");
|
|
2786
|
-
}
|
|
2787
|
-
function getOutputItemBytes(item) {
|
|
2788
|
-
if (item.type === "chunk") return typeof item.code === "string" ? Buffer.byteLength(item.code, "utf8") : 0;
|
|
2789
|
-
if (typeof item.source === "string") return Buffer.byteLength(item.source, "utf8");
|
|
2790
|
-
if (item.source instanceof Uint8Array) return item.source.byteLength;
|
|
2791
|
-
return 0;
|
|
2792
|
-
}
|
|
2793
|
-
function resolveSubPackageRoot(fileName, roots) {
|
|
2794
|
-
const normalized = normalizeFileName(fileName);
|
|
2795
|
-
return roots.find((root) => normalized === root || normalized.startsWith(`${root}/`));
|
|
2796
|
-
}
|
|
2797
|
-
function collectPackageSizeReports(output, subPackageMap) {
|
|
2798
|
-
const outputs = Array.isArray(output) ? output : [output];
|
|
2799
|
-
const roots = [...subPackageMap?.keys() ?? []].filter(Boolean).sort((a, b) => b.length - a.length || a.localeCompare(b));
|
|
2800
|
-
const packageBytes = /* @__PURE__ */ new Map([["__main__", 0]]);
|
|
2801
|
-
for (const root of roots) packageBytes.set(root, 0);
|
|
2802
|
-
for (const current of outputs) for (const item of current.output ?? []) {
|
|
2803
|
-
const root = resolveSubPackageRoot(item.fileName, roots) ?? "__main__";
|
|
2804
|
-
packageBytes.set(root, (packageBytes.get(root) ?? 0) + getOutputItemBytes(item));
|
|
2805
|
-
}
|
|
2806
|
-
const reports = [{
|
|
2807
|
-
root: "__main__",
|
|
2808
|
-
label: "主包",
|
|
2809
|
-
bytes: packageBytes.get("__main__") ?? 0
|
|
2810
|
-
}];
|
|
2811
|
-
for (const root of roots) {
|
|
2812
|
-
const meta = subPackageMap?.get(root);
|
|
2813
|
-
const isIndependent = Boolean(meta?.subPackage.independent);
|
|
2814
|
-
reports.push({
|
|
2815
|
-
root,
|
|
2816
|
-
label: `${isIndependent ? "独立分包" : "分包"} ${root}`,
|
|
2817
|
-
bytes: packageBytes.get(root) ?? 0
|
|
2818
|
-
});
|
|
2819
|
-
}
|
|
2820
|
-
return reports;
|
|
2821
|
-
}
|
|
2822
|
-
function logBuildPackageSizeReport(options) {
|
|
2823
|
-
const warningBytes = Number(options.warningBytes ?? 2097152);
|
|
2824
|
-
const reports = collectPackageSizeReports(options.output, options.subPackageMap);
|
|
2825
|
-
logger_default.success("主包/分包体积报告:");
|
|
2826
|
-
for (const report of reports) logger_default.info(`${report.label}:${formatBytes(report.bytes)}`);
|
|
2827
|
-
if (!(Number.isFinite(warningBytes) && warningBytes > 0)) return;
|
|
2828
|
-
for (const report of reports) {
|
|
2829
|
-
if (report.bytes <= warningBytes) continue;
|
|
2830
|
-
logger_default.warn(`[包体积] ${colors.yellow(report.label)} 体积 ${colors.yellow(formatBytes(report.bytes))},已超过阈值 ${colors.yellow(formatBytes(warningBytes))}。`);
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2833
|
-
//#endregion
|
|
2834
|
-
//#region src/cli/nodeEnv.ts
|
|
2835
|
-
function setCommandNodeEnv(nodeEnv) {
|
|
2836
|
-
process.env.NODE_ENV = nodeEnv;
|
|
2837
|
-
}
|
|
2838
|
-
//#endregion
|
|
2839
|
-
//#region src/cli/upload/autoMetadata.ts
|
|
2840
|
-
/** 只读取命令根目录的清单,不向父目录查找或修改文件。 */
|
|
2841
|
-
async function readPackageVersion(packagePath) {
|
|
2842
|
-
let manifest;
|
|
2843
|
-
try {
|
|
2844
|
-
manifest = JSON.parse(await readFile(packagePath, "utf8"));
|
|
2845
|
-
} catch (cause) {
|
|
2846
|
-
throw new Error("--bump 无法读取命令根目录的 package.json。", { cause });
|
|
2847
|
-
}
|
|
2848
|
-
if (manifest === null || typeof manifest !== "object" || !("version" in manifest) || typeof manifest.version !== "string") throw new Error("--bump 要求命令根目录 package.json 的 version 为有效的 SemVer 字符串。");
|
|
2849
|
-
return manifest.version;
|
|
2850
|
-
}
|
|
2851
|
-
/** 在首次编译配置求值前准备一次元数据;预演不写文件,实际版本由 npm 同步。 */
|
|
2852
|
-
function validateAutoUploadMetadata(options, action) {
|
|
2853
|
-
const { bump, gitDesc } = options;
|
|
2854
|
-
if (action === "preview" && (bump !== void 0 || gitDesc !== void 0)) throw new Error("--bump 和 --git-desc 仅支持 upload 或 build --upload,不支持 preview。");
|
|
2855
|
-
if (bump !== void 0 && options.uv !== void 0) throw new Error("--bump 不能与 --uv 同时使用。");
|
|
2856
|
-
if (gitDesc && options.desc !== void 0) throw new Error("--git-desc 不能与 --desc 同时使用。");
|
|
2857
|
-
if (bump !== void 0 && ![
|
|
2858
|
-
"patch",
|
|
2859
|
-
"minor",
|
|
2860
|
-
"major"
|
|
2861
|
-
].includes(bump)) throw new Error("--bump 仅支持 patch、minor 或 major。");
|
|
2862
|
-
}
|
|
2863
|
-
async function captureMetadataSnapshot(root) {
|
|
2864
|
-
const snapshots = [];
|
|
2865
|
-
for (const name of [
|
|
2866
|
-
"package.json",
|
|
2867
|
-
"package-lock.json",
|
|
2868
|
-
"npm-shrinkwrap.json"
|
|
2869
|
-
]) {
|
|
2870
|
-
const filePath = path$1.join(root, name);
|
|
2871
|
-
try {
|
|
2872
|
-
snapshots.push({
|
|
2873
|
-
path: filePath,
|
|
2874
|
-
content: await readFile(filePath, "utf8")
|
|
2875
|
-
});
|
|
2876
|
-
} catch (error) {
|
|
2877
|
-
if (error.code !== "ENOENT") throw error;
|
|
2878
|
-
snapshots.push({ path: filePath });
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
2881
|
-
return snapshots;
|
|
2882
|
-
}
|
|
2883
|
-
async function restoreMetadataSnapshot(snapshots) {
|
|
2884
|
-
for (const snapshot of snapshots) if (snapshot.content === void 0) await rm(snapshot.path, { force: true });
|
|
2885
|
-
else await writeFile(snapshot.path, snapshot.content);
|
|
2886
|
-
}
|
|
2887
|
-
async function prepareAutoUploadMetadata(cwd, options, action) {
|
|
2888
|
-
validateAutoUploadMetadata(options, action);
|
|
2889
|
-
const { bump, gitDesc } = options;
|
|
2890
|
-
if (bump === void 0 && !gitDesc) return options;
|
|
2891
|
-
if (bump !== void 0 && options.uv !== void 0) throw new Error("--bump 不能与 --uv 同时使用。");
|
|
2892
|
-
if (gitDesc && options.desc !== void 0) throw new Error("--git-desc 不能与 --desc 同时使用。");
|
|
2893
|
-
if (bump !== void 0 && bump !== "patch" && bump !== "minor" && bump !== "major") throw new Error("--bump 仅支持 patch、minor 或 major。");
|
|
2894
|
-
const root = path$1.resolve(cwd);
|
|
2895
|
-
const packagePath = path$1.join(root, "package.json");
|
|
2896
|
-
let currentVersion;
|
|
2897
|
-
let nextVersion;
|
|
2898
|
-
if (bump !== void 0) {
|
|
2899
|
-
currentVersion = await readPackageVersion(packagePath);
|
|
2900
|
-
nextVersion = semverInc(currentVersion, bump) ?? void 0;
|
|
2901
|
-
if (!nextVersion) throw new Error("--bump 要求命令根目录 package.json 的 version 为有效的 SemVer 字符串。");
|
|
2902
|
-
}
|
|
2903
|
-
let desc;
|
|
2904
|
-
if (gitDesc) {
|
|
2905
|
-
try {
|
|
2906
|
-
desc = (await x("git", [
|
|
2907
|
-
"log",
|
|
2908
|
-
"-1",
|
|
2909
|
-
"--format=%s"
|
|
2910
|
-
], {
|
|
2911
|
-
nodePath: false,
|
|
2912
|
-
throwOnError: true,
|
|
2913
|
-
nodeOptions: { cwd: root }
|
|
2914
|
-
})).stdout.trim();
|
|
2915
|
-
} catch (cause) {
|
|
2916
|
-
throw new Error("--git-desc 无法读取最新 Git 提交主题,请确认 Git 可用且仓库已有提交。", { cause });
|
|
2917
|
-
}
|
|
2918
|
-
if (!desc) throw new Error("--git-desc 要求最新 Git 提交主题非空。");
|
|
2919
|
-
}
|
|
2920
|
-
if (nextVersion) {
|
|
2921
|
-
if (options.dryRun) logger_default.info(`[upload] dry-run:计划版本 ${currentVersion} → ${nextVersion},未修改 package.json 或锁文件。`);
|
|
2922
|
-
else {
|
|
2923
|
-
try {
|
|
2924
|
-
await x("npm", [
|
|
2925
|
-
"version",
|
|
2926
|
-
nextVersion,
|
|
2927
|
-
"--no-git-tag-version",
|
|
2928
|
-
"--ignore-scripts",
|
|
2929
|
-
"--workspaces=false",
|
|
2930
|
-
"--prefix",
|
|
2931
|
-
root
|
|
2932
|
-
], {
|
|
2933
|
-
nodePath: false,
|
|
2934
|
-
throwOnError: true,
|
|
2935
|
-
nodeOptions: { cwd: root }
|
|
2936
|
-
});
|
|
2937
|
-
} catch (cause) {
|
|
2938
|
-
throw new Error("--bump 执行 npm version 失败,请检查本地版本与锁文件后重试。", { cause });
|
|
2939
|
-
}
|
|
2940
|
-
if (await readPackageVersion(packagePath) !== nextVersion) throw new Error("--bump 执行 npm version 后,命令根目录的 package.json 版本未按预期更新。");
|
|
2941
|
-
logger_default.info(`[upload] 版本已更新:${currentVersion} → ${nextVersion}。后续构建或上传失败不会回退;重试同一版本时请移除 --bump。`);
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
if (gitDesc) logger_default.info("[upload] 使用最新 Git 提交主题作为上传说明。");
|
|
2945
|
-
const metadata = { ...options };
|
|
2946
|
-
if (nextVersion !== void 0) metadata.uv = nextVersion;
|
|
2947
|
-
if (desc !== void 0) metadata.desc = desc;
|
|
2948
|
-
return metadata;
|
|
2949
|
-
}
|
|
2950
|
-
/** 配置尚未确认可上传时,保留 npm version 所有可能修改文件的回滚点。 */
|
|
2951
|
-
async function prepareAutoUploadMetadataWithRollback(cwd, options, action) {
|
|
2952
|
-
validateAutoUploadMetadata(options, action);
|
|
2953
|
-
const snapshots = options.bump !== void 0 && !options.dryRun ? await captureMetadataSnapshot(path$1.resolve(cwd)) : [];
|
|
2954
|
-
return {
|
|
2955
|
-
options: await prepareAutoUploadMetadata(cwd, options, action),
|
|
2956
|
-
rollback: () => restoreMetadataSnapshot(snapshots)
|
|
2957
|
-
};
|
|
2958
|
-
}
|
|
2959
|
-
//#endregion
|
|
2960
|
-
//#region src/cli/upload/env.ts
|
|
2961
|
-
/** 凭据不经过 Vite 的 env 调试日志;文件优先级和进程环境覆盖规则保持一致。 */
|
|
2962
|
-
async function loadUploadEnv(cwd, mode, root, envDir) {
|
|
2963
|
-
if (envDir === false) return { ...process.env };
|
|
2964
|
-
if (mode === "local") throw new Error("local 不能作为 mode,因为它与 .env.local 文件后缀冲突。");
|
|
2965
|
-
const directory = path$1.resolve(cwd, root ?? ".", envDir ?? ".");
|
|
2966
|
-
const parsed = {};
|
|
2967
|
-
for (const file of [
|
|
2968
|
-
".env",
|
|
2969
|
-
".env.local",
|
|
2970
|
-
`.env.${mode}`,
|
|
2971
|
-
`.env.${mode}.local`
|
|
2972
|
-
]) {
|
|
2973
|
-
const content = await readFile(path$1.join(directory, file), "utf8").catch((error) => {
|
|
2974
|
-
if (error.code === "ENOENT") return "";
|
|
2975
|
-
throw error;
|
|
2976
|
-
});
|
|
2977
|
-
for (const [key, value] of Object.entries(parse$2(content))) {
|
|
2978
|
-
delete parsed[key];
|
|
2979
|
-
parsed[key] = value;
|
|
2980
|
-
}
|
|
2981
|
-
}
|
|
2982
|
-
const processEnv = { ...parsed };
|
|
2983
|
-
for (const [key, value] of Object.entries(process.env)) if (value !== void 0) {
|
|
2984
|
-
processEnv[key] = value;
|
|
2985
|
-
delete parsed[key];
|
|
2986
|
-
}
|
|
2987
|
-
expand({
|
|
2988
|
-
parsed,
|
|
2989
|
-
processEnv
|
|
2990
|
-
});
|
|
2991
|
-
return {
|
|
2992
|
-
...parsed,
|
|
2993
|
-
...process.env
|
|
2994
|
-
};
|
|
2995
|
-
}
|
|
2996
|
-
//#endregion
|
|
2997
|
-
//#region src/cli/upload/executionError.ts
|
|
2998
|
-
/** 仅描述本地执行结果,不代表远端上传已取消。 */
|
|
2999
|
-
var UploadExecutionError = class extends Error {
|
|
3000
|
-
/** timeout 为本地超时,interrupted 为本地中断,failed 为工具或传输失败。 */
|
|
3001
|
-
reason;
|
|
3002
|
-
/** not-started 表示尚未允许 SDK 执行;unknown 表示远端结果需要人工核实。 */
|
|
3003
|
-
remoteOutcome;
|
|
3004
|
-
constructor(message, reason, remoteOutcome) {
|
|
3005
|
-
super(message);
|
|
3006
|
-
this.name = "UploadExecutionError";
|
|
3007
|
-
this.reason = reason;
|
|
3008
|
-
this.remoteOutcome = remoteOutcome;
|
|
3009
|
-
}
|
|
3010
|
-
};
|
|
3011
|
-
//#endregion
|
|
3012
|
-
//#region src/cli/upload/result.ts
|
|
3013
|
-
/** 只接受可展示的预览结果,文件必须由本次任务生成,不能沿用旧二维码。 */
|
|
3014
|
-
async function validatePreviewResult(value, context) {
|
|
3015
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("预览工具未返回二维码或预览链接。");
|
|
3016
|
-
const input = value;
|
|
3017
|
-
const result = {};
|
|
3018
|
-
for (const key of ["qrCodeUrl", "previewUrl"]) {
|
|
3019
|
-
if (input[key] === void 0) continue;
|
|
3020
|
-
if (typeof input[key] !== "string" || !input[key].trim()) throw new Error("预览工具返回了无效链接。");
|
|
3021
|
-
let url;
|
|
3022
|
-
try {
|
|
3023
|
-
url = new URL(input[key]);
|
|
3024
|
-
} catch {
|
|
3025
|
-
throw new Error("预览工具返回了无效链接。");
|
|
3026
|
-
}
|
|
3027
|
-
if (url.username || url.password || key === "qrCodeUrl" && url.protocol !== "http:" && url.protocol !== "https:" || [
|
|
3028
|
-
"javascript:",
|
|
3029
|
-
"data:",
|
|
3030
|
-
"file:"
|
|
3031
|
-
].includes(url.protocol)) throw new Error("预览工具返回了不支持的链接类型。");
|
|
3032
|
-
result[key] = input[key];
|
|
3033
|
-
}
|
|
3034
|
-
if (input.qrCodeFile !== void 0) {
|
|
3035
|
-
if (typeof input.qrCodeFile !== "string" || !context.qrCodePath || path$1.resolve(input.qrCodeFile) !== path$1.resolve(context.qrCodePath)) throw new Error("预览工具返回的二维码文件不是本次任务的输出路径。");
|
|
3036
|
-
const file = await stat(input.qrCodeFile);
|
|
3037
|
-
if (!file.isFile() || file.size === 0) throw new Error("预览二维码文件不存在或为空。");
|
|
3038
|
-
result.qrCodeFile = input.qrCodeFile;
|
|
3039
|
-
}
|
|
3040
|
-
if (!result.qrCodeUrl && !result.previewUrl && !result.qrCodeFile) throw new Error("预览工具未返回二维码或预览链接。");
|
|
3041
|
-
return result;
|
|
3042
|
-
}
|
|
3043
|
-
//#endregion
|
|
3044
|
-
//#region src/cli/upload/process.ts
|
|
3045
|
-
function readProgress(value, secrets) {
|
|
3046
|
-
if (!value || typeof value !== "object" || !("type" in value) || ![
|
|
3047
|
-
"progress",
|
|
3048
|
-
"log",
|
|
3049
|
-
"task-created",
|
|
3050
|
-
"version-created"
|
|
3051
|
-
].includes(String(value.type))) return;
|
|
3052
|
-
const event = { type: value.type };
|
|
3053
|
-
if ("message" in value && typeof value.message === "string") event.message = redactUploadSecrets(value.message, secrets);
|
|
3054
|
-
if ("percent" in value && typeof value.percent === "number" && Number.isFinite(value.percent) && value.percent >= 0 && value.percent <= 100) event.percent = value.percent;
|
|
3055
|
-
return event;
|
|
3056
|
-
}
|
|
3057
|
-
/** 强制终止可能截断日志尾部;同时隐藏已知凭据的不完整前缀,保留普通诊断。 */
|
|
3058
|
-
function redactDiagnostic(message, secrets) {
|
|
3059
|
-
const output = message.trimEnd();
|
|
3060
|
-
let end = output.length;
|
|
3061
|
-
for (const value of secrets) {
|
|
3062
|
-
const secret = value.trimEnd();
|
|
3063
|
-
if (!secret) continue;
|
|
3064
|
-
let start = output.indexOf(secret[0], Math.max(0, output.length - secret.length));
|
|
3065
|
-
while (start !== -1) {
|
|
3066
|
-
let offset = 0;
|
|
3067
|
-
while (start + offset < output.length && output[start + offset] === secret[offset]) offset++;
|
|
3068
|
-
if (start + offset === output.length) {
|
|
3069
|
-
end = Math.min(end, start);
|
|
3070
|
-
break;
|
|
3071
|
-
}
|
|
3072
|
-
start = output.indexOf(secret[0], start + 1);
|
|
3073
|
-
}
|
|
3074
|
-
}
|
|
3075
|
-
return redactUploadSecrets(end < output.length ? `${output.slice(0, end)}[REDACTED]` : output, secrets);
|
|
3076
|
-
}
|
|
3077
|
-
/** 隔离官方 SDK;完成确认与正常退出必须同时成立,取消仅作用于本地进程。 */
|
|
3078
|
-
async function executeUpload(platform, context, secrets, action = "upload", options = {}) {
|
|
3079
|
-
const label = `${platform} ${action === "preview" ? "预览" : "上传"}`;
|
|
3080
|
-
if (options.signal?.aborted) throw new UploadExecutionError(`${label}已中断(SDK 尚未开始)。`, "interrupted", "not-started");
|
|
3081
|
-
const { result, output } = await new Promise((resolve, reject) => {
|
|
3082
|
-
const spawnedAt = Date.now();
|
|
3083
|
-
let child;
|
|
3084
|
-
try {
|
|
3085
|
-
child = fork(fileURLToPath(new URL("./upload-worker.mjs", import.meta.url)), [], {
|
|
3086
|
-
cwd: context.cwd,
|
|
3087
|
-
env: context.env,
|
|
3088
|
-
execArgv: [],
|
|
3089
|
-
stdio: [
|
|
3090
|
-
"pipe",
|
|
3091
|
-
"pipe",
|
|
3092
|
-
"pipe",
|
|
3093
|
-
"ipc"
|
|
3094
|
-
],
|
|
3095
|
-
detached: process.platform !== "win32",
|
|
3096
|
-
windowsHide: true
|
|
3097
|
-
});
|
|
3098
|
-
} catch (error) {
|
|
3099
|
-
reject(new UploadExecutionError(redactUploadSecrets(`${label}无法启动:${String(error)}`, secrets), "failed", "not-started"));
|
|
3100
|
-
return;
|
|
3101
|
-
}
|
|
3102
|
-
let completed = false;
|
|
3103
|
-
let started = false;
|
|
3104
|
-
let settled = false;
|
|
3105
|
-
let result = {};
|
|
3106
|
-
const captured = {
|
|
3107
|
-
stdout: "",
|
|
3108
|
-
stderr: ""
|
|
3109
|
-
};
|
|
3110
|
-
let capturedSize = 0;
|
|
3111
|
-
let failure;
|
|
3112
|
-
let timer;
|
|
3113
|
-
let closeTimer;
|
|
3114
|
-
const terminate = (descendants = true) => {
|
|
3115
|
-
if (!child.pid) return;
|
|
3116
|
-
const exited = child.exitCode !== null || child.signalCode !== null;
|
|
3117
|
-
try {
|
|
3118
|
-
if (descendants && !exited && process.platform !== "win32") terminateUploadDescendants(child.pid);
|
|
3119
|
-
} catch (error) {
|
|
3120
|
-
failure ??= {
|
|
3121
|
-
reason: "failed",
|
|
3122
|
-
detail: "无法清理本地子进程"
|
|
3123
|
-
};
|
|
3124
|
-
failure.detail += `;${String(error)}`;
|
|
3125
|
-
}
|
|
3126
|
-
try {
|
|
3127
|
-
terminateUploadProcess(child.pid, {
|
|
3128
|
-
processGroup: true,
|
|
3129
|
-
exited,
|
|
3130
|
-
lifetime: {
|
|
3131
|
-
start: spawnedAt,
|
|
3132
|
-
end: Date.now()
|
|
3133
|
-
}
|
|
3134
|
-
});
|
|
3135
|
-
} catch (error) {
|
|
3136
|
-
failure ??= {
|
|
3137
|
-
reason: "failed",
|
|
3138
|
-
detail: "无法终止本地 worker"
|
|
3139
|
-
};
|
|
3140
|
-
failure.detail += `;${String(error)}`;
|
|
3141
|
-
if (!exited) child.kill("SIGKILL");
|
|
3142
|
-
}
|
|
3143
|
-
};
|
|
3144
|
-
const stop = (reason, detail) => {
|
|
3145
|
-
if (settled || failure) return;
|
|
3146
|
-
failure = {
|
|
3147
|
-
reason,
|
|
3148
|
-
detail
|
|
3149
|
-
};
|
|
3150
|
-
clearTimeout(timer);
|
|
3151
|
-
terminate();
|
|
3152
|
-
};
|
|
3153
|
-
const cancel = () => stop("interrupted", "本地执行已中断");
|
|
3154
|
-
const onParentExit = () => terminate();
|
|
3155
|
-
const collect = (stream, chunk) => {
|
|
3156
|
-
if (failure) return;
|
|
3157
|
-
const remaining = 4194304 - capturedSize;
|
|
3158
|
-
if (chunk.length > remaining) {
|
|
3159
|
-
captured.stdout = "";
|
|
3160
|
-
captured.stderr = "";
|
|
3161
|
-
stop("failed", "日志超限,已省略日志以保护凭据");
|
|
3162
|
-
return;
|
|
3163
|
-
}
|
|
3164
|
-
captured[stream] += chunk;
|
|
3165
|
-
capturedSize += chunk.length;
|
|
3166
|
-
};
|
|
3167
|
-
const onStdout = (chunk) => collect("stdout", chunk);
|
|
3168
|
-
const onStderr = (chunk) => collect("stderr", chunk);
|
|
3169
|
-
const onError = (error) => stop("failed", error.message);
|
|
3170
|
-
const onDisconnect = () => {
|
|
3171
|
-
if (!completed) stop("failed", "IPC 连接中断,未收到完成确认");
|
|
3172
|
-
};
|
|
3173
|
-
const onMessage = (message) => {
|
|
3174
|
-
if (settled || failure || !message || typeof message !== "object" || !("action" in message) || message.action !== action || !("type" in message)) return;
|
|
3175
|
-
if (message.type === "started" && !started) {
|
|
3176
|
-
started = true;
|
|
3177
|
-
child.send({
|
|
3178
|
-
type: "run",
|
|
3179
|
-
action
|
|
3180
|
-
}, (error) => {
|
|
3181
|
-
if (error) onError(error);
|
|
3182
|
-
});
|
|
3183
|
-
} else if (message.type === "progress" && "event" in message) {
|
|
3184
|
-
const event = readProgress(message.event, secrets);
|
|
3185
|
-
if (event) try {
|
|
3186
|
-
options.onProgress?.(event);
|
|
3187
|
-
} catch (error) {
|
|
3188
|
-
stop("failed", `进度处理失败:${String(error)}`);
|
|
3189
|
-
}
|
|
3190
|
-
} else if (message.type === "completed" && started) {
|
|
3191
|
-
completed = true;
|
|
3192
|
-
result = "result" in message && message.result && typeof message.result === "object" ? message.result : {};
|
|
3193
|
-
}
|
|
3194
|
-
};
|
|
3195
|
-
const onExit = () => {
|
|
3196
|
-
clearTimeout(timer);
|
|
3197
|
-
terminate(false);
|
|
3198
|
-
closeTimer = setTimeout(() => {
|
|
3199
|
-
failure ??= {
|
|
3200
|
-
reason: "failed",
|
|
3201
|
-
detail: "worker 已退出"
|
|
3202
|
-
};
|
|
3203
|
-
failure.detail += ";日志管道未关闭,本地子进程清理未确认";
|
|
3204
|
-
captured.stdout = "";
|
|
3205
|
-
captured.stderr = "";
|
|
3206
|
-
child.stdin?.destroy();
|
|
3207
|
-
child.stdout?.destroy();
|
|
3208
|
-
child.stderr?.destroy();
|
|
3209
|
-
}, 1e3);
|
|
3210
|
-
};
|
|
3211
|
-
const cleanup = () => {
|
|
3212
|
-
clearTimeout(timer);
|
|
3213
|
-
clearTimeout(closeTimer);
|
|
3214
|
-
options.signal?.removeEventListener("abort", cancel);
|
|
3215
|
-
process.off("SIGINT", cancel);
|
|
3216
|
-
process.off("SIGTERM", cancel);
|
|
3217
|
-
process.off("exit", onParentExit);
|
|
3218
|
-
child.off("message", onMessage);
|
|
3219
|
-
child.off("error", onError);
|
|
3220
|
-
child.off("disconnect", onDisconnect);
|
|
3221
|
-
child.off("exit", onExit);
|
|
3222
|
-
child.stdout?.off("data", onStdout);
|
|
3223
|
-
child.stderr?.off("data", onStderr);
|
|
3224
|
-
child.stdin?.off("error", onError);
|
|
3225
|
-
};
|
|
3226
|
-
child.once("close", (code, signal) => {
|
|
3227
|
-
settled = true;
|
|
3228
|
-
cleanup();
|
|
3229
|
-
const output = [redactDiagnostic(captured.stdout, secrets), redactDiagnostic(captured.stderr, secrets)].filter(Boolean).join("\n").trim();
|
|
3230
|
-
if (failure || code !== 0 || !completed) {
|
|
3231
|
-
const detail = failure?.detail ?? `${signal ?? code ?? "unknown"},${completed ? "进程异常结束" : "未收到完成确认"}`;
|
|
3232
|
-
reject(new UploadExecutionError(redactUploadSecrets(`${label}失败(${detail})。${started ? "远端结果未知,请核实平台状态;未自动重试。" : "SDK 尚未开始。"}${output ? `\n${output}` : ""}`, secrets), failure?.reason ?? "failed", started ? "unknown" : "not-started"));
|
|
3233
|
-
return;
|
|
3234
|
-
}
|
|
3235
|
-
resolve({
|
|
3236
|
-
result,
|
|
3237
|
-
output
|
|
3238
|
-
});
|
|
3239
|
-
});
|
|
3240
|
-
child.on("error", onError);
|
|
3241
|
-
child.on("disconnect", onDisconnect);
|
|
3242
|
-
child.on("message", onMessage);
|
|
3243
|
-
child.once("exit", onExit);
|
|
3244
|
-
child.stdout?.setEncoding("utf8").on("data", onStdout);
|
|
3245
|
-
child.stderr?.setEncoding("utf8").on("data", onStderr);
|
|
3246
|
-
child.stdin?.on("error", onError);
|
|
3247
|
-
process.once("exit", onParentExit);
|
|
3248
|
-
if (options.signal) options.signal.addEventListener("abort", cancel, { once: true });
|
|
3249
|
-
else {
|
|
3250
|
-
process.once("SIGINT", cancel);
|
|
3251
|
-
process.once("SIGTERM", cancel);
|
|
3252
|
-
}
|
|
3253
|
-
if (options.timeoutMs !== void 0) timer = setTimeout(stop, options.timeoutMs, "timeout", `本地执行超时(${options.timeoutMs}ms)`);
|
|
3254
|
-
if (options.signal?.aborted) cancel();
|
|
3255
|
-
else try {
|
|
3256
|
-
child.stdin?.end(JSON.stringify({
|
|
3257
|
-
platform,
|
|
3258
|
-
context,
|
|
3259
|
-
action
|
|
3260
|
-
}));
|
|
3261
|
-
} catch (error) {
|
|
3262
|
-
stop("failed", String(error));
|
|
3263
|
-
}
|
|
3264
|
-
});
|
|
3265
|
-
if (output) logger_default.info(output);
|
|
3266
|
-
return action === "preview" ? validatePreviewResult(result, context) : result;
|
|
3267
|
-
}
|
|
3268
|
-
//#endregion
|
|
3269
|
-
//#region src/cli/upload/project.ts
|
|
3270
|
-
/** 确保官方工具读取的目录正是本次构建输出,拒绝误传旧目录或同名配置。 */
|
|
3271
|
-
async function validateUploadProject(options) {
|
|
3272
|
-
const { projectConfigFileName } = getProjectPlatformOptions(options.platform);
|
|
3273
|
-
if (options.sourceConfigPath && path$1.basename(options.sourceConfigPath) !== projectConfigFileName) throw new Error(`上传工具只读取 ${projectConfigFileName},请使用此标准文件名,避免读取其他项目配置。`);
|
|
3274
|
-
const configPath = path$1.join(options.projectPath, projectConfigFileName);
|
|
3275
|
-
const config = JSON.parse(await readFile(configPath, "utf8"));
|
|
3276
|
-
if (!config || typeof config !== "object" || Array.isArray(config)) throw new TypeError(`${projectConfigFileName} 必须是 JSON 对象。`);
|
|
3277
|
-
const rootKey = options.platform === "swan" ? "smartProgramRoot" : "miniprogramRoot";
|
|
3278
|
-
const root = config[rootKey];
|
|
3279
|
-
if (typeof root !== "string" || !root.trim()) throw new Error(`${projectConfigFileName} 缺少代码目录 ${rootKey}。`);
|
|
3280
|
-
const sdkRoot = path$1.resolve(options.projectPath, root);
|
|
3281
|
-
const [actualRoot, outputRoot] = await Promise.all([realpath(sdkRoot), realpath(options.outDir)]);
|
|
3282
|
-
if (actualRoot !== outputRoot) throw new Error(`${projectConfigFileName} 的代码目录与本次构建输出不一致,已阻止上传旧产物。请检查项目配置根目录与 build.outDir。`);
|
|
3283
|
-
await access(path$1.join(outputRoot, "app.json"));
|
|
3284
|
-
}
|
|
3285
|
-
//#endregion
|
|
3286
|
-
//#region src/cli/upload/report.ts
|
|
3287
|
-
var UploadCommandError = class extends Error {
|
|
3288
|
-
report;
|
|
3289
|
-
constructor(report) {
|
|
3290
|
-
super(report.error);
|
|
3291
|
-
this.report = report;
|
|
3292
|
-
this.name = "UploadCommandError";
|
|
3293
|
-
}
|
|
3294
|
-
};
|
|
3295
|
-
function createFailedUploadReport(error) {
|
|
3296
|
-
return {
|
|
3297
|
-
schemaVersion: 1,
|
|
3298
|
-
action: "upload",
|
|
3299
|
-
status: "failed",
|
|
3300
|
-
results: [],
|
|
3301
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3302
|
-
};
|
|
3303
|
-
}
|
|
3304
|
-
function printUploadProgress(platform, event) {
|
|
3305
|
-
const percent = event.percent === void 0 ? "" : ` ${event.percent}%`;
|
|
3306
|
-
process.stderr.write(`[upload:${platform}] ${event.type}${percent}${event.message ? ` ${event.message}` : ""}\n`);
|
|
3307
|
-
}
|
|
3308
|
-
function printUploadSummary(report) {
|
|
3309
|
-
const labels = {
|
|
3310
|
-
"success": "上传成功",
|
|
3311
|
-
"failed": "失败",
|
|
3312
|
-
"not-run": "未执行",
|
|
3313
|
-
"dry-run": "仅构建",
|
|
3314
|
-
"unknown": "远端结果未确认"
|
|
3315
|
-
};
|
|
3316
|
-
for (const item of report.results) process.stderr.write(`[upload:${item.platform ?? "配置平台"}] ${labels[item.status]}${item.requestedVersion ? ` ${item.requestedVersion}` : ""} (${item.stage})\n`);
|
|
3317
|
-
}
|
|
3318
|
-
/** JSON 模式独占 stdout,构建配置及第三方工具的普通日志只进入 stderr。 */
|
|
3319
|
-
async function outputUploadReport(json, run) {
|
|
3320
|
-
const stdoutWrite = process.stdout.write;
|
|
3321
|
-
if (json) process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
3322
|
-
try {
|
|
3323
|
-
let report;
|
|
3324
|
-
try {
|
|
3325
|
-
report = await run();
|
|
3326
|
-
} catch (error) {
|
|
3327
|
-
report = error instanceof UploadCommandError ? error.report : createFailedUploadReport(error);
|
|
3328
|
-
}
|
|
3329
|
-
if (json) {
|
|
3330
|
-
await new Promise((resolve, reject) => {
|
|
3331
|
-
stdoutWrite.call(process.stdout, `${JSON.stringify(report)}\n`, "utf8", (error) => error ? reject(error) : resolve());
|
|
3332
|
-
});
|
|
3333
|
-
if (report.status === "failed") process.exitCode = 1;
|
|
3334
|
-
} else {
|
|
3335
|
-
printUploadSummary(report);
|
|
3336
|
-
if (report.status === "failed") throw new UploadCommandError(report);
|
|
3337
|
-
}
|
|
3338
|
-
} finally {
|
|
3339
|
-
process.stdout.write = stdoutWrite;
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
3342
|
-
//#endregion
|
|
3343
|
-
//#region src/cli/upload/builtProject.ts
|
|
3344
|
-
/** 仅由显式上传或预览入口读取参数,不执行构建或调用平台工具。 */
|
|
3345
|
-
async function createUploadTarget(config, options, action) {
|
|
3346
|
-
if (config.weappLibConfig?.enabled || config.pluginOnly) throw new Error(`${action} 仅支持完整小程序项目,不支持组件库或独立插件构建。`);
|
|
3347
|
-
const platform = resolveUploadPlatforms(config.platform)[0];
|
|
3348
|
-
const uploadConfig = action === "upload" ? config.weappViteConfig.upload : void 0;
|
|
3349
|
-
const version = (options.uv ?? uploadConfig?.version ?? config.packageJson.version)?.trim() ?? "";
|
|
3350
|
-
if (action === "upload" && !version) throw new Error("请通过 --uv、weapp.upload.version 或项目 package.json 的 version 指定上传版本。");
|
|
3351
|
-
const desc = (options.desc ?? uploadConfig?.desc)?.trim() || `${config.packageJson.name ?? platform}${version ? `@${version}` : ""}`;
|
|
3352
|
-
const env = options.dryRun ? {} : await loadUploadEnv(config.cwd, config.mode, config.inlineConfig.root, config.inlineConfig.envDir);
|
|
3353
|
-
const { projectConfigFileName } = getProjectPlatformOptions(config.platform);
|
|
3354
|
-
const generatedProjectConfig = config.multiPlatform.projectConfigs !== void 0;
|
|
3355
|
-
const projectPath = generatedProjectConfig ? config.outDir : config.multiPlatform.enabled ? path$1.dirname(config.outDir) : path$1.dirname(config.projectConfigPath ?? path$1.join(config.cwd, projectConfigFileName));
|
|
3356
|
-
const appid = config.projectConfig.appid ?? config.projectConfig.appId;
|
|
3357
|
-
const target = {
|
|
3358
|
-
platform,
|
|
3359
|
-
sourceConfigPath: config.projectConfigPath,
|
|
3360
|
-
context: {
|
|
3361
|
-
cwd: config.cwd,
|
|
3362
|
-
projectPath,
|
|
3363
|
-
appid: typeof appid === "string" ? appid : void 0,
|
|
3364
|
-
version,
|
|
3365
|
-
desc,
|
|
3366
|
-
qrCodePath: action === "preview" && !options.dryRun ? path$1.join(config.cwd, ".weapp-vite", "preview", `${platform}-${randomUUID()}.png`) : void 0,
|
|
3367
|
-
env: {
|
|
3368
|
-
...process.env,
|
|
3369
|
-
...env
|
|
3370
|
-
}
|
|
3371
|
-
}
|
|
3372
|
-
};
|
|
3373
|
-
(config.inlineConfig.plugins ??= []).push({
|
|
3374
|
-
name: "weapp-vite:upload-output",
|
|
3375
|
-
apply: (viteConfig) => resolveWeappViteHostMeta(viteConfig)?.runtime === "miniprogram",
|
|
3376
|
-
writeBundle(output, bundle) {
|
|
3377
|
-
if (output.dir && Object.hasOwn(bundle, "app.json")) {
|
|
3378
|
-
target.outDir = path$1.resolve(output.dir);
|
|
3379
|
-
if (generatedProjectConfig) target.context.projectPath = target.outDir;
|
|
3380
|
-
}
|
|
3381
|
-
}
|
|
3382
|
-
});
|
|
3383
|
-
return target;
|
|
3384
|
-
}
|
|
3385
|
-
/** 只消费本次构建的产物;产物校验通过前不会准备凭据或调用平台工具。 */
|
|
3386
|
-
async function executeUploadTarget(target, options, action, execution = {}) {
|
|
3387
|
-
const { platform, context } = target;
|
|
3388
|
-
if (!target.outDir) throw new Error("本次构建未写出 app.json,已阻止上传或预览旧产物。");
|
|
3389
|
-
await validateUploadProject({
|
|
3390
|
-
platform,
|
|
3391
|
-
projectPath: context.projectPath,
|
|
3392
|
-
outDir: target.outDir,
|
|
3393
|
-
sourceConfigPath: target.sourceConfigPath
|
|
3394
|
-
});
|
|
3395
|
-
if (options.dryRun) {
|
|
3396
|
-
logger_default.success(`[${action}:${platform}] dry-run:构建完成,未校验凭据或调用平台工具。项目:${path$1.relative(context.cwd, context.projectPath) || "."}`);
|
|
3397
|
-
return;
|
|
3398
|
-
}
|
|
3399
|
-
const upload = await prepareUpload(platform, context, action);
|
|
3400
|
-
execution.onUpload?.();
|
|
3401
|
-
const result = await executeUpload(platform, context, upload.secrets, action, {
|
|
3402
|
-
...execution,
|
|
3403
|
-
onProgress: execution.onProgress ?? ((event) => printUploadProgress(platform, event))
|
|
3404
|
-
});
|
|
3405
|
-
if (action === "preview") {
|
|
3406
|
-
if (!result) throw new Error("预览工具未返回二维码或预览链接。");
|
|
3407
|
-
for (const [label, value] of [
|
|
3408
|
-
["二维码图片", result.qrCodeUrl],
|
|
3409
|
-
["预览链接", result.previewUrl],
|
|
3410
|
-
["二维码文件", result.qrCodeFile && path$1.relative(context.cwd, result.qrCodeFile)]
|
|
3411
|
-
]) if (value) logger_default.info(`[preview:${platform}] ${label}:${redactUploadSecrets(value, upload.secrets)}`);
|
|
3412
|
-
logger_default.success(`[preview:${platform}] 预览已生成(未上传开发版本、未提审、未正式发布)。`);
|
|
3413
|
-
} else logger_default.success(`[upload:${platform}] ${context.version} 上传完成(未提审、未正式发布)。`);
|
|
3414
|
-
return result;
|
|
3415
|
-
}
|
|
3416
|
-
//#endregion
|
|
3417
|
-
//#region src/cli/upload/options.ts
|
|
3418
|
-
function readUploadMetadata(cli) {
|
|
3419
|
-
for (const name of [
|
|
3420
|
-
"uv",
|
|
3421
|
-
"desc",
|
|
3422
|
-
"bump"
|
|
3423
|
-
]) {
|
|
3424
|
-
const value = cli.options[name];
|
|
3425
|
-
if (typeof value === "boolean" || Array.isArray(value) && value.some((item) => typeof item === "boolean")) throw new Error(`--${name} 需要指定字符串参数。`);
|
|
3426
|
-
}
|
|
3427
|
-
const { values } = parseArgs({
|
|
3428
|
-
args: cli.rawArgs.slice(2),
|
|
3429
|
-
allowPositionals: true,
|
|
3430
|
-
strict: false,
|
|
3431
|
-
options: {
|
|
3432
|
-
uv: { type: "string" },
|
|
3433
|
-
desc: { type: "string" },
|
|
3434
|
-
bump: { type: "string" }
|
|
3435
|
-
}
|
|
3436
|
-
});
|
|
3437
|
-
return {
|
|
3438
|
-
uv: typeof values.uv === "string" ? values.uv : void 0,
|
|
3439
|
-
desc: typeof values.desc === "string" ? values.desc : void 0,
|
|
3440
|
-
bump: typeof values.bump === "string" ? values.bump : void 0
|
|
3441
|
-
};
|
|
3442
|
-
}
|
|
3443
|
-
/** 超时只约束单个平台的 SDK 进程,不含构建;未指定时不改变原有等待行为。 */
|
|
3444
|
-
function resolveUploadTimeout(value) {
|
|
3445
|
-
if (value === void 0) return;
|
|
3446
|
-
const seconds = typeof value === "number" ? value : Number(value);
|
|
3447
|
-
const milliseconds = Math.round(seconds * 1e3);
|
|
3448
|
-
if (typeof value !== "string" && typeof value !== "number" || !Number.isFinite(milliseconds) || milliseconds / 1e3 !== seconds || milliseconds <= 0 || milliseconds > 2147483647) throw new Error("--timeout 必须为正数秒,精度不超过毫秒且不能超过 2147483.647 秒。");
|
|
3449
|
-
return milliseconds;
|
|
3450
|
-
}
|
|
3451
|
-
/** 普通构建不读取上传元数据;上传必须是显式的一次性构建操作。 */
|
|
3452
|
-
function resolveBuildUploadOptions(cli, options) {
|
|
3453
|
-
if (options.upload !== true) {
|
|
3454
|
-
if (options.uv !== void 0 || options.desc !== void 0 || options.bump !== void 0 || options.gitDesc !== void 0 || options.dryRun !== void 0) throw new Error("--uv、--desc、--bump、--git-desc 和 --dry-run 仅能与 --upload 一起使用。");
|
|
3455
|
-
return;
|
|
3456
|
-
}
|
|
3457
|
-
if (options.watch) throw new Error("--upload 不能与 --watch 一起使用,请执行一次性构建。");
|
|
3458
|
-
return {
|
|
3459
|
-
...options,
|
|
3460
|
-
...readUploadMetadata(cli)
|
|
3461
|
-
};
|
|
3462
|
-
}
|
|
3463
|
-
//#endregion
|
|
3464
|
-
//#region src/cli/commands/build.ts
|
|
3465
|
-
function emitDashboardEvents$1(handle, events) {
|
|
3466
|
-
handle?.emitRuntimeEvents(events);
|
|
3467
|
-
}
|
|
3468
|
-
function normalizeBuildSourcemap(value) {
|
|
3469
|
-
if (value === "true") return true;
|
|
3470
|
-
if (value === "false") return false;
|
|
3471
|
-
return value;
|
|
3472
|
-
}
|
|
3473
|
-
function normalizeBuildMinify(value) {
|
|
3474
|
-
if (value === "true") return true;
|
|
3475
|
-
if (value === "false") return false;
|
|
3476
|
-
return value;
|
|
3477
|
-
}
|
|
3478
|
-
function hasCliOption(options, key) {
|
|
3479
|
-
return Object.prototype.hasOwnProperty.call(options, key);
|
|
3480
|
-
}
|
|
3481
|
-
function createBuildInlineConfig(options) {
|
|
3482
|
-
const build = {};
|
|
3483
|
-
if (typeof options.outDir === "string") build.outDir = options.outDir;
|
|
3484
|
-
if (hasCliOption(options, "sourcemap")) build.sourcemap = normalizeBuildSourcemap(options.sourcemap);
|
|
3485
|
-
if (hasCliOption(options, "minify")) build.minify = normalizeBuildMinify(options.minify);
|
|
3486
|
-
if (typeof options.emptyOutDir === "boolean") build.emptyOutDir = options.emptyOutDir;
|
|
3487
|
-
return Object.keys(build).length ? { build } : void 0;
|
|
3488
|
-
}
|
|
3489
|
-
function shouldScheduleCompletedProductionBuildExit(options, analyzeHandle) {
|
|
3490
|
-
return process.env.VITEST !== "true" && process.env.NODE_ENV !== "test" && process.env.WEAPP_VITE_DISABLE_COMPLETED_BUILD_EXIT !== "1" && options.watch !== true && options.open !== true && analyzeHandle === void 0;
|
|
3491
|
-
}
|
|
3492
|
-
function scheduleCompletedProductionBuildExit(options, analyzeHandle) {
|
|
3493
|
-
if (!shouldScheduleCompletedProductionBuildExit(options, analyzeHandle)) return;
|
|
3494
|
-
setTimeout(() => {
|
|
3495
|
-
if (process.exitCode === void 0 || process.exitCode === 0) process.exit(0);
|
|
3496
|
-
}, 0).unref?.();
|
|
3497
|
-
}
|
|
3498
|
-
function registerBuildCommand(cli) {
|
|
3499
|
-
cli.command("build [root]", "build for production").option("--target <target>", `[string] transpile target (default: 'modules')`).option("--outDir <dir>", `[string] output directory (default: dist)`).option("-p, --platform <platform>", `[string] target platform (weapp | web | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--sourcemap [output]", `[boolean | "inline" | "hidden"] output source maps for build (default: false)`).option("--minify [minifier]", "[boolean | \"terser\" | \"esbuild\"] enable/disable minification, or specify minifier to use (default: esbuild)").option("--emptyOutDir", `[boolean] force empty outDir when it's outside of root`).option("-w, --watch", `[boolean] rebuilds when modules have changed on disk`).option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).option("--ide-open-strategy <strategy>", "[string] IDE open strategy (cli | automator)", { default: "cli" }).option("--no-open-recovery", "[boolean] disable automatic target-project open retry (preserves existing IDE windows)").option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--ui-host <host>", `[string] 启动调试 UI 并选择宿主(standalone | hub)`).option("--analyze", `[boolean] 输出分包分析仪表盘`, { default: false }).option("--scope <scope>", `[string] 局部构建范围,例如 main,packages/order`).option("--upload", "[boolean] upload the mini program after this build succeeds").option("--uv <version>", "[string] upload version override (requires --upload)").option("--desc <text>", "[string] upload description override (requires --upload)").option("--bump <release>", "[string] increment local package version (patch | minor | major; requires --upload)").option("--git-desc", "[boolean] use the latest Git commit subject (requires --upload)").option("--dry-run", "[boolean] validate upload output without credentials or SDK calls (requires --upload)").action(async (root, options) => {
|
|
3500
|
-
let analyzeHandle;
|
|
3501
|
-
let session;
|
|
3502
|
-
let ctx;
|
|
3503
|
-
let targets;
|
|
3504
|
-
let uploadMetadataRollback;
|
|
3505
|
-
let buildCompleted = false;
|
|
3506
|
-
let buildFailed = false;
|
|
3507
|
-
try {
|
|
3508
|
-
options = { ...options };
|
|
3509
|
-
filterDuplicateOptions(options);
|
|
3510
|
-
options.uiHost = parseDashboardUiHost(options.uiHost);
|
|
3511
|
-
let uploadOptions = resolveBuildUploadOptions(cli, options);
|
|
3512
|
-
setCommandNodeEnv("production");
|
|
3513
|
-
const cwd = root ?? process.cwd();
|
|
3514
|
-
const configFile = resolveConfigFile(options);
|
|
3515
|
-
targets = resolveRuntimeTargets(options);
|
|
3516
|
-
if (uploadOptions) validateAutoUploadMetadata(uploadOptions, "upload");
|
|
3517
|
-
if (uploadOptions && !getBackendForCapability(targets, "miniprogram", "build")) throw new Error("--upload 仅支持包含小程序目标的构建,不能用于纯 Web 构建。");
|
|
3518
|
-
const inlineConfig = createInlineConfig(targets, {
|
|
3519
|
-
scope: options.scope,
|
|
3520
|
-
inlineConfig: createBuildInlineConfig(options)
|
|
3521
|
-
});
|
|
3522
|
-
if (uploadOptions) {
|
|
3523
|
-
const prepared = await prepareAutoUploadMetadataWithRollback(cwd, uploadOptions, "upload");
|
|
3524
|
-
uploadOptions = prepared.options;
|
|
3525
|
-
uploadMetadataRollback = prepared.rollback;
|
|
3526
|
-
}
|
|
3527
|
-
session = new CompilerSession();
|
|
3528
|
-
ctx = await session.initialize({
|
|
3529
|
-
cwd,
|
|
3530
|
-
mode: options.mode ?? "production",
|
|
3531
|
-
configFile,
|
|
3532
|
-
inlineConfig,
|
|
3533
|
-
cliPlatform: targets.rawPlatform,
|
|
3534
|
-
projectConfigPath: options.projectConfig,
|
|
3535
|
-
emitDefaultAutoImportOutputs: false,
|
|
3536
|
-
preloadAppEntry: false
|
|
3537
|
-
});
|
|
3538
|
-
targets = resolveConfiguredRuntimeTargets(targets, ctx.configService.options?.sourceConfig?.weapp?.platform);
|
|
3539
|
-
if (uploadOptions && !getBackendForCapability(targets, "miniprogram", "build")) {
|
|
3540
|
-
await uploadMetadataRollback?.();
|
|
3541
|
-
uploadMetadataRollback = void 0;
|
|
3542
|
-
throw new Error("--upload 仅支持包含小程序目标的构建,不能用于纯 Web 构建。");
|
|
3543
|
-
}
|
|
3544
|
-
uploadMetadataRollback = void 0;
|
|
3545
|
-
for (const backend of targets.select("build")) session.onClose(() => backend.driver.close(ctx));
|
|
3546
|
-
const { configService } = ctx;
|
|
3547
|
-
const uploadTarget = uploadOptions ? await createUploadTarget(configService, uploadOptions, "upload") : void 0;
|
|
3548
|
-
const miniBackend = getBackendForCapability(targets, "miniprogram", "build");
|
|
3549
|
-
const webBackend = getBackendForCapability(targets, "web", "build");
|
|
3550
|
-
logRuntimeTarget(targets, { resolvedConfigPlatform: configService.platform });
|
|
3551
|
-
const enableAnalyze = Boolean(isUiEnabled(options) && miniBackend);
|
|
3552
|
-
for (const backend of targets.select("build")) {
|
|
3553
|
-
if (backend.descriptor.id === "miniprogram") {
|
|
3554
|
-
const miniBuildStartedAt = Date.now();
|
|
3555
|
-
const output = await session.run(() => backend.driver.build(ctx, options));
|
|
3556
|
-
const miniBuildDurationMs = Date.now() - miniBuildStartedAt;
|
|
3557
|
-
logger_default.success(`小程序构建完成,耗时:${colors.green(formatDuration(miniBuildDurationMs))}`);
|
|
3558
|
-
if (!Array.isArray(output) && "output" in output) logBuildPackageSizeReport({
|
|
3559
|
-
output,
|
|
3560
|
-
subPackageMap: ctx.scanService?.subPackageMap,
|
|
3561
|
-
warningBytes: configService.weappViteConfig.packageSizeWarningBytes
|
|
3562
|
-
});
|
|
3563
|
-
if (enableAnalyze) {
|
|
3564
|
-
const analyzeStartedAt = Date.now();
|
|
3565
|
-
const previousAnalyzeResult = await readLatestAnalyzeHistorySnapshot(configService);
|
|
3566
|
-
const artifactSnapshot = createDashboardArtifactSnapshot();
|
|
3567
|
-
const analyzeResult = await analyzeSubpackages(ctx, { onArtifact: artifactSnapshot.capture });
|
|
3568
|
-
await writeAnalyzeHistorySnapshot(analyzeResult, configService);
|
|
3569
|
-
const analyzeDurationMs = Date.now() - analyzeStartedAt;
|
|
3570
|
-
analyzeHandle = await startAnalyzeDashboard(analyzeResult, {
|
|
3571
|
-
watch: true,
|
|
3572
|
-
uiHost: options.uiHost,
|
|
3573
|
-
artifacts: artifactSnapshot.files,
|
|
3574
|
-
cwd: configService.cwd,
|
|
3575
|
-
packageManagerAgent: configService.packageManager.agent,
|
|
3576
|
-
pluginRoot: configService.absolutePluginRoot,
|
|
3577
|
-
srcRoot: configService.absoluteSrcRoot,
|
|
3578
|
-
previousResult: previousAnalyzeResult,
|
|
3579
|
-
initialEvents: [{
|
|
3580
|
-
kind: "build",
|
|
3581
|
-
level: "success",
|
|
3582
|
-
title: "mini build completed",
|
|
3583
|
-
detail: `生产构建已完成,当前 analyze 结果包含 ${analyzeResult.packages.length} 个包。`,
|
|
3584
|
-
durationMs: miniBuildDurationMs,
|
|
3585
|
-
tags: ["build", "mini"]
|
|
3586
|
-
}, {
|
|
3587
|
-
kind: "build",
|
|
3588
|
-
level: "success",
|
|
3589
|
-
title: "analyze completed",
|
|
3590
|
-
detail: `分析已完成,当前包含 ${analyzeResult.packages.length} 个包与 ${analyzeResult.modules.length} 个模块。`,
|
|
3591
|
-
durationMs: analyzeDurationMs,
|
|
3592
|
-
tags: ["build", "analyze"]
|
|
3593
|
-
}]
|
|
3594
|
-
}) ?? void 0;
|
|
3595
|
-
}
|
|
3596
|
-
continue;
|
|
3597
|
-
}
|
|
3598
|
-
const webConfig = configService.weappWebConfig;
|
|
3599
|
-
if (backend.descriptor.id !== "web" || !webConfig?.enabled) continue;
|
|
3600
|
-
const webBuildStartedAt = Date.now();
|
|
3601
|
-
try {
|
|
3602
|
-
await session.run(() => backend.driver.build(ctx, options));
|
|
3603
|
-
const webBuildDurationMs = Date.now() - webBuildStartedAt;
|
|
3604
|
-
logger_default.success(`Web 构建完成,输出目录:${colors.green(configService.relativeCwd(webConfig.outDir))},耗时:${colors.green(formatDuration(webBuildDurationMs))}`);
|
|
3605
|
-
emitDashboardEvents$1(analyzeHandle, [{
|
|
3606
|
-
kind: "build",
|
|
3607
|
-
level: "success",
|
|
3608
|
-
title: "web build completed",
|
|
3609
|
-
detail: `Web 构建已完成,输出目录 ${configService.relativeCwd(webConfig.outDir)}。`,
|
|
3610
|
-
durationMs: webBuildDurationMs,
|
|
3611
|
-
tags: ["build", "web"]
|
|
3612
|
-
}]);
|
|
3613
|
-
} catch (error) {
|
|
3614
|
-
emitDashboardEvents$1(analyzeHandle, [{
|
|
3615
|
-
kind: "diagnostic",
|
|
3616
|
-
level: "error",
|
|
3617
|
-
title: "web build failed",
|
|
3618
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
3619
|
-
durationMs: Date.now() - webBuildStartedAt,
|
|
3620
|
-
tags: ["build", "web"]
|
|
3621
|
-
}]);
|
|
3622
|
-
logger_default.error(error);
|
|
3623
|
-
throw error;
|
|
3624
|
-
}
|
|
3625
|
-
}
|
|
3626
|
-
if (uploadTarget && uploadOptions) await executeUploadTarget(uploadTarget, uploadOptions, "upload");
|
|
3627
|
-
if (miniBackend) logBuildAppFinish(configService, void 0, { skipWeb: !webBackend });
|
|
3628
|
-
if (options.open && getBackendForCapability(targets, "miniprogram", "ide")) {
|
|
3629
|
-
emitDashboardEvents$1(analyzeHandle, [{
|
|
3630
|
-
kind: "command",
|
|
3631
|
-
level: "info",
|
|
3632
|
-
title: "opening ide",
|
|
3633
|
-
detail: "构建完成后准备打开 IDE 项目。",
|
|
3634
|
-
tags: ["ide", "open"]
|
|
3635
|
-
}]);
|
|
3636
|
-
const generatedProjectConfig = configService.multiPlatform.projectConfigs !== void 0;
|
|
3637
|
-
await openIde(configService.platform, resolveIdeProjectPath(generatedProjectConfig ? configService.outDir : configService.mpDistRoot, generatedProjectConfig), {
|
|
3638
|
-
openRecovery: options.openRecovery,
|
|
3639
|
-
trustProject: options.trustProject,
|
|
3640
|
-
openStrategy: options.ideOpenStrategy
|
|
3641
|
-
});
|
|
3642
|
-
}
|
|
3643
|
-
if (analyzeHandle) await analyzeHandle.waitForExit();
|
|
3644
|
-
buildCompleted = true;
|
|
3645
|
-
} catch (error) {
|
|
3646
|
-
buildFailed = true;
|
|
3647
|
-
throw error;
|
|
3648
|
-
} finally {
|
|
3649
|
-
if (buildFailed) await session?.close().catch((error) => logger_default.error(error));
|
|
3650
|
-
else await session?.close();
|
|
3651
|
-
if (buildCompleted) scheduleCompletedProductionBuildExit(options, analyzeHandle);
|
|
3652
|
-
}
|
|
113
|
+
await runAlipayCommand(action, root, options);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/cli/commands/analyze/index.ts
|
|
118
|
+
function registerAnalyzeCommand(cli) {
|
|
119
|
+
cli.command("analyze [root]", "analyze 两端包体与源码映射").option("--ui-host <host>", `[string] 启动调试 UI 并选择宿主(standalone | hub)`).option("--hmr-profile [file]", `[string | boolean] 分析 HMR JSONL profile,省略值时优先读取配置,否则回退到默认路径`).option("--json", `[boolean] 输出 JSON 结果`).option("--markdown", `[boolean] 输出 Markdown 报告`).option("--report <type>", `[string] 输出指定报告类型(pr)`).option("--budget-check", `[boolean] 检查 analyze 预算,超过预算时返回非 0 退出码`).option("--glass-easel-check", `[boolean] 检查 glass-easel 配置、模板和 SelectorQuery 迁移兼容性`).option("--preload", `[boolean] 分析静态页面跳转、实际分包体积和 preloadRule 额度`).option("--output <file>", `[string] 将分析结果写入指定文件(JSON 或 Markdown)`).option("-p, --platform <platform>", `[string] target platform (weapp | web)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).action(async (root, options) => {
|
|
120
|
+
const { runAnalyzeCommand } = await import("./action-BOk3vGI7.mjs");
|
|
121
|
+
return runAnalyzeCommand(root, options);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/cli/commands/build/index.ts
|
|
126
|
+
function registerBuildCommand(cli) {
|
|
127
|
+
cli.command("build [root]", "build for production").option("--target <target>", `[string] transpile target (default: 'modules')`).option("--outDir <dir>", `[string] output directory (default: dist)`).option("-p, --platform <platform>", `[string] target platform (weapp | web | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--sourcemap [output]", `[boolean | "inline" | "hidden"] output source maps for build (default: false)`).option("--minify [minifier]", "[boolean | \"terser\" | \"esbuild\"] enable/disable minification, or specify minifier to use (default: esbuild)").option("--emptyOutDir", `[boolean] force empty outDir when it's outside of root`).option("-w, --watch", `[boolean] rebuilds when modules have changed on disk`).option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).option("--ide-open-strategy <strategy>", "[string] IDE open strategy (cli | automator)", { default: "cli" }).option("--no-open-recovery", "[boolean] disable automatic target-project open retry (preserves existing IDE windows)").option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--ui-host <host>", `[string] 启动调试 UI 并选择宿主(standalone | hub)`).option("--analyze", `[boolean] 输出分包分析仪表盘`, { default: false }).option("--scope <scope>", `[string] 局部构建范围,例如 main,packages/order`).option("--upload", "[boolean] upload the mini program after this build succeeds").option("--uv <version>", "[string] upload version override (requires --upload)").option("--desc <text>", "[string] upload description override (requires --upload)").option("--bump <release>", "[string] increment local package version (patch | minor | major; requires --upload)").option("--git-desc", "[boolean] use the latest Git commit subject (requires --upload)").option("--dry-run", "[boolean] validate upload output without credentials or SDK calls (requires --upload)").action(async (root, options) => {
|
|
128
|
+
const { runBuildCommand } = await import("./action-zEDY7oh_.mjs");
|
|
129
|
+
return runBuildCommand(cli, root, options);
|
|
3653
130
|
});
|
|
3654
131
|
}
|
|
3655
132
|
//#endregion
|
|
@@ -3693,90 +170,6 @@ function registerDoctorCommand(cli) {
|
|
|
3693
170
|
});
|
|
3694
171
|
}
|
|
3695
172
|
//#endregion
|
|
3696
|
-
//#region src/schematics.ts
|
|
3697
|
-
function composePath(outDir, filename) {
|
|
3698
|
-
return `${outDir}${outDir ? "/" : ""}${filename}`;
|
|
3699
|
-
}
|
|
3700
|
-
const defaultExtensions = {
|
|
3701
|
-
js: "js",
|
|
3702
|
-
json: "json",
|
|
3703
|
-
wxml: "wxml",
|
|
3704
|
-
wxss: "wxss"
|
|
3705
|
-
};
|
|
3706
|
-
function resolveExtension(extension) {
|
|
3707
|
-
return extension ? extension.startsWith(".") ? extension : `.${extension}` : "";
|
|
3708
|
-
}
|
|
3709
|
-
async function readTemplateFile(templatePath, context) {
|
|
3710
|
-
const absolutePath = path.isAbsolute(templatePath) ? templatePath : path.resolve(context.cwd, templatePath);
|
|
3711
|
-
return fs$1.readFile(absolutePath, "utf8");
|
|
3712
|
-
}
|
|
3713
|
-
async function loadTemplate(template, context) {
|
|
3714
|
-
if (template === void 0) return;
|
|
3715
|
-
if (typeof template === "function") {
|
|
3716
|
-
const result = await template(context);
|
|
3717
|
-
return result == null ? void 0 : String(result);
|
|
3718
|
-
}
|
|
3719
|
-
if (typeof template === "string") return readTemplateFile(template, context);
|
|
3720
|
-
if ("content" in template && typeof template.content === "string") return template.content;
|
|
3721
|
-
if ("path" in template && typeof template.path === "string") return readTemplateFile(template.path, context);
|
|
3722
|
-
}
|
|
3723
|
-
function resolveTemplate(templates, type, fileType) {
|
|
3724
|
-
const scoped = templates?.[type]?.[fileType];
|
|
3725
|
-
if (scoped !== void 0) return scoped;
|
|
3726
|
-
return templates?.shared?.[fileType];
|
|
3727
|
-
}
|
|
3728
|
-
async function generate(options) {
|
|
3729
|
-
let { fileName, outDir, extensions, type, cwd, templates } = defu(options, {
|
|
3730
|
-
type: "component",
|
|
3731
|
-
extensions: { ...defaultExtensions },
|
|
3732
|
-
cwd: process.cwd(),
|
|
3733
|
-
templates: void 0
|
|
3734
|
-
});
|
|
3735
|
-
if (fileName === void 0) fileName = path.basename(outDir);
|
|
3736
|
-
const basepath = path.resolve(cwd, outDir);
|
|
3737
|
-
const targetFileTypes = type === "app" ? [
|
|
3738
|
-
"js",
|
|
3739
|
-
"wxss",
|
|
3740
|
-
"json"
|
|
3741
|
-
] : [
|
|
3742
|
-
"js",
|
|
3743
|
-
"wxss",
|
|
3744
|
-
"json",
|
|
3745
|
-
"wxml"
|
|
3746
|
-
];
|
|
3747
|
-
const files = [];
|
|
3748
|
-
for (const fileType of targetFileTypes) {
|
|
3749
|
-
const configuredExt = extensions[fileType] ?? defaultExtensions[fileType];
|
|
3750
|
-
let resolvedExt = configuredExt;
|
|
3751
|
-
let defaultCode;
|
|
3752
|
-
if (fileType === "js") defaultCode = generateJs(type);
|
|
3753
|
-
else if (fileType === "wxss") defaultCode = generateWxss();
|
|
3754
|
-
else if (fileType === "wxml") defaultCode = generateWxml(path.join(outDir, fileName));
|
|
3755
|
-
else if (fileType === "json") {
|
|
3756
|
-
defaultCode = generateJson(type, configuredExt);
|
|
3757
|
-
if (configuredExt === "js" || configuredExt === "ts") resolvedExt = `json.${configuredExt}`;
|
|
3758
|
-
}
|
|
3759
|
-
const context = {
|
|
3760
|
-
type,
|
|
3761
|
-
fileType,
|
|
3762
|
-
fileName,
|
|
3763
|
-
outDir,
|
|
3764
|
-
extension: resolvedExt,
|
|
3765
|
-
cwd,
|
|
3766
|
-
defaultCode
|
|
3767
|
-
};
|
|
3768
|
-
const finalCode = await loadTemplate(resolveTemplate(templates, type, fileType), context) ?? defaultCode;
|
|
3769
|
-
if (finalCode !== void 0) files.push({
|
|
3770
|
-
fileName: `${fileName}${resolveExtension(resolvedExt)}`,
|
|
3771
|
-
code: finalCode
|
|
3772
|
-
});
|
|
3773
|
-
}
|
|
3774
|
-
for (const { code, fileName } of files) if (code !== void 0) {
|
|
3775
|
-
await fs$1.outputFile(path.resolve(basepath, fileName), code, "utf8");
|
|
3776
|
-
logger_default.success(`${composePath(outDir, fileName)} 创建成功!`);
|
|
3777
|
-
}
|
|
3778
|
-
}
|
|
3779
|
-
//#endregion
|
|
3780
173
|
//#region src/cli/commands/generate.ts
|
|
3781
174
|
function registerGenerateCommand(cli) {
|
|
3782
175
|
cli.command("g [filepath]", "generate component").alias("generate").option("-a, --app", "type app").option("-p, --page", "type app").option("-n, --name <name>", "filename").action(async (filepath, options) => {
|
|
@@ -3796,6 +189,7 @@ function registerGenerateCommand(cli) {
|
|
|
3796
189
|
if (options.page) type = "page";
|
|
3797
190
|
const generateOptions = config?.config.weapp?.generate;
|
|
3798
191
|
fileName = generateOptions?.filenames?.[type] ?? fileName;
|
|
192
|
+
const { generate } = await import("./schematics-kO26eQRT.mjs");
|
|
3799
193
|
await generate({
|
|
3800
194
|
outDir: path.join(generateOptions?.dirs?.[type] ?? "", filepath),
|
|
3801
195
|
type,
|
|
@@ -3806,751 +200,11 @@ function registerGenerateCommand(cli) {
|
|
|
3806
200
|
});
|
|
3807
201
|
}
|
|
3808
202
|
//#endregion
|
|
3809
|
-
//#region src/cli/
|
|
3810
|
-
|
|
3811
|
-
"log",
|
|
3812
|
-
"info",
|
|
3813
|
-
"warn",
|
|
3814
|
-
"error"
|
|
3815
|
-
];
|
|
3816
|
-
let activeForwardConsoleSession;
|
|
3817
|
-
let activeForwardConsoleBridgeOptions;
|
|
3818
|
-
let activeForwardConsoleStart;
|
|
3819
|
-
let activeForwardConsoleClose;
|
|
3820
|
-
let forwardConsoleLifecycle = 0;
|
|
3821
|
-
const FORWARD_CONSOLE_RETRY_DELAY_MS = 1e3;
|
|
3822
|
-
const FORWARD_CONSOLE_RETRY_TIMES = 5;
|
|
3823
|
-
const FORWARD_CONSOLE_RETRY_WINDOW_MS = 1e4;
|
|
3824
|
-
const FORWARD_CONSOLE_RECOVERABLE_START_TIMEOUT_MS = 6e4;
|
|
3825
|
-
const FORWARD_CONSOLE_START_TIMEOUT_MS = 12e4;
|
|
3826
|
-
async function detectAgent() {
|
|
3827
|
-
try {
|
|
3828
|
-
const result = await determineAgent();
|
|
3829
|
-
return {
|
|
3830
|
-
isAgent: result.isAgent,
|
|
3831
|
-
agentName: result.isAgent ? result.agent.name : void 0
|
|
3832
|
-
};
|
|
3833
|
-
} catch {
|
|
3834
|
-
return {
|
|
3835
|
-
isAgent: false,
|
|
3836
|
-
agentName: void 0
|
|
3837
|
-
};
|
|
3838
|
-
}
|
|
3839
|
-
}
|
|
3840
|
-
function formatForwardConsolePrefix(level, color) {
|
|
3841
|
-
const label = `[mini:${level.padEnd(5)}]`;
|
|
3842
|
-
if (!color) return label;
|
|
3843
|
-
if (level === "error") return colors.bold(colors.red(label));
|
|
3844
|
-
if (level === "warn") return colors.bold(colors.yellow(label));
|
|
3845
|
-
if (level === "info") return colors.bold(colors.cyan(label));
|
|
3846
|
-
if (level === "debug") return colors.dim(label);
|
|
3847
|
-
return colors.bold(colors.green(label));
|
|
3848
|
-
}
|
|
3849
|
-
function formatForwardConsoleMessage(level, message, color) {
|
|
3850
|
-
if (!color) return message;
|
|
3851
|
-
if (level === "error") return colors.red(message);
|
|
3852
|
-
if (level === "warn") return colors.yellow(message);
|
|
3853
|
-
if (level === "debug") return colors.dim(message);
|
|
3854
|
-
return message;
|
|
3855
|
-
}
|
|
3856
|
-
function writeForwardConsoleLine(line) {
|
|
3857
|
-
process.stdout.write(`${line}\n`);
|
|
3858
|
-
}
|
|
3859
|
-
function isDevtoolsPortNotReadyError(error) {
|
|
3860
|
-
return error instanceof Error && (error.message === "DEVTOOLS_HTTP_PORT_ERROR" || error.message === "DEVTOOLS_WS_CONNECT_ERROR" || error.message === "DEVTOOLS_EXTENSION_CONTEXT_INVALIDATED");
|
|
3861
|
-
}
|
|
3862
|
-
function isDevtoolsProtocolTimeoutError(error) {
|
|
3863
|
-
return error instanceof Error && (error.message === "DEVTOOLS_PROTOCOL_TIMEOUT" || /DevTools did not respond to protocol method \S+ within \d+ms/i.test(error.message));
|
|
3864
|
-
}
|
|
3865
|
-
function isRecoverableAutomatorStartError(error) {
|
|
3866
|
-
return isDevtoolsPortNotReadyError(error) || isDevtoolsProtocolTimeoutError(error) || isRetryableAutomatorLaunchError(error);
|
|
3867
|
-
}
|
|
3868
|
-
function sleep(ms) {
|
|
3869
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3870
|
-
}
|
|
3871
|
-
async function withForwardConsoleRetry(runner) {
|
|
3872
|
-
let lastError;
|
|
3873
|
-
const startedAt = Date.now();
|
|
3874
|
-
for (let attempt = 0; attempt <= FORWARD_CONSOLE_RETRY_TIMES; attempt++) try {
|
|
3875
|
-
return await runner();
|
|
3876
|
-
} catch (error) {
|
|
3877
|
-
lastError = error;
|
|
3878
|
-
if (!isDevtoolsPortNotReadyError(error) || attempt === FORWARD_CONSOLE_RETRY_TIMES || Date.now() - startedAt >= FORWARD_CONSOLE_RETRY_WINDOW_MS) break;
|
|
3879
|
-
await sleep(FORWARD_CONSOLE_RETRY_DELAY_MS);
|
|
3880
|
-
}
|
|
3881
|
-
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
3882
|
-
}
|
|
3883
|
-
/**
|
|
3884
|
-
* @description 解析 forwardConsole 配置,并在 auto 模式下检测 AI 终端。
|
|
3885
|
-
*/
|
|
3886
|
-
async function resolveForwardConsoleOptions(config) {
|
|
3887
|
-
const rawConfig = config?.forwardConsole;
|
|
3888
|
-
const defaults = {
|
|
3889
|
-
logLevels: DEFAULT_FORWARD_CONSOLE_LEVELS,
|
|
3890
|
-
unhandledErrors: true
|
|
3891
|
-
};
|
|
3892
|
-
if (rawConfig === false) return {
|
|
3893
|
-
enabled: false,
|
|
3894
|
-
...defaults
|
|
3895
|
-
};
|
|
3896
|
-
if (rawConfig === true) return {
|
|
3897
|
-
enabled: true,
|
|
3898
|
-
...defaults
|
|
3899
|
-
};
|
|
3900
|
-
const normalizedConfig = rawConfig ?? {};
|
|
3901
|
-
const enabledMode = normalizedConfig.enabled ?? "auto";
|
|
3902
|
-
const logLevels = normalizedConfig.logLevels?.length ? normalizedConfig.logLevels : defaults.logLevels;
|
|
3903
|
-
const unhandledErrors = normalizedConfig.unhandledErrors ?? defaults.unhandledErrors;
|
|
3904
|
-
if (enabledMode === true) return {
|
|
3905
|
-
enabled: true,
|
|
3906
|
-
logLevels,
|
|
3907
|
-
unhandledErrors
|
|
3908
|
-
};
|
|
3909
|
-
if (enabledMode === false) return {
|
|
3910
|
-
enabled: false,
|
|
3911
|
-
logLevels,
|
|
3912
|
-
unhandledErrors
|
|
3913
|
-
};
|
|
3914
|
-
const agentResult = await detectAgent();
|
|
3915
|
-
return {
|
|
3916
|
-
enabled: agentResult.isAgent,
|
|
3917
|
-
agentName: agentResult.agentName,
|
|
3918
|
-
logLevels,
|
|
3919
|
-
unhandledErrors
|
|
3920
|
-
};
|
|
3921
|
-
}
|
|
3922
|
-
/**
|
|
3923
|
-
* @description 统一启动 DevTools 日志桥,并在 IDE 刚启动时做短暂重试。
|
|
3924
|
-
*/
|
|
3925
|
-
async function startForwardConsoleBridge(options) {
|
|
3926
|
-
return await withForwardConsoleRetry(async () => {
|
|
3927
|
-
const color = options.color ?? true;
|
|
3928
|
-
return await startForwardConsole({
|
|
3929
|
-
projectPath: options.projectPath,
|
|
3930
|
-
logLevels: options.logLevels,
|
|
3931
|
-
openedOnly: options.openedOnly,
|
|
3932
|
-
preferOpenedSession: options.preferOpenedSession,
|
|
3933
|
-
preserveProjectRoot: options.preserveProjectRoot,
|
|
3934
|
-
port: options.port,
|
|
3935
|
-
timeout: options.timeout,
|
|
3936
|
-
unhandledErrors: options.unhandledErrors,
|
|
3937
|
-
onReady: () => {
|
|
3938
|
-
const suffix = options.agentName ? `(AI 终端:${options.agentName})` : "";
|
|
3939
|
-
logger_default.info(`${options.onReadyMessage}${suffix}`);
|
|
3940
|
-
},
|
|
3941
|
-
onLog: (event) => {
|
|
3942
|
-
writeForwardConsoleLine(`${formatForwardConsolePrefix(event.level, color)} ${formatForwardConsoleMessage(event.level, event.message, color)}`);
|
|
3943
|
-
}
|
|
3944
|
-
});
|
|
3945
|
-
});
|
|
3946
|
-
}
|
|
3947
|
-
/**
|
|
3948
|
-
* @description 暂停当前 DevTools 日志桥,并返回恢复函数。
|
|
3949
|
-
*/
|
|
3950
|
-
async function pauseActiveForwardConsole() {
|
|
3951
|
-
const lifecycle = forwardConsoleLifecycle;
|
|
3952
|
-
await activeForwardConsoleStart;
|
|
3953
|
-
if (lifecycle !== forwardConsoleLifecycle) return;
|
|
3954
|
-
const session = activeForwardConsoleSession;
|
|
3955
|
-
const bridgeOptions = activeForwardConsoleBridgeOptions;
|
|
3956
|
-
if (!session || !bridgeOptions) return;
|
|
3957
|
-
activeForwardConsoleSession = void 0;
|
|
3958
|
-
activeForwardConsoleBridgeOptions = void 0;
|
|
3959
|
-
await session.close();
|
|
3960
|
-
return async () => {
|
|
3961
|
-
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
3962
|
-
if (activeForwardConsoleStart) return await activeForwardConsoleStart;
|
|
3963
|
-
if (activeForwardConsoleSession) return true;
|
|
3964
|
-
const startTask = (async () => {
|
|
3965
|
-
let resumedSession;
|
|
3966
|
-
try {
|
|
3967
|
-
resumedSession = await startForwardConsoleBridge(bridgeOptions);
|
|
3968
|
-
} catch (error) {
|
|
3969
|
-
if (lifecycle === forwardConsoleLifecycle) {
|
|
3970
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3971
|
-
logger_default.warn(`[forwardConsole] 恢复失败:${message}`);
|
|
3972
|
-
}
|
|
3973
|
-
return false;
|
|
3974
|
-
}
|
|
3975
|
-
if (lifecycle !== forwardConsoleLifecycle) {
|
|
3976
|
-
await resumedSession.close();
|
|
3977
|
-
return false;
|
|
3978
|
-
}
|
|
3979
|
-
activeForwardConsoleSession = resumedSession;
|
|
3980
|
-
activeForwardConsoleBridgeOptions = bridgeOptions;
|
|
3981
|
-
return true;
|
|
3982
|
-
})();
|
|
3983
|
-
activeForwardConsoleStart = startTask;
|
|
3984
|
-
try {
|
|
3985
|
-
return await startTask;
|
|
3986
|
-
} finally {
|
|
3987
|
-
if (activeForwardConsoleStart === startTask) activeForwardConsoleStart = void 0;
|
|
3988
|
-
}
|
|
3989
|
-
};
|
|
3990
|
-
}
|
|
3991
|
-
/**
|
|
3992
|
-
* @description 关闭当前 DevTools 日志桥并释放共享 automator 会话。
|
|
3993
|
-
*/
|
|
3994
|
-
function closeActiveForwardConsole() {
|
|
3995
|
-
if (activeForwardConsoleClose) return activeForwardConsoleClose;
|
|
3996
|
-
forwardConsoleLifecycle += 1;
|
|
3997
|
-
const session = activeForwardConsoleSession;
|
|
3998
|
-
const pendingStart = activeForwardConsoleStart;
|
|
3999
|
-
activeForwardConsoleSession = void 0;
|
|
4000
|
-
activeForwardConsoleBridgeOptions = void 0;
|
|
4001
|
-
const closing = (async () => {
|
|
4002
|
-
const errors = (await Promise.allSettled([Promise.resolve().then(() => session?.close()), pendingStart])).filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
4003
|
-
if (errors.length === 1) throw errors[0];
|
|
4004
|
-
if (errors.length > 1) throw new AggregateError(errors, "DevTools 日志桥关闭失败");
|
|
4005
|
-
})();
|
|
4006
|
-
activeForwardConsoleClose = closing;
|
|
4007
|
-
closing.finally(() => {
|
|
4008
|
-
if (activeForwardConsoleClose === closing) activeForwardConsoleClose = void 0;
|
|
4009
|
-
}).catch(() => {});
|
|
4010
|
-
return closing;
|
|
4011
|
-
}
|
|
4012
|
-
/**
|
|
4013
|
-
* @description 在 weapp 开发态按需启动控制台转发。
|
|
4014
|
-
*/
|
|
4015
|
-
async function maybeStartForwardConsole(options) {
|
|
4016
|
-
if (options.platform !== "weapp" || activeForwardConsoleClose) return false;
|
|
4017
|
-
const multiPlatform = options.weappViteConfig?.multiPlatform;
|
|
4018
|
-
const generatedProjectConfig = typeof multiPlatform === "object" && multiPlatform.projectConfigs !== void 0;
|
|
4019
|
-
const projectPath = resolveIdeProjectRoot(options.mpDistRoot, options.cwd, generatedProjectConfig);
|
|
4020
|
-
if (!projectPath) return false;
|
|
4021
|
-
if (activeForwardConsoleSession) return true;
|
|
4022
|
-
if (activeForwardConsoleStart) return await activeForwardConsoleStart;
|
|
4023
|
-
const lifecycle = forwardConsoleLifecycle;
|
|
4024
|
-
const startTask = (async () => {
|
|
4025
|
-
const resolved = await resolveForwardConsoleOptions(options.weappViteConfig);
|
|
4026
|
-
if (!resolved.enabled || lifecycle !== forwardConsoleLifecycle) return false;
|
|
4027
|
-
const bridgeOptions = {
|
|
4028
|
-
agentName: resolved.agentName,
|
|
4029
|
-
color: !resolved.agentName,
|
|
4030
|
-
projectPath,
|
|
4031
|
-
port: resolveProjectAutomatorPort(projectPath),
|
|
4032
|
-
timeout: options.recoverAutomatorSession ? FORWARD_CONSOLE_RECOVERABLE_START_TIMEOUT_MS : FORWARD_CONSOLE_START_TIMEOUT_MS,
|
|
4033
|
-
logLevels: resolved.logLevels,
|
|
4034
|
-
openedOnly: options.openedOnly,
|
|
4035
|
-
preferOpenedSession: options.preferOpenedSession,
|
|
4036
|
-
preserveProjectRoot: options.preserveProjectRoot ?? !shouldUseAutomatorProjectWrapper(projectPath),
|
|
4037
|
-
unhandledErrors: resolved.unhandledErrors,
|
|
4038
|
-
onReadyMessage: "[forwardConsole] 已连接微信开发者工具日志"
|
|
4039
|
-
};
|
|
4040
|
-
const activateSession = async (session, activeOptions) => {
|
|
4041
|
-
if (lifecycle !== forwardConsoleLifecycle) {
|
|
4042
|
-
await session.close();
|
|
4043
|
-
return false;
|
|
4044
|
-
}
|
|
4045
|
-
activeForwardConsoleSession = session;
|
|
4046
|
-
activeForwardConsoleBridgeOptions = activeOptions;
|
|
4047
|
-
return true;
|
|
4048
|
-
};
|
|
4049
|
-
const recoverAutomatorSession = async () => {
|
|
4050
|
-
try {
|
|
4051
|
-
await options.recoverAutomatorSession?.();
|
|
4052
|
-
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
4053
|
-
const recoveredOptions = {
|
|
4054
|
-
...bridgeOptions,
|
|
4055
|
-
openedOnly: true,
|
|
4056
|
-
preferOpenedSession: true
|
|
4057
|
-
};
|
|
4058
|
-
return activateSession(await startForwardConsoleBridge(recoveredOptions), recoveredOptions);
|
|
4059
|
-
} catch (recoveryError) {
|
|
4060
|
-
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
4061
|
-
const message = recoveryError instanceof Error ? recoveryError.message : String(recoveryError);
|
|
4062
|
-
logger_default.warn(`[forwardConsole] automator 会话恢复失败,回退到普通 IDE 打开流程:${message}`);
|
|
4063
|
-
return false;
|
|
4064
|
-
}
|
|
4065
|
-
};
|
|
4066
|
-
try {
|
|
4067
|
-
return activateSession(await startForwardConsoleBridge(bridgeOptions), bridgeOptions);
|
|
4068
|
-
} catch (error) {
|
|
4069
|
-
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
4070
|
-
if (options.recoverAutomatorSession && isRecoverableAutomatorStartError(error)) return await recoverAutomatorSession();
|
|
4071
|
-
if (!isDevtoolsPortNotReadyError(error)) {
|
|
4072
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
4073
|
-
logger_default.warn(`[forwardConsole] 启动失败,回退到普通 IDE 打开流程:${message}`);
|
|
4074
|
-
return false;
|
|
4075
|
-
}
|
|
4076
|
-
const fallbackOptions = {
|
|
4077
|
-
...bridgeOptions,
|
|
4078
|
-
openedOnly: true,
|
|
4079
|
-
port: void 0,
|
|
4080
|
-
preferOpenedSession: true
|
|
4081
|
-
};
|
|
4082
|
-
try {
|
|
4083
|
-
return activateSession(await startForwardConsoleBridge(fallbackOptions), fallbackOptions);
|
|
4084
|
-
} catch (fallbackError) {
|
|
4085
|
-
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
4086
|
-
const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
4087
|
-
logger_default.warn(`[forwardConsole] 启动失败,回退到普通 IDE 打开流程:${message}`);
|
|
4088
|
-
return false;
|
|
4089
|
-
}
|
|
4090
|
-
}
|
|
4091
|
-
})();
|
|
4092
|
-
activeForwardConsoleStart = startTask;
|
|
4093
|
-
try {
|
|
4094
|
-
return await startTask;
|
|
4095
|
-
} finally {
|
|
4096
|
-
if (activeForwardConsoleStart === startTask) activeForwardConsoleStart = void 0;
|
|
4097
|
-
}
|
|
4098
|
-
}
|
|
4099
|
-
//#endregion
|
|
4100
|
-
//#region src/cli/hmrProfileSummary.ts
|
|
4101
|
-
function isFiniteNumber(value) {
|
|
4102
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
4103
|
-
}
|
|
4104
|
-
function formatPhaseHint(sample) {
|
|
4105
|
-
const topPhase = [
|
|
4106
|
-
{
|
|
4107
|
-
label: "build-core",
|
|
4108
|
-
value: sample.buildCoreMs
|
|
4109
|
-
},
|
|
4110
|
-
{
|
|
4111
|
-
label: "transform",
|
|
4112
|
-
value: sample.transformMs
|
|
4113
|
-
},
|
|
4114
|
-
{
|
|
4115
|
-
label: "build-start",
|
|
4116
|
-
value: sample.buildStartMs
|
|
4117
|
-
},
|
|
4118
|
-
{
|
|
4119
|
-
label: "plugin-resolve",
|
|
4120
|
-
value: sample.pluginResolveMs
|
|
4121
|
-
},
|
|
4122
|
-
{
|
|
4123
|
-
label: "core-transform",
|
|
4124
|
-
value: sample.coreTransformMs
|
|
4125
|
-
},
|
|
4126
|
-
{
|
|
4127
|
-
label: "wevu-transform",
|
|
4128
|
-
value: sample.wevuTransformMs
|
|
4129
|
-
},
|
|
4130
|
-
{
|
|
4131
|
-
label: "vue-transform",
|
|
4132
|
-
value: sample.vueTransformMs
|
|
4133
|
-
},
|
|
4134
|
-
{
|
|
4135
|
-
label: "vue-read",
|
|
4136
|
-
value: sample.vueReadSourceMs
|
|
4137
|
-
},
|
|
4138
|
-
{
|
|
4139
|
-
label: "vue-compile",
|
|
4140
|
-
value: sample.vueCompileMs
|
|
4141
|
-
},
|
|
4142
|
-
{
|
|
4143
|
-
label: "vue-finalize-compiled",
|
|
4144
|
-
value: sample.vueFinalizeCompiledMs
|
|
4145
|
-
},
|
|
4146
|
-
{
|
|
4147
|
-
label: "vue-finalize-code",
|
|
4148
|
-
value: sample.vueFinalizeCodeMs
|
|
4149
|
-
},
|
|
4150
|
-
{
|
|
4151
|
-
label: "core-load",
|
|
4152
|
-
value: sample.coreLoadMs
|
|
4153
|
-
},
|
|
4154
|
-
{
|
|
4155
|
-
label: "entry-load",
|
|
4156
|
-
value: sample.entryLoadMs
|
|
4157
|
-
},
|
|
4158
|
-
{
|
|
4159
|
-
label: "entry-emit-output",
|
|
4160
|
-
value: sample.entryEmitOutputMs
|
|
4161
|
-
},
|
|
4162
|
-
{
|
|
4163
|
-
label: "entry-template-scan",
|
|
4164
|
-
value: sample.entryTemplateScanMs
|
|
4165
|
-
},
|
|
4166
|
-
{
|
|
4167
|
-
label: "entry-auto-import",
|
|
4168
|
-
value: sample.entryAutoImportMs
|
|
4169
|
-
},
|
|
4170
|
-
{
|
|
4171
|
-
label: "entry-script-setup",
|
|
4172
|
-
value: sample.entryScriptSetupMs
|
|
4173
|
-
},
|
|
4174
|
-
{
|
|
4175
|
-
label: "entry-vue-signature",
|
|
4176
|
-
value: sample.entryVueSignatureMs
|
|
4177
|
-
},
|
|
4178
|
-
{
|
|
4179
|
-
label: "entry-sidecar-resolve",
|
|
4180
|
-
value: sample.entrySidecarResolveMs
|
|
4181
|
-
},
|
|
4182
|
-
{
|
|
4183
|
-
label: "entry-json-read",
|
|
4184
|
-
value: sample.entryJsonReadMs
|
|
4185
|
-
},
|
|
4186
|
-
{
|
|
4187
|
-
label: "entry-vue-config",
|
|
4188
|
-
value: sample.entryVueConfigMs
|
|
4189
|
-
},
|
|
4190
|
-
{
|
|
4191
|
-
label: "entry-prepare",
|
|
4192
|
-
value: sample.entryPrepareMs
|
|
4193
|
-
},
|
|
4194
|
-
{
|
|
4195
|
-
label: "entry-resolve",
|
|
4196
|
-
value: sample.entryResolveMs
|
|
4197
|
-
},
|
|
4198
|
-
{
|
|
4199
|
-
label: "entry-style-scan",
|
|
4200
|
-
value: sample.entryStyleScanMs
|
|
4201
|
-
},
|
|
4202
|
-
{
|
|
4203
|
-
label: "entry-style-read",
|
|
4204
|
-
value: sample.entryStyleReadMs
|
|
4205
|
-
},
|
|
4206
|
-
{
|
|
4207
|
-
label: "entry-code-read",
|
|
4208
|
-
value: sample.entryCodeReadMs
|
|
4209
|
-
},
|
|
4210
|
-
{
|
|
4211
|
-
label: "entry-chunk-emit",
|
|
4212
|
-
value: sample.entryChunkEmitMs
|
|
4213
|
-
},
|
|
4214
|
-
{
|
|
4215
|
-
label: "entry-chunk-load",
|
|
4216
|
-
value: sample.entryChunkLoadMs
|
|
4217
|
-
},
|
|
4218
|
-
{
|
|
4219
|
-
label: "entry-chunk-emit-file",
|
|
4220
|
-
value: sample.entryChunkEmitFileMs
|
|
4221
|
-
},
|
|
4222
|
-
{
|
|
4223
|
-
label: "entry-layout",
|
|
4224
|
-
value: sample.entryLayoutMs
|
|
4225
|
-
},
|
|
4226
|
-
{
|
|
4227
|
-
label: "request-globals",
|
|
4228
|
-
value: sample.requestGlobalsMs
|
|
4229
|
-
},
|
|
4230
|
-
{
|
|
4231
|
-
label: "weapi-resolve",
|
|
4232
|
-
value: sample.weapiResolveMs
|
|
4233
|
-
},
|
|
4234
|
-
{
|
|
4235
|
-
label: "render-start",
|
|
4236
|
-
value: sample.renderStartMs
|
|
4237
|
-
},
|
|
4238
|
-
{
|
|
4239
|
-
label: "generate",
|
|
4240
|
-
value: sample.generateBundleMs
|
|
4241
|
-
},
|
|
4242
|
-
{
|
|
4243
|
-
label: "generate-shared",
|
|
4244
|
-
value: sample.generateSharedMs
|
|
4245
|
-
},
|
|
4246
|
-
{
|
|
4247
|
-
label: "generate-rewrite",
|
|
4248
|
-
value: sample.generateRewriteMs
|
|
4249
|
-
},
|
|
4250
|
-
{
|
|
4251
|
-
label: "module-graph",
|
|
4252
|
-
value: sample.generateModuleGraphMs
|
|
4253
|
-
},
|
|
4254
|
-
{
|
|
4255
|
-
label: "snapshot-resolve",
|
|
4256
|
-
value: sample.snapshotResolveMs
|
|
4257
|
-
},
|
|
4258
|
-
{
|
|
4259
|
-
label: "snapshot-build",
|
|
4260
|
-
value: sample.snapshotBuildMs
|
|
4261
|
-
},
|
|
4262
|
-
{
|
|
4263
|
-
label: "watch->dirty",
|
|
4264
|
-
value: sample.watchToDirtyMs
|
|
4265
|
-
},
|
|
4266
|
-
{
|
|
4267
|
-
label: "emit",
|
|
4268
|
-
value: sample.emitMs
|
|
4269
|
-
},
|
|
4270
|
-
{
|
|
4271
|
-
label: "shared",
|
|
4272
|
-
value: sample.sharedChunkResolveMs
|
|
4273
|
-
},
|
|
4274
|
-
{
|
|
4275
|
-
label: "write",
|
|
4276
|
-
value: sample.writeMs
|
|
4277
|
-
}
|
|
4278
|
-
].filter((phase) => isFiniteNumber(phase.value)).sort((left, right) => (right.value ?? 0) - (left.value ?? 0))[0];
|
|
4279
|
-
if (!topPhase || (topPhase.value ?? 0) < 5) return;
|
|
4280
|
-
return `${topPhase.label} ${topPhase.value.toFixed(2)} ms`;
|
|
4281
|
-
}
|
|
4282
|
-
/**
|
|
4283
|
-
* @description 读取最近一次 HMR profile,并格式化为 IDE 日志启动前的单行摘要。
|
|
4284
|
-
*/
|
|
4285
|
-
async function readLatestHmrProfileSummary(options) {
|
|
4286
|
-
const profilePath = resolveHmrProfileJsonPath({
|
|
4287
|
-
cwd: options.cwd,
|
|
4288
|
-
option: options.weappViteConfig?.hmr?.profileJson
|
|
4289
|
-
});
|
|
4290
|
-
if (!profilePath) return;
|
|
4291
|
-
const content = await fs$1.readFile(profilePath, "utf8").catch(() => void 0);
|
|
4292
|
-
if (!content) return;
|
|
4293
|
-
const { samples, skippedLineCount } = readHmrProfileLines(content);
|
|
4294
|
-
const sample = samples.at(-1);
|
|
4295
|
-
if (!sample || !isFiniteNumber(sample.totalMs)) return;
|
|
4296
|
-
const relativeCwd = options.relativeCwd ?? ((value) => value);
|
|
4297
|
-
const segments = [`[hmr] 最近一次热更新 ${sample.totalMs.toFixed(2)} ms`];
|
|
4298
|
-
if (sample.event) segments.push(sample.event);
|
|
4299
|
-
if (sample.file) segments.push(relativeCwd(sample.file));
|
|
4300
|
-
const phaseHint = formatPhaseHint(sample);
|
|
4301
|
-
if (phaseHint) segments.push(`主耗时 ${phaseHint}`);
|
|
4302
|
-
if (isFiniteNumber(sample.loadCount) || isFiniteNumber(sample.resolveCount) || isFiniteNumber(sample.chunkEmitCount) || isFiniteNumber(sample.skippedLoadedCount)) segments.push(`load/resolve/chunk/skip ${sample.loadCount ?? "unknown"}/${sample.resolveCount ?? "unknown"}/${sample.chunkEmitCount ?? "unknown"}/${sample.skippedLoadedCount ?? "unknown"}`);
|
|
4303
|
-
if (skippedLineCount) segments.push(`忽略 ${skippedLineCount} 条不兼容、未完成或无效记录`);
|
|
4304
|
-
return {
|
|
4305
|
-
file: sample.file,
|
|
4306
|
-
profilePath,
|
|
4307
|
-
line: segments.join(",")
|
|
4308
|
-
};
|
|
4309
|
-
}
|
|
4310
|
-
//#endregion
|
|
4311
|
-
//#region src/cli/commands/ide.ts
|
|
4312
|
-
async function runIdeDoctor(projectPath, options) {
|
|
4313
|
-
const resolvedCli = await resolveCliPath();
|
|
4314
|
-
const servicePort = await detectWechatDevtoolsServicePort().then((result) => ({
|
|
4315
|
-
...result,
|
|
4316
|
-
error: void 0
|
|
4317
|
-
})).catch((error) => ({
|
|
4318
|
-
servicePortEnabled: void 0,
|
|
4319
|
-
servicePort: void 0,
|
|
4320
|
-
error
|
|
4321
|
-
}));
|
|
4322
|
-
const cliCheck = resolvedCli.cliPath ? {
|
|
4323
|
-
status: "ok",
|
|
4324
|
-
value: {
|
|
4325
|
-
path: resolvedCli.cliPath,
|
|
4326
|
-
source: resolvedCli.source
|
|
4327
|
-
}
|
|
4328
|
-
} : {
|
|
4329
|
-
fix: "执行 `weapp config set cliPath <path-to-cli>` 配置微信开发者工具 CLI。",
|
|
4330
|
-
message: "未找到可用的微信开发者工具 CLI。",
|
|
4331
|
-
status: "failed",
|
|
4332
|
-
value: { source: resolvedCli.source }
|
|
4333
|
-
};
|
|
4334
|
-
const servicePortCheck = servicePort.servicePortEnabled === false ? {
|
|
4335
|
-
fix: "在微信开发者工具中打开:设置 -> 安全设置 -> 服务端口。",
|
|
4336
|
-
message: "微信开发者工具服务端口已关闭。",
|
|
4337
|
-
status: "failed",
|
|
4338
|
-
value: servicePort
|
|
4339
|
-
} : servicePort.error ? {
|
|
4340
|
-
fix: "执行 `wv ide doctor --json` 查看底层错误,并确认微信开发者工具已安装。",
|
|
4341
|
-
message: servicePort.error instanceof Error ? servicePort.error.message : String(servicePort.error),
|
|
4342
|
-
status: "warning"
|
|
4343
|
-
} : {
|
|
4344
|
-
status: servicePort.servicePortEnabled === true ? "ok" : "unknown",
|
|
4345
|
-
value: {
|
|
4346
|
-
enabled: servicePort.servicePortEnabled ?? null,
|
|
4347
|
-
port: servicePort.servicePort ?? null
|
|
4348
|
-
}
|
|
4349
|
-
};
|
|
4350
|
-
let loginCheck = {
|
|
4351
|
-
fix: "打开微信开发者工具完成登录后重试。",
|
|
4352
|
-
message: "暂未确认登录状态。",
|
|
4353
|
-
status: "unknown"
|
|
4354
|
-
};
|
|
4355
|
-
if (resolvedCli.cliPath) try {
|
|
4356
|
-
const result = await queryWechatIdeLogin(resolvedCli.cliPath, { timeout: 3e3 });
|
|
4357
|
-
loginCheck = result.status === "success" ? {
|
|
4358
|
-
status: result.login ? "ok" : "warning",
|
|
4359
|
-
value: result.login,
|
|
4360
|
-
...result.login ? {} : { message: "原生 CLI 明确返回未登录,请打开微信开发者工具完成登录。" }
|
|
4361
|
-
} : {
|
|
4362
|
-
status: "unknown",
|
|
4363
|
-
message: `登录查询未确认状态(${result.reason}),不能据此认定未登录。`
|
|
4364
|
-
};
|
|
4365
|
-
} catch {
|
|
4366
|
-
loginCheck = {
|
|
4367
|
-
message: "登录查询执行失败,登录状态仍未确认。",
|
|
4368
|
-
status: "unknown"
|
|
4369
|
-
};
|
|
4370
|
-
}
|
|
4371
|
-
const projectCheck = projectPath ? {
|
|
4372
|
-
status: "ok",
|
|
4373
|
-
value: projectPath
|
|
4374
|
-
} : {
|
|
4375
|
-
fix: "在项目目录执行命令,或显式传入 `wv ide doctor <project-root>`。",
|
|
4376
|
-
message: "未解析到微信开发者工具项目目录。",
|
|
4377
|
-
status: "warning"
|
|
4378
|
-
};
|
|
4379
|
-
let automatorCheck = {
|
|
4380
|
-
message: projectPath ? "当前项目没有可连接的已打开 automator 会话。" : "缺少项目目录,跳过 automator 检查。",
|
|
4381
|
-
status: "unknown"
|
|
4382
|
-
};
|
|
4383
|
-
let toolCheck = {
|
|
4384
|
-
message: "当前没有可读取的 DevTools Tool.getInfo。",
|
|
4385
|
-
status: "unknown"
|
|
4386
|
-
};
|
|
4387
|
-
if (projectPath) try {
|
|
4388
|
-
const miniProgram = await connectOpenedAutomator({
|
|
4389
|
-
projectPath,
|
|
4390
|
-
port: resolveProjectAutomatorPort(projectPath),
|
|
4391
|
-
timeout: 3e3
|
|
4392
|
-
});
|
|
4393
|
-
automatorCheck = {
|
|
4394
|
-
status: "ok",
|
|
4395
|
-
value: true
|
|
4396
|
-
};
|
|
4397
|
-
try {
|
|
4398
|
-
if (typeof miniProgram.toolInfo === "function") toolCheck = {
|
|
4399
|
-
status: "ok",
|
|
4400
|
-
value: await miniProgram.toolInfo()
|
|
4401
|
-
};
|
|
4402
|
-
} catch {
|
|
4403
|
-
toolCheck = {
|
|
4404
|
-
status: "warning",
|
|
4405
|
-
message: "项目自动化连接已建立,但 Tool.getInfo 未能返回工具信息;宿主版本仍未确认。",
|
|
4406
|
-
fix: "确认目标项目模拟器就绪后重新运行 doctor;此结果不证明登录失效。"
|
|
4407
|
-
};
|
|
4408
|
-
} finally {
|
|
4409
|
-
miniProgram.disconnect?.();
|
|
4410
|
-
}
|
|
4411
|
-
} catch (error) {
|
|
4412
|
-
automatorCheck = {
|
|
4413
|
-
fix: "先执行 `wv open` 打开项目;若服务端口已开启,关闭多余 DevTools 窗口后重试。",
|
|
4414
|
-
message: error instanceof Error ? error.message : String(error),
|
|
4415
|
-
status: "warning"
|
|
4416
|
-
};
|
|
4417
|
-
}
|
|
4418
|
-
const report = {
|
|
4419
|
-
checks: {
|
|
4420
|
-
automator: automatorCheck,
|
|
4421
|
-
cli: cliCheck,
|
|
4422
|
-
login: loginCheck,
|
|
4423
|
-
project: projectCheck,
|
|
4424
|
-
servicePort: servicePortCheck,
|
|
4425
|
-
tool: toolCheck
|
|
4426
|
-
},
|
|
4427
|
-
configFile: defaultCustomConfigFilePath,
|
|
4428
|
-
nodeVersion: process.version,
|
|
4429
|
-
platform: process.platform,
|
|
4430
|
-
weappViteVersion: VERSION
|
|
4431
|
-
};
|
|
4432
|
-
const output = JSON.stringify(report, null, 2);
|
|
4433
|
-
if (options.json) process.stdout.write(`${output}\n`);
|
|
4434
|
-
else logger_default.info(output);
|
|
4435
|
-
if (options.strict && Object.values(report.checks).some((check) => check.status === "failed")) throw new Error("微信开发者工具环境诊断未通过,请根据 doctor 输出修复失败项后重试。");
|
|
4436
|
-
return report;
|
|
4437
|
-
}
|
|
4438
|
-
async function waitForTermination(cleanup) {
|
|
4439
|
-
await new Promise((resolve) => {
|
|
4440
|
-
const signals = ["SIGINT", "SIGTERM"];
|
|
4441
|
-
let cleaning = false;
|
|
4442
|
-
const handlers = /* @__PURE__ */ new Map();
|
|
4443
|
-
const teardown = async () => {
|
|
4444
|
-
if (cleaning) return;
|
|
4445
|
-
cleaning = true;
|
|
4446
|
-
for (const signal of signals) process.off(signal, handlers.get(signal));
|
|
4447
|
-
await cleanup();
|
|
4448
|
-
resolve();
|
|
4449
|
-
};
|
|
4450
|
-
for (const signal of signals) {
|
|
4451
|
-
const handler = () => {
|
|
4452
|
-
teardown();
|
|
4453
|
-
};
|
|
4454
|
-
handlers.set(signal, handler);
|
|
4455
|
-
process.on(signal, handler);
|
|
4456
|
-
}
|
|
4457
|
-
});
|
|
4458
|
-
}
|
|
4459
|
-
function formatIdeOutput(data, options) {
|
|
4460
|
-
if (options.json) return JSON.stringify(data, null, 2);
|
|
4461
|
-
return JSON.stringify(data, null, 2);
|
|
4462
|
-
}
|
|
4463
|
-
/**
|
|
4464
|
-
* @description 执行 ide 子命令。
|
|
4465
|
-
*/
|
|
203
|
+
//#region src/cli/commands/ide/index.ts
|
|
204
|
+
/** 仅在执行 IDE 子命令时加载宿主工具和日志桥接实现。 */
|
|
4466
205
|
async function runIdeCommand(action, root, options) {
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
const configFile = resolveConfigFile(options);
|
|
4470
|
-
const targets = resolveRuntimeTargets(options);
|
|
4471
|
-
const ideBackend = getBackendForCapability(targets, "miniprogram", "ide");
|
|
4472
|
-
if (!ideBackend) throw new Error("`weapp-vite ide` 当前仅支持小程序平台。");
|
|
4473
|
-
const resolved = await resolveIdeCommandContext({
|
|
4474
|
-
configFile,
|
|
4475
|
-
mode: options.mode ?? "development",
|
|
4476
|
-
platform: ideBackend.platform,
|
|
4477
|
-
projectPath: root,
|
|
4478
|
-
cliPlatform: targets.rawPlatform
|
|
4479
|
-
});
|
|
4480
|
-
if (action !== "doctor" && resolved.platform !== "weapp") throw new Error("`weapp-vite ide logs` 当前仅支持微信小程序平台。");
|
|
4481
|
-
if (action === "doctor") {
|
|
4482
|
-
await runIdeDoctor(resolved.projectPath, options);
|
|
4483
|
-
return;
|
|
4484
|
-
}
|
|
4485
|
-
const projectPath = resolved.projectPath;
|
|
4486
|
-
if (!projectPath) throw new Error("无法解析微信开发者工具项目目录,请显式传入 root 或检查 project.config.json。");
|
|
4487
|
-
if (action === "setup") {
|
|
4488
|
-
const result = await bootstrapWechatDevtoolsSettings({
|
|
4489
|
-
projectPath,
|
|
4490
|
-
trustProject: options.trustProject
|
|
4491
|
-
});
|
|
4492
|
-
logger_default.info(`已完成微信开发者工具配置预热:扫描实例 ${result.touchedInstanceCount} 个,检测服务端口配置 ${result.detectedSecurityCount} 处,写入项目信任 ${result.trustedProjectCount} 处。`);
|
|
4493
|
-
return;
|
|
4494
|
-
}
|
|
4495
|
-
if (options.open) await openIde(resolved.platform, projectPath, {
|
|
4496
|
-
openRecovery: options.openRecovery,
|
|
4497
|
-
trustProject: options.trustProject,
|
|
4498
|
-
openStrategy: options.ideOpenStrategy
|
|
4499
|
-
});
|
|
4500
|
-
if (action === "info") {
|
|
4501
|
-
const result = await getWechatIdeToolInfo({ projectPath });
|
|
4502
|
-
logger_default.info(formatIdeOutput(result, options));
|
|
4503
|
-
return;
|
|
4504
|
-
}
|
|
4505
|
-
if (action === "test-accounts") {
|
|
4506
|
-
const result = await getWechatIdeTestAccounts({ projectPath });
|
|
4507
|
-
logger_default.info(formatIdeOutput(result, options));
|
|
4508
|
-
return;
|
|
4509
|
-
}
|
|
4510
|
-
if (action === "ticket") {
|
|
4511
|
-
const result = await getWechatIdeTicket({ projectPath });
|
|
4512
|
-
logger_default.info(formatIdeOutput(result, options));
|
|
4513
|
-
return;
|
|
4514
|
-
}
|
|
4515
|
-
if (action === "ticket:set") {
|
|
4516
|
-
if (!options.ticket) throw new Error("`weapp-vite ide ticket:set` 需要提供 --ticket。");
|
|
4517
|
-
await setWechatIdeTicket({
|
|
4518
|
-
projectPath,
|
|
4519
|
-
ticket: options.ticket
|
|
4520
|
-
});
|
|
4521
|
-
logger_default.info(`已设置微信开发者工具 ticket:${options.ticket}`);
|
|
4522
|
-
return;
|
|
4523
|
-
}
|
|
4524
|
-
if (action === "ticket:refresh") {
|
|
4525
|
-
await refreshWechatIdeTicket({ projectPath });
|
|
4526
|
-
logger_default.info("已刷新微信开发者工具 ticket。");
|
|
4527
|
-
return;
|
|
4528
|
-
}
|
|
4529
|
-
const forwardConsoleOptions = await resolveForwardConsoleOptions({
|
|
4530
|
-
...resolved.weappViteConfig,
|
|
4531
|
-
forwardConsole: resolved.weappViteConfig?.forwardConsole === false ? { enabled: true } : {
|
|
4532
|
-
...typeof resolved.weappViteConfig?.forwardConsole === "object" ? resolved.weappViteConfig.forwardConsole : {},
|
|
4533
|
-
enabled: true
|
|
4534
|
-
}
|
|
4535
|
-
});
|
|
4536
|
-
const latestHmrSummary = await readLatestHmrProfileSummary({
|
|
4537
|
-
cwd: resolved.cwd ?? process.cwd(),
|
|
4538
|
-
relativeCwd: (value) => resolved.cwd ? value.replace(`${resolved.cwd}/`, "") : value,
|
|
4539
|
-
weappViteConfig: resolved.weappViteConfig
|
|
4540
|
-
});
|
|
4541
|
-
if (latestHmrSummary) logger_default.info(latestHmrSummary.line);
|
|
4542
|
-
const session = await startForwardConsoleBridge({
|
|
4543
|
-
projectPath,
|
|
4544
|
-
port: resolveProjectAutomatorPort(projectPath),
|
|
4545
|
-
agentName: void 0,
|
|
4546
|
-
logLevels: forwardConsoleOptions.logLevels,
|
|
4547
|
-
unhandledErrors: forwardConsoleOptions.unhandledErrors,
|
|
4548
|
-
onReadyMessage: "[forwardConsole] 已进入持续监听模式,按 Ctrl+C 退出。"
|
|
4549
|
-
});
|
|
4550
|
-
await waitForTermination(async () => {
|
|
4551
|
-
logger_default.info("[forwardConsole] 正在关闭日志桥接...");
|
|
4552
|
-
await session.close();
|
|
4553
|
-
});
|
|
206
|
+
const { runIdeCommand } = await import("./action-B3YZMA-h.mjs");
|
|
207
|
+
return runIdeCommand(action, root, options);
|
|
4554
208
|
}
|
|
4555
209
|
/**
|
|
4556
210
|
* @description 注册 IDE 相关子命令。
|
|
@@ -4565,6 +219,7 @@ function registerIdeCommand(cli) {
|
|
|
4565
219
|
function registerInitCommand(cli) {
|
|
4566
220
|
cli.command("init").action(async () => {
|
|
4567
221
|
try {
|
|
222
|
+
const { initConfig } = await import("@weapp-core/init");
|
|
4568
223
|
await initConfig({ command: "weapp-vite" });
|
|
4569
224
|
} catch (error) {
|
|
4570
225
|
logger_default.error(error);
|
|
@@ -4637,6 +292,7 @@ async function maybeStartDetachedMcpServer(options) {
|
|
|
4637
292
|
logger_default.info(`[mcp] MCP 服务已存在,继续复用:${colors.cyan(url)}`);
|
|
4638
293
|
return;
|
|
4639
294
|
}
|
|
295
|
+
const target = getActiveWechatDevtoolsTarget();
|
|
4640
296
|
const args = [
|
|
4641
297
|
entrypoint,
|
|
4642
298
|
"mcp",
|
|
@@ -4653,6 +309,7 @@ async function maybeStartDetachedMcpServer(options) {
|
|
|
4653
309
|
];
|
|
4654
310
|
if (resolvedMcp.restEndpoint === false) args.push("--no-rest");
|
|
4655
311
|
else args.push("--rest-endpoint", resolvedMcp.restEndpoint);
|
|
312
|
+
if (target) args.push("--cli-path", target.cliPath);
|
|
4656
313
|
const child = spawn(process.execPath, args, {
|
|
4657
314
|
cwd: options.cwd,
|
|
4658
315
|
detached: true,
|
|
@@ -4714,20 +371,24 @@ function registerOpenCommand(cli) {
|
|
|
4714
371
|
if (latestHmrSummary) logger_default.info(latestHmrSummary.line);
|
|
4715
372
|
const cwdForMcp = cwd ?? process.cwd();
|
|
4716
373
|
const aiEnvironment = await detectAiDevelopmentEnvironment();
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
374
|
+
const openSelected = async () => {
|
|
375
|
+
await maybeStartDetachedMcpServer({
|
|
376
|
+
agentName: aiEnvironment.agentName,
|
|
377
|
+
cwd: cwdForMcp,
|
|
378
|
+
isAgent: aiEnvironment.isAgent,
|
|
379
|
+
mcpConfig: applyMcpCliOptions(weappViteConfig?.mcp, options)
|
|
380
|
+
});
|
|
381
|
+
await openIde(platform, projectPath ?? resolveIdeProjectRoot(mpDistRoot, process.cwd()), {
|
|
382
|
+
loginRetry: options.loginRetry,
|
|
383
|
+
loginRetryTimeout: options.loginRetryTimeout,
|
|
384
|
+
nonInteractive: options.nonInteractive,
|
|
385
|
+
openRecovery: options.openRecovery,
|
|
386
|
+
trustProject: options.trustProject,
|
|
387
|
+
openStrategy: options.ideOpenStrategy
|
|
388
|
+
});
|
|
389
|
+
};
|
|
390
|
+
if (platform === "weapp") await withWechatDevtoolsSelection({}, openSelected);
|
|
391
|
+
else await openSelected();
|
|
4731
392
|
});
|
|
4732
393
|
}
|
|
4733
394
|
//#endregion
|
|
@@ -4849,7 +510,7 @@ function formatResolvedScreenshotPath(cwd, fallbackPath, result) {
|
|
|
4849
510
|
}
|
|
4850
511
|
async function resolveDefaultScreenshotPage(cwd) {
|
|
4851
512
|
try {
|
|
4852
|
-
const raw = await fs
|
|
513
|
+
const raw = await fs.readFile(path.join(cwd, "app.json"), "utf8");
|
|
4853
514
|
const appJson = JSON.parse(raw);
|
|
4854
515
|
const [firstPage] = Array.isArray(appJson.pages) ? appJson.pages : [];
|
|
4855
516
|
return typeof firstPage === "string" && firstPage.trim() ? firstPage : void 0;
|
|
@@ -4862,7 +523,7 @@ async function resolveDefaultScreenshotPage(cwd) {
|
|
|
4862
523
|
*/
|
|
4863
524
|
async function runScreenshotAction(options) {
|
|
4864
525
|
const outputPath = resolveDevScreenshotOutputPath(options.cwd);
|
|
4865
|
-
await fs
|
|
526
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
4866
527
|
const page = await resolveDefaultScreenshotPage(options.cwd);
|
|
4867
528
|
logger_default.info(`[dev action] 正在截图当前页面,输出到 ${colors.cyan(formatLogPath(options.cwd, outputPath))}`);
|
|
4868
529
|
const resumeForwardConsole = await pauseActiveForwardConsole();
|
|
@@ -5419,7 +1080,7 @@ function createServeDevelopmentActions(ctx, miniBackend, options, shutdown) {
|
|
|
5419
1080
|
projectPath: resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd, generatedProjectConfig),
|
|
5420
1081
|
startForwardConsole: async (openOptions) => {
|
|
5421
1082
|
if (shutdown.stopping) return false;
|
|
5422
|
-
if (resolveServeIdeOpenStrategy(openOptions, options.ideOpenStrategy) === "automator") return await maybeStartForwardConsole({
|
|
1083
|
+
if (resolveServeIdeOpenStrategy(openOptions, options.ideOpenStrategy) === "automator" || process.env[MANAGED_PROJECT_JOURNAL_ENV]?.trim()) return await maybeStartForwardConsole({
|
|
5423
1084
|
openedOnly: true,
|
|
5424
1085
|
preferOpenedSession: true,
|
|
5425
1086
|
platform: configService.platform,
|
|
@@ -5496,7 +1157,7 @@ function hasAnalyzeData(result) {
|
|
|
5496
1157
|
return result.packages.length > 0 || result.modules.length > 0;
|
|
5497
1158
|
}
|
|
5498
1159
|
async function collectOutputFiles(root) {
|
|
5499
|
-
const entries = await fs
|
|
1160
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
5500
1161
|
const files = [];
|
|
5501
1162
|
for (const entry of entries) {
|
|
5502
1163
|
const absolutePath = path.join(root, entry.name);
|
|
@@ -5550,7 +1211,7 @@ async function analyzeUiFallback(ctx, options) {
|
|
|
5550
1211
|
for (const absoluteFile of absoluteFiles) {
|
|
5551
1212
|
const relativeFile = path.relative(distRoot, absoluteFile).replace(REG_DIST_POSIX_SEP, "/");
|
|
5552
1213
|
const packageInfo = classifyPackage(relativeFile);
|
|
5553
|
-
const content = await fs
|
|
1214
|
+
const content = await fs.readFile(absoluteFile);
|
|
5554
1215
|
ensurePackage(packageInfo.id, packageInfo.type).files.push({
|
|
5555
1216
|
file: relativeFile,
|
|
5556
1217
|
type: relativeFile.endsWith(".js") ? "chunk" : "asset",
|
|
@@ -5799,8 +1460,19 @@ async function startServeCommand(root, options, shutdown) {
|
|
|
5799
1460
|
projectConfigPath: options.projectConfig
|
|
5800
1461
|
});
|
|
5801
1462
|
const { configService } = ctx;
|
|
1463
|
+
let backendCloseFailed = false;
|
|
1464
|
+
shutdown.own(async () => {
|
|
1465
|
+
if (!backendCloseFailed) await getHmrRuntimeSession(ctx).classicIdeConfig.close();
|
|
1466
|
+
});
|
|
5802
1467
|
targets = resolveConfiguredRuntimeTargets(targets, configService.options?.sourceConfig?.weapp?.platform);
|
|
5803
|
-
for (const backend of targets.select("dev")) shutdown.own(() =>
|
|
1468
|
+
for (const backend of targets.select("dev")) shutdown.own(async () => {
|
|
1469
|
+
try {
|
|
1470
|
+
await backend.driver.close(ctx);
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
backendCloseFailed = true;
|
|
1473
|
+
throw error;
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
5804
1476
|
if (shutdown.stopping) return;
|
|
5805
1477
|
const webBackend = getBackendForCapability(targets, "web", "dev");
|
|
5806
1478
|
const miniBackend = getBackendForCapability(targets, "miniprogram", "dev");
|
|
@@ -5948,84 +1620,11 @@ function registerServeCommand(cli) {
|
|
|
5948
1620
|
});
|
|
5949
1621
|
}
|
|
5950
1622
|
//#endregion
|
|
5951
|
-
//#region src/cli/commands/upload.ts
|
|
5952
|
-
|
|
5953
|
-
const targets = resolveRuntimeTargets({ platform });
|
|
5954
|
-
const ctx = await createCompilerContext({
|
|
5955
|
-
cwd,
|
|
5956
|
-
mode: options.mode ?? "production",
|
|
5957
|
-
configFile: resolveConfigFile(options),
|
|
5958
|
-
inlineConfig: createInlineConfig(targets),
|
|
5959
|
-
cliPlatform: platform,
|
|
5960
|
-
projectConfigPath: options.projectConfig,
|
|
5961
|
-
emitDefaultAutoImportOutputs: false,
|
|
5962
|
-
preloadAppEntry: false
|
|
5963
|
-
});
|
|
5964
|
-
try {
|
|
5965
|
-
const target = await createUploadTarget(ctx.configService, options, action);
|
|
5966
|
-
if (item) {
|
|
5967
|
-
item.platform = target.platform;
|
|
5968
|
-
item.requestedVersion = target.context.version;
|
|
5969
|
-
item.stage = "build";
|
|
5970
|
-
}
|
|
5971
|
-
logger_default.info(`[${action}:${target.platform}] 构建${target.context.version ? ` ${target.context.version}` : ""}`);
|
|
5972
|
-
await ctx.buildService.build({});
|
|
5973
|
-
return target;
|
|
5974
|
-
} finally {
|
|
5975
|
-
ctx.watcherService.closeAll();
|
|
5976
|
-
terminateStaleSassEmbeddedProcess();
|
|
5977
|
-
}
|
|
5978
|
-
}
|
|
1623
|
+
//#region src/cli/commands/upload/index.ts
|
|
1624
|
+
/** 按实际上传或预览调用加载执行层,保留原有返回值重载。 */
|
|
5979
1625
|
async function runUploadCommand(root, options, action = "upload") {
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
setCommandNodeEnv("production");
|
|
5983
|
-
const cwd = path$1.resolve(root ?? process.cwd());
|
|
5984
|
-
if (action === "preview") {
|
|
5985
|
-
options = await prepareAutoUploadMetadata(cwd, options, action);
|
|
5986
|
-
for (const platform of platforms) await executeUploadTarget(await buildUploadTarget(cwd, platform, options, action), options, action);
|
|
5987
|
-
return;
|
|
5988
|
-
}
|
|
5989
|
-
const report = {
|
|
5990
|
-
schemaVersion: 1,
|
|
5991
|
-
action: "upload",
|
|
5992
|
-
status: "success",
|
|
5993
|
-
results: platforms.map((platform) => ({
|
|
5994
|
-
platform: platform ?? null,
|
|
5995
|
-
stage: "prepare",
|
|
5996
|
-
status: "not-run"
|
|
5997
|
-
}))
|
|
5998
|
-
};
|
|
5999
|
-
let active = report.results[0];
|
|
6000
|
-
try {
|
|
6001
|
-
const timeoutMs = resolveUploadTimeout(options.timeout);
|
|
6002
|
-
options = await prepareAutoUploadMetadata(cwd, options, action);
|
|
6003
|
-
for (const item of report.results) item.requestedVersion = options.uv?.trim();
|
|
6004
|
-
for (const [index, platform] of platforms.entries()) {
|
|
6005
|
-
active = report.results[index];
|
|
6006
|
-
const target = await buildUploadTarget(cwd, platform, options, action, active);
|
|
6007
|
-
active.stage = "validate";
|
|
6008
|
-
const result = await executeUploadTarget(target, options, action, {
|
|
6009
|
-
timeoutMs,
|
|
6010
|
-
onUpload: () => {
|
|
6011
|
-
active.stage = "upload";
|
|
6012
|
-
}
|
|
6013
|
-
});
|
|
6014
|
-
active.status = options.dryRun ? "dry-run" : "success";
|
|
6015
|
-
if (!options.dryRun) active.result = result;
|
|
6016
|
-
}
|
|
6017
|
-
return report;
|
|
6018
|
-
} catch (error) {
|
|
6019
|
-
report.status = "failed";
|
|
6020
|
-
report.error = error instanceof Error ? error.message : String(error);
|
|
6021
|
-
active.status = "failed";
|
|
6022
|
-
active.error = report.error;
|
|
6023
|
-
if (error instanceof UploadExecutionError) {
|
|
6024
|
-
active.remoteOutcome = error.remoteOutcome;
|
|
6025
|
-
if (error.remoteOutcome === "unknown" && error.reason !== "failed") active.status = "unknown";
|
|
6026
|
-
}
|
|
6027
|
-
throw new UploadCommandError(report);
|
|
6028
|
-
}
|
|
1626
|
+
const { runUploadCommand } = await import("./action-DlC6JYpt.mjs");
|
|
1627
|
+
return runUploadCommand(root, options, action);
|
|
6029
1628
|
}
|
|
6030
1629
|
function registerUploadCommand(cli) {
|
|
6031
1630
|
cli.command("upload [root]", "build and upload mini programs (does not submit for review or publish)").option("-p, --platform <platform>", "[string] weapp | alipay | tt | xhs | jd | swan; comma-separated targets or all").option("--project-config <path>", "[string] project config path").option("--uv <version>", "[string] upload version (default: weapp.upload.version or package.json version)").option("--desc <text>", "[string] upload description (default: weapp.upload.desc or project name and version)").option("--bump <release>", "[string] increment local package version once (patch | minor | major; dry-run does not write)").option("--git-desc", "[boolean] use the latest Git commit subject as upload description").option("--dry-run", "[boolean] build without validating upload credentials or uploading").option("--json", "[boolean] write one upload report to stdout; send logs to stderr").option("--timeout <seconds>", "[number] local timeout per platform SDK invocation, excluding build").example("wv upload -p weapp").example("wv upload -p xhs,tt --bump patch --git-desc --dry-run").example("wv upload --project ./dist --version 1.2.3 --desc \"release\" (deprecated IDE syntax; use wv ide upload to retain IDE behavior)").action(async (root, options) => {
|
|
@@ -6120,6 +1719,9 @@ const SDK_UPLOAD_OPTIONS = {
|
|
|
6120
1719
|
"--timeout": true
|
|
6121
1720
|
};
|
|
6122
1721
|
const UPLOAD_VALUE_OPTIONS = {
|
|
1722
|
+
"--devtools-channel": true,
|
|
1723
|
+
"--devtools-version": true,
|
|
1724
|
+
"--cli-path": true,
|
|
6123
1725
|
...LEGACY_UPLOAD_OPTIONS,
|
|
6124
1726
|
"-p": true,
|
|
6125
1727
|
"--desc": true,
|
|
@@ -6270,7 +1872,7 @@ function handlePrepareLifecycleError(args, error) {
|
|
|
6270
1872
|
//#endregion
|
|
6271
1873
|
//#region src/cli.ts
|
|
6272
1874
|
const cli = cac("weapp-vite");
|
|
6273
|
-
cli.option("-c, --config <file>", `[string] use specified config file`).option("--base <path>", `[string] public base path (default: /)`, { type: [convertBase] }).option("-l, --logLevel <level>", `[string] info | warn | error | silent`).option("--clearScreen", `[boolean] allow/disable clear screen when logging`).option("-d, --debug [feat]", `[string | boolean] show debug logs`).option("-f, --filter <filter>", `[string] filter debug logs`).option("-m, --mode <mode>", `[string] set env mode`);
|
|
1875
|
+
cli.option("-c, --config <file>", `[string] use specified config file`).option("--base <path>", `[string] public base path (default: /)`, { type: [convertBase] }).option("-l, --logLevel <level>", `[string] info | warn | error | silent`).option("--clearScreen", `[boolean] allow/disable clear screen when logging`).option("-d, --debug [feat]", `[string | boolean] show debug logs`).option("-f, --filter <filter>", `[string] filter debug logs`).option("-m, --mode <mode>", `[string] set env mode`).option("--devtools-channel <channel>", "select stable | rc | nightly").option("--devtools-version <version>", "select DevTools product version").option("--cli-path <path>", "select DevTools CLI installation").option("--require-official-latest", "require the official latest channel version");
|
|
6274
1876
|
registerAcceptCommand(cli);
|
|
6275
1877
|
registerIdeCommand(cli);
|
|
6276
1878
|
registerDoctorCommand(cli);
|
|
@@ -6328,22 +1930,24 @@ function resolveManagedTsconfigBootstrapRoot(args) {
|
|
|
6328
1930
|
return path.resolve(firstArg);
|
|
6329
1931
|
}
|
|
6330
1932
|
try {
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
1933
|
+
await withWechatDevtoolsSelectionArgs(process.argv.slice(2), async (args) => {
|
|
1934
|
+
if (!await tryRunIdeCommand(args)) {
|
|
1935
|
+
cli.parse([...process.argv.slice(0, 2), ...args], { run: false });
|
|
1936
|
+
const runCommand = async () => {
|
|
1937
|
+
const uiHost = Array.isArray(cli.options.uiHost) ? cli.options.uiHost.at(-1) : cli.options.uiHost;
|
|
1938
|
+
parseDashboardUiHost(uiHost);
|
|
1939
|
+
const managedTsconfigBootstrapRoot = resolveManagedTsconfigBootstrapRoot(args);
|
|
1940
|
+
if (managedTsconfigBootstrapRoot) await syncManagedTsconfigBootstrapFiles(managedTsconfigBootstrapRoot);
|
|
1941
|
+
if (cli.matchedCommand?.name === "" && cli.matchedCommandName === void 0) await maybeAutoStartMcpServer(args, cli.options);
|
|
1942
|
+
return cli.runMatchedCommand();
|
|
1943
|
+
};
|
|
1944
|
+
if (cli.matchedCommand?.name === "upload") {
|
|
1945
|
+
const json = Array.isArray(cli.options.json) ? cli.options.json.at(-1) : cli.options.json;
|
|
1946
|
+
await outputUploadReport(json === true, runCommand);
|
|
1947
|
+
scheduleCompletedProductionBuildExit({}, void 0);
|
|
1948
|
+
} else await runCommand();
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
6347
1951
|
} catch (error) {
|
|
6348
1952
|
if (!handlePrepareLifecycleError(process.argv.slice(2), error)) {
|
|
6349
1953
|
handleCLIError(error);
|