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.
Files changed (92) hide show
  1. package/README.md +51 -1
  2. package/bin/bootstrap.js +14 -1
  3. package/dist/adapters-CcHrsYaj.mjs +1 -0
  4. package/dist/analyze-ZJUvEdaY.mjs +248 -0
  5. package/dist/auto-import-components/resolvers.mjs +1 -2
  6. package/dist/auto-import-presets/wevu-router.mjs +1 -2
  7. package/dist/auto-import-presets/wevu.mjs +1 -2
  8. package/dist/auto-routes.mjs +2 -1
  9. package/dist/budget-BD7VqLCl.d.mts +178 -0
  10. package/dist/chunkStrategy-DRA5WpB-.mjs +1436 -0
  11. package/dist/cli-acceptance.d.mts +1 -0
  12. package/dist/cli-acceptance.mjs +21 -0
  13. package/dist/cli.mjs +1420 -1554
  14. package/dist/compatibility.mjs +1 -2
  15. package/dist/{config-Dbvj5yK7.d.mts → config-V0L-IAJf.d.mts} +87 -30
  16. package/dist/config.d.mts +2 -2
  17. package/dist/config.mjs +1 -1
  18. package/dist/{constants-WOsG3Eqd.mjs → constants-DEZdcsMn.mjs} +1 -1
  19. package/dist/{createContext-UOEgeYnA.mjs → createCompilerContextInstance-DJH_P_nZ.mjs} +31278 -32148
  20. package/dist/createContext-BqYzHhY9.mjs +12 -0
  21. package/dist/dashboard/analyze.d.mts +48 -0
  22. package/dist/dashboard/analyze.mjs +1 -0
  23. package/dist/dashboard/vite.d.mts +1 -1
  24. package/dist/dashboard-DvqF3Key.mjs +1842 -0
  25. package/dist/dashboard.d.mts +3 -2
  26. package/dist/dashboard.mjs +1 -2
  27. package/dist/dashboardViteBridge-Dy3nAD4m.mjs +299 -0
  28. package/dist/dev-fR5GiJRO.mjs +63 -0
  29. package/dist/devProvider-BGBkw28J.mjs +1050 -0
  30. package/dist/docs/README.md +51 -1
  31. package/dist/docs/ai-workflows.md +4 -0
  32. package/dist/docs/doctor.md +38 -0
  33. package/dist/docs/getting-started.md +3 -1
  34. package/dist/docs/index.md +3 -1
  35. package/dist/docs/mcp.md +94 -1
  36. package/dist/docs/testing.md +29 -2
  37. package/dist/docs/troubleshooting.md +8 -0
  38. package/dist/docs/uni-app-component-libraries.md +8 -7
  39. package/dist/docs/upload.md +40 -3
  40. package/dist/docs/vite-plugin.md +171 -0
  41. package/dist/docs/vue-sfc.md +19 -0
  42. package/dist/docs/weapp-config.md +54 -0
  43. package/dist/docs/wevu-authoring.md +10 -0
  44. package/dist/doctor-worker.d.mts +1 -0
  45. package/dist/doctor-worker.mjs +59 -0
  46. package/dist/doctor.d.mts +144 -0
  47. package/dist/doctor.mjs +848 -0
  48. package/dist/eslint.mjs +1 -2
  49. package/dist/file-DRb4eWqw.mjs +1 -0
  50. package/dist/{file-giE9UMZa.mjs → file-sC3jD7Fh.mjs} +15 -9
  51. package/dist/files-BqxFIPRp.mjs +45 -0
  52. package/dist/getInstance-DiGSzBNI.mjs +28 -0
  53. package/dist/getInstance-SiE6WZRh.mjs +1 -0
  54. package/dist/index-C9HjUX5c.d.mts +741 -0
  55. package/dist/index.d.mts +10 -8
  56. package/dist/index.mjs +3 -2
  57. package/dist/json.d.mts +1 -1
  58. package/dist/json.mjs +1 -2
  59. package/dist/lifecycle-BROTlwSL.mjs +320 -0
  60. package/dist/loadConfig-C3BQhz7F.mjs +59 -0
  61. package/dist/loadConfig-VGtBF4qm.mjs +1 -0
  62. package/dist/logger-DGn5iEVN.mjs +1 -0
  63. package/dist/{mcp-BG6TliEg.mjs → mcp-Cj8Maqo6.mjs} +16 -7
  64. package/dist/mcp-D3Yh24Cj.mjs +529 -0
  65. package/dist/mcp.d.mts +6 -3
  66. package/dist/mcp.mjs +1 -2
  67. package/dist/package-7swhyLmf.mjs +91 -0
  68. package/dist/path-doV773b7.mjs +103 -0
  69. package/dist/{platform-DTWs5QRi.mjs → platform-CnkySBKz.mjs} +1 -1
  70. package/dist/processCleanup-B6KA30Zc.mjs +77 -0
  71. package/dist/protocol-vg8HGJ4n.mjs +138 -0
  72. package/dist/{resolvers-icNxkpcy.mjs → resolvers-Dod-euqX.mjs} +4 -1
  73. package/dist/resolvers.mjs +1 -2
  74. package/dist/runtime.mjs +1 -2
  75. package/dist/session-PskhD-nZ.mjs +390 -0
  76. package/dist/test.d.mts +1 -0
  77. package/dist/test.mjs +201 -44
  78. package/dist/types.d.mts +2 -2
  79. package/dist/{upload-D0RcEWrq.mjs → upload-D4msblBf.mjs} +174 -15
  80. package/dist/upload-worker.mjs +136 -3
  81. package/dist/utils-CJSCgEj6.mjs +1094 -0
  82. package/dist/vite.d.mts +9 -0
  83. package/dist/vite.mjs +270 -0
  84. package/dist/volar.d.mts +2 -2
  85. package/dist/web-apis.mjs +1 -2
  86. package/package.json +67 -41
  87. package/dist/adapters-BwnMMIZR.mjs +0 -2
  88. package/dist/dashboard-BzaVesmQ.mjs +0 -558
  89. package/dist/file-D19nbDoN.mjs +0 -2
  90. package/dist/getInstance-CYF1vl5l.mjs +0 -2
  91. package/dist/index-DbqVupg0.d.mts +0 -246
  92. package/dist/logger-mt4mSTqV.mjs +0 -2
