@smwb/ui-mcp-solid 0.1.0

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.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/component-meta/base/animateHeight/animateHeight.json +74 -0
  4. package/component-meta/base/icon/icon.json +61 -0
  5. package/component-meta/base/ripple/ripple.json +51 -0
  6. package/component-meta/clickAwayListener/clickAwayListener.json +40 -0
  7. package/component-meta/dataDisplay/accordion/accordion.json +79 -0
  8. package/component-meta/dataDisplay/avatar/avatar.avatarGroup.json +41 -0
  9. package/component-meta/dataDisplay/avatar/avatar.json +56 -0
  10. package/component-meta/dataDisplay/badge/badge.json +66 -0
  11. package/component-meta/dataDisplay/card/card.json +46 -0
  12. package/component-meta/dataDisplay/carousel/carousel.json +89 -0
  13. package/component-meta/dataDisplay/chat/chat.json +225 -0
  14. package/component-meta/dataDisplay/chat/chatAttachmentImage.json +27 -0
  15. package/component-meta/dataDisplay/chat/chatAttachments.json +32 -0
  16. package/component-meta/dataDisplay/chat/chatComposer.json +76 -0
  17. package/component-meta/dataDisplay/chat/chatHeader.json +60 -0
  18. package/component-meta/dataDisplay/chat/chatIconButton.json +38 -0
  19. package/component-meta/dataDisplay/chat/chatLayout.chatLayoutChat.json +20 -0
  20. package/component-meta/dataDisplay/chat/chatLayout.json +85 -0
  21. package/component-meta/dataDisplay/chat/chatMessageAvatar.json +34 -0
  22. package/component-meta/dataDisplay/chat/chatMessageBubble.json +91 -0
  23. package/component-meta/dataDisplay/chat/chatMessageIncoming.json +60 -0
  24. package/component-meta/dataDisplay/chat/chatMessageItem.json +65 -0
  25. package/component-meta/dataDisplay/chat/chatMessageOutgoing.json +60 -0
  26. package/component-meta/dataDisplay/chat/chatMessageSkeleton.json +39 -0
  27. package/component-meta/dataDisplay/chat/chatMessageStatus.json +44 -0
  28. package/component-meta/dataDisplay/chat/chatMessageSystem.json +29 -0
  29. package/component-meta/dataDisplay/chat/chatMessages.json +91 -0
  30. package/component-meta/dataDisplay/chat/chatOverlayLayout.chatOverlayLayoutChat.json +20 -0
  31. package/component-meta/dataDisplay/chat/chatOverlayLayout.json +70 -0
  32. package/component-meta/dataDisplay/chat/chatShell.json +71 -0
  33. package/component-meta/dataDisplay/chip/chip.json +104 -0
  34. package/component-meta/dataDisplay/expansionPanel/expansionPanel.json +85 -0
  35. package/component-meta/dataDisplay/imagesList/imagesList.json +57 -0
  36. package/component-meta/dataDisplay/imagesList/imagesListItem/imagesListItem.json +64 -0
  37. package/component-meta/dataDisplay/imagesList/imagesListItem/imagesListItemModal.json +32 -0
  38. package/component-meta/dataDisplay/list/list.json +36 -0
  39. package/component-meta/dataDisplay/list/listItem.json +62 -0
  40. package/component-meta/dataDisplay/table/table.json +59 -0
  41. package/component-meta/dataDisplay/table/tableBody.json +26 -0
  42. package/component-meta/dataDisplay/table/tableCell.json +31 -0
  43. package/component-meta/dataDisplay/table/tableHead.json +26 -0
  44. package/component-meta/dataDisplay/table/tableHeadCell.json +58 -0
  45. package/component-meta/dataDisplay/table/tablePagination/tablePagination.baseTablePagination.json +47 -0
  46. package/component-meta/dataDisplay/table/tablePagination/tablePagination.json +21 -0
  47. package/component-meta/dataDisplay/table/tableRow.json +36 -0
  48. package/component-meta/dataDisplay/treeView/treeView.json +93 -0
  49. package/component-meta/dataDisplay/typography/typography.json +56 -0
  50. package/component-meta/feedBack/message/message.json +53 -0
  51. package/component-meta/feedBack/modal/modal.json +92 -0
  52. package/component-meta/feedBack/modal/modal.modalSlot.json +22 -0
  53. package/component-meta/feedBack/progressIndicator/progressIndicator.circularProgress.json +22 -0
  54. package/component-meta/feedBack/progressIndicator/progressIndicator.json +57 -0
  55. package/component-meta/feedBack/skeleton/skeleton.json +61 -0
  56. package/component-meta/feedBack/snackbar/snackbar.json +71 -0
  57. package/component-meta/feedBack/snackbar/snackbar.snackbarsProvider.json +51 -0
  58. package/component-meta/feedBack/tooltip/tooltip.json +62 -0
  59. package/component-meta/inputs/button/button.json +94 -0
  60. package/component-meta/inputs/buttonGroups/buttonGroup.json +59 -0
  61. package/component-meta/inputs/checkbox/checkbox.json +89 -0
  62. package/component-meta/inputs/datePicker/dataPickerDays.json +87 -0
  63. package/component-meta/inputs/datePicker/dataPickerHeader.json +48 -0
  64. package/component-meta/inputs/datePicker/dataPickerInput.json +34 -0
  65. package/component-meta/inputs/datePicker/datePicker.json +129 -0
  66. package/component-meta/inputs/datePicker/datePickerActionLabel.json +55 -0
  67. package/component-meta/inputs/datePicker/datePickerDay.json +61 -0
  68. package/component-meta/inputs/datePicker/datePickerIconButton.json +18 -0
  69. package/component-meta/inputs/datePicker/datePickerList.json +33 -0
  70. package/component-meta/inputs/dateTimePicker/dateTimePicker.json +120 -0
  71. package/component-meta/inputs/dateTimePicker/dateTimePickerInput.json +35 -0
  72. package/component-meta/inputs/fileDrop/countPreview.json +40 -0
  73. package/component-meta/inputs/fileDrop/fileDrop.json +71 -0
  74. package/component-meta/inputs/fileDrop/preview.json +46 -0
  75. package/component-meta/inputs/fileDrop/previewWrapper.json +51 -0
  76. package/component-meta/inputs/fileInput/fileInput.json +61 -0
  77. package/component-meta/inputs/floatingButton/floatingButton.json +43 -0
  78. package/component-meta/inputs/radioButton/radioButton.json +90 -0
  79. package/component-meta/inputs/rating/rating.json +104 -0
  80. package/component-meta/inputs/selectField/dropdownMenu.json +107 -0
  81. package/component-meta/inputs/selectField/selectField.json +203 -0
  82. package/component-meta/inputs/slider/slider.json +133 -0
  83. package/component-meta/inputs/textField/textField.json +126 -0
  84. package/component-meta/inputs/textField/textFieldAdornment.json +47 -0
  85. package/component-meta/inputs/textField/textFieldWrapper.json +96 -0
  86. package/component-meta/inputs/timePicker/timePicker.json +75 -0
  87. package/component-meta/inputs/timePicker/timePickerColumns.json +37 -0
  88. package/component-meta/inputs/timePicker/timePickerInput.json +35 -0
  89. package/component-meta/inputs/timePicker/timePickerList.json +32 -0
  90. package/component-meta/inputs/toggle/toggle.json +90 -0
  91. package/component-meta/layout/appLayout/appLayout.json +88 -0
  92. package/component-meta/layout/divider/divider.json +56 -0
  93. package/component-meta/layout/grid/grid.column.json +81 -0
  94. package/component-meta/layout/grid/grid.container.json +36 -0
  95. package/component-meta/layout/grid/grid.row.json +51 -0
  96. package/component-meta/layout/page/page.json +41 -0
  97. package/component-meta/layout/pageHeader/pageHeader.json +53 -0
  98. package/component-meta/layout/screenDivider/screenDivider.json +57 -0
  99. package/component-meta/layout/sheet/sheet.json +114 -0
  100. package/component-meta/layout/stack/stack.json +56 -0
  101. package/component-meta/navigation/appBar/appBar.json +71 -0
  102. package/component-meta/navigation/bottomBar/bottomBar.bottomBarMenuItem.json +55 -0
  103. package/component-meta/navigation/bottomBar/bottomBar.json +61 -0
  104. package/component-meta/navigation/breadcrumbs/breadcrumbs.json +61 -0
  105. package/component-meta/navigation/menu/menu.json +20 -0
  106. package/component-meta/navigation/menu/menuDivider.json +26 -0
  107. package/component-meta/navigation/menu/menuFloating.json +51 -0
  108. package/component-meta/navigation/menu/menuItem.json +68 -0
  109. package/component-meta/navigation/menu/menuItemIcon.json +31 -0
  110. package/component-meta/navigation/menu/menuItemText.json +31 -0
  111. package/component-meta/navigation/menu/menuList.json +26 -0
  112. package/component-meta/navigation/menu/menuSubmenu.json +47 -0
  113. package/component-meta/navigation/pagination/pagination.json +98 -0
  114. package/component-meta/navigation/sidebar/sidebar.json +79 -0
  115. package/component-meta/navigation/sidebar/sidebar.sidebarMenuItem.json +56 -0
  116. package/component-meta/navigation/stepper/stepper.json +53 -0
  117. package/component-meta/navigation/tabs/tab/tab.json +64 -0
  118. package/component-meta/navigation/tabs/tabs.json +60 -0
  119. package/componentMeta.mjs +493 -0
  120. package/customizationMeta.mjs +451 -0
  121. package/package.json +37 -0
  122. package/server.mjs +169 -0
