@qilitt-mickey/vue3-temp-skill 1.2.6 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -115
- package/SKILL.md +41 -159
- package/bin/cli.js +297 -657
- package/package.json +5 -2
- package/references/adapters/_new-adapter.md +17 -82
- package/references/adapters/vue-antdv.md +21 -107
- package/references/adapters/vue-element-plus.md +39 -179
- package/references/code-quality.md +52 -168
- package/references/core-kernel.md +12 -3
- package/references/crud-pages.md +10 -11
- package/references/data-screen.md +8 -9
- package/references/design-apply.md +59 -358
- package/references/layout-theme.md +6 -4
- package/references/visual-verification.md +49 -0
- package/scripts/check-landing.mjs +234 -47
- package/scripts/generate-css.mjs +17 -397
- package/scripts/match.py +30 -11
- package/scripts/modules.json +3 -3
- package/scripts/verify-render.mjs +17 -524
- package/scripts/generate-page.mjs +0 -734
package/scripts/generate-css.mjs
CHANGED
|
@@ -1,405 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* 用法:
|
|
7
|
-
* node generate-css.mjs --project <项目根> [--adapter vue-element-plus] [--out src/styles]
|
|
8
|
-
*
|
|
9
|
-
* 职责(design-apply 第 3/4/5 步的机器执行版,消灭手抄):
|
|
10
|
-
* 1. ui-tokens.css : tokens 族.名 → --族-名 实值 + adapter.roleSupplementVars + adapter.variableBridges
|
|
11
|
-
* 2. ui-roles.css : roles 逐条翻译 host/inner/states/variants + adapter.extraRules 原样追加
|
|
12
|
-
* 3. ui-layout.css : layout.regions 按 adapter.layoutBridge 的挂载类与 skipGeometry 翻译
|
|
13
|
-
*
|
|
14
|
-
* 原则: 契约是唯一事实源;本脚本不做任何视觉判断;同一输入必得同一输出(可复现)。
|
|
15
|
-
*/
|
|
3
|
+
/** 退役兼容入口:不读取项目、不写文件、不启动浏览器或网络。 */
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
16
5
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
const NOTICE = `generate-css.mjs 已退役,拒绝执行旧三 CSS 生成引擎。
|
|
7
|
+
visualStatus=unverified;本入口不提供业务/视觉验收结论。
|
|
8
|
+
请迁移到 references/design-apply.md 和 references/visual-verification.md:
|
|
9
|
+
以原稿为事实源,手写项目视图,逐区域、逐实例、逐状态进行浏览器验收。
|
|
10
|
+
旧 visual-contract 可作为输入复用,但不自动选取契约、不用 adapter 覆盖原稿值、不生成或覆盖样式。`;
|
|
20
11
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
for (let i = 0; i < argv.length; i++) {
|
|
28
|
-
const a = argv[i];
|
|
29
|
-
if (a === "--project" || a === "-p") out.project = path.resolve(argv[++i]);
|
|
30
|
-
else if (a === "--adapter") out.adapter = argv[++i];
|
|
31
|
-
else if (a === "--out") out.out = argv[++i];
|
|
32
|
-
}
|
|
33
|
-
return out;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ────────────────────────────── 名称转换(与 check-landing.mjs 的 expectedClassName 完全一致)
|
|
37
|
-
|
|
38
|
-
function expectedClassName(roleName) {
|
|
39
|
-
return (
|
|
40
|
-
"role-" +
|
|
41
|
-
roleName
|
|
42
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
43
|
-
.replace(/\./g, "-")
|
|
44
|
-
.replace(/_/g, "-")
|
|
45
|
-
.toLowerCase()
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function variantClassName(variantName) {
|
|
50
|
-
return variantName
|
|
51
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
52
|
-
.replace(/_/g, "-")
|
|
53
|
-
.toLowerCase();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function tokenVar(tokenRef) {
|
|
57
|
-
// "color.brand" → "--color-brand";"space.6" → "--space-6"
|
|
58
|
-
return "--" + tokenRef.replace(/\./g, "-").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** 展开值中的 {族.名} 令牌引用 → var(--族-名);字面量原样(色值统一小写,过 prettier 且保证确定性输出) */
|
|
62
|
-
function expandValue(value, tokenIndex) {
|
|
63
|
-
return String(value)
|
|
64
|
-
.replace(/\{([a-zA-Z0-9.]+)\}/g, (m, ref) => {
|
|
65
|
-
const v = tokenVar(ref);
|
|
66
|
-
if (!tokenIndex.has(ref)) {
|
|
67
|
-
// 引用未定义令牌:仍生成 var() 引用(浏览器解析为空时可见),但记入警告
|
|
68
|
-
warnings.push(`令牌引用未在 tokens 定义: {${ref}} → ${v}(请检查契约或补 roleSupplementVars)`);
|
|
69
|
-
}
|
|
70
|
-
return `var(${v})`;
|
|
71
|
-
})
|
|
72
|
-
.replace(/#[0-9a-fA-F]{3,8}\b/g, (c) => c.toLowerCase());
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** camelCase 键 → kebab CSS 属性 */
|
|
76
|
-
function cssProp(key) {
|
|
77
|
-
if (key.includes("-")) return key;
|
|
78
|
-
return key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const warnings = [];
|
|
82
|
-
|
|
83
|
-
// ────────────────────────────── 读取
|
|
84
|
-
|
|
85
|
-
function loadInputs(args) {
|
|
86
|
-
const designDir = path.join(args.project, ".design");
|
|
87
|
-
const names = fs.readdirSync(designDir).filter((n) => /^visual-contract.*\.json$/.test(n));
|
|
88
|
-
if (!names.length) throw new Error(".design/ 下没有 visual-contract-*.json");
|
|
89
|
-
const contractPath = path.join(designDir, names[0]);
|
|
90
|
-
const contract = JSON.parse(fs.readFileSync(contractPath, "utf-8"));
|
|
91
|
-
|
|
92
|
-
let adapterName = args.adapter;
|
|
93
|
-
if (!adapterName) {
|
|
94
|
-
// 从 mapping/<scene>.json 读取
|
|
95
|
-
const mapPath = path.join(SKILL_DIR, "mapping", `${contract.meta.scene}.json`);
|
|
96
|
-
if (fs.existsSync(mapPath)) {
|
|
97
|
-
adapterName = JSON.parse(fs.readFileSync(mapPath, "utf-8")).adapter;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (!adapterName) throw new Error("无法确定 adapter(契约场景无 mapping 且未传 --adapter)");
|
|
101
|
-
|
|
102
|
-
const adapterPath = path.join(SKILL_DIR, "adapters", `${adapterName}.json`);
|
|
103
|
-
if (!fs.existsSync(adapterPath)) throw new Error(`adapter JSON 不存在: ${adapterPath}`);
|
|
104
|
-
const adapter = JSON.parse(fs.readFileSync(adapterPath, "utf-8"));
|
|
105
|
-
|
|
106
|
-
return { contract, adapter, contractPath, adapterName };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ────────────────────────────── ui-tokens.css
|
|
110
|
-
|
|
111
|
-
function genTokens(contract, adapter) {
|
|
112
|
-
const L = [];
|
|
113
|
-
L.push("/**");
|
|
114
|
-
L.push(" * ui-tokens.css —— 视觉契约令牌层(由 generate-css.mjs 生成,禁止手改)");
|
|
115
|
-
L.push(` * 事实源: .design/visual-contract 令牌 + adapter(${adapter.adapter}) 桥接段`);
|
|
116
|
-
L.push(" */");
|
|
117
|
-
L.push("");
|
|
118
|
-
L.push(":root {");
|
|
119
|
-
|
|
120
|
-
const tokenIndex = new Set();
|
|
121
|
-
for (const [family, names] of Object.entries(contract.tokens || {})) {
|
|
122
|
-
L.push(` /* ===== ${family} 族 ===== */`);
|
|
123
|
-
for (const [name, def] of Object.entries(names)) {
|
|
124
|
-
const v = tokenVar(`${family}.${name}`);
|
|
125
|
-
tokenIndex.add(`${family}.${name}`);
|
|
126
|
-
L.push(` ${v}: ${String(def.value).replace(/#[0-9a-fA-F]{3,8}\b/g, (c) => c.toLowerCase())};`);
|
|
127
|
-
}
|
|
128
|
-
L.push("");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 辅助变量(契约 role 声明用到但 tokens 未定义的)
|
|
132
|
-
if (adapter.roleSupplementVars) {
|
|
133
|
-
L.push(" /* ===== 辅助变量(adapter.roleSupplementVars) ===== */");
|
|
134
|
-
for (const [k, v] of Object.entries(adapter.roleSupplementVars)) {
|
|
135
|
-
if (k.startsWith("$")) continue;
|
|
136
|
-
tokenIndex.add(k.replace(/^--/, "").replace(/-/g, ".")); // 宽松
|
|
137
|
-
L.push(` ${k}: ${String(v).replace(/#[0-9a-fA-F]{3,8}\b/g, (c) => c.toLowerCase())};`);
|
|
138
|
-
}
|
|
139
|
-
L.push("");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 桥接段
|
|
143
|
-
L.push(" /* ==========================================================");
|
|
144
|
-
L.push(" * 桥接段(adapter.variableBridges):契约值 → 项目既有变量消费链");
|
|
145
|
-
L.push(" * ========================================================== */");
|
|
146
|
-
const regions = (contract.layout && contract.layout.regions) || {};
|
|
147
|
-
const bridge = adapter.variableBridges || {};
|
|
148
|
-
for (const group of Object.keys(bridge)) {
|
|
149
|
-
if (group.startsWith("$")) continue;
|
|
150
|
-
L.push("");
|
|
151
|
-
L.push(` /* -- ${group} -- */`);
|
|
152
|
-
for (const b of bridge[group]) {
|
|
153
|
-
let value;
|
|
154
|
-
if (b.from && b.from.startsWith("layout:")) {
|
|
155
|
-
// "layout:header.height" → regions.header.height(展开令牌引用)
|
|
156
|
-
const [region, key] = b.from.slice(7).split(".");
|
|
157
|
-
const raw = regions[region] && regions[region][key];
|
|
158
|
-
if (raw == null) {
|
|
159
|
-
warnings.push(`桥接 layout:${region}.${key} 在契约 layout.regions 中不存在,跳过`);
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
value = expandValue(raw, tokenIndex);
|
|
163
|
-
} else if (b.from && b.from.startsWith("--")) {
|
|
164
|
-
value = `var(${b.from})`;
|
|
165
|
-
} else {
|
|
166
|
-
value = expandValue(b.value, tokenIndex);
|
|
167
|
-
}
|
|
168
|
-
L.push(` ${b.var}: ${value}${b.important ? " !important" : ""};`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
L.push("}");
|
|
173
|
-
L.push("");
|
|
174
|
-
return { css: L.join("\n"), tokenIndex };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ────────────────────────────── ui-roles.css
|
|
178
|
-
|
|
179
|
-
function declBlock(decls, indent, tokenIndex) {
|
|
180
|
-
const out = [];
|
|
181
|
-
for (const d of decls) {
|
|
182
|
-
out.push(`${indent}${cssProp(d.prop)}: ${expandValue(d.value, tokenIndex)};`);
|
|
183
|
-
}
|
|
184
|
-
return out;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function genRoles(contract, adapter, tokenIndex) {
|
|
188
|
-
const L = [];
|
|
189
|
-
L.push("/**");
|
|
190
|
-
L.push(" * ui-roles.css —— 视觉契约角色层(由 generate-css.mjs 生成,禁止手改)");
|
|
191
|
-
L.push(` * 事实源: .design/visual-contract roles + adapter(${adapter.adapter}) 结构等价转换`);
|
|
192
|
-
L.push(" * 全局文件:禁止 :deep();inner 穿透用普通后代选择器(adapter.innerTargets)");
|
|
193
|
-
L.push(" */");
|
|
194
|
-
L.push("");
|
|
195
|
-
|
|
196
|
-
const roles = contract.roles || {};
|
|
197
|
-
const innerTargets = adapter.innerTargets || {};
|
|
198
|
-
const stateChannels = adapter.stateChannels || {};
|
|
199
|
-
const extraRules = adapter.extraRules || {};
|
|
200
|
-
|
|
201
|
-
// adapter 映射表的键 = 类名去掉 role- 前缀
|
|
202
|
-
const bare = (roleName) => expectedClassName(roleName).replace(/^role-/, "");
|
|
203
|
-
const innerTargetOf = (roleName) => innerTargets[bare(roleName)] || "default";
|
|
204
|
-
|
|
205
|
-
for (const [roleName, role] of Object.entries(roles)) {
|
|
206
|
-
if (roleName.startsWith("$")) continue;
|
|
207
|
-
const cls = "." + expectedClassName(roleName);
|
|
208
|
-
L.push(`/* ===== 角色 ${roleName}${role.label ? " · " + role.label : ""} ===== */`);
|
|
209
|
-
|
|
210
|
-
// host
|
|
211
|
-
if (Array.isArray(role.host) && role.host.length) {
|
|
212
|
-
L.push(`${cls} {`);
|
|
213
|
-
L.push(...declBlock(role.host, " ", tokenIndex));
|
|
214
|
-
L.push("}");
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// inner
|
|
218
|
-
if (Array.isArray(role.inner) && role.inner.length) {
|
|
219
|
-
const target = innerTargetOf(roleName);
|
|
220
|
-
const sel = target === "default" ? cls : `${cls} ${target}`;
|
|
221
|
-
L.push(`${sel} {`);
|
|
222
|
-
L.push(...declBlock(role.inner, " ", tokenIndex));
|
|
223
|
-
L.push("}");
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// 结构键 box/flow/align/span/sticky
|
|
227
|
-
const structDecls = [];
|
|
228
|
-
if (role.box) for (const [k, v] of Object.entries(role.box)) structDecls.push({ prop: k, value: v });
|
|
229
|
-
if (role.flow) for (const [k, v] of Object.entries(role.flow)) structDecls.push({ prop: k, value: v });
|
|
230
|
-
if (role.align) for (const [k, v] of Object.entries(role.align)) structDecls.push({ prop: k, value: v });
|
|
231
|
-
if (role.span) for (const [k, v] of Object.entries(role.span)) structDecls.push({ prop: k, value: v });
|
|
232
|
-
if (role.sticky && role.sticky.position) {
|
|
233
|
-
structDecls.push({ prop: "position", value: role.sticky.position });
|
|
234
|
-
if (role.sticky.side) structDecls.push({ prop: role.sticky.side, value: "0" });
|
|
235
|
-
}
|
|
236
|
-
if (structDecls.length) {
|
|
237
|
-
L.push(`${cls} {`);
|
|
238
|
-
L.push(...declBlock(structDecls, " ", tokenIndex));
|
|
239
|
-
L.push("}");
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// states
|
|
243
|
-
if (role.states) {
|
|
244
|
-
for (const [state, decl] of Object.entries(role.states)) {
|
|
245
|
-
const channel = stateChannels[state] || `:${state}`;
|
|
246
|
-
// 多通道(逗号分隔):作为选择器附加段整体展开(如 ".is-disabled, :disabled")
|
|
247
|
-
for (const ch of channel.split(",").map((c) => c.trim())) {
|
|
248
|
-
const hostSel = ch.startsWith(":") ? `${cls}${ch}` : `${cls}${ch}`;
|
|
249
|
-
if (Array.isArray(decl.host) && decl.host.length) {
|
|
250
|
-
L.push(`${hostSel} {`);
|
|
251
|
-
L.push(...declBlock(decl.host, " ", tokenIndex));
|
|
252
|
-
L.push("}");
|
|
253
|
-
}
|
|
254
|
-
if (Array.isArray(decl.inner) && decl.inner.length) {
|
|
255
|
-
const target = innerTargetOf(roleName);
|
|
256
|
-
const innerSel = target === "default" ? hostSel : `${hostSel} ${target}`;
|
|
257
|
-
L.push(`${innerSel} {`);
|
|
258
|
-
L.push(...declBlock(decl.inner, " ", tokenIndex));
|
|
259
|
-
L.push("}");
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// variants(整份替换:variant host 覆盖基础 host,inner 同理)
|
|
266
|
-
if (Array.isArray(role.variants)) {
|
|
267
|
-
for (const v of role.variants) {
|
|
268
|
-
const vcls = `${cls}--${variantClassName(v.name)}`;
|
|
269
|
-
const vhost = [];
|
|
270
|
-
if (Array.isArray(role.host)) for (const d of role.host) vhost.push(d);
|
|
271
|
-
if (Array.isArray(v.host)) {
|
|
272
|
-
for (const d of v.host) {
|
|
273
|
-
const i = vhost.findIndex((x) => cssProp(x.prop) === cssProp(d.prop));
|
|
274
|
-
if (i >= 0) vhost[i] = d;
|
|
275
|
-
else vhost.push(d);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
if (vhost.length) {
|
|
279
|
-
L.push(`${vcls} {`);
|
|
280
|
-
L.push(...declBlock(vhost, " ", tokenIndex));
|
|
281
|
-
L.push("}");
|
|
282
|
-
}
|
|
283
|
-
if (Array.isArray(v.inner) && v.inner.length) {
|
|
284
|
-
const target = innerTargetOf(roleName);
|
|
285
|
-
const sel = target === "default" ? vcls : `${vcls} ${target}`;
|
|
286
|
-
L.push(`${sel} {`);
|
|
287
|
-
L.push(...declBlock(v.inner, " ", tokenIndex));
|
|
288
|
-
L.push("}");
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// extraRules(adapter 的结构等价转换 / 提权段,原样追加——后置覆盖基础翻译)
|
|
294
|
-
const extras = extraRules[bare(roleName)] || extraRules[expectedClassName(roleName)];
|
|
295
|
-
if (extras) {
|
|
296
|
-
L.push(`/* -- adapter 结构等价转换(${adapter.adapter}) -- */`);
|
|
297
|
-
for (const r of extras) {
|
|
298
|
-
const emits = Array.isArray(r.emit) ? r.emit : [r.emit];
|
|
299
|
-
for (const e of emits) L.push(e.trim());
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
L.push("");
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return L.join("\n");
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// ────────────────────────────── ui-layout.css
|
|
309
|
-
|
|
310
|
-
function genLayout(contract, adapter, tokenIndex) {
|
|
311
|
-
const L = [];
|
|
312
|
-
L.push("/**");
|
|
313
|
-
L.push(" * ui-layout.css —— 视觉契约布局层(由 generate-css.mjs 生成,禁止手改)");
|
|
314
|
-
L.push(` * 事实源: .design/visual-contract layout.regions + adapter(${adapter.adapter}) layoutBridge`);
|
|
315
|
-
L.push(" */");
|
|
316
|
-
L.push("");
|
|
317
|
-
|
|
318
|
-
const bridge = adapter.layoutBridge || {};
|
|
319
|
-
const regions = (contract.layout && contract.layout.regions) || {};
|
|
320
|
-
const areas = new Set((contract.layout?.areas || []).map((a) => a.name));
|
|
321
|
-
const skipGeometry = bridge.skipGeometry || {};
|
|
322
|
-
const regionClasses = bridge.regionClasses || {};
|
|
323
|
-
|
|
324
|
-
// 壳层
|
|
325
|
-
const shell = contract.roles?.["shell.shell"];
|
|
326
|
-
if (shell && bridge.shellClass) {
|
|
327
|
-
L.push(`/* ===== 内容壳(shell.shell) ===== */`);
|
|
328
|
-
L.push(`${bridge.shellClass.join(",\n")} {`);
|
|
329
|
-
L.push(...declBlock(shell.host || [], " ", tokenIndex));
|
|
330
|
-
L.push("}");
|
|
331
|
-
L.push("");
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
for (const [regionName, def] of Object.entries(regions)) {
|
|
335
|
-
if (!areas.has(regionName)) continue; // 未写入 areas 的区域不生成
|
|
336
|
-
const sels = regionClasses[regionName] || [`ui-region-${regionName}`].map((c) => `.${c}`);
|
|
337
|
-
// skipGeometry 用 CSS 属性名;契约键是 camelCase(collapsedWidth)——统一按 cssProp 归一后匹配
|
|
338
|
-
const skip = new Set((skipGeometry[regionName] || []).map((k) => cssProp(k)));
|
|
339
|
-
L.push(`/* ===== 区域 ${regionName}(skip: ${[...skip].join(", ") || "无"}) ===== */`);
|
|
340
|
-
const decls = [];
|
|
341
|
-
const push = (prop, value) => {
|
|
342
|
-
if (skip.has(cssProp(prop))) return;
|
|
343
|
-
decls.push({ prop: cssProp(prop), value: expandValue(value, tokenIndex) });
|
|
344
|
-
};
|
|
345
|
-
if (def.width) push("width", def.width);
|
|
346
|
-
if (def.height) push("height", def.height);
|
|
347
|
-
if (def.minHeight) push("min-height", def.minHeight);
|
|
348
|
-
if (def.padding) push("padding", def.padding);
|
|
349
|
-
if (def.gap) push("gap", def.gap);
|
|
350
|
-
if (def.bg) push("background-color", def.bg);
|
|
351
|
-
if (def.display) push("display", def.display);
|
|
352
|
-
if (def.direction) push("flex-direction", def.direction);
|
|
353
|
-
if (def.alignItems) push("align-items", def.alignItems);
|
|
354
|
-
if (def.justifyContent) push("justify-content", def.justifyContent);
|
|
355
|
-
if (def.position) push("position", def.position);
|
|
356
|
-
if (def.zIndex) push("z-index", def.zIndex);
|
|
357
|
-
// 区域角色同名视觉(如 shell.header 的 border-bottom)从契约 roles 合并(同受 skip 过滤)
|
|
358
|
-
const roleForRegion = contract.roles?.[`shell.${regionName}`];
|
|
359
|
-
if (roleForRegion && Array.isArray(roleForRegion.host)) {
|
|
360
|
-
for (const d of roleForRegion.host) {
|
|
361
|
-
if (skip.has(cssProp(d.prop))) continue;
|
|
362
|
-
if (!decls.some((x) => x.prop === cssProp(d.prop))) {
|
|
363
|
-
decls.push({ prop: cssProp(d.prop), value: expandValue(d.value, tokenIndex) });
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
L.push(`${sels.join(",\n")} {`);
|
|
368
|
-
for (const d of decls) L.push(` ${d.prop}: ${d.value};`);
|
|
369
|
-
L.push("}");
|
|
370
|
-
L.push("");
|
|
12
|
+
export function main(argv = process.argv.slice(2)) {
|
|
13
|
+
if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
|
|
14
|
+
console.log(`用法: node generate-css.mjs --help
|
|
15
|
+
${NOTICE}
|
|
16
|
+
仅帮助退出 0;其他调用(包括旧参数或未知参数)均退出 2。`);
|
|
17
|
+
return 0;
|
|
371
18
|
}
|
|
372
|
-
|
|
373
|
-
return
|
|
19
|
+
console.error(NOTICE);
|
|
20
|
+
return 2;
|
|
374
21
|
}
|
|
375
22
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
function main() {
|
|
379
|
-
const args = parseArgs(process.argv.slice(2));
|
|
380
|
-
const { contract, adapter, contractPath } = loadInputs(args);
|
|
381
|
-
|
|
382
|
-
const outDir = path.join(args.project, args.out || adapter.output?.dir || "src/styles");
|
|
383
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
384
|
-
|
|
385
|
-
const { css: tokensCss, tokenIndex } = genTokens(contract, adapter);
|
|
386
|
-
const rolesCss = genRoles(contract, adapter, tokenIndex);
|
|
387
|
-
const layoutCss = genLayout(contract, adapter, tokenIndex);
|
|
388
|
-
|
|
389
|
-
fs.writeFileSync(path.join(outDir, "ui-tokens.css"), tokensCss);
|
|
390
|
-
fs.writeFileSync(path.join(outDir, "ui-roles.css"), rolesCss);
|
|
391
|
-
fs.writeFileSync(path.join(outDir, "ui-layout.css"), layoutCss);
|
|
392
|
-
|
|
393
|
-
console.log(`契约: ${path.relative(args.project, contractPath)}`);
|
|
394
|
-
console.log(`adapter: ${adapter.adapter}`);
|
|
395
|
-
console.log(`生成: ${path.relative(args.project, outDir)}/ui-tokens.css ui-roles.css ui-layout.css`);
|
|
396
|
-
const roleCount = Object.keys(contract.roles || {}).filter((k) => !k.startsWith("$")).length;
|
|
397
|
-
console.log(`角色 ${roleCount} 个全部翻译;令牌族 ${Object.keys(contract.tokens || {}).join("/")}`);
|
|
398
|
-
if (warnings.length) {
|
|
399
|
-
console.log("");
|
|
400
|
-
console.log("警告(不阻塞,但需回查):");
|
|
401
|
-
for (const w of [...new Set(warnings)]) console.log(` - ${w}`);
|
|
402
|
-
}
|
|
23
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
24
|
+
process.exitCode = main();
|
|
403
25
|
}
|
|
404
|
-
|
|
405
|
-
main();
|
package/scripts/match.py
CHANGED
|
@@ -96,22 +96,41 @@ def print_result(result):
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def batch():
|
|
99
|
-
"""
|
|
99
|
+
"""Assert routing and resource availability; fail on a regression."""
|
|
100
100
|
scenarios = [
|
|
101
|
-
("customer-crud-detail", "客户管理列表,按名称和状态查询,跳转详情"),
|
|
102
|
-
("dashboard-echarts", "仪表盘展示 ECharts 趋势图"),
|
|
103
|
-
("workflow-bpmn", "审批流程 BPMN 页面"),
|
|
104
|
-
("mobile-h5-list", "移动端客户 H5 列表"),
|
|
105
|
-
("file-upload-export", "附件上传、预览和导出"),
|
|
106
|
-
("edit-data-compare", "编辑页增加数据变更对比"),
|
|
101
|
+
("customer-crud-detail", "客户管理列表,按名称和状态查询,跳转详情", {"page-crud", "page-detail"}, {"design-apply"}),
|
|
102
|
+
("dashboard-echarts", "仪表盘展示 ECharts 趋势图", {"visualization"}, set()),
|
|
103
|
+
("workflow-bpmn", "审批流程 BPMN 页面", {"workflow"}, set()),
|
|
104
|
+
("mobile-h5-list", "移动端客户 H5 列表", {"realtime-and-mobile", "page-crud"}, set()),
|
|
105
|
+
("file-upload-export", "附件上传、预览和导出", {"files-and-editors"}, set()),
|
|
106
|
+
("edit-data-compare", "编辑页增加数据变更对比", {"data-compare", "page-detail"}, set()),
|
|
107
|
+
("two-skills", "/project-ui-design 按照设计效果进行还原落地到项目中\n/vue3-temp-skill 代码规范按照此要求", {"design-apply"}, set()),
|
|
108
|
+
("pixel-restore", "按图逐像素还原此页", {"design-apply"}, set()),
|
|
109
|
+
("raw-mockup", "按这个效果图做,保持原有业务", {"design-apply"}, set()),
|
|
110
|
+
("new-handoff", "落地 ui.design-handoff/1.0", {"design-apply"}, set()),
|
|
111
|
+
("old-contract", "复用 visual-contract 设计文件", {"design-apply"}, set()),
|
|
112
|
+
("api-contract", "更新 API 契约和响应类型", {"data-service"}, {"design-apply"}),
|
|
113
|
+
("git-restore", "git 还原这个文件", set(), {"design-apply"}),
|
|
107
114
|
]
|
|
108
115
|
manifest = load_manifest()
|
|
116
|
+
root = SCRIPT_DIR.parent
|
|
117
|
+
for group in manifest["modules"].values():
|
|
118
|
+
for module_id in group["load"]:
|
|
119
|
+
if not (root / "references" / f"{module_id}.md").is_file():
|
|
120
|
+
raise AssertionError(f"Missing reference: {module_id}")
|
|
109
121
|
print(f"match batch: {len(scenarios)} scenarios")
|
|
110
|
-
for label, text in scenarios:
|
|
122
|
+
for label, text, required, forbidden in scenarios:
|
|
111
123
|
result = run(text, manifest)
|
|
112
|
-
groups =
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
groups = {item["id"] for item in result["matched"]}
|
|
125
|
+
if not required.issubset(groups) or forbidden.intersection(groups):
|
|
126
|
+
raise AssertionError(f"{label}: expected {required}, excluded {forbidden}, got {groups}")
|
|
127
|
+
files = result["load_files"]
|
|
128
|
+
if files[0] != "references/core-kernel.md" or "references/code-quality.md" not in files or len(files) != len(set(files)):
|
|
129
|
+
raise AssertionError(f"{label}: invalid reference order or duplicates")
|
|
130
|
+
if "design-apply" in groups and "references/visual-verification.md" not in files:
|
|
131
|
+
raise AssertionError(f"{label}: missing visual verification")
|
|
132
|
+
print(f" [{label}] -> {', '.join(sorted(groups)) or 'core-kernel'}")
|
|
133
|
+
print(f"OK {len(scenarios)}/{len(scenarios)} scenarios; all references exist")
|
|
115
134
|
|
|
116
135
|
|
|
117
136
|
def main():
|
package/scripts/modules.json
CHANGED
|
@@ -100,9 +100,9 @@
|
|
|
100
100
|
"packages": []
|
|
101
101
|
},
|
|
102
102
|
"design-apply": {
|
|
103
|
-
"description": "
|
|
104
|
-
"load": ["design-apply", "code-quality"],
|
|
105
|
-
"keywords": ["
|
|
103
|
+
"description": "设计还原:固定原稿 → 保留项目业务 → 调整视图与样式 → 逐区域视觉及功能验证",
|
|
104
|
+
"load": ["design-apply", "visual-verification", "code-quality"],
|
|
105
|
+
"keywords": ["project-ui-design", "design-handoff", "视觉契约", "visual-contract", "设计还原", "视觉还原", "设计落地", "设计稿", "设计效果", "效果图", "按图", "逐像素", "UI还原", "UI改版", "外观改版", "换肤", "改主题", "全系统风格", "设计文件"],
|
|
106
106
|
"packages": []
|
|
107
107
|
}
|
|
108
108
|
}
|