@agile-team/wl-skills-ui 1.7.0 → 1.7.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ All notable changes to **@agile-team/wl-skills-ui** will be documented in this f
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [1.7.1] - 2026-05-12
8
+
9
+ ### Added
10
+
11
+ - 新增 Vite 插件 `@agile-team/wl-skills-ui/vite`:消费方在 `vite.config.ts` 加一行 `wlSkillsCheck()` 即可在每次 `dev/build` 启动期自动校验 vendor 版本配对,偏离推荐组合时彩色打印警告与一键修复片段(`enforce: 'warn' | 'error' | 'silent'`)。
12
+ - 新增 `npx wl-ui doctor --print-overrides` 子命令:检测到偏离时直接输出 pnpm/npm/yarn `overrides` JSON 片段,复制即可修复。
13
+ - `skills/_meta/_compat/loader.mjs` 抽出共享 compat 加载器,统一 `evaluateVendor` / `buildOverridesSnippet` 语义,scanner、MCP、Vite、CLI 单源共用。
14
+ - `vendors.json` 的 `compat` 升级为结构化 schema(`peers / gatingPeer / conflictsWith / domAssumptions`),同时保留旧平铺字段兜底;未来新增 vendor 配对无需改读取方代码。
15
+
16
+ ### Changed
17
+
18
+ - scanner `I005` 改为遍历全部声明 `compat` 的 vendor,输出按 vendor 拆分的子检查项 `I005:<id>`,更易定位。
19
+ - MCP `wl_ui_detect_skin` 返回结构升级:`vendors[].verdict`、`fixSnippet`、`summary` 统一暴露,AI 一次拿全多 vendor 评估结果。
20
+ - `package.json` `files` 字段加入 `runtime/vite`,确保 Vite 插件随包发布。
21
+
7
22
  ## [1.7.0] - 2026-05-12
8
23
 
9
24
  ### Added
package/README.md CHANGED
@@ -201,13 +201,27 @@ yarn add @agile-team/wl-skills-ui
201
201
  | `@jhlc/jh-ui` | **`3.1.0`** | SCSS 皮肤包,`.com-text` label 包裹 + `.has-colon` 冒号注入 |
202
202
  | `@agile-team/wl-skills-ui` | `^1.7.0` | 已对齐上述组合的 DOM 假设 |
203
203
 
204
- 新接入项目可执行 `npx wl-ui check --project .`(看 `I005`)或 MCP 工具 `wl_ui_detect_skin` 自动判断当前组合是否命中推荐。完整版本-项目实测表见 `docs/compat-matrix.md`。
204
+ 三种识别方式,任选其一:
205
+
206
+ - **启动期自动**:`vite.config.ts` 加一行 `import { wlSkillsCheck } from '@agile-team/wl-skills-ui/vite'; export default defineConfig({ plugins: [wlSkillsCheck()] })`,每次 `dev/build` 偏离推荐组合时彩色打印警告(支持 `enforce: 'error'` 阻断)。
207
+ - **手动 CLI**:`npx wl-ui check --project .` 看 `I005:<vendor>`;偏离时执行 `npx wl-ui doctor --print-overrides` 拿到可直接复制的 `pnpm.overrides` 修复片段。
208
+ - **AI 协作**:MCP 工具 `wl_ui_detect_skin` 一次返回多 vendor 评估结果(`vendors[].verdict / fixSnippet / summary`)。
209
+
210
+ 完整版本-项目实测表见 `docs/compat-matrix.md`。
205
211
 
206
212
  ---
207
213
 
208
214
  ## 版本亮点
209
215
 
210
- 当前 v1.7.0 版本聚焦“**项目集群依赖配对单一事实源 + AI/Skill 强约束识别**”:
216
+ 当前 v1.7.1 版本在 v1.7.0「单一事实源」基础上,把"识别"升级为"启动期自动 + 一键修复":
217
+
218
+ - 新增 Vite 插件 `@agile-team/wl-skills-ui/vite`:消费方一行配置即可在每次启动 dev/build 时自动跑版本配对校验,偏离时彩色提示 + 修复片段
219
+ - 新增 `npx wl-ui doctor --print-overrides`:检测到偏离直接输出 pnpm/npm/yarn overrides JSON,复制粘贴即可修复
220
+ - `vendors.json` `compat` 升级为 `peers / gatingPeer / conflictsWith / domAssumptions` 结构化 schema,未来新增 vendor 配对零代码改动
221
+ - scanner `I005` 拆为 `I005:<vendor>`,MCP `wl_ui_detect_skin` 一次返回多 vendor 结果
222
+ - 抽出 `skills/_meta/_compat/loader.mjs` 共享加载器,scanner/MCP/Vite/CLI 单源共用
223
+
224
+ 历史亮点(v1.7.0 起):
211
225
 
