weapp-vite 7.3.0 → 7.4.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/dist/cli.mjs CHANGED
@@ -1,30 +1,39 @@
1
- import { A as getDefaultIdeProjectRoot, C as collectPreloadPages, D as loadViteConfigFile, E as suggestPreloadRules, M as isPathInside, O as createCjsConfigLoadError, S as getProjectConfigFileName, T as resolvePreloadPackageIdentifier, _ as SHARED_CHUNK_VIRTUAL_PREFIX, a as formatBytes, b as resolveWeappConfigFile, d as getBackendForCapability, f as resolveBackendExecution, g as createSharedBuildConfig, h as createGlassEaselAnalyzeResult, j as shouldPassPlatformArgToIdeOpen, k as parseCommentJson, m as syncManagedTsconfigBootstrapFiles, p as syncProjectSupportFiles, t as createCompilerContext, v as resolveHmrProfileJsonPath, w as findPreloadRuleKey, x as checkRuntime } from "./createContext-cqr9Y4EV.mjs";
2
- import { f as VERSION, i as findJsEntry, o as findTemplateEntry, s as findVueEntry } from "./file-CKB0Tn8Z.mjs";
1
+ import { a as resolveWeappViteHostMeta } from "./pluginHost--CaeyWpA.mjs";
2
+ import { C as collectPreloadPages, D as loadViteConfigFile, E as suggestPreloadRules, O as createCjsConfigLoadError, S as getProjectConfigFileName, T as resolvePreloadPackageIdentifier, _ as SHARED_CHUNK_VIRTUAL_PREFIX, a as formatBytes, b as resolveWeappConfigFile, d as getBackendForCapability, f as resolveBackendExecution, g as createSharedBuildConfig, h as createGlassEaselAnalyzeResult, k as isPathInside, m as syncManagedTsconfigBootstrapFiles, p as syncProjectSupportFiles, t as createCompilerContext, v as resolveHmrProfileJsonPath, w as findPreloadRuleKey, x as checkRuntime } from "./createContext-UOEgeYnA.mjs";
3
+ import { l as getProjectPlatformOptions, n as getDefaultIdeProjectRoot, w as shouldPassPlatformArgToIdeOpen } from "./platform-DTWs5QRi.mjs";
4
+ import { i as findJsEntry, o as findTemplateEntry, s as findVueEntry } from "./file-giE9UMZa.mjs";
5
+ import { t as VERSION } from "./constants-WOsG3Eqd.mjs";
3
6
  import { r as logger_default, t as colors } from "./logger-mt4mSTqV.mjs";
7
+ import { a as createDashboardArtifactSnapshot, i as resolveDashboardRoot, n as ANALYZE_DASHBOARD_PACKAGE_NAME, t as createAnalyzeDashboardDevframe } from "./dashboard-BzaVesmQ.mjs";
8
+ import { n as resolveUploadPlatforms, r as redactUploadSecrets, t as prepareUpload } from "./upload-D0RcEWrq.mjs";
4
9
  import { c as startWeappViteMcpServer, l as detectAiDevelopmentEnvironment, s as resolveWeappMcpConfig } from "./mcp-BG6TliEg.mjs";
5
- import { createRequire } from "node:module";
6
- import fs from "node:fs";
7
10
  import path, { posix } from "pathe";
11
+ import fs from "node:fs";
12
+ import { fileURLToPath } from "node:url";
8
13
  import path$1 from "node:path";
9
14
  import { defu } from "@weapp-core/shared";
10
15
  import { fs as fs$1 } from "@weapp-core/shared/fs";
11
16
  import process from "node:process";
12
17
  import { parse } from "vue/compiler-sfc";
13
- import fs$2, { mkdir, writeFile } from "node:fs/promises";
18
+ import fs$2, { access, mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
14
19
  import { build, createServer } from "vite";
15
- import { createHash } from "node:crypto";
16
- import { promisify } from "node:util";
20
+ import { randomUUID } from "node:crypto";
21
+ import { parseArgs, parseEnv, promisify } from "node:util";
17
22
  import os from "node:os";
18
- import { execFile, spawn } from "node:child_process";
23
+ import { execFile, fork, spawn } from "node:child_process";
19
24
  import { Buffer } from "node:buffer";
20
25
  import { cac } from "cac";
21
26
  import { RETRY_CANCEL_KEYS, RETRY_CONFIRM_KEYS, bootstrapWechatDevtoolsSettings, buildWechatIdeNpm, clearWechatIdeCache, clearWechatIdeCacheByAutomator, closeSharedMiniProgram, closeWechatIdeProject, compileWechatIdeByAutomator, connectOpenedAutomator, createSharedInputSession, createWechatIdeLoginRequiredExitError, defaultCustomConfigFilePath, detectWechatDevtoolsServicePort, dispatchWechatCliCommand, formatAutomatorLoginError, getConfig, getWechatIdeTestAccounts, getWechatIdeTicket, getWechatIdeToolInfo, isAutomatorLoginError, isRetryableAutomatorLaunchError, isWeappIdeTopLevelCommand, isWechatIdeEngineBuildEndpointMissingError, isWechatIdeLoggedIn, isWechatIdeLoginRequiredError, isWechatIdeLoginRequiredExitError, launchAutomator, parse as parse$1, promptRetryKeypress, promptWechatIdeLoginRetry, quitWechatIde, refreshWechatIdeTicket, resetWechatIdeFileUtilsByHttp, resolveCliPath, resolveProjectAutomatorPort, runRetryableCommand, runWechatIdeEngineBuild, runWithSuspendedSharedInput, setWechatIdeTicket, startForwardConsole, takeScreenshot } from "weapp-ide-cli";
22
27
  import { brotliCompressSync, gzipSync } from "node:zlib";
28
+ import { defineRpcFunction } from "devframe";
23
29
  import { buildOtpAuthUrl, refreshTempAuthCode } from "devframe/node/auth";
24
30
  import { resolveCommand } from "package-manager-detector/commands";
25
- import { defineDevframe, defineRpcFunction } from "devframe";
26
31
  import { Server } from "node:http";
32
+ import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_SSE_ROUTE, DEVFRAME_WS_ROUTE } from "devframe/constants";
27
33
  import { initDevframe } from "devframe/initiate";
34
+ import semverInc from "semver/functions/inc.js";
35
+ import { x } from "tinyexec";
36
+ import { expand } from "dotenv-expand";
28
37
  import { generateJs, generateJson, generateWxml, generateWxss } from "@weapp-core/schematics";
29
38
  import { determineAgent } from "@vercel/detect-agent";
30
39
  import { initConfig } from "@weapp-core/init";
@@ -449,10 +458,11 @@ async function runWechatIdeOpenWithRetry(argv) {
449
458
  });
450
459
  }
451
460
  /**
452
- * @description 根据 mpDistRoot 推导 IDE 项目目录(目录内应包含 project/mini 配置)
461
+ * @description 根据代码输出目录推导 IDE 项目目录;内联项目配置与 app.json 同目录。
453
462
  */
