weapp-vite 7.4.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 +51 -1
- 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 +1420 -1554
- package/dist/compatibility.mjs +1 -2
- package/dist/{config-Dbvj5yK7.d.mts → config-V0L-IAJf.d.mts} +87 -30
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +1 -1
- package/dist/{constants-WOsG3Eqd.mjs → constants-DEZdcsMn.mjs} +1 -1
- package/dist/{createContext-UOEgeYnA.mjs → createCompilerContextInstance-DJH_P_nZ.mjs} +31278 -32148
- 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 +1 -1
- package/dist/dashboard-DvqF3Key.mjs +1842 -0
- package/dist/dashboard.d.mts +3 -2
- package/dist/dashboard.mjs +1 -2
- 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 +51 -1
- package/dist/docs/ai-workflows.md +4 -0
- package/dist/docs/doctor.md +38 -0
- package/dist/docs/getting-started.md +3 -1
- package/dist/docs/index.md +3 -1
- 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 +40 -3
- package/dist/docs/vite-plugin.md +171 -0
- package/dist/docs/vue-sfc.md +19 -0
- package/dist/docs/weapp-config.md +54 -0
- 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-giE9UMZa.mjs → file-sC3jD7Fh.mjs} +15 -9
- 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-DTWs5QRi.mjs → platform-CnkySBKz.mjs} +1 -1
- 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-D0RcEWrq.mjs → upload-D4msblBf.mjs} +174 -15
- package/dist/upload-worker.mjs +136 -3
- 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 +67 -41
- package/dist/adapters-BwnMMIZR.mjs +0 -2
- package/dist/dashboard-BzaVesmQ.mjs +0 -558
- package/dist/file-D19nbDoN.mjs +0 -2
- package/dist/getInstance-CYF1vl5l.mjs +0 -2
- package/dist/index-DbqVupg0.d.mts +0 -246
- package/dist/logger-mt4mSTqV.mjs +0 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,44 +1,47 @@
|
|
|
1
1
|
import { a as resolveWeappViteHostMeta } from "./pluginHost--CaeyWpA.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { l as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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";
|
|
10
20
|
import path, { posix } from "pathe";
|
|
11
|
-
import fs from "node:fs";
|
|
21
|
+
import fs, { readFileSync } from "node:fs";
|
|
12
22
|
import { fileURLToPath } from "node:url";
|
|
13
23
|
import path$1 from "node:path";
|
|
14
24
|
import { defu } from "@weapp-core/shared";
|
|
15
25
|
import { fs as fs$1 } from "@weapp-core/shared/fs";
|
|
16
|
-
import process from "node:process";
|
|
17
26
|
import { parse } from "vue/compiler-sfc";
|
|
18
|
-
import fs$2, { access, mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
|
19
|
-
import { build
|
|
20
|
-
import { randomUUID } from "node:crypto";
|
|
21
|
-
import { parseArgs
|
|
22
|
-
import os from "node:os";
|
|
23
|
-
import { execFile, fork, 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";
|
|
24
31
|
import { Buffer } from "node:buffer";
|
|
32
|
+
import { fork, spawn } from "node:child_process";
|
|
25
33
|
import { cac } from "cac";
|
|
26
|
-
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";
|
|
27
35
|
import { brotliCompressSync, gzipSync } from "node:zlib";
|
|
28
|
-
import { defineRpcFunction } from "devframe";
|
|
29
36
|
import { buildOtpAuthUrl, refreshTempAuthCode } from "devframe/node/auth";
|
|
30
37
|
import { resolveCommand } from "package-manager-detector/commands";
|
|
31
|
-
import { Server } from "node:http";
|
|
32
|
-
import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_SSE_ROUTE, DEVFRAME_WS_ROUTE } from "devframe/constants";
|
|
33
|
-
import { initDevframe } from "devframe/initiate";
|
|
34
38
|
import semverInc from "semver/functions/inc.js";
|
|
35
39
|
import { x } from "tinyexec";
|
|
40
|
+
import { parse as parse$2 } from "dotenv";
|
|
36
41
|
import { expand } from "dotenv-expand";
|
|
37
42
|
import { generateJs, generateJson, generateWxml, generateWxss } from "@weapp-core/schematics";
|
|
38
43
|
import { determineAgent } from "@vercel/detect-agent";
|
|
39
44
|
import { initConfig } from "@weapp-core/init";
|
|
40
|
-
import { createInterface } from "node:readline/promises";
|
|
41
|
-
import { clearTimeout as clearTimeout$1, setTimeout as setTimeout$1 } from "node:timers";
|
|
42
45
|
import net from "node:net";
|
|
43
46
|
//#region src/cli/runtime.ts
|
|
44
47
|
function logRuntimeTarget(targets, options = {}) {
|
|
@@ -64,6 +67,9 @@ function resolveRuntimeTargets(options) {
|
|
|
64
67
|
rawPlatform
|
|
65
68
|
};
|
|
66
69
|
}
|
|
70
|
+
function resolveConfiguredRuntimeTargets(targets, platform) {
|
|
71
|
+
return targets.rawPlatform === void 0 && platform === "web" ? resolveRuntimeTargets({ platform: "web" }) : targets;
|
|
72
|
+
}
|
|
67
73
|
function createInlineConfig(execution, options = {}) {
|
|
68
74
|
const configs = execution.entries.map((entry) => entry.driver.createInlineConfig({
|
|
69
75
|
execution,
|
|
@@ -207,33 +213,10 @@ async function executeWechatIdeCliCommand(argv, options = {}) {
|
|
|
207
213
|
}
|
|
208
214
|
//#endregion
|
|
209
215
|
//#region src/cli/openIde/close.ts
|
|
210
|
-
const execFileAsync = promisify(execFile);
|
|
211
|
-
async function closeIdeByAppleScript() {
|
|
212
|
-
if (process.platform !== "darwin") return false;
|
|
213
|
-
const appName = process.env.WEAPP_DEVTOOLS_APP_NAME || "wechatwebdevtools";
|
|
214
|
-
try {
|
|
215
|
-
await execFileAsync("osascript", ["-e", `tell application "${appName}" to quit`]);
|
|
216
|
-
return true;
|
|
217
|
-
} catch {
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
async function closeIdeByProcessKill(cliPath) {
|
|
222
|
-
if (!cliPath) return false;
|
|
223
|
-
const appContentsRoot = cliPath.includes(".app/") ? cliPath.slice(0, cliPath.indexOf(".app/") + 4) : path.dirname(path.dirname(cliPath));
|
|
224
|
-
try {
|
|
225
|
-
await execFileAsync("pkill", ["-f", appContentsRoot]);
|
|
226
|
-
return true;
|
|
227
|
-
} catch {
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
216
|
/**
|
|
232
|
-
* @description
|
|
217
|
+
* @description 执行用户显式请求的 CLI close;失败时保留宿主,不升级为系统级退出。
|
|
233
218
|
*/
|
|
234
219
|
async function closeIde$1() {
|
|
235
|
-
const config = await getConfig();
|
|
236
|
-
const cliPath = config.cliPath?.trim() ? config.cliPath : null;
|
|
237
220
|
try {
|
|
238
221
|
await closeWechatIdeProject();
|
|
239
222
|
return true;
|
|
@@ -249,17 +232,9 @@ async function closeIde$1() {
|
|
|
249
232
|
logger_default.error(retryError);
|
|
250
233
|
}
|
|
251
234
|
else {
|
|
252
|
-
logger_default.warn("微信开发者工具 CLI close
|
|
235
|
+
logger_default.warn("微信开发者工具 CLI close 执行失败,已保留现有宿主;请在开发者工具中手动关闭目标项目。");
|
|
253
236
|
logger_default.error(error);
|
|
254
237
|
}
|
|
255
|
-
if (await closeIdeByAppleScript()) {
|
|
256
|
-
logger_default.info("已回退为系统级关闭微信开发者工具。");
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
if (await closeIdeByProcessKill(cliPath)) {
|
|
260
|
-
logger_default.info("已回退为进程级关闭微信开发者工具。");
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
238
|
return false;
|
|
264
239
|
}
|
|
265
240
|
}
|
|
@@ -308,7 +283,7 @@ function logWechatIdeServicePortDisabledHint(projectPath) {
|
|
|
308
283
|
const OPENED_PROJECT_HEALTH_CHECK_TIMEOUT = 3e3;
|
|
309
284
|
const OPEN_AUTOMATOR_TIMEOUT = 12e4;
|
|
310
285
|
function formatReuseOpenedWechatIdePrompt() {
|
|
311
|
-
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(" / ")} 重新连接目标项目(保留现有窗口)。`;
|
|
312
287
|
}
|
|
313
288
|
function disconnectMiniProgram(miniProgram) {
|
|
314
289
|
miniProgram.disconnect();
|
|
@@ -363,7 +338,7 @@ async function connectOpenedProject(projectPath) {
|
|
|
363
338
|
/**
|
|
364
339
|
* @description 若当前项目已在微信开发者工具中打开且自动化可连通,则直接复用现有会话,避免重复拉起 IDE。
|
|
365
340
|
*/
|
|
366
|
-
async function tryReuseOpenedWechatIde(projectPath,
|
|
341
|
+
async function tryReuseOpenedWechatIde(projectPath, options = {}) {
|
|
367
342
|
const connection = await connectOpenedProject(projectPath);
|
|
368
343
|
if (connection.status === "unhealthy") return {
|
|
369
344
|
reopened: false,
|
|
@@ -385,8 +360,7 @@ async function tryReuseOpenedWechatIde(projectPath, closeIde, options = {}) {
|
|
|
385
360
|
reopened: false,
|
|
386
361
|
reused: true
|
|
387
362
|
};
|
|
388
|
-
logger_default.info(colors.bold(colors.green("
|
|
389
|
-
if (!await closeIde()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。");
|
|
363
|
+
logger_default.info(colors.bold(colors.green("正在重新连接目标项目,保留现有微信开发者工具窗口...")));
|
|
390
364
|
await openWechatIdeByAutomator(projectPath, {
|
|
391
365
|
preserveProjectRoot: options.preserveProjectRoot,
|
|
392
366
|
trustProject: options.trustProject
|
|
@@ -397,16 +371,15 @@ async function tryReuseOpenedWechatIde(projectPath, closeIde, options = {}) {
|
|
|
397
371
|
};
|
|
398
372
|
}
|
|
399
373
|
/**
|
|
400
|
-
* @description
|
|
374
|
+
* @description 重新连接目标项目以刷新构建产物;连接失败不授予关闭共享宿主的权限。
|
|
401
375
|
*/
|
|
402
|
-
async function reopenOpenedWechatIde(projectPath,
|
|
376
|
+
async function reopenOpenedWechatIde(projectPath, options = {}) {
|
|
403
377
|
const connection = await connectOpenedProject(projectPath);
|
|
404
378
|
if (connection.status === "connected") {
|
|
405
379
|
disconnectMiniProgram(connection.miniProgram);
|
|
406
|
-
logger_default.info("
|
|
407
|
-
} else if (connection.status === "unhealthy") logger_default.info("目标项目的 automator
|
|
408
|
-
else logger_default.info("未检测到可复用的 automator
|
|
409
|
-
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 会话,将尝试打开目标项目,保留其他窗口。");
|
|
410
383
|
await openWechatIdeByAutomator(projectPath, options);
|
|
411
384
|
return true;
|
|
412
385
|
}
|
|
@@ -417,11 +390,7 @@ function shouldLogAutomatorFallbackError() {
|
|
|
417
390
|
return flag === "1" || flag === "true";
|
|
418
391
|
}
|
|
419
392
|
const PREPARE_AUTOMATOR_SESSION_TIMEOUT = 8e3;
|
|
420
|
-
const AUTOMATOR_RECOVERY_QUIT_SETTLE_MS = 5e3;
|
|
421
393
|
const RESET_FILEUTILS_ENV = "WEAPP_VITE_RESET_IDE_FILEUTILS";
|
|
422
|
-
function sleep$1(ms) {
|
|
423
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
424
|
-
}
|
|
425
394
|
function readProjectConfigObject(projectPath) {
|
|
426
395
|
try {
|
|
427
396
|
const raw = fs.readFileSync(path.join(projectPath, "project.config.json"), "utf8");
|
|
@@ -450,7 +419,10 @@ function shouldResetWechatIdeFileUtils() {
|
|
|
450
419
|
async function runWechatIdeOpenWithRetry(argv) {
|
|
451
420
|
await executeWechatIdeCliCommand(argv, {
|
|
452
421
|
cancelLevel: "warn",
|
|
453
|
-
onNonLoginError: (error) =>
|
|
422
|
+
onNonLoginError: (error) => {
|
|
423
|
+
logger_default.error(error);
|
|
424
|
+
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL?.trim()) throw error;
|
|
425
|
+
},
|
|
454
426
|
onRetry: () => {
|
|
455
427
|
logger_default.info(colors.bold(colors.green("正在重试连接微信开发者工具...")));
|
|
456
428
|
},
|
|
@@ -477,25 +449,15 @@ function resolveIdeProjectRoot(mpDistRoot, cwd, generatedProjectConfig = false)
|
|
|
477
449
|
async function closeIde() {
|
|
478
450
|
return await closeIde$1();
|
|
479
451
|
}
|
|
480
|
-
async function restartWechatIdeForAutomatorRecovery() {
|
|
481
|
-
try {
|
|
482
|
-
await quitWechatIde();
|
|
483
|
-
await sleep$1(AUTOMATOR_RECOVERY_QUIT_SETTLE_MS);
|
|
484
|
-
return true;
|
|
485
|
-
} catch {
|
|
486
|
-
return await closeIde();
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
452
|
async function tryOpenWechatIdeByAutomator(projectPath, options) {
|
|
490
453
|
const preserveProjectRoot = !shouldUseAutomatorProjectWrapper(projectPath);
|
|
491
454
|
if (options.reuseOpenedProject === false) {
|
|
492
|
-
if (await reopenOpenedWechatIde(projectPath,
|
|
455
|
+
if (await reopenOpenedWechatIde(projectPath, {
|
|
493
456
|
preserveProjectRoot,
|
|
494
|
-
restartIde: restartWechatIdeForAutomatorRecovery,
|
|
495
457
|
trustProject: options.trustProject
|
|
496
458
|
})) return "reopened";
|
|
497
459
|
}
|
|
498
|
-
const reuseResult = await tryReuseOpenedWechatIde(projectPath,
|
|
460
|
+
const reuseResult = await tryReuseOpenedWechatIde(projectPath, {
|
|
499
461
|
preserveProjectRoot,
|
|
500
462
|
promptReopen: options.reuseOpenedProject !== true,
|
|
501
463
|
trustProject: options.trustProject
|
|
@@ -660,12 +622,11 @@ async function recoverOpenedWechatIdeProject(platform, projectPath, servicePortE
|
|
|
660
622
|
logger_default.warn("已跳过微信开发者工具自动恢复;请按上方提示手动关闭并重新打开目标项目。");
|
|
661
623
|
return failedResult;
|
|
662
624
|
}
|
|
663
|
-
logger_default.info(`检测到微信开发者工具打开后状态不稳定(${formatWechatIdeOpenHealthReason(failedResult)}
|
|
664
|
-
if (!await closeIde()) logger_default.warn("自动恢复时关闭当前微信开发者工具失败,仍继续尝试重新打开目标项目。");
|
|
625
|
+
logger_default.info(`检测到微信开发者工具打开后状态不稳定(${formatWechatIdeOpenHealthReason(failedResult)}),正在重试打开目标项目(保留现有窗口)...`);
|
|
665
626
|
await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, options));
|
|
666
627
|
const recoveredResult = await verifyOpenedWechatIdeProject(projectPath, servicePortEnabled, options);
|
|
667
628
|
if (recoveredResult.ok) logger_default.info("微信开发者工具已完成自动恢复。");
|
|
668
|
-
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` 跳过自动重试,并按提示手动处理。");
|
|
669
630
|
return recoveredResult;
|
|
670
631
|
}
|
|
671
632
|
async function verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, servicePortEnabled, options) {
|
|
@@ -702,6 +663,7 @@ async function openIde(platform, projectPath, options = {}) {
|
|
|
702
663
|
return;
|
|
703
664
|
}
|
|
704
665
|
} catch (error) {
|
|
666
|
+
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL?.trim()) throw error;
|
|
705
667
|
if (isAutomatorLoginError(error)) {
|
|
706
668
|
logger_default.error("检测到微信开发者工具登录状态失效,请先登录后重试。");
|
|
707
669
|
logger_default.warn(formatAutomatorLoginError(error));
|
|
@@ -709,9 +671,6 @@ async function openIde(platform, projectPath, options = {}) {
|
|
|
709
671
|
logger_default.warn(`通过 automator 启动微信开发者工具失败,回退到普通 open 流程:${formatWechatIdeAutomatorFailure(error)}`);
|
|
710
672
|
if (shouldLogAutomatorFallbackError()) logger_default.error(error);
|
|
711
673
|
}
|
|
712
|
-
else if (platform === "weapp" && projectPath && normalizedOptions.reuseOpenedProject === false) {
|
|
713
|
-
if (!await closeIde()) logger_default.warn("关闭当前微信开发者工具失败,仍继续尝试打开目标项目。");
|
|
714
|
-
}
|
|
715
674
|
await runWechatIdeOpenWithRetry(createIdeOpenArgv(platform, projectPath, normalizedOptions));
|
|
716
675
|
if (platform === "weapp" && projectPath && normalizedOptions.prepareAutomatorSession) await prepareWechatIdeAutomatorSession(projectPath, normalizedOptions);
|
|
717
676
|
if (platform === "weapp" && projectPath && !normalizedOptions.skipPostOpenHealthCheck) await verifyAndRecoverOpenedWechatIdeProject(platform, projectPath, bootstrapResult?.servicePortEnabled, normalizedOptions);
|
|
@@ -753,35 +712,6 @@ async function resolveIdeCommandContext(options) {
|
|
|
753
712
|
};
|
|
754
713
|
}
|
|
755
714
|
//#endregion
|
|
756
|
-
//#region src/cli/options.ts
|
|
757
|
-
function filterDuplicateOptions(options) {
|
|
758
|
-
for (const [key, value] of Object.entries(options)) if (Array.isArray(value)) options[key] = value[value.length - 1];
|
|
759
|
-
}
|
|
760
|
-
function resolveConfigFile(options) {
|
|
761
|
-
if (typeof options.config === "string") return options.config;
|
|
762
|
-
if (typeof options.c === "string") return options.c;
|
|
763
|
-
}
|
|
764
|
-
function convertBase(value) {
|
|
765
|
-
if (value === 0) return "";
|
|
766
|
-
return value;
|
|
767
|
-
}
|
|
768
|
-
function coerceBooleanOption(value) {
|
|
769
|
-
if (value === void 0) return;
|
|
770
|
-
if (typeof value === "boolean") return value;
|
|
771
|
-
if (typeof value === "string") {
|
|
772
|
-
const normalized = value.trim().toLowerCase();
|
|
773
|
-
if (normalized === "") return true;
|
|
774
|
-
if (normalized === "false" || normalized === "0" || normalized === "off" || normalized === "no") return false;
|
|
775
|
-
if (normalized === "true" || normalized === "1" || normalized === "on" || normalized === "yes") return true;
|
|
776
|
-
return true;
|
|
777
|
-
}
|
|
778
|
-
if (typeof value === "number") return value !== 0;
|
|
779
|
-
return Boolean(value);
|
|
780
|
-
}
|
|
781
|
-
function isUiEnabled(options) {
|
|
782
|
-
return Boolean(options.ui || options.analyze);
|
|
783
|
-
}
|
|
784
|
-
//#endregion
|
|
785
715
|
//#region src/cli/commands/alipayExecute.ts
|
|
786
716
|
function createSpawnOptions() {
|
|
787
717
|
return {
|
|
@@ -867,6 +797,154 @@ function registerAlipayCommand(cli) {
|
|
|
867
797
|
});
|
|
868
798
|
}
|
|
869
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
|
|
870
948
|
//#region src/analyze/hmr.ts
|
|
871
949
|
function createMetricSummary(values) {
|
|
872
950
|
if (!values.length) return { count: 0 };
|
|
@@ -905,26 +983,19 @@ function isFiniteNumber$1(value) {
|
|
|
905
983
|
* @description 聚合 HMR JSONL profile,为命令行与后续仪表盘复用。
|
|
906
984
|
*/
|
|
907
985
|
async function analyzeHmrProfile(options) {
|
|
908
|
-
const
|
|
909
|
-
const samples = [];
|
|
910
|
-
let skippedLineCount = 0;
|
|
911
|
-
for (const line of lines) {
|
|
912
|
-
const trimmed = line.trim();
|
|
913
|
-
if (!trimmed) continue;
|
|
914
|
-
try {
|
|
915
|
-
const parsed = JSON.parse(trimmed);
|
|
916
|
-
if (!isFiniteNumber$1(parsed.totalMs)) {
|
|
917
|
-
skippedLineCount += 1;
|
|
918
|
-
continue;
|
|
919
|
-
}
|
|
920
|
-
samples.push(parsed);
|
|
921
|
-
} catch {
|
|
922
|
-
skippedLineCount += 1;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
986
|
+
const { samples, coverage: inputCoverage, skippedLineCount } = readHmrProfileLines(await fs$1.readFile(options.profilePath, "utf8"));
|
|
925
987
|
const eventCounts = /* @__PURE__ */ new Map();
|
|
926
988
|
const dirtyReasonCounts = /* @__PURE__ */ new Map();
|
|
927
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 = [];
|
|
928
999
|
const totalValues = [];
|
|
929
1000
|
const buildCoreValues = [];
|
|
930
1001
|
const buildStartValues = [];
|
|
@@ -974,6 +1045,15 @@ async function analyzeHmrProfile(options) {
|
|
|
974
1045
|
const resolveCountValues = [];
|
|
975
1046
|
const skippedLoadedCountValues = [];
|
|
976
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);
|
|
977
1057
|
totalValues.push(sample.totalMs);
|
|
978
1058
|
if (sample.event) eventCounts.set(sample.event, (eventCounts.get(sample.event) ?? 0) + 1);
|
|
979
1059
|
if (isFiniteNumber$1(sample.buildCoreMs)) buildCoreValues.push(sample.buildCoreMs);
|
|
@@ -1040,9 +1120,24 @@ async function analyzeHmrProfile(options) {
|
|
|
1040
1120
|
profilePath: options.profilePath,
|
|
1041
1121
|
sampleCount: samples.length,
|
|
1042
1122
|
skippedLineCount,
|
|
1123
|
+
inputCoverage,
|
|
1124
|
+
timelines: samples.map((sample) => ({
|
|
1125
|
+
batchId: sample.batchId,
|
|
1126
|
+
completionBoundary: sample.completionBoundary,
|
|
1127
|
+
...attributeHmrProfile(sample)
|
|
1128
|
+
})),
|
|
1043
1129
|
firstTimestamp: orderedByTime[0]?.timestamp,
|
|
1044
1130
|
lastTimestamp: orderedByTime[orderedByTime.length - 1]?.timestamp,
|
|
1045
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),
|
|
1046
1141
|
totalMs: createMetricSummary(totalValues),
|
|
1047
1142
|
buildCoreMs: createMetricSummary(buildCoreValues),
|
|
1048
1143
|
buildStartMs: createMetricSummary(buildStartValues),
|
|
@@ -1103,7 +1198,7 @@ async function analyzeHmrProfile(options) {
|
|
|
1103
1198
|
//#endregion
|
|
1104
1199
|
//#region src/analyze/preload/budget.ts
|
|
1105
1200
|
const PRELOAD_LIMIT_BYTES = 2097152;
|
|
1106
|
-
function createPackageSizeMap
|
|
1201
|
+
function createPackageSizeMap(result) {
|
|
1107
1202
|
return new Map((result?.packages ?? []).map((pkg) => [pkg.id, pkg.files.reduce((total, file) => total + (file.size ?? 0), 0)]));
|
|
1108
1203
|
}
|
|
1109
1204
|
function getConfiguredPackages(rule, appJson) {
|
|
@@ -1113,7 +1208,7 @@ function getConfiguredPackages(rule, appJson) {
|
|
|
1113
1208
|
return packages.flatMap((item) => typeof item === "string" ? [resolvePreloadPackageIdentifier(item, appJson)] : []).filter((item) => Boolean(item));
|
|
1114
1209
|
}
|
|
1115
1210
|
function createPreloadBudgets(appJson, result, configuredRules, packageAnalysis) {
|
|
1116
|
-
const packageSizes = createPackageSizeMap
|
|
1211
|
+
const packageSizes = createPackageSizeMap(packageAnalysis);
|
|
1117
1212
|
const bySourcePackage = /* @__PURE__ */ new Map();
|
|
1118
1213
|
const ensureSourcePackage = (page) => {
|
|
1119
1214
|
const sourcePackage = page.packageRoot ?? "__main__";
|
|
@@ -1442,140 +1537,100 @@ function analyzeComponentUsage(options) {
|
|
|
1442
1537
|
});
|
|
1443
1538
|
}
|
|
1444
1539
|
//#endregion
|
|
1445
|
-
//#region src/analyze/subpackages/
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
const
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
const packageFallback = resolveBudgetValue(legacyPackageBudget, defaultPackageBudgetBytes);
|
|
1461
|
-
return {
|
|
1462
|
-
totalBytes: resolveBudgetValue(budgets?.totalBytes, defaultTotalBudgetBytes),
|
|
1463
|
-
mainBytes: resolveBudgetValue(budgets?.mainBytes, packageFallback),
|
|
1464
|
-
subPackageBytes: resolveBudgetValue(budgets?.subPackageBytes, packageFallback),
|
|
1465
|
-
independentBytes: resolveBudgetValue(budgets?.independentBytes, packageFallback),
|
|
1466
|
-
warningRatio: resolveBudgetValue(budgets?.warningRatio, defaultWarningRatio),
|
|
1467
|
-
source: budgets ? "config" : "default"
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1470
|
-
function resolveAnalyzeHistoryMetadata(configService) {
|
|
1471
|
-
const history = configService.weappViteConfig.analyze?.history;
|
|
1472
|
-
if (history === false) return {
|
|
1473
|
-
enabled: false,
|
|
1474
|
-
dir: path.resolve(configService.cwd, defaultHistoryDir),
|
|
1475
|
-
limit: defaultHistoryLimit
|
|
1476
|
-
};
|
|
1477
|
-
const historyConfig = typeof history === "object" && history ? history : {};
|
|
1478
|
-
const rawDir = typeof historyConfig.dir === "string" && historyConfig.dir.trim() ? historyConfig.dir.trim() : defaultHistoryDir;
|
|
1479
|
-
return {
|
|
1480
|
-
enabled: historyConfig.enabled !== false,
|
|
1481
|
-
dir: path.isAbsolute(rawDir) ? rawDir : path.resolve(configService.cwd, rawDir),
|
|
1482
|
-
limit: resolveHistoryLimit(historyConfig.limit)
|
|
1483
|
-
};
|
|
1484
|
-
}
|
|
1485
|
-
function createAnalyzeMetadata(configService, now = /* @__PURE__ */ new Date()) {
|
|
1486
|
-
const history = resolveAnalyzeHistoryMetadata(configService);
|
|
1487
|
-
const packageName = configService.packageJson?.name;
|
|
1488
|
-
return {
|
|
1489
|
-
projectName: typeof packageName === "string" && packageName.trim() ? packageName.trim() : path.basename(configService.cwd),
|
|
1490
|
-
generatedAt: now.toISOString(),
|
|
1491
|
-
budgets: resolveAnalyzeBudgets(configService),
|
|
1492
|
-
history: {
|
|
1493
|
-
...history,
|
|
1494
|
-
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;
|
|
1495
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;
|
|
1496
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";
|
|
1497
1576
|
}
|
|
1498
1577
|
//#endregion
|
|
1499
|
-
//#region src/analyze/subpackages/
|
|
1500
|
-
|
|
1501
|
-
const
|
|
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
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
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
|
+
});
|
|
1538
1622
|
}
|
|
1623
|
+
const artifacts = [...files.values()].sort((a, b) => a.file.localeCompare(b.file));
|
|
1624
|
+
if (artifacts.length === 0) throw new Error("未生成可分析产物,不能把缺失输出计为零字节。");
|
|
1539
1625
|
return {
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
return posix.normalize(id);
|
|
1549
|
-
}
|
|
1550
|
-
function resolveModuleSourceType(absoluteId, ctx) {
|
|
1551
|
-
const { configService } = ctx;
|
|
1552
|
-
const isNodeModule = absoluteId.includes("/node_modules/") || absoluteId.includes("\\node_modules\\");
|
|
1553
|
-
const pluginRoot = configService.absolutePluginRoot;
|
|
1554
|
-
const srcRoot = configService.absoluteSrcRoot;
|
|
1555
|
-
const sourceType = classifyModuleSourceKind({
|
|
1556
|
-
isNodeModule,
|
|
1557
|
-
inSrc: isPathInside(srcRoot, absoluteId),
|
|
1558
|
-
inPlugin: pluginRoot ? isPathInside(pluginRoot, absoluteId) : false
|
|
1559
|
-
});
|
|
1560
|
-
return {
|
|
1561
|
-
source: configService.relativeAbsoluteSrcRoot(absoluteId),
|
|
1562
|
-
sourceType
|
|
1563
|
-
};
|
|
1564
|
-
}
|
|
1565
|
-
function resolveAssetSource(fileName, ctx) {
|
|
1566
|
-
const { configService } = ctx;
|
|
1567
|
-
const normalized = posix.normalize(fileName);
|
|
1568
|
-
const srcCandidate = posix.isAbsolute(normalized) ? normalized : posix.resolve(configService.absoluteSrcRoot, normalized);
|
|
1569
|
-
if (isPathInside(configService.absoluteSrcRoot, srcCandidate)) return {
|
|
1570
|
-
absolute: srcCandidate,
|
|
1571
|
-
source: configService.relativeAbsoluteSrcRoot(srcCandidate),
|
|
1572
|
-
sourceType: "src"
|
|
1573
|
-
};
|
|
1574
|
-
const pluginAbsolute = resolvePluginAssetAbsolute(normalized, configService.absolutePluginRoot);
|
|
1575
|
-
if (pluginAbsolute) return {
|
|
1576
|
-
absolute: pluginAbsolute,
|
|
1577
|
-
source: configService.relativeAbsoluteSrcRoot(pluginAbsolute),
|
|
1578
|
-
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
|
+
}
|
|
1579
1634
|
};
|
|
1580
1635
|
}
|
|
1581
1636
|
//#endregion
|
|
@@ -1630,6 +1685,7 @@ function processChunk(chunk, origin, ctx, classifierContext, packages, modules)
|
|
|
1630
1685
|
type: "chunk",
|
|
1631
1686
|
from: origin,
|
|
1632
1687
|
size: typeof chunk.code === "string" ? Buffer.byteLength(chunk.code, "utf8") : void 0,
|
|
1688
|
+
sha256: createHash("sha256").update(chunk.code).digest("hex"),
|
|
1633
1689
|
...getCompressedSizes$1(chunk.code),
|
|
1634
1690
|
isEntry: chunk.isEntry,
|
|
1635
1691
|
modules: []
|
|
@@ -1637,10 +1693,15 @@ function processChunk(chunk, origin, ctx, classifierContext, packages, modules)
|
|
|
1637
1693
|
if (chunk.imports.length > 0) chunkEntry.imports = [...chunk.imports].sort((a, b) => a.localeCompare(b));
|
|
1638
1694
|
if (chunk.dynamicImports.length > 0) chunkEntry.dynamicImports = [...chunk.dynamicImports].sort((a, b) => a.localeCompare(b));
|
|
1639
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);
|
|
1640
1697
|
for (const [rawModuleId, info] of moduleEntries) {
|
|
1641
|
-
const
|
|
1698
|
+
const generatedRuntime = rawModuleId === "\0rolldown/runtime.js";
|
|
1699
|
+
const absoluteId = generatedRuntime ? rawModuleId : normalizeModuleId(rawModuleId);
|
|
1642
1700
|
if (!absoluteId) continue;
|
|
1643
|
-
const { source, sourceType } =
|
|
1701
|
+
const { source, sourceType } = generatedRuntime ? {
|
|
1702
|
+
source: "rolldown:runtime",
|
|
1703
|
+
sourceType: "workspace"
|
|
1704
|
+
} : resolveModuleSourceType(absoluteId, ctx);
|
|
1644
1705
|
const moduleEntry = {
|
|
1645
1706
|
id: absoluteId,
|
|
1646
1707
|
source,
|
|
@@ -1663,6 +1724,7 @@ function processAsset(asset, origin, ctx, classifierContext, packages, modules)
|
|
|
1663
1724
|
type: "asset",
|
|
1664
1725
|
from: origin,
|
|
1665
1726
|
size: assetBuffer?.byteLength,
|
|
1727
|
+
sha256: assetBuffer ? createHash("sha256").update(assetBuffer).digest("hex") : void 0,
|
|
1666
1728
|
...getCompressedSizes$1(assetBuffer)
|
|
1667
1729
|
};
|
|
1668
1730
|
let assetSource;
|
|
@@ -1683,7 +1745,16 @@ function processOutput(output, origin, ctx, classifierContext, packages, modules
|
|
|
1683
1745
|
processChunk(item, origin, ctx, classifierContext, packages, modules);
|
|
1684
1746
|
onArtifact?.(item.fileName, item.code);
|
|
1685
1747
|
} else if (item.type === "asset") {
|
|
1686
|
-
|
|
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);
|
|
1687
1758
|
onArtifact?.(item.fileName, item.source);
|
|
1688
1759
|
}
|
|
1689
1760
|
}
|
|
@@ -1788,7 +1859,7 @@ function summarizeSubPackages(metas) {
|
|
|
1788
1859
|
}
|
|
1789
1860
|
//#endregion
|
|
1790
1861
|
//#region src/analyze/subpackages/index.ts
|
|
1791
|
-
async function
|
|
1862
|
+
async function analyzeBuild(ctx, options) {
|
|
1792
1863
|
const { configService, scanService, buildService } = ctx;
|
|
1793
1864
|
if (!configService || !scanService || !buildService) throw new Error("analyzeSubpackages 需要先初始化 configService、scanService 和 buildService。");
|
|
1794
1865
|
await scanService.loadAppEntry();
|
|
@@ -1826,9 +1897,22 @@ async function analyzeSubpackages(ctx, options) {
|
|
|
1826
1897
|
}
|
|
1827
1898
|
expandVirtualModulePlacements(modules, packages, classifierContext);
|
|
1828
1899
|
const subPackages = summarizeSubPackages(subPackageMetas);
|
|
1829
|
-
|
|
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,
|
|
1830
1914
|
metadata: createAnalyzeMetadata(ctx.configService),
|
|
1831
|
-
packages:
|
|
1915
|
+
packages: packageReports,
|
|
1832
1916
|
modules: summarizeModules(modules),
|
|
1833
1917
|
subPackages,
|
|
1834
1918
|
components: analyzeComponentUsage({
|
|
@@ -1837,6 +1921,17 @@ async function analyzeSubpackages(ctx, options) {
|
|
|
1837
1921
|
}),
|
|
1838
1922
|
glassEasel: createGlassEaselAnalyzeResult(ctx)
|
|
1839
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
|
+
}
|
|
1840
1935
|
}
|
|
1841
1936
|
//#endregion
|
|
1842
1937
|
//#region src/analyze/subpackages/history.ts
|
|
@@ -1908,6 +2003,7 @@ function formatAnalyzeBytes(bytes) {
|
|
|
1908
2003
|
return `${value.toFixed(value >= 100 || unitIndex === 0 ? 0 : 2)} ${units[unitIndex]}`;
|
|
1909
2004
|
}
|
|
1910
2005
|
function formatDelta(bytes) {
|
|
2006
|
+
if (bytes === null) return "缺少体积测量";
|
|
1911
2007
|
if (typeof bytes !== "number" || Number.isNaN(bytes) || bytes === 0) return "无变化";
|
|
1912
2008
|
return `${bytes > 0 ? "+" : "-"}${formatAnalyzeBytes(Math.abs(bytes))}`;
|
|
1913
2009
|
}
|
|
@@ -1917,77 +2013,8 @@ function getFileSize(file) {
|
|
|
1917
2013
|
function getCompressedSize(file) {
|
|
1918
2014
|
return file.brotliSize ?? file.gzipSize ?? 0;
|
|
1919
2015
|
}
|
|
1920
|
-
function getBudgetLimit(type, budgets) {
|
|
1921
|
-
if (!budgets) return;
|
|
1922
|
-
if (type === "main") return budgets.mainBytes;
|
|
1923
|
-
if (type === "subPackage") return budgets.subPackageBytes;
|
|
1924
|
-
if (type === "independent") return budgets.independentBytes;
|
|
1925
|
-
}
|
|
1926
|
-
function createPackageSizeMap(result) {
|
|
1927
|
-
return new Map((result?.packages ?? []).map((pkg) => {
|
|
1928
|
-
const totalBytes = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
|
|
1929
|
-
return [pkg.id, totalBytes];
|
|
1930
|
-
}));
|
|
1931
|
-
}
|
|
1932
|
-
function createFileKey(packageId, fileName) {
|
|
1933
|
-
return `${packageId}\u0000${fileName}`;
|
|
1934
|
-
}
|
|
1935
|
-
function createFileSizeMap(result) {
|
|
1936
|
-
const map = /* @__PURE__ */ new Map();
|
|
1937
|
-
for (const pkg of result?.packages ?? []) for (const file of pkg.files) map.set(createFileKey(pkg.id, file.file), getFileSize(file));
|
|
1938
|
-
return map;
|
|
1939
|
-
}
|
|
1940
|
-
function createPackageTypeMap(result) {
|
|
1941
|
-
return new Map(result.packages.map((pkg) => [pkg.id, pkg.type]));
|
|
1942
|
-
}
|
|
1943
|
-
function createModuleByteMap(result) {
|
|
1944
|
-
const map = /* @__PURE__ */ new Map();
|
|
1945
|
-
for (const pkg of result.packages) for (const file of pkg.files) for (const mod of file.modules ?? []) {
|
|
1946
|
-
const bytes = mod.bytes ?? mod.originalBytes ?? 0;
|
|
1947
|
-
map.set(mod.id, Math.max(map.get(mod.id) ?? 0, bytes));
|
|
1948
|
-
}
|
|
1949
|
-
return map;
|
|
1950
|
-
}
|
|
1951
|
-
function classifyIncrementCategory(source, sourceType) {
|
|
1952
|
-
if (source.includes("wevu") || source.includes("@weapp-vite/dashboard")) return "WeVu / runtime";
|
|
1953
|
-
if (sourceType === "node_modules" || source.includes("node_modules")) return "第三方依赖";
|
|
1954
|
-
if (sourceType === "workspace") return "工作区包";
|
|
1955
|
-
if (sourceType === "plugin") return "插件生成";
|
|
1956
|
-
if (source.endsWith(".wxss") || source.endsWith(".css") || source.endsWith(".scss")) return "样式资源";
|
|
1957
|
-
if (source.endsWith(".wxml") || source.endsWith(".json")) return "页面结构";
|
|
1958
|
-
return "业务源码";
|
|
1959
|
-
}
|
|
1960
|
-
function createIncrementAdvice(item) {
|
|
1961
|
-
if (item.category === "第三方依赖") return "检查依赖是否只在必要分包引用,必要时收敛到公共入口或替换轻量实现。";
|
|
1962
|
-
if (item.category === "WeVu / runtime") return "确认运行时能力是否被新页面引入,优先排查组件和 API 引用边界。";
|
|
1963
|
-
if (item.category === "样式资源") return "检查样式重复、原子类生成范围和组件样式裁剪。";
|
|
1964
|
-
if (item.type === "new-file" || item.type === "new-module") return "确认是否为本次需求必要新增,评估分包归属和懒加载边界。";
|
|
1965
|
-
return "对比本次变更,优先查看新增引用、共享模块和大对象常量。";
|
|
1966
|
-
}
|
|
1967
|
-
function createModuleSizeMap(result) {
|
|
1968
|
-
const map = /* @__PURE__ */ new Map();
|
|
1969
|
-
for (const pkg of result?.packages ?? []) for (const file of pkg.files) for (const module of file.modules ?? []) {
|
|
1970
|
-
const bytes = module.bytes ?? module.originalBytes ?? 0;
|
|
1971
|
-
const existing = map.get(module.id);
|
|
1972
|
-
if (existing && existing.bytes >= bytes) continue;
|
|
1973
|
-
map.set(module.id, {
|
|
1974
|
-
source: module.source,
|
|
1975
|
-
sourceType: module.sourceType,
|
|
1976
|
-
bytes,
|
|
1977
|
-
packageLabel: pkg.label,
|
|
1978
|
-
file: file.file
|
|
1979
|
-
});
|
|
1980
|
-
}
|
|
1981
|
-
return map;
|
|
1982
|
-
}
|
|
1983
|
-
function createDuplicateAdvice(sourceType, packageIds, packageTypeMap, estimatedSavingBytes) {
|
|
1984
|
-
if (packageIds.some((packageId) => packageTypeMap.get(packageId) === "independent")) return estimatedSavingBytes > 0 ? "含独立分包,先确认隔离要求,再评估是否抽公共入口。" : "含独立分包,重复可能来自隔离边界。";
|
|
1985
|
-
if (sourceType === "node_modules") return "依赖被多个包带入,检查引用边界或考虑主包公共入口。";
|
|
1986
|
-
if (sourceType === "src" || sourceType === "workspace") return "共享源码跨包重复,优先抽公共模块或调整分包归属。";
|
|
1987
|
-
if (sourceType === "plugin") return "插件生成内容跨包重复,检查插件产物输出策略。";
|
|
1988
|
-
return "检查该模块是否需要在多个包内重复存在。";
|
|
1989
|
-
}
|
|
1990
2016
|
function formatBudgetStatus(item) {
|
|
2017
|
+
if (item.status === "unknown") return "归因或产物字节不足,无法验收";
|
|
1991
2018
|
if (item.status === "ok") return "正常";
|
|
1992
2019
|
return `${item.status === "exceeded" ? "超预算" : "接近预算"} ${(item.ratio * 100).toFixed(1)}%`;
|
|
1993
2020
|
}
|
|
@@ -2002,124 +2029,22 @@ function createActionItems(options) {
|
|
|
2002
2029
|
if (actions.length === 0) actions.push("当前没有预算超限或高收益重复模块,保持观察即可。");
|
|
2003
2030
|
return actions;
|
|
2004
2031
|
}
|
|
2005
|
-
function
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
const items = [];
|
|
2009
|
-
const totalBytes = result.packages.flatMap((pkg) => pkg.files).reduce((sum, file) => sum + getFileSize(file), 0);
|
|
2010
|
-
const warningRatio = budgets.warningRatio;
|
|
2011
|
-
const createItem = (options) => {
|
|
2012
|
-
const ratio = options.limitBytes > 0 ? options.currentBytes / options.limitBytes : 0;
|
|
2013
|
-
const status = ratio >= 1 ? "exceeded" : ratio >= warningRatio ? "warning" : "ok";
|
|
2014
|
-
return {
|
|
2015
|
-
...options,
|
|
2016
|
-
ratio,
|
|
2017
|
-
status
|
|
2018
|
-
};
|
|
2019
|
-
};
|
|
2020
|
-
items.push(createItem({
|
|
2021
|
-
id: "__total__",
|
|
2022
|
-
label: "总包",
|
|
2023
|
-
scope: "total",
|
|
2024
|
-
currentBytes: totalBytes,
|
|
2025
|
-
limitBytes: budgets.totalBytes
|
|
2026
|
-
}));
|
|
2027
|
-
for (const pkg of result.packages) {
|
|
2028
|
-
const limitBytes = getBudgetLimit(pkg.type, budgets);
|
|
2029
|
-
if (!limitBytes) continue;
|
|
2030
|
-
items.push(createItem({
|
|
2031
|
-
id: pkg.id,
|
|
2032
|
-
label: pkg.label,
|
|
2033
|
-
scope: pkg.type,
|
|
2034
|
-
currentBytes: pkg.files.reduce((sum, file) => sum + getFileSize(file), 0),
|
|
2035
|
-
limitBytes
|
|
2036
|
-
}));
|
|
2037
|
-
}
|
|
2038
|
-
return items.sort((a, b) => b.ratio - a.ratio || a.label.localeCompare(b.label));
|
|
2039
|
-
}
|
|
2040
|
-
function createDuplicateModuleInsights(result) {
|
|
2041
|
-
const moduleByteMap = createModuleByteMap(result);
|
|
2042
|
-
const packageTypeMap = createPackageTypeMap(result);
|
|
2043
|
-
return result.modules.filter((module) => module.packages.length > 1).map((module) => {
|
|
2044
|
-
const bytes = moduleByteMap.get(module.id) ?? 0;
|
|
2045
|
-
const packageIds = module.packages.map((pkg) => pkg.packageId);
|
|
2046
|
-
const estimatedSavingBytes = bytes * Math.max(module.packages.length - 1, 0);
|
|
2047
|
-
return {
|
|
2048
|
-
id: module.id,
|
|
2049
|
-
source: module.source,
|
|
2050
|
-
sourceType: module.sourceType,
|
|
2051
|
-
packageCount: module.packages.length,
|
|
2052
|
-
bytes,
|
|
2053
|
-
estimatedSavingBytes,
|
|
2054
|
-
packages: packageIds,
|
|
2055
|
-
advice: createDuplicateAdvice(module.sourceType, packageIds, packageTypeMap, estimatedSavingBytes)
|
|
2056
|
-
};
|
|
2057
|
-
}).sort((a, b) => b.estimatedSavingBytes - a.estimatedSavingBytes || b.packageCount - a.packageCount || a.source.localeCompare(b.source));
|
|
2058
|
-
}
|
|
2059
|
-
function createAnalyzeIncrementAttribution(result, previousResult) {
|
|
2060
|
-
if (!previousResult) return [];
|
|
2061
|
-
const previousFiles = createFileSizeMap(previousResult);
|
|
2062
|
-
const previousModules = createModuleSizeMap(previousResult);
|
|
2063
|
-
const currentModules = createModuleSizeMap(result);
|
|
2064
|
-
const items = [];
|
|
2065
|
-
for (const pkg of result.packages) for (const file of pkg.files) {
|
|
2066
|
-
const currentBytes = getFileSize(file);
|
|
2067
|
-
const previousBytes = previousFiles.get(createFileKey(pkg.id, file.file)) ?? 0;
|
|
2068
|
-
const deltaBytes = currentBytes - previousBytes;
|
|
2069
|
-
if (deltaBytes <= 0) continue;
|
|
2070
|
-
const type = previousBytes > 0 ? "increased-file" : "new-file";
|
|
2071
|
-
const item = {
|
|
2072
|
-
key: `file:${pkg.id}:${file.file}`,
|
|
2073
|
-
type,
|
|
2074
|
-
label: file.file,
|
|
2075
|
-
category: classifyIncrementCategory(file.source ?? file.file),
|
|
2076
|
-
packageLabel: pkg.label,
|
|
2077
|
-
file: file.file,
|
|
2078
|
-
currentBytes,
|
|
2079
|
-
previousBytes,
|
|
2080
|
-
deltaBytes,
|
|
2081
|
-
advice: ""
|
|
2082
|
-
};
|
|
2083
|
-
items.push({
|
|
2084
|
-
...item,
|
|
2085
|
-
advice: createIncrementAdvice(item)
|
|
2086
|
-
});
|
|
2087
|
-
}
|
|
2088
|
-
for (const [id, module] of currentModules) {
|
|
2089
|
-
const previousBytes = previousModules.get(id)?.bytes ?? 0;
|
|
2090
|
-
const deltaBytes = module.bytes - previousBytes;
|
|
2091
|
-
if (deltaBytes <= 0) continue;
|
|
2092
|
-
const type = previousBytes > 0 ? "increased-module" : "new-module";
|
|
2093
|
-
const item = {
|
|
2094
|
-
key: `module:${id}`,
|
|
2095
|
-
type,
|
|
2096
|
-
label: module.source,
|
|
2097
|
-
category: classifyIncrementCategory(module.source, module.sourceType),
|
|
2098
|
-
packageLabel: module.packageLabel,
|
|
2099
|
-
file: module.file,
|
|
2100
|
-
currentBytes: module.bytes,
|
|
2101
|
-
previousBytes,
|
|
2102
|
-
deltaBytes,
|
|
2103
|
-
advice: ""
|
|
2104
|
-
};
|
|
2105
|
-
items.push({
|
|
2106
|
-
...item,
|
|
2107
|
-
advice: createIncrementAdvice(item)
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
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));
|
|
2111
2035
|
}
|
|
2112
2036
|
function createAnalyzeIncrementCategorySummary(items) {
|
|
2113
2037
|
const map = /* @__PURE__ */ new Map();
|
|
2114
2038
|
for (const item of items) {
|
|
2115
|
-
const
|
|
2116
|
-
|
|
2039
|
+
const category = item.category ?? "";
|
|
2040
|
+
const entry = map.get(category) ?? {
|
|
2041
|
+
category,
|
|
2117
2042
|
count: 0,
|
|
2118
2043
|
deltaBytes: 0
|
|
2119
2044
|
};
|
|
2120
2045
|
entry.count += 1;
|
|
2121
2046
|
entry.deltaBytes += item.deltaBytes;
|
|
2122
|
-
map.set(
|
|
2047
|
+
map.set(category, entry);
|
|
2123
2048
|
}
|
|
2124
2049
|
return [...map.values()].sort((a, b) => b.deltaBytes - a.deltaBytes || b.count - a.count || a.category.localeCompare(b.category));
|
|
2125
2050
|
}
|
|
@@ -2130,8 +2055,8 @@ function createAnalyzePrMarkdownReport(result, previousResult) {
|
|
|
2130
2055
|
})));
|
|
2131
2056
|
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
2132
2057
|
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
2133
|
-
const
|
|
2134
|
-
const incrementItems =
|
|
2058
|
+
const comparison = previousResult ? createAnalyzeComparison(result, previousResult) : void 0;
|
|
2059
|
+
const incrementItems = createReportGrowth(comparison);
|
|
2135
2060
|
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
2136
2061
|
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
2137
2062
|
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
@@ -2147,7 +2072,7 @@ function createAnalyzePrMarkdownReport(result, previousResult) {
|
|
|
2147
2072
|
return [
|
|
2148
2073
|
"## weapp-vite analyze PR 摘要",
|
|
2149
2074
|
"",
|
|
2150
|
-
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}(较上次 ${formatDelta(
|
|
2075
|
+
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}(较上次 ${formatDelta(comparison?.deltaBytes)})`,
|
|
2151
2076
|
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
2152
2077
|
`- 预算告警:${budgetIssues.length}`,
|
|
2153
2078
|
`- 增量归因:${incrementItems.length > 0 ? `${incrementItems.length} 项正向增长` : "无正向增长"}`,
|
|
@@ -2191,11 +2116,11 @@ function createAnalyzeMarkdownReport(result, previousResult) {
|
|
|
2191
2116
|
const totalBytes = files.reduce((sum, item) => sum + getFileSize(item.file), 0);
|
|
2192
2117
|
const compressedBytes = files.reduce((sum, item) => sum + getCompressedSize(item.file), 0);
|
|
2193
2118
|
const duplicateInsights = createDuplicateModuleInsights(result);
|
|
2194
|
-
const
|
|
2119
|
+
const comparison = previousResult ? createAnalyzeComparison(result, previousResult) : void 0;
|
|
2120
|
+
const incrementItems = createReportGrowth(comparison);
|
|
2195
2121
|
const incrementSummary = createAnalyzeIncrementCategorySummary(incrementItems);
|
|
2196
2122
|
const budgetItems = createAnalyzeBudgetCheck(result);
|
|
2197
|
-
const
|
|
2198
|
-
const previousPackageSizes = createPackageSizeMap(previousResult);
|
|
2123
|
+
const packageDeltas = new Map(comparison?.packages.map((item) => [item.packageId, item.deltaBytes]));
|
|
2199
2124
|
const budgets = result.metadata?.budgets;
|
|
2200
2125
|
const budgetIssues = budgetItems.filter((item) => item.status !== "ok");
|
|
2201
2126
|
const actionItems = createActionItems({
|
|
@@ -2205,9 +2130,9 @@ function createAnalyzeMarkdownReport(result, previousResult) {
|
|
|
2205
2130
|
const packageRows = result.packages.map((pkg) => {
|
|
2206
2131
|
const size = pkg.files.reduce((sum, file) => sum + getFileSize(file), 0);
|
|
2207
2132
|
const compressed = pkg.files.reduce((sum, file) => sum + getCompressedSize(file), 0);
|
|
2208
|
-
const
|
|
2133
|
+
const delta = packageDeltas.has(pkg.id) ? packageDeltas.get(pkg.id) : 0;
|
|
2209
2134
|
const budgetStatus = budgetItems.find((item) => item.id === pkg.id);
|
|
2210
|
-
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) : "正常"} |`;
|
|
2211
2136
|
}).join("\n");
|
|
2212
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");
|
|
2213
2138
|
const duplicateRows = duplicateInsights.slice(0, 10).map((module) => `| ${module.source} | ${module.sourceType} | ${module.packageCount} | ${formatAnalyzeBytes(module.estimatedSavingBytes)} | ${module.advice} |`).join("\n");
|
|
@@ -2223,7 +2148,7 @@ function createAnalyzeMarkdownReport(result, previousResult) {
|
|
|
2223
2148
|
"",
|
|
2224
2149
|
`- 总产物体积:${formatAnalyzeBytes(totalBytes)}`,
|
|
2225
2150
|
`- 压缩后体积:${formatAnalyzeBytes(compressedBytes)}`,
|
|
2226
|
-
`- 较上次:${formatDelta(
|
|
2151
|
+
`- 较上次:${formatDelta(comparison?.deltaBytes)}`,
|
|
2227
2152
|
`- 包体数量:${result.packages.length}`,
|
|
2228
2153
|
`- 源码模块:${result.modules.length}`,
|
|
2229
2154
|
`- 跨包复用:${duplicateInsights.length}`,
|
|
@@ -2270,95 +2195,6 @@ function createAnalyzeMarkdownReport(result, previousResult) {
|
|
|
2270
2195
|
].join("\n");
|
|
2271
2196
|
}
|
|
2272
2197
|
//#endregion
|
|
2273
|
-
//#region src/cli/analyze/dashboardPolicy.ts
|
|
2274
|
-
const rejectDashboardSharedStateSet = defineRpcFunction({
|
|
2275
|
-
name: "devframe:rpc:server-state:set",
|
|
2276
|
-
type: "action",
|
|
2277
|
-
handler: () => {
|
|
2278
|
-
throw new Error("Dashboard Devframe shared state 只允许服务端修改。");
|
|
2279
|
-
}
|
|
2280
|
-
});
|
|
2281
|
-
const rejectDashboardSharedStatePatch = defineRpcFunction({
|
|
2282
|
-
name: "devframe:rpc:server-state:patch",
|
|
2283
|
-
type: "action",
|
|
2284
|
-
handler: () => {
|
|
2285
|
-
throw new Error("Dashboard Devframe shared state 只允许服务端修改。");
|
|
2286
|
-
}
|
|
2287
|
-
});
|
|
2288
|
-
/** 独立宿主在开放 RPC 前禁止全局共享状态写入,不影响嵌入宿主的策略。 */
|
|
2289
|
-
function withStandaloneDashboardPolicy(definition) {
|
|
2290
|
-
return {
|
|
2291
|
-
...definition,
|
|
2292
|
-
async setup(ctx, info) {
|
|
2293
|
-
ctx.rpc.register(rejectDashboardSharedStateSet, true);
|
|
2294
|
-
ctx.rpc.register(rejectDashboardSharedStatePatch, true);
|
|
2295
|
-
await definition.setup?.(ctx, info);
|
|
2296
|
-
}
|
|
2297
|
-
};
|
|
2298
|
-
}
|
|
2299
|
-
//#endregion
|
|
2300
|
-
//#region src/cli/analyze/dashboardViteBridge.ts
|
|
2301
|
-
const ANALYZE_DASHBOARD_DEVFRAME_BASE = "/__weapp-vite/";
|
|
2302
|
-
const transportPaths = /* @__PURE__ */ new Set([
|
|
2303
|
-
`${ANALYZE_DASHBOARD_DEVFRAME_BASE}${DEVFRAME_CONNECTION_META_FILENAME}`,
|
|
2304
|
-
`${ANALYZE_DASHBOARD_DEVFRAME_BASE}${DEVFRAME_SSE_ROUTE}`,
|
|
2305
|
-
`${ANALYZE_DASHBOARD_DEVFRAME_BASE}${DEVFRAME_WS_ROUTE}`
|
|
2306
|
-
]);
|
|
2307
|
-
function createAnalyzeDashboardViteBridge(controller) {
|
|
2308
|
-
let instance;
|
|
2309
|
-
let closing;
|
|
2310
|
-
const closeInstance = async () => {
|
|
2311
|
-
controller.dispose();
|
|
2312
|
-
if (closing) return await closing;
|
|
2313
|
-
const current = instance;
|
|
2314
|
-
instance = void 0;
|
|
2315
|
-
if (!current) return;
|
|
2316
|
-
closing = current.close().finally(() => {
|
|
2317
|
-
closing = void 0;
|
|
2318
|
-
});
|
|
2319
|
-
await closing;
|
|
2320
|
-
};
|
|
2321
|
-
return {
|
|
2322
|
-
name: "weapp-vite:dashboard-devframe",
|
|
2323
|
-
apply: "serve",
|
|
2324
|
-
async configureServer(server) {
|
|
2325
|
-
const httpServer = server.httpServer instanceof Server ? server.httpServer : void 0;
|
|
2326
|
-
const created = initDevframe(withStandaloneDashboardPolicy(controller.definition), {
|
|
2327
|
-
base: ANALYZE_DASHBOARD_DEVFRAME_BASE,
|
|
2328
|
-
distDir: false,
|
|
2329
|
-
...httpServer ? { server: httpServer } : {
|
|
2330
|
-
host: "127.0.0.1",
|
|
2331
|
-
ws: { sidecar: true }
|
|
2332
|
-
},
|
|
2333
|
-
allowedOrigins: [],
|
|
2334
|
-
auth: true,
|
|
2335
|
-
mcp: false
|
|
2336
|
-
});
|
|
2337
|
-
try {
|
|
2338
|
-
server.middlewares.use((request, response, next) => {
|
|
2339
|
-
const url = request.url ?? "/";
|
|
2340
|
-
const queryIndex = url.indexOf("?");
|
|
2341
|
-
const pathname = queryIndex < 0 ? url : url.slice(0, queryIndex);
|
|
2342
|
-
if (transportPaths.has(pathname)) created.nodeMiddleware(request, response, next);
|
|
2343
|
-
else next();
|
|
2344
|
-
});
|
|
2345
|
-
await created.ready;
|
|
2346
|
-
instance = created;
|
|
2347
|
-
} catch (error) {
|
|
2348
|
-
controller.dispose();
|
|
2349
|
-
await created.close().catch(() => {});
|
|
2350
|
-
throw error;
|
|
2351
|
-
}
|
|
2352
|
-
server.httpServer?.once("close", () => {
|
|
2353
|
-
closeInstance();
|
|
2354
|
-
});
|
|
2355
|
-
},
|
|
2356
|
-
async closeBundle() {
|
|
2357
|
-
await closeInstance();
|
|
2358
|
-
}
|
|
2359
|
-
};
|
|
2360
|
-
}
|
|
2361
|
-
//#endregion
|
|
2362
2198
|
//#region src/cli/analyze/dashboard.ts
|
|
2363
2199
|
function createInstallCommand(agent) {
|
|
2364
2200
|
const resolved = resolveCommand(agent ?? "npm", "install", [ANALYZE_DASHBOARD_PACKAGE_NAME]);
|
|
@@ -2366,6 +2202,7 @@ function createInstallCommand(agent) {
|
|
|
2366
2202
|
return `${resolved.command} ${resolved.args.join(" ")}`;
|
|
2367
2203
|
}
|
|
2368
2204
|
async function startAnalyzeDashboard(result, options) {
|
|
2205
|
+
const shutdown = getDevShutdownScope();
|
|
2369
2206
|
const resolved = resolveDashboardRoot(options);
|
|
2370
2207
|
if (!resolved) {
|
|
2371
2208
|
logger_default.warn(`[weapp-vite ui] 未安装可选仪表盘包 ${colors.bold(colors.green(ANALYZE_DASHBOARD_PACKAGE_NAME))},已自动降级关闭 dashboard 能力。`);
|
|
@@ -2373,7 +2210,9 @@ async function startAnalyzeDashboard(result, options) {
|
|
|
2373
2210
|
return;
|
|
2374
2211
|
}
|
|
2375
2212
|
const { root, configFile } = resolved;
|
|
2213
|
+
const { ANALYZE_DASHBOARD_DEVFRAME_BASE, ANALYZE_DASHBOARD_HUB_BASE, createAnalyzeDashboardViteBridge } = await import("./dashboardViteBridge-Dy3nAD4m.mjs");
|
|
2376
2214
|
const devframe = createAnalyzeDashboardDevframe({
|
|
2215
|
+
clientAssets: options.uiHost === "hub" ? root : void 0,
|
|
2377
2216
|
snapshot: {
|
|
2378
2217
|
current: result,
|
|
2379
2218
|
previous: options.previousResult ?? null,
|
|
@@ -2392,9 +2231,21 @@ async function startAnalyzeDashboard(result, options) {
|
|
|
2392
2231
|
srcRoot: options.srcRoot ?? (options.cwd ? path.resolve(options.cwd, "src") : void 0)
|
|
2393
2232
|
}
|
|
2394
2233
|
});
|
|
2234
|
+
const bridge = createAnalyzeDashboardViteBridge(devframe, {
|
|
2235
|
+
projectRoot: options.cwd ?? process.cwd(),
|
|
2236
|
+
uiHost: options.uiHost
|
|
2237
|
+
});
|
|
2395
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
|
+
};
|
|
2396
2247
|
try {
|
|
2397
|
-
server = await
|
|
2248
|
+
server = await createDevViteServer({
|
|
2398
2249
|
root,
|
|
2399
2250
|
base: ANALYZE_DASHBOARD_DEVFRAME_BASE,
|
|
2400
2251
|
configFile: configFile ?? false,
|
|
@@ -2405,9 +2256,18 @@ async function startAnalyzeDashboard(result, options) {
|
|
|
2405
2256
|
name: "weapp-vite:dashboard-lifetime",
|
|
2406
2257
|
enforce: "pre",
|
|
2407
2258
|
configureServer(createdServer) {
|
|
2408
|
-
server
|
|
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
|
+
});
|
|
2409
2269
|
}
|
|
2410
|
-
},
|
|
2270
|
+
}, bridge],
|
|
2411
2271
|
server: {
|
|
2412
2272
|
host: "127.0.0.1",
|
|
2413
2273
|
port: 0,
|
|
@@ -2415,16 +2275,19 @@ async function startAnalyzeDashboard(result, options) {
|
|
|
2415
2275
|
},
|
|
2416
2276
|
logLevel: "error"
|
|
2417
2277
|
});
|
|
2418
|
-
await server.listen(0);
|
|
2278
|
+
if (!shutdown?.stopping) await server.listen(0);
|
|
2419
2279
|
} catch (error) {
|
|
2420
|
-
|
|
2421
|
-
|
|
2280
|
+
const cleanupErrors = await closeResources();
|
|
2281
|
+
if (cleanupErrors.length) throw new AggregateError([error, ...cleanupErrors], "Dashboard 启动及资源清理失败", { cause: error });
|
|
2422
2282
|
throw error;
|
|
2423
2283
|
}
|
|
2424
2284
|
const activeServer = server;
|
|
2425
2285
|
const urls = [...activeServer.resolvedUrls?.local ?? [], ...activeServer.resolvedUrls?.network ?? []];
|
|
2426
2286
|
const authCode = refreshTempAuthCode();
|
|
2427
|
-
const authenticatedUrls = urls.map((url) =>
|
|
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
|
+
});
|
|
2428
2291
|
let closing;
|
|
2429
2292
|
let resolveExit;
|
|
2430
2293
|
const waitPromise = new Promise((resolve) => {
|
|
@@ -2432,22 +2295,26 @@ async function startAnalyzeDashboard(result, options) {
|
|
|
2432
2295
|
});
|
|
2433
2296
|
const signals = ["SIGINT", "SIGTERM"];
|
|
2434
2297
|
let onExit;
|
|
2298
|
+
let releaseOwnership;
|
|
2299
|
+
const finish = () => {
|
|
2300
|
+
for (const signal of signals) process.removeListener(signal, onExit);
|
|
2301
|
+
releaseOwnership?.();
|
|
2302
|
+
resolveExit();
|
|
2303
|
+
};
|
|
2304
|
+
onHostClose = () => {
|
|
2305
|
+
if (!closing) finish();
|
|
2306
|
+
};
|
|
2435
2307
|
const close = () => {
|
|
2436
|
-
if (!closing) {
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
activeServer.httpServer?.removeListener("close", onExit);
|
|
2441
|
-
resolveExit();
|
|
2442
|
-
});
|
|
2443
|
-
}
|
|
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);
|
|
2444
2312
|
return closing;
|
|
2445
2313
|
};
|
|
2446
2314
|
onExit = async () => {
|
|
2447
2315
|
await close().catch((error) => logger_default.error(error));
|
|
2448
2316
|
};
|
|
2449
|
-
for (const signal of signals) process.once(signal, onExit);
|
|
2450
|
-
activeServer.httpServer?.once("close", onExit);
|
|
2317
|
+
if (!shutdown) for (const signal of signals) process.once(signal, onExit);
|
|
2451
2318
|
const handle = {
|
|
2452
2319
|
update: devframe.update,
|
|
2453
2320
|
emitRuntimeEvents: devframe.emitRuntimeEvents,
|
|
@@ -2455,6 +2322,11 @@ async function startAnalyzeDashboard(result, options) {
|
|
|
2455
2322
|
close,
|
|
2456
2323
|
urls: authenticatedUrls
|
|
2457
2324
|
};
|
|
2325
|
+
releaseOwnership = shutdown?.own(close);
|
|
2326
|
+
if (shutdown?.stopping) {
|
|
2327
|
+
await close();
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2458
2330
|
if (options.watch) {
|
|
2459
2331
|
if (!options.silentStartupLog) {
|
|
2460
2332
|
logger_default.info("weapp-vite UI 已启动(分析视图,实时模式),按 Ctrl+C 退出。");
|
|
@@ -2469,21 +2341,17 @@ async function startAnalyzeDashboard(result, options) {
|
|
|
2469
2341
|
await waitPromise;
|
|
2470
2342
|
}
|
|
2471
2343
|
//#endregion
|
|
2472
|
-
//#region src/cli/
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
const
|
|
2476
|
-
const
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
if (!Array.isArray(handles)) return;
|
|
2484
|
-
for (const handle of handles) if (isSassEmbeddedChild(handle)) try {
|
|
2485
|
-
handle.kill();
|
|
2486
|
-
} catch {}
|
|
2344
|
+
//#region src/cli/analyze/output.ts
|
|
2345
|
+
/** CLI 单次分析期间保留 JSON 专用写入入口,配置、插件和清理日志走 stderr。 */
|
|
2346
|
+
async function withAnalyzeOutput(json, run) {
|
|
2347
|
+
const stdoutWrite = process.stdout.write;
|
|
2348
|
+
const writeJson = (value) => stdoutWrite.call(process.stdout, `${JSON.stringify(value, null, 2)}\n`);
|
|
2349
|
+
if (json) process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
2350
|
+
try {
|
|
2351
|
+
await run(writeJson);
|
|
2352
|
+
} finally {
|
|
2353
|
+
process.stdout.write = stdoutWrite;
|
|
2354
|
+
}
|
|
2487
2355
|
}
|
|
2488
2356
|
//#endregion
|
|
2489
2357
|
//#region src/cli/commands/analyze.ts
|
|
@@ -2578,13 +2446,17 @@ function printAnalysisSummary(result) {
|
|
|
2578
2446
|
if (duplicates.length > limit) logger_default.info(`- …其余 ${duplicates.length - limit} 项请使用 ${colors.bold(colors.green("weapp-vite analyze --json"))} 查看`);
|
|
2579
2447
|
}
|
|
2580
2448
|
function printBudgetCheckSummary(result) {
|
|
2581
|
-
const exceededItems = createAnalyzeBudgetCheck(result).filter((item) => item.status === "exceeded");
|
|
2449
|
+
const exceededItems = createAnalyzeBudgetCheck(result).filter((item) => item.status === "exceeded" || item.status === "unknown");
|
|
2582
2450
|
if (exceededItems.length === 0) {
|
|
2583
2451
|
logger_default.success("包体预算检查通过");
|
|
2584
2452
|
return false;
|
|
2585
2453
|
}
|
|
2586
2454
|
logger_default.error(`包体预算检查失败:${exceededItems.length} 项超限`);
|
|
2587
|
-
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
|
+
}
|
|
2588
2460
|
return true;
|
|
2589
2461
|
}
|
|
2590
2462
|
function printGlassEaselCheckSummary(result) {
|
|
@@ -2760,107 +2632,111 @@ function printHmrProfileAnalysisSummary(result, configService) {
|
|
|
2760
2632
|
}
|
|
2761
2633
|
}
|
|
2762
2634
|
function registerAnalyzeCommand(cli) {
|
|
2763
|
-
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) => {
|
|
2764
2636
|
filterDuplicateOptions(options);
|
|
2637
|
+
options.uiHost = parseDashboardUiHost(options.uiHost);
|
|
2765
2638
|
const configFile = resolveConfigFile(options);
|
|
2766
2639
|
const outputJson = coerceBooleanOption(options.json);
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
logRuntimeTarget(targets, {
|
|
2787
|
-
silent: outputJson || outputMarkdown,
|
|
2788
|
-
resolvedConfigPlatform: ctx.configService.platform
|
|
2789
|
-
});
|
|
2790
|
-
const outputOption = typeof options.output === "string" ? options.output.trim() : "";
|
|
2791
|
-
if (options.hmrProfile !== void 0 && options.hmrProfile !== false) {
|
|
2792
|
-
const profileOption = typeof options.hmrProfile === "string" && options.hmrProfile.trim() ? options.hmrProfile.trim() : ctx.configService.weappViteConfig.hmr?.profileJson;
|
|
2793
|
-
const profilePath = resolveHmrProfileJsonPath({
|
|
2794
|
-
cwd: ctx.configService.cwd,
|
|
2795
|
-
option: profileOption,
|
|
2796
|
-
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
|
|
2797
2659
|
});
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
if (outputJson) {
|
|
2802
|
-
if (!writtenPath) process.stdout.write(`${JSON.stringify(hmrProfileResult, null, 2)}\n`);
|
|
2803
|
-
} else printHmrProfileAnalysisSummary(hmrProfileResult, ctx.configService);
|
|
2804
|
-
return;
|
|
2805
|
-
}
|
|
2806
|
-
if (coerceBooleanOption(options.preload)) {
|
|
2807
|
-
if (targets.kind !== "miniprogram" || ctx.configService.platform !== "weapp") throw new Error("preloadRule 分析目前仅支持微信小程序平台。");
|
|
2808
|
-
const packageAnalysis = await analyzeSubpackages(ctx);
|
|
2809
|
-
const preloadResult = await analyzePreloadRules(ctx, { packageAnalysis });
|
|
2810
|
-
const writtenPath = await writeAnalyzeResult(preloadResult, outputOption, ctx.configService);
|
|
2811
|
-
if (outputJson && !writtenPath) process.stdout.write(`${JSON.stringify(preloadResult, null, 2)}\n`);
|
|
2812
|
-
if (!outputJson && !writtenPath) printPreloadAnalysisSummary(preloadResult);
|
|
2813
|
-
return;
|
|
2814
|
-
}
|
|
2815
|
-
if (getBackendForCapability(targets, "web", "analyze")) {
|
|
2816
|
-
const webResult = createWebAnalyzeResult(ctx.configService, { platform: "web" });
|
|
2817
|
-
const writtenPath = await writeAnalyzeResult(webResult, outputOption, ctx.configService);
|
|
2818
|
-
if (outputJson) {
|
|
2819
|
-
if (!writtenPath) process.stdout.write(`${JSON.stringify(webResult, null, 2)}\n`);
|
|
2820
|
-
} else printWebAnalysisSummary(webResult);
|
|
2821
|
-
return;
|
|
2822
|
-
}
|
|
2823
|
-
if (!getBackendForCapability(targets, "miniprogram", "analyze")) {
|
|
2824
|
-
logger_default.warn("当前命令不支持该平台,请通过 --platform weapp 或 --platform web 指定目标。");
|
|
2825
|
-
return;
|
|
2826
|
-
}
|
|
2827
|
-
const previousResult = await readLatestAnalyzeHistorySnapshot(ctx.configService);
|
|
2828
|
-
ctx.runtimeState.glassEasel.silent = Boolean(outputJson || outputMarkdown || outputPrReport || glassEaselCheck);
|
|
2829
|
-
const artifactSnapshot = launchDashboard ? createDashboardArtifactSnapshot() : void 0;
|
|
2830
|
-
const result = artifactSnapshot ? await analyzeSubpackages(ctx, { onArtifact: artifactSnapshot.capture }) : await analyzeSubpackages(ctx);
|
|
2831
|
-
await writeAnalyzeHistorySnapshot(result, ctx.configService);
|
|
2832
|
-
const writtenPath = await writeAnalyzeResult(result, outputOption, ctx.configService, outputPrReport ? "pr" : outputMarkdown ? "markdown" : "json", previousResult);
|
|
2833
|
-
if (outputPrReport) {
|
|
2834
|
-
if (!writtenPath) process.stdout.write(`${createAnalyzePrMarkdownReport(result, previousResult)}\n`);
|
|
2835
|
-
} else if (outputMarkdown) {
|
|
2836
|
-
if (!writtenPath) process.stdout.write(`${createAnalyzeMarkdownReport(result, previousResult)}\n`);
|
|
2837
|
-
} else if (outputJson) {
|
|
2838
|
-
if (!writtenPath) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
2839
|
-
}
|
|
2840
|
-
const budgetFailed = budgetCheck ? printBudgetCheckSummary(result) : false;
|
|
2841
|
-
const glassEaselFailed = glassEaselCheck && !outputJson ? printGlassEaselCheckSummary(result) : glassEaselCheck && result.glassEasel.summary.errors > 0;
|
|
2842
|
-
if (budgetFailed || glassEaselFailed) process.exitCode = 1;
|
|
2843
|
-
if (budgetCheck || glassEaselCheck) return;
|
|
2844
|
-
if (artifactSnapshot) {
|
|
2845
|
-
printAnalysisSummary(result);
|
|
2846
|
-
await startAnalyzeDashboard(result, {
|
|
2847
|
-
artifacts: artifactSnapshot.files,
|
|
2848
|
-
cwd: ctx.configService.cwd,
|
|
2849
|
-
packageManagerAgent: ctx.configService.packageManager.agent,
|
|
2850
|
-
pluginRoot: ctx.configService.absolutePluginRoot,
|
|
2851
|
-
srcRoot: ctx.configService.absoluteSrcRoot,
|
|
2852
|
-
previousResult
|
|
2660
|
+
logRuntimeTarget(targets, {
|
|
2661
|
+
silent: outputJson || outputMarkdown,
|
|
2662
|
+
resolvedConfigPlatform: ctx.configService.platform
|
|
2853
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();
|
|
2854
2738
|
}
|
|
2855
|
-
}
|
|
2856
|
-
logger_default.error(error);
|
|
2857
|
-
process.exitCode = 1;
|
|
2858
|
-
} finally {
|
|
2859
|
-
if (ctx) {
|
|
2860
|
-
for (const backend of [...targets.entries].reverse()) if (backend.descriptor.capabilities.analyze) await backend.driver?.close?.(ctx);
|
|
2861
|
-
}
|
|
2862
|
-
terminateStaleSassEmbeddedProcess();
|
|
2863
|
-
}
|
|
2739
|
+
});
|
|
2864
2740
|
});
|
|
2865
2741
|
}
|
|
2866
2742
|
//#endregion
|
|
@@ -2973,9 +2849,44 @@ async function readPackageVersion(packagePath) {
|
|
|
2973
2849
|
return manifest.version;
|
|
2974
2850
|
}
|
|
2975
2851
|
/** 在首次编译配置求值前准备一次元数据;预演不写文件,实际版本由 npm 同步。 */
|
|
2976
|
-
|
|
2852
|
+
function validateAutoUploadMetadata(options, action) {
|
|
2977
2853
|
const { bump, gitDesc } = options;
|
|
2978
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;
|
|
2979
2890
|
if (bump === void 0 && !gitDesc) return options;
|
|
2980
2891
|
if (bump !== void 0 && options.uv !== void 0) throw new Error("--bump 不能与 --uv 同时使用。");
|
|
2981
2892
|
if (gitDesc && options.desc !== void 0) throw new Error("--git-desc 不能与 --desc 同时使用。");
|
|
@@ -3036,6 +2947,15 @@ async function prepareAutoUploadMetadata(cwd, options, action) {
|
|
|
3036
2947
|
if (desc !== void 0) metadata.desc = desc;
|
|
3037
2948
|
return metadata;
|
|
3038
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
|
+
}
|
|
3039
2959
|
//#endregion
|
|
3040
2960
|
//#region src/cli/upload/env.ts
|
|
3041
2961
|
/** 凭据不经过 Vite 的 env 调试日志;文件优先级和进程环境覆盖规则保持一致。 */
|
|
@@ -3054,10 +2974,16 @@ async function loadUploadEnv(cwd, mode, root, envDir) {
|
|
|
3054
2974
|
if (error.code === "ENOENT") return "";
|
|
3055
2975
|
throw error;
|
|
3056
2976
|
});
|
|
3057
|
-
|
|
2977
|
+
for (const [key, value] of Object.entries(parse$2(content))) {
|
|
2978
|
+
delete parsed[key];
|
|
2979
|
+
parsed[key] = value;
|
|
2980
|
+
}
|
|
3058
2981
|
}
|
|
3059
2982
|
const processEnv = { ...parsed };
|
|
3060
|
-
for (const [key, value] of Object.entries(process.env)) if (value !== void 0)
|
|
2983
|
+
for (const [key, value] of Object.entries(process.env)) if (value !== void 0) {
|
|
2984
|
+
processEnv[key] = value;
|
|
2985
|
+
delete parsed[key];
|
|
2986
|
+
}
|
|
3061
2987
|
expand({
|
|
3062
2988
|
parsed,
|
|
3063
2989
|
processEnv
|
|
@@ -3068,6 +2994,21 @@ async function loadUploadEnv(cwd, mode, root, envDir) {
|
|
|
3068
2994
|
};
|
|
3069
2995
|
}
|
|
3070
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
|
|
3071
3012
|
//#region src/cli/upload/result.ts
|
|
3072
3013
|
/** 只接受可展示的预览结果,文件必须由本次任务生成,不能沿用旧二维码。 */
|
|
3073
3014
|
async function validatePreviewResult(value, context) {
|
|
@@ -3101,67 +3042,228 @@ async function validatePreviewResult(value, context) {
|
|
|
3101
3042
|
}
|
|
3102
3043
|
//#endregion
|
|
3103
3044
|
//#region src/cli/upload/process.ts
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
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
|
+
}
|
|
3119
3102
|
let completed = false;
|
|
3120
|
-
let
|
|
3121
|
-
let
|
|
3122
|
-
let
|
|
3123
|
-
const
|
|
3124
|
-
|
|
3125
|
-
|
|
3103
|
+
let started = false;
|
|
3104
|
+
let settled = false;
|
|
3105
|
+
let result = {};
|
|
3106
|
+
const captured = {
|
|
3107
|
+
stdout: "",
|
|
3108
|
+
stderr: ""
|
|
3126
3109
|
};
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
if (
|
|
3133
|
-
|
|
3134
|
-
|
|
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)}`;
|
|
3135
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 连接中断,未收到完成确认");
|
|
3136
3172
|
};
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
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) {
|
|
3141
3191
|
completed = true;
|
|
3142
|
-
result = "result" in message ? message.result :
|
|
3192
|
+
result = "result" in message && message.result && typeof message.result === "object" ? message.result : {};
|
|
3143
3193
|
}
|
|
3144
|
-
}
|
|
3145
|
-
|
|
3146
|
-
|
|
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);
|
|
3147
3215
|
process.off("SIGINT", cancel);
|
|
3148
3216
|
process.off("SIGTERM", cancel);
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
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"));
|
|
3152
3233
|
return;
|
|
3153
3234
|
}
|
|
3154
|
-
resolve(
|
|
3235
|
+
resolve({
|
|
3236
|
+
result,
|
|
3237
|
+
output
|
|
3238
|
+
});
|
|
3155
3239
|
});
|
|
3156
|
-
child.
|
|
3157
|
-
child.
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
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
|
+
}
|
|
3162
3264
|
});
|
|
3163
3265
|
if (output) logger_default.info(output);
|
|
3164
|
-
return action === "preview" ? validatePreviewResult(result, context) :
|
|
3266
|
+
return action === "preview" ? validatePreviewResult(result, context) : result;
|
|
3165
3267
|
}
|
|
3166
3268
|
//#endregion
|
|
3167
3269
|
//#region src/cli/upload/project.ts
|
|
@@ -3181,6 +3283,63 @@ async function validateUploadProject(options) {
|
|
|
3181
3283
|
await access(path$1.join(outputRoot, "app.json"));
|
|
3182
3284
|
}
|
|
3183
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
|
|
3184
3343
|
//#region src/cli/upload/builtProject.ts
|
|
3185
3344
|
/** 仅由显式上传或预览入口读取参数,不执行构建或调用平台工具。 */
|
|
3186
3345
|
async function createUploadTarget(config, options, action) {
|
|
@@ -3224,7 +3383,7 @@ async function createUploadTarget(config, options, action) {
|
|
|
3224
3383
|
return target;
|
|
3225
3384
|
}
|
|
3226
3385
|
/** 只消费本次构建的产物;产物校验通过前不会准备凭据或调用平台工具。 */
|
|
3227
|
-
async function executeUploadTarget(target, options, action) {
|
|
3386
|
+
async function executeUploadTarget(target, options, action, execution = {}) {
|
|
3228
3387
|
const { platform, context } = target;
|
|
3229
3388
|
if (!target.outDir) throw new Error("本次构建未写出 app.json,已阻止上传或预览旧产物。");
|
|
3230
3389
|
await validateUploadProject({
|
|
@@ -3238,7 +3397,11 @@ async function executeUploadTarget(target, options, action) {
|
|
|
3238
3397
|
return;
|
|
3239
3398
|
}
|
|
3240
3399
|
const upload = await prepareUpload(platform, context, action);
|
|
3241
|
-
|
|
3400
|
+
execution.onUpload?.();
|
|
3401
|
+
const result = await executeUpload(platform, context, upload.secrets, action, {
|
|
3402
|
+
...execution,
|
|
3403
|
+
onProgress: execution.onProgress ?? ((event) => printUploadProgress(platform, event))
|
|
3404
|
+
});
|
|
3242
3405
|
if (action === "preview") {
|
|
3243
3406
|
if (!result) throw new Error("预览工具未返回二维码或预览链接。");
|
|
3244
3407
|
for (const [label, value] of [
|
|
@@ -3248,6 +3411,7 @@ async function executeUploadTarget(target, options, action) {
|
|
|
3248
3411
|
]) if (value) logger_default.info(`[preview:${platform}] ${label}:${redactUploadSecrets(value, upload.secrets)}`);
|
|
3249
3412
|
logger_default.success(`[preview:${platform}] 预览已生成(未上传开发版本、未提审、未正式发布)。`);
|
|
3250
3413
|
} else logger_default.success(`[upload:${platform}] ${context.version} 上传完成(未提审、未正式发布)。`);
|
|
3414
|
+
return result;
|
|
3251
3415
|
}
|
|
3252
3416
|
//#endregion
|
|
3253
3417
|
//#region src/cli/upload/options.ts
|
|
@@ -3276,6 +3440,14 @@ function readUploadMetadata(cli) {
|
|
|
3276
3440
|
bump: typeof values.bump === "string" ? values.bump : void 0
|
|
3277
3441
|
};
|
|
3278
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
|
+
}
|
|
3279
3451
|
/** 普通构建不读取上传元数据;上传必须是显式的一次性构建操作。 */
|
|
3280
3452
|
function resolveBuildUploadOptions(cli, options) {
|
|
3281
3453
|
if (options.upload !== true) {
|
|
@@ -3324,26 +3496,36 @@ function scheduleCompletedProductionBuildExit(options, analyzeHandle) {
|
|
|
3324
3496
|
}, 0).unref?.();
|
|
3325
3497
|
}
|
|
3326
3498
|
function registerBuildCommand(cli) {
|
|
3327
|
-
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) => {
|
|
3328
3500
|
let analyzeHandle;
|
|
3501
|
+
let session;
|
|
3329
3502
|
let ctx;
|
|
3330
3503
|
let targets;
|
|
3504
|
+
let uploadMetadataRollback;
|
|
3331
3505
|
let buildCompleted = false;
|
|
3506
|
+
let buildFailed = false;
|
|
3332
3507
|
try {
|
|
3333
3508
|
options = { ...options };
|
|
3334
3509
|
filterDuplicateOptions(options);
|
|
3510
|
+
options.uiHost = parseDashboardUiHost(options.uiHost);
|
|
3335
3511
|
let uploadOptions = resolveBuildUploadOptions(cli, options);
|
|
3336
3512
|
setCommandNodeEnv("production");
|
|
3337
3513
|
const cwd = root ?? process.cwd();
|
|
3338
3514
|
const configFile = resolveConfigFile(options);
|
|
3339
3515
|
targets = resolveRuntimeTargets(options);
|
|
3516
|
+
if (uploadOptions) validateAutoUploadMetadata(uploadOptions, "upload");
|
|
3340
3517
|
if (uploadOptions && !getBackendForCapability(targets, "miniprogram", "build")) throw new Error("--upload 仅支持包含小程序目标的构建,不能用于纯 Web 构建。");
|
|
3341
|
-
if (uploadOptions) uploadOptions = await prepareAutoUploadMetadata(cwd, uploadOptions, "upload");
|
|
3342
3518
|
const inlineConfig = createInlineConfig(targets, {
|
|
3343
3519
|
scope: options.scope,
|
|
3344
3520
|
inlineConfig: createBuildInlineConfig(options)
|
|
3345
3521
|
});
|
|
3346
|
-
|
|
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({
|
|
3347
3529
|
cwd,
|
|
3348
3530
|
mode: options.mode ?? "production",
|
|
3349
3531
|
configFile,
|
|
@@ -3353,6 +3535,14 @@ function registerBuildCommand(cli) {
|
|
|
3353
3535
|
emitDefaultAutoImportOutputs: false,
|
|
3354
3536
|
preloadAppEntry: false
|
|
3355
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));
|
|
3356
3546
|
const { configService } = ctx;
|
|
3357
3547
|
const uploadTarget = uploadOptions ? await createUploadTarget(configService, uploadOptions, "upload") : void 0;
|
|
3358
3548
|
const miniBackend = getBackendForCapability(targets, "miniprogram", "build");
|
|
@@ -3362,7 +3552,7 @@ function registerBuildCommand(cli) {
|
|
|
3362
3552
|
for (const backend of targets.select("build")) {
|
|
3363
3553
|
if (backend.descriptor.id === "miniprogram") {
|
|
3364
3554
|
const miniBuildStartedAt = Date.now();
|
|
3365
|
-
const output = await backend.driver.build(ctx, options);
|
|
3555
|
+
const output = await session.run(() => backend.driver.build(ctx, options));
|
|
3366
3556
|
const miniBuildDurationMs = Date.now() - miniBuildStartedAt;
|
|
3367
3557
|
logger_default.success(`小程序构建完成,耗时:${colors.green(formatDuration(miniBuildDurationMs))}`);
|
|
3368
3558
|
if (!Array.isArray(output) && "output" in output) logBuildPackageSizeReport({
|
|
@@ -3379,6 +3569,7 @@ function registerBuildCommand(cli) {
|
|
|
3379
3569
|
const analyzeDurationMs = Date.now() - analyzeStartedAt;
|
|
3380
3570
|
analyzeHandle = await startAnalyzeDashboard(analyzeResult, {
|
|
3381
3571
|
watch: true,
|
|
3572
|
+
uiHost: options.uiHost,
|
|
3382
3573
|
artifacts: artifactSnapshot.files,
|
|
3383
3574
|
cwd: configService.cwd,
|
|
3384
3575
|
packageManagerAgent: configService.packageManager.agent,
|
|
@@ -3408,7 +3599,7 @@ function registerBuildCommand(cli) {
|
|
|
3408
3599
|
if (backend.descriptor.id !== "web" || !webConfig?.enabled) continue;
|
|
3409
3600
|
const webBuildStartedAt = Date.now();
|
|
3410
3601
|
try {
|
|
3411
|
-
await backend.driver.build(ctx, options);
|
|
3602
|
+
await session.run(() => backend.driver.build(ctx, options));
|
|
3412
3603
|
const webBuildDurationMs = Date.now() - webBuildStartedAt;
|
|
3413
3604
|
logger_default.success(`Web 构建完成,输出目录:${colors.green(configService.relativeCwd(webConfig.outDir))},耗时:${colors.green(formatDuration(webBuildDurationMs))}`);
|
|
3414
3605
|
emitDashboardEvents$1(analyzeHandle, [{
|
|
@@ -3451,9 +3642,12 @@ function registerBuildCommand(cli) {
|
|
|
3451
3642
|
}
|
|
3452
3643
|
if (analyzeHandle) await analyzeHandle.waitForExit();
|
|
3453
3644
|
buildCompleted = true;
|
|
3645
|
+
} catch (error) {
|
|
3646
|
+
buildFailed = true;
|
|
3647
|
+
throw error;
|
|
3454
3648
|
} finally {
|
|
3455
|
-
if (
|
|
3456
|
-
|
|
3649
|
+
if (buildFailed) await session?.close().catch((error) => logger_default.error(error));
|
|
3650
|
+
else await session?.close();
|
|
3457
3651
|
if (buildCompleted) scheduleCompletedProductionBuildExit(options, analyzeHandle);
|
|
3458
3652
|
}
|
|
3459
3653
|
});
|
|
@@ -3466,6 +3660,39 @@ function registerCloseCommand(cli) {
|
|
|
3466
3660
|
});
|
|
3467
3661
|
}
|
|
3468
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
|
|
3469
3696
|
//#region src/schematics.ts
|
|
3470
3697
|
function composePath(outDir, filename) {
|
|
3471
3698
|
return `${outDir}${outDir ? "/" : ""}${filename}`;
|
|
@@ -3550,60 +3777,6 @@ async function generate(options) {
|
|
|
3550
3777
|
}
|
|
3551
3778
|
}
|
|
3552
3779
|
//#endregion
|
|
3553
|
-
//#region src/cli/loadConfig.ts
|
|
3554
|
-
async function loadConfig(configFile) {
|
|
3555
|
-
const cwd = process.cwd();
|
|
3556
|
-
let resolvedConfigFile = configFile;
|
|
3557
|
-
if (resolvedConfigFile && !path.isAbsolute(resolvedConfigFile)) resolvedConfigFile = path.resolve(cwd, resolvedConfigFile);
|
|
3558
|
-
const configEnv = {
|
|
3559
|
-
command: "serve",
|
|
3560
|
-
mode: "development"
|
|
3561
|
-
};
|
|
3562
|
-
let loaded;
|
|
3563
|
-
try {
|
|
3564
|
-
loaded = await loadViteConfigFile(configEnv, resolvedConfigFile, cwd, void 0, void 0, "runner");
|
|
3565
|
-
} catch (error) {
|
|
3566
|
-
const cjsError = createCjsConfigLoadError({
|
|
3567
|
-
error,
|
|
3568
|
-
configPath: resolvedConfigFile,
|
|
3569
|
-
cwd
|
|
3570
|
-
});
|
|
3571
|
-
if (cjsError) throw cjsError;
|
|
3572
|
-
throw error;
|
|
3573
|
-
}
|
|
3574
|
-
const weappConfigFilePath = await resolveWeappConfigFile({
|
|
3575
|
-
root: cwd,
|
|
3576
|
-
specified: resolvedConfigFile
|
|
3577
|
-
});
|
|
3578
|
-
let weappLoaded;
|
|
3579
|
-
if (weappConfigFilePath) {
|
|
3580
|
-
const normalizedWeappPath = path.resolve(weappConfigFilePath);
|
|
3581
|
-
const normalizedLoadedPath = loaded?.path ? path.resolve(loaded.path) : void 0;
|
|
3582
|
-
if (normalizedLoadedPath && normalizedLoadedPath === normalizedWeappPath) weappLoaded = loaded;
|
|
3583
|
-
else try {
|
|
3584
|
-
weappLoaded = await loadViteConfigFile(configEnv, weappConfigFilePath, cwd, void 0, void 0, "runner");
|
|
3585
|
-
} catch (error) {
|
|
3586
|
-
const cjsError = createCjsConfigLoadError({
|
|
3587
|
-
error,
|
|
3588
|
-
configPath: weappConfigFilePath,
|
|
3589
|
-
cwd
|
|
3590
|
-
});
|
|
3591
|
-
if (cjsError) throw cjsError;
|
|
3592
|
-
throw error;
|
|
3593
|
-
}
|
|
3594
|
-
}
|
|
3595
|
-
if (!loaded && !weappLoaded) return;
|
|
3596
|
-
const config = weappLoaded?.config ? defu(weappLoaded.config, loaded?.config ?? {}) : loaded?.config ?? {};
|
|
3597
|
-
const dependencySet = /* @__PURE__ */ new Set();
|
|
3598
|
-
for (const dependency of loaded?.dependencies ?? []) dependencySet.add(dependency);
|
|
3599
|
-
for (const dependency of weappLoaded?.dependencies ?? []) dependencySet.add(dependency);
|
|
3600
|
-
return {
|
|
3601
|
-
config,
|
|
3602
|
-
path: weappLoaded?.path ?? loaded?.path ?? resolvedConfigFile,
|
|
3603
|
-
dependencies: Array.from(dependencySet)
|
|
3604
|
-
};
|
|
3605
|
-
}
|
|
3606
|
-
//#endregion
|
|
3607
3780
|
//#region src/cli/commands/generate.ts
|
|
3608
3781
|
function registerGenerateCommand(cli) {
|
|
3609
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) => {
|
|
@@ -3643,6 +3816,7 @@ const DEFAULT_FORWARD_CONSOLE_LEVELS = [
|
|
|
3643
3816
|
let activeForwardConsoleSession;
|
|
3644
3817
|
let activeForwardConsoleBridgeOptions;
|
|
3645
3818
|
let activeForwardConsoleStart;
|
|
3819
|
+
let activeForwardConsoleClose;
|
|
3646
3820
|
let forwardConsoleLifecycle = 0;
|
|
3647
3821
|
const FORWARD_CONSOLE_RETRY_DELAY_MS = 1e3;
|
|
3648
3822
|
const FORWARD_CONSOLE_RETRY_TIMES = 5;
|
|
@@ -3788,15 +3962,9 @@ async function pauseActiveForwardConsole() {
|
|
|
3788
3962
|
if (activeForwardConsoleStart) return await activeForwardConsoleStart;
|
|
3789
3963
|
if (activeForwardConsoleSession) return true;
|
|
3790
3964
|
const startTask = (async () => {
|
|
3965
|
+
let resumedSession;
|
|
3791
3966
|
try {
|
|
3792
|
-
|
|
3793
|
-
if (lifecycle !== forwardConsoleLifecycle) {
|
|
3794
|
-
await resumedSession.close();
|
|
3795
|
-
return false;
|
|
3796
|
-
}
|
|
3797
|
-
activeForwardConsoleSession = resumedSession;
|
|
3798
|
-
activeForwardConsoleBridgeOptions = bridgeOptions;
|
|
3799
|
-
return true;
|
|
3967
|
+
resumedSession = await startForwardConsoleBridge(bridgeOptions);
|
|
3800
3968
|
} catch (error) {
|
|
3801
3969
|
if (lifecycle === forwardConsoleLifecycle) {
|
|
3802
3970
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -3804,6 +3972,13 @@ async function pauseActiveForwardConsole() {
|
|
|
3804
3972
|
}
|
|
3805
3973
|
return false;
|
|
3806
3974
|
}
|
|
3975
|
+
if (lifecycle !== forwardConsoleLifecycle) {
|
|
3976
|
+
await resumedSession.close();
|
|
3977
|
+
return false;
|
|
3978
|
+
}
|
|
3979
|
+
activeForwardConsoleSession = resumedSession;
|
|
3980
|
+
activeForwardConsoleBridgeOptions = bridgeOptions;
|
|
3981
|
+
return true;
|
|
3807
3982
|
})();
|
|
3808
3983
|
activeForwardConsoleStart = startTask;
|
|
3809
3984
|
try {
|
|
@@ -3816,20 +3991,29 @@ async function pauseActiveForwardConsole() {
|
|
|
3816
3991
|
/**
|
|
3817
3992
|
* @description 关闭当前 DevTools 日志桥并释放共享 automator 会话。
|
|
3818
3993
|
*/
|
|
3819
|
-
|
|
3994
|
+
function closeActiveForwardConsole() {
|
|
3995
|
+
if (activeForwardConsoleClose) return activeForwardConsoleClose;
|
|
3820
3996
|
forwardConsoleLifecycle += 1;
|
|
3821
3997
|
const session = activeForwardConsoleSession;
|
|
3822
3998
|
const pendingStart = activeForwardConsoleStart;
|
|
3823
3999
|
activeForwardConsoleSession = void 0;
|
|
3824
4000
|
activeForwardConsoleBridgeOptions = void 0;
|
|
3825
|
-
|
|
3826
|
-
|
|
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;
|
|
3827
4011
|
}
|
|
3828
4012
|
/**
|
|
3829
4013
|
* @description 在 weapp 开发态按需启动控制台转发。
|
|
3830
4014
|
*/
|
|
3831
4015
|
async function maybeStartForwardConsole(options) {
|
|
3832
|
-
if (options.platform !== "weapp") return false;
|
|
4016
|
+
if (options.platform !== "weapp" || activeForwardConsoleClose) return false;
|
|
3833
4017
|
const multiPlatform = options.weappViteConfig?.multiPlatform;
|
|
3834
4018
|
const generatedProjectConfig = typeof multiPlatform === "object" && multiPlatform.projectConfigs !== void 0;
|
|
3835
4019
|
const projectPath = resolveIdeProjectRoot(options.mpDistRoot, options.cwd, generatedProjectConfig);
|
|
@@ -3871,7 +4055,7 @@ async function maybeStartForwardConsole(options) {
|
|
|
3871
4055
|
openedOnly: true,
|
|
3872
4056
|
preferOpenedSession: true
|
|
3873
4057
|
};
|
|
3874
|
-
return
|
|
4058
|
+
return activateSession(await startForwardConsoleBridge(recoveredOptions), recoveredOptions);
|
|
3875
4059
|
} catch (recoveryError) {
|
|
3876
4060
|
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
3877
4061
|
const message = recoveryError instanceof Error ? recoveryError.message : String(recoveryError);
|
|
@@ -3880,7 +4064,7 @@ async function maybeStartForwardConsole(options) {
|
|
|
3880
4064
|
}
|
|
3881
4065
|
};
|
|
3882
4066
|
try {
|
|
3883
|
-
return
|
|
4067
|
+
return activateSession(await startForwardConsoleBridge(bridgeOptions), bridgeOptions);
|
|
3884
4068
|
} catch (error) {
|
|
3885
4069
|
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
3886
4070
|
if (options.recoverAutomatorSession && isRecoverableAutomatorStartError(error)) return await recoverAutomatorSession();
|
|
@@ -3896,7 +4080,7 @@ async function maybeStartForwardConsole(options) {
|
|
|
3896
4080
|
preferOpenedSession: true
|
|
3897
4081
|
};
|
|
3898
4082
|
try {
|
|
3899
|
-
return
|
|
4083
|
+
return activateSession(await startForwardConsoleBridge(fallbackOptions), fallbackOptions);
|
|
3900
4084
|
} catch (fallbackError) {
|
|
3901
4085
|
if (lifecycle !== forwardConsoleLifecycle) return false;
|
|
3902
4086
|
const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
@@ -3917,19 +4101,6 @@ async function maybeStartForwardConsole(options) {
|
|
|
3917
4101
|
function isFiniteNumber(value) {
|
|
3918
4102
|
return typeof value === "number" && Number.isFinite(value);
|
|
3919
4103
|
}
|
|
3920
|
-
function parseLatestHmrProfileSample(content) {
|
|
3921
|
-
const lines = content.split(/\r?\n/);
|
|
3922
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
3923
|
-
const trimmed = lines[index]?.trim();
|
|
3924
|
-
if (!trimmed) continue;
|
|
3925
|
-
try {
|
|
3926
|
-
const parsed = JSON.parse(trimmed);
|
|
3927
|
-
if (isFiniteNumber(parsed.totalMs)) return parsed;
|
|
3928
|
-
} catch {
|
|
3929
|
-
continue;
|
|
3930
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
}
|
|
3933
4104
|
function formatPhaseHint(sample) {
|
|
3934
4105
|
const topPhase = [
|
|
3935
4106
|
{
|
|
@@ -4119,7 +4290,8 @@ async function readLatestHmrProfileSummary(options) {
|
|
|
4119
4290
|
if (!profilePath) return;
|
|
4120
4291
|
const content = await fs$1.readFile(profilePath, "utf8").catch(() => void 0);
|
|
4121
4292
|
if (!content) return;
|
|
4122
|
-
const
|
|
4293
|
+
const { samples, skippedLineCount } = readHmrProfileLines(content);
|
|
4294
|
+
const sample = samples.at(-1);
|
|
4123
4295
|
if (!sample || !isFiniteNumber(sample.totalMs)) return;
|
|
4124
4296
|
const relativeCwd = options.relativeCwd ?? ((value) => value);
|
|
4125
4297
|
const segments = [`[hmr] 最近一次热更新 ${sample.totalMs.toFixed(2)} ms`];
|
|
@@ -4127,7 +4299,8 @@ async function readLatestHmrProfileSummary(options) {
|
|
|
4127
4299
|
if (sample.file) segments.push(relativeCwd(sample.file));
|
|
4128
4300
|
const phaseHint = formatPhaseHint(sample);
|
|
4129
4301
|
if (phaseHint) segments.push(`主耗时 ${phaseHint}`);
|
|
4130
|
-
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} 条不兼容、未完成或无效记录`);
|
|
4131
4304
|
return {
|
|
4132
4305
|
file: sample.file,
|
|
4133
4306
|
profilePath,
|
|
@@ -4180,20 +4353,19 @@ async function runIdeDoctor(projectPath, options) {
|
|
|
4180
4353
|
status: "unknown"
|
|
4181
4354
|
};
|
|
4182
4355
|
if (resolvedCli.cliPath) try {
|
|
4183
|
-
await
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
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}),不能据此认定未登录。`
|
|
4190
4364
|
};
|
|
4191
|
-
} catch
|
|
4365
|
+
} catch {
|
|
4192
4366
|
loginCheck = {
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
status: "warning",
|
|
4196
|
-
value: false
|
|
4367
|
+
message: "登录查询执行失败,登录状态仍未确认。",
|
|
4368
|
+
status: "unknown"
|
|
4197
4369
|
};
|
|
4198
4370
|
}
|
|
4199
4371
|
const projectCheck = projectPath ? {
|
|
@@ -4222,11 +4394,20 @@ async function runIdeDoctor(projectPath, options) {
|
|
|
4222
4394
|
status: "ok",
|
|
4223
4395
|
value: true
|
|
4224
4396
|
};
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
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
|
+
}
|
|
4230
4411
|
} catch (error) {
|
|
4231
4412
|
automatorCheck = {
|
|
4232
4413
|
fix: "先执行 `wv open` 打开项目;若服务端口已开启,关闭多余 DevTools 窗口后重试。",
|
|
@@ -4375,7 +4556,7 @@ async function runIdeCommand(action, root, options) {
|
|
|
4375
4556
|
* @description 注册 IDE 相关子命令。
|
|
4376
4557
|
*/
|
|
4377
4558
|
function registerIdeCommand(cli) {
|
|
4378
|
-
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) => {
|
|
4379
4560
|
await runIdeCommand(action, root, options);
|
|
4380
4561
|
});
|
|
4381
4562
|
}
|
|
@@ -4391,405 +4572,6 @@ function registerInitCommand(cli) {
|
|
|
4391
4572
|
});
|
|
4392
4573
|
}
|
|
4393
4574
|
//#endregion
|
|
4394
|
-
//#region src/cli/mcpClient/shared.ts
|
|
4395
|
-
const CODEX_BLOCK_PREFIX = "# >>> weapp-vite mcp ";
|
|
4396
|
-
const CODEX_BLOCK_SUFFIX = " >>>";
|
|
4397
|
-
const CODEX_BLOCK_END_PREFIX = "# <<< weapp-vite mcp ";
|
|
4398
|
-
const CODEX_BLOCK_END_SUFFIX = " <<<";
|
|
4399
|
-
const DEFAULT_HTTP_TIMEOUT_MS = 1500;
|
|
4400
|
-
const WORKSPACE_FOLDER_TOKEN = "${workspaceFolder}";
|
|
4401
|
-
const REG_CodexUrl = /^\s*url\s*=\s*"([^"]+)"/m;
|
|
4402
|
-
const REG_CodexCommand = /^\s*command\s*=\s*"([^"]+)"/m;
|
|
4403
|
-
const REG_CodexArgs = /^\s*args\s*=\s*\[([^\]]*)\]/m;
|
|
4404
|
-
const REG_FirstTomlString = /"([^"]+)"/;
|
|
4405
|
-
function resolveTargetWorkspaceRoot(target) {
|
|
4406
|
-
if (target.client === "cursor") return path.dirname(path.dirname(target.configPath));
|
|
4407
|
-
if (target.client === "claude-code") return path.dirname(target.configPath);
|
|
4408
|
-
return process.cwd();
|
|
4409
|
-
}
|
|
4410
|
-
function escapeRegExp(value) {
|
|
4411
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4412
|
-
}
|
|
4413
|
-
function renderTomlString(value) {
|
|
4414
|
-
return JSON.stringify(value);
|
|
4415
|
-
}
|
|
4416
|
-
function sanitizeServerNameSegment(value) {
|
|
4417
|
-
return value.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
|
|
4418
|
-
}
|
|
4419
|
-
function resolveServerName(workspaceRoot) {
|
|
4420
|
-
return `weapp-vite-${sanitizeServerNameSegment(path.basename(workspaceRoot)) || "project"}`;
|
|
4421
|
-
}
|
|
4422
|
-
function resolveNodeBinCommand() {
|
|
4423
|
-
return process.execPath || "node";
|
|
4424
|
-
}
|
|
4425
|
-
function resolveProjectCliBin(workspaceRoot) {
|
|
4426
|
-
return path.join(workspaceRoot, "node_modules", "weapp-vite", "bin", "weapp-vite.js");
|
|
4427
|
-
}
|
|
4428
|
-
function renderCommandArgs(args) {
|
|
4429
|
-
return args.map(renderTomlString).join(", ");
|
|
4430
|
-
}
|
|
4431
|
-
function renderCodexBlock(serverName, entry) {
|
|
4432
|
-
const startMarker = `${CODEX_BLOCK_PREFIX}${serverName}${CODEX_BLOCK_SUFFIX}`;
|
|
4433
|
-
const endMarker = `${CODEX_BLOCK_END_PREFIX}${serverName}${CODEX_BLOCK_END_SUFFIX}`;
|
|
4434
|
-
if (entry.url) return `${startMarker}
|
|
4435
|
-
[mcp_servers.${serverName}]
|
|
4436
|
-
url = ${renderTomlString(entry.url)}
|
|
4437
|
-
${endMarker}
|
|
4438
|
-
`;
|
|
4439
|
-
return `${startMarker}
|
|
4440
|
-
[mcp_servers.${serverName}]
|
|
4441
|
-
command = ${renderTomlString(entry.command ?? resolveNodeBinCommand())}
|
|
4442
|
-
args = [${renderCommandArgs(entry.args ?? [])}]
|
|
4443
|
-
${endMarker}
|
|
4444
|
-
`;
|
|
4445
|
-
}
|
|
4446
|
-
function renderJsonPreview(serverName, entry) {
|
|
4447
|
-
return `${JSON.stringify({ mcpServers: { [serverName]: entry } }, null, 2)}
|
|
4448
|
-
`;
|
|
4449
|
-
}
|
|
4450
|
-
function resolveCommandEntry(client, workspaceRoot) {
|
|
4451
|
-
const binPath = resolveProjectCliBin(workspaceRoot);
|
|
4452
|
-
if (client === "cursor") return {
|
|
4453
|
-
command: "node",
|
|
4454
|
-
args: [
|
|
4455
|
-
`${WORKSPACE_FOLDER_TOKEN}/node_modules/weapp-vite/bin/weapp-vite.js`,
|
|
4456
|
-
"mcp",
|
|
4457
|
-
"--workspace-root",
|
|
4458
|
-
WORKSPACE_FOLDER_TOKEN
|
|
4459
|
-
]
|
|
4460
|
-
};
|
|
4461
|
-
return {
|
|
4462
|
-
command: resolveNodeBinCommand(),
|
|
4463
|
-
args: [
|
|
4464
|
-
binPath,
|
|
4465
|
-
"mcp",
|
|
4466
|
-
"--workspace-root",
|
|
4467
|
-
workspaceRoot
|
|
4468
|
-
]
|
|
4469
|
-
};
|
|
4470
|
-
}
|
|
4471
|
-
function resolveHttpEntry(client, url) {
|
|
4472
|
-
if (client === "claude-code") return {
|
|
4473
|
-
type: "http",
|
|
4474
|
-
url
|
|
4475
|
-
};
|
|
4476
|
-
return { url };
|
|
4477
|
-
}
|
|
4478
|
-
function resolveConfigEntry(client, transport, workspaceRoot, url) {
|
|
4479
|
-
return transport === "http" ? resolveHttpEntry(client, url ?? "") : resolveCommandEntry(client, workspaceRoot);
|
|
4480
|
-
}
|
|
4481
|
-
function resolveCodexBlockPattern(serverName) {
|
|
4482
|
-
const startMarker = `${CODEX_BLOCK_PREFIX}${serverName}${CODEX_BLOCK_SUFFIX}`;
|
|
4483
|
-
const endMarker = `${CODEX_BLOCK_END_PREFIX}${serverName}${CODEX_BLOCK_END_SUFFIX}`;
|
|
4484
|
-
return new RegExp(`${escapeRegExp(startMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}\\n?`, "g");
|
|
4485
|
-
}
|
|
4486
|
-
function upsertCodexManagedBlock(content, serverName, block) {
|
|
4487
|
-
const withoutExisting = content.trimEnd().replace(resolveCodexBlockPattern(serverName), "").trimEnd();
|
|
4488
|
-
if (!withoutExisting) return block;
|
|
4489
|
-
return `${withoutExisting}\n\n${block}`;
|
|
4490
|
-
}
|
|
4491
|
-
function parseJsonConfig(content, configPath) {
|
|
4492
|
-
if (!content.trim()) return {};
|
|
4493
|
-
try {
|
|
4494
|
-
return JSON.parse(content);
|
|
4495
|
-
} catch (error) {
|
|
4496
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
4497
|
-
throw new Error(`无法解析 MCP 配置文件 ${configPath}:${message}`);
|
|
4498
|
-
}
|
|
4499
|
-
}
|
|
4500
|
-
function resolveTarget(client, workspaceRoot) {
|
|
4501
|
-
const serverName = resolveServerName(workspaceRoot);
|
|
4502
|
-
if (client === "codex") return {
|
|
4503
|
-
client,
|
|
4504
|
-
configPath: path.join(os.homedir(), ".codex", "config.toml"),
|
|
4505
|
-
displayName: "Codex",
|
|
4506
|
-
serverName
|
|
4507
|
-
};
|
|
4508
|
-
if (client === "claude-code") return {
|
|
4509
|
-
client,
|
|
4510
|
-
configPath: path.join(workspaceRoot, ".mcp.json"),
|
|
4511
|
-
displayName: "Claude Code",
|
|
4512
|
-
serverName
|
|
4513
|
-
};
|
|
4514
|
-
return {
|
|
4515
|
-
client,
|
|
4516
|
-
configPath: path.join(workspaceRoot, ".cursor", "mcp.json"),
|
|
4517
|
-
displayName: "Cursor",
|
|
4518
|
-
serverName
|
|
4519
|
-
};
|
|
4520
|
-
}
|
|
4521
|
-
//#endregion
|
|
4522
|
-
//#region src/cli/mcpClient/inspect.ts
|
|
4523
|
-
async function probeHttpEndpoint(url) {
|
|
4524
|
-
const controller = new AbortController();
|
|
4525
|
-
const timer = setTimeout$1(() => {
|
|
4526
|
-
controller.abort();
|
|
4527
|
-
}, DEFAULT_HTTP_TIMEOUT_MS);
|
|
4528
|
-
try {
|
|
4529
|
-
return (await fetch(url, {
|
|
4530
|
-
method: "GET",
|
|
4531
|
-
signal: controller.signal
|
|
4532
|
-
})).status > 0;
|
|
4533
|
-
} catch {
|
|
4534
|
-
return false;
|
|
4535
|
-
} finally {
|
|
4536
|
-
clearTimeout$1(timer);
|
|
4537
|
-
controller.abort();
|
|
4538
|
-
}
|
|
4539
|
-
}
|
|
4540
|
-
async function inspectJsonConfig(target) {
|
|
4541
|
-
if (!await fs$1.pathExists(target.configPath)) return {
|
|
4542
|
-
configExists: false,
|
|
4543
|
-
configPath: target.configPath,
|
|
4544
|
-
displayName: target.displayName,
|
|
4545
|
-
issues: [`未找到配置文件:${target.configPath}`],
|
|
4546
|
-
serverName: target.serverName
|
|
4547
|
-
};
|
|
4548
|
-
const entry = parseJsonConfig(await fs$1.readFile(target.configPath, "utf8"), target.configPath).mcpServers?.[target.serverName];
|
|
4549
|
-
if (!entry) return {
|
|
4550
|
-
configExists: true,
|
|
4551
|
-
configPath: target.configPath,
|
|
4552
|
-
displayName: target.displayName,
|
|
4553
|
-
issues: [`配置文件中未找到服务器 ${target.serverName}`],
|
|
4554
|
-
serverName: target.serverName
|
|
4555
|
-
};
|
|
4556
|
-
const transport = entry.url ? "http" : "command";
|
|
4557
|
-
const issues = [];
|
|
4558
|
-
let httpReachable;
|
|
4559
|
-
if (transport === "command") {
|
|
4560
|
-
const normalizedBin = (entry.args?.[0])?.replaceAll(WORKSPACE_FOLDER_TOKEN, resolveTargetWorkspaceRoot(target));
|
|
4561
|
-
if (!normalizedBin || !await fs$1.pathExists(normalizedBin)) issues.push(`未找到本地 CLI 入口:${normalizedBin ?? "缺少 args[0]"}`);
|
|
4562
|
-
} else if (entry.url) {
|
|
4563
|
-
httpReachable = await probeHttpEndpoint(entry.url);
|
|
4564
|
-
if (!httpReachable) issues.push(`HTTP MCP 地址不可达:${entry.url}`);
|
|
4565
|
-
}
|
|
4566
|
-
return {
|
|
4567
|
-
configExists: true,
|
|
4568
|
-
configPath: target.configPath,
|
|
4569
|
-
displayName: target.displayName,
|
|
4570
|
-
httpReachable,
|
|
4571
|
-
issues,
|
|
4572
|
-
serverName: target.serverName,
|
|
4573
|
-
transport
|
|
4574
|
-
};
|
|
4575
|
-
}
|
|
4576
|
-
async function inspectCodexConfig(target) {
|
|
4577
|
-
if (!await fs$1.pathExists(target.configPath)) return {
|
|
4578
|
-
configExists: false,
|
|
4579
|
-
configPath: target.configPath,
|
|
4580
|
-
displayName: target.displayName,
|
|
4581
|
-
issues: [`未找到配置文件:${target.configPath}`],
|
|
4582
|
-
serverName: target.serverName
|
|
4583
|
-
};
|
|
4584
|
-
const blockMatch = (await fs$1.readFile(target.configPath, "utf8")).match(resolveCodexBlockPattern(target.serverName));
|
|
4585
|
-
if (!blockMatch?.[0]) return {
|
|
4586
|
-
configExists: true,
|
|
4587
|
-
configPath: target.configPath,
|
|
4588
|
-
displayName: target.displayName,
|
|
4589
|
-
issues: [`未找到 weapp-vite 生成的 Codex 配置区块:${target.serverName}`],
|
|
4590
|
-
serverName: target.serverName
|
|
4591
|
-
};
|
|
4592
|
-
const block = blockMatch[0];
|
|
4593
|
-
const issues = [];
|
|
4594
|
-
const urlMatch = block.match(REG_CodexUrl);
|
|
4595
|
-
const commandMatch = block.match(REG_CodexCommand);
|
|
4596
|
-
const argsMatch = block.match(REG_CodexArgs);
|
|
4597
|
-
if (urlMatch?.[1]) {
|
|
4598
|
-
const reachable = await probeHttpEndpoint(urlMatch[1]);
|
|
4599
|
-
if (!reachable) issues.push(`HTTP MCP 地址不可达:${urlMatch[1]}`);
|
|
4600
|
-
return {
|
|
4601
|
-
configExists: true,
|
|
4602
|
-
configPath: target.configPath,
|
|
4603
|
-
displayName: target.displayName,
|
|
4604
|
-
httpReachable: reachable,
|
|
4605
|
-
issues,
|
|
4606
|
-
serverName: target.serverName,
|
|
4607
|
-
transport: "http"
|
|
4608
|
-
};
|
|
4609
|
-
}
|
|
4610
|
-
const binPath = (argsMatch?.[1]?.match(REG_FirstTomlString))?.[1];
|
|
4611
|
-
if (!commandMatch?.[1]) issues.push("Codex 配置区块缺少 command 字段");
|
|
4612
|
-
if (!binPath || !await fs$1.pathExists(binPath)) issues.push(`未找到本地 CLI 入口:${binPath ?? "缺少 args[0]"}`);
|
|
4613
|
-
return {
|
|
4614
|
-
configExists: true,
|
|
4615
|
-
configPath: target.configPath,
|
|
4616
|
-
displayName: target.displayName,
|
|
4617
|
-
issues,
|
|
4618
|
-
serverName: target.serverName,
|
|
4619
|
-
transport: "command"
|
|
4620
|
-
};
|
|
4621
|
-
}
|
|
4622
|
-
async function inspectMcpClientConfig(options) {
|
|
4623
|
-
const target = resolveTarget(options.client, path.resolve(options.workspaceRoot));
|
|
4624
|
-
if (target.client === "codex") return await inspectCodexConfig(target);
|
|
4625
|
-
return await inspectJsonConfig(target);
|
|
4626
|
-
}
|
|
4627
|
-
//#endregion
|
|
4628
|
-
//#region src/cli/mcpClient/plan.ts
|
|
4629
|
-
function resolveSupportedMcpClient(input) {
|
|
4630
|
-
if (input === "codex" || input === "claude-code" || input === "cursor") return input;
|
|
4631
|
-
throw new Error(`不支持的 MCP 客户端:${input}。当前仅支持 codex、claude-code、cursor。`);
|
|
4632
|
-
}
|
|
4633
|
-
function buildMcpClientConfigPlan(options) {
|
|
4634
|
-
const workspaceRoot = path.resolve(options.workspaceRoot);
|
|
4635
|
-
const target = resolveTarget(options.client, workspaceRoot);
|
|
4636
|
-
const entry = resolveConfigEntry(options.client, options.transport, workspaceRoot, options.url);
|
|
4637
|
-
if (options.transport === "http" && !entry.url) throw new Error("HTTP 模式缺少 MCP 服务地址,请使用 --url 指定或在当前项目中检测到可用地址。");
|
|
4638
|
-
return {
|
|
4639
|
-
entry,
|
|
4640
|
-
preview: target.client === "codex" ? renderCodexBlock(target.serverName, entry) : renderJsonPreview(target.serverName, entry),
|
|
4641
|
-
target,
|
|
4642
|
-
transport: options.transport
|
|
4643
|
-
};
|
|
4644
|
-
}
|
|
4645
|
-
function formatMcpQuickStart(options) {
|
|
4646
|
-
const suffix = options.transport === "http" && options.httpUrl ? ` --transport http --url ${options.httpUrl}` : "";
|
|
4647
|
-
return [
|
|
4648
|
-
"在 AI 工具中接入 weapp-vite MCP:",
|
|
4649
|
-
` - Codex: wv mcp init codex${suffix}`,
|
|
4650
|
-
` - Claude Code: wv mcp init claude-code${suffix}`,
|
|
4651
|
-
` - Cursor: wv mcp init cursor${suffix}`
|
|
4652
|
-
];
|
|
4653
|
-
}
|
|
4654
|
-
//#endregion
|
|
4655
|
-
//#region src/cli/mcpClient/index.ts
|
|
4656
|
-
async function writeMcpClientConfig(plan) {
|
|
4657
|
-
await fs$1.ensureDir(path.dirname(plan.target.configPath));
|
|
4658
|
-
const existing = await fs$1.readFile(plan.target.configPath, "utf8").catch(() => "");
|
|
4659
|
-
if (plan.target.client === "codex") {
|
|
4660
|
-
const nextContent = upsertCodexManagedBlock(existing, plan.target.serverName, plan.preview);
|
|
4661
|
-
await fs$1.writeFile(plan.target.configPath, nextContent, "utf8");
|
|
4662
|
-
return;
|
|
4663
|
-
}
|
|
4664
|
-
const parsed = parseJsonConfig(existing, plan.target.configPath);
|
|
4665
|
-
const nextConfig = {
|
|
4666
|
-
...parsed,
|
|
4667
|
-
mcpServers: {
|
|
4668
|
-
...parsed.mcpServers ?? {},
|
|
4669
|
-
[plan.target.serverName]: plan.entry
|
|
4670
|
-
}
|
|
4671
|
-
};
|
|
4672
|
-
await fs$1.writeFile(plan.target.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
4673
|
-
}
|
|
4674
|
-
//#endregion
|
|
4675
|
-
//#region src/cli/commands/mcp.ts
|
|
4676
|
-
function resolvePort(port) {
|
|
4677
|
-
if (typeof port === "number" && Number.isInteger(port)) return port;
|
|
4678
|
-
if (typeof port === "string" && port.trim()) {
|
|
4679
|
-
const parsed = Number.parseInt(port, 10);
|
|
4680
|
-
if (Number.isInteger(parsed)) return parsed;
|
|
4681
|
-
}
|
|
4682
|
-
}
|
|
4683
|
-
async function resolveHttpUrl(options) {
|
|
4684
|
-
if (options.url?.trim()) return options.url.trim();
|
|
4685
|
-
const workspaceRoot = options.workspaceRoot ? path.resolve(options.workspaceRoot) : process.cwd();
|
|
4686
|
-
const originalCwd = process.cwd();
|
|
4687
|
-
try {
|
|
4688
|
-
if (workspaceRoot !== originalCwd) process.chdir(workspaceRoot);
|
|
4689
|
-
const loaded = await loadConfig(resolveConfigFile(options));
|
|
4690
|
-
const resolved = resolveWeappMcpConfig(loaded?.config?.weapp?.mcp);
|
|
4691
|
-
return `http://${resolved.host}:${resolved.port}${resolved.endpoint}`;
|
|
4692
|
-
} catch {
|
|
4693
|
-
const resolved = resolveWeappMcpConfig(void 0);
|
|
4694
|
-
return `http://${resolved.host}:${resolved.port}${resolved.endpoint}`;
|
|
4695
|
-
} finally {
|
|
4696
|
-
if (process.cwd() !== originalCwd) process.chdir(originalCwd);
|
|
4697
|
-
}
|
|
4698
|
-
}
|
|
4699
|
-
async function confirmWrite() {
|
|
4700
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
4701
|
-
return await runWithSuspendedSharedInput(async () => {
|
|
4702
|
-
const rl = createInterface({
|
|
4703
|
-
input: process.stdin,
|
|
4704
|
-
output: process.stdout
|
|
4705
|
-
});
|
|
4706
|
-
try {
|
|
4707
|
-
const normalized = (await rl.question("是否写入配置文件?(Y/n) ")).trim().toLowerCase();
|
|
4708
|
-
return normalized === "" || normalized === "y" || normalized === "yes";
|
|
4709
|
-
} finally {
|
|
4710
|
-
rl.close();
|
|
4711
|
-
}
|
|
4712
|
-
});
|
|
4713
|
-
}
|
|
4714
|
-
function resolveClientTransport(transport) {
|
|
4715
|
-
return transport === "http" ? "http" : "command";
|
|
4716
|
-
}
|
|
4717
|
-
async function handlePrint(clientName, options, write = false) {
|
|
4718
|
-
const client = resolveSupportedMcpClient(clientName);
|
|
4719
|
-
const workspaceRoot = options.workspaceRoot ?? process.cwd();
|
|
4720
|
-
const transport = resolveClientTransport(options.transport);
|
|
4721
|
-
const plan = buildMcpClientConfigPlan({
|
|
4722
|
-
client,
|
|
4723
|
-
transport,
|
|
4724
|
-
url: transport === "http" ? await resolveHttpUrl(options) : void 0,
|
|
4725
|
-
workspaceRoot
|
|
4726
|
-
});
|
|
4727
|
-
logger_default.info(`${plan.target.displayName} 配置文件:${plan.target.configPath}`);
|
|
4728
|
-
logger_default.info(`服务器名称:${plan.target.serverName}`);
|
|
4729
|
-
process.stdout.write(`${plan.preview}\n`);
|
|
4730
|
-
if (!write) return;
|
|
4731
|
-
if (options.yes || await confirmWrite()) {
|
|
4732
|
-
await writeMcpClientConfig(plan);
|
|
4733
|
-
logger_default.success(`已写入 ${plan.target.displayName} MCP 配置。`);
|
|
4734
|
-
logger_default.info(`请重启 ${plan.target.displayName},然后执行:wv mcp doctor ${client}`);
|
|
4735
|
-
return;
|
|
4736
|
-
}
|
|
4737
|
-
logger_default.info("已取消写入。");
|
|
4738
|
-
}
|
|
4739
|
-
async function handleDoctor(clientName, options) {
|
|
4740
|
-
const result = await inspectMcpClientConfig({
|
|
4741
|
-
client: resolveSupportedMcpClient(clientName),
|
|
4742
|
-
workspaceRoot: options.workspaceRoot ?? process.cwd()
|
|
4743
|
-
});
|
|
4744
|
-
logger_default.info(`${result.displayName} 配置文件:${result.configPath}`);
|
|
4745
|
-
logger_default.info(`服务器名称:${result.serverName}`);
|
|
4746
|
-
if (!result.configExists || result.issues.length > 0) {
|
|
4747
|
-
for (const issue of result.issues) logger_default.warn(issue);
|
|
4748
|
-
throw new Error("MCP 客户端配置检查未通过。");
|
|
4749
|
-
}
|
|
4750
|
-
if (result.transport) logger_default.info(`传输模式:${result.transport}`);
|
|
4751
|
-
if (result.httpReachable !== void 0) logger_default.info(`HTTP 服务可达:${result.httpReachable ? "是" : "否"}`);
|
|
4752
|
-
logger_default.success("MCP 客户端配置检查通过。");
|
|
4753
|
-
}
|
|
4754
|
-
async function handleServer(options) {
|
|
4755
|
-
const resolvedTransport = options.transport === "http" ? "streamable-http" : options.transport === "command" ? void 0 : options.transport;
|
|
4756
|
-
await startWeappViteMcpServer({
|
|
4757
|
-
endpoint: options.endpoint,
|
|
4758
|
-
host: options.host,
|
|
4759
|
-
port: resolvePort(options.port),
|
|
4760
|
-
restEndpoint: options.rest === false ? false : options.restEndpoint,
|
|
4761
|
-
transport: resolvedTransport,
|
|
4762
|
-
unref: options.unref,
|
|
4763
|
-
workspaceRoot: options.workspaceRoot
|
|
4764
|
-
});
|
|
4765
|
-
for (const line of formatMcpQuickStart({
|
|
4766
|
-
httpUrl: resolvedTransport === "streamable-http" ? `http://${options.host ?? "127.0.0.1"}:${resolvePort(options.port) ?? 3088}${options.endpoint ?? "/mcp"}` : void 0,
|
|
4767
|
-
transport: resolvedTransport === "streamable-http" ? "http" : "command"
|
|
4768
|
-
})) logger_default.info(line);
|
|
4769
|
-
}
|
|
4770
|
-
function registerMcpCommand(cli) {
|
|
4771
|
-
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) => {
|
|
4772
|
-
const [subcommand, client] = args;
|
|
4773
|
-
if (subcommand === "init") {
|
|
4774
|
-
if (!client) throw new Error("缺少客户端名称,请使用:wv mcp init <codex|claude-code|cursor>");
|
|
4775
|
-
await handlePrint(client, options, true);
|
|
4776
|
-
return;
|
|
4777
|
-
}
|
|
4778
|
-
if (subcommand === "print") {
|
|
4779
|
-
if (!client) throw new Error("缺少客户端名称,请使用:wv mcp print <codex|claude-code|cursor>");
|
|
4780
|
-
await handlePrint(client, options, false);
|
|
4781
|
-
return;
|
|
4782
|
-
}
|
|
4783
|
-
if (subcommand === "doctor") {
|
|
4784
|
-
if (!client) throw new Error("缺少客户端名称,请使用:wv mcp doctor <codex|claude-code|cursor>");
|
|
4785
|
-
await handleDoctor(client, options);
|
|
4786
|
-
return;
|
|
4787
|
-
}
|
|
4788
|
-
if (subcommand) throw new Error(`未知的 mcp 子命令:${subcommand}`);
|
|
4789
|
-
await handleServer(options);
|
|
4790
|
-
});
|
|
4791
|
-
}
|
|
4792
|
-
//#endregion
|
|
4793
4575
|
//#region src/cli/commands/npm.ts
|
|
4794
4576
|
function registerNpmCommand(cli) {
|
|
4795
4577
|
cli.command("npm").alias("build:npm").alias("build-npm").action(async () => {
|
|
@@ -4911,7 +4693,7 @@ function applyMcpCliOptions(config, options) {
|
|
|
4911
4693
|
//#endregion
|
|
4912
4694
|
//#region src/cli/commands/open.ts
|
|
4913
4695
|
function registerOpenCommand(cli) {
|
|
4914
|
-
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) => {
|
|
4915
4697
|
filterDuplicateOptions(options);
|
|
4916
4698
|
const configFile = resolveConfigFile(options);
|
|
4917
4699
|
const targets = resolveRuntimeTargets(options);
|
|
@@ -5214,9 +4996,6 @@ function resolveRunnableHotkeyDefinition(input) {
|
|
|
5214
4996
|
return DEV_HOTKEY_DEFINITIONS.find((action) => action.run && action.key === input);
|
|
5215
4997
|
}
|
|
5216
4998
|
//#endregion
|
|
5217
|
-
//#region package.json
|
|
5218
|
-
var version = "7.4.0";
|
|
5219
|
-
//#endregion
|
|
5220
4999
|
//#region src/cli/devHotkeys/format.ts
|
|
5221
5000
|
const FULLWIDTH_ASCII_START = 65281;
|
|
5222
5001
|
const FULLWIDTH_ASCII_END = 65374;
|
|
@@ -5376,6 +5155,8 @@ function startDevHotkeys(options) {
|
|
|
5376
5155
|
if (options.platform !== "weapp" || !process.stdin.isTTY) return;
|
|
5377
5156
|
let closed = false;
|
|
5378
5157
|
let running = false;
|
|
5158
|
+
let closing;
|
|
5159
|
+
let actionPromise;
|
|
5379
5160
|
let mcpHandle;
|
|
5380
5161
|
let onData;
|
|
5381
5162
|
let onKeypress;
|
|
@@ -5410,17 +5191,25 @@ function startDevHotkeys(options) {
|
|
|
5410
5191
|
}
|
|
5411
5192
|
});
|
|
5412
5193
|
const close = () => {
|
|
5413
|
-
if (
|
|
5194
|
+
if (closing) return closing;
|
|
5414
5195
|
closed = true;
|
|
5415
5196
|
if (onSigcont) process.off("SIGCONT", onSigcont);
|
|
5416
|
-
|
|
5197
|
+
const inputErrors = [];
|
|
5198
|
+
try {
|
|
5199
|
+
inputSession?.close();
|
|
5200
|
+
} catch (error) {
|
|
5201
|
+
inputErrors.push(error);
|
|
5202
|
+
}
|
|
5417
5203
|
inputSession = void 0;
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
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
|
+
})();
|
|
5424
5213
|
};
|
|
5425
5214
|
const printPanel = (message, force = false) => {
|
|
5426
5215
|
if (!force && message === lastRenderedPanel) return;
|
|
@@ -5458,8 +5247,9 @@ function startDevHotkeys(options) {
|
|
|
5458
5247
|
const resolvePendingLabel = (input) => {
|
|
5459
5248
|
if (input === "m") return mcpHandle?.close ? "正在关闭 MCP 服务" : "正在启动 MCP 服务";
|
|
5460
5249
|
};
|
|
5461
|
-
const runAction = (label, pendingLabel, action) => {
|
|
5250
|
+
const runAction = (label, pendingLabel, action, quiet = false) => {
|
|
5462
5251
|
if (running) {
|
|
5252
|
+
if (quiet) return;
|
|
5463
5253
|
const current = currentAction ?? "已有命令";
|
|
5464
5254
|
const elapsed = actionStartedAt === void 0 ? "" : `(已运行 ${Math.max(0, Date.now() - actionStartedAt)} ms)`;
|
|
5465
5255
|
logger_default.warn(`[dev action] 当前正在${current.replace(REG_PENDING_PREFIX, "")}${elapsed},已忽略重复请求。`);
|
|
@@ -5468,24 +5258,8 @@ function startDevHotkeys(options) {
|
|
|
5468
5258
|
running = true;
|
|
5469
5259
|
actionStartedAt = Date.now();
|
|
5470
5260
|
currentAction = pendingLabel;
|
|
5471
|
-
printHint();
|
|
5472
|
-
action().then((summary) => {
|
|
5473
|
-
if (summary) lastAction = summary;
|
|
5474
|
-
}).catch((error) => {
|
|
5475
|
-
logger_default.error(`[dev action] ${label}失败:${error instanceof Error ? error.message : String(error)}`);
|
|
5476
|
-
}).finally(() => {
|
|
5477
|
-
running = false;
|
|
5478
|
-
actionStartedAt = void 0;
|
|
5479
|
-
currentAction = void 0;
|
|
5480
|
-
if (!closed) printHint();
|
|
5481
|
-
});
|
|
5482
|
-
};
|
|
5483
|
-
const runBackgroundAction = (label, pendingLabel, action) => {
|
|
5484
|
-
if (running) return;
|
|
5485
|
-
running = true;
|
|
5486
|
-
actionStartedAt = Date.now();
|
|
5487
|
-
currentAction = pendingLabel;
|
|
5488
|
-
action().then((summary) => {
|
|
5261
|
+
if (!quiet) printHint();
|
|
5262
|
+
actionPromise = action().then((summary) => {
|
|
5489
5263
|
if (summary) lastAction = summary;
|
|
5490
5264
|
}).catch((error) => {
|
|
5491
5265
|
logger_default.error(`[dev action] ${label}失败:${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -5493,13 +5267,14 @@ function startDevHotkeys(options) {
|
|
|
5493
5267
|
running = false;
|
|
5494
5268
|
actionStartedAt = void 0;
|
|
5495
5269
|
currentAction = void 0;
|
|
5270
|
+
if (!closed && !quiet) printHint();
|
|
5496
5271
|
});
|
|
5497
5272
|
};
|
|
5498
5273
|
const handleInput = (input) => {
|
|
5499
5274
|
if (closed) return;
|
|
5500
5275
|
const normalizedInput = normalizeInputChar(input);
|
|
5501
5276
|
if (normalizedInput === "") {
|
|
5502
|
-
close();
|
|
5277
|
+
close().catch((error) => logger_default.error(error));
|
|
5503
5278
|
forwardSigint();
|
|
5504
5279
|
return;
|
|
5505
5280
|
}
|
|
@@ -5509,7 +5284,7 @@ function startDevHotkeys(options) {
|
|
|
5509
5284
|
}
|
|
5510
5285
|
const normalized = normalizedInput.toLowerCase();
|
|
5511
5286
|
if (normalized === "q") {
|
|
5512
|
-
close();
|
|
5287
|
+
close().catch((error) => logger_default.error(error));
|
|
5513
5288
|
forwardSigint();
|
|
5514
5289
|
return;
|
|
5515
5290
|
}
|
|
@@ -5559,9 +5334,9 @@ function startDevHotkeys(options) {
|
|
|
5559
5334
|
};
|
|
5560
5335
|
process.on("SIGCONT", onSigcont);
|
|
5561
5336
|
if (!options.silentStartupHint) printHint();
|
|
5562
|
-
if (resolvedMcp.enabled && resolvedMcp.autoStart)
|
|
5337
|
+
if (resolvedMcp.enabled && resolvedMcp.autoStart) runAction("MCP 自动启动", "正在启动 MCP 服务", async () => {
|
|
5563
5338
|
await toggleMcp({ silent: true });
|
|
5564
|
-
});
|
|
5339
|
+
}, true);
|
|
5565
5340
|
return {
|
|
5566
5341
|
close,
|
|
5567
5342
|
restore,
|
|
@@ -5569,6 +5344,127 @@ function startDevHotkeys(options) {
|
|
|
5569
5344
|
};
|
|
5570
5345
|
}
|
|
5571
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
|
|
5572
5468
|
//#region src/cli/commands/serve/analyze.ts
|
|
5573
5469
|
const REG_DIST_PAGE_ENTRY = /pages\/.+\/index\.js$/;
|
|
5574
5470
|
const REG_DIST_POSIX_SEP = /\\/g;
|
|
@@ -5683,7 +5579,7 @@ async function analyzeUiFallback(ctx, options) {
|
|
|
5683
5579
|
};
|
|
5684
5580
|
}
|
|
5685
5581
|
function createAnalyzeController(options) {
|
|
5686
|
-
const { configFile, ctx, options: cliOptions, targets } = options;
|
|
5582
|
+
const { configFile, ctx, options: cliOptions, targets, shutdown } = options;
|
|
5687
5583
|
const { configService } = ctx;
|
|
5688
5584
|
let analyzeRunId = 0;
|
|
5689
5585
|
let analyzeHandle;
|
|
@@ -5734,8 +5630,8 @@ function createAnalyzeController(options) {
|
|
|
5734
5630
|
}
|
|
5735
5631
|
return runFallback("完整分析结果为空,已回退到 dist 文件扫描。");
|
|
5736
5632
|
};
|
|
5737
|
-
const
|
|
5738
|
-
if (!analyzeHandle) return;
|
|
5633
|
+
const runAnalyzeUpdate = async (reason) => {
|
|
5634
|
+
if (!analyzeHandle || shutdown?.stopping) return;
|
|
5739
5635
|
emitDashboardEvents(analyzeHandle, [{
|
|
5740
5636
|
kind: reason === "watch" ? "hmr" : "build",
|
|
5741
5637
|
level: "info",
|
|
@@ -5744,6 +5640,7 @@ function createAnalyzeController(options) {
|
|
|
5744
5640
|
tags: reason === "watch" ? ["watch", "analyze"] : ["initial", "analyze"]
|
|
5745
5641
|
}]);
|
|
5746
5642
|
const next = await runAnalyze();
|
|
5643
|
+
if (shutdown?.stopping) return;
|
|
5747
5644
|
if (next.mode === "fallback") emitDashboardEvents(analyzeHandle, [{
|
|
5748
5645
|
kind: "diagnostic",
|
|
5749
5646
|
level: "warning",
|
|
@@ -5762,6 +5659,10 @@ function createAnalyzeController(options) {
|
|
|
5762
5659
|
tags: next.mode === "fallback" ? ["analyze", "fallback"] : ["analyze", reason === "watch" ? "refresh" : "initial"]
|
|
5763
5660
|
}]);
|
|
5764
5661
|
};
|
|
5662
|
+
const triggerAnalyzeUpdate = (reason = "watch") => {
|
|
5663
|
+
if (shutdown?.stopping) return Promise.resolve();
|
|
5664
|
+
return shutdown ? shutdown.run("startup", () => runAnalyzeUpdate(reason)) : runAnalyzeUpdate(reason);
|
|
5665
|
+
};
|
|
5765
5666
|
const bindWatcher = (buildResult) => {
|
|
5766
5667
|
let initialUpdatePromise;
|
|
5767
5668
|
let watchUpdatePending = false;
|
|
@@ -5791,6 +5692,7 @@ function createAnalyzeController(options) {
|
|
|
5791
5692
|
return watchUpdatePromise;
|
|
5792
5693
|
};
|
|
5793
5694
|
if (analyzeHandle && buildResult && typeof buildResult.on === "function") buildResult.on("event", (event) => {
|
|
5695
|
+
if (shutdown?.stopping) return;
|
|
5794
5696
|
if (event.code === "ERROR") {
|
|
5795
5697
|
emitDashboardEvents(analyzeHandle, [{
|
|
5796
5698
|
kind: "diagnostic",
|
|
@@ -5814,8 +5716,10 @@ function createAnalyzeController(options) {
|
|
|
5814
5716
|
getHandle: () => analyzeHandle,
|
|
5815
5717
|
async startDashboard(startDashboard) {
|
|
5816
5718
|
const initialAnalyze = await runAnalyze();
|
|
5719
|
+
if (shutdown?.stopping) return;
|
|
5817
5720
|
analyzeHandle = await startDashboard(initialAnalyze.result, {
|
|
5818
5721
|
watch: true,
|
|
5722
|
+
uiHost: cliOptions.uiHost,
|
|
5819
5723
|
artifacts: initialAnalyze.artifacts,
|
|
5820
5724
|
cwd: configService.cwd,
|
|
5821
5725
|
packageManagerAgent: configService.packageManager.agent,
|
|
@@ -5867,279 +5771,185 @@ function createAnalyzeController(options) {
|
|
|
5867
5771
|
};
|
|
5868
5772
|
}
|
|
5869
5773
|
//#endregion
|
|
5870
|
-
//#region src/cli/commands/serve/
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
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
|
|
5877
5907
|
});
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
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
|
+
}
|
|
5881
5928
|
}
|
|
5882
|
-
}
|
|
5883
|
-
function resolveServeIdeOpenStrategy(options = {}, fallback = "cli") {
|
|
5884
|
-
return options.openStrategy ?? (options.useAutomatorOpen ? "automator" : fallback);
|
|
5885
|
-
}
|
|
5886
|
-
function resolveWebHost(host) {
|
|
5887
|
-
if (host === void 0) return;
|
|
5888
|
-
if (typeof host === "boolean") return host;
|
|
5889
|
-
if (typeof host === "string") return host;
|
|
5890
|
-
return String(host);
|
|
5891
|
-
}
|
|
5892
|
-
/**
|
|
5893
|
-
* @description 为 serve 模式构造统一的小程序开发动作,供热键与命令复用。
|
|
5894
|
-
*/
|
|
5895
|
-
function createServeMiniProgramDevActions(options) {
|
|
5896
|
-
const projectPath = options.projectPath ?? options.fallbackProjectPath;
|
|
5897
5929
|
return {
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
if (!openOptions.forceOpen && await options.tryReuseForwardConsole?.()) return "已通过控制台转发复用当前开发者工具会话";
|
|
5901
|
-
await options.openIde(projectPath, openOptions);
|
|
5902
|
-
startBackgroundForwardConsole(options.startForwardConsole, openOptions);
|
|
5903
|
-
return openOptions.forceReopen ? "已重新打开微信开发者工具项目" : "已打开或复用微信开发者工具项目";
|
|
5904
|
-
},
|
|
5905
|
-
rebuild: async () => {
|
|
5906
|
-
await options.build();
|
|
5907
|
-
return "已手动重新构建当前小程序产物";
|
|
5908
|
-
}
|
|
5930
|
+
watch: targets.has("dev"),
|
|
5931
|
+
waitForExit: analyzeHandle ? () => analyzeController.waitForExit() : void 0
|
|
5909
5932
|
};
|
|
5910
5933
|
}
|
|
5911
|
-
function waitForServeShutdownSignal() {
|
|
5912
|
-
return new Promise((resolve) => {
|
|
5913
|
-
const onSignal = () => {
|
|
5914
|
-
process.off("SIGINT", onSignal);
|
|
5915
|
-
process.off("SIGTERM", onSignal);
|
|
5916
|
-
resolve();
|
|
5917
|
-
};
|
|
5918
|
-
process.on("SIGINT", onSignal);
|
|
5919
|
-
process.on("SIGTERM", onSignal);
|
|
5920
|
-
});
|
|
5921
|
-
}
|
|
5922
5934
|
//#endregion
|
|
5923
5935
|
//#region src/cli/commands/serve/index.ts
|
|
5924
|
-
function writePostOpenSeparator() {
|
|
5925
|
-
process.stdout?.write?.("\n");
|
|
5926
|
-
}
|
|
5927
5936
|
function registerServeCommand(cli) {
|
|
5928
|
-
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
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
const cwd = root ?? process.cwd();
|
|
5932
|
-
const configFile = resolveConfigFile(options);
|
|
5933
|
-
const targets = resolveRuntimeTargets(options);
|
|
5934
|
-
const webBackend = getBackendForCapability(targets, "web", "dev");
|
|
5935
|
-
const host = webBackend ? resolveWebHost(options.host) : void 0;
|
|
5936
|
-
const inlineConfig = createInlineConfig(targets, {
|
|
5937
|
-
scope: options.scope,
|
|
5938
|
-
host
|
|
5939
|
-
});
|
|
5940
|
-
const ctx = await createCompilerContext({
|
|
5941
|
-
cwd,
|
|
5942
|
-
mode: options.mode ?? "development",
|
|
5943
|
-
isDev: true,
|
|
5944
|
-
configFile,
|
|
5945
|
-
inlineConfig,
|
|
5946
|
-
cliPlatform: targets.rawPlatform,
|
|
5947
|
-
preloadAppEntry: false,
|
|
5948
|
-
syncAutoImportSupportFiles: false,
|
|
5949
|
-
projectConfigPath: options.projectConfig
|
|
5950
|
-
});
|
|
5951
|
-
const { configService } = ctx;
|
|
5952
|
-
const miniBackend = getBackendForCapability(targets, "miniprogram", "dev");
|
|
5953
|
-
const aiEnvironment = await detectAiDevelopmentEnvironment();
|
|
5954
|
-
const mcpConfig = applyMcpCliOptions(configService.weappViteConfig?.mcp, options);
|
|
5955
|
-
logRuntimeTarget(targets, { resolvedConfigPlatform: configService.platform });
|
|
5956
|
-
const enableAnalyze = Boolean(isUiEnabled(options) && miniBackend);
|
|
5957
|
-
let analyzeHandle;
|
|
5958
|
-
const generatedProjectConfig = configService.multiPlatform.projectConfigs !== void 0;
|
|
5959
|
-
const miniProgramDevActions = createServeMiniProgramDevActions({
|
|
5960
|
-
build: async () => {
|
|
5961
|
-
await miniBackend?.driver.dev(ctx, options);
|
|
5962
|
-
},
|
|
5963
|
-
fallbackProjectPath: configService.cwd,
|
|
5964
|
-
openIde: async (projectPath, openOptions) => {
|
|
5965
|
-
const forceReopen = openOptions?.forceReopen === true;
|
|
5966
|
-
const openStrategy = resolveServeIdeOpenStrategy(openOptions, options.ideOpenStrategy);
|
|
5967
|
-
const useAutomatorOpen = openStrategy === "automator";
|
|
5968
|
-
await openIde(configService.platform, projectPath, {
|
|
5969
|
-
loginRetry: options.loginRetry,
|
|
5970
|
-
loginRetryTimeout: options.loginRetryTimeout,
|
|
5971
|
-
nonInteractive: options.nonInteractive,
|
|
5972
|
-
openRecovery: false,
|
|
5973
|
-
prepareAutomatorSession: useAutomatorOpen,
|
|
5974
|
-
reuseOpenedProject: !forceReopen,
|
|
5975
|
-
skipAutomatorCompile: !forceReopen,
|
|
5976
|
-
skipPostOpenHealthCheck: true,
|
|
5977
|
-
trustProject: options.trustProject,
|
|
5978
|
-
openStrategy,
|
|
5979
|
-
useAutomatorOpen
|
|
5980
|
-
});
|
|
5981
|
-
writePostOpenSeparator();
|
|
5982
|
-
},
|
|
5983
|
-
projectPath: resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd, generatedProjectConfig),
|
|
5984
|
-
startForwardConsole: async (openOptions) => {
|
|
5985
|
-
if (resolveServeIdeOpenStrategy(openOptions, options.ideOpenStrategy) === "automator") return await maybeStartForwardConsole({
|
|
5986
|
-
openedOnly: true,
|
|
5987
|
-
preferOpenedSession: true,
|
|
5988
|
-
platform: configService.platform,
|
|
5989
|
-
mpDistRoot: configService.mpDistRoot,
|
|
5990
|
-
cwd: configService.cwd,
|
|
5991
|
-
weappViteConfig: configService.weappViteConfig
|
|
5992
|
-
});
|
|
5993
|
-
return await maybeStartForwardConsole({
|
|
5994
|
-
platform: configService.platform,
|
|
5995
|
-
mpDistRoot: configService.mpDistRoot,
|
|
5996
|
-
cwd: configService.cwd,
|
|
5997
|
-
preferOpenedSession: false,
|
|
5998
|
-
recoverAutomatorSession: async () => {
|
|
5999
|
-
const projectPath = resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd, generatedProjectConfig);
|
|
6000
|
-
await openIde(configService.platform, projectPath, {
|
|
6001
|
-
loginRetry: options.loginRetry,
|
|
6002
|
-
loginRetryTimeout: options.loginRetryTimeout,
|
|
6003
|
-
nonInteractive: options.nonInteractive,
|
|
6004
|
-
openRecovery: false,
|
|
6005
|
-
openStrategy: "automator",
|
|
6006
|
-
prepareAutomatorSession: true,
|
|
6007
|
-
reuseOpenedProject: false,
|
|
6008
|
-
skipAutomatorCompile: false,
|
|
6009
|
-
skipPostOpenHealthCheck: true,
|
|
6010
|
-
trustProject: options.trustProject,
|
|
6011
|
-
useAutomatorOpen: true
|
|
6012
|
-
});
|
|
6013
|
-
},
|
|
6014
|
-
weappViteConfig: configService.weappViteConfig
|
|
6015
|
-
});
|
|
6016
|
-
},
|
|
6017
|
-
tryReuseForwardConsole: async () => {
|
|
6018
|
-
return await maybeStartForwardConsole({
|
|
6019
|
-
openedOnly: true,
|
|
6020
|
-
platform: configService.platform,
|
|
6021
|
-
mpDistRoot: configService.mpDistRoot,
|
|
6022
|
-
cwd: configService.cwd,
|
|
6023
|
-
weappViteConfig: configService.weappViteConfig
|
|
6024
|
-
});
|
|
6025
|
-
}
|
|
6026
|
-
});
|
|
6027
|
-
const devHotkeysSession = miniBackend ? startDevHotkeys({
|
|
6028
|
-
cwd: configService.cwd,
|
|
6029
|
-
agentName: aiEnvironment.agentName,
|
|
6030
|
-
isAgent: aiEnvironment.isAgent,
|
|
6031
|
-
mcpConfig,
|
|
6032
|
-
openIde: async () => await miniProgramDevActions.openIde({
|
|
6033
|
-
forceOpen: true,
|
|
6034
|
-
forceReopen: true
|
|
6035
|
-
}),
|
|
6036
|
-
platform: configService.platform,
|
|
6037
|
-
projectPath: miniProgramDevActions.projectPath ?? configService.cwd,
|
|
6038
|
-
rebuild: miniProgramDevActions.rebuild,
|
|
6039
|
-
silentStartupHint: true,
|
|
6040
|
-
weappViteConfig: configService.weappViteConfig
|
|
6041
|
-
}) : 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;
|
|
6042
5940
|
try {
|
|
6043
|
-
const
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
});
|
|
6049
|
-
let webServer;
|
|
6050
|
-
for (const backend of targets.select("dev")) {
|
|
6051
|
-
if (backend.descriptor.id === "miniprogram") {
|
|
6052
|
-
const miniBuildStartedAt = Date.now();
|
|
6053
|
-
const buildResult = await backend.driver.dev(ctx, options);
|
|
6054
|
-
const miniBuildDurationMs = Date.now() - miniBuildStartedAt;
|
|
6055
|
-
logger_default.success(`小程序初次构建完成,耗时:${formatDuration(miniBuildDurationMs)}`);
|
|
6056
|
-
ctx.autoImportService?.syncSupportFileResolverComponents().catch((error) => {
|
|
6057
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
6058
|
-
logger_default.warn(`[prepare] 后台同步 .weapp-vite 支持文件失败:${message}`);
|
|
6059
|
-
});
|
|
6060
|
-
if (enableAnalyze) {
|
|
6061
|
-
await analyzeController.startDashboard(startAnalyzeDashboard);
|
|
6062
|
-
analyzeHandle = analyzeController.getHandle();
|
|
6063
|
-
analyzeController.emitRuntimeEvents([{
|
|
6064
|
-
kind: "build",
|
|
6065
|
-
level: "success",
|
|
6066
|
-
title: "mini initial build completed",
|
|
6067
|
-
detail: "小程序开发态初次构建已完成,dashboard 可继续监听后续刷新。",
|
|
6068
|
-
durationMs: miniBuildDurationMs,
|
|
6069
|
-
tags: [
|
|
6070
|
-
"dev",
|
|
6071
|
-
"mini",
|
|
6072
|
-
"initial"
|
|
6073
|
-
]
|
|
6074
|
-
}]);
|
|
6075
|
-
await analyzeController.bindWatcher(buildResult).runInitialUpdate();
|
|
6076
|
-
}
|
|
6077
|
-
continue;
|
|
6078
|
-
}
|
|
6079
|
-
if (backend.descriptor.id !== "web") continue;
|
|
6080
|
-
const webServerStartedAt = Date.now();
|
|
6081
|
-
try {
|
|
6082
|
-
webServer = await backend.driver.dev(ctx, options);
|
|
6083
|
-
logger_default.success(`Web 开发服务启动完成,耗时:${formatDuration(Date.now() - webServerStartedAt)}`);
|
|
6084
|
-
analyzeController.emitRuntimeEvents([{
|
|
6085
|
-
kind: "system",
|
|
6086
|
-
level: "success",
|
|
6087
|
-
title: "web dev server started",
|
|
6088
|
-
detail: "Web 开发服务器已启动,可与小程序调试 UI 并行工作。",
|
|
6089
|
-
durationMs: Date.now() - webServerStartedAt,
|
|
6090
|
-
tags: ["dev", "web"]
|
|
6091
|
-
}]);
|
|
6092
|
-
} catch (error) {
|
|
6093
|
-
analyzeController.emitRuntimeEvents([{
|
|
6094
|
-
kind: "diagnostic",
|
|
6095
|
-
level: "error",
|
|
6096
|
-
title: "web dev server failed",
|
|
6097
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
6098
|
-
durationMs: Date.now() - webServerStartedAt,
|
|
6099
|
-
tags: ["dev", "web"]
|
|
6100
|
-
}]);
|
|
6101
|
-
logger_default.error(error);
|
|
6102
|
-
throw error;
|
|
6103
|
-
}
|
|
6104
|
-
}
|
|
6105
|
-
if (miniBackend) {
|
|
6106
|
-
logBuildAppFinish(configService, webServer, {
|
|
6107
|
-
skipWeb: !webBackend,
|
|
6108
|
-
uiUrls: analyzeHandle?.urls
|
|
6109
|
-
});
|
|
6110
|
-
devHotkeysSession?.restore();
|
|
6111
|
-
} else if (webBackend) logBuildAppFinish(configService, webServer, { skipMini: true });
|
|
6112
|
-
if (options.open && getBackendForCapability(targets, "miniprogram", "ide")) {
|
|
6113
|
-
analyzeController.emitRuntimeEvents([{
|
|
6114
|
-
kind: "command",
|
|
6115
|
-
level: "info",
|
|
6116
|
-
title: "opening ide",
|
|
6117
|
-
detail: "开发服务已就绪,准备打开 IDE 项目。",
|
|
6118
|
-
tags: ["ide", "open"]
|
|
6119
|
-
}]);
|
|
6120
|
-
devHotkeysSession?.suspend();
|
|
6121
|
-
try {
|
|
6122
|
-
await miniProgramDevActions.openIde({
|
|
6123
|
-
forceOpen: true,
|
|
6124
|
-
forceReopen: false,
|
|
6125
|
-
openStrategy: options.ideOpenStrategy ?? "cli"
|
|
6126
|
-
});
|
|
6127
|
-
} finally {
|
|
6128
|
-
devHotkeysSession?.restore();
|
|
6129
|
-
}
|
|
6130
|
-
}
|
|
6131
|
-
if (analyzeHandle) await analyzeController.waitForExit();
|
|
6132
|
-
else if (targets.has("dev")) await waitForServeShutdownSignal();
|
|
6133
|
-
} finally {
|
|
6134
|
-
devHotkeysSession?.close();
|
|
6135
|
-
await closeActiveForwardConsole();
|
|
6136
|
-
for (const backend of [...targets.select("dev")].reverse()) await backend.driver.close(ctx);
|
|
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;
|
|
6137
5946
|
}
|
|
5947
|
+
await shutdown.close(failure);
|
|
6138
5948
|
});
|
|
6139
5949
|
}
|
|
6140
5950
|
//#endregion
|
|
6141
5951
|
//#region src/cli/commands/upload.ts
|
|
6142
|
-
async function buildUploadTarget(cwd, platform, options, action) {
|
|
5952
|
+
async function buildUploadTarget(cwd, platform, options, action, item) {
|
|
6143
5953
|
const targets = resolveRuntimeTargets({ platform });
|
|
6144
5954
|
const ctx = await createCompilerContext({
|
|
6145
5955
|
cwd,
|
|
@@ -6153,6 +5963,11 @@ async function buildUploadTarget(cwd, platform, options, action) {
|
|
|
6153
5963
|
});
|
|
6154
5964
|
try {
|
|
6155
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
|
+
}
|
|
6156
5971
|
logger_default.info(`[${action}:${target.platform}] 构建${target.context.version ? ` ${target.context.version}` : ""}`);
|
|
6157
5972
|
await ctx.buildService.build({});
|
|
6158
5973
|
return target;
|
|
@@ -6166,16 +5981,58 @@ async function runUploadCommand(root, options, action = "upload") {
|
|
|
6166
5981
|
const platforms = resolveUploadPlatforms(options.platform ?? options.p);
|
|
6167
5982
|
setCommandNodeEnv("production");
|
|
6168
5983
|
const cwd = path$1.resolve(root ?? process.cwd());
|
|
6169
|
-
|
|
6170
|
-
|
|
5984
|
+
if (action === "preview") {
|
|
5985
|
+
options = await prepareAutoUploadMetadata(cwd, options, action);
|
|
5986
|
+
for (const platform of platforms) await executeUploadTarget(await buildUploadTarget(cwd, platform, options, action), options, action);
|
|
5987
|
+
return;
|
|
5988
|
+
}
|
|
5989
|
+
const report = {
|
|
5990
|
+
schemaVersion: 1,
|
|
5991
|
+
action: "upload",
|
|
5992
|
+
status: "success",
|
|
5993
|
+
results: platforms.map((platform) => ({
|
|
5994
|
+
platform: platform ?? null,
|
|
5995
|
+
stage: "prepare",
|
|
5996
|
+
status: "not-run"
|
|
5997
|
+
}))
|
|
5998
|
+
};
|
|
5999
|
+
let active = report.results[0];
|
|
6000
|
+
try {
|
|
6001
|
+
const timeoutMs = resolveUploadTimeout(options.timeout);
|
|
6002
|
+
options = await prepareAutoUploadMetadata(cwd, options, action);
|
|
6003
|
+
for (const item of report.results) item.requestedVersion = options.uv?.trim();
|
|
6004
|
+
for (const [index, platform] of platforms.entries()) {
|
|
6005
|
+
active = report.results[index];
|
|
6006
|
+
const target = await buildUploadTarget(cwd, platform, options, action, active);
|
|
6007
|
+
active.stage = "validate";
|
|
6008
|
+
const result = await executeUploadTarget(target, options, action, {
|
|
6009
|
+
timeoutMs,
|
|
6010
|
+
onUpload: () => {
|
|
6011
|
+
active.stage = "upload";
|
|
6012
|
+
}
|
|
6013
|
+
});
|
|
6014
|
+
active.status = options.dryRun ? "dry-run" : "success";
|
|
6015
|
+
if (!options.dryRun) active.result = result;
|
|
6016
|
+
}
|
|
6017
|
+
return report;
|
|
6018
|
+
} catch (error) {
|
|
6019
|
+
report.status = "failed";
|
|
6020
|
+
report.error = error instanceof Error ? error.message : String(error);
|
|
6021
|
+
active.status = "failed";
|
|
6022
|
+
active.error = report.error;
|
|
6023
|
+
if (error instanceof UploadExecutionError) {
|
|
6024
|
+
active.remoteOutcome = error.remoteOutcome;
|
|
6025
|
+
if (error.remoteOutcome === "unknown" && error.reason !== "failed") active.status = "unknown";
|
|
6026
|
+
}
|
|
6027
|
+
throw new UploadCommandError(report);
|
|
6028
|
+
}
|
|
6171
6029
|
}
|
|
6172
6030
|
function registerUploadCommand(cli) {
|
|
6173
|
-
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").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) => {
|
|
6174
|
-
|
|
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, {
|
|
6175
6033
|
...options,
|
|
6176
6034
|
...readUploadMetadata(cli)
|
|
6177
6035
|
});
|
|
6178
|
-
scheduleCompletedProductionBuildExit({}, void 0);
|
|
6179
6036
|
});
|
|
6180
6037
|
}
|
|
6181
6038
|
function registerPreviewCommand(cli) {
|
|
@@ -6241,7 +6098,8 @@ const WEAPP_VITE_NATIVE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
6241
6098
|
"build-npm",
|
|
6242
6099
|
"generate",
|
|
6243
6100
|
"g",
|
|
6244
|
-
"mcp"
|
|
6101
|
+
"mcp",
|
|
6102
|
+
"accept"
|
|
6245
6103
|
]);
|
|
6246
6104
|
const LEGACY_UPLOAD_OPTIONS = {
|
|
6247
6105
|
"--version": true,
|
|
@@ -6257,7 +6115,9 @@ const SDK_UPLOAD_OPTIONS = {
|
|
|
6257
6115
|
"--uv": true,
|
|
6258
6116
|
"--bump": true,
|
|
6259
6117
|
"--git-desc": true,
|
|
6260
|
-
"--dry-run": true
|
|
6118
|
+
"--dry-run": true,
|
|
6119
|
+
"--json": true,
|
|
6120
|
+
"--timeout": true
|
|
6261
6121
|
};
|
|
6262
6122
|
const UPLOAD_VALUE_OPTIONS = {
|
|
6263
6123
|
...LEGACY_UPLOAD_OPTIONS,
|
|
@@ -6267,6 +6127,7 @@ const UPLOAD_VALUE_OPTIONS = {
|
|
|
6267
6127
|
"--platform": true,
|
|
6268
6128
|
"--uv": true,
|
|
6269
6129
|
"--bump": true,
|
|
6130
|
+
"--timeout": true,
|
|
6270
6131
|
"--project-config": true,
|
|
6271
6132
|
"--port": true,
|
|
6272
6133
|
"--lang": true,
|
|
@@ -6409,15 +6270,10 @@ function handlePrepareLifecycleError(args, error) {
|
|
|
6409
6270
|
//#endregion
|
|
6410
6271
|
//#region src/cli.ts
|
|
6411
6272
|
const cli = cac("weapp-vite");
|
|
6412
|
-
try {
|
|
6413
|
-
checkRuntime({
|
|
6414
|
-
bun: "0.0.0",
|
|
6415
|
-
deno: "0.0.0",
|
|
6416
|
-
node: "^20.19.0 || >=22.12.0"
|
|
6417
|
-
});
|
|
6418
|
-
} catch {}
|
|
6419
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);
|
|
6420
6275
|
registerIdeCommand(cli);
|
|
6276
|
+
registerDoctorCommand(cli);
|
|
6421
6277
|
registerAlipayCommand(cli);
|
|
6422
6278
|
registerBuildCommand(cli);
|
|
6423
6279
|
registerUploadCommand(cli);
|
|
@@ -6440,7 +6296,9 @@ const skipManagedTsconfigBootstrapCommands = /* @__PURE__ */ new Set([
|
|
|
6440
6296
|
"init",
|
|
6441
6297
|
"mcp",
|
|
6442
6298
|
"npm",
|
|
6443
|
-
"alipay"
|
|
6299
|
+
"alipay",
|
|
6300
|
+
"doctor",
|
|
6301
|
+
"accept"
|
|
6444
6302
|
]);
|
|
6445
6303
|
function resolveManagedTsconfigBootstrapRoot(args) {
|
|
6446
6304
|
const [firstArg, secondArg] = args;
|
|
@@ -6472,11 +6330,19 @@ function resolveManagedTsconfigBootstrapRoot(args) {
|
|
|
6472
6330
|
try {
|
|
6473
6331
|
const args = process.argv.slice(2);
|
|
6474
6332
|
if (!await tryRunIdeCommand(args)) {
|
|
6475
|
-
const managedTsconfigBootstrapRoot = resolveManagedTsconfigBootstrapRoot(args);
|
|
6476
|
-
if (managedTsconfigBootstrapRoot) await syncManagedTsconfigBootstrapFiles(managedTsconfigBootstrapRoot);
|
|
6477
6333
|
cli.parse(process.argv, { run: false });
|
|
6478
|
-
|
|
6479
|
-
|
|
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();
|
|
6480
6346
|
}
|
|
6481
6347
|
} catch (error) {
|
|
6482
6348
|
if (!handlePrepareLifecycleError(process.argv.slice(2), error)) {
|