@aleph-alpha/lib-mcp 1.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.
- package/LICENSE +202 -0
- package/README.md +99 -0
- package/components-meta.json +1172 -0
- package/dist/cli.js +21599 -0
- package/dist/index.js +7 -0
- package/dist/scripts/generate-component-meta.js +253 -0
- package/dist/server.js +21467 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/scripts/generate-component-meta.ts
|
|
4
|
+
import * as fs4 from "node:fs";
|
|
5
|
+
import * as path3 from "node:path";
|
|
6
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7
|
+
|
|
8
|
+
// src/constants.ts
|
|
9
|
+
import * as fs3 from "node:fs";
|
|
10
|
+
import * as path2 from "node:path";
|
|
11
|
+
|
|
12
|
+
// src/utils/paths.ts
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
var __dirname = path.dirname(__filename);
|
|
18
|
+
function findUiLibraryRoot() {
|
|
19
|
+
const possiblePaths = [
|
|
20
|
+
// Monorepo development: from packages/lib-mcp/dist/utils -> packages/ui-library
|
|
21
|
+
path.resolve(__dirname, "../../../ui-library"),
|
|
22
|
+
// Installed as dependency: from node_modules/@aleph-alpha/lib-mcp/dist/utils -> node_modules/@aleph-alpha/ui-library
|
|
23
|
+
path.resolve(__dirname, "../../@aleph-alpha/ui-library"),
|
|
24
|
+
// Alternative: resolve from cwd
|
|
25
|
+
path.join(process.cwd(), "node_modules/@aleph-alpha/ui-library")
|
|
26
|
+
];
|
|
27
|
+
for (const pkgPath of possiblePaths) {
|
|
28
|
+
if (fs.existsSync(path.join(pkgPath, "package.json"))) {
|
|
29
|
+
return pkgPath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new Error("Could not find @aleph-alpha/ui-library. Is it installed?");
|
|
33
|
+
}
|
|
34
|
+
function getLibrarySourcePath() {
|
|
35
|
+
const uiLibraryRoot = findUiLibraryRoot();
|
|
36
|
+
const srcPath = path.join(uiLibraryRoot, "src");
|
|
37
|
+
if (fs.existsSync(srcPath)) {
|
|
38
|
+
return srcPath;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Could not find UI library source directory. Is the package installed correctly?"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
function getPackageRoot() {
|
|
45
|
+
const pkgRoot = path.resolve(__dirname, "../..");
|
|
46
|
+
if (fs.existsSync(path.join(pkgRoot, "package.json"))) {
|
|
47
|
+
return pkgRoot;
|
|
48
|
+
}
|
|
49
|
+
return process.cwd();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/utils/file-utils.ts
|
|
53
|
+
import * as fs2 from "node:fs";
|
|
54
|
+
|
|
55
|
+
// src/constants.ts
|
|
56
|
+
function detectCategoryDirs() {
|
|
57
|
+
const srcPath = getSrcPath();
|
|
58
|
+
if (!srcPath || !fs3.existsSync(srcPath)) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const entries = fs3.readdirSync(srcPath, { withFileTypes: true });
|
|
62
|
+
return entries.filter((entry) => entry.isDirectory()).filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules").filter((entry) => {
|
|
63
|
+
try {
|
|
64
|
+
const dirPath = path2.join(srcPath, entry.name);
|
|
65
|
+
const subEntries = fs3.readdirSync(dirPath, { withFileTypes: true });
|
|
66
|
+
return subEntries.some((sub) => sub.isDirectory() && sub.name.startsWith("Ui"));
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}).map((entry) => entry.name);
|
|
71
|
+
}
|
|
72
|
+
var cachedCategoryDirs = null;
|
|
73
|
+
function getCategoryDirs() {
|
|
74
|
+
if (cachedCategoryDirs) return cachedCategoryDirs;
|
|
75
|
+
cachedCategoryDirs = detectCategoryDirs();
|
|
76
|
+
return cachedCategoryDirs;
|
|
77
|
+
}
|
|
78
|
+
function getSrcPath() {
|
|
79
|
+
try {
|
|
80
|
+
return getLibrarySourcePath();
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/scripts/generate-component-meta.ts
|
|
87
|
+
function escapeRegex(str) {
|
|
88
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
89
|
+
}
|
|
90
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
91
|
+
var __dirname2 = path3.dirname(__filename2);
|
|
92
|
+
var PACKAGE_ROOT = getPackageRoot();
|
|
93
|
+
var OUTPUT_PATH = path3.join(PACKAGE_ROOT, "components-meta.json");
|
|
94
|
+
function extractJSDocTags(content, interfaceName) {
|
|
95
|
+
const declarationRegex = new RegExp(
|
|
96
|
+
`export\\s+(?:interface|type)\\s+${escapeRegex(interfaceName)}\\b`
|
|
97
|
+
);
|
|
98
|
+
const declarationMatch = content.match(declarationRegex);
|
|
99
|
+
if (!declarationMatch || declarationMatch.index === void 0) {
|
|
100
|
+
return { description: "", useCases: [], keywords: [], related: [] };
|
|
101
|
+
}
|
|
102
|
+
const contentBefore = content.substring(0, declarationMatch.index);
|
|
103
|
+
const jsdocEndIndex = contentBefore.lastIndexOf("*/");
|
|
104
|
+
if (jsdocEndIndex === -1) {
|
|
105
|
+
return { description: "", useCases: [], keywords: [], related: [] };
|
|
106
|
+
}
|
|
107
|
+
const betweenJsdocAndDeclaration = contentBefore.substring(jsdocEndIndex + 2);
|
|
108
|
+
if (betweenJsdocAndDeclaration.trim() !== "") {
|
|
109
|
+
return { description: "", useCases: [], keywords: [], related: [] };
|
|
110
|
+
}
|
|
111
|
+
const jsdocStartIndex = contentBefore.lastIndexOf("/**");
|
|
112
|
+
if (jsdocStartIndex === -1 || jsdocStartIndex > jsdocEndIndex) {
|
|
113
|
+
return { description: "", useCases: [], keywords: [], related: [] };
|
|
114
|
+
}
|
|
115
|
+
const jsdoc = contentBefore.substring(jsdocStartIndex + 3, jsdocEndIndex);
|
|
116
|
+
const lines = jsdoc.split("\n").map((l) => l.replace(/^\s*\*\s?/, "").trim());
|
|
117
|
+
let description = "";
|
|
118
|
+
const useCases = [];
|
|
119
|
+
const keywords = [];
|
|
120
|
+
const related = [];
|
|
121
|
+
let category;
|
|
122
|
+
for (const line of lines) {
|
|
123
|
+
if (line.startsWith("@useCases") || line.startsWith("@use-cases")) {
|
|
124
|
+
const values = line.replace(/@use-?[cC]ases\s*/, "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
125
|
+
useCases.push(...values);
|
|
126
|
+
} else if (line.startsWith("@keywords")) {
|
|
127
|
+
const values = line.replace(/@keywords\s*/, "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
128
|
+
keywords.push(...values);
|
|
129
|
+
} else if (line.startsWith("@related")) {
|
|
130
|
+
const values = line.replace(/@related\s*/, "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
131
|
+
related.push(...values);
|
|
132
|
+
} else if (line.startsWith("@category")) {
|
|
133
|
+
category = line.replace(/@category\s*/, "").trim();
|
|
134
|
+
} else if (!line.startsWith("@") && line) {
|
|
135
|
+
description += (description ? " " : "") + line;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { description, useCases, keywords, related, category };
|
|
139
|
+
}
|
|
140
|
+
function extractKeywordsFromName(componentName) {
|
|
141
|
+
const name = componentName.replace(/^Ui/, "");
|
|
142
|
+
return name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/\s+/).filter((word) => word.length > 2);
|
|
143
|
+
}
|
|
144
|
+
function extractProps(content, interfaceName) {
|
|
145
|
+
const regex = new RegExp(
|
|
146
|
+
`export\\s+interface\\s+${escapeRegex(interfaceName)}\\s*\\{([\\s\\S]*?)\\n\\}`,
|
|
147
|
+
"m"
|
|
148
|
+
);
|
|
149
|
+
const match = content.match(regex);
|
|
150
|
+
if (!match) return [];
|
|
151
|
+
const interfaceBody = match[1].replace(/\/\*\*[\s\S]*?\*\//g, "");
|
|
152
|
+
const propRegex = /^\s*(\w+)\??:/gm;
|
|
153
|
+
const props = [];
|
|
154
|
+
let propMatch;
|
|
155
|
+
while ((propMatch = propRegex.exec(interfaceBody)) !== null) {
|
|
156
|
+
props.push(propMatch[1]);
|
|
157
|
+
}
|
|
158
|
+
return [...new Set(props)];
|
|
159
|
+
}
|
|
160
|
+
function extractExamples(storiesPath) {
|
|
161
|
+
if (!fs4.existsSync(storiesPath)) return [];
|
|
162
|
+
const content = fs4.readFileSync(storiesPath, "utf-8");
|
|
163
|
+
const examples = [];
|
|
164
|
+
const storyRegex = /export\s+const\s+(\w+):\s*Story/g;
|
|
165
|
+
let match;
|
|
166
|
+
while ((match = storyRegex.exec(content)) !== null) {
|
|
167
|
+
if (match[1] !== "default") {
|
|
168
|
+
examples.push(match[1]);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return examples;
|
|
172
|
+
}
|
|
173
|
+
function processComponent(componentDir, componentName) {
|
|
174
|
+
const typesPath = path3.join(componentDir, "types.ts");
|
|
175
|
+
const storiesPath = path3.join(componentDir, `${componentName}.stories.ts`);
|
|
176
|
+
let typesContent = "";
|
|
177
|
+
if (fs4.existsSync(typesPath)) {
|
|
178
|
+
typesContent = fs4.readFileSync(typesPath, "utf-8");
|
|
179
|
+
}
|
|
180
|
+
const escapedName = escapeRegex(componentName);
|
|
181
|
+
const exactPropsRegex = new RegExp(`export\\s+(?:interface|type)\\s+(${escapedName}Props)\\b`);
|
|
182
|
+
const exactMatch = typesContent.match(exactPropsRegex);
|
|
183
|
+
const subComponentPropsRegex = new RegExp(
|
|
184
|
+
`export\\s+(?:interface|type)\\s+(${escapedName}\\w*Props)\\b`
|
|
185
|
+
);
|
|
186
|
+
const subComponentMatch = typesContent.match(subComponentPropsRegex);
|
|
187
|
+
const anyPropsMatch = typesContent.match(/export\s+(?:interface|type)\s+(Ui\w+Props)/);
|
|
188
|
+
const interfaceName = exactMatch?.[1] || subComponentMatch?.[1] || anyPropsMatch?.[1] || `${componentName}Props`;
|
|
189
|
+
const jsdocData = extractJSDocTags(typesContent, interfaceName);
|
|
190
|
+
const nameKeywords = extractKeywordsFromName(componentName);
|
|
191
|
+
const keywords = jsdocData.keywords.length > 0 ? jsdocData.keywords : nameKeywords;
|
|
192
|
+
const category = jsdocData.category || "Uncategorized";
|
|
193
|
+
if (!jsdocData.category) {
|
|
194
|
+
console.warn(
|
|
195
|
+
` \u26A0\uFE0F ${componentName}: Missing @category in types.ts. See docs/CONTRIBUTION_GUIDELINES.md`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
const props = extractProps(typesContent, interfaceName);
|
|
199
|
+
const examples = extractExamples(storiesPath);
|
|
200
|
+
return {
|
|
201
|
+
description: jsdocData.description || `${componentName} component`,
|
|
202
|
+
category,
|
|
203
|
+
useCases: jsdocData.useCases,
|
|
204
|
+
keywords: [.../* @__PURE__ */ new Set([...keywords, ...nameKeywords])],
|
|
205
|
+
related: jsdocData.related,
|
|
206
|
+
props,
|
|
207
|
+
examples,
|
|
208
|
+
_autoGenerated: true
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function scanComponents() {
|
|
212
|
+
const meta = {};
|
|
213
|
+
const srcPath = getLibrarySourcePath();
|
|
214
|
+
for (const category of getCategoryDirs()) {
|
|
215
|
+
const categoryPath = path3.join(srcPath, category);
|
|
216
|
+
if (!fs4.existsSync(categoryPath)) continue;
|
|
217
|
+
const entries = fs4.readdirSync(categoryPath, { withFileTypes: true });
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
if (!entry.isDirectory()) continue;
|
|
220
|
+
if (!entry.name.startsWith("Ui")) continue;
|
|
221
|
+
if (entry.name === "shadcn") continue;
|
|
222
|
+
const componentDir = path3.join(categoryPath, entry.name);
|
|
223
|
+
const componentMeta = processComponent(componentDir, entry.name);
|
|
224
|
+
if (componentMeta) {
|
|
225
|
+
meta[entry.name] = componentMeta;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return meta;
|
|
230
|
+
}
|
|
231
|
+
function main() {
|
|
232
|
+
console.log("Scanning components...");
|
|
233
|
+
const meta = scanComponents();
|
|
234
|
+
console.log(`Found ${Object.keys(meta).length} components`);
|
|
235
|
+
console.log(`Writing to ${OUTPUT_PATH}...`);
|
|
236
|
+
fs4.writeFileSync(OUTPUT_PATH, JSON.stringify(meta, null, 2));
|
|
237
|
+
console.log("Done!");
|
|
238
|
+
const withUseCases = Object.values(meta).filter((m) => m.useCases.length > 0).length;
|
|
239
|
+
const withKeywords = Object.values(meta).filter((m) => m.keywords.length > 0).length;
|
|
240
|
+
console.log(`
|
|
241
|
+
Summary:`);
|
|
242
|
+
console.log(` Components with @useCases: ${withUseCases}/${Object.keys(meta).length}`);
|
|
243
|
+
console.log(` Components with keywords: ${withKeywords}/${Object.keys(meta).length}`);
|
|
244
|
+
}
|
|
245
|
+
var isMain = import.meta.url === `file://${process.argv[1]}`;
|
|
246
|
+
if (isMain) {
|
|
247
|
+
main();
|
|
248
|
+
}
|
|
249
|
+
export {
|
|
250
|
+
extractJSDocTags,
|
|
251
|
+
extractKeywordsFromName,
|
|
252
|
+
extractProps
|
|
253
|
+
};
|