454
- function resolveIdeProjectPath(mpDistRoot) {
463
+ function resolveIdeProjectPath(mpDistRoot, generatedProjectConfig = false) {
455
464
  if (!mpDistRoot || !mpDistRoot.trim()) return;
465
+ if (generatedProjectConfig) return mpDistRoot;
456
466
  const parent = path.dirname(mpDistRoot);
457
467
  if (!parent || parent === "." || parent === "/") return;
458
468
  return parent;
@@ -460,8 +470,9 @@ function resolveIdeProjectPath(mpDistRoot) {
460
470
  /**
461
471
  * @description 结合 mpDistRoot 与配置根目录解析最终 IDE 项目目录。
462
472
  */
463
- function resolveIdeProjectRoot(mpDistRoot, cwd) {
464
- return resolveIdeProjectPath(mpDistRoot) ?? cwd;
473
+ function resolveIdeProjectRoot(mpDistRoot, cwd, generatedProjectConfig = false) {
474
+ const projectPath = resolveIdeProjectPath(mpDistRoot, generatedProjectConfig);
475
+ return generatedProjectConfig && projectPath && cwd ? path.resolve(cwd, projectPath) : projectPath ?? cwd;
465
476
  }
466
477
  async function closeIde() {
467
478
  return await closeIde$1();
@@ -722,7 +733,7 @@ async function resolveIdeCommandContext(options) {
722
733
  cliPlatform: options.cliPlatform
723
734
  });
724
735
  platform ??= ctx.configService.platform;
725
- if (!projectPath) projectPath = resolveIdeProjectRoot(ctx.configService.mpDistRoot, ctx.configService.cwd);
736
+ if (!projectPath) projectPath = resolveIdeProjectRoot(ctx.configService.mpDistRoot, ctx.configService.cwd, ctx.configService.multiPlatform.projectConfigs !== void 0);
726
737
  return {
727
738
  cwd: ctx.configService.cwd,
728
739
  platform,
@@ -2259,316 +2270,7 @@ function createAnalyzeMarkdownReport(result, previousResult) {
2259
2270
  ].join("\n");
2260
2271
  }
2261
2272
  //#endregion
2262
- //#region src/cli/analyze/dashboardDevframe/artifacts.ts
2263
- const MAX_DASHBOARD_FILE_CONTENT_BYTES = 2097152;
2264
- /** 保留同一次分析的产物内容,不读取或改写开发构建目录。 */
2265
- function createDashboardArtifactSnapshot() {
2266
- const files = /* @__PURE__ */ new Map();
2267
- let retainedBytes = 0;
2268
- return {
2269
- files,
2270
- capture(fileName, source) {
2271
- const key = fileName.replaceAll("\\", "/");
2272
- const previous = files.get(key);
2273
- if (previous?.content !== void 0) retainedBytes -= previous.size;
2274
- const size = typeof source === "string" ? Buffer.byteLength(source, "utf8") : source.byteLength;
2275
- if (size > 2097152) {
2276
- files.set(key, {
2277
- size,
2278
- error: `文件超过 ${MAX_DASHBOARD_FILE_CONTENT_BYTES} 字节,已拒绝读取。`
2279
- });
2280
- return;
2281
- }
2282
- if (retainedBytes + size > 33554432) {
2283
- files.set(key, {
2284
- size,
2285
- error: "分析产物内容超过快照容量上限,已拒绝读取。"
2286
- });
2287
- return;
2288
- }
2289
- const content = typeof source === "string" ? source : Buffer.from(source.buffer, source.byteOffset, source.byteLength).toString("utf8");
2290
- files.set(key, {
2291
- content,
2292
- size
2293
- });
2294
- retainedBytes += size;
2295
- }
2296
- };
2297
- }
2298
- //#endregion
2299
- //#region src/cli/analyze/dashboardDevframe/paths.ts
2300
- function normalizeDashboardRelativePath(value) {
2301
- return value.replaceAll("\\", "/");
2302
- }
2303
- function stripDashboardFileQuery(value) {
2304
- const queryIndex = value.indexOf("?");
2305
- return queryIndex === -1 ? value : value.slice(0, queryIndex);
2306
- }
2307
- function addDashboardAllowedPath(paths, value) {
2308
- if (!value || value.includes("\0")) return;
2309
- const normalizedPath = normalizeDashboardRelativePath(stripDashboardFileQuery(value));
2310
- if (!normalizedPath || path.isAbsolute(normalizedPath)) return;
2311
- paths.add(normalizedPath);
2312
- }
2313
- function addDashboardAllowedSourcePath(paths, value, rootKind) {
2314
- if (!value || value.includes("\0")) return;
2315
- const normalizedPath = normalizeDashboardRelativePath(stripDashboardFileQuery(value));
2316
- if (!normalizedPath || path.isAbsolute(normalizedPath)) return;
2317
- const rootKinds = paths.get(normalizedPath) ?? /* @__PURE__ */ new Set();
2318
- rootKinds.add(rootKind);
2319
- paths.set(normalizedPath, rootKinds);
2320
- }
2321
- function resolveDashboardSourceRootKind(sourceType) {
2322
- if (sourceType === "plugin") return "plugin";
2323
- if (sourceType === "workspace" || sourceType === "node_modules") return "project";
2324
- return "src";
2325
- }
2326
- function createDashboardContentAllowlist(result) {
2327
- const artifactPaths = /* @__PURE__ */ new Set();
2328
- const sourcePaths = /* @__PURE__ */ new Map();
2329
- for (const packageReport of result.packages) for (const file of packageReport.files) {
2330
- addDashboardAllowedPath(artifactPaths, file.file);
2331
- const fileSourceType = file.sourceType ?? file.modules?.find((module) => module.source === file.source)?.sourceType;
2332
- addDashboardAllowedSourcePath(sourcePaths, file.source, resolveDashboardSourceRootKind(fileSourceType));
2333
- for (const module of file.modules ?? []) addDashboardAllowedSourcePath(sourcePaths, module.source, module.sourceType === "src" ? "src" : module.sourceType === "plugin" ? "plugin" : "project");
2334
- }
2335
- return {
2336
- artifactPaths,
2337
- sourcePaths
2338
- };
2339
- }
2340
- function resolveDashboardContentPath(root, requestPath, options) {
2341
- if (!root || !requestPath || requestPath.includes("\0")) return;
2342
- const normalizedRequestPath = normalizeDashboardRelativePath(stripDashboardFileQuery(requestPath));
2343
- if (path.isAbsolute(normalizedRequestPath) || !options.allowedPaths.has(normalizedRequestPath)) return;
2344
- const rootPath = path.resolve(root);
2345
- const absolutePath = path.resolve(rootPath, normalizedRequestPath);
2346
- const relativePath = path.relative(rootPath, absolutePath);
2347
- const allowParent = options.allowParent === true;
2348
- if (!relativePath || !allowParent && (relativePath.startsWith("..") || path.isAbsolute(relativePath))) return;
2349
- return {
2350
- absolutePath,
2351
- allowParent,
2352
- relativePath: allowParent ? normalizedRequestPath : normalizeDashboardRelativePath(relativePath),
2353
- rootPath
2354
- };
2355
- }
2356
- function resolveDashboardSourceContentPaths(roots, requestPath, allowedPaths) {
2357
- const normalizedRequestPath = normalizeDashboardRelativePath(stripDashboardFileQuery(requestPath));
2358
- const rootKinds = allowedPaths.get(normalizedRequestPath);
2359
- if (!rootKinds || rootKinds.size !== 1) return [];
2360
- const [rootKind] = rootKinds;
2361
- if (rootKind === "project") {
2362
- const resolved = resolveDashboardContentPath(roots.projectRoot, normalizedRequestPath, {
2363
- allowParent: true,
2364
- allowedPaths: /* @__PURE__ */ new Set([normalizedRequestPath])
2365
- });
2366
- return resolved ? [resolved] : [];
2367
- }
2368
- if (rootKind === "plugin") {
2369
- if (!roots.pluginRoot) return [];
2370
- const pluginBase = path.basename(roots.pluginRoot);
2371
- if (normalizedRequestPath !== pluginBase && !normalizedRequestPath.startsWith(`${pluginBase}/`)) return [];
2372
- const pluginRelativePath = normalizedRequestPath === pluginBase ? "" : normalizedRequestPath.slice(pluginBase.length + 1);
2373
- const resolved = resolveDashboardContentPath(roots.pluginRoot, pluginRelativePath, { allowedPaths: /* @__PURE__ */ new Set([pluginRelativePath]) });
2374
- return resolved ? [{
2375
- ...resolved,
2376
- relativePath: normalizedRequestPath
2377
- }] : [];
2378
- }
2379
- if (!roots.srcRoot) return [];
2380
- const candidates = (normalizedRequestPath.startsWith("src/") ? [normalizedRequestPath, normalizedRequestPath.slice(4)] : [normalizedRequestPath]).flatMap((relativePath) => {
2381
- const resolved = resolveDashboardContentPath(roots.srcRoot, relativePath, { allowedPaths: /* @__PURE__ */ new Set([relativePath]) });
2382
- return resolved ? [{
2383
- ...resolved,
2384
- relativePath: normalizedRequestPath
2385
- }] : [];
2386
- });
2387
- return [...new Map(candidates.map((candidate) => [candidate.absolutePath, candidate])).values()];
2388
- }
2389
- function resolveDashboardArtifactPath(requestPath, allowedPaths) {
2390
- const normalizedPath = normalizeDashboardRelativePath(stripDashboardFileQuery(requestPath));
2391
- if (!normalizedPath || normalizedPath.includes("\0") || path.isAbsolute(normalizedPath) || path.posix.normalize(normalizedPath).startsWith("..") || !allowedPaths.has(normalizedPath)) return;
2392
- return normalizedPath;
2393
- }
2394
- //#endregion
2395
- //#region src/cli/analyze/dashboardDevframe/content.ts
2396
- function isPathWithin(rootPath, targetPath) {
2397
- const relativePath = path.relative(rootPath, targetPath);
2398
- return !relativePath || !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
2399
- }
2400
- function resolveInspectionRoot(resolved) {
2401
- let inspectionRoot = resolved.rootPath;
2402
- if (!resolved.allowParent) return inspectionRoot;
2403
- while (!isPathWithin(inspectionRoot, resolved.absolutePath)) {
2404
- const parent = path.dirname(inspectionRoot);
2405
- if (parent === inspectionRoot) return;
2406
- inspectionRoot = parent;
2407
- }
2408
- return inspectionRoot;
2409
- }
2410
- async function assertDashboardPathHasNoSymlink(resolved) {
2411
- const inspectionRoot = resolveInspectionRoot(resolved);
2412
- if (!inspectionRoot) throw new Error("文件路径超出允许范围。");
2413
- const relativePath = path.relative(inspectionRoot, resolved.absolutePath);
2414
- let currentPath = inspectionRoot;
2415
- for (const segment of relativePath.split(path.sep).filter(Boolean)) {
2416
- currentPath = path.join(currentPath, segment);
2417
- if ((await fs.promises.lstat(currentPath)).isSymbolicLink()) throw new Error("文件路径包含不允许的符号链接。");
2418
- }
2419
- }
2420
- function isSameOpenedFile(pathStat, handleStat) {
2421
- if (pathStat.dev !== handleStat.dev) return false;
2422
- return pathStat.ino === 0 || handleStat.ino === 0 || pathStat.ino === handleStat.ino;
2423
- }
2424
- async function openDashboardFile(resolved) {
2425
- await assertDashboardPathHasNoSymlink(resolved);
2426
- const flags = process.platform === "win32" ? fs.constants.O_RDONLY : fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW;
2427
- const handle = await fs.promises.open(resolved.absolutePath, flags);
2428
- try {
2429
- await assertDashboardPathHasNoSymlink(resolved);
2430
- const [pathStat, handleStat] = await Promise.all([fs.promises.stat(resolved.absolutePath), handle.stat()]);
2431
- if (!isSameOpenedFile(pathStat, handleStat)) throw new Error("文件路径在读取期间发生变化。");
2432
- return handle;
2433
- } catch (error) {
2434
- await handle.close();
2435
- throw error;
2436
- }
2437
- }
2438
- async function readDashboardFileHandle(handle) {
2439
- const stat = await handle.stat();
2440
- if (!stat.isFile()) throw new Error("目标路径不是文件。");
2441
- if (stat.size > 2097152) throw new Error(`文件超过 ${MAX_DASHBOARD_FILE_CONTENT_BYTES} 字节,已拒绝读取。`);
2442
- let content = Buffer.allocUnsafe(Math.min(Math.max(stat.size + 1, 1), 2097153));
2443
- let offset = 0;
2444
- while (true) {
2445
- if (offset === content.length) {
2446
- if (content.length === 2097153) throw new Error(`文件超过 ${MAX_DASHBOARD_FILE_CONTENT_BYTES} 字节,已拒绝读取。`);
2447
- const expanded = Buffer.allocUnsafe(Math.min(content.length * 2, 2097153));
2448
- content.copy(expanded, 0, 0, offset);
2449
- content = expanded;
2450
- }
2451
- const { bytesRead } = await handle.read(content, offset, content.length - offset, offset);
2452
- if (bytesRead === 0) break;
2453
- offset += bytesRead;
2454
- if (offset > 2097152) throw new Error(`文件超过 ${MAX_DASHBOARD_FILE_CONTENT_BYTES} 字节,已拒绝读取。`);
2455
- }
2456
- return {
2457
- content: content.subarray(0, offset).toString("utf8"),
2458
- size: offset
2459
- };
2460
- }
2461
- function resolveDashboardFileLanguage(filePath) {
2462
- const extension = path.extname(filePath).toLowerCase();
2463
- if ([
2464
- ".js",
2465
- ".mjs",
2466
- ".cjs",
2467
- ".wxs",
2468
- ".sjs"
2469
- ].includes(extension)) return "javascript";
2470
- if ([
2471
- ".ts",
2472
- ".mts",
2473
- ".cts"
2474
- ].includes(extension)) return "typescript";
2475
- if ([".json", ".map"].includes(extension)) return "json";
2476
- if ([
2477
- ".css",
2478
- ".wxss",
2479
- ".scss",
2480
- ".sass",
2481
- ".less"
2482
- ].includes(extension)) return "css";
2483
- if ([
2484
- ".vue",
2485
- ".wxml",
2486
- ".html"
2487
- ].includes(extension)) return "html";
2488
- return "plaintext";
2489
- }
2490
- function normalizeDashboardFileRequest(input) {
2491
- if (!input || typeof input !== "object" || !("kind" in input) || !("path" in input)) throw new Error("必须传入合法的 kind 和相对路径。");
2492
- const kind = input.kind;
2493
- const requestPath = input.path;
2494
- if (kind !== "source" && kind !== "artifact" || typeof requestPath !== "string") throw new Error("必须传入合法的 kind 和相对路径。");
2495
- return {
2496
- kind,
2497
- path: requestPath
2498
- };
2499
- }
2500
- async function readAllowedDashboardFile(input, roots, allowlist, artifacts) {
2501
- const request = normalizeDashboardFileRequest(input);
2502
- if (request.kind === "artifact") {
2503
- const filePath = resolveDashboardArtifactPath(request.path, allowlist.artifactPaths);
2504
- if (!filePath) throw new Error("必须传入合法的 kind 和相对路径。");
2505
- const file = artifacts.get(filePath);
2506
- if (!file) throw new Error("分析快照中没有此产物内容。");
2507
- if (file.error || file.content === void 0) throw new Error(file.error ?? "分析快照中没有此产物内容。");
2508
- return {
2509
- kind: request.kind,
2510
- path: filePath,
2511
- language: resolveDashboardFileLanguage(filePath),
2512
- size: file.size,
2513
- content: file.content
2514
- };
2515
- }
2516
- const resolvedCandidates = resolveDashboardSourceContentPaths(roots, request.path, allowlist.sourcePaths);
2517
- if (resolvedCandidates.length === 0) throw new Error("必须传入合法的 kind 和相对路径。");
2518
- const openedCandidates = [];
2519
- try {
2520
- for (const resolved of resolvedCandidates) try {
2521
- openedCandidates.push({
2522
- handle: await openDashboardFile(resolved),
2523
- resolved
2524
- });
2525
- } catch (error) {
2526
- if ((typeof error === "object" && error && "code" in error ? String(error.code) : "") !== "ENOENT") throw error;
2527
- }
2528
- if (openedCandidates.length === 0) throw new Error("文件不存在。");
2529
- if (openedCandidates.length > 1) throw new Error("源码路径存在多个候选文件,已拒绝读取。");
2530
- const [{ handle, resolved }] = openedCandidates;
2531
- const file = await readDashboardFileHandle(handle);
2532
- return {
2533
- kind: request.kind,
2534
- path: resolved.relativePath,
2535
- language: resolveDashboardFileLanguage(resolved.relativePath),
2536
- size: file.size,
2537
- content: file.content
2538
- };
2539
- } catch (error) {
2540
- if (error instanceof Error && (error.message === "文件不存在。" || error.message === "源码路径存在多个候选文件,已拒绝读取。" || error.message === "目标路径不是文件。" || error.message.startsWith("文件超过 ") || error.message.startsWith("文件路径"))) throw error;
2541
- throw new Error("读取文件失败。");
2542
- } finally {
2543
- await Promise.allSettled(openedCandidates.map((candidate) => candidate.handle.close()));
2544
- }
2545
- }
2546
- function createDashboardFileReader(roots, result, artifacts) {
2547
- let allowlist = createDashboardContentAllowlist(result);
2548
- let currentArtifacts = artifacts;
2549
- let disposed = false;
2550
- return {
2551
- read: async (input) => {
2552
- if (disposed) throw new Error("Dashboard 文件读取会话已关闭。");
2553
- return await readAllowedDashboardFile(input, roots, allowlist, currentArtifacts);
2554
- },
2555
- update(nextResult, nextArtifacts) {
2556
- allowlist = createDashboardContentAllowlist(nextResult);
2557
- currentArtifacts = nextArtifacts;
2558
- },
2559
- dispose() {
2560
- disposed = true;
2561
- currentArtifacts = /* @__PURE__ */ new Map();
2562
- allowlist.artifactPaths.clear();
2563
- allowlist.sourcePaths.clear();
2564
- }
2565
- };
2566
- }
2567
- //#endregion
2568
- //#region src/cli/analyze/dashboardDevframe/index.ts
2569
- const DEVFRAME_ID = "weapp-vite";
2570
- const MAX_DASHBOARD_ANALYZE_PAGE_CHARACTERS = 65536;
2571
- const STALE_DASHBOARD_ANALYZE_REVISION_MESSAGE = "Analyze revision 已变化,请重新获取 Dashboard 状态。";
2273
+ //#region src/cli/analyze/dashboardPolicy.ts
2572
2274
  const rejectDashboardSharedStateSet = defineRpcFunction({
2573
2275
  name: "devframe:rpc:server-state:set",
2574
2276
  type: "action",
@@ -2583,145 +2285,30 @@ const rejectDashboardSharedStatePatch = defineRpcFunction({
2583
2285
  throw new Error("Dashboard Devframe shared state 只允许服务端修改。");
2584
2286
  }
2585
2287
  });
2586
- function serializeDashboardAnalyzePayload(result) {
2587
- const value = JSON.stringify(result);
2588
- return {
2589
- descriptor: {
2590
- characters: value.length,
2591
- hash: createHash("sha256").update(value).digest("hex"),
2592
- pages: Math.max(1, Math.ceil(value.length / MAX_DASHBOARD_ANALYZE_PAGE_CHARACTERS))
2593
- },
2594
- source: result,
2595
- value
2596
- };
2597
- }
2598
- function serializeDashboardAnalyzeSnapshot(snapshot, cached) {
2599
- const previous = snapshot.previous ? cached?.current.source === snapshot.previous ? cached.current : cached?.previous?.source === snapshot.previous ? cached.previous : serializeDashboardAnalyzePayload(snapshot.previous) : null;
2600
- return {
2601
- current: cached?.current.source === snapshot.current ? cached.current : serializeDashboardAnalyzePayload(snapshot.current),
2602
- previous
2603
- };
2604
- }
2605
- function createDashboardState(revision, snapshot, runtimeEvents) {
2606
- return {
2607
- analyze: {
2608
- current: snapshot.current.descriptor,
2609
- previous: snapshot.previous?.descriptor ?? null
2610
- },
2611
- revision,
2612
- runtimeEvents: [...runtimeEvents]
2613
- };
2614
- }
2615
- function normalizeAnalyzePageRequest(input) {
2616
- if (!input || typeof input !== "object") throw new Error("必须传入合法的 Analyze 分页请求。");
2617
- if (!("revision" in input) || !("target" in input) || !("index" in input)) throw new Error("必须传入合法的 Analyze 分页请求。");
2618
- const revision = input.revision;
2619
- const target = input.target;
2620
- const index = input.index;
2621
- if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 0 || target !== "current" && target !== "previous" || typeof index !== "number" || !Number.isSafeInteger(index) || index < 0) throw new Error("必须传入合法的 Analyze 分页请求。");
2622
- return {
2623
- revision,
2624
- target,
2625
- index
2626
- };
2627
- }
2628
- function readDashboardAnalyzePage(input, revision, snapshot) {
2629
- const request = normalizeAnalyzePageRequest(input);
2630
- if (request.revision !== revision) throw new Error(STALE_DASHBOARD_ANALYZE_REVISION_MESSAGE);
2631
- const payload = request.target === "current" ? snapshot.current : snapshot.previous;
2632
- if (!payload || request.index >= payload.descriptor.pages) throw new Error("Analyze 分页不存在。");
2633
- const start = request.index * MAX_DASHBOARD_ANALYZE_PAGE_CHARACTERS;
2288
+ /** 独立宿主在开放 RPC 前禁止全局共享状态写入,不影响嵌入宿主的策略。 */
2289
+ function withStandaloneDashboardPolicy(definition) {
2634
2290
  return {
2635
- content: payload.value.slice(start, start + MAX_DASHBOARD_ANALYZE_PAGE_CHARACTERS),
2636
- descriptor: payload.descriptor,
2637
- index: request.index,
2638
- revision,
2639
- target: request.target
2640
- };
2641
- }
2642
- function createAnalyzeDashboardDevframe(options) {
2643
- let revision = 0;
2644
- let disposed = false;
2645
- let serializedSnapshot = serializeDashboardAnalyzeSnapshot(options.getAnalyzeSnapshot());
2646
- let broadcastDashboardState;
2647
- const initialSnapshot = options.getAnalyzeSnapshot();
2648
- const fileReader = createDashboardFileReader(options.roots, initialSnapshot.current, initialSnapshot.artifacts);
2649
- const getDashboardState = defineRpcFunction({
2650
- name: "get-dashboard-state",
2651
- type: "query",
2652
- jsonSerializable: true,
2653
- handler: () => createDashboardState(revision, serializedSnapshot, options.getRuntimeEvents())
2654
- });
2655
- const getAnalyzePage = defineRpcFunction({
2656
- name: "get-analyze-page",
2657
- type: "query",
2658
- jsonSerializable: true,
2659
- handler: (input) => readDashboardAnalyzePage(input, revision, serializedSnapshot)
2660
- });
2661
- const readDashboardFile = defineRpcFunction({
2662
- name: "read-dashboard-file",
2663
- type: "query",
2664
- jsonSerializable: true,
2665
- handler: async (input) => {
2666
- if (!input || typeof input !== "object" || !("revision" in input) || typeof input.revision !== "number" || !Number.isSafeInteger(input.revision) || input.revision < 0) throw new Error("必须传入合法的文件读取 revision。");
2667
- const requestedRevision = input.revision;
2668
- if (disposed || requestedRevision !== revision) throw new Error(STALE_DASHBOARD_ANALYZE_REVISION_MESSAGE);
2669
- const content = await fileReader.read(input);
2670
- if (disposed || requestedRevision !== revision) throw new Error(STALE_DASHBOARD_ANALYZE_REVISION_MESSAGE);
2671
- return content;
2672
- }
2673
- });
2674
- return {
2675
- definition: defineDevframe({
2676
- id: DEVFRAME_ID,
2677
- name: "weapp-vite",
2678
- version: VERSION,
2679
- packageName: "weapp-vite",
2680
- importMetaUrl: import.meta.url,
2681
- homepage: "https://vite.weapp.dev/",
2682
- description: "weapp-vite 构建分析与小程序开发工具。",
2683
- icon: "ph:rocket-launch-duotone",
2684
- async setup(ctx) {
2685
- ctx.rpc.register(rejectDashboardSharedStateSet, true);
2686
- ctx.rpc.register(rejectDashboardSharedStatePatch, true);
2687
- const dashboard = ctx.scope(DEVFRAME_ID);
2688
- dashboard.rpc.register(getDashboardState);
2689
- dashboard.rpc.register(getAnalyzePage);
2690
- dashboard.rpc.register(readDashboardFile);
2691
- broadcastDashboardState = async () => {
2692
- await dashboard.rpc.broadcast({
2693
- method: "dashboard-state-updated",
2694
- args: [createDashboardState(revision, serializedSnapshot, options.getRuntimeEvents())],
2695
- event: true
2696
- });
2697
- };
2698
- }
2699
- }),
2700
- notifyAnalyzeUpdate() {
2701
- if (disposed) return;
2702
- revision += 1;
2703
- const snapshot = options.getAnalyzeSnapshot();
2704
- serializedSnapshot = serializeDashboardAnalyzeSnapshot(snapshot, serializedSnapshot);
2705
- fileReader.update(snapshot.current, snapshot.artifacts);
2706
- broadcastDashboardState?.();
2707
- },
2708
- syncRuntimeEvents() {
2709
- broadcastDashboardState?.();
2710
- },
2711
- dispose() {
2712
- disposed = true;
2713
- fileReader.dispose();
2714
- broadcastDashboardState = void 0;
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);
2715
2296
  }
2716
2297
  };
2717
2298
  }
2718
2299
  //#endregion
2719
2300
  //#region src/cli/analyze/dashboardViteBridge.ts
2720
2301
  const ANALYZE_DASHBOARD_DEVFRAME_BASE = "/__weapp-vite/";
2721
- function createAnalyzeDashboardViteBridge(definition) {
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) {
2722
2308
  let instance;
2723
2309
  let closing;
2724
2310
  const closeInstance = async () => {
2311
+ controller.dispose();
2725
2312
  if (closing) return await closing;
2726
2313
  const current = instance;
2727
2314
  instance = void 0;
@@ -2735,9 +2322,8 @@ function createAnalyzeDashboardViteBridge(definition) {
2735
2322
  name: "weapp-vite:dashboard-devframe",
2736
2323
  apply: "serve",
2737
2324
  async configureServer(server) {
2738
- await closeInstance();
2739
2325
  const httpServer = server.httpServer instanceof Server ? server.httpServer : void 0;
2740
- const created = initDevframe(definition, {
2326
+ const created = initDevframe(withStandaloneDashboardPolicy(controller.definition), {
2741
2327
  base: ANALYZE_DASHBOARD_DEVFRAME_BASE,
2742
2328
  distDir: false,
2743
2329
  ...httpServer ? { server: httpServer } : {
@@ -2748,12 +2334,19 @@ function createAnalyzeDashboardViteBridge(definition) {
2748
2334
  auth: true,
2749
2335
  mcp: false
2750
2336
  });
2751
- server.middlewares.use(created.nodeMiddleware);
2752
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
+ });
2753
2345
  await created.ready;
2754
2346
  instance = created;
2755
2347
  } catch (error) {
2756
- await created.close();
2348
+ controller.dispose();
2349
+ await created.close().catch(() => {});
2757
2350
  throw error;
2758
2351
  }
2759
2352
  server.httpServer?.once("close", () => {
@@ -2767,195 +2360,109 @@ function createAnalyzeDashboardViteBridge(definition) {
2767
2360
  }
2768
2361
  //#endregion
2769
2362
  //#region src/cli/analyze/dashboard.ts
2770
- const ANALYZE_DASHBOARD_PACKAGE_NAME = "@weapp-vite/dashboard";
2771
- const require = createRequire(import.meta.url);
2772
2363
  function createInstallCommand(agent) {
2773
2364
  const resolved = resolveCommand(agent ?? "npm", "install", [ANALYZE_DASHBOARD_PACKAGE_NAME]);
2774
2365
  if (!resolved) return `npm install ${ANALYZE_DASHBOARD_PACKAGE_NAME}`;
2775
2366
  return `${resolved.command} ${resolved.args.join(" ")}`;
2776
2367
  }
2777
- function formatEventTimestamp(date = /* @__PURE__ */ new Date()) {
2778
- return date.toLocaleTimeString("zh-CN", { hour12: false });
2779
- }
2780
- function createDashboardRuntimeEvent(input) {
2781
- return {
2782
- id: `dashboard:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`,
2783
- kind: input.kind,
2784
- level: input.level,
2785
- title: input.title,
2786
- detail: input.detail,
2787
- timestamp: formatEventTimestamp(),
2788
- source: input.source ?? "weapp-vite",
2789
- durationMs: input.durationMs,
2790
- tags: input.tags,
2791
- profile: input.profile
2792
- };
2793
- }
2794
- function readDashboardManifest(packageJsonPath) {
2795
- try {
2796
- return parseCommentJson(fs.readFileSync(packageJsonPath, "utf8"));
2797
- } catch {
2798
- return;
2799
- }
2800
- }
2801
- function resolveDashboardDistRoot(packageRoot, manifest) {
2802
- const distDir = manifest?.weappViteDashboard?.distDir ?? "dist";
2803
- const distRoot = path.resolve(packageRoot, distDir);
2804
- if (!fs.existsSync(distRoot)) return;
2805
- return { root: distRoot };
2806
- }
2807
- function resolveDashboardDevRoot(packageRoot, manifest) {
2808
- const devRoot = manifest?.weappViteDashboard?.devRoot;
2809
- const devConfigFile = manifest?.weappViteDashboard?.devConfigFile;
2810
- if (!devRoot || !devConfigFile) return;
2811
- const root = path.resolve(packageRoot, devRoot);
2812
- const configFile = path.resolve(root, devConfigFile);
2813
- if (!fs.existsSync(root) || !fs.existsSync(configFile)) return;
2814
- return {
2815
- root,
2816
- configFile
2817
- };
2818
- }
2819
- function resolveDashboardRoot(options) {
2820
- const resolvePaths = options?.cwd && options.cwd !== process.cwd() ? [options.cwd, process.cwd()] : options?.cwd ? [options.cwd] : void 0;
2821
- let dashboardPackageRoot;
2822
- let dashboardManifest;
2823
- try {
2824
- const dashboardPackageJsonPath = require.resolve(`${ANALYZE_DASHBOARD_PACKAGE_NAME}/package.json`, { paths: resolvePaths });
2825
- dashboardPackageRoot = path.dirname(dashboardPackageJsonPath);
2826
- dashboardManifest = readDashboardManifest(dashboardPackageJsonPath);
2827
- } catch {
2828
- dashboardPackageRoot = void 0;
2829
- dashboardManifest = void 0;
2830
- }
2831
- if (dashboardPackageRoot) {
2832
- const devResolved = resolveDashboardDevRoot(dashboardPackageRoot, dashboardManifest);
2833
- const distResolved = resolveDashboardDistRoot(dashboardPackageRoot, dashboardManifest);
2834
- const resolved = options?.watch ? devResolved ?? distResolved : distResolved ?? devResolved;
2835
- if (resolved) return resolved;
2836
- }
2837
- logger_default.warn(`[weapp-vite ui] 未安装可选仪表盘包 ${colors.bold(colors.green(ANALYZE_DASHBOARD_PACKAGE_NAME))},已自动降级关闭 dashboard 能力。`);
2838
- logger_default.info(`如需启用,请执行 ${colors.bold(colors.green(createInstallCommand(options?.packageManagerAgent)))}`);
2839
- }
2840
- async function waitForServerExit(server) {
2841
- let resolved = false;
2842
- const cleanup = async () => {
2843
- if (resolved) return;
2844
- resolved = true;
2845
- try {
2846
- await server.close();
2847
- } catch (error) {
2848
- logger_default.error(error);
2849
- }
2850
- };
2851
- const signals = ["SIGINT", "SIGTERM"];
2852
- await new Promise((resolvePromise) => {
2853
- const resolveOnce = async () => {
2854
- await cleanup();
2855
- signals.forEach((signal) => {
2856
- process.removeListener(signal, resolveOnce);
2857
- });
2858
- resolvePromise();
2859
- };
2860
- signals.forEach((signal) => {
2861
- process.once(signal, resolveOnce);
2862
- });
2863
- server.httpServer?.once("close", resolveOnce);
2864
- });
2865
- }
2866
2368
  async function startAnalyzeDashboard(result, options) {
2867
2369
  const resolved = resolveDashboardRoot(options);
2868
- if (!resolved) return;
2370
+ if (!resolved) {
2371
+ logger_default.warn(`[weapp-vite ui] 未安装可选仪表盘包 ${colors.bold(colors.green(ANALYZE_DASHBOARD_PACKAGE_NAME))},已自动降级关闭 dashboard 能力。`);
2372
+ logger_default.info(`如需启用,请执行 ${colors.bold(colors.green(createInstallCommand(options.packageManagerAgent)))}`);
2373
+ return;
2374
+ }
2869
2375
  const { root, configFile } = resolved;
2870
- const state = {
2871
- current: result,
2872
- previous: options.previousResult ?? null,
2873
- artifacts: options.artifacts
2874
- };
2875
- const runtimeEvents = { current: [createDashboardRuntimeEvent({
2876
- kind: "command",
2877
- level: "success",
2878
- title: options?.watch ? "dashboard watch session started" : "dashboard static session started",
2879
- detail: options?.watch ? "weapp-vite UI 已进入实时分析模式,后续 analyze 结果会继续推送到 dashboard。" : "weapp-vite UI 已进入静态分析模式,当前页面展示的是一次性分析结果。",
2880
- tags: options?.watch ? ["watch", "analyze"] : ["static", "analyze"]
2881
- }), ...(options?.initialEvents ?? []).map((event) => createDashboardRuntimeEvent(event))] };
2882
2376
  const devframe = createAnalyzeDashboardDevframe({
2883
- getAnalyzeSnapshot: () => state,
2884
- getRuntimeEvents: () => runtimeEvents.current,
2377
+ snapshot: {
2378
+ current: result,
2379
+ previous: options.previousResult ?? null,
2380
+ artifacts: options.artifacts
2381
+ },
2382
+ initialEvents: [{
2383
+ kind: "command",
2384
+ level: "success",
2385
+ title: options.watch ? "dashboard watch session started" : "dashboard static session started",
2386
+ detail: options.watch ? "weapp-vite UI 已进入实时分析模式,后续 analyze 结果会继续推送到 dashboard。" : "weapp-vite UI 已进入静态分析模式,当前页面展示的是一次性分析结果。",
2387
+ tags: options.watch ? ["watch", "analyze"] : ["static", "analyze"]
2388
+ }, ...options.initialEvents ?? []],
2885
2389
  roots: {
2886
- pluginRoot: options?.pluginRoot,
2887
- projectRoot: options?.cwd,
2888
- srcRoot: options?.srcRoot ?? (options?.cwd ? path.resolve(options.cwd, "src") : void 0)
2390
+ pluginRoot: options.pluginRoot,
2391
+ projectRoot: options.cwd,
2392
+ srcRoot: options.srcRoot ?? (options.cwd ? path.resolve(options.cwd, "src") : void 0)
2889
2393
  }
2890
2394
  });
2891
- const plugins = [createAnalyzeDashboardViteBridge(devframe.definition)];
2892
- const serverOptions = {
2893
- root,
2894
- configFile: configFile ?? false,
2895
- clearScreen: false,
2896
- appType: "spa",
2897
- publicDir: false,
2898
- plugins,
2899
- server: {
2900
- host: "127.0.0.1",
2901
- port: 0,
2902
- watch: { ignored: ["**/*"] }
2903
- },
2904
- logLevel: "error"
2905
- };
2906
- const server = await createServer(serverOptions);
2907
- const requestedPort = typeof serverOptions.server?.port === "number" ? serverOptions.server.port : void 0;
2908
- await server.listen(requestedPort);
2909
- const urls = (() => {
2910
- const resolved = server.resolvedUrls;
2911
- if (!resolved) return [];
2912
- return [...resolved.local ?? [], ...resolved.network ?? []];
2913
- })();
2395
+ let server;
2396
+ try {
2397
+ server = await createServer({
2398
+ root,
2399
+ base: ANALYZE_DASHBOARD_DEVFRAME_BASE,
2400
+ configFile: configFile ?? false,
2401
+ clearScreen: false,
2402
+ appType: "spa",
2403
+ publicDir: false,
2404
+ plugins: [{
2405
+ name: "weapp-vite:dashboard-lifetime",
2406
+ enforce: "pre",
2407
+ configureServer(createdServer) {
2408
+ server = createdServer;
2409
+ }
2410
+ }, createAnalyzeDashboardViteBridge(devframe)],
2411
+ server: {
2412
+ host: "127.0.0.1",
2413
+ port: 0,
2414
+ watch: { ignored: ["**/*"] }
2415
+ },
2416
+ logLevel: "error"
2417
+ });
2418
+ await server.listen(0);
2419
+ } catch (error) {
2420
+ devframe.dispose();
2421
+ await server?.close().catch((closeError) => logger_default.error(closeError));
2422
+ throw error;
2423
+ }
2424
+ const activeServer = server;
2425
+ const urls = [...activeServer.resolvedUrls?.local ?? [], ...activeServer.resolvedUrls?.network ?? []];
2914
2426
  const authCode = refreshTempAuthCode();
2915
2427
  const authenticatedUrls = urls.map((url) => buildOtpAuthUrl(url, authCode));
2916
- let closed = false;
2917
- const waitPromise = waitForServerExit(server).then(() => {
2918
- closed = true;
2919
- state.artifacts = /* @__PURE__ */ new Map();
2920
- devframe.dispose();
2428
+ let closing;
2429
+ let resolveExit;
2430
+ const waitPromise = new Promise((resolve) => {
2431
+ resolveExit = resolve;
2921
2432
  });
2922
- const emitRuntimeEvents = (events) => {
2923
- if (events.length === 0) return;
2924
- const nextEvents = events.map((event) => createDashboardRuntimeEvent(event));
2925
- runtimeEvents.current = [...nextEvents, ...runtimeEvents.current].slice(0, 24);
2926
- devframe.syncRuntimeEvents();
2433
+ const signals = ["SIGINT", "SIGTERM"];
2434
+ let onExit;
2435
+ const close = () => {
2436
+ if (!closing) {
2437
+ devframe.dispose();
2438
+ closing = Promise.resolve().then(() => activeServer.close()).finally(() => {
2439
+ for (const signal of signals) process.removeListener(signal, onExit);
2440
+ activeServer.httpServer?.removeListener("close", onExit);
2441
+ resolveExit();
2442
+ });
2443
+ }
2444
+ return closing;
2445
+ };
2446
+ onExit = async () => {
2447
+ await close().catch((error) => logger_default.error(error));
2927
2448
  };
2449
+ for (const signal of signals) process.once(signal, onExit);
2450
+ activeServer.httpServer?.once("close", onExit);
2928
2451
  const handle = {
2929
- async update(nextResult, artifacts, previousResult) {
2930
- if (closed) return;
2931
- state.previous = previousResult ?? state.current;
2932
- state.current = nextResult;
2933
- state.artifacts = artifacts;
2934
- emitRuntimeEvents([{
2935
- kind: "build",
2936
- level: "info",
2937
- title: "analyze payload refreshed",
2938
- detail: `已推送新的 analyze 结果,当前包含 ${nextResult.packages.length} 个包与 ${nextResult.modules.length} 个模块。`,
2939
- tags: ["analyze", "refresh"]
2940
- }]);
2941
- devframe.notifyAnalyzeUpdate();
2942
- },
2943
- emitRuntimeEvents,
2452
+ update: devframe.update,
2453
+ emitRuntimeEvents: devframe.emitRuntimeEvents,
2944
2454
  waitForExit: () => waitPromise,
2945
- close: async () => {
2946
- closed = true;
2947
- await server.close();
2948
- },
2455
+ close,
2949
2456
  urls: authenticatedUrls
2950
2457
  };
2951
- if (options?.watch) {
2458
+ if (options.watch) {
2952
2459
  if (!options.silentStartupLog) {
2953
2460
  logger_default.info("weapp-vite UI 已启动(分析视图,实时模式),按 Ctrl+C 退出。");
2954
2461
  for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
2955
2462
  }
2956
2463
  return handle;
2957
2464
  }
2958
- if (!options?.silentStartupLog) {
2465
+ if (!options.silentStartupLog) {
2959
2466
  logger_default.info("weapp-vite UI 已启动(分析视图,静态模式),按 Ctrl+C 退出。");
2960
2467
  for (const url of handle.urls) logger_default.info(` ➜ ${colors.bold(colors.cyan(url))}`);
2961
2468
  }
@@ -3453,6 +2960,335 @@ function setCommandNodeEnv(nodeEnv) {
3453
2960
  process.env.NODE_ENV = nodeEnv;
3454
2961
  }
3455
2962
  //#endregion
2963
+ //#region src/cli/upload/autoMetadata.ts
2964
+ /** 只读取命令根目录的清单,不向父目录查找或修改文件。 */
2965
+ async function readPackageVersion(packagePath) {
2966
+ let manifest;
2967
+ try {
2968
+ manifest = JSON.parse(await readFile(packagePath, "utf8"));
2969
+ } catch (cause) {
2970
+ throw new Error("--bump 无法读取命令根目录的 package.json。", { cause });
2971
+ }
2972
+ if (manifest === null || typeof manifest !== "object" || !("version" in manifest) || typeof manifest.version !== "string") throw new Error("--bump 要求命令根目录 package.json 的 version 为有效的 SemVer 字符串。");
2973
+ return manifest.version;
2974
+ }
2975
+ /** 在首次编译配置求值前准备一次元数据;预演不写文件,实际版本由 npm 同步。 */
2976
+ async function prepareAutoUploadMetadata(cwd, options, action) {
2977
+ const { bump, gitDesc } = options;
2978
+ if (action === "preview" && (bump !== void 0 || gitDesc !== void 0)) throw new Error("--bump 和 --git-desc 仅支持 upload 或 build --upload,不支持 preview。");
2979
+ if (bump === void 0 && !gitDesc) return options;
2980
+ if (bump !== void 0 && options.uv !== void 0) throw new Error("--bump 不能与 --uv 同时使用。");
2981
+ if (gitDesc && options.desc !== void 0) throw new Error("--git-desc 不能与 --desc 同时使用。");
2982
+ if (bump !== void 0 && bump !== "patch" && bump !== "minor" && bump !== "major") throw new Error("--bump 仅支持 patch、minor 或 major。");
2983
+ const root = path$1.resolve(cwd);
2984
+ const packagePath = path$1.join(root, "package.json");
2985
+ let currentVersion;
2986
+ let nextVersion;
2987
+ if (bump !== void 0) {
2988
+ currentVersion = await readPackageVersion(packagePath);
2989
+ nextVersion = semverInc(currentVersion, bump) ?? void 0;
2990
+ if (!nextVersion) throw new Error("--bump 要求命令根目录 package.json 的 version 为有效的 SemVer 字符串。");
2991
+ }
2992
+ let desc;
2993
+ if (gitDesc) {
2994
+ try {
2995
+ desc = (await x("git", [
2996
+ "log",
2997
+ "-1",
2998
+ "--format=%s"
2999
+ ], {
3000
+ nodePath: false,
3001
+ throwOnError: true,
3002
+ nodeOptions: { cwd: root }
3003
+ })).stdout.trim();
3004
+ } catch (cause) {
3005
+ throw new Error("--git-desc 无法读取最新 Git 提交主题,请确认 Git 可用且仓库已有提交。", { cause });
3006
+ }
3007
+ if (!desc) throw new Error("--git-desc 要求最新 Git 提交主题非空。");
3008
+ }
3009
+ if (nextVersion) {
3010
+ if (options.dryRun) logger_default.info(`[upload] dry-run:计划版本 ${currentVersion} → ${nextVersion},未修改 package.json 或锁文件。`);
3011
+ else {
3012
+ try {
3013
+ await x("npm", [
3014
+ "version",
3015
+ nextVersion,
3016
+ "--no-git-tag-version",
3017
+ "--ignore-scripts",
3018
+ "--workspaces=false",
3019
+ "--prefix",
3020
+ root
3021
+ ], {
3022
+ nodePath: false,
3023
+ throwOnError: true,
3024
+ nodeOptions: { cwd: root }
3025
+ });
3026
+ } catch (cause) {
3027
+ throw new Error("--bump 执行 npm version 失败,请检查本地版本与锁文件后重试。", { cause });
3028
+ }
3029
+ if (await readPackageVersion(packagePath) !== nextVersion) throw new Error("--bump 执行 npm version 后,命令根目录的 package.json 版本未按预期更新。");
3030
+ logger_default.info(`[upload] 版本已更新:${currentVersion} → ${nextVersion}。后续构建或上传失败不会回退;重试同一版本时请移除 --bump。`);
3031
+ }
3032
+ }
3033
+ if (gitDesc) logger_default.info("[upload] 使用最新 Git 提交主题作为上传说明。");
3034
+ const metadata = { ...options };
3035
+ if (nextVersion !== void 0) metadata.uv = nextVersion;
3036
+ if (desc !== void 0) metadata.desc = desc;
3037
+ return metadata;
3038
+ }
3039
+ //#endregion
3040
+ //#region src/cli/upload/env.ts
3041
+ /** 凭据不经过 Vite 的 env 调试日志;文件优先级和进程环境覆盖规则保持一致。 */
3042
+ async function loadUploadEnv(cwd, mode, root, envDir) {
3043
+ if (envDir === false) return { ...process.env };
3044
+ if (mode === "local") throw new Error("local 不能作为 mode,因为它与 .env.local 文件后缀冲突。");
3045
+ const directory = path$1.resolve(cwd, root ?? ".", envDir ?? ".");
3046
+ const parsed = {};
3047
+ for (const file of [
3048
+ ".env",
3049
+ ".env.local",
3050
+ `.env.${mode}`,
3051
+ `.env.${mode}.local`
3052
+ ]) {
3053
+ const content = await readFile(path$1.join(directory, file), "utf8").catch((error) => {
3054
+ if (error.code === "ENOENT") return "";
3055
+ throw error;
3056
+ });
3057
+ Object.assign(parsed, parseEnv(content));
3058
+ }
3059
+ const processEnv = { ...parsed };
3060
+ for (const [key, value] of Object.entries(process.env)) if (value !== void 0) processEnv[key] = value;
3061
+ expand({
3062
+ parsed,
3063
+ processEnv
3064
+ });
3065
+ return {
3066
+ ...parsed,
3067
+ ...process.env
3068
+ };
3069
+ }
3070
+ //#endregion
3071
+ //#region src/cli/upload/result.ts
3072
+ /** 只接受可展示的预览结果,文件必须由本次任务生成,不能沿用旧二维码。 */
3073
+ async function validatePreviewResult(value, context) {
3074
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("预览工具未返回二维码或预览链接。");
3075
+ const input = value;
3076
+ const result = {};
3077
+ for (const key of ["qrCodeUrl", "previewUrl"]) {
3078
+ if (input[key] === void 0) continue;
3079
+ if (typeof input[key] !== "string" || !input[key].trim()) throw new Error("预览工具返回了无效链接。");
3080
+ let url;
3081
+ try {
3082
+ url = new URL(input[key]);
3083
+ } catch {
3084
+ throw new Error("预览工具返回了无效链接。");
3085
+ }
3086
+ if (url.username || url.password || key === "qrCodeUrl" && url.protocol !== "http:" && url.protocol !== "https:" || [
3087
+ "javascript:",
3088
+ "data:",
3089
+ "file:"
3090
+ ].includes(url.protocol)) throw new Error("预览工具返回了不支持的链接类型。");
3091
+ result[key] = input[key];
3092
+ }
3093
+ if (input.qrCodeFile !== void 0) {
3094
+ if (typeof input.qrCodeFile !== "string" || !context.qrCodePath || path$1.resolve(input.qrCodeFile) !== path$1.resolve(context.qrCodePath)) throw new Error("预览工具返回的二维码文件不是本次任务的输出路径。");
3095
+ const file = await stat(input.qrCodeFile);
3096
+ if (!file.isFile() || file.size === 0) throw new Error("预览二维码文件不存在或为空。");
3097
+ result.qrCodeFile = input.qrCodeFile;
3098
+ }
3099
+ if (!result.qrCodeUrl && !result.previewUrl && !result.qrCodeFile) throw new Error("预览工具未返回二维码或预览链接。");
3100
+ return result;
3101
+ }
3102
+ //#endregion
3103
+ //#region src/cli/upload/process.ts
3104
+ /** 隔离官方 SDK 的日志和进程级副作用,主进程通过 stdin 传入上下文。 */
3105
+ async function executeUpload(platform, context, secrets, action = "upload") {
3106
+ let result;
3107
+ const output = await new Promise((resolve, reject) => {
3108
+ const child = fork(fileURLToPath(new URL("./upload-worker.mjs", import.meta.url)), [], {
3109
+ cwd: context.cwd,
3110
+ env: context.env,
3111
+ execArgv: [],
3112
+ stdio: [
3113
+ "pipe",
3114
+ "pipe",
3115
+ "pipe",
3116
+ "ipc"
3117
+ ]
3118
+ });
3119
+ let completed = false;
3120
+ let exceeded = false;
3121
+ let captured = "";
3122
+ let cancelled = false;
3123
+ const cancel = () => {
3124
+ cancelled = true;
3125
+ child.kill();
3126
+ };
3127
+ process.once("SIGINT", cancel);
3128
+ process.once("SIGTERM", cancel);
3129
+ const collect = (chunk) => {
3130
+ if (exceeded) return;
3131
+ captured += chunk;
3132
+ if (captured.length > 4194304) {
3133
+ exceeded = true;
3134
+ child.kill();
3135
+ }
3136
+ };
3137
+ child.stdout?.setEncoding("utf8").on("data", collect);
3138
+ child.stderr?.setEncoding("utf8").on("data", collect);
3139
+ child.on("message", (message) => {
3140
+ if (message !== null && typeof message === "object" && "type" in message && message.type === "completed" && "action" in message && message.action === action) {
3141
+ completed = true;
3142
+ result = "result" in message ? message.result : void 0;
3143
+ }
3144
+ });
3145
+ child.once("error", reject);
3146
+ child.once("close", (code, signal) => {
3147
+ process.off("SIGINT", cancel);
3148
+ process.off("SIGTERM", cancel);
3149
+ const safeOutput = redactUploadSecrets(captured, secrets).trim();
3150
+ if (code !== 0 || !completed || exceeded || cancelled) {
3151
+ reject(/* @__PURE__ */ new Error(`${platform} ${action === "preview" ? "预览" : "上传"}失败(${exceeded ? "日志超限" : signal ?? code ?? "unknown"},${completed ? "进程异常结束" : "未收到完成确认"})${safeOutput ? `\n${safeOutput}` : ""}`));
3152
+ return;
3153
+ }
3154
+ resolve(safeOutput);
3155
+ });
3156
+ child.stdin?.on("error", () => {});
3157
+ child.stdin?.end(JSON.stringify({
3158
+ platform,
3159
+ context,
3160
+ action
3161
+ }));
3162
+ });
3163
+ if (output) logger_default.info(output);
3164
+ return action === "preview" ? validatePreviewResult(result, context) : void 0;
3165
+ }
3166
+ //#endregion
3167
+ //#region src/cli/upload/project.ts
3168
+ /** 确保官方工具读取的目录正是本次构建输出,拒绝误传旧目录或同名配置。 */
3169
+ async function validateUploadProject(options) {
3170
+ const { projectConfigFileName } = getProjectPlatformOptions(options.platform);
3171
+ if (options.sourceConfigPath && path$1.basename(options.sourceConfigPath) !== projectConfigFileName) throw new Error(`上传工具只读取 ${projectConfigFileName},请使用此标准文件名,避免读取其他项目配置。`);
3172
+ const configPath = path$1.join(options.projectPath, projectConfigFileName);
3173
+ const config = JSON.parse(await readFile(configPath, "utf8"));
3174
+ if (!config || typeof config !== "object" || Array.isArray(config)) throw new TypeError(`${projectConfigFileName} 必须是 JSON 对象。`);
3175
+ const rootKey = options.platform === "swan" ? "smartProgramRoot" : "miniprogramRoot";
3176
+ const root = config[rootKey];
3177
+ if (typeof root !== "string" || !root.trim()) throw new Error(`${projectConfigFileName} 缺少代码目录 ${rootKey}。`);
3178
+ const sdkRoot = path$1.resolve(options.projectPath, root);
3179
+ const [actualRoot, outputRoot] = await Promise.all([realpath(sdkRoot), realpath(options.outDir)]);
3180
+ if (actualRoot !== outputRoot) throw new Error(`${projectConfigFileName} 的代码目录与本次构建输出不一致,已阻止上传旧产物。请检查项目配置根目录与 build.outDir。`);
3181
+ await access(path$1.join(outputRoot, "app.json"));
3182
+ }
3183
+ //#endregion
3184
+ //#region src/cli/upload/builtProject.ts
3185
+ /** 仅由显式上传或预览入口读取参数,不执行构建或调用平台工具。 */
3186
+ async function createUploadTarget(config, options, action) {
3187
+ if (config.weappLibConfig?.enabled || config.pluginOnly) throw new Error(`${action} 仅支持完整小程序项目,不支持组件库或独立插件构建。`);
3188
+ const platform = resolveUploadPlatforms(config.platform)[0];
3189
+ const uploadConfig = action === "upload" ? config.weappViteConfig.upload : void 0;
3190
+ const version = (options.uv ?? uploadConfig?.version ?? config.packageJson.version)?.trim() ?? "";
3191
+ if (action === "upload" && !version) throw new Error("请通过 --uv、weapp.upload.version 或项目 package.json 的 version 指定上传版本。");
3192
+ const desc = (options.desc ?? uploadConfig?.desc)?.trim() || `${config.packageJson.name ?? platform}${version ? `@${version}` : ""}`;
3193
+ const env = options.dryRun ? {} : await loadUploadEnv(config.cwd, config.mode, config.inlineConfig.root, config.inlineConfig.envDir);
3194
+ const { projectConfigFileName } = getProjectPlatformOptions(config.platform);
3195
+ const generatedProjectConfig = config.multiPlatform.projectConfigs !== void 0;
3196
+ const projectPath = generatedProjectConfig ? config.outDir : config.multiPlatform.enabled ? path$1.dirname(config.outDir) : path$1.dirname(config.projectConfigPath ?? path$1.join(config.cwd, projectConfigFileName));
3197
+ const appid = config.projectConfig.appid ?? config.projectConfig.appId;
3198
+ const target = {
3199
+ platform,
3200
+ sourceConfigPath: config.projectConfigPath,
3201
+ context: {
3202
+ cwd: config.cwd,
3203
+ projectPath,
3204
+ appid: typeof appid === "string" ? appid : void 0,
3205
+ version,
3206
+ desc,
3207
+ qrCodePath: action === "preview" && !options.dryRun ? path$1.join(config.cwd, ".weapp-vite", "preview", `${platform}-${randomUUID()}.png`) : void 0,
3208
+ env: {
3209
+ ...process.env,
3210
+ ...env
3211
+ }
3212
+ }
3213
+ };
3214
+ (config.inlineConfig.plugins ??= []).push({
3215
+ name: "weapp-vite:upload-output",
3216
+ apply: (viteConfig) => resolveWeappViteHostMeta(viteConfig)?.runtime === "miniprogram",
3217
+ writeBundle(output, bundle) {
3218
+ if (output.dir && Object.hasOwn(bundle, "app.json")) {
3219
+ target.outDir = path$1.resolve(output.dir);
3220
+ if (generatedProjectConfig) target.context.projectPath = target.outDir;
3221
+ }
3222
+ }
3223
+ });
3224
+ return target;
3225
+ }
3226
+ /** 只消费本次构建的产物;产物校验通过前不会准备凭据或调用平台工具。 */
3227
+ async function executeUploadTarget(target, options, action) {
3228
+ const { platform, context } = target;
3229
+ if (!target.outDir) throw new Error("本次构建未写出 app.json,已阻止上传或预览旧产物。");
3230
+ await validateUploadProject({
3231
+ platform,
3232
+ projectPath: context.projectPath,
3233
+ outDir: target.outDir,
3234
+ sourceConfigPath: target.sourceConfigPath
3235
+ });
3236
+ if (options.dryRun) {
3237
+ logger_default.success(`[${action}:${platform}] dry-run:构建完成,未校验凭据或调用平台工具。项目:${path$1.relative(context.cwd, context.projectPath) || "."}`);
3238
+ return;
3239
+ }
3240
+ const upload = await prepareUpload(platform, context, action);
3241
+ const result = await executeUpload(platform, context, upload.secrets, action);
3242
+ if (action === "preview") {
3243
+ if (!result) throw new Error("预览工具未返回二维码或预览链接。");
3244
+ for (const [label, value] of [
3245
+ ["二维码图片", result.qrCodeUrl],
3246
+ ["预览链接", result.previewUrl],
3247
+ ["二维码文件", result.qrCodeFile && path$1.relative(context.cwd, result.qrCodeFile)]
3248
+ ]) if (value) logger_default.info(`[preview:${platform}] ${label}:${redactUploadSecrets(value, upload.secrets)}`);
3249
+ logger_default.success(`[preview:${platform}] 预览已生成(未上传开发版本、未提审、未正式发布)。`);
3250
+ } else logger_default.success(`[upload:${platform}] ${context.version} 上传完成(未提审、未正式发布)。`);
3251
+ }
3252
+ //#endregion
3253
+ //#region src/cli/upload/options.ts
3254
+ function readUploadMetadata(cli) {
3255
+ for (const name of [
3256
+ "uv",
3257
+ "desc",
3258
+ "bump"
3259
+ ]) {
3260
+ const value = cli.options[name];
3261
+ if (typeof value === "boolean" || Array.isArray(value) && value.some((item) => typeof item === "boolean")) throw new Error(`--${name} 需要指定字符串参数。`);
3262
+ }
3263
+ const { values } = parseArgs({
3264
+ args: cli.rawArgs.slice(2),
3265
+ allowPositionals: true,
3266
+ strict: false,
3267
+ options: {
3268
+ uv: { type: "string" },
3269
+ desc: { type: "string" },
3270
+ bump: { type: "string" }
3271
+ }
3272
+ });
3273
+ return {
3274
+ uv: typeof values.uv === "string" ? values.uv : void 0,
3275
+ desc: typeof values.desc === "string" ? values.desc : void 0,
3276
+ bump: typeof values.bump === "string" ? values.bump : void 0
3277
+ };
3278
+ }
3279
+ /** 普通构建不读取上传元数据;上传必须是显式的一次性构建操作。 */
3280
+ function resolveBuildUploadOptions(cli, options) {
3281
+ if (options.upload !== true) {
3282
+ if (options.uv !== void 0 || options.desc !== void 0 || options.bump !== void 0 || options.gitDesc !== void 0 || options.dryRun !== void 0) throw new Error("--uv、--desc、--bump、--git-desc 和 --dry-run 仅能与 --upload 一起使用。");
3283
+ return;
3284
+ }
3285
+ if (options.watch) throw new Error("--upload 不能与 --watch 一起使用,请执行一次性构建。");
3286
+ return {
3287
+ ...options,
3288
+ ...readUploadMetadata(cli)
3289
+ };
3290
+ }
3291
+ //#endregion
3456
3292
  //#region src/cli/commands/build.ts
3457
3293
  function emitDashboardEvents$1(handle, events) {
3458
3294
  handle?.emitRuntimeEvents(events);
@@ -3488,17 +3324,21 @@ function scheduleCompletedProductionBuildExit(options, analyzeHandle) {
3488
3324
  }, 0).unref?.();
3489
3325
  }
3490
3326
  function registerBuildCommand(cli) {
3491
- cli.command("build [root]", "build for production").option("--target <target>", `[string] transpile target (default: 'modules')`).option("--outDir <dir>", `[string] output directory (default: dist)`).option("-p, --platform <platform>", `[string] target platform (weapp | web | all)`).option("--project-config <path>", `[string] project config path (miniprogram only)`).option("--sourcemap [output]", `[boolean | "inline" | "hidden"] output source maps for build (default: false)`).option("--minify [minifier]", "[boolean | \"terser\" | \"esbuild\"] enable/disable minification, or specify minifier to use (default: esbuild)").option("--emptyOutDir", `[boolean] force empty outDir when it's outside of root`).option("-w, --watch", `[boolean] rebuilds when modules have changed on disk`).option("--skipNpm", `[boolean] if skip npm build`).option("-o, --open", `[boolean] open ide`).option("--trust-project", "[boolean] auto trust Wechat DevTools project on open", { default: true }).option("--ide-open-strategy <strategy>", "[string] IDE open strategy (cli | automator)", { default: "cli" }).option("--no-open-recovery", "[boolean] disable automatic Wechat DevTools close-and-reopen recovery").option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--analyze", `[boolean] 输出分包分析仪表盘`, { default: false }).option("--scope <scope>", `[string] 局部构建范围,例如 main,packages/order`).action(async (root, options) => {
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 Wechat DevTools close-and-reopen recovery").option("--ui", `[boolean] 启动调试 UI(当前提供分析视图)`, { default: false }).option("--analyze", `[boolean] 输出分包分析仪表盘`, { default: false }).option("--scope <scope>", `[string] 局部构建范围,例如 main,packages/order`).option("--upload", "[boolean] upload the mini program after this build succeeds").option("--uv <version>", "[string] upload version override (requires --upload)").option("--desc <text>", "[string] upload description override (requires --upload)").option("--bump <release>", "[string] increment local package version (patch | minor | major; requires --upload)").option("--git-desc", "[boolean] use the latest Git commit subject (requires --upload)").option("--dry-run", "[boolean] validate upload output without credentials or SDK calls (requires --upload)").action(async (root, options) => {
3492
3328
  let analyzeHandle;
3493
3329
  let ctx;
3494
3330
  let targets;
3495
3331
  let buildCompleted = false;
3496
3332
  try {
3333
+ options = { ...options };
3497
3334
  filterDuplicateOptions(options);
3335
+ let uploadOptions = resolveBuildUploadOptions(cli, options);
3498
3336
  setCommandNodeEnv("production");
3499
3337
  const cwd = root ?? process.cwd();
3500
3338
  const configFile = resolveConfigFile(options);
3501
3339
  targets = resolveRuntimeTargets(options);
3340
+ if (uploadOptions && !getBackendForCapability(targets, "miniprogram", "build")) throw new Error("--upload 仅支持包含小程序目标的构建,不能用于纯 Web 构建。");
3341
+ if (uploadOptions) uploadOptions = await prepareAutoUploadMetadata(cwd, uploadOptions, "upload");
3502
3342
  const inlineConfig = createInlineConfig(targets, {
3503
3343
  scope: options.scope,
3504
3344
  inlineConfig: createBuildInlineConfig(options)
@@ -3514,6 +3354,7 @@ function registerBuildCommand(cli) {
3514
3354
  preloadAppEntry: false
3515
3355
  });
3516
3356
  const { configService } = ctx;
3357
+ const uploadTarget = uploadOptions ? await createUploadTarget(configService, uploadOptions, "upload") : void 0;
3517
3358
  const miniBackend = getBackendForCapability(targets, "miniprogram", "build");
3518
3359
  const webBackend = getBackendForCapability(targets, "web", "build");
3519
3360
  logRuntimeTarget(targets, { resolvedConfigPlatform: configService.platform });
@@ -3591,6 +3432,7 @@ function registerBuildCommand(cli) {
3591
3432
  throw error;
3592
3433
  }
3593
3434
  }
3435
+ if (uploadTarget && uploadOptions) await executeUploadTarget(uploadTarget, uploadOptions, "upload");
3594
3436
  if (miniBackend) logBuildAppFinish(configService, void 0, { skipWeb: !webBackend });
3595
3437
  if (options.open && getBackendForCapability(targets, "miniprogram", "ide")) {
3596
3438
  emitDashboardEvents$1(analyzeHandle, [{
@@ -3600,7 +3442,8 @@ function registerBuildCommand(cli) {
3600
3442
  detail: "构建完成后准备打开 IDE 项目。",
3601
3443
  tags: ["ide", "open"]
3602
3444
  }]);
3603
- await openIde(configService.platform, resolveIdeProjectPath(configService.mpDistRoot), {
3445
+ const generatedProjectConfig = configService.multiPlatform.projectConfigs !== void 0;
3446
+ await openIde(configService.platform, resolveIdeProjectPath(generatedProjectConfig ? configService.outDir : configService.mpDistRoot, generatedProjectConfig), {
3604
3447
  openRecovery: options.openRecovery,
3605
3448
  trustProject: options.trustProject,
3606
3449
  openStrategy: options.ideOpenStrategy
@@ -3987,7 +3830,9 @@ async function closeActiveForwardConsole() {
3987
3830
  */
3988
3831
  async function maybeStartForwardConsole(options) {
3989
3832
  if (options.platform !== "weapp") return false;
3990
- const projectPath = resolveIdeProjectPath(options.mpDistRoot) ?? options.cwd;
3833
+ const multiPlatform = options.weappViteConfig?.multiPlatform;
3834
+ const generatedProjectConfig = typeof multiPlatform === "object" && multiPlatform.projectConfigs !== void 0;
3835
+ const projectPath = resolveIdeProjectRoot(options.mpDistRoot, options.cwd, generatedProjectConfig);
3991
3836
  if (!projectPath) return false;
3992
3837
  if (activeForwardConsoleSession) return true;
3993
3838
  if (activeForwardConsoleStart) return await activeForwardConsoleStart;
@@ -5370,7 +5215,7 @@ function resolveRunnableHotkeyDefinition(input) {
5370
5215
  }
5371
5216
  //#endregion
5372
5217
  //#region package.json
5373
- var version = "7.3.0";
5218
+ var version = "7.4.0";
5374
5219
  //#endregion
5375
5220
  //#region src/cli/devHotkeys/format.ts
5376
5221
  const FULLWIDTH_ASCII_START = 65281;
@@ -6110,6 +5955,7 @@ function registerServeCommand(cli) {
6110
5955
  logRuntimeTarget(targets, { resolvedConfigPlatform: configService.platform });
6111
5956
  const enableAnalyze = Boolean(isUiEnabled(options) && miniBackend);
6112
5957
  let analyzeHandle;
5958
+ const generatedProjectConfig = configService.multiPlatform.projectConfigs !== void 0;
6113
5959
  const miniProgramDevActions = createServeMiniProgramDevActions({
6114
5960
  build: async () => {
6115
5961
  await miniBackend?.driver.dev(ctx, options);
@@ -6134,7 +5980,7 @@ function registerServeCommand(cli) {
6134
5980
  });
6135
5981
  writePostOpenSeparator();
6136
5982
  },
6137
- projectPath: resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd),
5983
+ projectPath: resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd, generatedProjectConfig),
6138
5984
  startForwardConsole: async (openOptions) => {
6139
5985
  if (resolveServeIdeOpenStrategy(openOptions, options.ideOpenStrategy) === "automator") return await maybeStartForwardConsole({
6140
5986
  openedOnly: true,
@@ -6150,7 +5996,7 @@ function registerServeCommand(cli) {
6150
5996
  cwd: configService.cwd,
6151
5997
  preferOpenedSession: false,
6152
5998
  recoverAutomatorSession: async () => {
6153
- const projectPath = resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd);
5999
+ const projectPath = resolveIdeProjectRoot(configService.mpDistRoot, configService.cwd, generatedProjectConfig);
6154
6000
  await openIde(configService.platform, projectPath, {
6155
6001
  loginRetry: options.loginRetry,
6156
6002
  loginRetryTimeout: options.loginRetryTimeout,
@@ -6292,6 +6138,56 @@ function registerServeCommand(cli) {
6292
6138
  });
6293
6139
  }
6294
6140
  //#endregion
6141
+ //#region src/cli/commands/upload.ts
6142
+ async function buildUploadTarget(cwd, platform, options, action) {
6143
+ const targets = resolveRuntimeTargets({ platform });
6144
+ const ctx = await createCompilerContext({
6145
+ cwd,
6146
+ mode: options.mode ?? "production",
6147
+ configFile: resolveConfigFile(options),
6148
+ inlineConfig: createInlineConfig(targets),
6149
+ cliPlatform: platform,
6150
+ projectConfigPath: options.projectConfig,
6151
+ emitDefaultAutoImportOutputs: false,
6152
+ preloadAppEntry: false
6153
+ });
6154
+ try {
6155
+ const target = await createUploadTarget(ctx.configService, options, action);
6156
+ logger_default.info(`[${action}:${target.platform}] 构建${target.context.version ? ` ${target.context.version}` : ""}`);
6157
+ await ctx.buildService.build({});
6158
+ return target;
6159
+ } finally {
6160
+ ctx.watcherService.closeAll();
6161
+ terminateStaleSassEmbeddedProcess();
6162
+ }
6163
+ }
6164
+ async function runUploadCommand(root, options, action = "upload") {
6165
+ filterDuplicateOptions(options);
6166
+ const platforms = resolveUploadPlatforms(options.platform ?? options.p);
6167
+ setCommandNodeEnv("production");
6168
+ const cwd = path$1.resolve(root ?? process.cwd());
6169
+ options = await prepareAutoUploadMetadata(cwd, options, action);
6170
+ for (const platform of platforms) await executeUploadTarget(await buildUploadTarget(cwd, platform, options, action), options, action);
6171
+ }
6172
+ 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
+ await runUploadCommand(root, {
6175
+ ...options,
6176
+ ...readUploadMetadata(cli)
6177
+ });
6178
+ scheduleCompletedProductionBuildExit({}, void 0);
6179
+ });
6180
+ }
6181
+ function registerPreviewCommand(cli) {
6182
+ cli.command("preview [root]", "build mini programs and generate preview QR codes or links").option("-p, --platform <platform>", "[string] weapp | alipay | tt | xhs | jd | swan; comma-separated targets or all").option("--project-config <path>", "[string] project config path").option("--desc <text>", "[string] preview description").option("--dry-run", "[boolean] build without validating credentials or generating previews").action(async (root, options) => {
6183
+ await runUploadCommand(root, {
6184
+ ...options,
6185
+ ...readUploadMetadata(cli)
6186
+ }, "preview");
6187
+ scheduleCompletedProductionBuildExit({}, void 0);
6188
+ });
6189
+ }
6190
+ //#endregion
6295
6191
  //#region src/cli/error.ts
6296
6192
  const watchLimitErrorCodes = /* @__PURE__ */ new Set(["EMFILE", "ENOSPC"]);
6297
6193
  const watchLimitMessagePattern = /EMFILE|ENOSPC|unable to start FSEvent stream/i;
@@ -6333,6 +6229,8 @@ const WEAPP_VITE_NATIVE_COMMANDS = /* @__PURE__ */ new Set([
6333
6229
  "dev",
6334
6230
  "serve",
6335
6231
  "build",
6232
+ "upload",
6233
+ "preview",
6336
6234
  "close",
6337
6235
  "analyze",
6338
6236
  "alipay",
@@ -6345,8 +6243,66 @@ const WEAPP_VITE_NATIVE_COMMANDS = /* @__PURE__ */ new Set([
6345
6243
  "g",
6346
6244
  "mcp"
6347
6245
  ]);
6246
+ const LEGACY_UPLOAD_OPTIONS = {
6247
+ "--version": true,
6248
+ "-v": true,
6249
+ "--project": true,
6250
+ "--appid": true,
6251
+ "--ext-appid": true,
6252
+ "--info-output": true,
6253
+ "-i": true
6254
+ };
6255
+ const SDK_UPLOAD_OPTIONS = {
6256
+ "--platform": true,
6257
+ "--uv": true,
6258
+ "--bump": true,
6259
+ "--git-desc": true,
6260
+ "--dry-run": true
6261
+ };
6262
+ const UPLOAD_VALUE_OPTIONS = {
6263
+ ...LEGACY_UPLOAD_OPTIONS,
6264
+ "-p": true,
6265
+ "--desc": true,
6266
+ "-d": true,
6267
+ "--platform": true,
6268
+ "--uv": true,
6269
+ "--bump": true,
6270
+ "--project-config": true,
6271
+ "--port": true,
6272
+ "--lang": true,
6273
+ "-c": true,
6274
+ "--config": true,
6275
+ "--base": true,
6276
+ "-l": true,
6277
+ "--logLevel": true,
6278
+ "-f": true,
6279
+ "--filter": true,
6280
+ "-m": true,
6281
+ "--mode": true
6282
+ };
6283
+ /**
6284
+ * @description 仅按显式参数区分上传方言,不根据 -p 的值或文件系统猜测后端。
6285
+ */
6286
+ function isLegacyUploadInvocation(argv) {
6287
+ let legacyOption;
6288
+ let sdkOption;
6289
+ for (let index = 1; index < argv.length; index += 1) {
6290
+ const token = argv[index];
6291
+ if (token === "--") break;
6292
+ const equalsIndex = token.indexOf("=");
6293
+ const option = equalsIndex === -1 ? token : token.slice(0, equalsIndex);
6294
+ if (LEGACY_UPLOAD_OPTIONS[option] === true) legacyOption = option;
6295
+ if (SDK_UPLOAD_OPTIONS[option.startsWith("--no-") ? `--${option.slice(5)}` : option] === true) sdkOption = option;
6296
+ if (legacyOption && sdkOption) throw new Error(`不能混用微信 IDE 上传参数 ${legacyOption} 与 SDK 上传参数 ${sdkOption};请使用 wv ide upload 保留 IDE 行为,或使用 wv build --upload 迁移到 SDK。`);
6297
+ if (equalsIndex === -1 && UPLOAD_VALUE_OPTIONS[option] === true) index += 1;
6298
+ }
6299
+ return legacyOption !== void 0;
6300
+ }
6301
+ function warnLegacyUpload() {
6302
+ logger_default.warn("顶层微信 IDE 上传语法已弃用,将在未来版本移除。请迁移到 wv build --upload -p weapp --uv <version> --desc <description>(SDK 流程需要源码目录与上传凭据,并非 IDE 命令的等价替换);或使用 wv ide upload ... 保留现有 IDE 行为。");
6303
+ }
6348
6304
  /**
6349
- * @description 让 weapp-vite 在未命中自身命令时,回退到 weapp-ide-cli。
6305
+ * @description 保留旧版上传方言;其余未命中自身命令的调用回退到 weapp-ide-cli。
6350
6306
  */
6351
6307
  async function tryRunIdeCommand(argv) {
6352
6308
  const command = argv[0];
@@ -6359,11 +6315,14 @@ async function tryRunIdeCommand(argv) {
6359
6315
  if (command.startsWith("-")) return false;
6360
6316
  if (command === "help") {
6361
6317
  const target = argv[1];
6362
- if (!target || WEAPP_VITE_NATIVE_COMMANDS.has(target) || !isWeappIdeTopLevelCommand(target)) return false;
6318
+ if (!target || target !== "upload" && WEAPP_VITE_NATIVE_COMMANDS.has(target) || !isWeappIdeTopLevelCommand(target)) return false;
6319
+ if (target === "upload") warnLegacyUpload();
6363
6320
  await executeWechatIdeCliCommand(argv);
6364
6321
  return true;
6365
6322
  }
6366
- if (WEAPP_VITE_NATIVE_COMMANDS.has(command) || !isWeappIdeTopLevelCommand(command)) return false;
6323
+ const legacyUpload = command === "upload" && isLegacyUploadInvocation(argv);
6324
+ if (!legacyUpload && WEAPP_VITE_NATIVE_COMMANDS.has(command) || !isWeappIdeTopLevelCommand(command)) return false;
6325
+ if (legacyUpload) warnLegacyUpload();
6367
6326
  if (!await dispatchWechatCliCommand(argv)) await executeWechatIdeCliCommand(argv);
6368
6327
  return true;
6369
6328
  }
@@ -6461,6 +6420,8 @@ cli.option("-c, --config <file>", `[string] use specified config file`).option("
6461
6420
  registerIdeCommand(cli);
6462
6421
  registerAlipayCommand(cli);
6463
6422
  registerBuildCommand(cli);
6423
+ registerUploadCommand(cli);
6424
+ registerPreviewCommand(cli);
6464
6425
  registerCloseCommand(cli);
6465
6426
  registerAnalyzeCommand(cli);
6466
6427
  registerInitCommand(cli);
@@ -6499,7 +6460,9 @@ function resolveManagedTsconfigBootstrapRoot(args) {
6499
6460
  "dev",
6500
6461
  "open",
6501
6462
  "prepare",
6502
- "serve"
6463
+ "preview",
6464
+ "serve",
6465
+ "upload"
6503
6466
  ].includes(firstArg)) {
6504
6467
  if (secondArg && !secondArg.startsWith("-")) return path.resolve(secondArg);
6505
6468
  return process.cwd();
@@ -6512,7 +6475,7 @@ try {
6512
6475
  const managedTsconfigBootstrapRoot = resolveManagedTsconfigBootstrapRoot(args);
6513
6476
  if (managedTsconfigBootstrapRoot) await syncManagedTsconfigBootstrapFiles(managedTsconfigBootstrapRoot);
6514
6477
  cli.parse(process.argv, { run: false });
6515
- await maybeAutoStartMcpServer(args, cli.options);
6478
+ if (cli.matchedCommand?.name === "" && cli.matchedCommandName === void 0) await maybeAutoStartMcpServer(args, cli.options);
6516
6479
  await cli.runMatchedCommand();
6517
6480
  }
6518
6481
  } catch (error) {