weapp-vite 7.3.0 → 7.5.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 +154 -6
- package/bin/bootstrap.js +14 -1
- package/dist/adapters-CcHrsYaj.mjs +1 -0
- package/dist/analyze-ZJUvEdaY.mjs +248 -0
- package/dist/auto-import-components/resolvers.mjs +1 -2
- package/dist/auto-import-presets/wevu-router.mjs +1 -2
- package/dist/auto-import-presets/wevu.mjs +1 -2
- package/dist/auto-routes.mjs +2 -1
- package/dist/budget-BD7VqLCl.d.mts +178 -0
- package/dist/chunkStrategy-DRA5WpB-.mjs +1436 -0
- package/dist/cli-acceptance.d.mts +1 -0
- package/dist/cli-acceptance.mjs +21 -0
- package/dist/cli.mjs +1918 -2089
- package/dist/compatibility.mjs +1 -2
- package/dist/{config-1J4rToMR.d.mts → config-V0L-IAJf.d.mts} +419 -39
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +1 -1
- package/dist/constants-DEZdcsMn.mjs +85 -0
- package/dist/{createContext-cqr9Y4EV.mjs → createCompilerContextInstance-DJH_P_nZ.mjs} +17602 -15886
- package/dist/createContext-BqYzHhY9.mjs +12 -0
- package/dist/dashboard/analyze.d.mts +48 -0
- package/dist/dashboard/analyze.mjs +1 -0
- package/dist/dashboard/vite.d.mts +9 -0
- package/dist/dashboard/vite.mjs +44 -0
- package/dist/dashboard-DvqF3Key.mjs +1842 -0
- package/dist/dashboard.d.mts +3 -0
- package/dist/dashboard.mjs +1 -0
- package/dist/dashboardViteBridge-Dy3nAD4m.mjs +299 -0
- package/dist/dev-fR5GiJRO.mjs +63 -0
- package/dist/devProvider-BGBkw28J.mjs +1050 -0
- package/dist/docs/README.md +154 -6
- package/dist/docs/ai-workflows.md +4 -0
- package/dist/docs/doctor.md +38 -0
- package/dist/docs/getting-started.md +52 -1
- package/dist/docs/index.md +6 -2
- package/dist/docs/mcp.md +94 -1
- package/dist/docs/testing.md +29 -2
- package/dist/docs/troubleshooting.md +8 -0
- package/dist/docs/uni-app-component-libraries.md +8 -7
- package/dist/docs/upload.md +226 -0
- package/dist/docs/vite-plugin.md +171 -0
- package/dist/docs/vue-sfc.md +19 -0
- package/dist/docs/weapp-config.md +374 -2
- package/dist/docs/wevu-authoring.md +10 -0
- package/dist/doctor-worker.d.mts +1 -0
- package/dist/doctor-worker.mjs +59 -0
- package/dist/doctor.d.mts +144 -0
- package/dist/doctor.mjs +848 -0
- package/dist/eslint.mjs +1 -2
- package/dist/file-DRb4eWqw.mjs +1 -0
- package/dist/{file-CKB0Tn8Z.mjs → file-sC3jD7Fh.mjs} +16 -93
- package/dist/files-BqxFIPRp.mjs +45 -0
- package/dist/getInstance-DiGSzBNI.mjs +28 -0
- package/dist/getInstance-SiE6WZRh.mjs +1 -0
- package/dist/index-C9HjUX5c.d.mts +741 -0
- package/dist/index.d.mts +10 -8
- package/dist/index.mjs +3 -2
- package/dist/json.d.mts +1 -1
- package/dist/json.mjs +1 -2
- package/dist/lifecycle-BROTlwSL.mjs +320 -0
- package/dist/loadConfig-C3BQhz7F.mjs +59 -0
- package/dist/loadConfig-VGtBF4qm.mjs +1 -0
- package/dist/logger-DGn5iEVN.mjs +1 -0
- package/dist/{mcp-BG6TliEg.mjs → mcp-Cj8Maqo6.mjs} +16 -7
- package/dist/mcp-D3Yh24Cj.mjs +529 -0
- package/dist/mcp.d.mts +6 -3
- package/dist/mcp.mjs +1 -2
- package/dist/package-7swhyLmf.mjs +91 -0
- package/dist/path-doV773b7.mjs +103 -0
- package/dist/platform-CnkySBKz.mjs +175 -0
- package/dist/processCleanup-B6KA30Zc.mjs +77 -0
- package/dist/protocol-vg8HGJ4n.mjs +138 -0
- package/dist/{resolvers-icNxkpcy.mjs → resolvers-Dod-euqX.mjs} +4 -1
- package/dist/resolvers.mjs +1 -2
- package/dist/runtime.mjs +1 -2
- package/dist/session-PskhD-nZ.mjs +390 -0
- package/dist/test.d.mts +1 -0
- package/dist/test.mjs +201 -44
- package/dist/types.d.mts +2 -2
- package/dist/upload-D4msblBf.mjs +562 -0
- package/dist/upload-worker.d.mts +1 -0
- package/dist/upload-worker.mjs +161 -0
- package/dist/utils-CJSCgEj6.mjs +1094 -0
- package/dist/vite.d.mts +9 -0
- package/dist/vite.mjs +270 -0
- package/dist/volar.d.mts +2 -2
- package/dist/web-apis.mjs +1 -2
- package/package.json +85 -37
- package/dist/file-CvgkbGIx.mjs +0 -2
- package/dist/getInstance-CZ1s63zd.mjs +0 -2
- package/dist/logger-mt4mSTqV.mjs +0 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,35 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
1
|
+
import { a as resolveWeappViteHostMeta } from "./pluginHost--CaeyWpA.mjs";
|
|
2
|
+
import { a as getDevShutdownScope, i as createDevShutdownScope, o as getDevServerCloseRecord, s as replaceDevServerClose } from "./lifecycle-BROTlwSL.mjs";
|
|
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";
|
|
7
|
+
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
|
+
import { r as logger_default, t as colors } from "./logger-DGn5iEVN.mjs";
|
|
10
|
+
import { t as createCompilerContext } from "./createContext-BqYzHhY9.mjs";
|
|
11
|
+
import { a as filterDuplicateOptions, c as resolveConfigFile, i as convertBase, l as registerAcceptCommand, n as formatMcpQuickStart, o as isUiEnabled, r as coerceBooleanOption, s as parseDashboardUiHost, t as registerMcpCommand } from "./mcp-D3Yh24Cj.mjs";
|
|
12
|
+
import { a as isRuntimeCategory, i as createAnalyzeBudgetCheck, r as createAnalyzeComparison, t as createDuplicateModuleInsights } from "./analyze-ZJUvEdaY.mjs";
|
|
13
|
+
import { i as resolveAnalyzeHistoryMetadata, n as createAnalyzeMetadata, t as terminateStaleSassEmbeddedProcess } from "./processCleanup-B6KA30Zc.mjs";
|
|
14
|
+
import { a as resolveModuleSourceType, i as resolveAssetSource, n as classifyPackage, r as normalizeModuleId, t as version } from "./package-7swhyLmf.mjs";
|
|
15
|
+
import { a as createDashboardArtifactSnapshot, i as resolveDashboardRoot, n as ANALYZE_DASHBOARD_PACKAGE_NAME, t as createAnalyzeDashboardDevframe } from "./dashboard-DvqF3Key.mjs";
|
|
16
|
+
import { a as terminateUploadProcess, i as terminateUploadDescendants, n as resolveUploadPlatforms, r as redactUploadSecrets, t as prepareUpload } from "./upload-D4msblBf.mjs";
|
|
17
|
+
import { t as loadConfig } from "./loadConfig-C3BQhz7F.mjs";
|
|
18
|
+
import { c as startWeappViteMcpServer, l as detectAiDevelopmentEnvironment, s as resolveWeappMcpConfig } from "./mcp-Cj8Maqo6.mjs";
|
|
19
|
+
import process from "node:process";
|
|
7
20
|
import path, { posix } from "pathe";
|
|
21
|
+
import fs, { readFileSync } from "node:fs";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
8
23
|
import path$1 from "node:path";
|
|
9
24
|
import { defu } from "@weapp-core/shared";
|
|
10
25
|
import { fs as fs$1 } from "@weapp-core/shared/fs";
|
|
11
|
-
import process from "node:process";
|
|
12
26
|
import { parse } from "vue/compiler-sfc";
|
|
13
|
-
import fs$2, { mkdir, writeFile } from "node:fs/promises";
|
|
14
|
-
import { build
|
|
15
|
-
import { createHash } from "node:crypto";
|
|
16
|
-
import {
|
|
17
|
-
import os from "node:os";
|
|
18
|
-
import { execFile, spawn } from "node:child_process";
|
|
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";
|
|
19
31
|
import { Buffer } from "node:buffer";
|
|
32
|
+
import { fork, spawn } from "node:child_process";
|
|
20
33
|
import { cac } from "cac";
|
|
21
|
-
import { RETRY_CANCEL_KEYS, RETRY_CONFIRM_KEYS, bootstrapWechatDevtoolsSettings, buildWechatIdeNpm, clearWechatIdeCache, clearWechatIdeCacheByAutomator, closeSharedMiniProgram, closeWechatIdeProject, compileWechatIdeByAutomator, connectOpenedAutomator, createSharedInputSession, createWechatIdeLoginRequiredExitError, defaultCustomConfigFilePath, detectWechatDevtoolsServicePort, dispatchWechatCliCommand, formatAutomatorLoginError,
|
|
34
|
+
import { RETRY_CANCEL_KEYS, RETRY_CONFIRM_KEYS, bootstrapWechatDevtoolsSettings, buildWechatIdeNpm, clearWechatIdeCache, clearWechatIdeCacheByAutomator, closeSharedMiniProgram, closeWechatIdeProject, compileWechatIdeByAutomator, connectOpenedAutomator, createSharedInputSession, createWechatIdeLoginRequiredExitError, defaultCustomConfigFilePath, detectWechatDevtoolsServicePort, dispatchWechatCliCommand, formatAutomatorLoginError, getWechatIdeTestAccounts, getWechatIdeTicket, getWechatIdeToolInfo, isAutomatorLoginError, isRetryableAutomatorLaunchError, isWeappIdeTopLevelCommand, isWechatIdeEngineBuildEndpointMissingError, isWechatIdeLoginRequiredError, isWechatIdeLoginRequiredExitError, launchAutomator, parse as parse$1, promptRetryKeypress, promptWechatIdeLoginRetry, queryWechatIdeLogin, quitWechatIde, refreshWechatIdeTicket, resetWechatIdeFileUtilsByHttp, resolveCliPath, resolveProjectAutomatorPort, runRetryableCommand, runWechatIdeEngineBuild, runWithSuspendedSharedInput, setWechatIdeTicket, startForwardConsole, takeScreenshot } from "weapp-ide-cli";
|
|
22
35
|
import { brotliCompressSync, gzipSync } from "node:zlib";
|
|
23
36
|
import { buildOtpAuthUrl, refreshTempAuthCode } from "devframe/node/auth";
|
|
24
37
|
import { resolveCommand } from "package-manager-detector/commands";
|
|
25
|
-
import
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
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";
|
|
28
42
|
import { generateJs, generateJson, generateWxml, generateWxss } from "@weapp-core/schematics";
|
|
29
43
|
import { determineAgent } from "@vercel/detect-agent";
|
|
30
44
|
import { initConfig } from "@weapp-core/init";
|
|
31
|
-
import { createInterface } from "node:readline/promises";
|
|
32
|
-
import { clearTimeout as clearTimeout$1, setTimeout as setTimeout$1 } from "node:timers";
|
|
33
45
|
import net from "node:net";
|
|
34
46
|
//#region src/cli/runtime.ts
|
|
35
47
|
function logRuntimeTarget(targets, options = {}) {
|
|
@@ -55,6 +67,9 @@ function resolveRuntimeTargets(options) {
|
|
|
55
67
|
rawPlatform
|
|
56
68
|
};
|
|
57
69
|
}
|
|
70
|
+
function resolveConfiguredRuntimeTargets(targets, platform) {
|
|
71
|
+
return targets.rawPlatform === void 0 && platform === "web" ? resolveRuntimeTargets({ platform: "web" }) : targets;
|
|
72
|
+
}
|
|
58
73
|
function createInlineConfig(execution, options = {}) {
|
|
59
74
|
const configs = execution.entries.map((entry) => entry.driver.createInlineConfig({
|
|
60
75
|
execution,
|
|
@@ -198,33 +213,10 @@ async function executeWechatIdeCliCommand(argv, options = {}) {
|
|
|
198
213
|
}
|
|
199
214
|
//#endregion
|
|
200
215
|
//#region src/cli/openIde/close.ts
|
|
201
|
-
const execFileAsync = promisify(execFile);
|
|
202
|
-
async function closeIdeByAppleScript() {
|
|
203
|
-
if (process.platform !== "darwin") return false;
|
|
204
|
-
const appName = process.env.WEAPP_DEVTOOLS_APP_NAME || "wechatwebdevtools";
|
|
205
|
-
try {
|
|
206
|
-
await execFileAsync("osascript", ["-e", `tell application "${appName}" to quit`]);
|
|
207
|
-
return true;
|
|
208
|
-
} catch {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
async function closeIdeByProcessKill(cliPath) {
|
|
213
|
-
if (!cliPath) return false;
|
|
214
|
-
const appContentsRoot = cliPath.includes(".app/") ? cliPath.slice(0, cliPath.indexOf(".app/") + 4) : path.dirname(path.dirname(cliPath));
|
|
215
|
-
try {
|
|
216
|
-
await execFileAsync("pkill", ["-f", appContentsRoot]);
|
|
217
|
-
return true;
|
|
218
|
-
} catch {
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
216
|
/**
|
|
223
|
-
* @description
|
|
217
|
+
* @description 执行用户显式请求的 CLI close;失败时保留宿主,不升级为系统级退出。
|
|
224
218
|
*/
|
|
225
219
|
async function closeIde$1() {
|
|
226
|
-
const config = await getConfig();
|
|
227
|
-
const cliPath = config.cliPath?.trim() ? config.cliPath : null;
|
|
228
220
|
try {
|
|
229
221
|
await closeWechatIdeProject();
|
|
230
222
|
return true;
|
|
@@ -240,17 +232,9 @@ async function closeIde$1() {
|
|
|
240
232
|
logger_default.error(retryError);
|
|
241
233
|
}
|
|
242
234
|
else {
|
|
243
|
-
logger_default.warn("微信开发者工具 CLI close
|
|
235
|
+
logger_default.warn("微信开发者工具 CLI close 执行失败,已保留现有宿主;请在开发者工具中手动关闭目标项目。");
|
|
244
236
|
logger_default.error(error);
|
|
245
237
|
}
|
|
246
|
-
if (await closeIdeByAppleScript()) {
|
|
247
|
-
logger_default.info("已回退为系统级关闭微信开发者工具。");
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
if (await closeIdeByProcessKill(cliPath)) {
|
|
251
|
-
logger_default.info("已回退为进程级关闭微信开发者工具。");
|
|
252
|
-
return true;
|
|
253
|
-
}
|
|
254
238
|
return false;
|
|
255
239
|
}
|
|
256
240
|
}
|
|
@@ -299,7 +283,7 @@ function logWechatIdeServicePortDisabledHint(projectPath) {
|
|
|
299
283
|
const OPENED_PROJECT_HEALTH_CHECK_TIMEOUT = 3e3;
|
|
300
284
|
const OPEN_AUTOMATOR_TIMEOUT = 12e4;
|
|
301
285
|
function formatReuseOpenedWechatIdePrompt() {
|
|
302
|
-
return `目标项目已在微信开发者工具中打开,已跳过重复打开。按 ${RETRY_CONFIRM_KEYS.map((key) => colors.bold(colors.green(key))).join(" / ")}
|
|
286
|
+
return `目标项目已在微信开发者工具中打开,已跳过重复打开。按 ${RETRY_CONFIRM_KEYS.map((key) => colors.bold(colors.green(key))).join(" / ")} 重新连接目标项目(保留现有窗口)。`;
|
|
303
287
|
}
|
|
304
288
|
function disconnectMiniProgram(miniProgram) {
|
|
305
289
|
miniProgram.disconnect();
|
|
@@ -354,7 +338,7 @@ async function connectOpenedProject(projectPath) {
|
|
|
354
338
|
/**
|
|
355
339
|
* @description 若当前项目已在微信开发者工具中打开且自动化可连通,则直接复用现有会话,避免重复拉起 IDE。
|
|
356
340
|
*/
|
|
357
|
-
async function tryReuseOpenedWechatIde(projectPath,
|
|
341
|
+
async function tryReuseOpenedWechatIde(projectPath, options = {}) {
|
|
358
342
|
const connection = await connectOpenedProject(projectPath);
|
|
359
343
|
if (connection.status === "unhealthy") return {
|
|
360
344
|
reopened: false,
|
|
@@ -376,8 +360,7 @@ async function tryReuseOpenedWechatIde(projectPath, closeIde, options = {}) {
|
|
|
376
360
|
reopened: false,
|
|
377
361
|
reused: true
|
|
378
362
|
};
|
|
379
|
-
logger_default.info(colors.bold(colors.green("
|
|
380
|
-
if (!await closeIde()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。");
|
|
363
|
+
logger_default.info(colors.bold(colors.green("正在重新连接目标项目,保留现有微信开发者工具窗口...")));
|
|
381
364
|
await openWechatIdeByAutomator(projectPath, {
|
|
382
365
|
preserveProjectRoot: options.preserveProjectRoot,
|
|
383
366
|
trustProject: options.trustProject
|
|
@@ -388,16 +371,15 @@ async function tryReuseOpenedWechatIde(projectPath, closeIde, options = {}) {
|
|
|
388
371
|
};
|
|
389
372
|
}
|
|
390
373
|
/**
|
|
391
|
-
* @description
|
|
374
|
+
* @description 重新连接目标项目以刷新构建产物;连接失败不授予关闭共享宿主的权限。
|
|
392
375
|
*/
|
|
393
|
-
async function reopenOpenedWechatIde(projectPath,
|
|
376
|
+
async function reopenOpenedWechatIde(projectPath, options = {}) {
|
|
394
377
|
const connection = await connectOpenedProject(projectPath);
|
|
395
378
|
if (connection.status === "connected") {
|
|
396
379
|
disconnectMiniProgram(connection.miniProgram);
|
|
397
|
-
logger_default.info("
|
|
398
|
-
} else if (connection.status === "unhealthy") logger_default.info("目标项目的 automator
|
|
399
|
-
else logger_default.info("未检测到可复用的 automator
|
|
400
|
-
if (!await (connection.status === "connected" ? closeIde : options.restartIde ?? closeIde)()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。");
|
|
380
|
+
logger_default.info("目标项目已在微信开发者工具中打开,当前命令将重新连接以刷新最新构建产物,保留现有窗口。");
|
|
381
|
+
} else if (connection.status === "unhealthy") logger_default.info("目标项目的 automator 会话未通过健康检查,将重试连接目标项目,保留现有窗口。");
|
|
382
|
+
else logger_default.info("未检测到可复用的 automator 会话,将尝试打开目标项目,保留其他窗口。");
|
|
401
383
|
await openWechatIdeByAutomator(projectPath, options);
|
|
402
384
|
return true;
|
|
403
385
|
}
|
|
@@ -408,11 +390,7 @@ function shouldLogAutomatorFallbackError() {
|
|
|
408
390
|
return flag === "1" || flag === "true";
|
|
409
391
|
}
|
|
410
392
|
const PREPARE_AUTOMATOR_SESSION_TIMEOUT = 8e3;
|
|
411
|
-
const AUTOMATOR_RECOVERY_QUIT_SETTLE_MS = 5e3;
|
|
412
393
|
const RESET_FILEUTILS_ENV = "WEAPP_VITE_RESET_IDE_FILEUTILS";
|
|
413
|
-
function sleep$1(ms) {
|
|
414
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
415
|
-
}
|
|
416
394
|
function readProjectConfigObject(projectPath) {
|
|
417
395
|
try {
|
|
418
396
|
const raw = fs.readFileSync(path.join(projectPath, "project.config.json"), "utf8");
|
|
@@ -441,7 +419,10 @@ function shouldResetWechatIdeFileUtils() {
|
|
|
441
419
|
async function runWechatIdeOpenWithRetry(argv) {
|
|
442
420
|
await executeWechatIdeCliCommand(argv, {
|
|
443
421
|
cancelLevel: "warn",
|
|
444
|
-
onNonLoginError: (error) =>
|
|
422
|
+
onNonLoginError: (error) => {
|
|
423
|
+
logger_default.error(error);
|
|
424
|
+
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL?.trim()) throw error;
|
|
425
|
+
},
|
|
445
426
|
onRetry: () => {
|
|
446
427
|
logger_default.info(colors.bold(colors.green("正在重试连接微信开发者工具...")));
|
|
447
428
|
},
|
|
@@ -449,10 +430,11 @@ async function runWechatIdeOpenWithRetry(argv) {
|
|
|
449
430
|
});
|
|
450
431
|
}
|
|
451
432
|
/**
|
|
452
|
-
* @description
|
|
433
|
+
* @description 根据代码输出目录推导 IDE 项目目录;内联项目配置与 app.json 同目录。
|
|
453
434
|
*/
|
|
454
|
-
function resolveIdeProjectPath(mpDistRoot) {
|
|
435
|
+
function resolveIdeProjectPath(mpDistRoot, generatedProjectConfig = false) {
|
|
455
436
|
if (!mpDistRoot || !mpDistRoot.trim()) return;
|
|
437
|
+
if (generatedProjectConfig) return mpDistRoot;
|
|
456
438
|
const parent = path.dirname(mpDistRoot);
|
|
457
439
|
if (!parent || parent === "." || parent === "/") return;
|
|
458
440
|
return parent;
|
|
@@ -460,31 +442,22 @@ function resolveIdeProjectPath(mpDistRoot) {
|
|
|
460
442
|
/**
|
|
461
443
|
* @description 结合 mpDistRoot 与配置根目录解析最终 IDE 项目目录。
|
|
462
444
|
*/
|
|
463
|
-
function resolveIdeProjectRoot(mpDistRoot, cwd) {
|
|
464
|
-
|
|
445
|
+
function resolveIdeProjectRoot(mpDistRoot, cwd, generatedProjectConfig = false) {
|
|
446
|
+
const projectPath = resolveIdeProjectPath(mpDistRoot, generatedProjectConfig);
|
|
447
|
+
return generatedProjectConfig && projectPath && cwd ? path.resolve(cwd, projectPath) : projectPath ?? cwd;
|
|
465
448
|
}
|
|
466
449
|
async function closeIde() {
|
|
467
450
|
return await closeIde$1();
|
|
468
451
|
}
|
|
469
|
-
async function restartWechatIdeForAutomatorRecovery() {
|
|
470
|
-
try {
|
|
471
|
-
await quitWechatIde();
|
|
472
|
-
await sleep$1(AUTOMATOR_RECOVERY_QUIT_SETTLE_MS);
|
|
473
|
-
return true;
|
|
474
|
-
} catch {
|
|
475
|
-
return await closeIde();
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
452
|
async function tryOpenWechatIdeByAutomator(projectPath, options) {
|
|
479
453
|
const preserveProjectRoot = !shouldUseAutomatorProjectWrapper(projectPath);
|
|
480
454
|
if (options.reuseOpenedProject === false) {
|
|
481
|
-
if (await reopenOpenedWechatIde(projectPath,
|
|
455
|
+
if (await reopenOpenedWechatIde(projectPath, {
|
|
482
456
|
preserveProjectRoot,
|
|
483
|
-
restartIde: restartWechatIdeForAutomatorRecovery,
|
|
484
457
|
trustProject: options.trustProject
|
|
485
458
|
})) return "reopened";
|
|
486
459
|
}
|
|
487
|
-
const reuseResult = await tryReuseOpenedWechatIde(projectPath,
|
|
460
|
+
const reuseResult = await tryReuseOpenedWechatIde(projectPath, {
|
|
488
461
|
preserveProjectRoot,
|
|
489
462
|
promptReopen: options.reuseOpenedProject !== true,
|
|
490
463
|
trustProject: options.trustProject
|
|
@@ -649,12 +622,11 @@ async function recoverOpenedWechatIdeProject(platform, projectPath, servicePortE
|
|
|
649
622
|
logger_default.warn("已跳过微信开发者工具自动恢复;请按上方提示手动关闭并重新打开目标项目。");
|
|
650
623
|
return failedResult;
|
|
651
624
|
}
|
|
652
|
-
logger_default.info(`检测到微信开发者工具打开后状态不稳定(${formatWechatIdeOpenHealthReason(failedResult)}
|
|
653
|
-
if (!await closeIde()) logger_default.warn("自动恢复时关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。");
|
|
625
|
+
logger_default.info(`检测到微信开发者工具打开后状态不稳定(${formatWechatIdeOpenHealthReason(failedResult)}),正在重试打开目标项目(保留现有窗口)...`);
|
|
654
626
|
await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, options));
|
|
655
627
|
const recoveredResult = await verifyOpenedWechatIdeProject(projectPath, servicePortEnabled, options);
|
|
656
628
|
if (recoveredResult.ok) logger_default.info("微信开发者工具已完成自动恢复。");
|
|
657
|
-
else logger_default.warn("微信开发者工具自动恢复未完成;可设置 `WEAPP_VITE_DISABLE_IDE_OPEN_RECOVERY=1` 或传入 `--no-open-recovery`
|
|
629
|
+
else logger_default.warn("微信开发者工具自动恢复未完成;可设置 `WEAPP_VITE_DISABLE_IDE_OPEN_RECOVERY=1` 或传入 `--no-open-recovery` 跳过自动重试,并按提示手动处理。");
|
|
658
630
|
return recoveredResult;
|
|
659
631
|
}
|
|
660
632
|
async function verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, servicePortEnabled, options) {
|
|
@@ -691,6 +663,7 @@ async function openIde(platform, projectPath, options = {}) {
|
|
|
691
663
|
return;
|
|
692
664
|
}
|
|
693
665
|
} catch (error) {
|
|
666
|
+
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL?.trim()) throw error;
|
|
694
667
|
if (isAutomatorLoginError(error)) {
|
|
695
668
|
logger_default.error("检测到微信开发者工具登录状态失效,请先登录后重试。");
|
|
696
669
|
logger_default.warn(formatAutomatorLoginError(error));
|
|
@@ -698,9 +671,6 @@ async function openIde(platform, projectPath, options = {}) {
|
|
|
698
671
|
logger_default.warn(`通过 automator 启动微信开发者工具失败,回退到普通 open 流程:${formatWechatIdeAutomatorFailure(error)}`);
|
|
699
672
|
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
700
673
|
}
|
|
701
|
-
else if (platform === "weapp" && projectPath && normalizedOptions.reuseOpenedProject === false) {
|
|
702
|
-
if (!await closeIde()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试打开目标项目。");
|
|
703
|
-
}
|
|
704
674
|
await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, normalizedOptions));
|
|
705
675
|
if (platform === "weapp" && projectPath && normalizedOptions.prepareAutomatorSession) await prepareWechatIdeAutomatorSession(projectPath, normalizedOptions);
|
|
706
676
|
if (platform === "weapp" && projectPath && !normalizedOptions.skipPostOpenHealthCheck) await verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, bootstrapResult?.servicePortEnabled, normalizedOptions);
|
|
@@ -722,7 +692,7 @@ async function resolveIdeCommandContext(options) {
|
|
|
722
692
|
cliPlatform: options.cliPlatform
|
|
723
693
|
});
|
|
724
694
|
platform ??= ctx.configService.platform;
|
|
725
|
-
if (!projectPath) projectPath = resolveIdeProjectRoot(ctx.configService.mpDistRoot, ctx.configService.cwd);
|
|
695
|
+
if (!projectPath) projectPath = resolveIdeProjectRoot(ctx.configService.mpDistRoot, ctx.configService.cwd, ctx.configService.multiPlatform.projectConfigs !== void 0);
|
|
726
696
|
return {
|
|
727
697
|
cwd: ctx.configService.cwd,
|
|
728
698
|
platform,
|
|
@@ -742,35 +712,6 @@ async function resolveIdeCommandContext(options) {
|
|
|
742
712
|
};
|
|
743
713
|
}
|
|
744
714
|
//#endregion
|
|
745
|
-
//#region src/cli/options.ts
|
|
746
|
-
function filterDuplicateOptions(options) {
|
|
747
|
-
for (const [key, value] of Object.entries(options)) if (Array.isArray(value)) options[key] = value[value.length - 1];
|
|
748
|
-
}
|
|
749
|
-
function resolveConfigFile(options) {
|
|
750
|
-
if (typeof options.config === "string") return options.config;
|
|
751
|
-
if (typeof options.c === "string") return options.c;
|
|
752
|
-
}
|
|
753
|
-
function convertBase(value) {
|
|
754
|
-
if (value === 0) return "";
|
|
755
|
-
return value;
|
|
756
|
-
}
|
|
757
|
-
function coerceBooleanOption(value) {
|
|
758
|
-
if (value === void 0) return;
|
|
759
|
-
if (typeof value === "boolean") return value;
|
|
760
|
-
if (typeof value === "string") {
|
|
761
|
-
const normalized = value.trim().toLowerCase();
|
|
762
|
-
if (normalized === "") return true;
|
|
763
|
-
if (normalized === "false" || normalized === "0" || normalized === "off" || normalized === "no") return false;
|
|
764
|
-
if (normalized === "true" || normalized === "1" || normalized === "on" || normalized === "yes") return true;
|
|
765
|
-
return true;
|
|
766
|
-
}
|
|
767
|
-
if (typeof value === "number") return value !== 0;
|
|
768
|
-
return Boolean(value);
|
|
769
|
-
}
|
|
770
|
-
function isUiEnabled(options) {
|
|
771
|
-
return Boolean(options.ui || options.analyze);
|
|
772
|
-
}
|
|
773
|
-
//#endregion
|
|
774
715
|
//#region src/cli/commands/alipayExecute.ts
|
|
775
716
|
function createSpawnOptions() {
|
|
776
717
|
return {
|
|
@@ -856,6 +797,154 @@ function registerAlipayCommand(cli) {
|
|
|
856
797
|
});
|
|
857
798
|
}
|
|
858
799
|
//#endregion
|
|
800
|
+
//#region src/analyze/hmr/attribution.ts
|
|
801
|
+
const nonnegative = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
802
|
+
function difference(whole, ...parts) {
|
|
803
|
+
return nonnegative(whole) && parts.every(nonnegative) && whole >= parts.reduce((sum, value) => sum + value, 0) ? whole - parts.reduce((sum, value) => sum + value, 0) : null;
|
|
804
|
+
}
|
|
805
|
+
/** 外层时段互不重叠;插件 hook 属于 bundler 内部的嵌套观测,不能再次累加。 */
|
|
806
|
+
function attributeHmrProfile(sample, external) {
|
|
807
|
+
const value = (key) => nonnegative(sample[key]) ? sample[key] : null;
|
|
808
|
+
const phases = sample.pipeline === "stateful" ? Object.fromEntries((sample.profileMode === "delivery" ? [
|
|
809
|
+
"sourceToBatchMs",
|
|
810
|
+
"deliveryQueueMs",
|
|
811
|
+
"prepareMs",
|
|
812
|
+
"commitQueueMs",
|
|
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
|
|
859
948
|
//#region src/analyze/hmr.ts
|
|
860
949
|
function createMetricSummary(values) {
|
|
861
950
|
if (!values.length) return { count: 0 };
|
|
@@ -894,26 +983,19 @@ function isFiniteNumber$1(value) {
|
|
|
894
983
|
* @description 聚合 HMR JSONL profile,为命令行与后续仪表盘复用。
|
|
895
984
|
*/
|
|
896
985
|
async function analyzeHmrProfile(options) {
|
|
897
|
-
const
|
|
898
|
-
const samples = [];
|
|
899
|
-
let skippedLineCount = 0;
|
|
900
|
-
for (const line of lines) {
|
|
901
|
-
const trimmed = line.trim();
|
|
902
|
-
if (!trimmed) continue;
|
|
903
|
-
try {
|
|
904
|
-
const parsed = JSON.parse(trimmed);
|
|
905
|
-
if (!isFiniteNumber$1(parsed.totalMs)) {
|
|
906
|
-
skippedLineCount += 1;
|
|
907
|
-
continue;
|
|
908
|
-
}
|
|
909
|
-
samples.push(parsed);
|
|
910
|
-
} catch {
|
|
911
|
-
skippedLineCount += 1;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
986
|
+
const { samples, coverage: inputCoverage, skippedLineCount } = readHmrProfileLines(await fs$1.readFile(options.profilePath, "utf8"));
|
|
914
987
|
const eventCounts = /* @__PURE__ */ new Map();
|
|
915
988
|
const dirtyReasonCounts = /* @__PURE__ */ new Map();
|
|
916
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 = [];
|
|
917
999
|
const totalValues = [];
|
|
918
1000
|
const buildCoreValues = [];
|
|
919
1001
|
const buildStartValues = [];
|
|
@@ -963,6 +1045,15 @@ async function analyzeHmrProfile(options) {
|
|
|
963
1045
|
const resolveCountValues = [];
|
|
964
1046
|
const skippedLoadedCountValues = [];
|
|
965
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);
|
|
966
1057
|
totalValues.push(sample.totalMs);
|
|
967
1058
|
if (sample.event) eventCounts.set(sample.event, (eventCounts.get(sample.event) ?? 0) + 1);
|
|
968
1059
|
if (isFiniteNumber$1(sample.buildCoreMs)) buildCoreValues.push(sample.buildCoreMs);
|
|
@@ -1029,9 +1120,24 @@ async function analyzeHmrProfile(options) {
|
|
|
1029
1120
|
profilePath: options.profilePath,
|
|
1030
1121
|
sampleCount: samples.length,
|
|
1031
1122
|
skippedLineCount,
|
|
1123
|
+
inputCoverage,
|
|
1124
|
+
timelines: samples.map((sample) => ({
|
|
1125
|
+
batchId: sample.batchId,
|
|
1126
|
+
completionBoundary: sample.completionBoundary,
|
|
1127
|
+
...attributeHmrProfile(sample)
|
|
1128
|
+
})),
|
|
1032
1129
|
firstTimestamp: orderedByTime[0]?.timestamp,
|
|
1033
1130
|
lastTimestamp: orderedByTime[orderedByTime.length - 1]?.timestamp,
|
|
1034
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),
|
|
1035
1141
|
totalMs: createMetricSummary(totalValues),
|
|
1036
1142
|
buildCoreMs: createMetricSummary(buildCoreValues),
|
|
1037
1143
|
buildStartMs: createMetricSummary(buildStartValues),
|
|
@@ -1092,7 +1198,7 @@ async function analyzeHmrProfile(options) {
|
|
|
1092
1198
|
//#endregion
|
|
1093
1199
|
//#region src/analyze/preload/budget.ts
|
|
1094
1200
|
const PRELOAD_LIMIT_BYTES = 2097152;
|
|
1095
|
-
function createPackageSizeMap
|
|
1201
|
+
function createPackageSizeMap(result) {
|
|
1096
1202
|
return new Map((result?.packages ?? []).map((pkg) => [pkg.id, pkg.files.reduce((total, file) => total + (file.size ?? 0), 0)]));
|
|
1097
1203
|
}
|
|
1098
1204
|
function getConfiguredPackages(rule, appJson) {
|
|
@@ -1102,7 +1208,7 @@ function getConfiguredPackages(rule, appJson) {
|
|
|
1102
1208
|
return packages.flatMap((item) => typeof item === "string" ? [resolvePreloadPackageIdentifier(item, appJson)] : []).filter((item) => Boolean(item));
|
|
1103
1209
|
}
|
|
1104
1210
|
function createPreloadBudgets(appJson, result, configuredRules, packageAnalysis) {
|
|
1105
|
-
const packageSizes = createPackageSizeMap
|
|
1211
|
+
const packageSizes = createPackageSizeMap(packageAnalysis);
|
|
1106
1212
|
const bySourcePackage = /* @__PURE__ */ new Map();
|
|
1107
1213
|
const ensureSourcePackage = (page) => {
|
|
1108
1214
|
const sourcePackage = page.packageRoot ?? "__main__";
|
|
@@ -1431,140 +1537,100 @@ function analyzeComponentUsage(options) {
|
|
|
1431
1537
|
});
|
|
1432
1538
|
}
|
|
1433
1539
|
//#endregion
|
|
1434
|
-
//#region src/analyze/subpackages/
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
const
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
const packageFallback = resolveBudgetValue(legacyPackageBudget, defaultPackageBudgetBytes);
|
|
1450
|
-
return {
|
|
1451
|
-
totalBytes: resolveBudgetValue(budgets?.totalBytes, defaultTotalBudgetBytes),
|
|
1452
|
-
mainBytes: resolveBudgetValue(budgets?.mainBytes, packageFallback),
|
|
1453
|
-
subPackageBytes: resolveBudgetValue(budgets?.subPackageBytes, packageFallback),
|
|
1454
|
-
independentBytes: resolveBudgetValue(budgets?.independentBytes, packageFallback),
|
|
1455
|
-
warningRatio: resolveBudgetValue(budgets?.warningRatio, defaultWarningRatio),
|
|
1456
|
-
source: budgets ? "config" : "default"
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
|
-
function resolveAnalyzeHistoryMetadata(configService) {
|
|
1460
|
-
const history = configService.weappViteConfig.analyze?.history;
|
|
1461
|
-
if (history === false) return {
|
|
1462
|
-
enabled: false,
|
|
1463
|
-
dir: path.resolve(configService.cwd, defaultHistoryDir),
|
|
1464
|
-
limit: defaultHistoryLimit
|
|
1465
|
-
};
|
|
1466
|
-
const historyConfig = typeof history === "object" && history ? history : {};
|
|
1467
|
-
const rawDir = typeof historyConfig.dir === "string" && historyConfig.dir.trim() ? historyConfig.dir.trim() : defaultHistoryDir;
|
|
1468
|
-
return {
|
|
1469
|
-
enabled: historyConfig.enabled !== false,
|
|
1470
|
-
dir: path.isAbsolute(rawDir) ? rawDir : path.resolve(configService.cwd, rawDir),
|
|
1471
|
-
limit: resolveHistoryLimit(historyConfig.limit)
|
|
1472
|
-
};
|
|
1473
|
-
}
|
|
1474
|
-
function createAnalyzeMetadata(configService, now = /* @__PURE__ */ new Date()) {
|
|
1475
|
-
const history = resolveAnalyzeHistoryMetadata(configService);
|
|
1476
|
-
const packageName = configService.packageJson?.name;
|
|
1477
|
-
return {
|
|
1478
|
-
projectName: typeof packageName === "string" && packageName.trim() ? packageName.trim() : path.basename(configService.cwd),
|
|
1479
|
-
generatedAt: now.toISOString(),
|
|
1480
|
-
budgets: resolveAnalyzeBudgets(configService),
|
|
1481
|
-
history: {
|
|
1482
|
-
...history,
|
|
1483
|
-
dir: configService.relativeCwd(history.dir)
|
|
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;
|
|
1484
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;
|
|
1485
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";
|
|
1486
1576
|
}
|
|
1487
1577
|
//#endregion
|
|
1488
|
-
//#region src/analyze/subpackages/
|
|
1489
|
-
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
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
|
+
});
|
|
1527
1622
|
}
|
|
1623
|
+
const artifacts = [...files.values()].sort((a, b) => a.file.localeCompare(b.file));
|
|
1624
|
+
if (artifacts.length === 0) throw new Error("未生成可分析产物,不能把缺失输出计为零字节。");
|
|
1528
1625
|
return {
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
return posix.normalize(id);
|
|
1538
|
-
}
|
|
1539
|
-
function resolveModuleSourceType(absoluteId, ctx) {
|
|
1540
|
-
const { configService } = ctx;
|
|
1541
|
-
const isNodeModule = absoluteId.includes("/node_modules/") || absoluteId.includes("\\node_modules\\");
|
|
1542
|
-
const pluginRoot = configService.absolutePluginRoot;
|
|
1543
|
-
const srcRoot = configService.absoluteSrcRoot;
|
|
1544
|
-
const sourceType = classifyModuleSourceKind({
|
|
1545
|
-
isNodeModule,
|
|
1546
|
-
inSrc: isPathInside(srcRoot, absoluteId),
|
|
1547
|
-
inPlugin: pluginRoot ? isPathInside(pluginRoot, absoluteId) : false
|
|
1548
|
-
});
|
|
1549
|
-
return {
|
|
1550
|
-
source: configService.relativeAbsoluteSrcRoot(absoluteId),
|
|
1551
|
-
sourceType
|
|
1552
|
-
};
|
|
1553
|
-
}
|
|
1554
|
-
function resolveAssetSource(fileName, ctx) {
|
|
1555
|
-
const { configService } = ctx;
|
|
1556
|
-
const normalized = posix.normalize(fileName);
|
|
1557
|
-
const srcCandidate = posix.isAbsolute(normalized) ? normalized : posix.resolve(configService.absoluteSrcRoot, normalized);
|
|
1558
|
-
if (isPathInside(configService.absoluteSrcRoot, srcCandidate)) return {
|
|
1559
|
-
absolute: srcCandidate,
|
|
1560
|
-
source: configService.relativeAbsoluteSrcRoot(srcCandidate),
|
|
1561
|
-
sourceType: "src"
|
|
1562
|
-
};
|
|
1563
|
-
const pluginAbsolute = resolvePluginAssetAbsolute(normalized, configService.absolutePluginRoot);
|
|
1564
|
-
if (pluginAbsolute) return {
|
|
1565
|
-
absolute: pluginAbsolute,
|
|
1566
|
-
source: configService.relativeAbsoluteSrcRoot(pluginAbsolute),
|
|
1567
|
-
sourceType: "plugin"
|
|
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
|
+
}
|
|
1568
1634
|
};
|
|
1569
1635
|
}
|
|
1570
1636
|
//#endregion
|
|
@@ -1619,6 +1685,7 @@ function processChunk(chunk, origin, ctx, classifierContext, packages, modules)
|
|
|
1619
1685
|
type: "chunk",
|
|
1620
1686
|
from: origin,
|
|
1621
1687
|
size: typeof chunk.code === "string" ? Buffer.byteLength(chunk.code, "utf8") : void 0,
|
|
1688
|
+
sha256: createHash("sha256").update(chunk.code).digest("hex"),
|
|
1622
1689
|
...getCompressedSizes$1(chunk.code),
|
|
1623
1690
|
isEntry: chunk.isEntry,
|
|
1624
1691
|
modules: []
|
|
@@ -1626,10 +1693,15 @@ function processChunk(chunk, origin, ctx, classifierContext, packages, modules)
|
|
|
1626
1693
|
if (chunk.imports.length > 0) chunkEntry.imports = [...chunk.imports].sort((a, b) => a.localeCompare(b));
|
|
1627
1694
|
if (chunk.dynamicImports.length > 0) chunkEntry.dynamicImports = [...chunk.dynamicImports].sort((a, b) => a.localeCompare(b));
|
|
1628
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);
|
|
1629
1697
|
for (const [rawModuleId, info] of moduleEntries) {
|
|
1630
|
-
const
|
|
1698
|
+
const generatedRuntime = rawModuleId === "\0rolldown/runtime.js";
|
|
1699
|
+
const absoluteId = generatedRuntime ? rawModuleId : normalizeModuleId(rawModuleId);
|
|
1631
1700
|
if (!absoluteId) continue;
|
|
1632
|
-
const { source, sourceType } =
|
|
1701
|
+
const { source, sourceType } = generatedRuntime ? {
|
|
1702
|
+
source: "rolldown:runtime",
|
|
1703
|
+
sourceType: "workspace"
|
|
1704
|
+
} : resolveModuleSourceType(absoluteId, ctx);
|
|
1633
1705
|
const moduleEntry = {
|
|
1634
1706
|
id: absoluteId,
|
|
1635
1707
|
source,
|
|
@@ -1652,6 +1724,7 @@ function processAsset(asset, origin, ctx, classifierContext, packages, modules)
|
|
|
1652
1724
|
type: "asset",
|
|
1653
1725
|
from: origin,
|
|
1654
1726
|
size: assetBuffer?.byteLength,
|
|
1727
|
+
sha256: assetBuffer ? createHash("sha256").update(assetBuffer).digest("hex") : void 0,
|
|
1655
1728
|
...getCompressedSizes$1(assetBuffer)
|
|
1656
1729
|
};
|
|
1657
1730
|
let assetSource;
|
|
@@ -1672,7 +1745,16 @@ function processOutput(output, origin, ctx, classifierContext, packages, modules
|
|
|
1672
1745
|
processChunk(item, origin, ctx, classifierContext, packages, modules);
|
|
1673
1746
|
onArtifact?.(item.fileName, item.code);
|
|
1674
1747
|
} else if (item.type === "asset") {
|
|
1675
|
-
|
|
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);
|
|
1676
1758
|
onArtifact?.(item.fileName, item.source);
|
|
1677
1759
|
}
|
|
1678
1760
|
}
|
|
@@ -1777,7 +1859,7 @@ function summarizeSubPackages(metas) {
|
|
|
1777
1859
|
}
|
|
1778
1860
|
//#endregion
|
|
1779
1861
|
//#region src/analyze/subpackages/index.ts
|
|
1780
|
-
async function
|
|
1862
|
+
async function analyzeBuild(ctx, options) {
|
|
1781
1863
|
const { configService, scanService, buildService } = ctx;
|
|
1782
1864
|
if (!configService || !scanService || !buildService) throw new Error("analyzeSubpackages 需要先初始化 configService、scanService 和 buildService。");
|
|
1783
1865
|
await scanService.loadAppEntry();
|
|
@@ -1815,9 +1897,22 @@ async function analyzeSubpackages(ctx, options) {
|
|
|
1815
1897
|
}
|
|
1816
1898
|
expandVirtualModulePlacements(modules, packages, classifierContext);
|
|
1817
1899
|
const subPackages = summarizeSubPackages(subPackageMetas);
|
|
1818
|
-
|
|
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,
|
|
1819
1914
|
metadata: createAnalyzeMetadata(ctx.configService),
|
|
1820
|
-
packages:
|
|
1915
|
+
packages: packageReports,
|
|
1821
1916
|
modules: summarizeModules(modules),
|
|
1822
1917
|
subPackages,
|
|
1823
1918
|
components: analyzeComponentUsage({
|
|
@@ -1826,6 +1921,17 @@ async function analyzeSubpackages(ctx, options) {
|
|
|
1826
1921
|
}),
|
|
1827
1922
|
glassEasel: createGlassEaselAnalyzeResult(ctx)
|
|
1828
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
|
+
}
|
|
1829
1935
|
}
|
|
1830
1936
|
//#endregion
|
|
1831
1937
|
//#region src/analyze/subpackages/history.ts
|
|
@@ -1897,6 +2003,7 @@ function formatAnalyzeBytes(bytes) {
|
|
|
1897
2003
|
return `${value.toFixed(value >= 100 || unitIndex === 0 ? 0 : 2)} ${units[unitIndex]}`;
|
|
1898
2004
|
}
|
|
1899
2005
|
function formatDelta(bytes) {
|
|
2006
|
+
if (bytes === null) return "缺少体积测量";
|
|
1900
2007
|
if (typeof bytes !== "number" || Number.isNaN(bytes) || bytes === 0) return "无变化";
|
|
1901
2008
|
return `${bytes > 0 ? "+" : "-"}${formatAnalyzeBytes(Math.abs(bytes))}`;
|
|
1902
2009
|
}
|
|
@@ -1906,77 +2013,8 @@ function getFileSize(file) {
|
|
|
1906
2013
|
function getCompressedSize(file) {
|
|
1907
2014
|
return file.brotliSize ?? file.gzipSize ?? 0;
|
|
1908
2015
|
}
|
|
1909
|
-
function getBudgetLimit(type, budgets) {
|
|
1910
|
-
if (!budgets) return;
|
|
1911
|
-
if (type === "main") return budgets.mainBytes;
|
|
1912
|
-
if (type === "subPackage") return budgets.subPackageBytes;
|
|
1913
|
-
if (type === "independent") return budgets.independentBytes;
|
|
1914
|
-
}
|
|
1915
|
-
function createPackageSizeMap(result) {
|
|
1916
|
-
return new Map((result?.packages ?? []).map((pkg) => {
|
|
1917
|
-
const totalBytes = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
|
|
1918
|
-
return [pkg.id, totalBytes];
|
|
1919
|
-
}));
|
|
1920
|
-
}
|
|
1921
|
-
function createFileKey(packageId, fileName) {
|
|
1922
|
-
return `${packageId}\u0000${fileName}`;
|
|
1923
|
-
}
|
|
1924
|
-
function createFileSizeMap(result) {
|
|
1925
|
-
const map = /* @__PURE__ */ new Map();
|
|
1926
|
-
for (const pkg of result?.packages ?? []) for (const file of pkg.files) map.set(createFileKey(pkg.id, file.file), getFileSize(file));
|
|
1927
|
-
return map;
|
|
1928
|
-
}
|
|
1929
|
-
function createPackageTypeMap(result) {
|
|
1930
|
-
return new Map(result.packages.map((pkg) => [pkg.id, pkg.type]));
|
|
1931
|
-
}
|
|
1932
|
-
function createModuleByteMap(result) {
|
|
1933
|
-
const map = /* @__PURE__ */ new Map();
|
|
1934
|
-
for (const pkg of result.packages) for (const file of pkg.files) for (const mod of file.modules ?? []) {
|
|
1935
|
-
const bytes = mod.bytes ?? mod.originalBytes ?? 0;
|
|
1936
|
-
map.set(mod.id, Math.max(map.get(mod.id) ?? 0, bytes));
|
|
1937
|
-
}
|
|
1938
|
-
return map;
|
|
1939
|
-
}
|
|
1940
|
-
function classifyIncrementCategory(source, sourceType) {
|
|
1941
|
-
if (source.includes("wevu") || source.includes("@weapp-vite/dashboard")) return "WeVu / runtime";
|
|
1942
|
-
if (sourceType === "node_modules" || source.includes("node_modules")) return "第三方依赖";
|
|
1943
|
-
if (sourceType === "workspace") return "工作区包";
|
|
1944
|
-
if (sourceType === "plugin") return "插件生成";
|
|
1945
|
-
if (source.endsWith(".wxss") || source.endsWith(".css") || source.endsWith(".scss")) return "样式资源";
|
|
1946
|
-
if (source.endsWith(".wxml") || source.endsWith(".json")) return "页面结构";
|
|
1947
|
-
return "业务源码";
|
|
1948
|
-
}
|
|
1949
|
-
function createIncrementAdvice(item) {
|
|
1950
|
-
if (item.category === "第三方依赖") return "检查依赖是否只在必要分包引用,必要时收敛到公共入口或替换轻量实现。";
|
|
1951
|
-
if (item.category === "WeVu / runtime") return "确认运行时能力是否被新页面引入,优先排查组件和 API 引用边界。";
|
|
1952
|
-
if (item.category === "样式资源") return "检查样式重复、原子类生成范围和组件样式裁剪。";
|
|
1953
|
-
if (item.type === "new-file" || item.type === "new-module") return "确认是否为本次需求必要新增,评估分包归属和懒加载边界。";
|
|
1954
|
-
return "对比本次变更,优先查看新增引用、共享模块和大对象常量。";
|
|
1955
|
-
}
|
|
1956
|
-
function createModuleSizeMap(result) {
|
|
1957
|
-
const map = /* @__PURE__ */ new Map();
|
|
1958
|
-
for (const pkg of result?.packages ?? []) for (const file of pkg.files) for (const module of file.modules ?? []) {
|
|
1959
|
-
const bytes = module.bytes ?? module.originalBytes ?? 0;
|
|
1960
|
-
const existing = map.get(module.id);
|
|
1961
|
-
if (existing && existing.bytes >= bytes) continue;
|
|
1962
|
-
map.set(module.id, {
|
|
1963
|
-
source: module.source,
|
|
1964
|
-
sourceType: module.sourceType,
|
|
1965
|
-
bytes,
|
|
1966
|
-
packageLabel: pkg.label,
|
|
1967
|
-
file: file.file
|
|
1968
|
-
});
|
|
1969
|
-
}
|
|
1970
|
-
return map;
|
|
1971
|
-
}
|
|
1972
|
-
function createDuplicateAdvice(sourceType, packageIds, packageTypeMap, estimatedSavingBytes) {
|
|
1973
|
-
if (packageIds.some((packageId) => packageTypeMap.get(packageId) === "independent")) return estimatedSavingBytes > 0 ? "含独立分包,先确认隔离要求,再评估是否抽公共入口。" : "含独立分包,重复可能来自隔离边界。";
|
|
1974
|
-
if (sourceType === "node_modules") return "依赖被多个包带入,检查引用边界或考虑主包公共入口。";
|
|
1975
|
-
if (sourceType === "src" || sourceType === "workspace") return "共享源码跨包重复,优先抽公共模块或调整分包归属。";
|
|
1976
|
-
if (sourceType === "plugin") return "插件生成内容跨包重复,检查插件产物输出策略。";
|
|
1977
|
-
return "检查该模块是否需要在多个包内重复存在。";
|
|
1978
|
-
}
|
|
1979
2016
|
function formatBudgetStatus(item) {
|
|
2017
|
+
if (item.status === "unknown") return "归因或产物字节不足,无法验收";
|
|
1980
2018
|
if (item.status === "ok") return "正常";
|
|
1981
2019
|
return `${item.status === "exceeded" ? "超预算" : "接近预算"} ${(item.ratio * 100).toFixed(1)}%`;
|
|
1982
2020
|
}
|
|
@@ -1991,124 +2029,22 @@ function createActionItems(options) {
|
|
|
1991
2029
|
if (actions.length === 0) actions.push("当前没有预算超限或高收益重复模块,保持观察即可。");
|
|
1992
2030
|
return actions;
|
|
1993
2031
|
}
|
|
1994
|
-
function
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
const items = [];
|
|
1998
|
-
const totalBytes = result.packages.flatMap((pkg) => pkg.files).reduce((sum, file) => sum + getFileSize(file), 0);
|
|
1999
|
-
const warningRatio = budgets.warningRatio;
|
|
2000
|
-
const createItem = (options) => {
|
|
2001
|
-
const ratio = options.limitBytes > 0 ? options.currentBytes / options.limitBytes : 0;
|
|
2002
|
-
const status = ratio >= 1 ? "exceeded" : ratio >= warningRatio ? "warning" : "ok";
|
|
2003
|
-
return {
|
|
2004
|
-
...options,
|
|
2005
|
-
ratio,
|
|
2006
|
-
status
|
|
2007
|
-
};
|
|
2008
|
-
};
|
|
2009
|
-
items.push(createItem({
|
|
2010
|
-
id: "__total__",
|
|
2011
|
-
label: "总包",
|
|
2012
|
-
scope: "total",
|
|
2013
|
-
currentBytes: totalBytes,
|
|
2014
|
-
limitBytes: budgets.totalBytes
|
|
2015
|
-
}));
|
|
2016
|
-
for (const pkg of result.packages) {
|
|
2017
|
-
const limitBytes = getBudgetLimit(pkg.type, budgets);
|
|
2018
|
-
if (!limitBytes) continue;
|
|
2019
|
-
items.push(createItem({
|
|
2020
|
-
id: pkg.id,
|
|
2021
|
-
label: pkg.label,
|
|
2022
|
-
scope: pkg.type,
|
|
2023
|
-
currentBytes: pkg.files.reduce((sum, file) => sum + getFileSize(file), 0),
|
|
2024
|
-
limitBytes
|
|
2025
|
-
}));
|
|
2026
|
-
}
|
|
2027
|
-
return items.sort((a, b) => b.ratio - a.ratio || a.label.localeCompare(b.label));
|
|
2028
|
-
}
|
|
2029
|
-
function createDuplicateModuleInsights(result) {
|
|
2030
|
-
const moduleByteMap = createModuleByteMap(result);
|
|
2031
|
-
const packageTypeMap = createPackageTypeMap(result);
|
|
2032
|
-
return result.modules.filter((module) => module.packages.length > 1).map((module) => {
|
|
2033
|
-
const bytes = moduleByteMap.get(module.id) ?? 0;
|
|
2034
|
-
const packageIds = module.packages.map((pkg) => pkg.packageId);
|
|
2035
|
-
const estimatedSavingBytes = bytes * Math.max(module.packages.length - 1, 0);
|
|
2036
|
-
return {
|
|
2037
|
-
id: module.id,
|
|
2038
|
-
source: module.source,
|
|
2039
|
-
sourceType: module.sourceType,
|
|
2040
|
-
packageCount: module.packages.length,
|
|
2041
|
-
bytes,
|
|
2042
|
-
estimatedSavingBytes,
|
|
2043
|
-
packages: packageIds,
|
|
2044
|
-
advice: createDuplicateAdvice(module.sourceType, packageIds, packageTypeMap, estimatedSavingBytes)
|
|
2045
|
-
};
|
|
2046
|
-
}).sort((a, b) => b.estimatedSavingBytes - a.estimatedSavingBytes || b.packageCount - a.packageCount || a.source.localeCompare(b.source));
|
|
2047
|
-
}
|
|
2048
|
-
function createAnalyzeIncrementAttribution(result, previousResult) {
|
|
2049
|
-
if (!previousResult) return [];
|
|
2050
|
-
const previousFiles = createFileSizeMap(previousResult);
|
|
2051
|
-
const previousModules = createModuleSizeMap(previousResult);
|
|
2052
|
-
const currentModules = createModuleSizeMap(result);
|
|
2053
|
-
const items = [];
|
|
2054
|
-
for (const pkg of result.packages) for (const file of pkg.files) {
|
|
2055
|
-
const currentBytes = getFileSize(file);
|
|
2056
|
-
const previousBytes = previousFiles.get(createFileKey(pkg.id, file.file)) ?? 0;
|
|
2057
|
-
const deltaBytes = currentBytes - previousBytes;
|
|
2058
|
-
if (deltaBytes <= 0) continue;
|
|
2059
|
-
const type = previousBytes > 0 ? "increased-file" : "new-file";
|
|
2060
|
-
const item = {
|
|
2061
|
-
key: `file:${pkg.id}:${file.file}`,
|
|
2062
|
-
type,
|
|
2063
|
-
label: file.file,
|
|
2064
|
-
category: classifyIncrementCategory(file.source ?? file.file),
|
|
2065
|
-
packageLabel: pkg.label,
|
|
2066
|
-
file: file.file,
|
|
2067
|
-
currentBytes,
|
|
2068
|
-
previousBytes,
|
|
2069
|
-
deltaBytes,
|
|
2070
|
-
advice: ""
|
|
2071
|
-
};
|
|
2072
|
-
items.push({
|
|
2073
|
-
...item,
|
|
2074
|
-
advice: createIncrementAdvice(item)
|
|
2075
|
-
});
|
|
2076
|
-
}
|
|
2077
|
-
for (const [id, module] of currentModules) {
|
|
2078
|
-
const previousBytes = previousModules.get(id)?.bytes ?? 0;
|
|
2079
|
-
const deltaBytes = module.bytes - previousBytes;
|
|
2080
|
-
if (deltaBytes <= 0) continue;
|
|
2081
|
-
const type = previousBytes > 0 ? "increased-module" : "new-module";
|
|
2082
|
-
const item = {
|
|
2083
|
-
key: `module:${id}`,
|
|
2084
|
-
type,
|
|
2085
|
-
label: module.source,
|
|
2086
|
-
category: classifyIncrementCategory(module.source, module.sourceType),
|
|
2087
|
-
packageLabel: module.packageLabel,
|
|
2088
|
-
file: module.file,
|
|
2089
|
-
currentBytes: module.bytes,
|
|
2090
|
-
previousBytes,
|
|
2091
|
-
deltaBytes,
|
|
2092
|
-
advice: ""
|
|
2093
|
-
};
|
|
2094
|
-
items.push({
|
|
2095
|
-
...item,
|
|
2096
|
-
advice: createIncrementAdvice(item)
|
|
2097
|
-
});
|
|
2098
|
-
}
|
|
2099
|
-
return items.sort((a, b) => b.deltaBytes - a.deltaBytes || a.category.localeCompare(b.category) || a.label.localeCompare(b.label));
|
|
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));
|
|
2100
2035
|
}
|
|
2101
2036
|
function createAnalyzeIncrementCategorySummary(items) {
|
|
2102
2037
|
const map = /* @__PURE__ */ new Map();
|
|
2103
2038
|
for (const item of items) {
|
|
2104
|
-
const
|
|
2105
|
-
|
|
2039
|
+
const category = item.category ?? "";
|
|
2040
|
+
const entry = map.get(category) ?? {
|
|
2041
|
+
category,
|
|
2106
2042
|
count: 0,
|
|
2107
2043
|
deltaBytes: 0
|
|
2108
2044
|
};
|
|
2109
2045
|
entry.count += 1;
|
|
2110
2046
|
entry.deltaBytes += item.deltaBytes;
|
|
2111
|
-
map.set(
|
|
2047
|
+
map.set(category, entry);
|
|
2112
2048
|
}
|
|
2113
2049
|
return [...map.values()].sort((a, b) => b.deltaBytes - a.deltaBytes || b.count - a.count || a.category.localeCompare(b.category));
|
|
2114
2050
|
}
|
|
@@ -2119,8 +2055,8 @@ function createAnalyzePrMarkdownReport(result, previousResult) {
|
|
|
2119
2055
|
})));
|
|
2120
2056
|
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
2121
2057
|
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
2122
|
-
const
|
|
2123
|
-
const incrementItems =
|
|
2058
|
+
const comparison = previousResult ? createAnalyzeComparison(result, previousResult) : void 0;
|
|
2059
|
+
const incrementItems = createReportGrowth(comparison);
|
|
2124
2060
|
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
2125
2061
|
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
2126
2062
|
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
@@ -2136,7 +2072,7 @@ function createAnalyzePrMarkdownReport(result, previousResult) {
|
|
|
2136
2072
|
return [
|
|
2137
2073
|
"## weapp-vite analyze PR 摘要",
|
|
2138
2074
|
"",
|
|
2139
|
-
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}(较上次 ${formatDelta(
|
|
2075
|
+
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}(较上次 ${formatDelta(comparison?.deltaBytes)})`,
|
|
2140
2076
|
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
2141
2077
|
`- 预算告警:${budgetIssues.length}`,
|
|
2142
2078
|
`- 增量归因:${incrementItems.length > 0 ? `${incrementItems.length} 项正向增长` : "无正向增长"}`,
|
|
@@ -2180,11 +2116,11 @@ function createAnalyzeMarkdownReport(result, previousResult) {
|
|
|
2180
2116
|
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
2181
2117
|
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
2182
2118
|
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
2183
|
-
const
|
|
2119
|
+
const comparison = previousResult ? createAnalyzeComparison(result, previousResult) : void 0;
|
|
2120
|
+
const incrementItems = createReportGrowth(comparison);
|
|
2184
2121
|
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
2185
2122
|
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
2186
|
-
const
|
|
2187
|
-
const previousPackageSizes = createPackageSizeMap(previousResult);
|
|
2123
|
+
const packageDeltas = new Map(comparison?.packages.map((item) => [item.packageId, item.deltaBytes]));
|
|
2188
2124
|
const budgets = result.metadata?.budgets;
|
|
2189
2125
|
const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
|
|
2190
2126
|
const actionItems = createActionItems({
|
|
@@ -2194,9 +2130,9 @@ function createAnalyzeMarkdownReport(result, previousResult) {
|
|
|
2194
2130
|
const packageRows = result.packages.map((pkg) => {
|
|
2195
2131
|
const size = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
|
|
2196
2132
|
const compressed = pkg.files.reduce((sum, file) => sum + getCompressedSize(file), 0);
|
|
2197
|
-
const
|
|
2133
|
+
const delta = packageDeltas.has(pkg.id) ? packageDeltas.get(pkg.id) : 0;
|
|
2198
2134
|
const budgetStatus = budgetItems.find((item) => item.id === pkg.id);
|
|
2199
|
-
return `| ${pkg.label} | ${pkg.type} | ${formatAnalyzeBytes(size)} | ${formatAnalyzeBytes(compressed)} | ${formatDelta(
|
|
2135
|
+
return `| ${pkg.label} | ${pkg.type} | ${formatAnalyzeBytes(size)} | ${formatAnalyzeBytes(compressed)} | ${formatDelta(delta)} | ${budgetStatus ? formatBudgetStatus(budgetStatus) : "正常"} |`;
|
|
2200
2136
|
}).join("\n");
|
|
2201
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");
|
|
2202
2138
|
const duplicateRows = duplicateInsights.slice(0, 10).map((module) => `| ${module.source} | ${module.sourceType} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
|
|
@@ -2212,7 +2148,7 @@ function createAnalyzeMarkdownReport(result, previousResult) {
|
|
|
2212
2148
|
"",
|
|
2213
2149
|
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}`,
|
|
2214
2150
|
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
2215
|
-
`- 较上次:${formatDelta(
|
|
2151
|
+
`- 较上次:${formatDelta(comparison?.deltaBytes)}`,
|
|
2216
2152
|
`- 包体数量:${result.packages.length}`,
|
|
2217
2153
|
`- 源码模块:${result.modules.length}`,
|
|
2218
2154
|
`- 跨包复用:${duplicateInsights.length}`,
|
|
@@ -2259,725 +2195,164 @@ function createAnalyzeMarkdownReport(result, previousResult) {
|
|
|
2259
2195
|
].join("\n");
|
|
2260
2196
|
}
|
|
2261
2197
|
//#endregion
|
|
2262
|
-
//#region src/cli/analyze/
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
let retainedBytes = 0;
|
|
2268
|
-
return {
|
|
2269
|
-
files,
|
|
2270
|
-
capture(fileName, source) {
|
|
2271
|
-
const key = fileName.replaceAll("\\", "/");
|
|
2272
|
-
const previous = files.get(key);
|
|
2273
|
-
if (previous?.content !== void 0) retainedBytes -= previous.size;
|
|
2274
|
-
const size = typeof source === "string" ? Buffer.byteLength(source, "utf8") : source.byteLength;
|
|
2275
|
-
if (size > 2097152) {
|
|
2276
|
-
files.set(key, {
|
|
2277
|
-
size,
|
|
2278
|
-
error: `文件超过 ${MAX_DASHBOARD_FILE_CONTENT_BYTES} 字节,已拒绝读取。`
|
|
2279
|
-
});
|
|
2280
|
-
return;
|
|
2281
|
-
}
|
|
2282
|
-
if (retainedBytes + size > 33554432) {
|
|
2283
|
-
files.set(key, {
|
|
2284
|
-
size,
|
|
2285
|
-
error: "分析产物内容超过快照容量上限,已拒绝读取。"
|
|
2286
|
-
});
|
|
2287
|
-
return;
|
|
2288
|
-
}
|
|
2289
|
-
const content = typeof source === "string" ? source : Buffer.from(source.buffer, source.byteOffset, source.byteLength).toString("utf8");
|
|
2290
|
-
files.set(key, {
|
|
2291
|
-
content,
|
|
2292
|
-
size
|
|
2293
|
-
});
|
|
2294
|
-
retainedBytes += size;
|
|
2295
|
-
}
|
|
2296
|
-
};
|
|
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(" ")}`;
|
|
2297
2203
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
return queryIndex === -1 ? value : value.slice(0, queryIndex);
|
|
2306
|
-
}
|
|
2307
|
-
function addDashboardAllowedPath(paths, value) {
|
|
2308
|
-
if (!value || value.includes("\0")) return;
|
|
2309
|
-
const normalizedPath = normalizeDashboardRelativePath(stripDashboardFileQuery(value));
|
|
2310
|
-
if (!normalizedPath || path.isAbsolute(normalizedPath)) return;
|
|
2311
|
-
paths.add(normalizedPath);
|
|
2312
|
-
}
|
|
2313
|
-
function addDashboardAllowedSourcePath(paths, value, rootKind) {
|
|
2314
|
-
if (!value || value.includes("\0")) return;
|
|
2315
|
-
const normalizedPath = normalizeDashboardRelativePath(stripDashboardFileQuery(value));
|
|
2316
|
-
if (!normalizedPath || path.isAbsolute(normalizedPath)) return;
|
|
2317
|
-
const rootKinds = paths.get(normalizedPath) ?? /* @__PURE__ */ new Set();
|
|
2318
|
-
rootKinds.add(rootKind);
|
|
2319
|
-
paths.set(normalizedPath, rootKinds);
|
|
2320
|
-
}
|
|
2321
|
-
function resolveDashboardSourceRootKind(sourceType) {
|
|
2322
|
-
if (sourceType === "plugin") return "plugin";
|
|
2323
|
-
if (sourceType === "workspace" || sourceType === "node_modules") return "project";
|
|
2324
|
-
return "src";
|
|
2325
|
-
}
|
|
2326
|
-
function createDashboardContentAllowlist(result) {
|
|
2327
|
-
const artifactPaths = /* @__PURE__ */ new Set();
|
|
2328
|
-
const sourcePaths = /* @__PURE__ */ new Map();
|
|
2329
|
-
for (const packageReport of result.packages) for (const file of packageReport.files) {
|
|
2330
|
-
addDashboardAllowedPath(artifactPaths, file.file);
|
|
2331
|
-
const fileSourceType = file.sourceType ?? file.modules?.find((module) => module.source === file.source)?.sourceType;
|
|
2332
|
-
addDashboardAllowedSourcePath(sourcePaths, file.source, resolveDashboardSourceRootKind(fileSourceType));
|
|
2333
|
-
for (const module of file.modules ?? []) addDashboardAllowedSourcePath(sourcePaths, module.source, module.sourceType === "src" ? "src" : module.sourceType === "plugin" ? "plugin" : "project");
|
|
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;
|
|
2334
2211
|
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
}
|
|
2356
|
-
function resolveDashboardSourceContentPaths(roots, requestPath, allowedPaths) {
|
|
2357
|
-
const normalizedRequestPath = normalizeDashboardRelativePath(stripDashboardFileQuery(requestPath));
|
|
2358
|
-
const rootKinds = allowedPaths.get(normalizedRequestPath);
|
|
2359
|
-
if (!rootKinds || rootKinds.size !== 1) return [];
|
|
2360
|
-
const [rootKind] = rootKinds;
|
|
2361
|
-
if (rootKind === "project") {
|
|
2362
|
-
const resolved = resolveDashboardContentPath(roots.projectRoot, normalizedRequestPath, {
|
|
2363
|
-
allowParent: true,
|
|
2364
|
-
allowedPaths: /* @__PURE__ */ new Set([normalizedRequestPath])
|
|
2365
|
-
});
|
|
2366
|
-
return resolved ? [resolved] : [];
|
|
2367
|
-
}
|
|
2368
|
-
if (rootKind === "plugin") {
|
|
2369
|
-
if (!roots.pluginRoot) return [];
|
|
2370
|
-
const pluginBase = path.basename(roots.pluginRoot);
|
|
2371
|
-
if (normalizedRequestPath !== pluginBase && !normalizedRequestPath.startsWith(`${pluginBase}/`)) return [];
|
|
2372
|
-
const pluginRelativePath = normalizedRequestPath === pluginBase ? "" : normalizedRequestPath.slice(pluginBase.length + 1);
|
|
2373
|
-
const resolved = resolveDashboardContentPath(roots.pluginRoot, pluginRelativePath, { allowedPaths: /* @__PURE__ */ new Set([pluginRelativePath]) });
|
|
2374
|
-
return resolved ? [{
|
|
2375
|
-
...resolved,
|
|
2376
|
-
relativePath: normalizedRequestPath
|
|
2377
|
-
}] : [];
|
|
2378
|
-
}
|
|
2379
|
-
if (!roots.srcRoot) return [];
|
|
2380
|
-
const candidates = (normalizedRequestPath.startsWith("src/") ? [normalizedRequestPath, normalizedRequestPath.slice(4)] : [normalizedRequestPath]).flatMap((relativePath) => {
|
|
2381
|
-
const resolved = resolveDashboardContentPath(roots.srcRoot, relativePath, { allowedPaths: /* @__PURE__ */ new Set([relativePath]) });
|
|
2382
|
-
return resolved ? [{
|
|
2383
|
-
...resolved,
|
|
2384
|
-
relativePath: normalizedRequestPath
|
|
2385
|
-
}] : [];
|
|
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
|
+
}
|
|
2386
2233
|
});
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
}
|
|
2400
|
-
function resolveInspectionRoot(resolved) {
|
|
2401
|
-
let inspectionRoot = resolved.rootPath;
|
|
2402
|
-
if (!resolved.allowParent) return inspectionRoot;
|
|
2403
|
-
while (!isPathWithin(inspectionRoot, resolved.absolutePath)) {
|
|
2404
|
-
const parent = path.dirname(inspectionRoot);
|
|
2405
|
-
if (parent === inspectionRoot) return;
|
|
2406
|
-
inspectionRoot = parent;
|
|
2407
|
-
}
|
|
2408
|
-
return inspectionRoot;
|
|
2409
|
-
}
|
|
2410
|
-
async function assertDashboardPathHasNoSymlink(resolved) {
|
|
2411
|
-
const inspectionRoot = resolveInspectionRoot(resolved);
|
|
2412
|
-
if (!inspectionRoot) throw new Error("文件路径超出允许范围。");
|
|
2413
|
-
const relativePath = path.relative(inspectionRoot, resolved.absolutePath);
|
|
2414
|
-
let currentPath = inspectionRoot;
|
|
2415
|
-
for (const segment of relativePath.split(path.sep).filter(Boolean)) {
|
|
2416
|
-
currentPath = path.join(currentPath, segment);
|
|
2417
|
-
if ((await fs.promises.lstat(currentPath)).isSymbolicLink()) throw new Error("文件路径包含不允许的符号链接。");
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2420
|
-
function isSameOpenedFile(pathStat, handleStat) {
|
|
2421
|
-
if (pathStat.dev !== handleStat.dev) return false;
|
|
2422
|
-
return pathStat.ino === 0 || handleStat.ino === 0 || pathStat.ino === handleStat.ino;
|
|
2423
|
-
}
|
|
2424
|
-
async function openDashboardFile(resolved) {
|
|
2425
|
-
await assertDashboardPathHasNoSymlink(resolved);
|
|
2426
|
-
const flags = process.platform === "win32" ? fs.constants.O_RDONLY : fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW;
|
|
2427
|
-
const handle = await fs.promises.open(resolved.absolutePath, flags);
|
|
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
|
+
};
|
|
2428
2247
|
try {
|
|
2429
|
-
await
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
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);
|
|
2433
2279
|
} catch (error) {
|
|
2434
|
-
await
|
|
2280
|
+
const cleanupErrors = await closeResources();
|
|
2281
|
+
if (cleanupErrors.length) throw new AggregateError([error, ...cleanupErrors], "Dashboard 启动及资源清理失败", { cause: error });
|
|
2435
2282
|
throw error;
|
|
2436
2283
|
}
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
return {
|
|
2457
|
-
content: content.subarray(0, offset).toString("utf8"),
|
|
2458
|
-
size: offset
|
|
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();
|
|
2459
2303
|
};
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
const extension = path.extname(filePath).toLowerCase();
|
|
2463
|
-
if ([
|
|
2464
|
-
".js",
|
|
2465
|
-
".mjs",
|
|
2466
|
-
".cjs",
|
|
2467
|
-
".wxs",
|
|
2468
|
-
".sjs"
|
|
2469
|
-
].includes(extension)) return "javascript";
|
|
2470
|
-
if ([
|
|
2471
|
-
".ts",
|
|
2472
|
-
".mts",
|
|
2473
|
-
".cts"
|
|
2474
|
-
].includes(extension)) return "typescript";
|
|
2475
|
-
if ([".json", ".map"].includes(extension)) return "json";
|
|
2476
|
-
if ([
|
|
2477
|
-
".css",
|
|
2478
|
-
".wxss",
|
|
2479
|
-
".scss",
|
|
2480
|
-
".sass",
|
|
2481
|
-
".less"
|
|
2482
|
-
].includes(extension)) return "css";
|
|
2483
|
-
if ([
|
|
2484
|
-
".vue",
|
|
2485
|
-
".wxml",
|
|
2486
|
-
".html"
|
|
2487
|
-
].includes(extension)) return "html";
|
|
2488
|
-
return "plaintext";
|
|
2489
|
-
}
|
|
2490
|
-
function normalizeDashboardFileRequest(input) {
|
|
2491
|
-
if (!input || typeof input !== "object" || !("kind" in input) || !("path" in input)) throw new Error("必须传入合法的 kind 和相对路径。");
|
|
2492
|
-
const kind = input.kind;
|
|
2493
|
-
const requestPath = input.path;
|
|
2494
|
-
if (kind !== "source" && kind !== "artifact" || typeof requestPath !== "string") throw new Error("必须传入合法的 kind 和相对路径。");
|
|
2495
|
-
return {
|
|
2496
|
-
kind,
|
|
2497
|
-
path: requestPath
|
|
2304
|
+
onHostClose = () => {
|
|
2305
|
+
if (!closing) finish();
|
|
2498
2306
|
};
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
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;
|
|
2515
2329
|
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
for (const resolved of resolvedCandidates) try {
|
|
2521
|
-
openedCandidates.push({
|
|
2522
|
-
handle: await openDashboardFile(resolved),
|
|
2523
|
-
resolved
|
|
2524
|
-
});
|
|
2525
|
-
} catch (error) {
|
|
2526
|
-
if ((typeof error === "object" && error && "code" in error ? String(error.code) : "") !== "ENOENT") throw error;
|
|
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))}`);
|
|
2527
2334
|
}
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
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);
|
|
2542
2352
|
} finally {
|
|
2543
|
-
|
|
2353
|
+
process.stdout.write = stdoutWrite;
|
|
2544
2354
|
}
|
|
2545
2355
|
}
|
|
2546
|
-
function createDashboardFileReader(roots, result, artifacts) {
|
|
2547
|
-
let allowlist = createDashboardContentAllowlist(result);
|
|
2548
|
-
let currentArtifacts = artifacts;
|
|
2549
|
-
let disposed = false;
|
|
2550
|
-
return {
|
|
2551
|
-
read: async (input) => {
|
|
2552
|
-
if (disposed) throw new Error("Dashboard 文件读取会话已关闭。");
|
|
2553
|
-
return await readAllowedDashboardFile(input, roots, allowlist, currentArtifacts);
|
|
2554
|
-
},
|
|
2555
|
-
update(nextResult, nextArtifacts) {
|
|
2556
|
-
allowlist = createDashboardContentAllowlist(nextResult);
|
|
2557
|
-
currentArtifacts = nextArtifacts;
|
|
2558
|
-
},
|
|
2559
|
-
dispose() {
|
|
2560
|
-
disposed = true;
|
|
2561
|
-
currentArtifacts = /* @__PURE__ */ new Map();
|
|
2562
|
-
allowlist.artifactPaths.clear();
|
|
2563
|
-
allowlist.sourcePaths.clear();
|
|
2564
|
-
}
|
|
2565
|
-
};
|
|
2566
|
-
}
|
|
2567
|
-
//#endregion
|
|
2568
|
-
//#region src/cli/analyze/dashboardDevframe/index.ts
|
|
2569
|
-
const DEVFRAME_ID = "weapp-vite";
|
|
2570
|
-
const MAX_DASHBOARD_ANALYZE_PAGE_CHARACTERS = 65536;
|
|
2571
|
-
const STALE_DASHBOARD_ANALYZE_REVISION_MESSAGE = "Analyze revision 已变化,请重新获取 Dashboard 状态。";
|
|
2572
|
-
const rejectDashboardSharedStateSet = defineRpcFunction({
|
|
2573
|
-
name: "devframe:rpc:server-state:set",
|
|
2574
|
-
type: "action",
|
|
2575
|
-
handler: () => {
|
|
2576
|
-
throw new Error("Dashboard Devframe shared state 只允许服务端修改。");
|
|
2577
|
-
}
|
|
2578
|
-
});
|
|
2579
|
-
const rejectDashboardSharedStatePatch = defineRpcFunction({
|
|
2580
|
-
name: "devframe:rpc:server-state:patch",
|
|
2581
|
-
type: "action",
|
|
2582
|
-
handler: () => {
|
|
2583
|
-
throw new Error("Dashboard Devframe shared state 只允许服务端修改。");
|
|
2584
|
-
}
|
|
2585
|
-
});
|
|
2586
|
-
function serializeDashboardAnalyzePayload(result) {
|
|
2587
|
-
const value = JSON.stringify(result);
|
|
2588
|
-
return {
|
|
2589
|
-
descriptor: {
|
|
2590
|
-
characters: value.length,
|
|
2591
|
-
hash: createHash("sha256").update(value).digest("hex"),
|
|
2592
|
-
pages: Math.max(1, Math.ceil(value.length / MAX_DASHBOARD_ANALYZE_PAGE_CHARACTERS))
|
|
2593
|
-
},
|
|
2594
|
-
source: result,
|
|
2595
|
-
value
|
|
2596
|
-
};
|
|
2597
|
-
}
|
|
2598
|
-
function serializeDashboardAnalyzeSnapshot(snapshot, cached) {
|
|
2599
|
-
const previous = snapshot.previous ? cached?.current.source === snapshot.previous ? cached.current : cached?.previous?.source === snapshot.previous ? cached.previous : serializeDashboardAnalyzePayload(snapshot.previous) : null;
|
|
2600
|
-
return {
|
|
2601
|
-
current: cached?.current.source === snapshot.current ? cached.current : serializeDashboardAnalyzePayload(snapshot.current),
|
|
2602
|
-
previous
|
|
2603
|
-
};
|
|
2604
|
-
}
|
|
2605
|
-
function createDashboardState(revision, snapshot, runtimeEvents) {
|
|
2606
|
-
return {
|
|
2607
|
-
analyze: {
|
|
2608
|
-
current: snapshot.current.descriptor,
|
|
2609
|
-
previous: snapshot.previous?.descriptor ?? null
|
|
2610
|
-
},
|
|
2611
|
-
revision,
|
|
2612
|
-
runtimeEvents: [...runtimeEvents]
|
|
2613
|
-
};
|
|
2614
|
-
}
|
|
2615
|
-
function normalizeAnalyzePageRequest(input) {
|
|
2616
|
-
if (!input || typeof input !== "object") throw new Error("必须传入合法的 Analyze 分页请求。");
|
|
2617
|
-
if (!("revision" in input) || !("target" in input) || !("index" in input)) throw new Error("必须传入合法的 Analyze 分页请求。");
|
|
2618
|
-
const revision = input.revision;
|
|
2619
|
-
const target = input.target;
|
|
2620
|
-
const index = input.index;
|
|
2621
|
-
if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 0 || target !== "current" && target !== "previous" || typeof index !== "number" || !Number.isSafeInteger(index) || index < 0) throw new Error("必须传入合法的 Analyze 分页请求。");
|
|
2622
|
-
return {
|
|
2623
|
-
revision,
|
|
2624
|
-
target,
|
|
2625
|
-
index
|
|
2626
|
-
};
|
|
2627
|
-
}
|
|
2628
|
-
function readDashboardAnalyzePage(input, revision, snapshot) {
|
|
2629
|
-
const request = normalizeAnalyzePageRequest(input);
|
|
2630
|
-
if (request.revision !== revision) throw new Error(STALE_DASHBOARD_ANALYZE_REVISION_MESSAGE);
|
|
2631
|
-
const payload = request.target === "current" ? snapshot.current : snapshot.previous;
|
|
2632
|
-
if (!payload || request.index >= payload.descriptor.pages) throw new Error("Analyze 分页不存在。");
|
|
2633
|
-
const start = request.index * MAX_DASHBOARD_ANALYZE_PAGE_CHARACTERS;
|
|
2634
|
-
return {
|
|
2635
|
-
content: payload.value.slice(start, start + MAX_DASHBOARD_ANALYZE_PAGE_CHARACTERS),
|
|
2636
|
-
descriptor: payload.descriptor,
|
|
2637
|
-
index: request.index,
|
|
2638
|
-
revision,
|
|
2639
|
-
target: request.target
|
|
2640
|
-
};
|
|
2641
|
-
}
|
|
2642
|
-
function createAnalyzeDashboardDevframe(options) {
|
|
2643
|
-
let revision = 0;
|
|
2644
|
-
let disposed = false;
|
|
2645
|
-
let serializedSnapshot = serializeDashboardAnalyzeSnapshot(options.getAnalyzeSnapshot());
|
|
2646
|
-
let broadcastDashboardState;
|
|
2647
|
-
const initialSnapshot = options.getAnalyzeSnapshot();
|
|
2648
|
-
const fileReader = createDashboardFileReader(options.roots, initialSnapshot.current, initialSnapshot.artifacts);
|
|
2649
|
-
const getDashboardState = defineRpcFunction({
|
|
2650
|
-
name: "get-dashboard-state",
|
|
2651
|
-
type: "query",
|
|
2652
|
-
jsonSerializable: true,
|
|
2653
|
-
handler: () => createDashboardState(revision, serializedSnapshot, options.getRuntimeEvents())
|
|
2654
|
-
});
|
|
2655
|
-
const getAnalyzePage = defineRpcFunction({
|
|
2656
|
-
name: "get-analyze-page",
|
|
2657
|
-
type: "query",
|
|
2658
|
-
jsonSerializable: true,
|
|
2659
|
-
handler: (input) => readDashboardAnalyzePage(input, revision, serializedSnapshot)
|
|
2660
|
-
});
|
|
2661
|
-
const readDashboardFile = defineRpcFunction({
|
|
2662
|
-
name: "read-dashboard-file",
|
|
2663
|
-
type: "query",
|
|
2664
|
-
jsonSerializable: true,
|
|
2665
|
-
handler: async (input) => {
|
|
2666
|
-
if (!input || typeof input !== "object" || !("revision" in input) || typeof input.revision !== "number" || !Number.isSafeInteger(input.revision) || input.revision < 0) throw new Error("必须传入合法的文件读取 revision。");
|
|
2667
|
-
const requestedRevision = input.revision;
|
|
2668
|
-
if (disposed || requestedRevision !== revision) throw new Error(STALE_DASHBOARD_ANALYZE_REVISION_MESSAGE);
|
|
2669
|
-
const content = await fileReader.read(input);
|
|
2670
|
-
if (disposed || requestedRevision !== revision) throw new Error(STALE_DASHBOARD_ANALYZE_REVISION_MESSAGE);
|
|
2671
|
-
return content;
|
|
2672
|
-
}
|
|
2673
|
-
});
|
|
2674
|
-
return {
|
|
2675
|
-
definition: defineDevframe({
|
|
2676
|
-
id: DEVFRAME_ID,
|
|
2677
|
-
name: "weapp-vite",
|
|
2678
|
-
version: VERSION,
|
|
2679
|
-
packageName: "weapp-vite",
|
|
2680
|
-
importMetaUrl: import.meta.url,
|
|
2681
|
-
homepage: "https://vite.weapp.dev/",
|
|
2682
|
-
description: "weapp-vite 构建分析与小程序开发工具。",
|
|
2683
|
-
icon: "ph:rocket-launch-duotone",
|
|
2684
|
-
async setup(ctx) {
|
|
2685
|
-
ctx.rpc.register(rejectDashboardSharedStateSet, true);
|
|
2686
|
-
ctx.rpc.register(rejectDashboardSharedStatePatch, true);
|
|
2687
|
-
const dashboard = ctx.scope(DEVFRAME_ID);
|
|
2688
|
-
dashboard.rpc.register(getDashboardState);
|
|
2689
|
-
dashboard.rpc.register(getAnalyzePage);
|
|
2690
|
-
dashboard.rpc.register(readDashboardFile);
|
|
2691
|
-
broadcastDashboardState = async () => {
|
|
2692
|
-
await dashboard.rpc.broadcast({
|
|
2693
|
-
method: "dashboard-state-updated",
|
|
2694
|
-
args: [createDashboardState(revision, serializedSnapshot, options.getRuntimeEvents())],
|
|
2695
|
-
event: true
|
|
2696
|
-
});
|
|
2697
|
-
};
|
|
2698
|
-
}
|
|
2699
|
-
}),
|
|
2700
|
-
notifyAnalyzeUpdate() {
|
|
2701
|
-
if (disposed) return;
|
|
2702
|
-
revision += 1;
|
|
2703
|
-
const snapshot = options.getAnalyzeSnapshot();
|
|
2704
|
-
serializedSnapshot = serializeDashboardAnalyzeSnapshot(snapshot, serializedSnapshot);
|
|
2705
|
-
fileReader.update(snapshot.current, snapshot.artifacts);
|
|
2706
|
-
broadcastDashboardState?.();
|
|
2707
|
-
},
|
|
2708
|
-
syncRuntimeEvents() {
|
|
2709
|
-
broadcastDashboardState?.();
|
|
2710
|
-
},
|
|
2711
|
-
dispose() {
|
|
2712
|
-
disposed = true;
|
|
2713
|
-
fileReader.dispose();
|
|
2714
|
-
broadcastDashboardState = void 0;
|
|
2715
|
-
}
|
|
2716
|
-
};
|
|
2717
|
-
}
|
|
2718
|
-
//#endregion
|
|
2719
|
-
//#region src/cli/analyze/dashboardViteBridge.ts
|
|
2720
|
-
const ANALYZE_DASHBOARD_DEVFRAME_BASE = "/__weapp-vite/";
|
|
2721
|
-
function createAnalyzeDashboardViteBridge(definition) {
|
|
2722
|
-
let instance;
|
|
2723
|
-
let closing;
|
|
2724
|
-
const closeInstance = async () => {
|
|
2725
|
-
if (closing) return await closing;
|
|
2726
|
-
const current = instance;
|
|
2727
|
-
instance = void 0;
|
|
2728
|
-
if (!current) return;
|
|
2729
|
-
closing = current.close().finally(() => {
|
|
2730
|
-
closing = void 0;
|
|
2731
|
-
});
|
|
2732
|
-
await closing;
|
|
2733
|
-
};
|
|
2734
|
-
return {
|
|
2735
|
-
name: "weapp-vite:dashboard-devframe",
|
|
2736
|
-
apply: "serve",
|
|
2737
|
-
async configureServer(server) {
|
|
2738
|
-
await closeInstance();
|
|
2739
|
-
const httpServer = server.httpServer instanceof Server ? server.httpServer : void 0;
|
|
2740
|
-
const created = initDevframe(definition, {
|
|
2741
|
-
base: ANALYZE_DASHBOARD_DEVFRAME_BASE,
|
|
2742
|
-
distDir: false,
|
|
2743
|
-
...httpServer ? { server: httpServer } : {
|
|
2744
|
-
host: "127.0.0.1",
|
|
2745
|
-
ws: { sidecar: true }
|
|
2746
|
-
},
|
|
2747
|
-
allowedOrigins: [],
|
|
2748
|
-
auth: true,
|
|
2749
|
-
mcp: false
|
|
2750
|
-
});
|
|
2751
|
-
server.middlewares.use(created.nodeMiddleware);
|
|
2752
|
-
try {
|
|
2753
|
-
await created.ready;
|
|
2754
|
-
instance = created;
|
|
2755
|
-
} catch (error) {
|
|
2756
|
-
await created.close();
|
|
2757
|
-
throw error;
|
|
2758
|
-
}
|
|
2759
|
-
server.httpServer?.once("close", () => {
|
|
2760
|
-
closeInstance();
|
|
2761
|
-
});
|
|
2762
|
-
},
|
|
2763
|
-
async closeBundle() {
|
|
2764
|
-
await closeInstance();
|
|
2765
|
-
}
|
|
2766
|
-
};
|
|
2767
|
-
}
|
|
2768
|
-
//#endregion
|
|
2769
|
-
//#region src/cli/analyze/dashboard.ts
|
|
2770
|
-
const ANALYZE_DASHBOARD_PACKAGE_NAME = "@weapp-vite/dashboard";
|
|
2771
|
-
const require = createRequire(import.meta.url);
|
|
2772
|
-
function createInstallCommand(agent) {
|
|
2773
|
-
const resolved = resolveCommand(agent ?? "npm", "install", [ANALYZE_DASHBOARD_PACKAGE_NAME]);
|
|
2774
|
-
if (!resolved) return `npm install ${ANALYZE_DASHBOARD_PACKAGE_NAME}`;
|
|
2775
|
-
return `${resolved.command} ${resolved.args.join(" ")}`;
|
|
2776
|
-
}
|
|
2777
|
-
function formatEventTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
2778
|
-
return date.toLocaleTimeString("zh-CN", { hour12: false });
|
|
2779
|
-
}
|
|
2780
|
-
function createDashboardRuntimeEvent(input) {
|
|
2781
|
-
return {
|
|
2782
|
-
id: `dashboard:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`,
|
|
2783
|
-
kind: input.kind,
|
|
2784
|
-
level: input.level,
|
|
2785
|
-
title: input.title,
|
|
2786
|
-
detail: input.detail,
|
|
2787
|
-
timestamp: formatEventTimestamp(),
|
|
2788
|
-
source: input.source ?? "weapp-vite",
|
|
2789
|
-
durationMs: input.durationMs,
|
|
2790
|
-
tags: input.tags,
|
|
2791
|
-
profile: input.profile
|
|
2792
|
-
};
|
|
2793
|
-
}
|
|
2794
|
-
function readDashboardManifest(packageJsonPath) {
|
|
2795
|
-
try {
|
|
2796
|
-
return parseCommentJson(fs.readFileSync(packageJsonPath, "utf8"));
|
|
2797
|
-
} catch {
|
|
2798
|
-
return;
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
function resolveDashboardDistRoot(packageRoot, manifest) {
|
|
2802
|
-
const distDir = manifest?.weappViteDashboard?.distDir ?? "dist";
|
|
2803
|
-
const distRoot = path.resolve(packageRoot, distDir);
|
|
2804
|
-
if (!fs.existsSync(distRoot)) return;
|
|
2805
|
-
return { root: distRoot };
|
|
2806
|
-
}
|
|
2807
|
-
function resolveDashboardDevRoot(packageRoot, manifest) {
|
|
2808
|
-
const devRoot = manifest?.weappViteDashboard?.devRoot;
|
|
2809
|
-
const devConfigFile = manifest?.weappViteDashboard?.devConfigFile;
|
|
2810
|
-
if (!devRoot || !devConfigFile) return;
|
|
2811
|
-
const root = path.resolve(packageRoot, devRoot);
|
|
2812
|
-
const configFile = path.resolve(root, devConfigFile);
|
|
2813
|
-
if (!fs.existsSync(root) || !fs.existsSync(configFile)) return;
|
|
2814
|
-
return {
|
|
2815
|
-
root,
|
|
2816
|
-
configFile
|
|
2817
|
-
};
|
|
2818
|
-
}
|
|
2819
|
-
function resolveDashboardRoot(options) {
|
|
2820
|
-
const resolvePaths = options?.cwd && options.cwd !== process.cwd() ? [options.cwd, process.cwd()] : options?.cwd ? [options.cwd] : void 0;
|
|
2821
|
-
let dashboardPackageRoot;
|
|
2822
|
-
let dashboardManifest;
|
|
2823
|
-
try {
|
|
2824
|
-
const dashboardPackageJsonPath = require.resolve(`${ANALYZE_DASHBOARD_PACKAGE_NAME}/package.json`, { paths: resolvePaths });
|
|
2825
|
-
dashboardPackageRoot = path.dirname(dashboardPackageJsonPath);
|
|
2826
|
-
dashboardManifest = readDashboardManifest(dashboardPackageJsonPath);
|
|
2827
|
-
} catch {
|
|
2828
|
-
dashboardPackageRoot = void 0;
|
|
2829
|
-
dashboardManifest = void 0;
|
|
2830
|
-
}
|
|
2831
|
-
if (dashboardPackageRoot) {
|
|
2832
|
-
const devResolved = resolveDashboardDevRoot(dashboardPackageRoot, dashboardManifest);
|
|
2833
|
-
const distResolved = resolveDashboardDistRoot(dashboardPackageRoot, dashboardManifest);
|
|
2834
|
-
const resolved = options?.watch ? devResolved ?? distResolved : distResolved ?? devResolved;
|
|
2835
|
-
if (resolved) return resolved;
|
|
2836
|
-
}
|
|
2837
|
-
logger_default.warn(`[weapp-vite ui] 未安装可选仪表盘包 ${colors.bold(colors.green(ANALYZE_DASHBOARD_PACKAGE_NAME))},已自动降级关闭 dashboard 能力。`);
|
|
2838
|
-
logger_default.info(`如需启用,请执行 ${colors.bold(colors.green(createInstallCommand(options?.packageManagerAgent)))}`);
|
|
2839
|
-
}
|
|
2840
|
-
async function waitForServerExit(server) {
|
|
2841
|
-
let resolved = false;
|
|
2842
|
-
const cleanup = async () => {
|
|
2843
|
-
if (resolved) return;
|
|
2844
|
-
resolved = true;
|
|
2845
|
-
try {
|
|
2846
|
-
await server.close();
|
|
2847
|
-
} catch (error) {
|
|
2848
|
-
logger_default.error(error);
|
|
2849
|
-
}
|
|
2850
|
-
};
|
|
2851
|
-
const signals = ["SIGINT", "SIGTERM"];
|
|
2852
|
-
await new Promise((resolvePromise) => {
|
|
2853
|
-
const resolveOnce = async () => {
|
|
2854
|
-
await cleanup();
|
|
2855
|
-
signals.forEach((signal) => {
|
|
2856
|
-
process.removeListener(signal, resolveOnce);
|
|
2857
|
-
});
|
|
2858
|
-
resolvePromise();
|
|
2859
|
-
};
|
|
2860
|
-
signals.forEach((signal) => {
|
|
2861
|
-
process.once(signal, resolveOnce);
|
|
2862
|
-
});
|
|
2863
|
-
server.httpServer?.once("close", resolveOnce);
|
|
2864
|
-
});
|
|
2865
|
-
}
|
|
2866
|
-
async function startAnalyzeDashboard(result, options) {
|
|
2867
|
-
const resolved = resolveDashboardRoot(options);
|
|
2868
|
-
if (!resolved) return;
|
|
2869
|
-
const { root, configFile } = resolved;
|
|
2870
|
-
const state = {
|
|
2871
|
-
current: result,
|
|
2872
|
-
previous: options.previousResult ?? null,
|
|
2873
|
-
artifacts: options.artifacts
|
|
2874
|
-
};
|
|
2875
|
-
const runtimeEvents = { current: [createDashboardRuntimeEvent({
|
|
2876
|
-
kind: "command",
|
|
2877
|
-
level: "success",
|
|
2878
|
-
title: options?.watch ? "dashboard watch session started" : "dashboard static session started",
|
|
2879
|
-
detail: options?.watch ? "weapp-vite UI 已进入实时分析模式,后续 analyze 结果会继续推送到 dashboard。" : "weapp-vite UI 已进入静态分析模式,当前页面展示的是一次性分析结果。",
|
|
2880
|
-
tags: options?.watch ? ["watch", "analyze"] : ["static", "analyze"]
|
|
2881
|
-
}), ...(options?.initialEvents ?? []).map((event) => createDashboardRuntimeEvent(event))] };
|
|
2882
|
-
const devframe = createAnalyzeDashboardDevframe({
|
|
2883
|
-
getAnalyzeSnapshot: () => state,
|
|
2884
|
-
getRuntimeEvents: () => runtimeEvents.current,
|
|
2885
|
-
roots: {
|
|
2886
|
-
pluginRoot: options?.pluginRoot,
|
|
2887
|
-
projectRoot: options?.cwd,
|
|
2888
|
-
srcRoot: options?.srcRoot ?? (options?.cwd ? path.resolve(options.cwd, "src") : void 0)
|
|
2889
|
-
}
|
|
2890
|
-
});
|
|
2891
|
-
const plugins = [createAnalyzeDashboardViteBridge(devframe.definition)];
|
|
2892
|
-
const serverOptions = {
|
|
2893
|
-
root,
|
|
2894
|
-
configFile: configFile ?? false,
|
|
2895
|
-
clearScreen: false,
|
|
2896
|
-
appType: "spa",
|
|
2897
|
-
publicDir: false,
|
|
2898
|
-
plugins,
|
|
2899
|
-
server: {
|
|
2900
|
-
host: "127.0.0.1",
|
|
2901
|
-
port: 0,
|
|
2902
|
-
watch: { ignored: ["**/*"] }
|
|
2903
|
-
},
|
|
2904
|
-
logLevel: "error"
|
|
2905
|
-
};
|
|
2906
|
-
const server = await createServer(serverOptions);
|
|
2907
|
-
const requestedPort = typeof serverOptions.server?.port === "number" ? serverOptions.server.port : void 0;
|
|
2908
|
-
await server.listen(requestedPort);
|
|
2909
|
-
const urls = (() => {
|
|
2910
|
-
const resolved = server.resolvedUrls;
|
|
2911
|
-
if (!resolved) return [];
|
|
2912
|
-
return [...resolved.local ?? [], ...resolved.network ?? []];
|
|
2913
|
-
})();
|
|
2914
|
-
const authCode = refreshTempAuthCode();
|
|
2915
|
-
const authenticatedUrls = urls.map((url) => buildOtpAuthUrl(url, authCode));
|
|
2916
|
-
let closed = false;
|
|
2917
|
-
const waitPromise = waitForServerExit(server).then(() => {
|
|
2918
|
-
closed = true;
|
|
2919
|
-
state.artifacts = /* @__PURE__ */ new Map();
|
|
2920
|
-
devframe.dispose();
|
|
2921
|
-
});
|
|
2922
|
-
const emitRuntimeEvents = (events) => {
|
|
2923
|
-
if (events.length === 0) return;
|
|
2924
|
-
const nextEvents = events.map((event) => createDashboardRuntimeEvent(event));
|
|
2925
|
-
runtimeEvents.current = [...nextEvents, ...runtimeEvents.current].slice(0, 24);
|
|
2926
|
-
devframe.syncRuntimeEvents();
|
|
2927
|
-
};
|
|
2928
|
-
const handle = {
|
|
2929
|
-
async update(nextResult, artifacts, previousResult) {
|
|
2930
|
-
if (closed) return;
|
|
2931
|
-
state.previous = previousResult ?? state.current;
|
|
2932
|
-
state.current = nextResult;
|
|
2933
|
-
state.artifacts = artifacts;
|
|
2934
|
-
emitRuntimeEvents([{
|
|
2935
|
-
kind: "build",
|
|
2936
|
-
level: "info",
|
|
2937
|
-
title: "analyze payload refreshed",
|
|
2938
|
-
detail: `已推送新的 analyze 结果,当前包含 ${nextResult.packages.length} 个包与 ${nextResult.modules.length} 个模块。`,
|
|
2939
|
-
tags: ["analyze", "refresh"]
|
|
2940
|
-
}]);
|
|
2941
|
-
devframe.notifyAnalyzeUpdate();
|
|
2942
|
-
},
|
|
2943
|
-
emitRuntimeEvents,
|
|
2944
|
-
waitForExit: () => waitPromise,
|
|
2945
|
-
close: async () => {
|
|
2946
|
-
closed = true;
|
|
2947
|
-
await server.close();
|
|
2948
|
-
},
|
|
2949
|
-
urls: authenticatedUrls
|
|
2950
|
-
};
|
|
2951
|
-
if (options?.watch) {
|
|
2952
|
-
if (!options.silentStartupLog) {
|
|
2953
|
-
logger_default.info("weapp-vite UI 已启动(分析视图,实时模式),按 Ctrl+C 退出。");
|
|
2954
|
-
for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
|
|
2955
|
-
}
|
|
2956
|
-
return handle;
|
|
2957
|
-
}
|
|
2958
|
-
if (!options?.silentStartupLog) {
|
|
2959
|
-
logger_default.info("weapp-vite UI 已启动(分析视图,静态模式),按 Ctrl+C 退出。");
|
|
2960
|
-
for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
|
|
2961
|
-
}
|
|
2962
|
-
await waitPromise;
|
|
2963
|
-
}
|
|
2964
|
-
//#endregion
|
|
2965
|
-
//#region src/cli/processCleanup.ts
|
|
2966
|
-
function isSassEmbeddedChild(handle) {
|
|
2967
|
-
if (!handle || typeof handle !== "object") return false;
|
|
2968
|
-
const child = handle;
|
|
2969
|
-
const spawnfile = typeof child.spawnfile === "string" ? child.spawnfile : "";
|
|
2970
|
-
const spawnargs = Array.isArray(child.spawnargs) ? child.spawnargs : [];
|
|
2971
|
-
return Boolean("kill" in handle && "spawnfile" in handle && (spawnfile.includes("sass-embedded") || spawnfile.includes("dart-sass") && spawnargs.includes("--embedded")));
|
|
2972
|
-
}
|
|
2973
|
-
function terminateStaleSassEmbeddedProcess() {
|
|
2974
|
-
const getHandles = process._getActiveHandles;
|
|
2975
|
-
const handles = typeof getHandles === "function" ? getHandles() : void 0;
|
|
2976
|
-
if (!Array.isArray(handles)) return;
|
|
2977
|
-
for (const handle of handles) if (isSassEmbeddedChild(handle)) try {
|
|
2978
|
-
handle.kill();
|
|
2979
|
-
} catch {}
|
|
2980
|
-
}
|
|
2981
2356
|
//#endregion
|
|
2982
2357
|
//#region src/cli/commands/analyze.ts
|
|
2983
2358
|
function normalizeDisplayPath(value) {
|
|
@@ -3071,13 +2446,17 @@ function printAnalysisSummary(result) {
|
|
|
3071
2446
|
if (duplicates.length > limit) logger_default.info(`- …其余 ${duplicates.length - limit} 项请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
|
|
3072
2447
|
}
|
|
3073
2448
|
function printBudgetCheckSummary(result) {
|
|
3074
|
-
const exceededItems = createAnalyzeBudgetCheck(result).filter((item) => item.status === "exceeded");
|
|
2449
|
+
const exceededItems = createAnalyzeBudgetCheck(result).filter((item) => item.status === "exceeded" || item.status === "unknown");
|
|
3075
2450
|
if (exceededItems.length === 0) {
|
|
3076
2451
|
logger_default.success("包体预算检查通过");
|
|
3077
2452
|
return false;
|
|
3078
2453
|
}
|
|
3079
2454
|
logger_default.error(`包体预算检查失败:${exceededItems.length} 项超限`);
|
|
3080
|
-
for (const item of exceededItems)
|
|
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
|
+
}
|
|
3081
2460
|
return true;
|
|
3082
2461
|
}
|
|
3083
2462
|
function printGlassEaselCheckSummary(result) {
|
|
@@ -3253,107 +2632,111 @@ function printHmrProfileAnalysisSummary(result, configService) {
|
|
|
3253
2632
|
}
|
|
3254
2633
|
}
|
|
3255
2634
|
function registerAnalyzeCommand(cli) {
|
|
3256
|
-
cli.command("analyze [root]", "analyze 两端包体与源码映射").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) => {
|
|
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) => {
|
|
3257
2636
|
filterDuplicateOptions(options);
|
|
2637
|
+
options.uiHost = parseDashboardUiHost(options.uiHost);
|
|
3258
2638
|
const configFile = resolveConfigFile(options);
|
|
3259
2639
|
const outputJson = coerceBooleanOption(options.json);
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
logRuntimeTarget(targets, {
|
|
3280
|
-
silent: outputJson || outputMarkdown,
|
|
3281
|
-
resolvedConfigPlatform: ctx.configService.platform
|
|
3282
|
-
});
|
|
3283
|
-
const outputOption = typeof options.output === "string" ? options.output.trim() : "";
|
|
3284
|
-
if (options.hmrProfile !== void 0 && options.hmrProfile !== false) {
|
|
3285
|
-
const profileOption = typeof options.hmrProfile === "string" && options.hmrProfile.trim() ? options.hmrProfile.trim() : ctx.configService.weappViteConfig.hmr?.profileJson;
|
|
3286
|
-
const profilePath = resolveHmrProfileJsonPath({
|
|
3287
|
-
cwd: ctx.configService.cwd,
|
|
3288
|
-
option: profileOption,
|
|
3289
|
-
fallbackToDefault: true
|
|
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
|
|
3290
2659
|
});
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
if (outputJson) {
|
|
3295
|
-
if (!writtenPath) process.stdout.write(`${JSON.stringify(hmrProfileResult, null, 2)}\n`);
|
|
3296
|
-
} else printHmrProfileAnalysisSummary(hmrProfileResult, ctx.configService);
|
|
3297
|
-
return;
|
|
3298
|
-
}
|
|
3299
|
-
if (coerceBooleanOption(options.preload)) {
|
|
3300
|
-
if (targets.kind !== "miniprogram" || ctx.configService.platform !== "weapp") throw new Error("preloadRule 分析目前仅支持微信小程序平台。");
|
|
3301
|
-
const packageAnalysis = await analyzeSubpackages(ctx);
|
|
3302
|
-
const preloadResult = await analyzePreloadRules(ctx, { packageAnalysis });
|
|
3303
|
-
const writtenPath = await writeAnalyzeResult(preloadResult, outputOption, ctx.configService);
|
|
3304
|
-
if (outputJson && !writtenPath) process.stdout.write(`${JSON.stringify(preloadResult, null, 2)}\n`);
|
|
3305
|
-
if (!outputJson && !writtenPath) printPreloadAnalysisSummary(preloadResult);
|
|
3306
|
-
return;
|
|
3307
|
-
}
|
|
3308
|
-
if (getBackendForCapability(targets, "web", "analyze")) {
|
|
3309
|
-
const webResult = createWebAnalyzeResult(ctx.configService, { platform: "web" });
|
|
3310
|
-
const writtenPath = await writeAnalyzeResult(webResult, outputOption, ctx.configService);
|
|
3311
|
-
if (outputJson) {
|
|
3312
|
-
if (!writtenPath) process.stdout.write(`${JSON.stringify(webResult, null, 2)}\n`);
|
|
3313
|
-
} else printWebAnalysisSummary(webResult);
|
|
3314
|
-
return;
|
|
3315
|
-
}
|
|
3316
|
-
if (!getBackendForCapability(targets, "miniprogram", "analyze")) {
|
|
3317
|
-
logger_default.warn("当前命令不支持该平台,请通过 --platform weapp 或 --platform web 指定目标。");
|
|
3318
|
-
return;
|
|
3319
|
-
}
|
|
3320
|
-
const previousResult = await readLatestAnalyzeHistorySnapshot(ctx.configService);
|
|
3321
|
-
ctx.runtimeState.glassEasel.silent = Boolean(outputJson || outputMarkdown || outputPrReport || glassEaselCheck);
|
|
3322
|
-
const artifactSnapshot = launchDashboard ? createDashboardArtifactSnapshot() : void 0;
|
|
3323
|
-
const result = artifactSnapshot ? await analyzeSubpackages(ctx, { onArtifact: artifactSnapshot.capture }) : await analyzeSubpackages(ctx);
|
|
3324
|
-
await writeAnalyzeHistorySnapshot(result, ctx.configService);
|
|
3325
|
-
const writtenPath = await writeAnalyzeResult(result, outputOption, ctx.configService, outputPrReport ? "pr" : outputMarkdown ? "markdown" : "json", previousResult);
|
|
3326
|
-
if (outputPrReport) {
|
|
3327
|
-
if (!writtenPath) process.stdout.write(`${createAnalyzePrMarkdownReport(result, previousResult)}\n`);
|
|
3328
|
-
} else if (outputMarkdown) {
|
|
3329
|
-
if (!writtenPath) process.stdout.write(`${createAnalyzeMarkdownReport(result, previousResult)}\n`);
|
|
3330
|
-
} else if (outputJson) {
|
|
3331
|
-
if (!writtenPath) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
3332
|
-
}
|
|
3333
|
-
const budgetFailed = budgetCheck ? printBudgetCheckSummary(result) : false;
|
|
3334
|
-
const glassEaselFailed = glassEaselCheck && !outputJson ? printGlassEaselCheckSummary(result) : glassEaselCheck && result.glassEasel.summary.errors > 0;
|
|
3335
|
-
if (budgetFailed || glassEaselFailed) process.exitCode = 1;
|
|
3336
|
-
if (budgetCheck || glassEaselCheck) return;
|
|
3337
|
-
if (artifactSnapshot) {
|
|
3338
|
-
printAnalysisSummary(result);
|
|
3339
|
-
await startAnalyzeDashboard(result, {
|
|
3340
|
-
artifacts: artifactSnapshot.files,
|
|
3341
|
-
cwd: ctx.configService.cwd,
|
|
3342
|
-
packageManagerAgent: ctx.configService.packageManager.agent,
|
|
3343
|
-
pluginRoot: ctx.configService.absolutePluginRoot,
|
|
3344
|
-
srcRoot: ctx.configService.absoluteSrcRoot,
|
|
3345
|
-
previousResult
|
|
2660
|
+
logRuntimeTarget(targets, {
|
|
2661
|
+
silent: outputJson || outputMarkdown,
|
|
2662
|
+
resolvedConfigPlatform: ctx.configService.platform
|
|
3346
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();
|
|
3347
2738
|
}
|
|
3348
|
-
}
|
|
3349
|
-
logger_default.error(error);
|
|
3350
|
-
process.exitCode = 1;
|
|
3351
|
-
} finally {
|
|
3352
|
-
if (ctx) {
|
|
3353
|
-
for (const backend of [...targets.entries].reverse()) if (backend.descriptor.capabilities.analyze) await backend.driver?.close?.(ctx);
|
|
3354
|
-
}
|
|
3355
|
-
terminateStaleSassEmbeddedProcess();
|
|
3356
|
-
}
|
|
2739
|
+
});
|
|
3357
2740
|
});
|
|
3358
2741
|
}
|
|
3359
2742
|
//#endregion
|
|
@@ -3453,6 +2836,631 @@ function setCommandNodeEnv(nodeEnv) {
|
|
|
3453
2836
|
process.env.NODE_ENV = nodeEnv;
|
|
3454
2837
|
}
|
|
3455
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
|
|
3456
3464
|
//#region src/cli/commands/build.ts
|
|
3457
3465
|
function emitDashboardEvents$1(handle, events) {
|
|
3458
3466
|
handle?.emitRuntimeEvents(events);
|
|
@@ -3488,22 +3496,36 @@ function scheduleCompletedProductionBuildExit(options, analyzeHandle) {
|
|
|
3488
3496
|
}, 0).unref?.();
|
|
3489
3497
|
}
|
|
3490
3498
|
function registerBuildCommand(cli) {
|
|
3491
|
-
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
|
|
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) => {
|
|
3492
3500
|
let analyzeHandle;
|
|
3501
|
+
let session;
|
|
3493
3502
|
let ctx;
|
|
3494
3503
|
let targets;
|
|
3504
|
+
let uploadMetadataRollback;
|
|
3495
3505
|
let buildCompleted = false;
|
|
3506
|
+
let buildFailed = false;
|
|
3496
3507
|
try {
|
|
3508
|
+
options = { ...options };
|
|
3497
3509
|
filterDuplicateOptions(options);
|
|
3510
|
+
options.uiHost = parseDashboardUiHost(options.uiHost);
|
|
3511
|
+
let uploadOptions = resolveBuildUploadOptions(cli, options);
|
|
3498
3512
|
setCommandNodeEnv("production");
|
|
3499
3513
|
const cwd = root ?? process.cwd();
|
|
3500
3514
|
const configFile = resolveConfigFile(options);
|
|
3501
3515
|
targets = resolveRuntimeTargets(options);
|
|
3516
|
+
if (uploadOptions) validateAutoUploadMetadata(uploadOptions, "upload");
|
|
3517
|
+
if (uploadOptions && !getBackendForCapability(targets, "miniprogram", "build")) throw new Error("--upload 仅支持包含小程序目标的构建,不能用于纯 Web 构建。");
|
|
3502
3518
|
const inlineConfig = createInlineConfig(targets, {
|
|
3503
3519
|
scope: options.scope,
|
|
3504
3520
|
inlineConfig: createBuildInlineConfig(options)
|
|
3505
3521
|
});
|
|
3506
|
-
|
|
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({
|
|
3507
3529
|
cwd,
|
|
3508
3530
|
mode: options.mode ?? "production",
|
|
3509
3531
|
configFile,
|
|
@@ -3513,7 +3535,16 @@ function registerBuildCommand(cli) {
|
|
|
3513
3535
|
emitDefaultAutoImportOutputs: false,
|
|
3514
3536
|
preloadAppEntry: false
|
|
3515
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));
|
|
3516
3546
|
const { configService } = ctx;
|
|
3547
|
+
const uploadTarget = uploadOptions ? await createUploadTarget(configService, uploadOptions, "upload") : void 0;
|
|
3517
3548
|
const miniBackend = getBackendForCapability(targets, "miniprogram", "build");
|
|
3518
3549
|
const webBackend = getBackendForCapability(targets, "web", "build");
|
|
3519
3550
|
logRuntimeTarget(targets, { resolvedConfigPlatform: configService.platform });
|
|
@@ -3521,7 +3552,7 @@ function registerBuildCommand(cli) {
|
|
|
3521
3552
|
for (const backend of targets.select("build")) {
|
|
3522
3553
|
if (backend.descriptor.id === "miniprogram") {
|
|
3523
3554
|
const miniBuildStartedAt = Date.now();
|
|
3524
|
-
const output = await backend.driver.build(ctx, options);
|
|
3555
|
+
const output = await session.run(() => backend.driver.build(ctx, options));
|
|
3525
3556
|
const miniBuildDurationMs = Date.now() - miniBuildStartedAt;
|
|
3526
3557
|
logger_default.success(`小程序构建完成,耗时:${colors.green(formatDuration(miniBuildDurationMs))}`);
|
|
3527
3558
|
if (!Array.isArray(output) && "output" in output) logBuildPackageSizeReport({
|
|
@@ -3538,6 +3569,7 @@ function registerBuildCommand(cli) {
|
|
|
3538
3569
|
const analyzeDurationMs = Date.now() - analyzeStartedAt;
|
|
3539
3570
|
analyzeHandle = await startAnalyzeDashboard(analyzeResult, {
|
|
3540
3571
|
watch: true,
|
|
3572
|
+
uiHost: options.uiHost,
|
|
3541
3573
|
artifacts: artifactSnapshot.files,
|
|
3542
3574
|
cwd: configService.cwd,
|
|
3543
3575
|
packageManagerAgent: configService.packageManager.agent,
|
|
@@ -3567,7 +3599,7 @@ function registerBuildCommand(cli) {
|
|
|
3567
3599
|
if (backend.descriptor.id !== "web" || !webConfig?.enabled) continue;
|
|
3568
3600
|
const webBuildStartedAt = Date.now();
|
|
3569
3601
|
try {
|
|
3570
|
-
await backend.driver.build(ctx, options);
|
|
3602
|
+
await session.run(() => backend.driver.build(ctx, options));
|
|
3571
3603
|
const webBuildDurationMs = Date.now() - webBuildStartedAt;
|
|
3572
3604
|
logger_default.success(`Web 构建完成,输出目录:${colors.green(configService.relativeCwd(webConfig.outDir))},耗时:${colors.green(formatDuration(webBuildDurationMs))}`);
|
|
3573
3605
|
emitDashboardEvents$1(analyzeHandle, [{
|
|
@@ -3591,6 +3623,7 @@ function registerBuildCommand(cli) {
|
|
|
3591
3623
|
throw error;
|
|
3592
3624
|
}
|
|
3593
3625
|
}
|
|
3626
|
+
if (uploadTarget && uploadOptions) await executeUploadTarget(uploadTarget, uploadOptions, "upload");
|
|
3594
3627
|
if (miniBackend) logBuildAppFinish(configService, void 0, { skipWeb: !webBackend });
|
|
3595
3628
|
if (options.open && getBackendForCapability(targets, "miniprogram", "ide")) {
|
|
3596
3629
|
emitDashboardEvents$1(analyzeHandle, [{
|
|
@@ -3600,7 +3633,8 @@ function registerBuildCommand(cli) {
|
|
|
3600
3633
|
detail: "构建完成后准备打开 IDE 项目。",
|
|
3601
3634
|
tags: ["ide", "open"]
|
|
3602
3635
|
}]);
|
|
3603
|
-
|
|
3636
|
+
const generatedProjectConfig = configService.multiPlatform.projectConfigs !== void 0;
|
|
3637
|
+
await openIde(configService.platform, resolveIdeProjectPath(generatedProjectConfig ? configService.outDir : configService.mpDistRoot, generatedProjectConfig), {
|
|
3604
3638
|
openRecovery: options.openRecovery,
|
|
3605
3639
|
trustProject: options.trustProject,
|
|
3606
3640
|
openStrategy: options.ideOpenStrategy
|
|
@@ -3608,9 +3642,12 @@ function registerBuildCommand(cli) {
|
|
|
3608
3642
|
}
|
|
3609
3643
|
if (analyzeHandle) await analyzeHandle.waitForExit();
|
|
3610
3644
|
buildCompleted = true;
|
|
3645
|
+
} catch (error) {
|
|
3646
|
+
buildFailed = true;
|
|
3647
|
+
throw error;
|
|
3611
3648
|
} finally {
|
|
3612
|
-
if (
|
|
3613
|
-
|
|
3649
|
+
if (buildFailed) await session?.close().catch((error) => logger_default.error(error));
|
|
3650
|
+
else await session?.close();
|
|
3614
3651
|
if (buildCompleted) scheduleCompletedProductionBuildExit(options, analyzeHandle);
|
|
3615
3652
|
}
|
|
3616
3653
|
});
|
|
@@ -3623,6 +3660,39 @@ function registerCloseCommand(cli) {
|
|
|
3623
3660
|
});
|
|
3624
3661
|
}
|
|
3625
3662
|
//#endregion
|
|
3663
|
+
//#region src/cli/commands/doctor.ts
|
|
3664
|
+
function registerDoctorCommand(cli) {
|
|
3665
|
+
cli.command("doctor [root]", "检查项目、源码、产物与显式选择的宿主证据").option("--targets <targets>", "编译目标,逗号分隔(默认 weapp)").option("--platform <platform>", "单个编译目标").option("--source <directory>", "静态源码目录(默认 src,不执行配置推断)").option("--artifact <directory>", "复用最终产物目录(新鲜度未验证)").option("--build", "显式执行配置和构建,使用独立输出目录").option("--runtime", "连接已打开项目的宿主探针,不启动 IDE").option("--runtime-port <port>", "已有 automator 会话的端口(默认项目派生端口)").option("--runtime-cli <path>", "只读检查所选 CLI 的可执行条件,不回退安装").option("--runtime-service-port <port>", "检查指定回环 TCP 服务监听,不推断宿主身份").option("--runtime-login", "显式运行原生登录查询,可能启动 IDE;需要 --runtime-cli").option("--format <format>", "terminal | json | sarif", { default: "terminal" }).action(async (root, options) => {
|
|
3666
|
+
const { formatDoctorReport, runDoctor } = await import("./doctor.mjs");
|
|
3667
|
+
const format = options.format;
|
|
3668
|
+
if (![
|
|
3669
|
+
"terminal",
|
|
3670
|
+
"json",
|
|
3671
|
+
"sarif"
|
|
3672
|
+
].includes(format)) {
|
|
3673
|
+
process.stderr.write("doctor: format 必须为 terminal、json 或 sarif\n");
|
|
3674
|
+
process.exitCode = 2;
|
|
3675
|
+
return;
|
|
3676
|
+
}
|
|
3677
|
+
const report = await runDoctor({
|
|
3678
|
+
cwd: root,
|
|
3679
|
+
targets: String(options.targets ?? options.platform ?? "weapp").split(",").map((item) => item.trim()),
|
|
3680
|
+
source: options.source,
|
|
3681
|
+
artifact: options.artifact,
|
|
3682
|
+
build: options.build === true,
|
|
3683
|
+
runtime: options.runtime === true,
|
|
3684
|
+
configFile: options.config,
|
|
3685
|
+
runtimePort: options.runtimePort === void 0 ? void 0 : Number(options.runtimePort),
|
|
3686
|
+
runtimeCliPath: options.runtimeCli,
|
|
3687
|
+
runtimeServicePort: options.runtimeServicePort === void 0 ? void 0 : Number(options.runtimeServicePort),
|
|
3688
|
+
runtimeLogin: options.runtimeLogin === true ? true : void 0,
|
|
3689
|
+
onBuildLog: (text) => process.stderr.write(text)
|
|
3690
|
+
});
|
|
3691
|
+
process.stdout.write(`${formatDoctorReport(report, format)}\n`);
|
|
3692
|
+
process.exitCode = report.exitCode;
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
//#endregion
|
|
3626
3696
|
//#region src/schematics.ts
|
|
3627
3697
|
function composePath(outDir, filename) {
|
|
3628
3698
|
return `${outDir}${outDir ? "/" : ""}${filename}`;
|
|
@@ -3707,60 +3777,6 @@ async function generate(options) {
|
|
|
3707
3777
|
}
|
|
3708
3778
|
}
|
|
3709
3779
|
//#endregion
|
|
3710
|
-
//#region src/cli/loadConfig.ts
|
|
3711
|
-
async function loadConfig(configFile) {
|
|
3712
|
-
const cwd = process.cwd();
|
|
3713
|
-
let resolvedConfigFile = configFile;
|
|
3714
|
-
if (resolvedConfigFile && !path.isAbsolute(resolvedConfigFile)) resolvedConfigFile = path.resolve(cwd, resolvedConfigFile);
|
|
3715
|
-
const configEnv = {
|
|
3716
|
-
command: "serve",
|
|
3717
|
-
mode: "development"
|
|
3718
|
-
};
|
|
3719
|
-
let loaded;
|
|
3720
|
-
try {
|
|
3721
|
-
loaded = await loadViteConfigFile(configEnv, resolvedConfigFile, cwd, void 0, void 0, "runner");
|
|
3722
|
-
} catch (error) {
|
|
3723
|
-
const cjsError = createCjsConfigLoadError({
|
|
3724
|
-
error,
|
|
3725
|
-
configPath: resolvedConfigFile,
|
|
3726
|
-
cwd
|
|
3727
|
-
});
|
|
3728
|
-
if (cjsError) throw cjsError;
|
|
3729
|
-
throw error;
|
|
3730
|
-
}
|
|
3731
|
-
const weappConfigFilePath = await resolveWeappConfigFile({
|
|
3732
|
-
root: cwd,
|
|
3733
|
-
specified: resolvedConfigFile
|
|
3734
|
-
});
|
|
3735
|
-
let weappLoaded;
|
|
3736
|
-
if (weappConfigFilePath) {
|
|
3737
|
-
const normalizedWeappPath = path.resolve(weappConfigFilePath);
|
|
3738
|
-
const normalizedLoadedPath = loaded?.path ? path.resolve(loaded.path) : void 0;
|
|
3739
|
-
if (normalizedLoadedPath && normalizedLoadedPath === normalizedWeappPath) weappLoaded = loaded;
|
|
3740
|
-
else try {
|
|
3741
|
-
weappLoaded = await loadViteConfigFile(configEnv, weappConfigFilePath, cwd, void 0, void 0, "runner");
|
|
3742
|
-
} catch (error) {
|
|
3743
|
-
const cjsError = createCjsConfigLoadError({
|
|
3744
|
-
error,
|
|
3745
|
-
configPath: weappConfigFilePath,
|
|
3746
|
-
cwd
|
|
3747
|
-
});
|
|
3748
|
-
if (cjsError) throw cjsError;
|
|
3749
|
-
throw error;
|
|
3750
|
-
}
|
|
3751
|
-
}
|
|
3752
|
-
if (!loaded && !weappLoaded) return;
|
|
3753
|
-
const config = weappLoaded?.config ? defu(weappLoaded.config, loaded?.config ?? {}) : loaded?.config ?? {};
|
|
3754
|
-
const dependencySet = /* @__PURE__ */ new Set();
|
|
3755
|
-
for (const dependency of loaded?.dependencies ?? []) dependencySet.add(dependency);
|
|
3756
|
-
for (const dependency of weappLoaded?.dependencies ?? []) dependencySet.add(dependency);
|
|
3757
|
-
return {
|
|
3758
|
-
config,
|
|
3759
|
-
path: weappLoaded?.path ?? loaded?.path ?? resolvedConfigFile,
|
|
3760
|
-
dependencies: Array.from(dependencySet)
|
|
3761
|
-
};
|
|
3762
|
-
}
|
|
3763
|
-
//#endregion
|
|
3764
3780
|
//#region src/cli/commands/generate.ts
|
|
3765
3781
|
function registerGenerateCommand(cli) {
|
|
3766
3782
|
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) => {
|
|
@@ -3800,6 +3816,7 @@ const DEFAULT_FORWARD_CONSOLE_LEVELS = [
|
|
|
3800
3816
|
let activeForwardConsoleSession;
|
|
3801
3817
|
let activeForwardConsoleBridgeOptions;
|
|
3802
3818
|
let activeForwardConsoleStart;
|
|
3819
|
+
let activeForwardConsoleClose;
|
|
3803
3820
|
let forwardConsoleLifecycle = 0;
|
|
3804
3821
|
const FORWARD_CONSOLE_RETRY_DELAY_MS = 1e3;
|
|
3805
3822
|
const FORWARD_CONSOLE_RETRY_TIMES = 5;
|
|
@@ -3945,15 +3962,9 @@ async function pauseActiveForwardConsole() {
|
|
|
3945
3962
|
if (activeForwardConsoleStart) return await activeForwardConsoleStart;
|
|
3946
3963
|
if (activeForwardConsoleSession) return true;
|
|
3947
3964
|
const startTask = (async () => {
|
|
3965
|
+
let resumedSession;
|
|
3948
3966
|
try {
|
|
3949
|
-
|
|
3950
|
-
if (lifecycle !== forwardConsoleLifecycle) {
|
|
3951
|
-
await resumedSession.close();
|
|
3952
|
-
return false;
|
|
3953
|
-
}
|
|
3954
|
-
activeForwardConsoleSession = resumedSession;
|
|
3955
|
-
activeForwardConsoleBridgeOptions = bridgeOptions;
|
|
3956
|
-
return true;
|
|
3967
|
+
resumedSession = await startForwardConsoleBridge(bridgeOptions);
|
|
3957
3968
|
} catch (error) {
|
|
3958
3969
|
if (lifecycle === forwardConsoleLifecycle) {
|
|
3959
3970
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -3961,6 +3972,13 @@ async function pauseActiveForwardConsole() {
|
|
|
3961
3972
|
}
|
|
3962
3973
|
return false;
|
|
3963
3974
|
}
|
|
3975
|
+
if (lifecycle !== forwardConsoleLifecycle) {
|
|
3976
|
+
await resumedSession.close();
|
|
3977
|
+
return false;
|
|
3978
|
+
}
|
|
3979
|
+
activeForwardConsoleSession = resumedSession;
|
|
3980
|
+
activeForwardConsoleBridgeOptions = bridgeOptions;
|
|
3981
|
+
return true;
|
|
3964
3982
|
})();
|
|
3965
3983
|
activeForwardConsoleStart = startTask;
|
|
3966
3984
|
try {
|
|
@@ -3973,21 +3991,32 @@ async function pauseActiveForwardConsole() {
|
|
|
3973
3991
|
/**
|
|
3974
3992
|
* @description 关闭当前 DevTools 日志桥并释放共享 automator 会话。
|
|
3975
3993
|
*/
|
|
3976
|
-
|
|
3994
|
+
function closeActiveForwardConsole() {
|
|
3995
|
+
if (activeForwardConsoleClose) return activeForwardConsoleClose;
|
|
3977
3996
|
forwardConsoleLifecycle += 1;
|
|
3978
3997
|
const session = activeForwardConsoleSession;
|
|
3979
3998
|
const pendingStart = activeForwardConsoleStart;
|
|
3980
3999
|
activeForwardConsoleSession = void 0;
|
|
3981
4000
|
activeForwardConsoleBridgeOptions = void 0;
|
|
3982
|
-
|
|
3983
|
-
|
|
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;
|
|
3984
4011
|
}
|
|
3985
4012
|
/**
|
|
3986
4013
|
* @description 在 weapp 开发态按需启动控制台转发。
|
|
3987
4014
|
*/
|
|
3988
4015
|
async function maybeStartForwardConsole(options) {
|
|
3989
|
-
if (options.platform !== "weapp") return false;
|
|
3990
|
-
const
|
|
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);
|
|
3991
4020
|
if (!projectPath) return false;
|
|
3992
4021
|
if (activeForwardConsoleSession) return true;
|
|
3993
4022
|
if (activeForwardConsoleStart) return await activeForwardConsoleStart;
|
|
@@ -4026,7 +4055,7 @@ async function maybeStartForwardConsole(options) {
|
|
|
4026
4055
|
openedOnly: true,
|
|
4027
4056
|
preferOpenedSession: true
|
|
4028
4057
|
};
|
|
4029
|
-
return
|
|
4058
|
+
return activateSession(await startForwardConsoleBridge(recoveredOptions), recoveredOptions);
|
|
4030
4059
|
} catch (recoveryError) {
|
|
4031
4060
|
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
4032
4061
|
const message = recoveryError instanceof Error ? recoveryError.message : String(recoveryError);
|
|
@@ -4035,7 +4064,7 @@ async function maybeStartForwardConsole(options) {
|
|
|
4035
4064
|
}
|
|
4036
4065
|
};
|
|
4037
4066
|
try {
|
|
4038
|
-
return
|
|
4067
|
+
return activateSession(await startForwardConsoleBridge(bridgeOptions), bridgeOptions);
|
|
4039
4068
|
} catch (error) {
|
|
4040
4069
|
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
4041
4070
|
if (options.recoverAutomatorSession && isRecoverableAutomatorStartError(error)) return await recoverAutomatorSession();
|
|
@@ -4051,7 +4080,7 @@ async function maybeStartForwardConsole(options) {
|
|
|
4051
4080
|
preferOpenedSession: true
|
|
4052
4081
|
};
|
|
4053
4082
|
try {
|
|
4054
|
-
return
|
|
4083
|
+
return activateSession(await startForwardConsoleBridge(fallbackOptions), fallbackOptions);
|
|
4055
4084
|
} catch (fallbackError) {
|
|
4056
4085
|
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
4057
4086
|
const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
@@ -4072,19 +4101,6 @@ async function maybeStartForwardConsole(options) {
|
|
|
4072
4101
|
function isFiniteNumber(value) {
|
|
4073
4102
|
return typeof value === "number" && Number.isFinite(value);
|
|
4074
4103
|
}
|
|
4075
|
-
function parseLatestHmrProfileSample(content) {
|
|
4076
|
-
const lines = content.split(/\r?\n/);
|
|
4077
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
4078
|
-
const trimmed = lines[index]?.trim();
|
|
4079
|
-
if (!trimmed) continue;
|
|
4080
|
-
try {
|
|
4081
|
-
const parsed = JSON.parse(trimmed);
|
|
4082
|
-
if (isFiniteNumber(parsed.totalMs)) return parsed;
|
|
4083
|
-
} catch {
|
|
4084
|
-
continue;
|
|
4085
|
-
}
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4088
4104
|
function formatPhaseHint(sample) {
|
|
4089
4105
|
const topPhase = [
|
|
4090
4106
|
{
|
|
@@ -4274,7 +4290,8 @@ async function readLatestHmrProfileSummary(options) {
|
|
|
4274
4290
|
if (!profilePath) return;
|
|
4275
4291
|
const content = await fs$1.readFile(profilePath, "utf8").catch(() => void 0);
|
|
4276
4292
|
if (!content) return;
|
|
4277
|
-
const
|
|
4293
|
+
const { samples, skippedLineCount } = readHmrProfileLines(content);
|
|
4294
|
+
const sample = samples.at(-1);
|
|
4278
4295
|
if (!sample || !isFiniteNumber(sample.totalMs)) return;
|
|
4279
4296
|
const relativeCwd = options.relativeCwd ?? ((value) => value);
|
|
4280
4297
|
const segments = [`[hmr] 最近一次热更新 ${sample.totalMs.toFixed(2)} ms`];
|
|
@@ -4282,7 +4299,8 @@ async function readLatestHmrProfileSummary(options) {
|
|
|
4282
4299
|
if (sample.file) segments.push(relativeCwd(sample.file));
|
|
4283
4300
|
const phaseHint = formatPhaseHint(sample);
|
|
4284
4301
|
if (phaseHint) segments.push(`主耗时 ${phaseHint}`);
|
|
4285
|
-
if (isFiniteNumber(sample.loadCount) || isFiniteNumber(sample.resolveCount) || isFiniteNumber(sample.chunkEmitCount) || isFiniteNumber(sample.skippedLoadedCount)) segments.push(`load/resolve/chunk/skip ${sample.loadCount ??
|
|
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} 条不兼容、未完成或无效记录`);
|
|
4286
4304
|
return {
|
|
4287
4305
|
file: sample.file,
|
|
4288
4306
|
profilePath,
|
|
@@ -4335,20 +4353,19 @@ async function runIdeDoctor(projectPath, options) {
|
|
|
4335
4353
|
status: "unknown"
|
|
4336
4354
|
};
|
|
4337
4355
|
if (resolvedCli.cliPath) try {
|
|
4338
|
-
await
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
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}),不能据此认定未登录。`
|
|
4345
4364
|
};
|
|
4346
|
-
} catch
|
|
4365
|
+
} catch {
|
|
4347
4366
|
loginCheck = {
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
status: "warning",
|
|
4351
|
-
value: false
|
|
4367
|
+
message: "登录查询执行失败,登录状态仍未确认。",
|
|
4368
|
+
status: "unknown"
|
|
4352
4369
|
};
|
|
4353
4370
|
}
|
|
4354
4371
|
const projectCheck = projectPath ? {
|
|
@@ -4377,11 +4394,20 @@ async function runIdeDoctor(projectPath, options) {
|
|
|
4377
4394
|
status: "ok",
|
|
4378
4395
|
value: true
|
|
4379
4396
|
};
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
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
|
+
}
|
|
4385
4411
|
} catch (error) {
|
|
4386
4412
|
automatorCheck = {
|
|
4387
4413
|
fix: "先执行 `wv open` 打开项目;若服务端口已开启,关闭多余 DevTools 窗口后重试。",
|
|
@@ -4530,7 +4556,7 @@ async function runIdeCommand(action, root, options) {
|
|
|
4530
4556
|
* @description 注册 IDE 相关子命令。
|
|
4531
4557
|
*/
|
|
4532
4558
|
function registerIdeCommand(cli) {
|
|
4533
|
-
cli.command("ide [action] [root]", "run Wechat DevTools utility actions and log bridge commands").option("-o, --open", "[boolean] open ide before attaching log bridge").option("-p, --platform <platform>", "[string] target platform (weapp | web)").option("--project-config <path>", "[string] project config path (miniprogram only)").option("--json", "[boolean] output the doctor report as JSON").option("--ticket <value>", "[string] ticket used by `ide ticket:set`").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("--strict", "[boolean] fail when doctor finds blocking environment errors").option("--no-open-recovery", "[boolean] disable automatic
|
|
4559
|
+
cli.command("ide [action] [root]", "run Wechat DevTools utility actions and log bridge commands").option("-o, --open", "[boolean] open ide before attaching log bridge").option("-p, --platform <platform>", "[string] target platform (weapp | web)").option("--project-config <path>", "[string] project config path (miniprogram only)").option("--json", "[boolean] output the doctor report as JSON").option("--ticket <value>", "[string] ticket used by `ide ticket:set`").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("--strict", "[boolean] fail when doctor finds blocking environment errors").option("--no-open-recovery", "[boolean] disable automatic target-project open retry (preserves existing IDE windows)").action(async (action, root, options) => {
|
|
4534
4560
|
await runIdeCommand(action, root, options);
|
|
4535
4561
|
});
|
|
4536
4562
|
}
|
|
@@ -4546,405 +4572,6 @@ function registerInitCommand(cli) {
|
|
|
4546
4572
|
});
|
|
4547
4573
|
}
|
|
4548
4574
|
//#endregion
|
|
4549
|
-
//#region src/cli/mcpClient/shared.ts
|
|
4550
|
-
const CODEX_BLOCK_PREFIX = "# >>> weapp-vite mcp ";
|
|
4551
|
-
const CODEX_BLOCK_SUFFIX = " >>>";
|
|
4552
|
-
const CODEX_BLOCK_END_PREFIX = "# <<< weapp-vite mcp ";
|
|
4553
|
-
const CODEX_BLOCK_END_SUFFIX = " <<<";
|
|
4554
|
-
const DEFAULT_HTTP_TIMEOUT_MS = 1500;
|
|
4555
|
-
const WORKSPACE_FOLDER_TOKEN = "${workspaceFolder}";
|
|
4556
|
-
const REG_CodexUrl = /^\s*url\s*=\s*"([^"]+)"/m;
|
|
4557
|
-
const REG_CodexCommand = /^\s*command\s*=\s*"([^"]+)"/m;
|
|
4558
|
-
const REG_CodexArgs = /^\s*args\s*=\s*\[([^\]]*)\]/m;
|
|
4559
|
-
const REG_FirstTomlString = /"([^"]+)"/;
|
|
4560
|
-
function resolveTargetWorkspaceRoot(target) {
|
|
4561
|
-
if (target.client === "cursor") return path.dirname(path.dirname(target.configPath));
|
|
4562
|
-
if (target.client === "claude-code") return path.dirname(target.configPath);
|
|
4563
|
-
return process.cwd();
|
|
4564
|
-
}
|
|
4565
|
-
function escapeRegExp(value) {
|
|
4566
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4567
|
-
}
|
|
4568
|
-
function renderTomlString(value) {
|
|
4569
|
-
return JSON.stringify(value);
|
|
4570
|
-
}
|
|
4571
|
-
function sanitizeServerNameSegment(value) {
|
|
4572
|
-
return value.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
|
|
4573
|
-
}
|
|
4574
|
-
function resolveServerName(workspaceRoot) {
|
|
4575
|
-
return `weapp-vite-${sanitizeServerNameSegment(path.basename(workspaceRoot)) || "project"}`;
|
|
4576
|
-
}
|
|
4577
|
-
function resolveNodeBinCommand() {
|
|
4578
|
-
return process.execPath || "node";
|
|
4579
|
-
}
|
|
4580
|
-
function resolveProjectCliBin(workspaceRoot) {
|
|
4581
|
-
return path.join(workspaceRoot, "node_modules", "weapp-vite", "bin", "weapp-vite.js");
|
|
4582
|
-
}
|
|
4583
|
-
function renderCommandArgs(args) {
|
|
4584
|
-
return args.map(renderTomlString).join(", ");
|
|
4585
|
-
}
|
|
4586
|
-
function renderCodexBlock(serverName, entry) {
|
|
4587
|
-
const startMarker = `${CODEX_BLOCK_PREFIX}${serverName}${CODEX_BLOCK_SUFFIX}`;
|
|
4588
|
-
const endMarker = `${CODEX_BLOCK_END_PREFIX}${serverName}${CODEX_BLOCK_END_SUFFIX}`;
|
|
4589
|
-
if (entry.url) return `${startMarker}
|
|
4590
|
-
[mcp_servers.${serverName}]
|
|
4591
|
-
url = ${renderTomlString(entry.url)}
|
|
4592
|
-
${endMarker}
|
|
4593
|
-
`;
|
|
4594
|
-
return `${startMarker}
|
|
4595
|
-
[mcp_servers.${serverName}]
|
|
4596
|
-
command = ${renderTomlString(entry.command ?? resolveNodeBinCommand())}
|
|
4597
|
-
args = [${renderCommandArgs(entry.args ?? [])}]
|
|
4598
|
-
${endMarker}
|
|
4599
|
-
`;
|
|
4600
|
-
}
|
|
4601
|
-
function renderJsonPreview(serverName, entry) {
|
|
4602
|
-
return `${JSON.stringify({ mcpServers: { [serverName]: entry } }, null, 2)}
|
|
4603
|
-
`;
|
|
4604
|
-
}
|
|
4605
|
-
function resolveCommandEntry(client, workspaceRoot) {
|
|
4606
|
-
const binPath = resolveProjectCliBin(workspaceRoot);
|
|
4607
|
-
if (client === "cursor") return {
|
|
4608
|
-
command: "node",
|
|
4609
|
-
args: [
|
|
4610
|
-
`${WORKSPACE_FOLDER_TOKEN}/node_modules/weapp-vite/bin/weapp-vite.js`,
|
|
4611
|
-
"mcp",
|
|
4612
|
-
"--workspace-root",
|
|
4613
|
-
WORKSPACE_FOLDER_TOKEN
|
|
4614
|
-
]
|
|
4615
|
-
};
|
|
4616
|
-
return {
|
|
4617
|
-
command: resolveNodeBinCommand(),
|
|
4618
|
-
args: [
|
|
4619
|
-
binPath,
|
|
4620
|
-
"mcp",
|
|
4621
|
-
"--workspace-root",
|
|
4622
|
-
workspaceRoot
|
|
4623
|
-
]
|
|
4624
|
-
};
|
|
4625
|
-
}
|
|
4626
|
-
function resolveHttpEntry(client, url) {
|
|
4627
|
-
if (client === "claude-code") return {
|
|
4628
|
-
type: "http",
|
|
4629
|
-
url
|
|
4630
|
-
};
|
|
4631
|
-
return { url };
|
|
4632
|
-
}
|
|
4633
|
-
function resolveConfigEntry(client, transport, workspaceRoot, url) {
|
|
4634
|
-
return transport === "http" ? resolveHttpEntry(client, url ?? "") : resolveCommandEntry(client, workspaceRoot);
|
|
4635
|
-
}
|
|
4636
|
-
function resolveCodexBlockPattern(serverName) {
|
|
4637
|
-
const startMarker = `${CODEX_BLOCK_PREFIX}${serverName}${CODEX_BLOCK_SUFFIX}`;
|
|
4638
|
-
const endMarker = `${CODEX_BLOCK_END_PREFIX}${serverName}${CODEX_BLOCK_END_SUFFIX}`;
|
|
4639
|
-
return new RegExp(`${escapeRegExp(startMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}\\n?`, "g");
|
|
4640
|
-
}
|
|
4641
|
-
function upsertCodexManagedBlock(content, serverName, block) {
|
|
4642
|
-
const withoutExisting = content.trimEnd().replace(resolveCodexBlockPattern(serverName), "").trimEnd();
|
|
4643
|
-
if (!withoutExisting) return block;
|
|
4644
|
-
return `${withoutExisting}\n\n${block}`;
|
|
4645
|
-
}
|
|
4646
|
-
function parseJsonConfig(content, configPath) {
|
|
4647
|
-
if (!content.trim()) return {};
|
|
4648
|
-
try {
|
|
4649
|
-
return JSON.parse(content);
|
|
4650
|
-
} catch (error) {
|
|
4651
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
4652
|
-
throw new Error(`无法解析 MCP 配置文件 ${configPath}:${message}`);
|
|
4653
|
-
}
|
|
4654
|
-
}
|
|
4655
|
-
function resolveTarget(client, workspaceRoot) {
|
|
4656
|
-
const serverName = resolveServerName(workspaceRoot);
|
|
4657
|
-
if (client === "codex") return {
|
|
4658
|
-
client,
|
|
4659
|
-
configPath: path.join(os.homedir(), ".codex", "config.toml"),
|
|
4660
|
-
displayName: "Codex",
|
|
4661
|
-
serverName
|
|
4662
|
-
};
|
|
4663
|
-
if (client === "claude-code") return {
|
|
4664
|
-
client,
|
|
4665
|
-
configPath: path.join(workspaceRoot, ".mcp.json"),
|
|
4666
|
-
displayName: "Claude Code",
|
|
4667
|
-
serverName
|
|
4668
|
-
};
|
|
4669
|
-
return {
|
|
4670
|
-
client,
|
|
4671
|
-
configPath: path.join(workspaceRoot, ".cursor", "mcp.json"),
|
|
4672
|
-
displayName: "Cursor",
|
|
4673
|
-
serverName
|
|
4674
|
-
};
|
|
4675
|
-
}
|
|
4676
|
-
//#endregion
|
|
4677
|
-
//#region src/cli/mcpClient/inspect.ts
|
|
4678
|
-
async function probeHttpEndpoint(url) {
|
|
4679
|
-
const controller = new AbortController();
|
|
4680
|
-
const timer = setTimeout$1(() => {
|
|
4681
|
-
controller.abort();
|
|
4682
|
-
}, DEFAULT_HTTP_TIMEOUT_MS);
|
|
4683
|
-
try {
|
|
4684
|
-
return (await fetch(url, {
|
|
4685
|
-
method: "GET",
|
|
4686
|
-
signal: controller.signal
|
|
4687
|
-
})).status > 0;
|
|
4688
|
-
} catch {
|
|
4689
|
-
return false;
|
|
4690
|
-
} finally {
|
|
4691
|
-
clearTimeout$1(timer);
|
|
4692
|
-
controller.abort();
|
|
4693
|
-
}
|
|
4694
|
-
}
|
|
4695
|
-
async function inspectJsonConfig(target) {
|
|
4696
|
-
if (!await fs$1.pathExists(target.configPath)) return {
|
|
4697
|
-
configExists: false,
|
|
4698
|
-
configPath: target.configPath,
|
|
4699
|
-
displayName: target.displayName,
|
|
4700
|
-
issues: [`未找到配置文件:${target.configPath}`],
|
|
4701
|
-
serverName: target.serverName
|
|
4702
|
-
};
|
|
4703
|
-
const entry = parseJsonConfig(await fs$1.readFile(target.configPath, "utf8"), target.configPath).mcpServers?.[target.serverName];
|
|
4704
|
-
if (!entry) return {
|
|
4705
|
-
configExists: true,
|
|
4706
|
-
configPath: target.configPath,
|
|
4707
|
-
displayName: target.displayName,
|
|
4708
|
-
issues: [`配置文件中未找到服务器 ${target.serverName}`],
|
|
4709
|
-
serverName: target.serverName
|
|
4710
|
-
};
|
|
4711
|
-
const transport = entry.url ? "http" : "command";
|
|
4712
|
-
const issues = [];
|
|
4713
|
-
let httpReachable;
|
|
4714
|
-
if (transport === "command") {
|
|
4715
|
-
const normalizedBin = (entry.args?.[0])?.replaceAll(WORKSPACE_FOLDER_TOKEN, resolveTargetWorkspaceRoot(target));
|
|
4716
|
-
if (!normalizedBin || !await fs$1.pathExists(normalizedBin)) issues.push(`未找到本地 CLI 入口:${normalizedBin ?? "缺少 args[0]"}`);
|
|
4717
|
-
} else if (entry.url) {
|
|
4718
|
-
httpReachable = await probeHttpEndpoint(entry.url);
|
|
4719
|
-
if (!httpReachable) issues.push(`HTTP MCP 地址不可达:${entry.url}`);
|
|
4720
|
-
}
|
|
4721
|
-
return {
|
|
4722
|
-
configExists: true,
|
|
4723
|
-
configPath: target.configPath,
|
|
4724
|
-
displayName: target.displayName,
|
|
4725
|
-
httpReachable,
|
|
4726
|
-
issues,
|
|
4727
|
-
serverName: target.serverName,
|
|
4728
|
-
transport
|
|
4729
|
-
};
|
|
4730
|
-
}
|
|
4731
|
-
async function inspectCodexConfig(target) {
|
|
4732
|
-
if (!await fs$1.pathExists(target.configPath)) return {
|
|
4733
|
-
configExists: false,
|
|
4734
|
-
configPath: target.configPath,
|
|
4735
|
-
displayName: target.displayName,
|
|
4736
|
-
issues: [`未找到配置文件:${target.configPath}`],
|
|
4737
|
-
serverName: target.serverName
|
|
4738
|
-
};
|
|
4739
|
-
const blockMatch = (await fs$1.readFile(target.configPath, "utf8")).match(resolveCodexBlockPattern(target.serverName));
|
|
4740
|
-
if (!blockMatch?.[0]) return {
|
|
4741
|
-
configExists: true,
|
|
4742
|
-
configPath: target.configPath,
|
|
4743
|
-
displayName: target.displayName,
|
|
4744
|
-
issues: [`未找到 weapp-vite 生成的 Codex 配置区块:${target.serverName}`],
|
|
4745
|
-
serverName: target.serverName
|
|
4746
|
-
};
|
|
4747
|
-
const block = blockMatch[0];
|
|
4748
|
-
const issues = [];
|
|
4749
|
-
const urlMatch = block.match(REG_CodexUrl);
|
|
4750
|
-
const commandMatch = block.match(REG_CodexCommand);
|
|
4751
|
-
const argsMatch = block.match(REG_CodexArgs);
|
|
4752
|
-
if (urlMatch?.[1]) {
|
|
4753
|
-
const reachable = await probeHttpEndpoint(urlMatch[1]);
|
|
4754
|
-
if (!reachable) issues.push(`HTTP MCP 地址不可达:${urlMatch[1]}`);
|
|
4755
|
-
return {
|
|
4756
|
-
configExists: true,
|
|
4757
|
-
configPath: target.configPath,
|
|
4758
|
-
displayName: target.displayName,
|
|
4759
|
-
httpReachable: reachable,
|
|
4760
|
-
issues,
|
|
4761
|
-
serverName: target.serverName,
|
|
4762
|
-
transport: "http"
|
|
4763
|
-
};
|
|
4764
|
-
}
|
|
4765
|
-
const binPath = (argsMatch?.[1]?.match(REG_FirstTomlString))?.[1];
|
|
4766
|
-
if (!commandMatch?.[1]) issues.push("Codex 配置区块缺少 command 字段");
|
|
4767
|
-
if (!binPath || !await fs$1.pathExists(binPath)) issues.push(`未找到本地 CLI 入口:${binPath ?? "缺少 args[0]"}`);
|
|
4768
|
-
return {
|
|
4769
|
-
configExists: true,
|
|
4770
|
-
configPath: target.configPath,
|
|
4771
|
-
displayName: target.displayName,
|
|
4772
|
-
issues,
|
|
4773
|
-
serverName: target.serverName,
|
|
4774
|
-
transport: "command"
|
|
4775
|
-
};
|
|
4776
|
-
}
|
|
4777
|
-
async function inspectMcpClientConfig(options) {
|
|
4778
|
-
const target = resolveTarget(options.client, path.resolve(options.workspaceRoot));
|
|
4779
|
-
if (target.client === "codex") return await inspectCodexConfig(target);
|
|
4780
|
-
return await inspectJsonConfig(target);
|
|
4781
|
-
}
|
|
4782
|
-
//#endregion
|
|
4783
|
-
//#region src/cli/mcpClient/plan.ts
|
|
4784
|
-
function resolveSupportedMcpClient(input) {
|
|
4785
|
-
if (input === "codex" || input === "claude-code" || input === "cursor") return input;
|
|
4786
|
-
throw new Error(`不支持的 MCP 客户端:${input}。当前仅支持 codex、claude-code、cursor。`);
|
|
4787
|
-
}
|
|
4788
|
-
function buildMcpClientConfigPlan(options) {
|
|
4789
|
-
const workspaceRoot = path.resolve(options.workspaceRoot);
|
|
4790
|
-
const target = resolveTarget(options.client, workspaceRoot);
|
|
4791
|
-
const entry = resolveConfigEntry(options.client, options.transport, workspaceRoot, options.url);
|
|
4792
|
-
if (options.transport === "http" && !entry.url) throw new Error("HTTP 模式缺少 MCP 服务地址,请使用 --url 指定或在当前项目中检测到可用地址。");
|
|
4793
|
-
return {
|
|
4794
|
-
entry,
|
|
4795
|
-
preview: target.client === "codex" ? renderCodexBlock(target.serverName, entry) : renderJsonPreview(target.serverName, entry),
|
|
4796
|
-
target,
|
|
4797
|
-
transport: options.transport
|
|
4798
|
-
};
|
|
4799
|
-
}
|
|
4800
|
-
function formatMcpQuickStart(options) {
|
|
4801
|
-
const suffix = options.transport === "http" && options.httpUrl ? ` --transport http --url ${options.httpUrl}` : "";
|
|
4802
|
-
return [
|
|
4803
|
-
"在 AI 工具中接入 weapp-vite MCP:",
|
|
4804
|
-
` - Codex: wv mcp init codex${suffix}`,
|
|
4805
|
-
` - Claude Code: wv mcp init claude-code${suffix}`,
|
|
4806
|
-
` - Cursor: wv mcp init cursor${suffix}`
|
|
4807
|
-
];
|
|
4808
|
-
}
|
|
4809
|
-
//#endregion
|
|
4810
|
-
//#region src/cli/mcpClient/index.ts
|
|
4811
|
-
async function writeMcpClientConfig(plan) {
|
|
4812
|
-
await fs$1.ensureDir(path.dirname(plan.target.configPath));
|
|
4813
|
-
const existing = await fs$1.readFile(plan.target.configPath, "utf8").catch(() => "");
|
|
4814
|
-
if (plan.target.client === "codex") {
|
|
4815
|
-
const nextContent = upsertCodexManagedBlock(existing, plan.target.serverName, plan.preview);
|
|
4816
|
-
await fs$1.writeFile(plan.target.configPath, nextContent, "utf8");
|
|
4817
|
-
return;
|
|
4818
|
-
}
|
|
4819
|
-
const parsed = parseJsonConfig(existing, plan.target.configPath);
|
|
4820
|
-
const nextConfig = {
|
|
4821
|
-
...parsed,
|
|
4822
|
-
mcpServers: {
|
|
4823
|
-
...parsed.mcpServers ?? {},
|
|
4824
|
-
[plan.target.serverName]: plan.entry
|
|
4825
|
-
}
|
|
4826
|
-
};
|
|
4827
|
-
await fs$1.writeFile(plan.target.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
4828
|
-
}
|
|
4829
|
-
//#endregion
|
|
4830
|
-
//#region src/cli/commands/mcp.ts
|
|
4831
|
-
function resolvePort(port) {
|
|
4832
|
-
if (typeof port === "number" && Number.isInteger(port)) return port;
|
|
4833
|
-
if (typeof port === "string" && port.trim()) {
|
|
4834
|
-
const parsed = Number.parseInt(port, 10);
|
|
4835
|
-
if (Number.isInteger(parsed)) return parsed;
|
|
4836
|
-
}
|
|
4837
|
-
}
|
|
4838
|
-
async function resolveHttpUrl(options) {
|
|
4839
|
-
if (options.url?.trim()) return options.url.trim();
|
|
4840
|
-
const workspaceRoot = options.workspaceRoot ? path.resolve(options.workspaceRoot) : process.cwd();
|
|
4841
|
-
const originalCwd = process.cwd();
|
|
4842
|
-
try {
|
|
4843
|
-
if (workspaceRoot !== originalCwd) process.chdir(workspaceRoot);
|
|
4844
|
-
const loaded = await loadConfig(resolveConfigFile(options));
|
|
4845
|
-
const resolved = resolveWeappMcpConfig(loaded?.config?.weapp?.mcp);
|
|
4846
|
-
return `http://${resolved.host}:${resolved.port}${resolved.endpoint}`;
|
|
4847
|
-
} catch {
|
|
4848
|
-
const resolved = resolveWeappMcpConfig(void 0);
|
|
4849
|
-
return `http://${resolved.host}:${resolved.port}${resolved.endpoint}`;
|
|
4850
|
-
} finally {
|
|
4851
|
-
if (process.cwd() !== originalCwd) process.chdir(originalCwd);
|
|
4852
|
-
}
|
|
4853
|
-
}
|
|
4854
|
-
async function confirmWrite() {
|
|
4855
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
4856
|
-
return await runWithSuspendedSharedInput(async () => {
|
|
4857
|
-
const rl = createInterface({
|
|
4858
|
-
input: process.stdin,
|
|
4859
|
-
output: process.stdout
|
|
4860
|
-
});
|
|
4861
|
-
try {
|
|
4862
|
-
const normalized = (await rl.question("是否写入配置文件?(Y/n) ")).trim().toLowerCase();
|
|
4863
|
-
return normalized === "" || normalized === "y" || normalized === "yes";
|
|
4864
|
-
} finally {
|
|
4865
|
-
rl.close();
|
|
4866
|
-
}
|
|
4867
|
-
});
|
|
4868
|
-
}
|
|
4869
|
-
function resolveClientTransport(transport) {
|
|
4870
|
-
return transport === "http" ? "http" : "command";
|
|
4871
|
-
}
|
|
4872
|
-
async function handlePrint(clientName, options, write = false) {
|
|
4873
|
-
const client = resolveSupportedMcpClient(clientName);
|
|
4874
|
-
const workspaceRoot = options.workspaceRoot ?? process.cwd();
|
|
4875
|
-
const transport = resolveClientTransport(options.transport);
|
|
4876
|
-
const plan = buildMcpClientConfigPlan({
|
|
4877
|
-
client,
|
|
4878
|
-
transport,
|
|
4879
|
-
url: transport === "http" ? await resolveHttpUrl(options) : void 0,
|
|
4880
|
-
workspaceRoot
|
|
4881
|
-
});
|
|
4882
|
-
logger_default.info(`${plan.target.displayName} 配置文件:${plan.target.configPath}`);
|
|
4883
|
-
logger_default.info(`服务器名称:${plan.target.serverName}`);
|
|
4884
|
-
process.stdout.write(`${plan.preview}\n`);
|
|
4885
|
-
if (!write) return;
|
|
4886
|
-
if (options.yes || await confirmWrite()) {
|
|
4887
|
-
await writeMcpClientConfig(plan);
|
|
4888
|
-
logger_default.success(`已写入 ${plan.target.displayName} MCP 配置。`);
|
|
4889
|
-
logger_default.info(`请重启 ${plan.target.displayName},然后执行:wv mcp doctor ${client}`);
|
|
4890
|
-
return;
|
|
4891
|
-
}
|
|
4892
|
-
logger_default.info("已取消写入。");
|
|
4893
|
-
}
|
|
4894
|
-
async function handleDoctor(clientName, options) {
|
|
4895
|
-
const result = await inspectMcpClientConfig({
|
|
4896
|
-
client: resolveSupportedMcpClient(clientName),
|
|
4897
|
-
workspaceRoot: options.workspaceRoot ?? process.cwd()
|
|
4898
|
-
});
|
|
4899
|
-
logger_default.info(`${result.displayName} 配置文件:${result.configPath}`);
|
|
4900
|
-
logger_default.info(`服务器名称:${result.serverName}`);
|
|
4901
|
-
if (!result.configExists || result.issues.length > 0) {
|
|
4902
|
-
for (const issue of result.issues) logger_default.warn(issue);
|
|
4903
|
-
throw new Error("MCP 客户端配置检查未通过。");
|
|
4904
|
-
}
|
|
4905
|
-
if (result.transport) logger_default.info(`传输模式:${result.transport}`);
|
|
4906
|
-
if (result.httpReachable !== void 0) logger_default.info(`HTTP 服务可达:${result.httpReachable ? "是" : "否"}`);
|
|
4907
|
-
logger_default.success("MCP 客户端配置检查通过。");
|
|
4908
|
-
}
|
|
4909
|
-
async function handleServer(options) {
|
|
4910
|
-
const resolvedTransport = options.transport === "http" ? "streamable-http" : options.transport === "command" ? void 0 : options.transport;
|
|
4911
|
-
await startWeappViteMcpServer({
|
|
4912
|
-
endpoint: options.endpoint,
|
|
4913
|
-
host: options.host,
|
|
4914
|
-
port: resolvePort(options.port),
|
|
4915
|
-
restEndpoint: options.rest === false ? false : options.restEndpoint,
|
|
4916
|
-
transport: resolvedTransport,
|
|
4917
|
-
unref: options.unref,
|
|
4918
|
-
workspaceRoot: options.workspaceRoot
|
|
4919
|
-
});
|
|
4920
|
-
for (const line of formatMcpQuickStart({
|
|
4921
|
-
httpUrl: resolvedTransport === "streamable-http" ? `http://${options.host ?? "127.0.0.1"}:${resolvePort(options.port) ?? 3088}${options.endpoint ?? "/mcp"}` : void 0,
|
|
4922
|
-
transport: resolvedTransport === "streamable-http" ? "http" : "command"
|
|
4923
|
-
})) logger_default.info(line);
|
|
4924
|
-
}
|
|
4925
|
-
function registerMcpCommand(cli) {
|
|
4926
|
-
cli.command("mcp [...args]", "start weapp-vite MCP server or manage MCP client onboarding").option("--transport <type>", "[string] stdio | streamable-http | command | http", { default: "stdio" }).option("--host <host>", "[string] streamable-http host").option("--port <port>", "[number] streamable-http port").option("--endpoint <path>", "[string] streamable-http endpoint path").option("--rest-endpoint <path>", "[string] streamable-http REST runtime endpoint path").option("--no-rest", "[boolean] disable streamable-http REST runtime endpoints").option("--unref", "[boolean] unref HTTP server to not block process exit").option("--url <url>", "[string] explicit HTTP MCP url").option("--workspace-root <path>", "[string] workspace root path, defaults to cwd").option("-y, --yes", "[boolean] write config without prompt").action(async (args, options) => {
|
|
4927
|
-
const [subcommand, client] = args;
|
|
4928
|
-
if (subcommand === "init") {
|
|
4929
|
-
if (!client) throw new Error("缺少客户端名称,请使用:wv mcp init <codex|claude-code|cursor>");
|
|
4930
|
-
await handlePrint(client, options, true);
|
|
4931
|
-
return;
|
|
4932
|
-
}
|
|
4933
|
-
if (subcommand === "print") {
|
|
4934
|
-
if (!client) throw new Error("缺少客户端名称,请使用:wv mcp print <codex|claude-code|cursor>");
|
|
4935
|
-
await handlePrint(client, options, false);
|
|
4936
|
-
return;
|
|
4937
|
-
}
|
|
4938
|
-
if (subcommand === "doctor") {
|
|
4939
|
-
if (!client) throw new Error("缺少客户端名称,请使用:wv mcp doctor <codex|claude-code|cursor>");
|
|
4940
|
-
await handleDoctor(client, options);
|
|
4941
|
-
return;
|
|
4942
|
-
}
|
|
4943
|
-
if (subcommand) throw new Error(`未知的 mcp 子命令:${subcommand}`);
|
|
4944
|
-
await handleServer(options);
|
|
4945
|
-
});
|
|
4946
|
-
}
|
|
4947
|
-
//#endregion
|
|
4948
4575
|
//#region src/cli/commands/npm.ts
|
|
4949
4576
|
function registerNpmCommand(cli) {
|
|
4950
4577
|
cli.command("npm").alias("build:npm").alias("build-npm").action(async () => {
|
|
@@ -5066,7 +4693,7 @@ function applyMcpCliOptions(config, options) {
|
|
|
5066
4693
|
//#endregion
|
|
5067
4694
|
//#region src/cli/commands/open.ts
|
|
5068
4695
|
function registerOpenCommand(cli) {
|
|
5069
|
-
cli.command("open [root]").option("-p, --platform <platform>", `[string] target platform (weapp | web)`).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("--login-retry <mode>", "[string] login retry mode for Wechat DevTools (never | once | always)").option("--login-retry-timeout <ms>", "[number] login retry prompt timeout in milliseconds").option("--non-interactive", "[boolean] fail immediately when Wechat DevTools login has expired").option("--no-open-recovery", "[boolean] disable automatic
|
|
4696
|
+
cli.command("open [root]").option("-p, --platform <platform>", `[string] target platform (weapp | web)`).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("--login-retry <mode>", "[string] login retry mode for Wechat DevTools (never | once | always)").option("--login-retry-timeout <ms>", "[number] login retry prompt timeout in milliseconds").option("--non-interactive", "[boolean] fail immediately when Wechat DevTools login has expired").option("--no-open-recovery", "[boolean] disable automatic target-project open retry (preserves existing IDE windows)").option("--mcp", "[boolean] auto start MCP service before opening IDE").option("--no-mcp", "[boolean] disable MCP service before opening IDE").action(async (root, options) => {
|
|
5070
4697
|
filterDuplicateOptions(options);
|
|
5071
4698
|
const configFile = resolveConfigFile(options);
|
|
5072
4699
|
const targets = resolveRuntimeTargets(options);
|
|
@@ -5369,9 +4996,6 @@ function resolveRunnableHotkeyDefinition(input) {
|
|
|
5369
4996
|
return DEV_HOTKEY_DEFINITIONS.find((action) => action.run && action.key === input);
|
|
5370
4997
|
}
|
|
5371
4998
|
//#endregion
|
|
5372
|
-
//#region package.json
|
|
5373
|
-
var version = "7.3.0";
|
|
5374
|
-
//#endregion
|
|
5375
4999
|
//#region src/cli/devHotkeys/format.ts
|
|
5376
5000
|
const FULLWIDTH_ASCII_START = 65281;
|
|
5377
5001
|
const FULLWIDTH_ASCII_END = 65374;
|
|
@@ -5531,6 +5155,8 @@ function startDevHotkeys(options) {
|
|
|
5531
5155
|
if (options.platform !== "weapp" || !process.stdin.isTTY) return;
|
|
5532
5156
|
let closed = false;
|
|
5533
5157
|
let running = false;
|
|
5158
|
+
let closing;
|
|
5159
|
+
let actionPromise;
|
|
5534
5160
|
let mcpHandle;
|
|
5535
5161
|
let onData;
|
|
5536
5162
|
let onKeypress;
|
|
@@ -5565,17 +5191,25 @@ function startDevHotkeys(options) {
|
|
|
5565
5191
|
}
|
|
5566
5192
|
});
|
|
5567
5193
|
const close = () => {
|
|
5568
|
-
if (
|
|
5194
|
+
if (closing) return closing;
|
|
5569
5195
|
closed = true;
|
|
5570
5196
|
if (onSigcont) process.off("SIGCONT", onSigcont);
|
|
5571
|
-
|
|
5197
|
+
const inputErrors = [];
|
|
5198
|
+
try {
|
|
5199
|
+
inputSession?.close();
|
|
5200
|
+
} catch (error) {
|
|
5201
|
+
inputErrors.push(error);
|
|
5202
|
+
}
|
|
5572
5203
|
inputSession = void 0;
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5204
|
+
return closing = (async () => {
|
|
5205
|
+
await actionPromise;
|
|
5206
|
+
const handle = mcpHandle;
|
|
5207
|
+
const closeHandle = handle?.close?.bind(handle);
|
|
5208
|
+
mcpHandle = void 0;
|
|
5209
|
+
const results = await Promise.allSettled([Promise.resolve().then(() => closeHandle?.()), Promise.resolve().then(() => closeSharedMiniProgram(options.projectPath))]);
|
|
5210
|
+
const failures = [...inputErrors, ...results.filter((result) => result.status === "rejected").map((result) => result.reason)];
|
|
5211
|
+
if (failures.length) throw new AggregateError(failures, "开发快捷键资源关闭失败");
|
|
5212
|
+
})();
|
|
5579
5213
|
};
|
|
5580
5214
|
const printPanel = (message, force = false) => {
|
|
5581
5215
|
if (!force && message === lastRenderedPanel) return;
|
|
@@ -5611,36 +5245,21 @@ function startDevHotkeys(options) {
|
|
|
5611
5245
|
}
|
|
5612
5246
|
});
|
|
5613
5247
|
const resolvePendingLabel = (input) => {
|
|
5614
|
-
if (input === "m") return mcpHandle?.close ? "正在关闭 MCP 服务" : "正在启动 MCP 服务";
|
|
5615
|
-
};
|
|
5616
|
-
const runAction = (label, pendingLabel, action) => {
|
|
5617
|
-
if (running) {
|
|
5618
|
-
const current = currentAction ?? "已有命令";
|
|
5619
|
-
const elapsed = actionStartedAt === void 0 ? "" : `(已运行 ${Math.max(0, Date.now() - actionStartedAt)} ms)`;
|
|
5620
|
-
logger_default.warn(`[dev action] 当前正在${current.replace(REG_PENDING_PREFIX, "")}${elapsed},已忽略重复请求。`);
|
|
5621
|
-
return;
|
|
5622
|
-
}
|
|
5623
|
-
running = true;
|
|
5624
|
-
actionStartedAt = Date.now();
|
|
5625
|
-
currentAction = pendingLabel;
|
|
5626
|
-
printHint();
|
|
5627
|
-
action().then((summary) => {
|
|
5628
|
-
if (summary) lastAction = summary;
|
|
5629
|
-
}).catch((error) => {
|
|
5630
|
-
logger_default.error(`[dev action] ${label}失败:${error instanceof Error ? error.message : String(error)}`);
|
|
5631
|
-
}).finally(() => {
|
|
5632
|
-
running = false;
|
|
5633
|
-
actionStartedAt = void 0;
|
|
5634
|
-
currentAction = void 0;
|
|
5635
|
-
if (!closed) printHint();
|
|
5636
|
-
});
|
|
5248
|
+
if (input === "m") return mcpHandle?.close ? "正在关闭 MCP 服务" : "正在启动 MCP 服务";
|
|
5637
5249
|
};
|
|
5638
|
-
const
|
|
5639
|
-
if (running)
|
|
5250
|
+
const runAction = (label, pendingLabel, action, quiet = false) => {
|
|
5251
|
+
if (running) {
|
|
5252
|
+
if (quiet) return;
|
|
5253
|
+
const current = currentAction ?? "已有命令";
|
|
5254
|
+
const elapsed = actionStartedAt === void 0 ? "" : `(已运行 ${Math.max(0, Date.now() - actionStartedAt)} ms)`;
|
|
5255
|
+
logger_default.warn(`[dev action] 当前正在${current.replace(REG_PENDING_PREFIX, "")}${elapsed},已忽略重复请求。`);
|
|
5256
|
+
return;
|
|
5257
|
+
}
|
|
5640
5258
|
running = true;
|
|
5641
5259
|
actionStartedAt = Date.now();
|
|
5642
5260
|
currentAction = pendingLabel;
|
|
5643
|
-
|
|
5261
|
+
if (!quiet) printHint();
|
|
5262
|
+
actionPromise = action().then((summary) => {
|
|
5644
5263
|
if (summary) lastAction = summary;
|
|
5645
5264
|
}).catch((error) => {
|
|
5646
5265
|
logger_default.error(`[dev action] ${label}失败:${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -5648,13 +5267,14 @@ function startDevHotkeys(options) {
|
|
|
5648
5267
|
running = false;
|
|
5649
5268
|
actionStartedAt = void 0;
|
|
5650
5269
|
currentAction = void 0;
|
|
5270
|
+
if (!closed && !quiet) printHint();
|
|
5651
5271
|
});
|
|
5652
5272
|
};
|
|
5653
5273
|
const handleInput = (input) => {
|
|
5654
5274
|
if (closed) return;
|
|
5655
5275
|
const normalizedInput = normalizeInputChar(input);
|
|
5656
5276
|
if (normalizedInput === "") {
|
|
5657
|
-
close();
|
|
5277
|
+
close().catch((error) => logger_default.error(error));
|
|
5658
5278
|
forwardSigint();
|
|
5659
5279
|
return;
|
|
5660
5280
|
}
|
|
@@ -5664,7 +5284,7 @@ function startDevHotkeys(options) {
|
|
|
5664
5284
|
}
|
|
5665
5285
|
const normalized = normalizedInput.toLowerCase();
|
|
5666
5286
|
if (normalized === "q") {
|
|
5667
|
-
close();
|
|
5287
|
+
close().catch((error) => logger_default.error(error));
|
|
5668
5288
|
forwardSigint();
|
|
5669
5289
|
return;
|
|
5670
5290
|
}
|
|
@@ -5714,9 +5334,9 @@ function startDevHotkeys(options) {
|
|
|
5714
5334
|
};
|
|
5715
5335
|
process.on("SIGCONT", onSigcont);
|
|
5716
5336
|
if (!options.silentStartupHint) printHint();
|
|
5717
|
-
if (resolvedMcp.enabled && resolvedMcp.autoStart)
|
|
5337
|
+
if (resolvedMcp.enabled && resolvedMcp.autoStart) runAction("MCP 自动启动", "正在启动 MCP 服务", async () => {
|
|
5718
5338
|
await toggleMcp({ silent: true });
|
|
5719
|
-
});
|
|
5339
|
+
}, true);
|
|
5720
5340
|
return {
|
|
5721
5341
|
close,
|
|
5722
5342
|
restore,
|
|
@@ -5724,6 +5344,127 @@ function startDevHotkeys(options) {
|
|
|
5724
5344
|
};
|
|
5725
5345
|
}
|
|
5726
5346
|
//#endregion
|
|
5347
|
+
//#region src/cli/commands/serve/shared.ts
|
|
5348
|
+
function startBackgroundForwardConsole(startForwardConsole, options) {
|
|
5349
|
+
if (!startForwardConsole) return;
|
|
5350
|
+
try {
|
|
5351
|
+
startForwardConsole(options).catch((error) => {
|
|
5352
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5353
|
+
logger_default.warn(`[forwardConsole] 后台启动失败:${message}`);
|
|
5354
|
+
});
|
|
5355
|
+
} catch (error) {
|
|
5356
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5357
|
+
logger_default.warn(`[forwardConsole] 后台启动失败:${message}`);
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
function resolveServeIdeOpenStrategy(options = {}, fallback = "cli") {
|
|
5361
|
+
return options.openStrategy ?? (options.useAutomatorOpen ? "automator" : fallback);
|
|
5362
|
+
}
|
|
5363
|
+
function resolveWebHost(host) {
|
|
5364
|
+
if (host === void 0) return;
|
|
5365
|
+
if (typeof host === "boolean") return host;
|
|
5366
|
+
if (typeof host === "string") return host;
|
|
5367
|
+
return String(host);
|
|
5368
|
+
}
|
|
5369
|
+
/**
|
|
5370
|
+
* @description 为 serve 模式构造统一的小程序开发动作,供热键与命令复用。
|
|
5371
|
+
*/
|
|
5372
|
+
function createServeMiniProgramDevActions(options) {
|
|
5373
|
+
const projectPath = options.projectPath ?? options.fallbackProjectPath;
|
|
5374
|
+
return {
|
|
5375
|
+
projectPath,
|
|
5376
|
+
openIde: async (openOptions = {}) => {
|
|
5377
|
+
if (!openOptions.forceOpen && await options.tryReuseForwardConsole?.()) return "已通过控制台转发复用当前开发者工具会话";
|
|
5378
|
+
await options.openIde(projectPath, openOptions);
|
|
5379
|
+
startBackgroundForwardConsole(options.startForwardConsole, openOptions);
|
|
5380
|
+
return openOptions.forceReopen ? "已重新打开微信开发者工具项目" : "已打开或复用微信开发者工具项目";
|
|
5381
|
+
},
|
|
5382
|
+
rebuild: async () => {
|
|
5383
|
+
await options.build();
|
|
5384
|
+
return "已手动重新构建当前小程序产物";
|
|
5385
|
+
}
|
|
5386
|
+
};
|
|
5387
|
+
}
|
|
5388
|
+
//#endregion
|
|
5389
|
+
//#region src/cli/commands/serve/actions.ts
|
|
5390
|
+
/** 启动与热键共用动作,退出后不再创建新的 IDE 或日志消费者。 */
|
|
5391
|
+
function createServeDevelopmentActions(ctx, miniBackend, options, shutdown) {
|
|
5392
|
+
const { configService } = ctx;
|
|
5393
|
+
const generatedProjectConfig = configService.multiPlatform.projectConfigs !== void 0;
|
|
5394
|
+
return createServeMiniProgramDevActions({
|
|
5395
|
+
build: async () => {
|
|
5396
|
+
if (!shutdown.stopping) await shutdown.run("startup", () => miniBackend?.driver.dev(ctx, options));
|
|
5397
|
+
},
|
|
5398
|
+
fallbackProjectPath: configService.cwd,
|
|
5399
|
+
openIde: async (projectPath, openOptions) => {
|
|
5400
|
+
if (shutdown.stopping) return;
|
|
5401
|
+
const forceReopen = openOptions?.forceReopen === true;
|
|
5402
|
+
const openStrategy = resolveServeIdeOpenStrategy(openOptions, options.ideOpenStrategy);
|
|
5403
|
+
const useAutomatorOpen = openStrategy === "automator";
|
|
5404
|
+
await openIde(configService.platform, projectPath, {
|
|
5405
|
+
loginRetry: options.loginRetry,
|
|
5406
|
+
loginRetryTimeout: options.loginRetryTimeout,
|
|
5407
|
+
nonInteractive: options.nonInteractive,
|
|
5408
|
+
openRecovery: false,
|
|
5409
|
+
prepareAutomatorSession: useAutomatorOpen,
|
|
5410
|
+
reuseOpenedProject: !forceReopen,
|
|
5411
|
+
skipAutomatorCompile: !forceReopen,
|
|
5412
|
+
skipPostOpenHealthCheck: true,
|
|
5413
|
+
trustProject: options.trustProject,
|
|
5414
|
+
openStrategy,
|
|
5415
|
+
useAutomatorOpen
|
|
5416
|
+
});
|
|
5417
|
+
process.stdout?.write?.("\n");
|
|
5418
|
+
},
|
|
5419
|
+
projectPath: resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd, generatedProjectConfig),
|
|
5420
|
+
startForwardConsole: async (openOptions) => {
|
|
5421
|
+
if (shutdown.stopping) return false;
|
|
5422
|
+
if (resolveServeIdeOpenStrategy(openOptions, options.ideOpenStrategy) === "automator") return await maybeStartForwardConsole({
|
|
5423
|
+
openedOnly: true,
|
|
5424
|
+
preferOpenedSession: true,
|
|
5425
|
+
platform: configService.platform,
|
|
5426
|
+
mpDistRoot: configService.mpDistRoot,
|
|
5427
|
+
cwd: configService.cwd,
|
|
5428
|
+
weappViteConfig: configService.weappViteConfig
|
|
5429
|
+
});
|
|
5430
|
+
return await maybeStartForwardConsole({
|
|
5431
|
+
platform: configService.platform,
|
|
5432
|
+
mpDistRoot: configService.mpDistRoot,
|
|
5433
|
+
cwd: configService.cwd,
|
|
5434
|
+
preferOpenedSession: false,
|
|
5435
|
+
recoverAutomatorSession: async () => {
|
|
5436
|
+
if (shutdown.stopping) return;
|
|
5437
|
+
const projectPath = resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd, generatedProjectConfig);
|
|
5438
|
+
await openIde(configService.platform, projectPath, {
|
|
5439
|
+
loginRetry: options.loginRetry,
|
|
5440
|
+
loginRetryTimeout: options.loginRetryTimeout,
|
|
5441
|
+
nonInteractive: options.nonInteractive,
|
|
5442
|
+
openRecovery: false,
|
|
5443
|
+
openStrategy: "automator",
|
|
5444
|
+
prepareAutomatorSession: true,
|
|
5445
|
+
reuseOpenedProject: false,
|
|
5446
|
+
skipAutomatorCompile: false,
|
|
5447
|
+
skipPostOpenHealthCheck: true,
|
|
5448
|
+
trustProject: options.trustProject,
|
|
5449
|
+
useAutomatorOpen: true
|
|
5450
|
+
});
|
|
5451
|
+
},
|
|
5452
|
+
weappViteConfig: configService.weappViteConfig
|
|
5453
|
+
});
|
|
5454
|
+
},
|
|
5455
|
+
tryReuseForwardConsole: async () => {
|
|
5456
|
+
if (shutdown.stopping) return false;
|
|
5457
|
+
return await maybeStartForwardConsole({
|
|
5458
|
+
openedOnly: true,
|
|
5459
|
+
platform: configService.platform,
|
|
5460
|
+
mpDistRoot: configService.mpDistRoot,
|
|
5461
|
+
cwd: configService.cwd,
|
|
5462
|
+
weappViteConfig: configService.weappViteConfig
|
|
5463
|
+
});
|
|
5464
|
+
}
|
|
5465
|
+
});
|
|
5466
|
+
}
|
|
5467
|
+
//#endregion
|
|
5727
5468
|
//#region src/cli/commands/serve/analyze.ts
|
|
5728
5469
|
const REG_DIST_PAGE_ENTRY = /pages\/.+\/index\.js$/;
|
|
5729
5470
|
const REG_DIST_POSIX_SEP = /\\/g;
|
|
@@ -5838,7 +5579,7 @@ async function analyzeUiFallback(ctx, options) {
|
|
|
5838
5579
|
};
|
|
5839
5580
|
}
|
|
5840
5581
|
function createAnalyzeController(options) {
|
|
5841
|
-
const { configFile, ctx, options: cliOptions, targets } = options;
|
|
5582
|
+
const { configFile, ctx, options: cliOptions, targets, shutdown } = options;
|
|
5842
5583
|
const { configService } = ctx;
|
|
5843
5584
|
let analyzeRunId = 0;
|
|
5844
5585
|
let analyzeHandle;
|
|
@@ -5889,8 +5630,8 @@ function createAnalyzeController(options) {
|
|
|
5889
5630
|
}
|
|
5890
5631
|
return runFallback("完整分析结果为空,已回退到 dist 文件扫描。");
|
|
5891
5632
|
};
|
|
5892
|
-
const
|
|
5893
|
-
if (!analyzeHandle) return;
|
|
5633
|
+
const runAnalyzeUpdate = async (reason) => {
|
|
5634
|
+
if (!analyzeHandle || shutdown?.stopping) return;
|
|
5894
5635
|
emitDashboardEvents(analyzeHandle, [{
|
|
5895
5636
|
kind: reason === "watch" ? "hmr" : "build",
|
|
5896
5637
|
level: "info",
|
|
@@ -5899,6 +5640,7 @@ function createAnalyzeController(options) {
|
|
|
5899
5640
|
tags: reason === "watch" ? ["watch", "analyze"] : ["initial", "analyze"]
|
|
5900
5641
|
}]);
|
|
5901
5642
|
const next = await runAnalyze();
|
|
5643
|
+
if (shutdown?.stopping) return;
|
|
5902
5644
|
if (next.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
|
|
5903
5645
|
kind: "diagnostic",
|
|
5904
5646
|
level: "warning",
|
|
@@ -5917,6 +5659,10 @@ function createAnalyzeController(options) {
|
|
|
5917
5659
|
tags: next.mode === "fallback" ? ["analyze", "fallback"] : ["analyze", reason === "watch" ? "refresh" : "initial"]
|
|
5918
5660
|
}]);
|
|
5919
5661
|
};
|
|
5662
|
+
const triggerAnalyzeUpdate = (reason = "watch") => {
|
|
5663
|
+
if (shutdown?.stopping) return Promise.resolve();
|
|
5664
|
+
return shutdown ? shutdown.run("startup", () => runAnalyzeUpdate(reason)) : runAnalyzeUpdate(reason);
|
|
5665
|
+
};
|
|
5920
5666
|
const bindWatcher = (buildResult) => {
|
|
5921
5667
|
let initialUpdatePromise;
|
|
5922
5668
|
let watchUpdatePending = false;
|
|
@@ -5946,6 +5692,7 @@ function createAnalyzeController(options) {
|
|
|
5946
5692
|
return watchUpdatePromise;
|
|
5947
5693
|
};
|
|
5948
5694
|
if (analyzeHandle && buildResult && typeof buildResult.on === "function") buildResult.on("event", (event) => {
|
|
5695
|
+
if (shutdown?.stopping) return;
|
|
5949
5696
|
if (event.code === "ERROR") {
|
|
5950
5697
|
emitDashboardEvents(analyzeHandle, [{
|
|
5951
5698
|
kind: "diagnostic",
|
|
@@ -5969,8 +5716,10 @@ function createAnalyzeController(options) {
|
|
|
5969
5716
|
getHandle: () => analyzeHandle,
|
|
5970
5717
|
async startDashboard(startDashboard) {
|
|
5971
5718
|
const initialAnalyze = await runAnalyze();
|
|
5719
|
+
if (shutdown?.stopping) return;
|
|
5972
5720
|
analyzeHandle = await startDashboard(initialAnalyze.result, {
|
|
5973
5721
|
watch: true,
|
|
5722
|
+
uiHost: cliOptions.uiHost,
|
|
5974
5723
|
artifacts: initialAnalyze.artifacts,
|
|
5975
5724
|
cwd: configService.cwd,
|
|
5976
5725
|
packageManagerAgent: configService.packageManager.agent,
|
|
@@ -6022,273 +5771,277 @@ function createAnalyzeController(options) {
|
|
|
6022
5771
|
};
|
|
6023
5772
|
}
|
|
6024
5773
|
//#endregion
|
|
6025
|
-
//#region src/cli/commands/serve/
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
5774
|
+
//#region src/cli/commands/serve/startup.ts
|
|
5775
|
+
/** 启动阶段只取得资源;退出协调器负责等待在途操作并统一释放。 */
|
|
5776
|
+
async function startServeCommand(root, options, shutdown) {
|
|
5777
|
+
if (shutdown.stopping) return;
|
|
5778
|
+
filterDuplicateOptions(options);
|
|
5779
|
+
options.uiHost = parseDashboardUiHost(options.uiHost);
|
|
5780
|
+
setCommandNodeEnv("development");
|
|
5781
|
+
const cwd = root ?? process.cwd();
|
|
5782
|
+
const configFile = resolveConfigFile(options);
|
|
5783
|
+
let targets = resolveRuntimeTargets(options);
|
|
5784
|
+
const host = resolveWebHost(options.host);
|
|
5785
|
+
const inlineConfig = createInlineConfig(targets, {
|
|
5786
|
+
scope: options.scope,
|
|
5787
|
+
host,
|
|
5788
|
+
inlineConfig: host === void 0 ? void 0 : { server: { host } }
|
|
5789
|
+
});
|
|
5790
|
+
const ctx = await createCompilerContext({
|
|
5791
|
+
cwd,
|
|
5792
|
+
mode: options.mode ?? "development",
|
|
5793
|
+
isDev: true,
|
|
5794
|
+
configFile,
|
|
5795
|
+
inlineConfig,
|
|
5796
|
+
cliPlatform: targets.rawPlatform,
|
|
5797
|
+
preloadAppEntry: false,
|
|
5798
|
+
syncAutoImportSupportFiles: false,
|
|
5799
|
+
projectConfigPath: options.projectConfig
|
|
5800
|
+
});
|
|
5801
|
+
const { configService } = ctx;
|
|
5802
|
+
targets = resolveConfiguredRuntimeTargets(targets, configService.options?.sourceConfig?.weapp?.platform);
|
|
5803
|
+
for (const backend of targets.select("dev")) shutdown.own(() => backend.driver.close(ctx));
|
|
5804
|
+
if (shutdown.stopping) return;
|
|
5805
|
+
const webBackend = getBackendForCapability(targets, "web", "dev");
|
|
5806
|
+
const miniBackend = getBackendForCapability(targets, "miniprogram", "dev");
|
|
5807
|
+
const aiEnvironment = await detectAiDevelopmentEnvironment();
|
|
5808
|
+
if (shutdown.stopping) return;
|
|
5809
|
+
const mcpConfig = applyMcpCliOptions(configService.weappViteConfig?.mcp, options);
|
|
5810
|
+
logRuntimeTarget(targets, { resolvedConfigPlatform: configService.platform });
|
|
5811
|
+
const enableAnalyze = Boolean(isUiEnabled(options) && miniBackend);
|
|
5812
|
+
let analyzeHandle;
|
|
5813
|
+
const miniProgramDevActions = createServeDevelopmentActions(ctx, miniBackend, options, shutdown);
|
|
5814
|
+
const devHotkeysSession = miniBackend ? startDevHotkeys({
|
|
5815
|
+
cwd: configService.cwd,
|
|
5816
|
+
agentName: aiEnvironment.agentName,
|
|
5817
|
+
isAgent: aiEnvironment.isAgent,
|
|
5818
|
+
mcpConfig,
|
|
5819
|
+
openIde: async () => await miniProgramDevActions.openIde({
|
|
5820
|
+
forceOpen: true,
|
|
5821
|
+
forceReopen: true
|
|
5822
|
+
}),
|
|
5823
|
+
platform: configService.platform,
|
|
5824
|
+
projectPath: miniProgramDevActions.projectPath ?? configService.cwd,
|
|
5825
|
+
rebuild: miniProgramDevActions.rebuild,
|
|
5826
|
+
silentStartupHint: true,
|
|
5827
|
+
weappViteConfig: configService.weappViteConfig
|
|
5828
|
+
}) : void 0;
|
|
5829
|
+
shutdown.own(() => devHotkeysSession?.close());
|
|
5830
|
+
shutdown.own(closeActiveForwardConsole);
|
|
5831
|
+
shutdown.own(() => analyzeHandle?.close());
|
|
5832
|
+
const analyzeController = createAnalyzeController({
|
|
5833
|
+
configFile,
|
|
5834
|
+
ctx,
|
|
5835
|
+
options,
|
|
5836
|
+
targets,
|
|
5837
|
+
shutdown
|
|
5838
|
+
});
|
|
5839
|
+
let webServer;
|
|
5840
|
+
for (const backend of targets.select("dev")) {
|
|
5841
|
+
if (shutdown.stopping) return;
|
|
5842
|
+
if (backend.descriptor.id === "miniprogram") {
|
|
5843
|
+
const miniBuildStartedAt = Date.now();
|
|
5844
|
+
const buildResult = await backend.driver.dev(ctx, options);
|
|
5845
|
+
if (shutdown.stopping) return;
|
|
5846
|
+
const miniBuildDurationMs = Date.now() - miniBuildStartedAt;
|
|
5847
|
+
logger_default.success(`小程序初次构建完成,耗时:${formatDuration(miniBuildDurationMs)}`);
|
|
5848
|
+
shutdown.run("startup", async () => {
|
|
5849
|
+
await ctx.autoImportService?.syncSupportFileResolverComponents().catch((error) => {
|
|
5850
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5851
|
+
logger_default.warn(`[prepare] 后台同步 .weapp-vite 支持文件失败:${message}`);
|
|
5852
|
+
});
|
|
5853
|
+
});
|
|
5854
|
+
if (enableAnalyze) {
|
|
5855
|
+
await analyzeController.startDashboard(startAnalyzeDashboard);
|
|
5856
|
+
analyzeHandle = analyzeController.getHandle();
|
|
5857
|
+
if (shutdown.stopping) return;
|
|
5858
|
+
analyzeController.emitRuntimeEvents([{
|
|
5859
|
+
kind: "build",
|
|
5860
|
+
level: "success",
|
|
5861
|
+
title: "mini initial build completed",
|
|
5862
|
+
detail: "小程序开发态初次构建已完成,dashboard 可继续监听后续刷新。",
|
|
5863
|
+
durationMs: miniBuildDurationMs,
|
|
5864
|
+
tags: [
|
|
5865
|
+
"dev",
|
|
5866
|
+
"mini",
|
|
5867
|
+
"initial"
|
|
5868
|
+
]
|
|
5869
|
+
}]);
|
|
5870
|
+
await analyzeController.bindWatcher(buildResult).runInitialUpdate();
|
|
5871
|
+
if (shutdown.stopping) return;
|
|
5872
|
+
}
|
|
5873
|
+
continue;
|
|
5874
|
+
}
|
|
5875
|
+
if (backend.descriptor.id !== "web") continue;
|
|
5876
|
+
const webServerStartedAt = Date.now();
|
|
5877
|
+
try {
|
|
5878
|
+
webServer = await backend.driver.dev(ctx, options);
|
|
5879
|
+
if (shutdown.stopping) return;
|
|
5880
|
+
logger_default.success(`Web 开发服务启动完成,耗时:${formatDuration(Date.now() - webServerStartedAt)}`);
|
|
5881
|
+
analyzeController.emitRuntimeEvents([{
|
|
5882
|
+
kind: "system",
|
|
5883
|
+
level: "success",
|
|
5884
|
+
title: "web dev server started",
|
|
5885
|
+
detail: "Web 开发服务器已启动,可与小程序调试 UI 并行工作。",
|
|
5886
|
+
durationMs: Date.now() - webServerStartedAt,
|
|
5887
|
+
tags: ["dev", "web"]
|
|
5888
|
+
}]);
|
|
5889
|
+
} catch (error) {
|
|
5890
|
+
analyzeController.emitRuntimeEvents([{
|
|
5891
|
+
kind: "diagnostic",
|
|
5892
|
+
level: "error",
|
|
5893
|
+
title: "web dev server failed",
|
|
5894
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
5895
|
+
durationMs: Date.now() - webServerStartedAt,
|
|
5896
|
+
tags: ["dev", "web"]
|
|
5897
|
+
}]);
|
|
5898
|
+
logger_default.error(error);
|
|
5899
|
+
throw error;
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
if (shutdown.stopping) return;
|
|
5903
|
+
if (miniBackend) {
|
|
5904
|
+
logBuildAppFinish(configService, webServer, {
|
|
5905
|
+
skipWeb: !webBackend,
|
|
5906
|
+
uiUrls: analyzeHandle?.urls
|
|
6032
5907
|
});
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
5908
|
+
devHotkeysSession?.restore();
|
|
5909
|
+
} else if (webBackend) logBuildAppFinish(configService, webServer, { skipMini: true });
|
|
5910
|
+
if (options.open && getBackendForCapability(targets, "miniprogram", "ide")) {
|
|
5911
|
+
analyzeController.emitRuntimeEvents([{
|
|
5912
|
+
kind: "command",
|
|
5913
|
+
level: "info",
|
|
5914
|
+
title: "opening ide",
|
|
5915
|
+
detail: "开发服务已就绪,准备打开 IDE 项目。",
|
|
5916
|
+
tags: ["ide", "open"]
|
|
5917
|
+
}]);
|
|
5918
|
+
devHotkeysSession?.suspend();
|
|
5919
|
+
try {
|
|
5920
|
+
await miniProgramDevActions.openIde({
|
|
5921
|
+
forceOpen: true,
|
|
5922
|
+
forceReopen: false,
|
|
5923
|
+
openStrategy: options.ideOpenStrategy ?? "cli"
|
|
5924
|
+
});
|
|
5925
|
+
} finally {
|
|
5926
|
+
devHotkeysSession?.restore();
|
|
5927
|
+
}
|
|
6036
5928
|
}
|
|
6037
|
-
}
|
|
6038
|
-
function resolveServeIdeOpenStrategy(options = {}, fallback = "cli") {
|
|
6039
|
-
return options.openStrategy ?? (options.useAutomatorOpen ? "automator" : fallback);
|
|
6040
|
-
}
|
|
6041
|
-
function resolveWebHost(host) {
|
|
6042
|
-
if (host === void 0) return;
|
|
6043
|
-
if (typeof host === "boolean") return host;
|
|
6044
|
-
if (typeof host === "string") return host;
|
|
6045
|
-
return String(host);
|
|
6046
|
-
}
|
|
6047
|
-
/**
|
|
6048
|
-
* @description 为 serve 模式构造统一的小程序开发动作,供热键与命令复用。
|
|
6049
|
-
*/
|
|
6050
|
-
function createServeMiniProgramDevActions(options) {
|
|
6051
|
-
const projectPath = options.projectPath ?? options.fallbackProjectPath;
|
|
6052
5929
|
return {
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
if (!openOptions.forceOpen && await options.tryReuseForwardConsole?.()) return "已通过控制台转发复用当前开发者工具会话";
|
|
6056
|
-
await options.openIde(projectPath, openOptions);
|
|
6057
|
-
startBackgroundForwardConsole(options.startForwardConsole, openOptions);
|
|
6058
|
-
return openOptions.forceReopen ? "已重新打开微信开发者工具项目" : "已打开或复用微信开发者工具项目";
|
|
6059
|
-
},
|
|
6060
|
-
rebuild: async () => {
|
|
6061
|
-
await options.build();
|
|
6062
|
-
return "已手动重新构建当前小程序产物";
|
|
6063
|
-
}
|
|
5930
|
+
watch: targets.has("dev"),
|
|
5931
|
+
waitForExit: analyzeHandle ? () => analyzeController.waitForExit() : void 0
|
|
6064
5932
|
};
|
|
6065
5933
|
}
|
|
6066
|
-
function waitForServeShutdownSignal() {
|
|
6067
|
-
return new Promise((resolve) => {
|
|
6068
|
-
const onSignal = () => {
|
|
6069
|
-
process.off("SIGINT", onSignal);
|
|
6070
|
-
process.off("SIGTERM", onSignal);
|
|
6071
|
-
resolve();
|
|
6072
|
-
};
|
|
6073
|
-
process.on("SIGINT", onSignal);
|
|
6074
|
-
process.on("SIGTERM", onSignal);
|
|
6075
|
-
});
|
|
6076
|
-
}
|
|
6077
5934
|
//#endregion
|
|
6078
5935
|
//#region src/cli/commands/serve/index.ts
|
|
6079
|
-
function writePostOpenSeparator() {
|
|
6080
|
-
process.stdout?.write?.("\n");
|
|
6081
|
-
}
|
|
6082
5936
|
function registerServeCommand(cli) {
|
|
6083
|
-
cli.command("[root]", "start dev server").alias("serve").alias("dev").option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("-p, --platform <platform>", `[string] target platform (weapp | web | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).option("--ide-open-strategy <strategy>", "[string] IDE open strategy (cli | automator)", { default: "cli" }).option("--login-retry <mode>", "[string] login retry mode for Wechat DevTools (never | once | always)").option("--login-retry-timeout <ms>", "[number] login retry prompt timeout in milliseconds").option("--non-interactive", "[boolean] fail immediately when Wechat DevTools login has expired").option("--no-open-recovery", "[boolean] disable automatic
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
const cwd = root ?? process.cwd();
|
|
6087
|
-
const configFile = resolveConfigFile(options);
|
|
6088
|
-
const targets = resolveRuntimeTargets(options);
|
|
6089
|
-
const webBackend = getBackendForCapability(targets, "web", "dev");
|
|
6090
|
-
const host = webBackend ? resolveWebHost(options.host) : void 0;
|
|
6091
|
-
const inlineConfig = createInlineConfig(targets, {
|
|
6092
|
-
scope: options.scope,
|
|
6093
|
-
host
|
|
6094
|
-
});
|
|
6095
|
-
const ctx = await createCompilerContext({
|
|
6096
|
-
cwd,
|
|
6097
|
-
mode: options.mode ?? "development",
|
|
6098
|
-
isDev: true,
|
|
6099
|
-
configFile,
|
|
6100
|
-
inlineConfig,
|
|
6101
|
-
cliPlatform: targets.rawPlatform,
|
|
6102
|
-
preloadAppEntry: false,
|
|
6103
|
-
syncAutoImportSupportFiles: false,
|
|
6104
|
-
projectConfigPath: options.projectConfig
|
|
6105
|
-
});
|
|
6106
|
-
const { configService } = ctx;
|
|
6107
|
-
const miniBackend = getBackendForCapability(targets, "miniprogram", "dev");
|
|
6108
|
-
const aiEnvironment = await detectAiDevelopmentEnvironment();
|
|
6109
|
-
const mcpConfig = applyMcpCliOptions(configService.weappViteConfig?.mcp, options);
|
|
6110
|
-
logRuntimeTarget(targets, { resolvedConfigPlatform: configService.platform });
|
|
6111
|
-
const enableAnalyze = Boolean(isUiEnabled(options) && miniBackend);
|
|
6112
|
-
let analyzeHandle;
|
|
6113
|
-
const miniProgramDevActions = createServeMiniProgramDevActions({
|
|
6114
|
-
build: async () => {
|
|
6115
|
-
await miniBackend?.driver.dev(ctx, options);
|
|
6116
|
-
},
|
|
6117
|
-
fallbackProjectPath: configService.cwd,
|
|
6118
|
-
openIde: async (projectPath, openOptions) => {
|
|
6119
|
-
const forceReopen = openOptions?.forceReopen === true;
|
|
6120
|
-
const openStrategy = resolveServeIdeOpenStrategy(openOptions, options.ideOpenStrategy);
|
|
6121
|
-
const useAutomatorOpen = openStrategy === "automator";
|
|
6122
|
-
await openIde(configService.platform, projectPath, {
|
|
6123
|
-
loginRetry: options.loginRetry,
|
|
6124
|
-
loginRetryTimeout: options.loginRetryTimeout,
|
|
6125
|
-
nonInteractive: options.nonInteractive,
|
|
6126
|
-
openRecovery: false,
|
|
6127
|
-
prepareAutomatorSession: useAutomatorOpen,
|
|
6128
|
-
reuseOpenedProject: !forceReopen,
|
|
6129
|
-
skipAutomatorCompile: !forceReopen,
|
|
6130
|
-
skipPostOpenHealthCheck: true,
|
|
6131
|
-
trustProject: options.trustProject,
|
|
6132
|
-
openStrategy,
|
|
6133
|
-
useAutomatorOpen
|
|
6134
|
-
});
|
|
6135
|
-
writePostOpenSeparator();
|
|
6136
|
-
},
|
|
6137
|
-
projectPath: resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd),
|
|
6138
|
-
startForwardConsole: async (openOptions) => {
|
|
6139
|
-
if (resolveServeIdeOpenStrategy(openOptions, options.ideOpenStrategy) === "automator") return await maybeStartForwardConsole({
|
|
6140
|
-
openedOnly: true,
|
|
6141
|
-
preferOpenedSession: true,
|
|
6142
|
-
platform: configService.platform,
|
|
6143
|
-
mpDistRoot: configService.mpDistRoot,
|
|
6144
|
-
cwd: configService.cwd,
|
|
6145
|
-
weappViteConfig: configService.weappViteConfig
|
|
6146
|
-
});
|
|
6147
|
-
return await maybeStartForwardConsole({
|
|
6148
|
-
platform: configService.platform,
|
|
6149
|
-
mpDistRoot: configService.mpDistRoot,
|
|
6150
|
-
cwd: configService.cwd,
|
|
6151
|
-
preferOpenedSession: false,
|
|
6152
|
-
recoverAutomatorSession: async () => {
|
|
6153
|
-
const projectPath = resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd);
|
|
6154
|
-
await openIde(configService.platform, projectPath, {
|
|
6155
|
-
loginRetry: options.loginRetry,
|
|
6156
|
-
loginRetryTimeout: options.loginRetryTimeout,
|
|
6157
|
-
nonInteractive: options.nonInteractive,
|
|
6158
|
-
openRecovery: false,
|
|
6159
|
-
openStrategy: "automator",
|
|
6160
|
-
prepareAutomatorSession: true,
|
|
6161
|
-
reuseOpenedProject: false,
|
|
6162
|
-
skipAutomatorCompile: false,
|
|
6163
|
-
skipPostOpenHealthCheck: true,
|
|
6164
|
-
trustProject: options.trustProject,
|
|
6165
|
-
useAutomatorOpen: true
|
|
6166
|
-
});
|
|
6167
|
-
},
|
|
6168
|
-
weappViteConfig: configService.weappViteConfig
|
|
6169
|
-
});
|
|
6170
|
-
},
|
|
6171
|
-
tryReuseForwardConsole: async () => {
|
|
6172
|
-
return await maybeStartForwardConsole({
|
|
6173
|
-
openedOnly: true,
|
|
6174
|
-
platform: configService.platform,
|
|
6175
|
-
mpDistRoot: configService.mpDistRoot,
|
|
6176
|
-
cwd: configService.cwd,
|
|
6177
|
-
weappViteConfig: configService.weappViteConfig
|
|
6178
|
-
});
|
|
6179
|
-
}
|
|
6180
|
-
});
|
|
6181
|
-
const devHotkeysSession = miniBackend ? startDevHotkeys({
|
|
6182
|
-
cwd: configService.cwd,
|
|
6183
|
-
agentName: aiEnvironment.agentName,
|
|
6184
|
-
isAgent: aiEnvironment.isAgent,
|
|
6185
|
-
mcpConfig,
|
|
6186
|
-
openIde: async () => await miniProgramDevActions.openIde({
|
|
6187
|
-
forceOpen: true,
|
|
6188
|
-
forceReopen: true
|
|
6189
|
-
}),
|
|
6190
|
-
platform: configService.platform,
|
|
6191
|
-
projectPath: miniProgramDevActions.projectPath ?? configService.cwd,
|
|
6192
|
-
rebuild: miniProgramDevActions.rebuild,
|
|
6193
|
-
silentStartupHint: true,
|
|
6194
|
-
weappViteConfig: configService.weappViteConfig
|
|
6195
|
-
}) : void 0;
|
|
5937
|
+
cli.command("[root]", "start dev server").alias("serve").alias("dev").option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("-p, --platform <platform>", `[string] target platform (weapp | web | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).option("--ide-open-strategy <strategy>", "[string] IDE open strategy (cli | automator)", { default: "cli" }).option("--login-retry <mode>", "[string] login retry mode for Wechat DevTools (never | once | always)").option("--login-retry-timeout <ms>", "[number] login retry prompt timeout in milliseconds").option("--non-interactive", "[boolean] fail immediately when Wechat DevTools login has expired").option("--no-open-recovery", "[boolean] disable automatic target-project open retry (preserves existing IDE windows)").option("--mcp", "[boolean] auto start MCP service during dev").option("--no-mcp", "[boolean] disable MCP service during dev").option("--host [host]", `[string] web dev server host`).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`).action(async (root, options) => {
|
|
5938
|
+
const shutdown = createDevShutdownScope({ reportError: (error) => logger_default.error(error) });
|
|
5939
|
+
let failure;
|
|
6196
5940
|
try {
|
|
6197
|
-
const
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
kind: "command",
|
|
6269
|
-
level: "info",
|
|
6270
|
-
title: "opening ide",
|
|
6271
|
-
detail: "开发服务已就绪,准备打开 IDE 项目。",
|
|
6272
|
-
tags: ["ide", "open"]
|
|
6273
|
-
}]);
|
|
6274
|
-
devHotkeysSession?.suspend();
|
|
6275
|
-
try {
|
|
6276
|
-
await miniProgramDevActions.openIde({
|
|
6277
|
-
forceOpen: true,
|
|
6278
|
-
forceReopen: false,
|
|
6279
|
-
openStrategy: options.ideOpenStrategy ?? "cli"
|
|
6280
|
-
});
|
|
6281
|
-
} finally {
|
|
6282
|
-
devHotkeysSession?.restore();
|
|
5941
|
+
const started = await shutdown.run("startup", () => startServeCommand(root, options, shutdown));
|
|
5942
|
+
if (started?.waitForExit) await Promise.race([shutdown.signal, started.waitForExit()]);
|
|
5943
|
+
else if (started?.watch) await shutdown.signal;
|
|
5944
|
+
} catch (error) {
|
|
5945
|
+
failure = error;
|
|
5946
|
+
}
|
|
5947
|
+
await shutdown.close(failure);
|
|
5948
|
+
});
|
|
5949
|
+
}
|
|
5950
|
+
//#endregion
|
|
5951
|
+
//#region src/cli/commands/upload.ts
|
|
5952
|
+
async function buildUploadTarget(cwd, platform, options, action, item) {
|
|
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
|
+
}
|
|
5979
|
+
async function runUploadCommand(root, options, action = "upload") {
|
|
5980
|
+
filterDuplicateOptions(options);
|
|
5981
|
+
const platforms = resolveUploadPlatforms(options.platform ?? options.p);
|
|
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";
|
|
6283
6012
|
}
|
|
6284
|
-
}
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
}
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
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";
|
|
6291
6026
|
}
|
|
6027
|
+
throw new UploadCommandError(report);
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
function registerUploadCommand(cli) {
|
|
6031
|
+
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) => {
|
|
6032
|
+
return runUploadCommand(root, {
|
|
6033
|
+
...options,
|
|
6034
|
+
...readUploadMetadata(cli)
|
|
6035
|
+
});
|
|
6036
|
+
});
|
|
6037
|
+
}
|
|
6038
|
+
function registerPreviewCommand(cli) {
|
|
6039
|
+
cli.command("preview [root]", "build mini programs and generate preview QR codes or links").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("--desc <text>", "[string] preview description").option("--dry-run", "[boolean] build without validating credentials or generating previews").action(async (root, options) => {
|
|
6040
|
+
await runUploadCommand(root, {
|
|
6041
|
+
...options,
|
|
6042
|
+
...readUploadMetadata(cli)
|
|
6043
|
+
}, "preview");
|
|
6044
|
+
scheduleCompletedProductionBuildExit({}, void 0);
|
|
6292
6045
|
});
|
|
6293
6046
|
}
|
|
6294
6047
|
//#endregion
|
|
@@ -6333,6 +6086,8 @@ const WEAPP_VITE_NATIVE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
6333
6086
|
"dev",
|
|
6334
6087
|
"serve",
|
|
6335
6088
|
"build",
|
|
6089
|
+
"upload",
|
|
6090
|
+
"preview",
|
|
6336
6091
|
"close",
|
|
6337
6092
|
"analyze",
|
|
6338
6093
|
"alipay",
|
|
@@ -6343,10 +6098,72 @@ const WEAPP_VITE_NATIVE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
6343
6098
|
"build-npm",
|
|
6344
6099
|
"generate",
|
|
6345
6100
|
"g",
|
|
6346
|
-
"mcp"
|
|
6101
|
+
"mcp",
|
|
6102
|
+
"accept"
|
|
6347
6103
|
]);
|
|
6104
|
+
const LEGACY_UPLOAD_OPTIONS = {
|
|
6105
|
+
"--version": true,
|
|
6106
|
+
"-v": true,
|
|
6107
|
+
"--project": true,
|
|
6108
|
+
"--appid": true,
|
|
6109
|
+
"--ext-appid": true,
|
|
6110
|
+
"--info-output": true,
|
|
6111
|
+
"-i": true
|
|
6112
|
+
};
|
|
6113
|
+
const SDK_UPLOAD_OPTIONS = {
|
|
6114
|
+
"--platform": true,
|
|
6115
|
+
"--uv": true,
|
|
6116
|
+
"--bump": true,
|
|
6117
|
+
"--git-desc": true,
|
|
6118
|
+
"--dry-run": true,
|
|
6119
|
+
"--json": true,
|
|
6120
|
+
"--timeout": true
|
|
6121
|
+
};
|
|
6122
|
+
const UPLOAD_VALUE_OPTIONS = {
|
|
6123
|
+
...LEGACY_UPLOAD_OPTIONS,
|
|
6124
|
+
"-p": true,
|
|
6125
|
+
"--desc": true,
|
|
6126
|
+
"-d": true,
|
|
6127
|
+
"--platform": true,
|
|
6128
|
+
"--uv": true,
|
|
6129
|
+
"--bump": true,
|
|
6130
|
+
"--timeout": true,
|
|
6131
|
+
"--project-config": true,
|
|
6132
|
+
"--port": true,
|
|
6133
|
+
"--lang": true,
|
|
6134
|
+
"-c": true,
|
|
6135
|
+
"--config": true,
|
|
6136
|
+
"--base": true,
|
|
6137
|
+
"-l": true,
|
|
6138
|
+
"--logLevel": true,
|
|
6139
|
+
"-f": true,
|
|
6140
|
+
"--filter": true,
|
|
6141
|
+
"-m": true,
|
|
6142
|
+
"--mode": true
|
|
6143
|
+
};
|
|
6144
|
+
/**
|
|
6145
|
+
* @description 仅按显式参数区分上传方言,不根据 -p 的值或文件系统猜测后端。
|
|
6146
|
+
*/
|
|
6147
|
+
function isLegacyUploadInvocation(argv) {
|
|
6148
|
+
let legacyOption;
|
|
6149
|
+
let sdkOption;
|
|
6150
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
6151
|
+
const token = argv[index];
|
|
6152
|
+
if (token === "--") break;
|
|
6153
|
+
const equalsIndex = token.indexOf("=");
|
|
6154
|
+
const option = equalsIndex === -1 ? token : token.slice(0, equalsIndex);
|
|
6155
|
+
if (LEGACY_UPLOAD_OPTIONS[option] === true) legacyOption = option;
|
|
6156
|
+
if (SDK_UPLOAD_OPTIONS[option.startsWith("--no-") ? `--${option.slice(5)}` : option] === true) sdkOption = option;
|
|
6157
|
+
if (legacyOption && sdkOption) throw new Error(`不能混用微信 IDE 上传参数 ${legacyOption} 与 SDK 上传参数 ${sdkOption};请使用 wv ide upload 保留 IDE 行为,或使用 wv build --upload 迁移到 SDK。`);
|
|
6158
|
+
if (equalsIndex === -1 && UPLOAD_VALUE_OPTIONS[option] === true) index += 1;
|
|
6159
|
+
}
|
|
6160
|
+
return legacyOption !== void 0;
|
|
6161
|
+
}
|
|
6162
|
+
function warnLegacyUpload() {
|
|
6163
|
+
logger_default.warn("顶层微信 IDE 上传语法已弃用,将在未来版本移除。请迁移到 wv build --upload -p weapp --uv <version> --desc <description>(SDK 流程需要源码目录与上传凭据,并非 IDE 命令的等价替换);或使用 wv ide upload ... 保留现有 IDE 行为。");
|
|
6164
|
+
}
|
|
6348
6165
|
/**
|
|
6349
|
-
* @description
|
|
6166
|
+
* @description 保留旧版上传方言;其余未命中自身命令的调用回退到 weapp-ide-cli。
|
|
6350
6167
|
*/
|
|
6351
6168
|
async function tryRunIdeCommand(argv) {
|
|
6352
6169
|
const command = argv[0];
|
|
@@ -6359,11 +6176,14 @@ async function tryRunIdeCommand(argv) {
|
|
|
6359
6176
|
if (command.startsWith("-")) return false;
|
|
6360
6177
|
if (command === "help") {
|
|
6361
6178
|
const target = argv[1];
|
|
6362
|
-
if (!target || WEAPP_VITE_NATIVE_COMMANDS.has(target) || !isWeappIdeTopLevelCommand(target)) return false;
|
|
6179
|
+
if (!target || target !== "upload" && WEAPP_VITE_NATIVE_COMMANDS.has(target) || !isWeappIdeTopLevelCommand(target)) return false;
|
|
6180
|
+
if (target === "upload") warnLegacyUpload();
|
|
6363
6181
|
await executeWechatIdeCliCommand(argv);
|
|
6364
6182
|
return true;
|
|
6365
6183
|
}
|
|
6366
|
-
|
|
6184
|
+
const legacyUpload = command === "upload" && isLegacyUploadInvocation(argv);
|
|
6185
|
+
if (!legacyUpload && WEAPP_VITE_NATIVE_COMMANDS.has(command) || !isWeappIdeTopLevelCommand(command)) return false;
|
|
6186
|
+
if (legacyUpload) warnLegacyUpload();
|
|
6367
6187
|
if (!await dispatchWechatCliCommand(argv)) await executeWechatIdeCliCommand(argv);
|
|
6368
6188
|
return true;
|
|
6369
6189
|
}
|
|
@@ -6450,17 +6270,14 @@ function handlePrepareLifecycleError(args, error) {
|
|
|
6450
6270
|
//#endregion
|
|
6451
6271
|
//#region src/cli.ts
|
|
6452
6272
|
const cli = cac("weapp-vite");
|
|
6453
|
-
try {
|
|
6454
|
-
checkRuntime({
|
|
6455
|
-
bun: "0.0.0",
|
|
6456
|
-
deno: "0.0.0",
|
|
6457
|
-
node: "^20.19.0 || >=22.12.0"
|
|
6458
|
-
});
|
|
6459
|
-
} catch {}
|
|
6460
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`);
|
|
6274
|
+
registerAcceptCommand(cli);
|
|
6461
6275
|
registerIdeCommand(cli);
|
|
6276
|
+
registerDoctorCommand(cli);
|
|
6462
6277
|
registerAlipayCommand(cli);
|
|
6463
6278
|
registerBuildCommand(cli);
|
|
6279
|
+
registerUploadCommand(cli);
|
|
6280
|
+
registerPreviewCommand(cli);
|
|
6464
6281
|
registerCloseCommand(cli);
|
|
6465
6282
|
registerAnalyzeCommand(cli);
|
|
6466
6283
|
registerInitCommand(cli);
|
|
@@ -6479,7 +6296,9 @@ const skipManagedTsconfigBootstrapCommands = /* @__PURE__ */ new Set([
|
|
|
6479
6296
|
"init",
|
|
6480
6297
|
"mcp",
|
|
6481
6298
|
"npm",
|
|
6482
|
-
"alipay"
|
|
6299
|
+
"alipay",
|
|
6300
|
+
"doctor",
|
|
6301
|
+
"accept"
|
|
6483
6302
|
]);
|
|
6484
6303
|
function resolveManagedTsconfigBootstrapRoot(args) {
|
|
6485
6304
|
const [firstArg, secondArg] = args;
|
|
@@ -6499,7 +6318,9 @@ function resolveManagedTsconfigBootstrapRoot(args) {
|
|
|
6499
6318
|
"dev",
|
|
6500
6319
|
"open",
|
|
6501
6320
|
"prepare",
|
|
6502
|
-
"
|
|
6321
|
+
"preview",
|
|
6322
|
+
"serve",
|
|
6323
|
+
"upload"
|
|
6503
6324
|
].includes(firstArg)) {
|
|
6504
6325
|
if (secondArg && !secondArg.startsWith("-")) return path.resolve(secondArg);
|
|
6505
6326
|
return process.cwd();
|
|
@@ -6509,11 +6330,19 @@ function resolveManagedTsconfigBootstrapRoot(args) {
|
|
|
6509
6330
|
try {
|
|
6510
6331
|
const args = process.argv.slice(2);
|
|
6511
6332
|
if (!await tryRunIdeCommand(args)) {
|
|
6512
|
-
const managedTsconfigBootstrapRoot = resolveManagedTsconfigBootstrapRoot(args);
|
|
6513
|
-
if (managedTsconfigBootstrapRoot) await syncManagedTsconfigBootstrapFiles(managedTsconfigBootstrapRoot);
|
|
6514
6333
|
cli.parse(process.argv, { run: false });
|
|
6515
|
-
|
|
6516
|
-
|
|
6334
|
+
const runCommand = async () => {
|
|
6335
|
+
const uiHost = Array.isArray(cli.options.uiHost) ? cli.options.uiHost.at(-1) : cli.options.uiHost;
|
|
6336
|
+
parseDashboardUiHost(uiHost);
|
|
6337
|
+
const managedTsconfigBootstrapRoot = resolveManagedTsconfigBootstrapRoot(args);
|
|
6338
|
+
if (managedTsconfigBootstrapRoot) await syncManagedTsconfigBootstrapFiles(managedTsconfigBootstrapRoot);
|
|
6339
|
+
if (cli.matchedCommand?.name === "" && cli.matchedCommandName === void 0) await maybeAutoStartMcpServer(args, cli.options);
|
|
6340
|
+
return cli.runMatchedCommand();
|
|
6341
|
+
};
|
|
6342
|
+
if (cli.matchedCommand?.name === "upload") {
|
|
6343
|
+
await outputUploadReport((Array.isArray(cli.options.json) ? cli.options.json.at(-1) : cli.options.json) === true, runCommand);
|
|
6344
|
+
scheduleCompletedProductionBuildExit({}, void 0);
|
|
6345
|
+
} else await runCommand();
|
|
6517
6346
|
}
|
|
6518
6347
|
} catch (error) {
|
|
6519
6348
|
if (!handlePrepareLifecycleError(process.argv.slice(2), error)) {
|