@fragments-sdk/mcp 0.6.0 → 0.6.2
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/dist/bin.js +122 -21
- package/dist/bin.js.map +1 -1
- package/dist/chunk-2W7DAUUS.js +107 -0
- package/dist/chunk-2W7DAUUS.js.map +1 -0
- package/dist/chunk-4SVS3AA3.js +78 -0
- package/dist/chunk-4SVS3AA3.js.map +1 -0
- package/dist/chunk-FGIBLPSU.js +29 -0
- package/dist/chunk-FGIBLPSU.js.map +1 -0
- package/dist/chunk-NVHGG7GW.js +630 -0
- package/dist/chunk-NVHGG7GW.js.map +1 -0
- package/dist/chunk-VRPDT3Y6.js +52 -0
- package/dist/chunk-VRPDT3Y6.js.map +1 -0
- package/dist/chunk-WBOVO43F.js +2481 -0
- package/dist/chunk-WBOVO43F.js.map +1 -0
- package/dist/config-TUFA5J2S.js +7 -0
- package/dist/config-TUFA5J2S.js.map +1 -0
- package/dist/constants-YXOTMY3I.js +9 -0
- package/dist/constants-YXOTMY3I.js.map +1 -0
- package/dist/dist-V7D67NXS.js +1093 -0
- package/dist/dist-V7D67NXS.js.map +1 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/init.js +139 -0
- package/dist/init.js.map +1 -0
- package/dist/rules-WGBCECAK.js +7 -0
- package/dist/rules-WGBCECAK.js.map +1 -0
- package/dist/sass.node-4XJK6YBF-2NJM7G64.js +130796 -0
- package/dist/sass.node-4XJK6YBF-2NJM7G64.js.map +1 -0
- package/dist/server.js +3 -1
- package/package.json +22 -4
- package/dist/chunk-VHHXL3YB.js +0 -2274
- package/dist/chunk-VHHXL3YB.js.map +0 -1
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
// src/adapters/auto-extract.ts
|
|
2
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
3
|
+
import { join as join3, relative, sep } from "path";
|
|
4
|
+
|
|
5
|
+
// src/adapters/discover-components.ts
|
|
6
|
+
import { readdirSync, existsSync } from "fs";
|
|
7
|
+
import { join, extname, basename } from "path";
|
|
8
|
+
var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
9
|
+
"node_modules",
|
|
10
|
+
"dist",
|
|
11
|
+
"build",
|
|
12
|
+
".next",
|
|
13
|
+
".nuxt",
|
|
14
|
+
"coverage",
|
|
15
|
+
"__tests__",
|
|
16
|
+
"__mocks__",
|
|
17
|
+
".git",
|
|
18
|
+
".cache",
|
|
19
|
+
".turbo",
|
|
20
|
+
"out"
|
|
21
|
+
]);
|
|
22
|
+
var EXCLUDED_PATTERNS = [
|
|
23
|
+
/\.test\./,
|
|
24
|
+
/\.spec\./,
|
|
25
|
+
/\.stories\./,
|
|
26
|
+
/\.story\./,
|
|
27
|
+
/\.fragment\./,
|
|
28
|
+
/\.d\.ts$/,
|
|
29
|
+
/\.config\./,
|
|
30
|
+
/\.mock\./,
|
|
31
|
+
/\.fixture\./
|
|
32
|
+
];
|
|
33
|
+
function discoverComponentFiles(projectRoot) {
|
|
34
|
+
const results = [];
|
|
35
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36
|
+
const scanDirs = [
|
|
37
|
+
"src/components",
|
|
38
|
+
"components",
|
|
39
|
+
"lib/components",
|
|
40
|
+
"src/ui",
|
|
41
|
+
"lib/ui",
|
|
42
|
+
"packages"
|
|
43
|
+
].map((d) => join(projectRoot, d)).filter((d) => existsSync(d));
|
|
44
|
+
if (scanDirs.length === 0) {
|
|
45
|
+
const srcDir = join(projectRoot, "src");
|
|
46
|
+
if (existsSync(srcDir)) scanDirs.push(srcDir);
|
|
47
|
+
}
|
|
48
|
+
for (const dir of scanDirs) {
|
|
49
|
+
walkDir(dir, results, seen);
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
function walkDir(dir, results, seen, depth = 0) {
|
|
54
|
+
if (depth > 6) return;
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
58
|
+
} catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.name.startsWith(".")) continue;
|
|
63
|
+
if (entry.isDirectory()) {
|
|
64
|
+
if (EXCLUDED_DIRS.has(entry.name)) continue;
|
|
65
|
+
walkDir(join(dir, entry.name), results, seen, depth + 1);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!entry.isFile()) continue;
|
|
69
|
+
const ext = extname(entry.name);
|
|
70
|
+
if (ext !== ".tsx" && ext !== ".jsx") continue;
|
|
71
|
+
if (EXCLUDED_PATTERNS.some((p) => p.test(entry.name))) continue;
|
|
72
|
+
const filePath = join(dir, entry.name);
|
|
73
|
+
if (seen.has(filePath)) continue;
|
|
74
|
+
seen.add(filePath);
|
|
75
|
+
const name = inferComponentName(entry.name, dir);
|
|
76
|
+
if (name) {
|
|
77
|
+
results.push({ filePath, componentName: name });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function inferComponentName(fileName, dirPath) {
|
|
82
|
+
const withoutExt = fileName.replace(/\.(tsx|jsx)$/, "");
|
|
83
|
+
if (withoutExt === "index") {
|
|
84
|
+
return basename(dirPath);
|
|
85
|
+
}
|
|
86
|
+
return withoutExt;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/adapters/scan-tokens.ts
|
|
90
|
+
import { readdirSync as readdirSync2, readFileSync, existsSync as existsSync2 } from "fs";
|
|
91
|
+
import { join as join2, extname as extname2 } from "path";
|
|
92
|
+
function scanTokens(projectRoot) {
|
|
93
|
+
const cssFiles = discoverCssFiles(projectRoot);
|
|
94
|
+
if (cssFiles.length === 0) return void 0;
|
|
95
|
+
const allTokens = [];
|
|
96
|
+
let prefix = "";
|
|
97
|
+
for (const filePath of cssFiles) {
|
|
98
|
+
try {
|
|
99
|
+
const content = readFileSync(filePath, "utf-8");
|
|
100
|
+
const tokens = extractCustomProperties(content);
|
|
101
|
+
allTokens.push(...tokens);
|
|
102
|
+
} catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (allTokens.length === 0) return void 0;
|
|
107
|
+
prefix = detectPrefix(allTokens.map((t) => t.name));
|
|
108
|
+
const categories = {};
|
|
109
|
+
for (const token of allTokens) {
|
|
110
|
+
const category = inferTokenCategory(token.name);
|
|
111
|
+
if (!categories[category]) categories[category] = [];
|
|
112
|
+
if (!categories[category].some((t) => t.name === token.name)) {
|
|
113
|
+
categories[category].push(token);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
prefix,
|
|
118
|
+
total: Object.values(categories).reduce((sum, arr) => sum + arr.length, 0),
|
|
119
|
+
categories
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function discoverCssFiles(projectRoot) {
|
|
123
|
+
const files = [];
|
|
124
|
+
const searchDirs = [
|
|
125
|
+
"src",
|
|
126
|
+
"styles",
|
|
127
|
+
"css",
|
|
128
|
+
"app"
|
|
129
|
+
].map((d) => join2(projectRoot, d)).filter((d) => existsSync2(d));
|
|
130
|
+
searchDirs.push(projectRoot);
|
|
131
|
+
for (const dir of searchDirs) {
|
|
132
|
+
try {
|
|
133
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
if (!entry.isFile()) continue;
|
|
136
|
+
const ext = extname2(entry.name);
|
|
137
|
+
if (ext === ".css" || ext === ".scss") {
|
|
138
|
+
files.push(join2(dir, entry.name));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const srcDir = join2(projectRoot, "src");
|
|
146
|
+
if (existsSync2(srcDir)) {
|
|
147
|
+
try {
|
|
148
|
+
for (const subEntry of readdirSync2(srcDir, { withFileTypes: true })) {
|
|
149
|
+
if (subEntry.isDirectory() && ["styles", "css", "theme", "tokens"].includes(subEntry.name)) {
|
|
150
|
+
const subDir = join2(srcDir, subEntry.name);
|
|
151
|
+
for (const file of readdirSync2(subDir, { withFileTypes: true })) {
|
|
152
|
+
if (file.isFile() && (file.name.endsWith(".css") || file.name.endsWith(".scss"))) {
|
|
153
|
+
files.push(join2(subDir, file.name));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return [...new Set(files)];
|
|
162
|
+
}
|
|
163
|
+
function extractCustomProperties(content) {
|
|
164
|
+
const tokens = [];
|
|
165
|
+
let inRelevantBlock = false;
|
|
166
|
+
let braceDepth = 0;
|
|
167
|
+
const relevantSelectors = [":root", ".dark", "[data-theme", "@theme"];
|
|
168
|
+
const lines = content.split("\n");
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
const trimmed = line.trim();
|
|
171
|
+
if (!inRelevantBlock && braceDepth === 0) {
|
|
172
|
+
if (relevantSelectors.some((sel) => trimmed.startsWith(sel) || trimmed.includes(sel))) {
|
|
173
|
+
if (trimmed.includes("{")) {
|
|
174
|
+
inRelevantBlock = true;
|
|
175
|
+
braceDepth = 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (inRelevantBlock) {
|
|
180
|
+
for (const ch of trimmed) {
|
|
181
|
+
if (ch === "{") braceDepth++;
|
|
182
|
+
else if (ch === "}") {
|
|
183
|
+
braceDepth--;
|
|
184
|
+
if (braceDepth <= 0) {
|
|
185
|
+
inRelevantBlock = false;
|
|
186
|
+
braceDepth = 0;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (!inRelevantBlock && braceDepth === 0) continue;
|
|
192
|
+
const match = trimmed.match(/^(--[\w-]+)\s*:\s*(.+?)\s*;/);
|
|
193
|
+
if (match) {
|
|
194
|
+
const [, name, value] = match;
|
|
195
|
+
const commentMatch = value.match(/\/\*\s*(.+?)\s*\*\//);
|
|
196
|
+
const cleanValue = value.replace(/\/\*.*?\*\//, "").trim();
|
|
197
|
+
tokens.push({
|
|
198
|
+
name,
|
|
199
|
+
value: cleanValue,
|
|
200
|
+
description: commentMatch?.[1]
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return tokens;
|
|
205
|
+
}
|
|
206
|
+
function detectPrefix(names) {
|
|
207
|
+
if (names.length === 0) return "--";
|
|
208
|
+
const stripped = names.map((n) => n.slice(2));
|
|
209
|
+
let prefix = "";
|
|
210
|
+
const first = stripped[0];
|
|
211
|
+
for (let i = 0; i < first.length; i++) {
|
|
212
|
+
const ch = first[i];
|
|
213
|
+
if (stripped.every((n) => n[i] === ch)) {
|
|
214
|
+
prefix += ch;
|
|
215
|
+
} else {
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const lastDash = prefix.lastIndexOf("-");
|
|
220
|
+
if (lastDash > 0) {
|
|
221
|
+
return "--" + prefix.slice(0, lastDash + 1);
|
|
222
|
+
}
|
|
223
|
+
return "--";
|
|
224
|
+
}
|
|
225
|
+
function inferTokenCategory(name) {
|
|
226
|
+
const n = name.toLowerCase();
|
|
227
|
+
if (n.includes("color") || n.includes("background") || n.includes("foreground") || n.includes("primary") || n.includes("secondary") || n.includes("accent") || n.includes("muted") || n.includes("destructive") || n.includes("popover") || n.includes("card") && !n.includes("card-") || n.includes("chart")) {
|
|
228
|
+
return "colors";
|
|
229
|
+
}
|
|
230
|
+
if (n.includes("font") || n.includes("text") || n.includes("letter") || n.includes("line-height")) {
|
|
231
|
+
return "typography";
|
|
232
|
+
}
|
|
233
|
+
if (n.includes("space") || n.includes("gap") || n.includes("padding") || n.includes("margin")) {
|
|
234
|
+
return "spacing";
|
|
235
|
+
}
|
|
236
|
+
if (n.includes("radius")) {
|
|
237
|
+
return "radius";
|
|
238
|
+
}
|
|
239
|
+
if (n.includes("shadow")) {
|
|
240
|
+
return "shadows";
|
|
241
|
+
}
|
|
242
|
+
if (n.includes("border")) {
|
|
243
|
+
return "borders";
|
|
244
|
+
}
|
|
245
|
+
if (n.includes("ring")) {
|
|
246
|
+
return "focus";
|
|
247
|
+
}
|
|
248
|
+
if (n.includes("sidebar")) {
|
|
249
|
+
return "sidebar";
|
|
250
|
+
}
|
|
251
|
+
if (n.includes("input")) {
|
|
252
|
+
return "forms";
|
|
253
|
+
}
|
|
254
|
+
return "other";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/adapters/auto-extract.ts
|
|
258
|
+
var AutoExtractionAdapter = class {
|
|
259
|
+
name = "auto-extract";
|
|
260
|
+
discover(startDir) {
|
|
261
|
+
return discoverComponentFiles(startDir).map((f) => f.filePath);
|
|
262
|
+
}
|
|
263
|
+
async load(projectRoot) {
|
|
264
|
+
let extractMod;
|
|
265
|
+
try {
|
|
266
|
+
extractMod = await import("./dist-V7D67NXS.js");
|
|
267
|
+
} catch (e) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
"Auto-extraction requires @fragments-sdk/extract and TypeScript.\n\nIf you see this error, the MCP server may not be installed correctly.\nAlternative: pre-build your design system with `npx fragments build`"
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
const tsconfigPath = findTsConfig(projectRoot);
|
|
273
|
+
const extractor = extractMod.createComponentExtractor(tsconfigPath ?? void 0);
|
|
274
|
+
try {
|
|
275
|
+
const discovered = discoverComponentFiles(projectRoot);
|
|
276
|
+
if (discovered.length === 0) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`No component files found in ${projectRoot}.
|
|
279
|
+
Searched: src/components/, components/, lib/components/, src/ui/
|
|
280
|
+
|
|
281
|
+
If your components are elsewhere, create a fragments.json with:
|
|
282
|
+
npx fragments build`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
const components = {};
|
|
286
|
+
const packageMap = /* @__PURE__ */ new Map();
|
|
287
|
+
const defaultPackageName = readPackageName(projectRoot);
|
|
288
|
+
let extractedCount = 0;
|
|
289
|
+
const fileToComponents = /* @__PURE__ */ new Map();
|
|
290
|
+
for (const { filePath, componentName } of discovered) {
|
|
291
|
+
try {
|
|
292
|
+
const metas = extractor.extractAll(filePath);
|
|
293
|
+
for (const meta of metas) {
|
|
294
|
+
const fragment = mapToCompiledFragment(meta, filePath, projectRoot);
|
|
295
|
+
components[meta.name] = fragment;
|
|
296
|
+
if (defaultPackageName) {
|
|
297
|
+
packageMap.set(meta.name, defaultPackageName);
|
|
298
|
+
}
|
|
299
|
+
extractedCount++;
|
|
300
|
+
const relPath = relative(projectRoot, filePath);
|
|
301
|
+
if (!fileToComponents.has(relPath)) fileToComponents.set(relPath, []);
|
|
302
|
+
fileToComponents.get(relPath).push(meta.name);
|
|
303
|
+
}
|
|
304
|
+
if (metas.length === 0) {
|
|
305
|
+
const meta = extractor.extract(filePath, componentName);
|
|
306
|
+
if (meta) {
|
|
307
|
+
const fragment = mapToCompiledFragment(meta, filePath, projectRoot);
|
|
308
|
+
components[meta.name] = fragment;
|
|
309
|
+
if (defaultPackageName) {
|
|
310
|
+
packageMap.set(meta.name, defaultPackageName);
|
|
311
|
+
}
|
|
312
|
+
extractedCount++;
|
|
313
|
+
const relPath = relative(projectRoot, filePath);
|
|
314
|
+
if (!fileToComponents.has(relPath)) fileToComponents.set(relPath, []);
|
|
315
|
+
fileToComponents.get(relPath).push(meta.name);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} catch {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
inferRelations(components, fileToComponents);
|
|
323
|
+
console.error(`[fragments-mcp] Extracted ${extractedCount} components from ${discovered.length} files.`);
|
|
324
|
+
if (extractedCount === 0) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`Found ${discovered.length} component files but could not extract any props.
|
|
327
|
+
This usually means TypeScript cannot parse the files.
|
|
328
|
+
Check that your tsconfig.json includes the component directories.`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
const tokens = scanTokens(projectRoot);
|
|
332
|
+
if (tokens) {
|
|
333
|
+
console.error(`[fragments-mcp] Found ${tokens.total} design tokens across ${Object.keys(tokens.categories).length} categories.`);
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
components,
|
|
337
|
+
blocks: void 0,
|
|
338
|
+
tokens,
|
|
339
|
+
graph: void 0,
|
|
340
|
+
performanceSummary: void 0,
|
|
341
|
+
packageMap,
|
|
342
|
+
defaultPackageName
|
|
343
|
+
};
|
|
344
|
+
} finally {
|
|
345
|
+
extractor.dispose();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
var UNIVERSAL_INHERITED = /* @__PURE__ */ new Set(["children", "className", "id", "disabled"]);
|
|
350
|
+
var FORM_INPUT_INHERITED = /* @__PURE__ */ new Set([
|
|
351
|
+
"placeholder",
|
|
352
|
+
"value",
|
|
353
|
+
"defaultValue",
|
|
354
|
+
"onChange",
|
|
355
|
+
"name",
|
|
356
|
+
"required",
|
|
357
|
+
"autoFocus",
|
|
358
|
+
"autoComplete",
|
|
359
|
+
"checked",
|
|
360
|
+
"defaultChecked",
|
|
361
|
+
"type"
|
|
362
|
+
]);
|
|
363
|
+
var LABEL_INHERITED = /* @__PURE__ */ new Set(["htmlFor"]);
|
|
364
|
+
var INPUT_LIKE_NAMES = /input|select|textarea|checkbox|radio|switch|slider|combobox/i;
|
|
365
|
+
var LABEL_LIKE_NAMES = /label/i;
|
|
366
|
+
function getRelevantInheritedProps(componentName) {
|
|
367
|
+
const allowed = new Set(UNIVERSAL_INHERITED);
|
|
368
|
+
if (INPUT_LIKE_NAMES.test(componentName)) {
|
|
369
|
+
for (const p of FORM_INPUT_INHERITED) allowed.add(p);
|
|
370
|
+
}
|
|
371
|
+
if (LABEL_LIKE_NAMES.test(componentName)) {
|
|
372
|
+
for (const p of LABEL_INHERITED) allowed.add(p);
|
|
373
|
+
}
|
|
374
|
+
return allowed;
|
|
375
|
+
}
|
|
376
|
+
function mapToCompiledFragment(meta, filePath, projectRoot) {
|
|
377
|
+
const relevantInherited = getRelevantInheritedProps(meta.name);
|
|
378
|
+
const props = {};
|
|
379
|
+
for (const [name, propMeta] of Object.entries(meta.props)) {
|
|
380
|
+
if (propMeta.source === "inherited" && !relevantInherited.has(name)) continue;
|
|
381
|
+
props[name] = {
|
|
382
|
+
type: propMeta.type,
|
|
383
|
+
description: propMeta.description ?? "",
|
|
384
|
+
required: propMeta.required,
|
|
385
|
+
...propMeta.values && { values: propMeta.values },
|
|
386
|
+
...propMeta.default !== void 0 && { default: propMeta.default }
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const relativePath = relative(projectRoot, filePath);
|
|
390
|
+
const category = inferCategory(relativePath);
|
|
391
|
+
const importPath = buildImportPath(relativePath);
|
|
392
|
+
const description = buildDescription(meta, props);
|
|
393
|
+
const propsSummary = buildPropsSummary(props);
|
|
394
|
+
const codeExample = buildCodeExample(meta.name, props);
|
|
395
|
+
const fragmentMeta = {
|
|
396
|
+
name: meta.name,
|
|
397
|
+
description,
|
|
398
|
+
category
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
filePath: relativePath,
|
|
402
|
+
meta: fragmentMeta,
|
|
403
|
+
usage: {
|
|
404
|
+
when: [`Use ${meta.name} when you need a ${category} ${meta.name.toLowerCase()} element.`],
|
|
405
|
+
whenNot: [],
|
|
406
|
+
guidelines: importPath ? [`import { ${meta.name} } from "${importPath}"`] : []
|
|
407
|
+
},
|
|
408
|
+
props,
|
|
409
|
+
propsSummary,
|
|
410
|
+
variants: codeExample ? [{ name: "Default", description: `Basic ${meta.name} usage`, code: codeExample }] : [],
|
|
411
|
+
sourcePath: relativePath,
|
|
412
|
+
exportName: meta.name,
|
|
413
|
+
_generated: {
|
|
414
|
+
source: "extracted",
|
|
415
|
+
verified: false,
|
|
416
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
417
|
+
},
|
|
418
|
+
...meta.composition && {
|
|
419
|
+
ai: {
|
|
420
|
+
compositionPattern: meta.composition.pattern
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function buildImportPath(relativePath) {
|
|
426
|
+
const withoutExt = relativePath.replace(/\.(tsx|jsx|ts|js)$/, "");
|
|
427
|
+
if (withoutExt.startsWith(`src${sep}`)) {
|
|
428
|
+
return "@/" + withoutExt.slice(4).split(sep).join("/");
|
|
429
|
+
}
|
|
430
|
+
return "./" + withoutExt.split(sep).join("/");
|
|
431
|
+
}
|
|
432
|
+
function buildDescription(meta, localProps) {
|
|
433
|
+
if (meta.description && meta.description !== meta.name && !meta.description.endsWith(" component")) {
|
|
434
|
+
return meta.description;
|
|
435
|
+
}
|
|
436
|
+
const propEntries = Object.entries(localProps);
|
|
437
|
+
if (propEntries.length === 0) {
|
|
438
|
+
return `${meta.name} component`;
|
|
439
|
+
}
|
|
440
|
+
const details = [];
|
|
441
|
+
for (const [name, prop] of propEntries) {
|
|
442
|
+
if (prop.values && prop.values.length > 0) {
|
|
443
|
+
details.push(`${prop.values.length} ${name} options`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (details.length > 0) {
|
|
447
|
+
return `${meta.name} component with ${details.join(" and ")}.`;
|
|
448
|
+
}
|
|
449
|
+
return `${meta.name} component with ${propEntries.length} configurable prop${propEntries.length === 1 ? "" : "s"}.`;
|
|
450
|
+
}
|
|
451
|
+
function buildPropsSummary(props) {
|
|
452
|
+
const summaries = [];
|
|
453
|
+
for (const [name, prop] of Object.entries(props)) {
|
|
454
|
+
let summary = `${name}`;
|
|
455
|
+
if (prop.values && prop.values.length > 0) {
|
|
456
|
+
summary += `: ${prop.values.map((v) => `"${v}"`).join(" | ")}`;
|
|
457
|
+
} else {
|
|
458
|
+
summary += `: ${prop.type}`;
|
|
459
|
+
}
|
|
460
|
+
if (prop.default !== void 0) {
|
|
461
|
+
summary += ` (default: ${JSON.stringify(prop.default)})`;
|
|
462
|
+
}
|
|
463
|
+
if (prop.required) {
|
|
464
|
+
summary += " (required)";
|
|
465
|
+
}
|
|
466
|
+
summaries.push(summary);
|
|
467
|
+
}
|
|
468
|
+
return summaries;
|
|
469
|
+
}
|
|
470
|
+
function buildCodeExample(name, props) {
|
|
471
|
+
const propsStr = [];
|
|
472
|
+
for (const [pName, prop] of Object.entries(props)) {
|
|
473
|
+
if (pName === "children" || pName === "asChild" || pName === "className") continue;
|
|
474
|
+
if (pName === "onChange" || pName === "onSubmit" || pName === "value" || pName === "defaultValue" || pName === "checked" || pName === "defaultChecked" || pName === "autoFocus" || pName === "autoComplete" || pName === "required" || pName === "disabled" || pName === "name") continue;
|
|
475
|
+
if (prop.default !== void 0) {
|
|
476
|
+
if (prop.values && prop.values.length > 1) {
|
|
477
|
+
const nonDefault = prop.values.find((v) => v !== String(prop.default));
|
|
478
|
+
if (nonDefault) {
|
|
479
|
+
propsStr.push(`${pName}="${nonDefault}"`);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (pName === "placeholder" && INPUT_LIKE_NAMES.test(name)) {
|
|
485
|
+
propsStr.push(`placeholder="Enter ${name.toLowerCase()}..."`);
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (pName === "htmlFor" && LABEL_LIKE_NAMES.test(name)) {
|
|
489
|
+
propsStr.push(`htmlFor="field-id"`);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (pName === "id" || pName === "className" || pName === "children") continue;
|
|
493
|
+
if (prop.required) {
|
|
494
|
+
if (prop.values && prop.values.length > 0) {
|
|
495
|
+
propsStr.push(`${pName}="${prop.values[0]}"`);
|
|
496
|
+
} else if (prop.type === "string") {
|
|
497
|
+
propsStr.push(`${pName}="..."`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const propsJsx = propsStr.length > 0 ? " " + propsStr.join(" ") : "";
|
|
502
|
+
const nameLower = name.toLowerCase();
|
|
503
|
+
const selfClosing = nameLower.includes("input") || nameLower.includes("separator") || nameLower.includes("slider") || nameLower.includes("switch");
|
|
504
|
+
if (selfClosing) {
|
|
505
|
+
return `<${name}${propsJsx} />`;
|
|
506
|
+
}
|
|
507
|
+
return `<${name}${propsJsx}>${name}</${name}>`;
|
|
508
|
+
}
|
|
509
|
+
function buildCompositionExample(root, subs) {
|
|
510
|
+
const header = subs.filter((s) => s.includes("Header"));
|
|
511
|
+
const title = subs.filter((s) => s.includes("Title"));
|
|
512
|
+
const description = subs.filter((s) => s.includes("Description"));
|
|
513
|
+
const content = subs.filter((s) => s.includes("Content") || s.includes("Body"));
|
|
514
|
+
const footer = subs.filter((s) => s.includes("Footer"));
|
|
515
|
+
const action = subs.filter((s) => s.includes("Action"));
|
|
516
|
+
const other = subs.filter(
|
|
517
|
+
(s) => !header.includes(s) && !title.includes(s) && !description.includes(s) && !content.includes(s) && !footer.includes(s) && !action.includes(s)
|
|
518
|
+
);
|
|
519
|
+
const lines = [`<${root}>`];
|
|
520
|
+
if (header.length > 0) {
|
|
521
|
+
lines.push(` <${header[0]}>`);
|
|
522
|
+
for (const t of title) lines.push(` <${t}>Title</${t}>`);
|
|
523
|
+
for (const d of description) lines.push(` <${d}>Description</${d}>`);
|
|
524
|
+
lines.push(` </${header[0]}>`);
|
|
525
|
+
} else {
|
|
526
|
+
for (const t of title) lines.push(` <${t}>Title</${t}>`);
|
|
527
|
+
for (const d of description) lines.push(` <${d}>Description</${d}>`);
|
|
528
|
+
}
|
|
529
|
+
for (const c of content) lines.push(` <${c}>Content</${c}>`);
|
|
530
|
+
for (const o of other) lines.push(` <${o}>...</${o}>`);
|
|
531
|
+
if (footer.length > 0) {
|
|
532
|
+
lines.push(` <${footer[0]}>`);
|
|
533
|
+
for (const a of action) lines.push(` <${a}>Action</${a}>`);
|
|
534
|
+
lines.push(` </${footer[0]}>`);
|
|
535
|
+
} else {
|
|
536
|
+
for (const a of action) lines.push(` <${a}>Action</${a}>`);
|
|
537
|
+
}
|
|
538
|
+
lines.push(`</${root}>`);
|
|
539
|
+
return lines.join("\n");
|
|
540
|
+
}
|
|
541
|
+
function inferRelations(components, fileToComponents) {
|
|
542
|
+
for (const [_file, names] of fileToComponents) {
|
|
543
|
+
if (names.length <= 1) continue;
|
|
544
|
+
const sorted = [...names].sort((a, b) => a.length - b.length);
|
|
545
|
+
const root = sorted[0];
|
|
546
|
+
const subs = sorted.slice(1).filter((n) => n.startsWith(root));
|
|
547
|
+
if (subs.length === 0) continue;
|
|
548
|
+
const rootComp = components[root];
|
|
549
|
+
if (rootComp) {
|
|
550
|
+
rootComp.ai = {
|
|
551
|
+
...rootComp.ai,
|
|
552
|
+
compositionPattern: "compound",
|
|
553
|
+
subComponents: subs
|
|
554
|
+
};
|
|
555
|
+
rootComp.relations = subs.map((sub) => ({
|
|
556
|
+
component: sub,
|
|
557
|
+
relationship: "parent-of",
|
|
558
|
+
note: `${sub} is a sub-component of ${root}`
|
|
559
|
+
}));
|
|
560
|
+
const compositionCode = buildCompositionExample(root, subs);
|
|
561
|
+
rootComp.variants = [
|
|
562
|
+
...rootComp.variants ?? [],
|
|
563
|
+
{ name: "Composition", description: `${root} with all sub-components`, code: compositionCode }
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
const rootImportPath = rootComp?.usage?.guidelines?.[0];
|
|
567
|
+
for (const sub of subs) {
|
|
568
|
+
const subComp = components[sub];
|
|
569
|
+
if (subComp) {
|
|
570
|
+
subComp.relations = [{
|
|
571
|
+
component: root,
|
|
572
|
+
relationship: "child-of",
|
|
573
|
+
note: `Use inside <${root}>`
|
|
574
|
+
}];
|
|
575
|
+
if (rootImportPath) {
|
|
576
|
+
const allNames = [root, ...subs].join(", ");
|
|
577
|
+
const fromPath = rootImportPath.match(/from\s+"([^"]+)"/)?.[1] ?? "";
|
|
578
|
+
if (fromPath) {
|
|
579
|
+
subComp.usage = {
|
|
580
|
+
...subComp.usage,
|
|
581
|
+
guidelines: [`import { ${allNames} } from "${fromPath}"`]
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (rootComp && rootComp.usage?.guidelines?.[0]) {
|
|
588
|
+
const fromPath = rootComp.usage.guidelines[0].match(/from\s+"([^"]+)"/)?.[1] ?? "";
|
|
589
|
+
if (fromPath) {
|
|
590
|
+
const allNames = [root, ...subs].join(", ");
|
|
591
|
+
rootComp.usage.guidelines = [`import { ${allNames} } from "${fromPath}"`];
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function inferCategory(relativePath) {
|
|
597
|
+
const parts = relativePath.split(sep);
|
|
598
|
+
const componentsIdx = parts.findIndex(
|
|
599
|
+
(p) => p === "components" || p === "ui"
|
|
600
|
+
);
|
|
601
|
+
if (componentsIdx >= 0 && componentsIdx + 1 < parts.length - 1) {
|
|
602
|
+
const nextPart = parts[componentsIdx + 1];
|
|
603
|
+
if (/^[A-Z]/.test(nextPart)) return parts[componentsIdx] || "uncategorized";
|
|
604
|
+
return nextPart;
|
|
605
|
+
}
|
|
606
|
+
return "uncategorized";
|
|
607
|
+
}
|
|
608
|
+
function findTsConfig(projectRoot) {
|
|
609
|
+
const candidates = ["tsconfig.json", "tsconfig.app.json"];
|
|
610
|
+
for (const name of candidates) {
|
|
611
|
+
const p = join3(projectRoot, name);
|
|
612
|
+
if (existsSync3(p)) return p;
|
|
613
|
+
}
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
function readPackageName(projectRoot) {
|
|
617
|
+
try {
|
|
618
|
+
const pkgPath = join3(projectRoot, "package.json");
|
|
619
|
+
if (!existsSync3(pkgPath)) return void 0;
|
|
620
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
621
|
+
return pkg.name;
|
|
622
|
+
} catch {
|
|
623
|
+
return void 0;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export {
|
|
628
|
+
AutoExtractionAdapter
|
|
629
|
+
};
|
|
630
|
+
//# sourceMappingURL=chunk-NVHGG7GW.js.map
|