@tfdesign/b-end 1.0.4
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/AI_READ_FIRST.md +131 -0
- package/LICENSE +21 -0
- package/README.md +353 -0
- package/package.json +67 -0
- package/scripts/check-tfds-contract.mjs +334 -0
- package/scripts/check-tfds-integration.mjs +263 -0
- package/scripts/postinstall-cursor-skill.mjs +382 -0
- package/scripts/setup.mjs +520 -0
- package/skills/tfds/CHECKLIST.md +205 -0
- package/skills/tfds/COMMON_FAILURES.md +238 -0
- package/skills/tfds/DESIGN_PRINCIPLES.md +477 -0
- package/skills/tfds/GLOBAL_DESIGN_RULES.md +636 -0
- package/skills/tfds/LAYOUT_RECIPES.md +140 -0
- package/skills/tfds/LAYOUT_RULES.md +1355 -0
- package/skills/tfds/PAGE_ARCHETYPES.md +201 -0
- package/skills/tfds/SKILL.md +188 -0
- package/skills/tfds/components.index.json +7305 -0
- package/skills/tfds/components.summary.json +1809 -0
- package/src/_b_end_runtime/components/AiSuggestionShared.jsx +166 -0
- package/src/_b_end_runtime/components/Avatar.jsx +325 -0
- package/src/_b_end_runtime/components/Avatar.tokens.js +76 -0
- package/src/_b_end_runtime/components/AvatarGridPreview.jsx +56 -0
- package/src/_b_end_runtime/components/AvatarGroup.jsx +80 -0
- package/src/_b_end_runtime/components/AvatarGroup.tokens.js +28 -0
- package/src/_b_end_runtime/components/Button.jsx +144 -0
- package/src/_b_end_runtime/components/Button.tokens.js +90 -0
- package/src/_b_end_runtime/components/Card.jsx +460 -0
- package/src/_b_end_runtime/components/Card.tokens.js +124 -0
- package/src/_b_end_runtime/components/CardPreview.jsx +51 -0
- package/src/_b_end_runtime/components/ChatBubble.jsx +384 -0
- package/src/_b_end_runtime/components/ChatBubble.tokens.js +60 -0
- package/src/_b_end_runtime/components/ChatBubblePreview.jsx +129 -0
- package/src/_b_end_runtime/components/ChatInput.jsx +1399 -0
- package/src/_b_end_runtime/components/ChatInput.tokens.js +75 -0
- package/src/_b_end_runtime/components/ChatMessage.jsx +2215 -0
- package/src/_b_end_runtime/components/ChatMessage.tokens.js +257 -0
- package/src/_b_end_runtime/components/ChatMessagePreview.jsx +388 -0
- package/src/_b_end_runtime/components/Checkbox.jsx +317 -0
- package/src/_b_end_runtime/components/Checkbox.tokens.js +59 -0
- package/src/_b_end_runtime/components/ConversationList.jsx +1264 -0
- package/src/_b_end_runtime/components/ConversationList.tokens.js +135 -0
- package/src/_b_end_runtime/components/ConversationListPreview.jsx +108 -0
- package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.jsx +324 -0
- package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.tokens.js +69 -0
- package/src/_b_end_runtime/components/DatePicker.jsx +739 -0
- package/src/_b_end_runtime/components/DatePicker.tokens.js +99 -0
- package/src/_b_end_runtime/components/Empty.jsx +141 -0
- package/src/_b_end_runtime/components/Empty.tokens.js +40 -0
- package/src/_b_end_runtime/components/Form.jsx +609 -0
- package/src/_b_end_runtime/components/Form.tokens.js +77 -0
- package/src/_b_end_runtime/components/FormFieldStack.jsx +123 -0
- package/src/_b_end_runtime/components/FormFieldStack.tokens.js +12 -0
- package/src/_b_end_runtime/components/FormTitle.jsx +119 -0
- package/src/_b_end_runtime/components/FormTitle.tokens.js +87 -0
- package/src/_b_end_runtime/components/FullScreenPage.jsx +97 -0
- package/src/_b_end_runtime/components/FullScreenPage.tokens.js +19 -0
- package/src/_b_end_runtime/components/Icon.jsx +172 -0
- package/src/_b_end_runtime/components/Icon.tokens.js +26 -0
- package/src/_b_end_runtime/components/IconGridPreview.jsx +277 -0
- package/src/_b_end_runtime/components/InfoDisplayPanel.jsx +620 -0
- package/src/_b_end_runtime/components/InfoDisplayPanel.tokens.js +71 -0
- package/src/_b_end_runtime/components/InfoDisplayPanelPreview.jsx +133 -0
- package/src/_b_end_runtime/components/Input.jsx +258 -0
- package/src/_b_end_runtime/components/Input.tokens.js +68 -0
- package/src/_b_end_runtime/components/InputNumber.jsx +242 -0
- package/src/_b_end_runtime/components/InputNumber.tokens.js +55 -0
- package/src/_b_end_runtime/components/Modal.jsx +155 -0
- package/src/_b_end_runtime/components/Modal.tokens.js +73 -0
- package/src/_b_end_runtime/components/NavBar.jsx +842 -0
- package/src/_b_end_runtime/components/NavBar.tokens.js +97 -0
- package/src/_b_end_runtime/components/NavBarPreview.jsx +11 -0
- package/src/_b_end_runtime/components/Radio.jsx +227 -0
- package/src/_b_end_runtime/components/Radio.tokens.js +59 -0
- package/src/_b_end_runtime/components/Select.jsx +766 -0
- package/src/_b_end_runtime/components/Select.tokens.js +99 -0
- package/src/_b_end_runtime/components/Sheet.jsx +132 -0
- package/src/_b_end_runtime/components/Sheet.tokens.js +61 -0
- package/src/_b_end_runtime/components/Slider.jsx +346 -0
- package/src/_b_end_runtime/components/Slider.tokens.js +47 -0
- package/src/_b_end_runtime/components/Switch.jsx +124 -0
- package/src/_b_end_runtime/components/Switch.tokens.js +38 -0
- package/src/_b_end_runtime/components/Table.jsx +1338 -0
- package/src/_b_end_runtime/components/Table.tokens.js +147 -0
- package/src/_b_end_runtime/components/TablePreview.jsx +599 -0
- package/src/_b_end_runtime/components/Tabs.jsx +149 -0
- package/src/_b_end_runtime/components/Tabs.tokens.js +102 -0
- package/src/_b_end_runtime/components/Tag.jsx +199 -0
- package/src/_b_end_runtime/components/Tag.tokens.js +171 -0
- package/src/_b_end_runtime/components/TagBar.jsx +1134 -0
- package/src/_b_end_runtime/components/TagBar.tokens.js +75 -0
- package/src/_b_end_runtime/components/TagGridPreview.jsx +23 -0
- package/src/_b_end_runtime/components/TagInput.jsx +382 -0
- package/src/_b_end_runtime/components/TagInput.tokens.js +52 -0
- package/src/_b_end_runtime/components/TextArea.jsx +363 -0
- package/src/_b_end_runtime/components/TextArea.tokens.js +65 -0
- package/src/_b_end_runtime/components/TimePicker.jsx +444 -0
- package/src/_b_end_runtime/components/TimePicker.tokens.js +77 -0
- package/src/_b_end_runtime/components/Toast.jsx +120 -0
- package/src/_b_end_runtime/components/Toast.tokens.js +146 -0
- package/src/_b_end_runtime/components/Tooltip.jsx +282 -0
- package/src/_b_end_runtime/components/Tooltip.tokens.js +48 -0
- package/src/_b_end_runtime/components/TooltipPreview.jsx +50 -0
- package/src/_b_end_runtime/components/Upload.jsx +455 -0
- package/src/_b_end_runtime/components/Upload.tokens.js +47 -0
- package/src/_b_end_runtime/components/avatar-assets/avatar-default.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-1.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-2.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-3.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-4.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-5.png +0 -0
- package/src/_b_end_runtime/components/empty-assets/administrator-1.svg +40 -0
- package/src/_b_end_runtime/components/empty-assets/administrator-2.svg +33 -0
- package/src/_b_end_runtime/components/empty-assets/construction.svg +33 -0
- package/src/_b_end_runtime/components/empty-assets/failure.svg +49 -0
- package/src/_b_end_runtime/components/empty-assets/idle.svg +34 -0
- package/src/_b_end_runtime/components/empty-assets/no-access.svg +36 -0
- package/src/_b_end_runtime/components/empty-assets/no-content.svg +77 -0
- package/src/_b_end_runtime/components/empty-assets/no-result.svg +61 -0
- package/src/_b_end_runtime/components/empty-assets/not-found.svg +46 -0
- package/src/_b_end_runtime/components/empty-assets/success.svg +38 -0
- package/src/_b_end_runtime/components/file-type-assets/batch-report.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/catcat.svg +21 -0
- package/src/_b_end_runtime/components/file-type-assets/code.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/conversation.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/document.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/feishu-card.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/feishu-sheet.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/feishu.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/image.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/index.js +105 -0
- package/src/_b_end_runtime/components/file-type-assets/knowledge.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/pdf.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/pe.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/strategy.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/table.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/webpage.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/xmind.png +0 -0
- package/src/_b_end_runtime/components/icons/icon-data.js +12496 -0
- package/src/_b_end_runtime/components/nav-bar-assets/bytehi-logo-mark.svg +21 -0
- package/src/_b_end_runtime/components/table-assets/avatar.png +0 -0
- package/src/_b_end_runtime/components/table-assets/button.png +0 -0
- package/src/_b_end_runtime/components/table-assets/icon-chevron-down.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/avatar.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/button.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/checkbox.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/icon-chevron-right.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/icon.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/semi-icons-handle.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/semi-icons-tree-triangle-right.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/switch.png +0 -0
- package/src/_b_end_runtime/components/tagShared.js +3 -0
- package/src/_b_end_runtime/components/team-avatar-assets/chengcheng-murphy.png +0 -0
- package/src/_b_end_runtime/components/team-avatar-assets/duan-ran.png +0 -0
- package/src/_b_end_runtime/components/team-avatar-assets/guo-zhezhi.png +0 -0
- package/src/_b_end_runtime/components/team-avatar-assets/li-siru.png +0 -0
- package/src/_b_end_runtime/components/team-avatar-assets/liu-delin.png +0 -0
- package/src/_b_end_runtime/components.js +3499 -0
- package/src/_b_end_runtime/index.js +9 -0
- package/src/_b_end_runtime/page-patterns/BasePageFramePattern.jsx +395 -0
- package/src/_b_end_runtime/page-patterns/ChatConversationPattern.jsx +989 -0
- package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +281 -0
- package/src/_b_end_runtime/page-patterns/CopilotPagePattern.jsx +380 -0
- package/src/_b_end_runtime/page-patterns/CustomerServiceWorkspaceFramePattern.jsx +392 -0
- package/src/_b_end_runtime/page-patterns/IMConversationPattern.jsx +590 -0
- package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +237 -0
- package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +189 -0
- package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +594 -0
- package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +87 -0
- package/src/_b_end_runtime/page-patterns/pageListShared.jsx +177 -0
- package/src/_b_end_runtime/patterns.js +428 -0
- package/src/_b_end_runtime/preview-registry.jsx +4719 -0
- package/src/_b_end_runtime/teamMembers.js +56 -0
- package/src/_b_end_runtime/tokens.js +500 -0
- package/src/index.d.ts +1073 -0
- package/src/index.js +52 -0
- package/theme.css +350 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TFDS 规范契约轻量自检(只读,不改写用户任何配置文件)
|
|
3
|
+
*
|
|
4
|
+
* 在对方项目根目录执行:
|
|
5
|
+
* node node_modules/@tfdesign/b-end/scripts/check-tfds-contract.mjs
|
|
6
|
+
*
|
|
7
|
+
* 目标:在未接入 @tfds/eslint-config 时,提供一层“最低成本”的漂移提示。
|
|
8
|
+
* 注意:这是启发式扫描,不等价于 ESLint 的完整硬门禁。
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
|
|
13
|
+
const projectRoot = path.resolve(process.env.TFDS_PROJECT_ROOT || process.cwd());
|
|
14
|
+
|
|
15
|
+
/** 只扫描常见业务源码目录 */
|
|
16
|
+
const SCAN_DIR_CANDIDATES = ['src', 'app', 'pages', 'components'];
|
|
17
|
+
const SCAN_EXTS = new Set(['.js', '.jsx', '.ts', '.tsx', '.css']);
|
|
18
|
+
|
|
19
|
+
function exists(p) {
|
|
20
|
+
try {
|
|
21
|
+
fs.accessSync(p);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function read(p) {
|
|
29
|
+
try {
|
|
30
|
+
return fs.readFileSync(p, 'utf8');
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function walk(dir, out) {
|
|
37
|
+
const ents = fs.readdirSync(dir, { withFileTypes: true });
|
|
38
|
+
for (const e of ents) {
|
|
39
|
+
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
40
|
+
const p = path.join(dir, e.name);
|
|
41
|
+
if (e.isDirectory()) walk(p, out);
|
|
42
|
+
else if (e.isFile()) out.push(p);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function rel(p) {
|
|
47
|
+
return path.relative(projectRoot, p) || p;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function main() {
|
|
51
|
+
const issues = [];
|
|
52
|
+
const ok = [];
|
|
53
|
+
|
|
54
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
55
|
+
if (!exists(pkgPath)) {
|
|
56
|
+
issues.push(`未找到 package.json(当前检测根目录:${projectRoot})。请在对方项目根目录执行本脚本。`);
|
|
57
|
+
print(issues, ok);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 只读提示:若已接入 eslint-config,可少依赖本脚本
|
|
62
|
+
const depsRaw = read(pkgPath);
|
|
63
|
+
if (depsRaw) {
|
|
64
|
+
try {
|
|
65
|
+
const pkg = JSON.parse(depsRaw);
|
|
66
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
67
|
+
if (deps['@tfds/eslint-config']) ok.push('检测到 @tfds/eslint-config(推荐作为 CI 硬门禁)');
|
|
68
|
+
} catch {
|
|
69
|
+
// ignore
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const scanRoots = SCAN_DIR_CANDIDATES.map((d) => path.join(projectRoot, d)).filter(exists);
|
|
74
|
+
if (scanRoots.length === 0) {
|
|
75
|
+
issues.push(`未找到可扫描的源码目录(候选:${SCAN_DIR_CANDIDATES.join(', ')})。跳过规则扫描。`);
|
|
76
|
+
print(issues, ok);
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
ok.push(`将扫描:${scanRoots.map(rel).join(', ')}`);
|
|
80
|
+
|
|
81
|
+
/** @type {string[]} */
|
|
82
|
+
const files = [];
|
|
83
|
+
for (const r of scanRoots) walk(r, files);
|
|
84
|
+
const targets = files.filter((f) => SCAN_EXTS.has(path.extname(f)));
|
|
85
|
+
if (targets.length === 0) {
|
|
86
|
+
issues.push('未找到可扫描的 .js/.jsx/.ts/.tsx/.css 文件。');
|
|
87
|
+
print(issues, ok);
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Heuristics:
|
|
92
|
+
// - Tailwind 任意值:[...](强烈建议禁止)
|
|
93
|
+
// - CSS / JSX 内硬编码 hex:#fff / #ffffff(建议走 token/theme)
|
|
94
|
+
// - 明显的 spacing 任意值:p-[..] / m-[..] / gap-[..]
|
|
95
|
+
// - 伪 FormTitle:原生 h2/h3/h4 或自绘左竖条标题(应改用 <FormTitle />)
|
|
96
|
+
const reArbitrary = /\[[^\]]+\]/g;
|
|
97
|
+
const reHex = /#[0-9a-fA-F]{3,8}\b/g;
|
|
98
|
+
const reArbSpacing = /\b(?:p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y)-\[[^\]]+\]\b/g;
|
|
99
|
+
const reNativeHeading = /<h[2-4]\b[^>]*>/g;
|
|
100
|
+
const reClassAttr = /\b(?:className|class)\s*=\s*["']([^"']+)["']/g;
|
|
101
|
+
|
|
102
|
+
const hit = new Map(); // file -> collected heuristic hits
|
|
103
|
+
|
|
104
|
+
function collectPseudoTitleHits(text) {
|
|
105
|
+
const hits = [];
|
|
106
|
+
hits.push(...(text.match(reNativeHeading) || []).slice(0, 6));
|
|
107
|
+
|
|
108
|
+
for (const m of text.matchAll(reClassAttr)) {
|
|
109
|
+
const cls = m[1];
|
|
110
|
+
const hasTinyRailWidth = /\bw-1\b|\bw-\[(?:3|4|6)px\]/.test(cls);
|
|
111
|
+
const hasRailHeight = /\bh-(?:3\.5|4|5|6|8)\b|\bh-\[(?:14|16|18|20|24|32)px\]/.test(cls);
|
|
112
|
+
const hasAccentBg = /\bbg-(?:brand|teal|cyan|emerald|green|primary|sky|blue)-/.test(cls);
|
|
113
|
+
const hasLeftBorderTitle = /\bborder-l(?:-\d+)?\b/.test(cls) && /\b(?:font-medium|font-semibold|text-base|text-lg|text-xl)\b/.test(cls);
|
|
114
|
+
if ((hasTinyRailWidth && hasRailHeight && hasAccentBg) || hasLeftBorderTitle) {
|
|
115
|
+
hits.push(`className="${cls}"`);
|
|
116
|
+
}
|
|
117
|
+
if (hits.length >= 10) break;
|
|
118
|
+
}
|
|
119
|
+
return hits.slice(0, 10);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function collectAiEntryWhiteWrapperHits(text) {
|
|
123
|
+
const looksLikeAiEntry =
|
|
124
|
+
/<ChatInput\b/.test(text) &&
|
|
125
|
+
/<Card\b/.test(text) &&
|
|
126
|
+
/(今天想做什么|从模板|模板广场|模板中心|搜索助手|助手广场|Agent|AI\s*入口|AI\s*首页|AI\s*工作台|智能分析入口)/i.test(text);
|
|
127
|
+
|
|
128
|
+
if (!looksLikeAiEntry) return [];
|
|
129
|
+
|
|
130
|
+
const hits = [];
|
|
131
|
+
const classWrapper = /<(?:main|section|div)\b[^>]*(?:className|class)\s*=\s*["'][^"']*(?:bg-surface|bg-white)[^"']*(?:rounded|rounded-)[^"']*["'][^>]*>/gi;
|
|
132
|
+
const classWrapperReverse = /<(?:main|section|div)\b[^>]*(?:className|class)\s*=\s*["'][^"']*(?:rounded|rounded-)[^"']*(?:bg-surface|bg-white)[^"']*["'][^>]*>/gi;
|
|
133
|
+
const styleWrapper = /<(?:main|section|div)\b[^>]*style=\{\{[^}]*background\s*:\s*['"`](?:var\(--color-surface\)|#fff(?:fff)?|white)[^}]*\}\}[^>]*>/gi;
|
|
134
|
+
|
|
135
|
+
hits.push(...(text.match(classWrapper) || []).slice(0, 4));
|
|
136
|
+
hits.push(...(text.match(classWrapperReverse) || []).slice(0, 4));
|
|
137
|
+
hits.push(...(text.match(styleWrapper) || []).slice(0, 4));
|
|
138
|
+
return [...new Set(hits)].slice(0, 8);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function collectMainSpacingHits(text) {
|
|
142
|
+
const hits = [];
|
|
143
|
+
const reMainSpacing = /<main\b[^>]*(?:className|class)\s*=\s*["'][^"']*\b(?:p-2|p-6|p-8|px-6|px-8|py-6|py-8)\b[^"']*["'][^>]*>/gi;
|
|
144
|
+
hits.push(...(text.match(reMainSpacing) || []).slice(0, 8));
|
|
145
|
+
return hits;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function collectCenteredShellHits(text) {
|
|
149
|
+
const hits = [];
|
|
150
|
+
const reCentered =
|
|
151
|
+
/<(?:main|section|div)\b[^>]*(?:className|class)\s*=\s*["'][^"']*\bmax-w-(?:screen|[0-9a-zA-Z_-]+)[^"']*\bmx-auto\b[^"']*["'][^>]*>/gi;
|
|
152
|
+
const reCenteredReverse =
|
|
153
|
+
/<(?:main|section|div)\b[^>]*(?:className|class)\s*=\s*["'][^"']*\bmx-auto\b[^"']*\bmax-w-(?:screen|[0-9a-zA-Z_-]+)[^"']*["'][^>]*>/gi;
|
|
154
|
+
hits.push(...(text.match(reCentered) || []).slice(0, 4));
|
|
155
|
+
hits.push(...(text.match(reCenteredReverse) || []).slice(0, 4));
|
|
156
|
+
return [...new Set(hits)].slice(0, 8);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function collectSurfaceChromeHits(text) {
|
|
160
|
+
const hits = [];
|
|
161
|
+
const classChrome =
|
|
162
|
+
/<(?:main|section|div|Card)\b[^>]*(?:className|class)\s*=\s*["'][^"']*(?:bg-surface|bg-white)[^"']*\b(?:border|ring|shadow)\b[^"']*["'][^>]*>/gi;
|
|
163
|
+
const styleChrome =
|
|
164
|
+
/<(?:main|section|div)\b[^>]*style=\{\{[^}]*background\s*:\s*['"`](?:var\(--color-surface\)|white|#fff(?:fff)?)[^}]*\b(?:border|borderColor|boxShadow)\b[^}]*\}\}[^>]*>/gi;
|
|
165
|
+
hits.push(...(text.match(classChrome) || []).slice(0, 4));
|
|
166
|
+
hits.push(...(text.match(styleChrome) || []).slice(0, 4));
|
|
167
|
+
return [...new Set(hits)].slice(0, 8);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function collectAiEntryCardColorHits(text) {
|
|
171
|
+
const looksLikeAiEntry =
|
|
172
|
+
/<ChatInput\b/.test(text) &&
|
|
173
|
+
/<Card\b/.test(text) &&
|
|
174
|
+
/(今天想做什么|从模板|模板广场|模板中心|搜索助手|助手广场|Agent|AI\s*入口|AI\s*首页|AI\s*工作台|智能分析入口)/i.test(text);
|
|
175
|
+
if (!looksLikeAiEntry) return [];
|
|
176
|
+
return (text.match(/<Card\b[^>]*\bcolor\s*=\s*["']grey["'][^>]*>/gi) || []).slice(0, 8);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function collectTabsSizeDriftHits(text) {
|
|
180
|
+
const looksLikeContentTabs =
|
|
181
|
+
/(筛选|filter|FilterBar|模板|卡片|Card|panel|Panel|Playground|工作区|白卡|section|Section)/i.test(text);
|
|
182
|
+
if (!looksLikeContentTabs) return [];
|
|
183
|
+
const hits = [];
|
|
184
|
+
const reTabsLarge = /<Tabs\b[^>]*\bsize\s*=\s*["'](?:md|lg)["'][^>]*>/gi;
|
|
185
|
+
for (const m of text.matchAll(reTabsLarge)) {
|
|
186
|
+
const before = text.slice(Math.max(0, m.index - 360), m.index);
|
|
187
|
+
const match = m[0];
|
|
188
|
+
const allowedTopNavContext = /(TopBar|Header|PAGE_TABS|页面级|顶导|一级导航|顶部\s*header)/i.test(before);
|
|
189
|
+
if (allowedTopNavContext) continue;
|
|
190
|
+
hits.push(match);
|
|
191
|
+
if (hits.length >= 8) break;
|
|
192
|
+
}
|
|
193
|
+
return hits;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function collectFilterFullWidthHits(text) {
|
|
197
|
+
const looksLikeFilterBar = /(function|const)\s+\w*Filter\w*|筛选栏|FilterBar|filter bar/i.test(text);
|
|
198
|
+
if (!looksLikeFilterBar) return [];
|
|
199
|
+
const hits = [];
|
|
200
|
+
const inputFullWidth = /<Input\b[^>]*(?:className|class)\s*=\s*["'][^"']*\bw-full\b[^"']*["'][^>]*>/gi;
|
|
201
|
+
const inputStyleFull = /<Input\b[^>]*style=\{\{[^}]*(?:width\s*:\s*['"`]100%|flex\s*:\s*['"`]1)[^}]*\}\}[^>]*>/gi;
|
|
202
|
+
hits.push(...(text.match(inputFullWidth) || []).slice(0, 4));
|
|
203
|
+
hits.push(...(text.match(inputStyleFull) || []).slice(0, 4));
|
|
204
|
+
return [...new Set(hits)].slice(0, 8);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function collectRemoteAvatarHits(text) {
|
|
208
|
+
const hits = [];
|
|
209
|
+
const componentRemote =
|
|
210
|
+
/<(?:Avatar|ChatBubble|NavBar|ChatMessage)\b[^>]*(?:src|avatarSrc)\s*=\s*["']https?:\/\/[^"']+["'][^>]*>/gi;
|
|
211
|
+
const objectRemote =
|
|
212
|
+
/\b(?:avatarSrc|avatarUrl|avatarURL|profileImage|profilePic)\s*:\s*["']https?:\/\/[^"']+["']/gi;
|
|
213
|
+
const roundedImgRemote =
|
|
214
|
+
/<img\b(?=[^>]*src\s*=\s*["']https?:\/\/[^"']+["'])(?=[^>]*(?:rounded-full|avatar|头像))[^>]*>/gi;
|
|
215
|
+
|
|
216
|
+
hits.push(...(text.match(componentRemote) || []).slice(0, 4));
|
|
217
|
+
hits.push(...(text.match(objectRemote) || []).slice(0, 4));
|
|
218
|
+
hits.push(...(text.match(roundedImgRemote) || []).slice(0, 4));
|
|
219
|
+
return [...new Set(hits)].slice(0, 8);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const f of targets) {
|
|
223
|
+
const t = read(f);
|
|
224
|
+
if (!t) continue;
|
|
225
|
+
const arbitrary = (t.match(reArbitrary) || []).slice(0, 10);
|
|
226
|
+
const hex = (t.match(reHex) || []).slice(0, 10);
|
|
227
|
+
const arbSpacing = (t.match(reArbSpacing) || []).slice(0, 10);
|
|
228
|
+
const pseudoTitle = collectPseudoTitleHits(t);
|
|
229
|
+
const aiEntryWhiteWrapper = collectAiEntryWhiteWrapperHits(t);
|
|
230
|
+
const mainSpacing = collectMainSpacingHits(t);
|
|
231
|
+
const centeredShell = collectCenteredShellHits(t);
|
|
232
|
+
const surfaceChrome = collectSurfaceChromeHits(t);
|
|
233
|
+
const aiEntryCardColor = collectAiEntryCardColorHits(t);
|
|
234
|
+
const tabsSizeDrift = collectTabsSizeDriftHits(t);
|
|
235
|
+
const filterFullWidth = collectFilterFullWidthHits(t);
|
|
236
|
+
const remoteAvatar = collectRemoteAvatarHits(t);
|
|
237
|
+
if (
|
|
238
|
+
arbitrary.length ||
|
|
239
|
+
hex.length ||
|
|
240
|
+
arbSpacing.length ||
|
|
241
|
+
pseudoTitle.length ||
|
|
242
|
+
aiEntryWhiteWrapper.length ||
|
|
243
|
+
mainSpacing.length ||
|
|
244
|
+
centeredShell.length ||
|
|
245
|
+
surfaceChrome.length ||
|
|
246
|
+
aiEntryCardColor.length ||
|
|
247
|
+
tabsSizeDrift.length ||
|
|
248
|
+
filterFullWidth.length ||
|
|
249
|
+
remoteAvatar.length
|
|
250
|
+
) {
|
|
251
|
+
hit.set(f, {
|
|
252
|
+
arbitrary,
|
|
253
|
+
hex,
|
|
254
|
+
arbSpacing,
|
|
255
|
+
pseudoTitle,
|
|
256
|
+
aiEntryWhiteWrapper,
|
|
257
|
+
mainSpacing,
|
|
258
|
+
centeredShell,
|
|
259
|
+
surfaceChrome,
|
|
260
|
+
aiEntryCardColor,
|
|
261
|
+
tabsSizeDrift,
|
|
262
|
+
filterFullWidth,
|
|
263
|
+
remoteAvatar,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (hit.size === 0) {
|
|
269
|
+
ok.push('未发现明显的 Tailwind 任意值 / hex 硬编码(启发式)');
|
|
270
|
+
print(issues, ok);
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const [f, v] of hit.entries()) {
|
|
275
|
+
if (v.arbSpacing.length) {
|
|
276
|
+
issues.push(`[${rel(f)}] 发现 spacing 任意值(示例):${v.arbSpacing.join(', ')}`);
|
|
277
|
+
}
|
|
278
|
+
// 注意:arbitrary 会包含很多场景(如 grid-cols-[...]),这里不全禁,但先提示
|
|
279
|
+
if (v.arbitrary.length) {
|
|
280
|
+
issues.push(`[${rel(f)}] 发现 Tailwind 任意值(示例):${v.arbitrary.join(', ')}`);
|
|
281
|
+
}
|
|
282
|
+
if (v.hex.length) {
|
|
283
|
+
issues.push(`[${rel(f)}] 发现 hex 硬编码颜色(示例):${v.hex.join(', ')}`);
|
|
284
|
+
}
|
|
285
|
+
if (v.pseudoTitle.length) {
|
|
286
|
+
issues.push(`[${rel(f)}] 发现疑似手写标题 / 伪 FormTitle(示例):${v.pseudoTitle.join(', ')}。白卡、面板、卡片、section 标题必须改用 <FormTitle />。`);
|
|
287
|
+
}
|
|
288
|
+
if (v.aiEntryWhiteWrapper.length) {
|
|
289
|
+
issues.push(`[${rel(f)}] AI 入口页疑似被大白卡包住(示例):${v.aiEntryWhiteWrapper.join(', ')}。ChatHomePagePattern 应直接坐浅灰底,Hero / ChatInput / Tabs / 模板 Card 网格外层不要包 bg-surface / 白色圆角大容器;模板卡自身用 <Card color="white" />。`);
|
|
290
|
+
}
|
|
291
|
+
if (v.mainSpacing.length) {
|
|
292
|
+
issues.push(`[${rel(f)}] main 外框间距疑似不符合 TFDS(示例):${v.mainSpacing.join(', ')}。通用白卡工作区 main 应使用 p-4(16px),禁止 p-2 / p-6 / p-8 / px-6 / px-8。`);
|
|
293
|
+
}
|
|
294
|
+
if (v.centeredShell.length) {
|
|
295
|
+
issues.push(`[${rel(f)}] 主工作区疑似被 max-w + mx-auto 居中收窄(示例):${v.centeredShell.join(', ')}。TFDS B 端页应撑满浏览器可用宽度,禁止在 main/主内容外层套居中容器。`);
|
|
296
|
+
}
|
|
297
|
+
if (v.surfaceChrome.length) {
|
|
298
|
+
issues.push(`[${rel(f)}] 白色工作卡疑似带外层描边/阴影(示例):${v.surfaceChrome.join(', ')}。最外层 WorkSurface 只允许纯白底 + 12px 圆角,不加 border/ring/shadow;内部控件描边可保留。`);
|
|
299
|
+
}
|
|
300
|
+
if (v.aiEntryCardColor.length) {
|
|
301
|
+
issues.push(`[${rel(f)}] AI 入口页模板卡疑似使用 Card color="grey"(示例):${v.aiEntryCardColor.join(', ')}。ChatHome 父级是浅灰底,模板卡必须用 <Card color="white" />。`);
|
|
302
|
+
}
|
|
303
|
+
if (v.tabsSizeDrift.length) {
|
|
304
|
+
issues.push(`[${rel(f)}] 内容区 Tabs 疑似使用 md/lg 尺寸(示例):${v.tabsSizeDrift.join(', ')}。白卡内、筛选维度、卡片内、Playground 面板内 Tabs 默认 size="sm";md/lg 只用于平台顶部一级导航。`);
|
|
305
|
+
}
|
|
306
|
+
if (v.filterFullWidth.length) {
|
|
307
|
+
issues.push(`[${rel(f)}] 筛选栏 Input 疑似误用全宽(示例):${v.filterFullWidth.join(', ')}。FilterBar 搜索框默认约 240px,枚举/状态/时间控件 160-200px;白卡正文单列字段才默认 w-full。`);
|
|
308
|
+
}
|
|
309
|
+
if (v.remoteAvatar.length) {
|
|
310
|
+
issues.push(`[${rel(f)}] 头像疑似使用随机外链或手写 img(示例):${v.remoteAvatar.join(', ')}。TFDS 人像类头像默认应使用包内本地成员头像素材;Avatar/ChatBubble/NavBar/Table avatar/avatarText 缺省会自动取本地成员头像,只有业务真实接口返回头像 URL 时才传远程 src。`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
issues.push('建议:接入 @tfds/eslint-config 并在 CI 执行 npx eslint . 作为硬门禁(比本脚本更准确)。');
|
|
315
|
+
print(issues, ok);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function print(issues, ok) {
|
|
320
|
+
// eslint-disable-next-line no-console
|
|
321
|
+
console.log('\n[tfds-contract-check] 项目根:', projectRoot);
|
|
322
|
+
// eslint-disable-next-line no-console
|
|
323
|
+
console.log('\n── 已通过 ──');
|
|
324
|
+
if (ok.length === 0) console.log('(无)');
|
|
325
|
+
else ok.forEach((l) => console.log('✓', l));
|
|
326
|
+
// eslint-disable-next-line no-console
|
|
327
|
+
console.log('\n── 待处理 / 提示 ──');
|
|
328
|
+
if (issues.length === 0) console.log('(无)— 未发现明显漂移信号。');
|
|
329
|
+
else issues.forEach((l) => console.log('!', l));
|
|
330
|
+
// eslint-disable-next-line no-console
|
|
331
|
+
console.log('');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
main();
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TFDS 集成自检(只读,不改写用户任何配置文件)
|
|
3
|
+
*
|
|
4
|
+
* 在对方项目根目录执行:
|
|
5
|
+
* node node_modules/@tfdesign/b-end/scripts/check-tfds-integration.mjs
|
|
6
|
+
*
|
|
7
|
+
* 或通过环境变量指定根目录:
|
|
8
|
+
* TFDS_PROJECT_ROOT=/path/to/app node .../check-tfds-integration.mjs
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = path.dirname(__filename);
|
|
17
|
+
|
|
18
|
+
const projectRoot = path.resolve(process.env.TFDS_PROJECT_ROOT || process.cwd());
|
|
19
|
+
const codexHome = process.env.CODEX_HOME
|
|
20
|
+
? path.resolve(process.env.CODEX_HOME)
|
|
21
|
+
: path.join(os.homedir(), '.codex');
|
|
22
|
+
|
|
23
|
+
function read(p) {
|
|
24
|
+
try {
|
|
25
|
+
return fs.readFileSync(p, 'utf8');
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function exists(p) {
|
|
32
|
+
try {
|
|
33
|
+
fs.accessSync(p);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const REQUIRED_TFDS_SKILL_FILES = [
|
|
41
|
+
'SKILL.md',
|
|
42
|
+
'DESIGN_PRINCIPLES.md',
|
|
43
|
+
'PAGE_ARCHETYPES.md',
|
|
44
|
+
'LAYOUT_RECIPES.md',
|
|
45
|
+
'components.summary.json',
|
|
46
|
+
'components.index.json',
|
|
47
|
+
'LAYOUT_RULES.md',
|
|
48
|
+
'GLOBAL_DESIGN_RULES.md',
|
|
49
|
+
'CHECKLIST.md',
|
|
50
|
+
'COMMON_FAILURES.md',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function displayPath(p) {
|
|
54
|
+
const home = os.homedir();
|
|
55
|
+
if (p === home) return '~';
|
|
56
|
+
if (p.startsWith(home + path.sep)) return path.join('~', path.relative(home, p));
|
|
57
|
+
const rel = path.relative(projectRoot, p);
|
|
58
|
+
return rel && !rel.startsWith('..') ? rel : p;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 常见入口样式候选(存在则检查内容) */
|
|
62
|
+
function candidateCssFiles() {
|
|
63
|
+
const rels = [
|
|
64
|
+
'src/index.css',
|
|
65
|
+
'src/main.css',
|
|
66
|
+
'src/styles.css',
|
|
67
|
+
'src/global.css',
|
|
68
|
+
'app/globals.css',
|
|
69
|
+
'styles/globals.css',
|
|
70
|
+
'index.css',
|
|
71
|
+
];
|
|
72
|
+
return rels.map((r) => path.join(projectRoot, r)).filter(exists);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function candidateConfigFiles() {
|
|
76
|
+
const names = [
|
|
77
|
+
'vite.config.js',
|
|
78
|
+
'vite.config.mjs',
|
|
79
|
+
'vite.config.ts',
|
|
80
|
+
'vite.config.mts',
|
|
81
|
+
'tailwind.config.js',
|
|
82
|
+
'tailwind.config.mjs',
|
|
83
|
+
'tailwind.config.ts',
|
|
84
|
+
];
|
|
85
|
+
return names.map((n) => path.join(projectRoot, n)).filter(exists);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hasThemeImport(cssText) {
|
|
89
|
+
return /@tfdesign\/b-end\/theme\.css/.test(cssText);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function hasTailwindImport(cssText) {
|
|
93
|
+
return /@import\s+["']tailwindcss["']/.test(cssText) || /@import\s+['"]tailwindcss['"]/.test(cssText);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hasSourceScanForPackage(cssText) {
|
|
97
|
+
return /@source[^;]*@tfdesign\/b-end|@source[^;]*node_modules\/@tfdesign\/b-end/i.test(cssText);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function hasContentScanInConfigs(text) {
|
|
101
|
+
return /node_modules\/@tfdesign\/b-end|@tfdesign\/b-end\/\*\*/.test(text);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function main() {
|
|
105
|
+
const issues = [];
|
|
106
|
+
const ok = [];
|
|
107
|
+
|
|
108
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
109
|
+
const pkgRaw = read(pkgPath);
|
|
110
|
+
if (!pkgRaw) {
|
|
111
|
+
issues.push(`未找到 package.json(当前检测根目录:${projectRoot})。请在对方项目根目录执行本脚本。`);
|
|
112
|
+
printReport(issues, ok);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let pkg;
|
|
117
|
+
try {
|
|
118
|
+
pkg = JSON.parse(pkgRaw);
|
|
119
|
+
} catch {
|
|
120
|
+
issues.push('package.json 无法解析为 JSON');
|
|
121
|
+
printReport(issues, ok);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies };
|
|
126
|
+
if (!deps['@tfdesign/b-end']) {
|
|
127
|
+
issues.push('package.json 中未声明依赖 @tfdesign/b-end(dependencies / devDependencies / peerDependencies)。');
|
|
128
|
+
} else {
|
|
129
|
+
ok.push('已声明依赖 @tfdesign/b-end');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!exists(path.join(projectRoot, 'node_modules', '@tfdesign', 'b-end'))) {
|
|
133
|
+
issues.push('未找到 node_modules/@tfdesign/b-end/。请先在该目录执行 npm install。');
|
|
134
|
+
} else {
|
|
135
|
+
ok.push('node_modules/@tfdesign/b-end 目录存在');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const cssFiles = candidateCssFiles();
|
|
139
|
+
let themeOk = false;
|
|
140
|
+
let tailwindOk = false;
|
|
141
|
+
let sourceOk = false;
|
|
142
|
+
|
|
143
|
+
if (cssFiles.length === 0) {
|
|
144
|
+
issues.push('未在常见路径找到入口 CSS(如 src/index.css)。请手动确认已 @import "@tfdesign/b-end/theme.css"。可重新执行 npm i 触发 postinstall 自动补丁。');
|
|
145
|
+
} else {
|
|
146
|
+
ok.push(`将检查以下样式文件:${cssFiles.map((f) => path.relative(projectRoot, f)).join(', ')}`);
|
|
147
|
+
for (const f of cssFiles) {
|
|
148
|
+
const t = read(f);
|
|
149
|
+
if (!t) continue;
|
|
150
|
+
if (hasThemeImport(t)) themeOk = true;
|
|
151
|
+
if (hasTailwindImport(t)) tailwindOk = true;
|
|
152
|
+
if (hasSourceScanForPackage(t)) sourceOk = true;
|
|
153
|
+
}
|
|
154
|
+
if (!themeOk) issues.push('入口 CSS 中未发现 @import "@tfdesign/b-end/theme.css"(须在 @import "tailwindcss" 之后)。可重新执行 npm i 触发 postinstall 自动补丁,或手动追加。');
|
|
155
|
+
else ok.push('已检测到 theme.css 引入');
|
|
156
|
+
|
|
157
|
+
if (!tailwindOk) issues.push('入口 CSS 中未发现 @import "tailwindcss"(请确认 Tailwind v4 入口写法与官方一致)。');
|
|
158
|
+
else ok.push('已检测到 tailwindcss 引入');
|
|
159
|
+
|
|
160
|
+
if (sourceOk) ok.push('已检测到 @source 扫描 node_modules/@tfdesign/b-end');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let configScanHint = false;
|
|
164
|
+
for (const cf of candidateConfigFiles()) {
|
|
165
|
+
const t = read(cf);
|
|
166
|
+
if (t && hasContentScanInConfigs(t)) {
|
|
167
|
+
configScanHint = true;
|
|
168
|
+
ok.push(`在 ${path.relative(projectRoot, cf)} 中发现对 @tfdesign/b-end 的 content/扫描配置线索`);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (cssFiles.length > 0 && !sourceOk && !configScanHint) {
|
|
174
|
+
issues.push(
|
|
175
|
+
'未检测到 Tailwind 对 @tfdesign/b-end 的扫描:入口 CSS 缺少 @source … @tfdesign/b-end,且配置文件中无 content 线索。可重新执行 npm i 触发 postinstall 自动补丁,详见 README.md → "TL;DR 快速开始"。',
|
|
176
|
+
);
|
|
177
|
+
} else if (cssFiles.length > 0 && !sourceOk && configScanHint) {
|
|
178
|
+
ok.push('未使用 @source,但配置文件中出现对 @tfdesign/b-end 的扫描路径(方式 B),可接受');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function checkSkillDir(label, dir, options = {}) {
|
|
182
|
+
const { required = false, whenParentExists = null, successSuffix = '' } = options;
|
|
183
|
+
const parentExists = whenParentExists ? exists(whenParentExists) : false;
|
|
184
|
+
|
|
185
|
+
if (!exists(dir)) {
|
|
186
|
+
if (required || parentExists) {
|
|
187
|
+
issues.push(`未找到 ${label} Skill:${displayPath(dir)}。请执行 npm i 触发 postinstall 自动复制,或从包内 skills/tfds 目录手动复制。`);
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const missing = REQUIRED_TFDS_SKILL_FILES.filter((file) => !exists(path.join(dir, file)));
|
|
193
|
+
if (missing.length > 0) {
|
|
194
|
+
issues.push(
|
|
195
|
+
`${label} Skill 目录存在但不完整:${displayPath(dir)} 缺少 ${missing.join(', ')}。这通常是旧版 Skill 残留;请删除该目录后重新执行 npm i,或重新复制 node_modules/@tfdesign/b-end/skills/tfds/。`,
|
|
196
|
+
);
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
ok.push(`已安装 ${label} Skill:${displayPath(dir)}${successSuffix}`);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
checkSkillDir('Cursor', path.join(projectRoot, '.cursor', 'skills', 'tfds'), {
|
|
205
|
+
required: true,
|
|
206
|
+
successSuffix: '(唤起:/tfds)',
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
checkSkillDir('Claude Code', path.join(projectRoot, '.claude', 'skills', 'tfds'), {
|
|
210
|
+
whenParentExists: path.join(projectRoot, '.claude'),
|
|
211
|
+
successSuffix: '(唤起:/tfds)',
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
checkSkillDir('Codex 项目内参考', path.join(projectRoot, '.codex', 'skills', 'tfds'), {
|
|
215
|
+
whenParentExists: path.join(projectRoot, '.codex'),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
checkSkillDir('Codex 用户级', path.join(codexHome, 'skills', 'tfds'), {
|
|
219
|
+
required: true,
|
|
220
|
+
successSuffix: '(新开/重启 Codex 后 /tfds)',
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
checkSkillDir('Agents', path.join(projectRoot, '.agents', 'skills', 'tfds'), {
|
|
224
|
+
whenParentExists: path.join(projectRoot, '.agents'),
|
|
225
|
+
successSuffix: '(通用 agent 兜底)',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Trae:故意不自动写入 .trae/rules/(避免 always-on 污染)
|
|
229
|
+
if (exists(path.join(projectRoot, '.trae'))) {
|
|
230
|
+
ok.push('Trae 用户提示:把 skills/tfds/ 加入 Trae 项目知识库(命名 tfds),之后 @tfds 引用。本工具不会写入 .trae/rules/。');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
printReport(issues, ok);
|
|
234
|
+
|
|
235
|
+
const critical = issues.some(
|
|
236
|
+
(m) =>
|
|
237
|
+
m.includes('未声明依赖') ||
|
|
238
|
+
m.includes('未找到 node_modules/@tfdesign') ||
|
|
239
|
+
m.includes('未发现 @import "@tfdesign/b-end/theme.css"'),
|
|
240
|
+
);
|
|
241
|
+
process.exit(critical ? 1 : 0);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function printReport(issues, ok) {
|
|
245
|
+
// eslint-disable-next-line no-console
|
|
246
|
+
console.log('\n[tfds-integration-check] 项目根:', projectRoot);
|
|
247
|
+
// eslint-disable-next-line no-console
|
|
248
|
+
console.log('\n── 已通过 ──');
|
|
249
|
+
if (ok.length === 0) // eslint-disable-next-line no-console
|
|
250
|
+
console.log('(无)');
|
|
251
|
+
else ok.forEach((l) => console.log('✓', l));
|
|
252
|
+
// eslint-disable-next-line no-console
|
|
253
|
+
console.log('\n── 待处理 / 提示 ──');
|
|
254
|
+
if (issues.length === 0) // eslint-disable-next-line no-console
|
|
255
|
+
console.log('(无)— 基础集成项已齐。');
|
|
256
|
+
else issues.forEach((l) => console.log('!', l));
|
|
257
|
+
// eslint-disable-next-line no-console
|
|
258
|
+
console.log('\n详细步骤见:node_modules/@tfdesign/b-end/README.md → "TL;DR 快速开始"');
|
|
259
|
+
// eslint-disable-next-line no-console
|
|
260
|
+
console.log('');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
main();
|