@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 +15 -0
- package/README.md +16 -2
- package/bin/wl-ui.js +45 -3
- package/docs/compat-matrix.md +23 -2
- package/mcp/server.js +56 -3
- package/package.json +3 -1
- package/runtime/vite/check.mjs +130 -0
- package/scanner/integration.mjs +25 -20
- package/skills/_meta/_compat/loader.mjs +96 -0
- package/skills/_meta/_compat/vendors.json +17 -0
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
|
-
|
|
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
|
|
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: {
|
|
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
|
|
package/docs/compat-matrix.md
CHANGED
|
@@ -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.
|
|
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;
|
package/scanner/integration.mjs
CHANGED
|
@@ -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
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
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:
|
|
168
|
+
id: `I005:${compat.vendorId}`,
|
|
165
169
|
severity: ok ? "info" : "warning",
|
|
166
170
|
ok,
|
|
167
171
|
description: ok
|
|
168
|
-
?
|
|
169
|
-
:
|
|
172
|
+
? `${compat.vendorId} 推荐组合命中(${actuals})`
|
|
173
|
+
: `${compat.vendorId} 版本配对偏离推荐(实际 ${actuals})`,
|
|
170
174
|
suggestion: ok
|
|
171
175
|
? ""
|
|
172
|
-
:
|
|
176
|
+
: `推荐组合:${expecteds}(详见 docs/compat-matrix.md,或执行 npx wl-ui doctor --print-overrides)`,
|
|
173
177
|
});
|
|
174
|
-
}
|
|
178
|
+
}
|
|
179
|
+
if (!anyEvaluated) {
|
|
175
180
|
checks.push({
|
|
176
181
|
id: "I005",
|
|
177
182
|
severity: "info",
|
|
178
183
|
ok: true,
|
|
179
|
-
description: "
|
|
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",
|