@@ -0,0 +1,848 @@
1
+ import { l as getProjectPlatformOptions, m as resolveMiniPlatform, o as getPlatformOutputExtensions } from "./platform-CnkySBKz.mjs";
2
+ import { i as createAnalyzeBudgetCheck } from "./analyze-ZJUvEdaY.mjs";
3
+ import { n as classifyPackage, t as version } from "./package-7swhyLmf.mjs";
4
+ import { n as parseObject, r as readDoctorFiles, t as objectValue } from "./files-BqxFIPRp.mjs";
5
+ import process from "node:process";
6
+ import { constants } from "node:fs";
7
+ import { fileURLToPath } from "node:url";
8
+ import path, { posix } from "node:path";
9
+ import { parseJsLike, traverse } from "@weapp-vite/ast/babel";
10
+ import { access, readFile, stat } from "node:fs/promises";
11
+ import { createHash } from "node:crypto";
12
+ import { Parser } from "htmlparser2";
13
+ import postcss from "postcss";
14
+ import valueParser from "postcss-value-parser";
15
+ import { fork } from "node:child_process";
16
+ import { connectOpenedAutomator, queryWechatIdeLogin, resolveProjectAutomatorPort } from "weapp-ide-cli";
17
+ import { createConnection } from "node:net";
18
+ import { miniProgramRuntimePlugin, wevuCompatibilityPlugin } from "@weapp-vite/eslint";
19
+ import tsParser from "@typescript-eslint/parser";
20
+ import { Linter } from "eslint";
21
+ import vueParser from "vue-eslint-parser";
22
+ import { OperationLifecycle, classifyOperationError } from "@weapp-vite/miniprogram-automator";
23
+ //#region src/doctor/report.ts
24
+ function addDiagnostic(report, diagnostic) {
25
+ const fingerprint = createHash("sha256").update(JSON.stringify([
26
+ diagnostic.ruleId,
27
+ diagnostic.target,
28
+ diagnostic.layer,
29
+ diagnostic.location,
30
+ diagnostic.evidence
31
+ ])).digest("hex");
32
+ if (!report.diagnostics.some((item) => item.fingerprint === fingerprint)) report.diagnostics.push({
33
+ ...diagnostic,
34
+ fingerprint
35
+ });
36
+ }
37
+ function finishReport(report) {
38
+ report.diagnostics.sort((a, b) => a.fingerprint.localeCompare(b.fingerprint));
39
+ report.exitCode = report.coverage.some((item) => item.status === "incomplete") ? 2 : report.diagnostics.some((item) => item.severity === "error") ? 1 : 0;
40
+ return report;
41
+ }
42
+ function formatDoctorReport(report, format = "terminal") {
43
+ if (format === "json") return JSON.stringify(report, null, 2);
44
+ if (format === "sarif") return JSON.stringify({
45
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
46
+ version: "2.1.0",
47
+ runs: [{
48
+ tool: { driver: {
49
+ name: "weapp-vite doctor",
50
+ rules: [...new Set(report.diagnostics.map((d) => d.ruleId))].map((id) => ({ id }))
51
+ } },
52
+ invocations: [{ executionSuccessful: report.exitCode !== 2 }],
53
+ properties: {
54
+ coverage: report.coverage,
55
+ exitCode: report.exitCode,
56
+ artifacts: report.artifacts,
57
+ runtime: report.runtime
58
+ },
59
+ results: report.diagnostics.map((d) => ({
60
+ ruleId: d.ruleId,
61
+ level: d.severity,
62
+ message: { text: d.message },
63
+ partialFingerprints: { "doctor/v1": d.fingerprint },
64
+ locations: d.location ? [{ physicalLocation: {
65
+ artifactLocation: { uri: d.location.file.split("/").map(encodeURIComponent).join("/") },
66
+ region: d.location.line ? {
67
+ startLine: d.location.line,
68
+ startColumn: d.location.column ?? 1
69
+ } : void 0
70
+ } }] : [],
71
+ properties: {
72
+ target: d.target,
73
+ layer: d.layer,
74
+ evidence: d.evidence,
75
+ responsibility: d.responsibility,
76
+ suggestion: d.suggestion
77
+ }
78
+ }))
79
+ }]
80
+ }, null, 2);
81
+ return [
82
+ `Doctor: exit ${report.exitCode}`,
83
+ ...report.coverage.map((c) => `[${c.target}] ${c.layer}/${c.check}: ${c.status}${c.reason ? ` — ${c.reason}` : ""}`),
84
+ ...Object.entries(report.runtime).flatMap(([target, evidence]) => evidence.bundle ? [
85
+ `[${target}] framework=${evidence.bundle.frameworkVersion} node=${evidence.bundle.nodeVersion} IDE=${evidence.bundle.versions.ide ?? "unknown"} SDK=${evidence.bundle.versions.sdk ?? "unknown"}`,
86
+ `最后成功阶段:${evidence.bundle.lastSuccessfulStage ?? "unknown"}`,
87
+ `复现:${evidence.bundle.reproduction}`
88
+ ] : []),
89
+ ...report.diagnostics.map((d) => `[${d.severity}] ${d.target} ${d.ruleId} ${d.location?.file ?? ""} ${d.message}\n ${d.suggestion}`)
90
+ ].join("\n");
91
+ }
92
+ //#endregion
93
+ //#region src/doctor/source.ts
94
+ function checkDoctorScripts(report, target, files, layer) {
95
+ const linter = new Linter();
96
+ let incomplete = false;
97
+ const eligible = files.filter((file) => layer === "artifact" ? /\.js$/.test(file.path) : /\.(?:[cm]?[jt]sx?|vue)$/.test(file.path) && !/(?:^|\/)(?:scripts|generated|node_modules)\/|\.web\.|(?:^|\/)node\.|\.(?:test|spec)\.|\.d\.[cm]?ts$/.test(file.path));
98
+ for (const file of eligible) {
99
+ const messages = linter.verify(file.text ?? "", {
100
+ files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"],
101
+ languageOptions: {
102
+ parser: file.path.endsWith(".vue") ? vueParser : tsParser,
103
+ parserOptions: {
104
+ parser: tsParser,
105
+ ecmaVersion: "latest",
106
+ sourceType: "module",
107
+ ecmaFeatures: { jsx: true }
108
+ }
109
+ },
110
+ plugins: {
111
+ "mini-program": miniProgramRuntimePlugin,
112
+ "wevu": wevuCompatibilityPlugin
113
+ },
114
+ rules: {
115
+ "no-eval": "error",
116
+ "no-new-func": "error",
117
+ ...target === "weapp" ? {
118
+ "mini-program/no-unsupported-runtime-api": layer === "artifact" ? "error" : "warn",
119
+ "mini-program/no-implicit-runtime-polyfill": "warn"
120
+ } : {},
121
+ ...layer === "source" ? {
122
+ "wevu/no-unsupported-api": "warn",
123
+ "wevu/no-risky-api": "warn",
124
+ "wevu/no-unsupported-template-feature": "warn"
125
+ } : {}
126
+ }
127
+ }, { filename: file.path });
128
+ for (const message of messages) {
129
+ if (message.fatal) incomplete = true;
130
+ addDiagnostic(report, {
131
+ ruleId: `doctor/${layer}/${message.ruleId ?? "parse"}`,
132
+ layer,
133
+ target,
134
+ severity: message.severity === 2 ? "error" : "warning",
135
+ message: message.message,
136
+ location: {
137
+ file: file.path,
138
+ line: message.line,
139
+ column: message.column
140
+ },
141
+ evidence: {
142
+ expected: "可解析且符合所选执行域的静态契约",
143
+ actual: message.message
144
+ },
145
+ responsibility: {
146
+ owner: "unknown",
147
+ confidence: layer === "source" ? "suspected" : "confirmed"
148
+ },
149
+ suggestion: layer === "source" ? "核对编译转换与兼容层,再检查最终产物;源码风险不等同于宿主失败。" : "定位对应源码及构建插件,保留最小复现并在目标宿主验证。"
150
+ });
151
+ }
152
+ }
153
+ report.coverage.push({
154
+ target,
155
+ layer,
156
+ check: "script-syntax-and-static-risks",
157
+ status: incomplete ? "incomplete" : "complete",
158
+ reason: `检查 ${eligible.length} 个脚本;不推断动态类型或将静态扫描等同于完整运行正确性。`
159
+ });
160
+ if (target !== "weapp") report.coverage.push({
161
+ target,
162
+ layer,
163
+ check: "host-api-baseline",
164
+ status: "incomplete",
165
+ reason: "所选平台尚无可复用的完整 API 基线;未套用微信规则。"
166
+ });
167
+ }
168
+ function incompleteCoverage(report, target, layer, check, reason) {
169
+ report.coverage.push({
170
+ target,
171
+ layer,
172
+ check,
173
+ status: "incomplete",
174
+ reason
175
+ });
176
+ }
177
+ //#endregion
178
+ //#region src/doctor/references.ts
179
+ function checkArtifactReferences(report, target, snapshot, classifier) {
180
+ const files = new Set(snapshot.files.map((file) => file.path));
181
+ const ext = getPlatformOutputExtensions(target);
182
+ const reference = (owner, specifier, script = false) => {
183
+ if (/^[a-z]+:\/\//i.test(specifier) || specifier.includes("{{")) {
184
+ incompleteCoverage(report, target, "artifact", "references", "动态或外部引用不能由本地静态快照验证。");
185
+ return;
186
+ }
187
+ const base = posix.normalize(specifier.startsWith("/") ? specifier.slice(1) : posix.join(posix.dirname(owner), specifier));
188
+ const found = (script ? [
189
+ base,
190
+ `${base}.js`,
191
+ `${base}.json`,
192
+ `${base}/index.js`
193
+ ] : [base]).find((candidate) => files.has(candidate));
194
+ const from = classifyPackage(owner, "main", classifier);
195
+ const to = found && classifyPackage(found, "main", classifier);
196
+ const crosses = to && (from.type === "independent" && from.id !== to.id || to.type !== "main" && from.id !== to.id);
197
+ if (!found || crosses) addDiagnostic(report, {
198
+ ruleId: `doctor/artifact/${crosses ? "package-import" : "missing-import"}`,
199
+ target,
200
+ layer: "artifact",
201
+ severity: "error",
202
+ message: crosses ? "产物引用跨越包边界" : "产物引用指向不存在的文件",
203
+ location: { file: owner },
204
+ evidence: {
205
+ expected: "引用可在所属包及允许的依赖范围内解析",
206
+ actual: specifier
207
+ },
208
+ responsibility: {
209
+ owner: "unknown",
210
+ confidence: "confirmed"
211
+ },
212
+ suggestion: "检查最终产物路径、native npm 输出目录及独立分包归属;不要只修改源码导入规避编译器问题。"
213
+ });
214
+ };
215
+ for (const file of snapshot.files) {
216
+ if (file.text === void 0) continue;
217
+ try {
218
+ if (file.path.endsWith(`.${ext.js}`)) {
219
+ const ast = parseJsLike(file.text);
220
+ traverse(ast, {
221
+ ImportDeclaration(p) {
222
+ reference(file.path, p.node.source.value, true);
223
+ },
224
+ ExportNamedDeclaration(p) {
225
+ if (p.node.source) reference(file.path, p.node.source.value, true);
226
+ },
227
+ ExportAllDeclaration(p) {
228
+ reference(file.path, p.node.source.value, true);
229
+ },
230
+ CallExpression(p) {
231
+ if (!p.get("callee").isIdentifier({ name: "require" }) || p.scope.getBinding("require")) return;
232
+ const arg = p.node.arguments[0];
233
+ if (arg?.type === "StringLiteral") reference(file.path, arg.value, true);
234
+ else incompleteCoverage(report, target, "artifact", "dynamic-import", "动态 require 无法由静态快照解析。");
235
+ }
236
+ });
237
+ } else if (file.path.endsWith(`.${ext.wxml}`)) new Parser({ onopentag(name, attributes) {
238
+ if ([
239
+ "import",
240
+ "include",
241
+ "wxs",
242
+ "sjs",
243
+ "import-sjs"
244
+ ].includes(name) && attributes.src) reference(file.path, attributes.src);
245
+ } }, { xmlMode: true }).end(file.text);
246
+ else if (file.path.endsWith(`.${ext.wxss}`)) postcss.parse(file.text).walkAtRules("import", (rule) => {
247
+ const first = valueParser(rule.params).nodes[0];
248
+ const value = first?.type === "string" ? first.value : void 0;
249
+ if (value) reference(file.path, value);
250
+ else incompleteCoverage(report, target, "artifact", "style-import", "非字符串样式导入需要目标宿主或适配器验证。");
251
+ });
252
+ } catch {
253
+ incompleteCoverage(report, target, "artifact", "reference-parse", "产物无法解析,未将引用图视为完整。");
254
+ }
255
+ }
256
+ report.coverage.push({
257
+ target,
258
+ layer: "artifact",
259
+ check: "static-references",
260
+ status: "complete",
261
+ reason: "覆盖静态脚本、模板和样式引用;动态/外部引用另记 incomplete。"
262
+ });
263
+ }
264
+ //#endregion
265
+ //#region src/doctor/artifact.ts
266
+ function checkDoctorArtifact(report, target, snapshot) {
267
+ const files = new Map(snapshot.files.map((file) => [file.path, file]));
268
+ const ext = getPlatformOutputExtensions(target);
269
+ const fail = (rule, file, expected, actual) => addDiagnostic(report, {
270
+ ruleId: `doctor/artifact/${rule}`,
271
+ target,
272
+ layer: "artifact",
273
+ severity: "error",
274
+ message: `${file}: ${expected}`,
275
+ location: { file },
276
+ evidence: {
277
+ expected,
278
+ actual
279
+ },
280
+ responsibility: {
281
+ owner: "unknown",
282
+ confidence: "confirmed"
283
+ },
284
+ suggestion: "比较来源声明和最终编译产物,确定配置、插件或编译器责任后补充最小回归。"
285
+ });
286
+ const readJson = (file) => {
287
+ try {
288
+ return parseObject(files.get(file)?.text);
289
+ } catch {
290
+ fail("json", file, "存在合法的 JSON 对象", files.has(file) ? "无法解析 JSON 对象" : "文件缺失");
291
+ return;
292
+ }
293
+ };
294
+ const app = readJson("app.json");
295
+ if (!app) {
296
+ incompleteCoverage(report, target, "artifact", "reachable-graph", "app.json 不可读,无法遍历页面图。");
297
+ return;
298
+ }
299
+ if (!files.has(`app.${ext.js}`)) fail("entry-file", `app.${ext.js}`, "应用入口脚本存在", "文件缺失");
300
+ const packages = Array.isArray(app.subPackages ?? app.subpackages) ? app.subPackages ?? app.subpackages : [];
301
+ if ((app.subPackages ?? app.subpackages) !== void 0 && !Array.isArray(app.subPackages ?? app.subpackages)) fail("subpackage", "app.json", "subPackages 为数组", "非法分包声明");
302
+ const subPackageRoots = /* @__PURE__ */ new Set();
303
+ const independentRoots = /* @__PURE__ */ new Set();
304
+ const pages = [];
305
+ const addPages = (value, root = "") => {
306
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
307
+ fail("pages", "app.json", "pages 为字符串数组", "页面列表缺失或类型错误");
308
+ return;
309
+ }
310
+ for (const item of value) pages.push(root ? `${root}/${item}` : item);
311
+ };
312
+ addPages(app.pages);
313
+ for (const value of packages) {
314
+ const pkg = objectValue(value);
315
+ if (!pkg || typeof pkg.root !== "string" || !pkg.root || posix.normalize(pkg.root) !== pkg.root || /^[./]/.test(pkg.root)) {
316
+ fail("subpackage", "app.json", "分包 root 为规范的相对路径", "非法分包声明");
317
+ continue;
318
+ }
319
+ subPackageRoots.add(pkg.root);
320
+ if (pkg.independent === true) independentRoots.add(pkg.root);
321
+ addPages(pkg.pages, pkg.root);
322
+ }
323
+ const classifier = {
324
+ subPackageRoots,
325
+ independentRoots
326
+ };
327
+ const checked = /* @__PURE__ */ new Set();
328
+ const queued = pages.map((base) => ({
329
+ base,
330
+ component: false
331
+ }));
332
+ const resolveReference = (owner, reference) => posix.normalize(reference.startsWith("/") ? reference.slice(1) : posix.join(posix.dirname(owner), reference));
333
+ const checkComponents = (owner, config) => {
334
+ const components = objectValue(config.usingComponents);
335
+ if (config.usingComponents !== void 0 && !components) fail("components", owner, "usingComponents 为路径映射", "非法组件映射");
336
+ for (const [name, value] of Object.entries(components ?? {})) {
337
+ if (typeof value !== "string") {
338
+ fail("components", owner, "组件引用为字符串", name);
339
+ continue;
340
+ }
341
+ if (/^[a-z]+:\/\//i.test(value)) {
342
+ incompleteCoverage(report, target, "artifact", "external-component", "外部插件组件不在本地产物快照中。");
343
+ continue;
344
+ }
345
+ const base = resolveReference(owner, value);
346
+ const from = classifyPackage(owner, "main", classifier);
347
+ const to = classifyPackage(base, "main", classifier);
348
+ if (from.type === "independent" && from.id !== to.id || to.type !== "main" && from.id !== to.id) fail("package-boundary", owner, "组件引用不跨越独立包或其他分包边界", base);
349
+ queued.push({
350
+ base,
351
+ component: true
352
+ });
353
+ }
354
+ };
355
+ checkComponents("app.json", app);
356
+ while (queued.length) {
357
+ const { base, component } = queued.shift();
358
+ if (base.startsWith("../") || base.startsWith("/") || posix.normalize(base) !== base) {
359
+ fail("path", "app.json", "引用位于产物根目录内且已规范化", base);
360
+ continue;
361
+ }
362
+ if (checked.has(base)) continue;
363
+ checked.add(base);
364
+ for (const extension of [
365
+ ext.js,
366
+ ext.json,
367
+ ext.wxml
368
+ ]) if (!files.has(`${base}.${extension}`)) fail("entry-file", `${base}.${extension}`, "页面/组件的宿主必需文件存在", "文件缺失");
369
+ const config = readJson(`${base}.${ext.json}`);
370
+ if (config) {
371
+ if (component && config.component !== true) fail("component-flag", `${base}.${ext.json}`, "被引用的组件声明 component: true", "未声明组件");
372
+ checkComponents(`${base}.${ext.json}`, config);
373
+ }
374
+ }
375
+ if (typeof app.workers === "string" && !snapshot.files.some((file) => file.path.startsWith(`${app.workers}/`) && file.path.endsWith(ext.js))) fail("worker-root", "app.json", "workers 目录内有脚本产物", app.workers);
376
+ const packageMap = /* @__PURE__ */ new Map();
377
+ for (const file of snapshot.files) {
378
+ const pkg = classifyPackage(file.path, "main", classifier);
379
+ const current = packageMap.get(pkg.id) ?? {
380
+ ...pkg,
381
+ files: []
382
+ };
383
+ current.files.push({
384
+ file: file.path,
385
+ size: file.size,
386
+ type: file.path.endsWith(ext.js) ? "chunk" : "asset",
387
+ from: "main"
388
+ });
389
+ packageMap.set(pkg.id, current);
390
+ }
391
+ if (snapshot.budgets) {
392
+ for (const item of createAnalyzeBudgetCheck({
393
+ packages: [...packageMap.values()],
394
+ metadata: {
395
+ generatedAt: snapshot.capturedAt,
396
+ budgets: snapshot.budgets,
397
+ history: {
398
+ enabled: false,
399
+ dir: "",
400
+ limit: 0
401
+ }
402
+ }
403
+ })) if (item.status === "exceeded") fail("budget", item.id, `包体不超过 ${item.limitBytes} 字节`, `${item.currentBytes} 字节`);
404
+ } else incompleteCoverage(report, target, "artifact", "budgets", "复用目录未提供编译器实际预算;不套用其他平台默认预算。");
405
+ report.coverage.push({
406
+ target,
407
+ layer: "artifact",
408
+ check: "pages-components-packages-workers",
409
+ status: "complete"
410
+ });
411
+ checkArtifactReferences(report, target, snapshot, classifier);
412
+ checkDoctorScripts(report, target, snapshot.files, "artifact");
413
+ }
414
+ //#endregion
415
+ //#region src/doctor/build.ts
416
+ /** 将显式构建隔离到子进程,避免全局编译上下文和插件日志污染并发 API/JSON 输出。 */
417
+ async function buildDoctorSnapshot(options, target) {
418
+ const worker = path.join(path.dirname(fileURLToPath(import.meta.resolve("weapp-vite/package.json"))), "dist/doctor-worker.mjs");
419
+ return new Promise((resolve, reject) => {
420
+ const child = fork(worker, [], {
421
+ cwd: options.cwd,
422
+ stdio: [
423
+ "ignore",
424
+ "pipe",
425
+ "pipe",
426
+ "ipc"
427
+ ],
428
+ execArgv: []
429
+ });
430
+ let result;
431
+ const timer = setTimeout(() => {
432
+ child.kill();
433
+ reject(/* @__PURE__ */ new Error("Doctor 构建超时"));
434
+ }, 18e4);
435
+ child.stdout?.on("data", (chunk) => options.onBuildLog?.(String(chunk)));
436
+ child.stderr?.on("data", (chunk) => options.onBuildLog?.(String(chunk)));
437
+ child.on("message", (message) => {
438
+ result = message.snapshot;
439
+ });
440
+ child.on("error", (error) => {
441
+ clearTimeout(timer);
442
+ reject(error);
443
+ });
444
+ child.on("close", (code) => {
445
+ clearTimeout(timer);
446
+ if (code === 0 && result) resolve(result);
447
+ else reject(/* @__PURE__ */ new Error("Doctor 构建未完成"));
448
+ });
449
+ child.send({
450
+ cwd: options.cwd,
451
+ configFile: options.configFile,
452
+ target
453
+ });
454
+ });
455
+ }
456
+ //#endregion
457
+ //#region src/doctor/runtimeProbes.ts
458
+ /** 仅检查文件及平台执行条件,不执行 CLI 或项目配置。 */
459
+ async function inspectDoctorCli(cliPath) {
460
+ try {
461
+ if (!(await stat(cliPath)).isFile()) return false;
462
+ if (process.platform === "win32" && ![
463
+ ".exe",
464
+ ".cmd",
465
+ ".bat",
466
+ ".com"
467
+ ].includes(path.extname(cliPath).toLowerCase())) return false;
468
+ await access(cliPath, process.platform === "win32" ? constants.F_OK : constants.X_OK);
469
+ return true;
470
+ } catch {
471
+ return false;
472
+ }
473
+ }
474
+ /** TCP 可达只证明监听存在,不证明宿主身份;仅销毁本次创建的 socket。 */
475
+ async function inspectDoctorListener(port, signal) {
476
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return "invalid-port";
477
+ signal?.throwIfAborted();
478
+ return new Promise((resolve) => {
479
+ let abort;
480
+ const socket = createConnection({
481
+ host: "127.0.0.1",
482
+ port
483
+ });
484
+ let settled = false;
485
+ function finish(result) {
486
+ if (!settled) {
487
+ settled = true;
488
+ signal?.removeEventListener("abort", abort);
489
+ socket.destroy();
490
+ resolve(result);
491
+ }
492
+ }
493
+ abort = () => finish("timeout");
494
+ signal?.addEventListener("abort", abort, { once: true });
495
+ socket.setTimeout(1e3, () => finish("timeout"));
496
+ socket.once("connect", () => finish("listening"));
497
+ socket.once("error", (error) => finish(error.code === "ECONNREFUSED" ? "not-listening" : "unavailable"));
498
+ });
499
+ }
500
+ /** 宿主版本只保留数值版本,不透传任意 Tool.getInfo 字段。 */
501
+ function readDoctorHostVersions(info) {
502
+ if (!info || typeof info !== "object") return {};
503
+ const version = (value) => typeof value === "string" && value.length <= 64 && /^\d+(?:\.\d+){1,3}$/.test(value) ? value : void 0;
504
+ return {
505
+ ide: version("version" in info ? info.version : void 0),
506
+ sdk: version("SDKVersion" in info ? info.SDKVersion : void 0)
507
+ };
508
+ }
509
+ /** 页面查询参数可能包含业务凭据,只保留相对逻辑路由。 */
510
+ function readDoctorRoute(value) {
511
+ if (typeof value !== "string") return;
512
+ const route = value.split(/[?#]/, 1)[0];
513
+ if (!route || route.startsWith("/") || route.length > 256 || route.includes("..") || /[\\:\s]/.test(route)) return;
514
+ return route;
515
+ }
516
+ //#endregion
517
+ //#region src/doctor/runtime.ts
518
+ const stages = [
519
+ "cli-executable",
520
+ "service-listener",
521
+ "login-query",
522
+ "login-state",
523
+ "project-connection",
524
+ "tool-info",
525
+ "host-version",
526
+ "sdk-version",
527
+ "current-page",
528
+ "session-release"
529
+ ];
530
+ /** 每个探针保留局部上限,并受同一个总 deadline 约束。 */
531
+ async function probeStep(scope, stage, cap, run, disposeLate) {
532
+ const phase = new OperationLifecycle(scope.remainingMs(cap), stage, scope.signal);
533
+ return await scope.step(() => phase.run(() => phase.step(() => run(phase.signal, phase.remainingMs()), {
534
+ stage,
535
+ disposeLate
536
+ })), { stage });
537
+ }
538
+ /** 默认只连接已有项目;原生登录查询单独显式授权,不改变路由或业务状态。 */
539
+ async function probeDoctorRuntime(cwd, target, port, options = {}) {
540
+ const scope = new OperationLifecycle(options.timeout ?? 1e4, "Doctor runtime", options.signal);
541
+ let finalized = false;
542
+ const facts = stages.map((stage) => ({
543
+ stage,
544
+ status: "not-run",
545
+ code: "not-requested"
546
+ }));
547
+ const events = [];
548
+ const provider = target === "weapp" ? "devtools" : "unsupported";
549
+ const bundle = {
550
+ frameworkVersion: version,
551
+ nodeVersion: process.version,
552
+ versions: {},
553
+ configuration: {
554
+ target,
555
+ provider,
556
+ explicitCli: !!options.cliPath,
557
+ explicitAutomatorPort: port !== void 0,
558
+ explicitServicePort: options.servicePort !== void 0,
559
+ loginRequested: options.login === true
560
+ },
561
+ lastSuccessfulStage: void 0,
562
+ events,
563
+ operation: scope.diagnostics,
564
+ reproduction: `wv doctor --runtime --platform ${target} --format json${port !== void 0 ? " --runtime-port <automator-port>" : ""}${options.cliPath ? " --runtime-cli <selected-cli>" : ""}${options.servicePort !== void 0 ? " --runtime-service-port <service-port>" : ""}${options.login ? " --runtime-login" : ""}`
565
+ };
566
+ const evidence = {
567
+ host: "unknown",
568
+ route: "",
569
+ provider,
570
+ checks: [],
571
+ facts,
572
+ bundle,
573
+ complete: false
574
+ };
575
+ const record = (stage, status, code) => {
576
+ if (finalized) return;
577
+ const fact = {
578
+ stage,
579
+ status,
580
+ code
581
+ };
582
+ facts[stages.indexOf(stage)] = fact;
583
+ events.push(fact);
584
+ if (status === "passed" && stage !== "session-release") bundle.lastSuccessfulStage = stage;
585
+ };
586
+ if (target !== "weapp") {
587
+ record("project-connection", "unknown", "unsupported-host");
588
+ return evidence;
589
+ }
590
+ if (port !== void 0 && (!Number.isInteger(port) || port < 1 || port > 65535)) {
591
+ record("project-connection", "failed", "invalid-port");
592
+ return evidence;
593
+ }
594
+ try {
595
+ return await scope.run(async () => {
596
+ scope.attempt();
597
+ let executable = false;
598
+ if (options.cliPath) {
599
+ executable = await probeStep(scope, "cli-executable", 1e3, () => inspectDoctorCli(options.cliPath));
600
+ record("cli-executable", executable ? "passed" : "failed", executable ? "executable" : "not-executable");
601
+ }
602
+ if (options.servicePort !== void 0) {
603
+ const result = await probeStep(scope, "service-listener", 1e3, (signal) => inspectDoctorListener(options.servicePort, signal));
604
+ record("service-listener", result === "listening" ? "passed" : result === "not-listening" || result === "invalid-port" ? "failed" : "unknown", result);
605
+ }
606
+ if (options.login) {
607
+ if (!executable || !options.cliPath) {
608
+ record("login-query", "not-run", "explicit-executable-required");
609
+ record("login-state", "unknown", "query-not-run");
610
+ } else {
611
+ const result = await probeStep(scope, "login-query", 3e3, (signal, timeout) => queryWechatIdeLogin(options.cliPath, {
612
+ timeout,
613
+ signal
614
+ }));
615
+ record("login-query", result.status === "success" ? "passed" : "unknown", result.status === "success" ? "response-received" : result.reason);
616
+ record("login-state", result.status === "success" ? result.login ? "passed" : "failed" : "unknown", result.status === "success" ? result.login ? "logged-in" : "logged-out" : "query-incomplete");
617
+ }
618
+ }
619
+ let session;
620
+ try {
621
+ session = await probeStep(scope, "project-connection", 3e3, async (signal, timeout) => await connectOpenedAutomator({
622
+ projectPath: cwd,
623
+ cliPath: options.cliPath,
624
+ port: port ?? resolveProjectAutomatorPort(cwd),
625
+ timeout,
626
+ signal
627
+ }), (session) => session.disconnect());
628
+ record("project-connection", "passed", "connected");
629
+ } catch (error) {
630
+ record("project-connection", "failed", classifyOperationError(error) === "timeout" ? "timeout" : "connection-failed");
631
+ for (const stage of [
632
+ "tool-info",
633
+ "host-version",
634
+ "sdk-version",
635
+ "current-page"
636
+ ]) record(stage, "not-run", "connection-unavailable");
637
+ return evidence;
638
+ }
639
+ let released = false;
640
+ const release = () => {
641
+ if (released) return;
642
+ released = true;
643
+ try {
644
+ session.disconnect();
645
+ record("session-release", "passed", "disconnected");
646
+ } catch {
647
+ record("session-release", "failed", "disconnect-failed");
648
+ }
649
+ };
650
+ scope.own(release, "doctor-websocket");
651
+ const checks = [];
652
+ const read = async (stage, check, operation, accept) => {
653
+ checks.push(check);
654
+ try {
655
+ const valid = accept(await probeStep(scope, stage, 5e3, operation));
656
+ record(stage, valid ? "passed" : "failed", valid ? "snapshot-read" : "invalid-response");
657
+ } catch (error) {
658
+ record(stage, "failed", classifyOperationError(error) === "timeout" ? "timeout" : "rpc-failed");
659
+ }
660
+ };
661
+ try {
662
+ await Promise.all([read("tool-info", "Tool.getInfo", () => session.toolInfo(), (info) => {
663
+ if (!info || typeof info !== "object") return false;
664
+ evidence.host = "wechat-devtools";
665
+ bundle.versions = readDoctorHostVersions(info);
666
+ record("host-version", bundle.versions.ide ? "passed" : "unknown", bundle.versions.ide ? "version-read" : "version-unavailable");
667
+ record("sdk-version", bundle.versions.sdk ? "passed" : "unknown", bundle.versions.sdk ? "version-read" : "version-unavailable");
668
+ return true;
669
+ }), read("current-page", "App.getCurrentPage", () => session.currentPage(), (page) => {
670
+ const route = readDoctorRoute(page?.path);
671
+ if (!route) return false;
672
+ evidence.route = route;
673
+ return true;
674
+ })]);
675
+ } finally {
676
+ release();
677
+ }
678
+ for (const stage of ["host-version", "sdk-version"]) if (facts.find((fact) => fact.stage === stage)?.status === "not-run") record(stage, "unknown", "tool-info-unavailable");
679
+ evidence.checks = checks;
680
+ evidence.complete = facts.every((fact) => fact.status === "passed" || fact.status === "not-run");
681
+ return evidence;
682
+ });
683
+ } catch (error) {
684
+ record(stages.includes(scope.diagnostics.stage) ? scope.diagnostics.stage : "project-connection", "failed", classifyOperationError(error));
685
+ evidence.complete = false;
686
+ return evidence;
687
+ } finally {
688
+ bundle.operation = scope.diagnostics;
689
+ finalized = true;
690
+ }
691
+ }
692
+ //#endregion
693
+ //#region src/doctor/runtimeReport.ts
694
+ const suggestions = {
695
+ "cli-executable": "确认所选 CLI 是可执行文件;不会自动回退到其他安装。",
696
+ "service-listener": "核对所选安装的服务端口与监听状态;TCP 可达不能证明监听者身份。",
697
+ "login-query": "确认所选 CLI 可响应查询;未知结果不能推断未登录。",
698
+ "login-state": "仅在原生查询明确未登录时完成正常登录;其他情况先排查查询链路。",
699
+ "project-connection": "确认目标项目已有可连接的 automator 会话及正确端口,不清理其他项目。",
700
+ "tool-info": "确认目标项目工具协议可用;缺失版本不能归因于编译器。",
701
+ "host-version": "从实际连接宿主取得 IDE 版本,不用 CLI 路径或历史记录代替。",
702
+ "sdk-version": "从实际宿主取得基础库版本,不将项目配置值视为实际运行版本。",
703
+ "current-page": "确认目标项目已打开有效页面;页面快照不代替应用功能 E2E。",
704
+ "session-release": "保留环境现场并检查本次 websocket 释放,不终止共享 IDE。"
705
+ };
706
+ /** 部分失败保留已有事实,按稳定阶段诊断输出;不推断平台或框架责任。 */
707
+ function appendDoctorRuntimeEvidence(report, target, evidence) {
708
+ report.runtime[target] = evidence;
709
+ const incomplete = evidence.complete === false || evidence.facts?.some((fact) => fact.status === "failed" || fact.status === "unknown");
710
+ report.coverage.push({
711
+ target,
712
+ layer: "runtime",
713
+ check: "connected-page-snapshot",
714
+ status: incomplete ? "incomplete" : "complete",
715
+ reason: incomplete ? "请求的宿主探针没有全部完成,已保留部分事实。" : "仅证明请求的工具信息和当前页面可读取;不代表完整应用功能验收。"
716
+ });
717
+ for (const fact of evidence.facts ?? []) {
718
+ report.coverage.push({
719
+ target,
720
+ layer: "runtime",
721
+ check: fact.stage,
722
+ status: fact.status === "passed" ? "complete" : fact.status === "not-run" && fact.code === "not-requested" ? "not-requested" : "incomplete",
723
+ reason: fact.code
724
+ });
725
+ if (fact.status === "failed" || fact.status === "unknown") addDiagnostic(report, {
726
+ ruleId: `doctor/runtime/${fact.stage}`,
727
+ target,
728
+ layer: "runtime",
729
+ severity: "warning",
730
+ message: `宿主探针 ${fact.stage}:${fact.code}`,
731
+ evidence: {
732
+ expected: "取得该阶段明确事实",
733
+ actual: fact.code
734
+ },
735
+ responsibility: {
736
+ owner: "unknown",
737
+ confidence: "suspected"
738
+ },
739
+ suggestion: suggestions[fact.stage]
740
+ });
741
+ }
742
+ }
743
+ //#endregion
744
+ //#region src/doctor/index.ts
745
+ /** 分层收集事实;静态路径永不调用配置加载器、构建或宿主连接。 */
746
+ async function runDoctor(options = {}, adapters = {}) {
747
+ const cwd = path.resolve(options.cwd ?? process.cwd());
748
+ const report = {
749
+ schemaVersion: 1,
750
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
751
+ targets: [...new Set(options.targets ?? ["weapp"])],
752
+ coverage: [],
753
+ diagnostics: [],
754
+ artifacts: {},
755
+ runtime: {},
756
+ exitCode: 0
757
+ };
758
+ for (const requested of report.targets) {
759
+ const target = resolveMiniPlatform(requested);
760
+ if (!target) {
761
+ incompleteCoverage(report, requested, "project", "target", "当前编译器没有注册该目标。");
762
+ continue;
763
+ }
764
+ const configName = getProjectPlatformOptions(target).projectConfigFileName;
765
+ for (const file of ["package.json", configName]) try {
766
+ parseObject(await readFile(path.join(cwd, file), "utf8"));
767
+ report.coverage.push({
768
+ target,
769
+ layer: "project",
770
+ check: file,
771
+ status: "complete",
772
+ reason: "只读 JSON 结构;未执行项目配置或插件。"
773
+ });
774
+ } catch {
775
+ incompleteCoverage(report, target, "project", file, "文件缺失、不可读或不是合法 JSON 对象。");
776
+ }
777
+ let sourceRoot = options.source;
778
+ if (options.build && options.artifact) incompleteCoverage(report, target, "artifact", "selection", "build 与 artifact 不能同时选择。");
779
+ else if (options.build || options.artifact) try {
780
+ const snapshot = options.build ? await (adapters.build ?? buildDoctorSnapshot)({
781
+ ...options,
782
+ cwd
783
+ }, target) : {
784
+ files: await readDoctorFiles(path.resolve(cwd, options.artifact)),
785
+ origin: "existing",
786
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
787
+ freshness: "unverified"
788
+ };
789
+ report.artifacts[target] = {
790
+ ...snapshot,
791
+ files: snapshot.files.map(({ text: _text, ...file }) => file)
792
+ };
793
+ sourceRoot ??= snapshot.sourceRoot;
794
+ for (const limitation of snapshot.limitations ?? []) incompleteCoverage(report, target, "artifact", "compiler-scope", limitation);
795
+ checkDoctorArtifact(report, target, snapshot);
796
+ } catch {
797
+ incompleteCoverage(report, target, "artifact", "collection", "构建或最终产物快照失败;没有采用旧产物作为本轮通过证据。");
798
+ }
799
+ else report.coverage.push({
800
+ target,
801
+ layer: "artifact",
802
+ check: "collection",
803
+ status: "not-requested"
804
+ });
805
+ try {
806
+ checkDoctorScripts(report, target, await readDoctorFiles(path.resolve(cwd, sourceRoot ?? "src"), true), "source");
807
+ } catch {
808
+ incompleteCoverage(report, target, "source", "scan", "源码目录无法完整读取;可用 source 显式指定目录。");
809
+ }
810
+ if (options.runtime) try {
811
+ const probe = adapters.runtime ?? probeDoctorRuntime;
812
+ appendDoctorRuntimeEvidence(report, target, options.runtimeCliPath !== void 0 || options.runtimeLogin !== void 0 || options.runtimeServicePort !== void 0 ? await probe(cwd, target, options.runtimePort, {
813
+ cliPath: options.runtimeCliPath,
814
+ login: options.runtimeLogin,
815
+ servicePort: options.runtimeServicePort
816
+ }) : await probe(cwd, target, options.runtimePort));
817
+ } catch {
818
+ incompleteCoverage(report, target, "runtime", "connected-page-snapshot", "没有可连接的受支持宿主或探针执行失败;请先打开目标项目并启用服务端口。");
819
+ }
820
+ else report.coverage.push({
821
+ target,
822
+ layer: "runtime",
823
+ check: "connected-page-snapshot",
824
+ status: "not-requested"
825
+ });
826
+ }
827
+ if (!report.targets.length) incompleteCoverage(report, "unknown", "project", "target", "至少需要一个注册目标。");
828
+ if (!options.runtime && (options.runtimeCliPath !== void 0 || options.runtimeLogin || options.runtimeServicePort !== void 0)) incompleteCoverage(report, "all", "runtime", "selection", "宿主检查选项需要显式 runtime;本轮未执行宿主操作。");
829
+ if (options.build && options.artifact) addDiagnostic(report, {
830
+ ruleId: "doctor/options",
831
+ target: "all",
832
+ layer: "project",
833
+ severity: "error",
834
+ message: "build 与 artifact 不能同时选择",
835
+ evidence: {
836
+ expected: "一个产物来源",
837
+ actual: "两个产物来源"
838
+ },
839
+ responsibility: {
840
+ owner: "project",
841
+ confidence: "confirmed"
842
+ },
843
+ suggestion: "选择重新构建或复用目录。"
844
+ });
845
+ return finishReport(report);
846
+ }
847
+ //#endregion
848
+ export { formatDoctorReport, runDoctor };