212
226
  - 新增 `docs/compat-matrix.md` 作为项目集群推荐版本与 wl-skills-ui 的适配矩阵单一事实源;`skills/_meta/_compat/vendors.json` 在 `jh.compat` 字段钉死 `element-plus@2.2.6-prod.3` + `@jhlc/jh-ui@3.1.0`
213
227
  - scanner 接入完整性新增 `I005`:基于 `vendors.json` 校验消费方 `package.json` 是否锚定推荐组合,偏离时给出明确建议
package/bin/wl-ui.js CHANGED
@@ -207,9 +207,16 @@ if (subcommand === "clean") {
207
207
  if (subcommand === "doctor") {
208
208
  const { values } = parseArgs({
209
209
  args: rawArgs,
210
- options: { project: { type: "string", default: "." } },
210
+ options: {
211
+ project: { type: "string", default: "." },
212
+ "print-overrides": { type: "boolean", default: false },
213
+ },
211
214
  strict: false,
212
215
  });
216
+ if (values["print-overrides"]) {
217
+ await printOverrides(resolve(values.project));
218
+ process.exit(0);
219
+ }
213
220
  runDoctor(resolve(values.project));
214
221
  process.exit(0);
215
222
  }
@@ -895,6 +902,40 @@ function runClean(projectRoot, dryRun) {
895
902
  );
896
903
  }
897
904
 