@@ -0,0 +1,493 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const META_FILE_PATTERN = /\.json$/;
5
+ const SKIP_META_FILES = /\.mock\.json$/;
6
+
7
+ const QUERY_ALIASES = {
8
+ btn: "button",
9
+ btns: "button",
10
+ msg: "message",
11
+ msgs: "message",
12
+ txt: "text",
13
+ fld: "field",
14
+ nav: "navigation",
15
+ dlg: "dialog",
16
+ modal: "dialog",
17
+ dropdown: "select",
18
+ picker: "date",
19
+ avatar: "avatar",
20
+ tbl: "table",
21
+ pag: "pagination",
22
+ snack: "snackbar",
23
+ tooltip: "tooltip",
24
+ };
25
+
26
+ export function isComponentMetaFile(fileName) {
27
+ return META_FILE_PATTERN.test(fileName) && !SKIP_META_FILES.test(fileName) && fileName !== "manifest.json";
28
+ }
29
+
30
+ export function walkMetaFiles(rootDir, files = [], relativeRoot = rootDir) {
31
+ if (!fs.existsSync(rootDir)) {
32
+ return files;
33
+ }
34
+
35
+ for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) {
36
+ const fullPath = path.join(rootDir, entry.name);
37
+ if (entry.isDirectory()) {
38
+ walkMetaFiles(fullPath, files, relativeRoot);
39
+ continue;
40
+ }
41
+ if (!entry.isFile() || !isComponentMetaFile(entry.name)) {
42
+ continue;
43
+ }
44
+ files.push({
45
+ absolutePath: fullPath,
46
+ relativePath: path.relative(relativeRoot, fullPath).replace(/\\/g, "/"),
47
+ });
48
+ }
49
+
50
+ return files;
51
+ }
52
+
53
+ export function toComponentId(relativePath) {
54
+ return relativePath.replace(/\.json$/, "");
55
+ }
56
+
57
+ export function normalizeText(value) {
58
+ return String(value ?? "")
59
+ .trim()
60
+ .toLowerCase()
61
+ .normalize("NFKD")
62
+ .replace(/[\u0300-\u036f]/g, "");
63
+ }
64
+
65
+ export function compactText(value) {
66
+ return normalizeText(value).replace(/[^a-z0-9]+/g, "");
67
+ }
68
+
69
+ export function splitCamelCase(value) {
70
+ return String(value ?? "")
71
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
72
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
73
+ .toLowerCase()
74
+ .split(/[^a-z0-9]+/g)
75
+ .filter(Boolean);
76
+ }
77
+
78
+ export function tokenize(value) {
79
+ const parts = String(value ?? "")
80
+ .trim()
81
+ .split(/[^a-zA-Z0-9]+/g)
82
+ .filter(Boolean);
83
+ const expanded = [];
84
+
85
+ for (const part of parts) {
86
+ const camelParts = splitCamelCase(part);
87
+ const tokens = camelParts.length ? camelParts : [normalizeText(part)];
88
+
89
+ for (const token of tokens) {
90
+ if (!token) {
91
+ continue;
92
+ }
93
+ expanded.push(token);
94
+ if (QUERY_ALIASES[token]) {
95
+ expanded.push(QUERY_ALIASES[token]);
96
+ }
97
+ }
98
+ }
99
+
100
+ return [...new Set(expanded.filter(Boolean))];
101
+ }
102
+
103
+ export function levenshtein(a, b) {
104
+ if (a === b) {
105
+ return 0;
106
+ }
107
+ if (!a.length) {
108
+ return b.length;
109
+ }
110
+ if (!b.length) {
111
+ return a.length;
112
+ }
113
+
114
+ const rows = b.length + 1;
115
+ const cols = a.length + 1;
116
+ const matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
117
+
118
+ for (let i = 0; i < rows; i += 1) {
119
+ matrix[i][0] = i;
120
+ }
121
+ for (let j = 0; j < cols; j += 1) {
122
+ matrix[0][j] = j;
123
+ }
124
+
125
+ for (let i = 1; i < rows; i += 1) {
126
+ for (let j = 1; j < cols; j += 1) {
127
+ const cost = b.charCodeAt(i - 1) === a.charCodeAt(j - 1) ? 0 : 1;
128
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
129
+ }
130
+ }
131
+
132
+ return matrix[rows - 1][cols - 1];
133
+ }
134
+
135
+ export function similarityScore(a, b) {
136
+ const left = compactText(a);
137
+ const right = compactText(b);
138
+ const maxLen = Math.max(left.length, right.length);
139
+ if (!maxLen) {
140
+ return 1;
141
+ }
142
+ return 1 - levenshtein(left, right) / maxLen;
143
+ }
144
+
145
+ function buildSearchProfile(entry) {
146
+ const idSegments = entry.id.split("/").flatMap((segment) => tokenize(segment));
147
+ const nameTokens = splitCamelCase(entry.name);
148
+ const descriptionTokens = tokenize(entry.description);
149
+ const propTokens = entry.propNames.flatMap((name) => tokenize(name));
150
+
151
+ return {
152
+ name: normalizeText(entry.name),
153
+ nameCompact: compactText(entry.name),
154
+ id: normalizeText(entry.id),
155
+ idCompact: compactText(entry.id),
156
+ idSegments: [...new Set(idSegments)],
157
+ nameTokens: [...new Set(nameTokens)],
158
+ description: normalizeText(entry.description),
159
+ descriptionTokens: [...new Set(descriptionTokens)],
160
+ propTokens: [...new Set(propTokens)],
161
+ allTokens: [...new Set([...nameTokens, ...idSegments, ...descriptionTokens, ...propTokens])],
162
+ };
163
+ }
164
+
165
+ function acronymMatches(tokens, queryCompact) {
166
+ if (!queryCompact || queryCompact.length < 2) {
167
+ return false;
168
+ }
169
+ const acronym = tokens.map((token) => token[0]).join("");
170
+ return acronym.startsWith(queryCompact) || queryCompact.startsWith(acronym);
171
+ }
172
+
173
+ function countTokenMatches(tokens, queryTokens) {
174
+ let matched = 0;
175
+ for (const queryToken of queryTokens) {
176
+ if (tokens.some((token) => token === queryToken || token.startsWith(queryToken) || queryToken.startsWith(token))) {
177
+ matched += 1;
178
+ }
179
+ }
180
+ return matched;
181
+ }
182
+
183
+ function scoreComponent(entry, query, queryTokens, queryCompact) {
184
+ const profile = entry.searchProfile;
185
+ let score = 0;
186
+ const reasons = [];
187
+
188
+ if (profile.name === query) {
189
+ score += 1000;
190
+ reasons.push("exact-name");
191
+ }
192
+ if (profile.id === query || profile.id.endsWith(`/${query}`)) {
193
+ score += 950;
194
+ reasons.push("exact-id");
195
+ }
196
+ if (profile.nameCompact === queryCompact) {
197
+ score += 900;
198
+ reasons.push("compact-name");
199
+ }
200
+ if (profile.name.startsWith(query)) {
201
+ score += 700;
202
+ reasons.push("name-prefix");
203
+ }
204
+ if (profile.name.includes(query)) {
205
+ score += 520;
206
+ reasons.push("name-substring");
207
+ }
208
+ if (profile.id.includes(query)) {
209
+ score += 480;
210
+ reasons.push("id-substring");
211
+ }
212
+ if (profile.description.includes(query)) {
213
+ score += queryTokens.length > 1 ? 420 : 210;
214
+ reasons.push("description-phrase");
215
+ }
216
+
217
+ const nameTokenMatches = countTokenMatches(profile.nameTokens, queryTokens);
218
+ if (queryTokens.length && nameTokenMatches === queryTokens.length) {
219
+ score += 360 + nameTokenMatches * 40;
220
+ reasons.push("all-name-tokens");
221
+ } else {
222
+ score += nameTokenMatches * 70;
223
+ if (nameTokenMatches) {
224
+ reasons.push("name-token");
225
+ }
226
+ }
227
+
228
+ const idTokenMatches = countTokenMatches(profile.idSegments, queryTokens);
229
+ score += idTokenMatches * 55;
230
+ if (idTokenMatches) {
231
+ reasons.push("id-token");
232
+ }
233
+
234
+ const descriptionTokenMatches = countTokenMatches(profile.descriptionTokens, queryTokens);
235
+ score += descriptionTokenMatches * 45;
236
+ if (descriptionTokenMatches) {
237
+ reasons.push("description-token");
238
+ }
239
+
240
+ const propTokenMatches = countTokenMatches(profile.propTokens, queryTokens);
241
+ score += propTokenMatches * 35;
242
+ if (propTokenMatches) {
243
+ reasons.push("prop-token");
244
+ }
245
+
246
+ for (const queryToken of queryTokens) {
247
+ const exactProp = entry.propNames.find((prop) => normalizeText(prop) === queryToken);
248
+ if (exactProp) {
249
+ score += 340;
250
+ reasons.push("exact-prop-name");
251
+ }
252
+ }
253
+
254
+ if (queryTokens.length > 1) {
255
+ const allTokenMatches = countTokenMatches(profile.allTokens, queryTokens);
256
+ if (allTokenMatches === queryTokens.length) {
257
+ score += 180;
258
+ reasons.push("all-tokens");
259
+ }
260
+ }
261
+
262
+ if (acronymMatches(profile.nameTokens, queryCompact)) {
263
+ score += 260;
264
+ reasons.push("acronym");
265
+ }
266
+
267
+ const nameSimilarity = similarityScore(profile.nameCompact, queryCompact);
268
+ if (queryCompact.length >= 3 && nameSimilarity >= 0.72) {
269
+ score += Math.round(nameSimilarity * 280);
270
+ reasons.push("fuzzy-name");
271
+ }
272
+
273
+ const idSimilarity = similarityScore(profile.idCompact, queryCompact);
274
+ if (queryCompact.length >= 4 && idSimilarity >= 0.68) {
275
+ score += Math.round(idSimilarity * 180);
276
+ reasons.push("fuzzy-id");
277
+ }
278
+
279
+ for (const queryToken of queryTokens) {
280
+ if (queryToken.length < 3) {
281
+ continue;
282
+ }
283
+ for (const token of profile.nameTokens) {
284
+ if (token.startsWith(queryToken) || queryToken.startsWith(token)) {
285
+ score += 90;
286
+ reasons.push("token-prefix");
287
+ break;
288
+ }
289
+ }
290
+ }
291
+
292
+ return { score, reasons: [...new Set(reasons)] };
293
+ }
294
+
295
+ export function parseComponentMetaFile(absolutePath, relativePath) {
296
+ const raw = fs.readFileSync(absolutePath, "utf8");
297
+ const parsed = JSON.parse(raw);
298
+ if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string") {
299
+ throw new Error(`Invalid component metadata file: ${relativePath}`);
300
+ }
301
+
302
+ const id = toComponentId(relativePath);
303
+ const propNames = parsed.props ? Object.keys(parsed.props) : [];
304
+
305
+ const entry = {
306
+ id,
307
+ name: parsed.name,
308
+ description: parsed.description ?? "",
309
+ extends: parsed.extends,
310
+ inheritedAttributes: parsed.inheritedAttributes,
311
+ styles: parsed.styles,
312
+ props: parsed.props ?? {},
313
+ propNames,
314
+ sourcePath: relativePath,
315
+ };
316
+
317
+ entry.searchProfile = buildSearchProfile(entry);
318
+ return entry;
319
+ }
320
+
321
+ export function loadComponentCatalog(metaRootDir) {
322
+ const files = walkMetaFiles(metaRootDir);
323
+ const byId = new Map();
324
+ const byName = new Map();
325
+ const all = [];
326
+
327
+ for (const file of files) {
328
+ const entry = parseComponentMetaFile(file.absolutePath, file.relativePath);
329
+ all.push(entry);
330
+ byId.set(normalizeText(entry.id), entry);
331
+ byName.set(normalizeText(entry.name), entry);
332
+ }
333
+
334
+ all.sort((a, b) => a.name.localeCompare(b.name));
335
+
336
+ return {
337
+ metaRootDir,
338
+ count: all.length,
339
+ all,
340
+ byId,
341
+ byName,
342
+ };
343
+ }
344
+
345
+ export function searchComponents(catalog, query, limit = 10) {
346
+ const normalizedQuery = normalizeText(query ?? "");
347
+ if (!normalizedQuery) {
348
+ return catalog.all.slice(0, limit).map((entry) => ({
349
+ id: entry.id,
350
+ name: entry.name,
351
+ description: entry.description,
352
+ propCount: entry.propNames.length,
353
+ }));
354
+ }
355
+
356
+ const queryTokens = tokenize(normalizedQuery);
357
+ const queryCompact = compactText(normalizedQuery);
358
+
359
+ return catalog.all
360
+ .map((entry) => {
361
+ const result = scoreComponent(entry, normalizedQuery, queryTokens, queryCompact);
362
+ return { entry, ...result };
363
+ })
364
+ .filter((item) => item.score >= 40)
365
+ .sort(
366
+ (a, b) =>
367
+ b.score - a.score ||
368
+ similarityScore(b.entry.name, normalizedQuery) - similarityScore(a.entry.name, normalizedQuery) ||
369
+ a.entry.name.localeCompare(b.entry.name)
370
+ )
371
+ .slice(0, limit)
372
+ .map(({ entry, score, reasons }) => ({
373
+ id: entry.id,
374
+ name: entry.name,
375
+ description: entry.description,
376
+ propCount: entry.propNames.length,
377
+ score,
378
+ matchReasons: reasons,
379
+ }));
380
+ }
381
+
382
+ export function resolveComponent(catalog, nameOrId) {
383
+ const key = normalizeText(nameOrId ?? "");
384
+ if (!key) {
385
+ return null;
386
+ }
387
+
388
+ const exact =
389
+ catalog.byId.get(key) ??
390
+ catalog.byName.get(key) ??
391
+ catalog.all.find((entry) => normalizeText(entry.name) === key) ??
392
+ null;
393
+
394
+ if (exact) {
395
+ return exact;
396
+ }
397
+
398
+ const [bestMatch] = searchComponents(catalog, key, 1);
399
+ if (!bestMatch || bestMatch.score < 120) {
400
+ return null;
401
+ }
402
+
403
+ return catalog.byId.get(normalizeText(bestMatch.id)) ?? catalog.all.find((entry) => entry.id === bestMatch.id) ?? null;
404
+ }
405
+
406
+ export function getComponentProps(catalog, nameOrId) {
407
+ const entry = resolveComponent(catalog, nameOrId);
408
+ if (!entry) {
409
+ return null;
410
+ }
411
+
412
+ return {
413
+ id: entry.id,
414
+ name: entry.name,
415
+ description: entry.description,
416
+ extends: entry.extends,
417
+ inheritedAttributes: entry.inheritedAttributes,
418
+ styles: entry.styles,
419
+ props: entry.props,
420
+ sourcePath: entry.sourcePath,
421
+ };
422
+ }
423
+
424
+ export function resolveMetaRootDirs(packageRoot) {
425
+ const candidates = [
426
+ path.join(packageRoot, "component-meta"),
427
+ path.join(packageRoot, "dist", "mcp", "component-meta"),
428
+ path.join(packageRoot, "mcp", "component-meta"),
429
+ path.join(packageRoot, "src", "components"),
430
+ ];
431
+
432
+ const roots = [];
433
+ for (const candidate of candidates) {
434
+ if (!fs.existsSync(candidate)) {
435
+ continue;
436
+ }
437
+ if (candidate.endsWith(`${path.sep}components`)) {
438
+ roots.push({ dir: candidate, mode: "source-tree" });
439
+ roots.push({ dir: path.join(packageRoot, "src", "connects"), mode: "source-tree" });
440
+ break;
441
+ }
442
+ roots.push({ dir: candidate, mode: "bundle" });
443
+ break;
444
+ }
445
+
446
+ return roots;
447
+ }
448
+
449
+ export function loadCatalogFromPackageRoot(packageRoot) {
450
+ const roots = resolveMetaRootDirs(packageRoot);
451
+ if (!roots.length) {
452
+ throw new Error(`Component metadata directory was not found under ${packageRoot}`);
453
+ }
454
+
455
+ if (roots[0].mode === "bundle") {
456
+ return loadComponentCatalog(roots[0].dir);
457
+ }
458
+
459
+ const merged = [];
460
+ const byId = new Map();
461
+ const byName = new Map();
462
+
463
+ for (const root of roots) {
464
+ if (!fs.existsSync(root.dir)) {
465
+ continue;
466
+ }
467
+ const catalog = loadComponentCatalog(root.dir);
468
+ for (const entry of catalog.all) {
469
+ const prefixedId = root.dir.endsWith(`${path.sep}connects`)
470
+ ? `connects/${entry.id}`
471
+ : entry.id;
472
+ const normalized = {
473
+ ...entry,
474
+ id: prefixedId,
475
+ sourcePath: `${path.basename(root.dir)}/${entry.sourcePath}`,
476
+ };
477
+ normalized.searchProfile = buildSearchProfile(normalized);
478
+ merged.push(normalized);
479
+ byId.set(normalizeText(normalized.id), normalized);
480
+ byName.set(normalizeText(normalized.name), normalized);
481
+ }
482
+ }
483
+
484
+ merged.sort((a, b) => a.name.localeCompare(b.name));
485
+
486
+ return {
487
+ metaRootDir: packageRoot,
488
+ count: merged.length,
489
+ all: merged,
490
+ byId,
491
+ byName,
492
+ };
493
+ }