905
+ async function printOverrides(projectRoot) {
906
+ const pkgPath = join(projectRoot, "package.json");
907
+ if (!existsSync(pkgPath)) {
908
+ console.error(`[wl-ui doctor] 未找到 ${pkgPath}`);
909
+ process.exit(1);
910
+ }
911
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
912
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
913
+ const loader = await import("../skills/_meta/_compat/loader.mjs");
914
+ const vendors = loader.listCompatVendors();
915
+ const evaluations = vendors
916
+ .map((c) => loader.evaluateVendor(c, deps))
917
+ .filter((e) => e.verdict !== "not-applicable");
918
+ if (evaluations.length === 0) {
919
+ console.log(
920
+ "[wl-ui doctor] 当前项目未命中任何 vendor 适配矩阵,无需 overrides",
921
+ );
922
+ return;
923
+ }
924
+ const snippet = loader.buildOverridesSnippet(evaluations);
925
+ if (!snippet) {
926
+ console.log("[wl-ui doctor] 当前项目所有 vendor 配对已命中推荐组合 ✓");
927
+ return;
928
+ }
929
+ console.log(
930
+ "\n[wl-ui doctor --print-overrides] 检测到 vendor 版本偏离,复制以下片段到 package.json:\n",
931
+ );
932
+ console.log("// pnpm");
933
+ console.log(JSON.stringify(snippet.pnpm, null, 2));
934
+ console.log("\n// npm / yarn");
935
+ console.log(JSON.stringify(snippet.npmYarn, null, 2));
936
+ console.log("\n复制后执行:pnpm install(或对应包管理器的 install 命令)\n");
937
+ }
938
+
898
939
  function runDoctor(projectRoot) {
899
940
  const pkgPath = join(projectRoot, "package.json");
900
941
  let pkg = null;
@@ -973,8 +1014,9 @@ wl-ui — @agile-team/wl-skills-ui 统一 CLI v${PKG.version}
973
1014
  对比已安装文件与 manifest
974
1015
  wl-ui clean [--project <path>] [--dry-run]
975
1016
  清理 wl-skills-ui 安装文件
976
- wl-ui doctor [--project <path>]
977
- 检查安装状态 / MCP / 桥接 / 规范插件
1017
+ wl-ui doctor [--project <path>] [--print-overrides]
1018
+ 检查安装状态 / MCP / 桥接 / 规范插件;
1019
+ --print-overrides 时输出 vendor 版本偏离的 pnpm/npm/yarn overrides 修复片段
978
1020
  wl-ui prompts
979
1021
  打印 AI 触发提示词
980
1022
 
@@ -41,7 +41,28 @@
41
41
 
42
42
  ## 维护流程
43
43
 
44
- - 推荐版本字段统一在 `skills/_meta/_compat/vendors.json` 的 `vendors[id=jh].compat` 字段。
44
+ - 推荐版本字段统一在 `skills/_meta/_compat/vendors.json` 的 `vendors[id=jh].compat`(结构化 schema:`peers / gatingPeer / conflictsWith / domAssumptions`)。
45
+ - 共享加载器 `skills/_meta/_compat/loader.mjs` 暴露 `listCompatVendors / evaluateVendor / buildOverridesSnippet`,scanner、MCP、Vite 插件、CLI 单源共用。
45
46
  - 本文档由 `scripts/check-docs.mjs` 校验:版本号必须与 `vendors.json` 一致,避免漂移。
46
- - scanner `I005` 接入完整性检查会按本表校验消费方项目是否使用推荐组合。
47
+ - scanner `I005:<vendor>` 接入完整性检查会按本表校验消费方项目是否使用推荐组合。
47
48
  - MCP 工具 `wl_ui_detect_skin` 直接读取消费方 `package.json` 给 AI 返回结构化结果。
49
+ - Vite 插件 `@agile-team/wl-skills-ui/vite` 在启动期自动校验,无需手动调用。
50
+ - `npx wl-ui doctor --print-overrides` 在检测到偏离时输出可直接复制的 pnpm/npm/yarn `overrides` 片段。
51
+
52
+ ## 启动期自动校验(推荐)
53
+
54
+ ```ts
55
+ // vite.config.ts
56
+ import { defineConfig } from 'vite';
57
+ import { wlSkillsCheck } from '@agile-team/wl-skills-ui/vite';
58
+
59
+ export default defineConfig({
60
+ plugins: [
61
+ wlSkillsCheck({
62
+ // enforce: 'warn' | 'error' | 'silent' 默认 'warn'
63
+ // includeVendors: ['jh'] 只校验指定 vendor
64
+ // verbose: true 额外打印 match 项
65
+ }),
66
+ ],
67
+ });
68
+ ```
package/mcp/server.js CHANGED
@@ -128,6 +128,60 @@ function projectRoot(args = {}) {
128
128
  return resolve(args.project || process.env.WL_PROJECT_ROOT || process.cwd());
129
129
  }
130
130
 
131
+ async function detectSkin(args = {}) {
132
+ const root = projectRoot(args);
133
+ const pkgPath = join(root, "package.json");
134
+ const fs = require("node:fs");
135
+ if (!fs.existsSync(pkgPath)) {
136
+ return { ok: false, reason: `未找到 ${pkgPath}` };
137
+ }
138
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
139
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
140
+ const loader = await import("../skills/_meta/_compat/loader.mjs");
141
+ const vendors = loader.listCompatVendors();
142
+ const evaluations = vendors.map((c) => ({
143
+ compat: c,
144
+ evaluation: loader.evaluateVendor(c, deps),
145
+ }));
146
+ const applicable = evaluations.filter(
147
+ (e) => e.evaluation.verdict !== "not-applicable",
148
+ );
149
+ const overrides = loader.buildOverridesSnippet(
150
+ applicable.map((e) => e.evaluation),
151
+ );
152
+ return {
153
+ ok: true,
154
+ project: pkg.name,
155
+ vendors: evaluations.map(({ compat, evaluation }) => ({
156
+ vendorId: compat.vendorId,
157
+ vendorLabel: compat.vendorLabel,
158
+ gatingPeer: compat.gatingPeer,
159
+ verdict: evaluation.verdict,
160
+ peers: evaluation.peers,
161
+ conflictsWith: compat.conflictsWith,
162
+ domAssumptions: compat.domAssumptions,
163
+ note: compat.note,
164
+ })),
165
+ summary:
166
+ applicable.length === 0
167
+ ? "no-applicable-vendor"
168
+ : applicable.every((e) => e.evaluation.verdict === "match")
169
+ ? "all-match"
170
+ : "has-mismatch",
171
+ fixSnippet: overrides,
172
+ recommendedScss: applicable.some(
173
+ ({ compat }) => compat.vendorId === "jh" && deps["@jhlc/jh-ui"],
174
+ )
175
+ ? [
176
+ "styles/vendors/_jh-ui.scss",
177
+ "styles/vendors/_jh-tree.scss",
178
+ "styles/vendors/_jh-pagination.scss",
179
+ "styles/vendors/_jh-drag-col.scss",
180
+ ]
181
+ : ["styles/vendors/_base-components.scss"],
182
+ };
183
+ }
184
+
131
185
  function runScanner(command, args = {}) {
132
186
  const root = projectRoot(args);
133
187
  const scanner = join(PKG_ROOT, "scanner", "index.mjs");
@@ -349,10 +403,9 @@ async function dispatchTool(id, name, args) {
349
403
  return;
350
404
  }
351
405
  if (name === "wl_ui_detect_skin") {
406
+ const result = await detectSkin(args);
352
407
  sendResult(id, {
353
- content: [
354
- { type: "text", text: JSON.stringify(detectSkin(args), null, 2) },
355
- ],
408
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
356
409
  });
357
410
  return;
358
411
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agile-team/wl-skills-ui",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "企业级 UI 风格对齐框架 — Vue + Element Plus 项目通用化妆/原生双模式(tokens / element / vendors / layouts / runtime / scanner / fixer / skills)",
5
5
  "type": "module",
6
6
  "main": "./es/index.js",
@@ -19,6 +19,7 @@
19
19
  "types": "./es/presets/security.d.ts"
20
20
  },
21
21
  "./runtime/presets/*": "./runtime/presets/*",
22
+ "./vite": "./runtime/vite/check.mjs",
22
23
  "./styles": "./styles/index.scss",
23
24
  "./styles/*": "./styles/*",
24
25
  "./design/tokens": "./design/tokens/index.css",
@@ -52,6 +53,7 @@
52
53
  "files": [
53
54
  "dist",
54
55
  "es",
56
+ "runtime/vite",
55
57
  "scanner",
56
58
  "standards",
57
59
  "styles",
@@ -0,0 +1,130 @@
1
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2
+ // @agile-team/wl-skills-ui/vite — 启动期自检 Vite 插件
3
+ //
4
+ // 用法(消费方 vite.config.ts):
5
+ // import { wlSkillsCheck } from '@agile-team/wl-skills-ui/vite';
6
+ // export default defineConfig({ plugins: [wlSkillsCheck()] });
7
+ //
8
+ // 选项:
9
+ // enforce: 'warn' | 'error' | 'silent' 默认 'warn'
10
+ // includeVendors: string[] 只校验指定 vendor id,默认全部
11
+ // verbose: boolean 默认 false,true 时也打印 match 项
12
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import {
16
+ listCompatVendors,
17
+ evaluateVendor,
18
+ buildOverridesSnippet,
19
+ } from "../../skills/_meta/_compat/loader.mjs";
20
+
21
+ const C_RESET = "\x1b[0m";
22
+ const C_YELLOW = "\x1b[33m";
23
+ const C_RED = "\x1b[31m";
24
+ const C_GREEN = "\x1b[32m";
25
+ const C_DIM = "\x1b[2m";
26
+
27
+ /**
28
+ * Vite 插件:启动期校验项目依赖是否锚定 wl-skills-ui 推荐组合
29
+ * @param {{enforce?: 'warn'|'error'|'silent', includeVendors?: string[], verbose?: boolean}} options
30
+ */
31
+ export function wlSkillsCheck(options = {}) {
32
+ const enforce = options.enforce ?? "warn";
33
+ const includeVendors = options.includeVendors ?? null;
34
+ const verbose = options.verbose ?? false;
35
+ let hasRun = false;
36
+
37
+ return {
38
+ name: "wl-skills-ui:check",
39
+ configResolved(config) {
40
+ if (hasRun || enforce === "silent") return;
41
+ hasRun = true;
42
+ runCheck({
43
+ projectRoot: config.root,
44
+ enforce,
45
+ includeVendors,
46
+ verbose,
47
+ logger: config.logger,
48
+ });
49
+ },
50
+ };
51
+ }
52
+
53
+ function runCheck({ projectRoot, enforce, includeVendors, verbose, logger }) {
54
+ const pkgPath = join(projectRoot, "package.json");
55
+ if (!existsSync(pkgPath)) return;
56
+ let pkg;
57
+ try {
58
+ pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
59
+ } catch {
60
+ return;
61
+ }
62
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
63
+
64
+ const vendors = listCompatVendors().filter(
65
+ (v) => !includeVendors || includeVendors.includes(v.vendorId),
66
+ );
67
+
68
+ const mismatches = [];
69
+ const matches = [];
70
+ const evaluations = [];
71
+ for (const compat of vendors) {
72
+ const ev = evaluateVendor(compat, deps);
73
+ evaluations.push(ev);
74
+ if (ev.verdict === "match") matches.push({ compat, ev });
75
+ else if (ev.verdict === "mismatch") mismatches.push({ compat, ev });
76
+ }
77
+
78
+ if (verbose && matches.length > 0) {
79
+ const lines = matches.map(
80
+ ({ compat, ev }) =>
81
+ ` ${C_GREEN}✓${C_RESET} ${compat.vendorId}: ${ev.peers
82
+ .map((p) => `${p.name}@${p.actual}`)
83
+ .join(" + ")}`,
84
+ );
85
+ print(logger, "info", `${C_DIM}[wl-skills-ui] 适配矩阵命中:${C_RESET}\n${lines.join("\n")}`);
86
+ }
87
+
88
+ if (mismatches.length === 0) return;
89
+
90
+ const overrides = buildOverridesSnippet(evaluations);
91
+ const header =
92
+ enforce === "error"
93
+ ? `${C_RED}[wl-skills-ui] 启动期版本配对失败(enforce=error)${C_RESET}`
94
+ : `${C_YELLOW}[wl-skills-ui] 启动期版本配对警告${C_RESET}`;
95
+
96
+ const detail = mismatches
97
+ .map(({ compat, ev }) => {
98
+ const peers = ev.peers
99
+ .map(
100
+ (p) =>
101
+ ` - ${p.name}: 实际 ${p.actual || "未安装"} / 推荐 ${p.expected} ${p.ok ? `${C_GREEN}✓${C_RESET}` : `${C_RED}✗${C_RESET}`}`,
102
+ )
103
+ .join("\n");
104
+ return ` vendor=${compat.vendorId}\n${peers}\n ${C_DIM}→ ${compat.note || ""}${C_RESET}`;
105
+ })
106
+ .join("\n\n");
107
+
108
+ const fix = overrides
109
+ ? `\n${C_DIM}修复建议(复制到 package.json):${C_RESET}\n${C_DIM}${JSON.stringify(overrides.pnpm, null, 2)}${C_RESET}\n或执行:${C_DIM}npx wl-ui doctor --print-overrides${C_RESET}`
110
+ : "";
111
+
112
+ const message = `${header}\n${detail}${fix}`;
113
+
114
+ if (enforce === "error") {
115
+ print(logger, "error", message);
116
+ throw new Error("[wl-skills-ui] vendor 版本配对偏离推荐组合,详见上方提示");
117
+ } else {
118
+ print(logger, "warn", message);
119
+ }
120
+ }
121
+
122
+ function print(logger, level, msg) {
123
+ if (logger && typeof logger[level] === "function") {
124
+ logger[level](msg);
125
+ } else {
126
+ console[level === "error" ? "error" : "warn"](msg);
127
+ }
128
+ }
129
+
130
+ export default wlSkillsCheck;
@@ -12,16 +12,13 @@
12
12
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
13
13
  import { join, dirname } from 'node:path';
14
14
  import { fileURLToPath } from "node:url";
15
+ import {
16
+ listCompatVendors,
17
+ evaluateVendor,
18
+ } from "../skills/_meta/_compat/loader.mjs";
15
19
 
16
20
  const __filename = fileURLToPath(import.meta.url);
17
21
  const __dirname = dirname(__filename);
18
- const VENDORS_JSON = JSON.parse(
19
- readFileSync(
20
- join(__dirname, "..", "skills", "_meta", "_compat", "vendors.json"),
21
- "utf8",
22
- ),
23
- );
24
- const JH_COMPAT = VENDORS_JSON.vendors.find((v) => v.id === "jh")?.compat || {};
25
22
 
26
23
  /**
27
24
  * @param {string} projectRoot — 项目根目录(包含 index.html / src / package.json)
@@ -153,30 +150,38 @@ export function checkIntegration(projectRoot) {
153
150
  suggestion: hasEp && hasVue ? "" : "pnpm add vue element-plus",
154
151
  });
155
152
 
156
- // ── I005: jh-ui element-plus 版本配对 ──────────────────────────
157
- const jhUi = deps["@jhlc/jh-ui"];
158
- const ep = deps["element-plus"];
159
- if (jhUi) {
160
- const epOk = ep && ep.includes(JH_COMPAT.elementPlus || "2.2.6-prod.3");
161
- const jhOk = jhUi.includes(JH_COMPAT.jhUi || "3.1.0");
162
- const ok = epOk && jhOk;
153
+ // ── I005: vendor 配对(jh-ui / 未来 vendor 都走这条) ─────────────
154
+ const vendors = listCompatVendors();
155
+ let anyEvaluated = false;
156
+ for (const compat of vendors) {
157
+ const ev = evaluateVendor(compat, deps);
158
+ if (ev.verdict === "not-applicable") continue;
159
+ anyEvaluated = true;
160
+ const ok = ev.verdict === "match";
161
+ const actuals = ev.peers
162
+ .map((p) => `${p.name} ${p.actual || "未安装"}`)
163
+ .join(" + ");
164
+ const expecteds = ev.peers
165
+ .map((p) => `${p.name}@${p.expected}`)
166
+ .join(" + ");
163
167
  checks.push({
164
- id: "I005",
168
+ id: `I005:${compat.vendorId}`,
165
169
  severity: ok ? "info" : "warning",
166
170
  ok,
167
171
  description: ok
168
- ? `jh-ui 推荐组合命中(@jhlc/jh-ui ${jhUi} + element-plus ${ep})`
169
- : `jh-ui 版本配对偏离推荐(实际 @jhlc/jh-ui ${jhUi} + element-plus ${ep || "未安装"})`,
172
+ ? `${compat.vendorId} 推荐组合命中(${actuals})`
173
+ : `${compat.vendorId} 版本配对偏离推荐(实际 ${actuals})`,
170
174
  suggestion: ok
171
175
  ? ""
172
- : `推荐组合:@jhlc/jh-ui@${JH_COMPAT.jhUi} + element-plus@${JH_COMPAT.elementPlus}(详见 docs/compat-matrix.md)`,
176
+ : `推荐组合:${expecteds}(详见 docs/compat-matrix.md,或执行 npx wl-ui doctor --print-overrides)`,
173
177
  });
174
- } else {
178
+ }
179
+ if (!anyEvaluated) {
175
180
  checks.push({
176
181
  id: "I005",
177
182
  severity: "info",
178
183
  ok: true,
179
- description: "未检测到 @jhlc/jh-ui,跳过 jh-ui 配对校验",
184
+ description: "未命中任何已声明 compat vendor,跳过配对校验",
180
185
  suggestion: "",
181
186
  });
182
187
  }
@@ -0,0 +1,96 @@
1
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2
+ // vendors.json 适配矩阵共享加载器
3
+ //
4
+ // 所有读取方(scanner I005 / MCP wl_ui_detect_skin / Vite 插件 / bin doctor /
5
+ // scripts/check-docs.mjs)统一从这里取数据,避免逻辑漂移。
6
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
7
+ import { readFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const VENDORS_JSON_PATH = join(__dirname, "vendors.json");
13
+
14
+ let _cache = null;
15
+
16
+ /** 加载 vendors.json(带进程内缓存) */
17
+ export function loadVendorsJson() {
18
+ if (_cache) return _cache;
19
+ _cache = JSON.parse(readFileSync(VENDORS_JSON_PATH, "utf8"));
20
+ return _cache;
21
+ }
22
+
23
+ /** 规范化单个 vendor 的 compat(兼容旧平铺字段) */
24
+ export function normalizeCompat(vendor) {
25
+ const c = vendor?.compat;
26
+ if (!c) return null;
27
+ const peers = c.peers || {};
28
+ // 旧平铺字段兜底
29
+ if (!peers["element-plus"] && c.elementPlus) {
30
+ peers["element-plus"] = c.elementPlus;
31
+ }
32
+ if (!peers["@jhlc/jh-ui"] && c.jhUi) {
33
+ peers["@jhlc/jh-ui"] = c.jhUi;
34
+ }
35
+ return {
36
+ vendorId: vendor.id,
37
+ vendorLabel: vendor.label,
38
+ peers,
39
+ gatingPeer: c.gatingPeer || Object.keys(peers).find((k) => k.startsWith("@")) || null,
40
+ conflictsWith: c.conflictsWith || [],
41
+ domAssumptions: c.domAssumptions || [],
42
+ note: c.note || "",
43
+ };
44
+ }
45
+
46
+ /** 返回所有声明了 compat 的 vendor */
47
+ export function listCompatVendors() {
48
+ const { vendors = [] } = loadVendorsJson();
49
+ return vendors.map(normalizeCompat).filter(Boolean);
50
+ }
51
+
52
+ /**
53
+ * 针对一个项目的 dependencies + devDependencies 判定 vendor 配对状态
54
+ * @returns {{vendorId, gatingInstalled, peers: Array<{name, expected, actual, ok}>, verdict}}
55
+ * verdict: 'match' | 'mismatch' | 'not-applicable'
56
+ */
57
+ export function evaluateVendor(compat, deps) {
58
+ const gating = compat.gatingPeer;
59
+ const gatingInstalled = gating ? Boolean(deps[gating]) : true;
60
+ if (gating && !gatingInstalled) {
61
+ return {
62
+ vendorId: compat.vendorId,
63
+ gatingInstalled: false,
64
+ peers: [],
65
+ verdict: "not-applicable",
66
+ };
67
+ }
68
+ const peers = Object.entries(compat.peers).map(([name, expected]) => {
69
+ const actual = deps[name] || null;
70
+ const ok = !!actual && actual.includes(expected);
71
+ return { name, expected, actual, ok };
72
+ });
73
+ const allOk = peers.every((p) => p.ok);
74
+ return {
75
+ vendorId: compat.vendorId,
76
+ gatingInstalled: true,
77
+ peers,
78
+ verdict: allOk ? "match" : "mismatch",
79
+ };
80
+ }
81
+
82
+ /** 输出 pnpm overrides 修复片段(仅对 mismatch 的 peers) */
83
+ export function buildOverridesSnippet(evaluations) {
84
+ const overrides = {};
85
+ for (const ev of evaluations) {
86
+ if (ev.verdict !== "mismatch") continue;
87
+ for (const p of ev.peers) {
88
+ if (!p.ok) overrides[p.name] = p.expected;
89
+ }
90
+ }
91
+ if (Object.keys(overrides).length === 0) return null;
92
+ return {
93
+ pnpm: { overrides },
94
+ npmYarn: { resolutions: overrides, overrides },
95
+ };
96
+ }
@@ -41,6 +41,23 @@
41
41
  "_jh-drag-col.scss"
42
42
  ],
43
43
  "compat": {
44
+ "peers": {
45
+ "element-plus": "2.2.6-prod.3",
46
+ "@jhlc/jh-ui": "3.1.0"
47
+ },
48
+ "gatingPeer": "@jhlc/jh-ui",
49
+ "conflictsWith": [
50
+ {
51
+ "name": "element-plus",
52
+ "rangeNote": ">=2.3.0",
53
+ "reason": "EP 2.3.0 起引入 .el-input__wrapper,与 jh-ui 3.x 假设的 .el-input>.el-input__inner DOM 不兼容"
54
+ }
55
+ ],
56
+ "domAssumptions": [
57
+ ".el-input>.el-input__inner",
58
+ ".el-form-item__label>.com-text",
59
+ ".has-colon .com-text::after"
60
+ ],
44
61
  "elementPlus": "2.2.6-prod.3",
45
62
  "elementPlusRange": "<2.3.0",
46
63
  "jhUi": "3.1.0",