@tpitre/story-ui 1.0.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/.env.sample +17 -0
- package/LICENSE +21 -0
- package/README.md +531 -0
- package/dist/cli/index.js +250 -0
- package/dist/cli/setup.js +289 -0
- package/dist/index.js +12 -0
- package/dist/mcp-server/index.js +64 -0
- package/dist/mcp-server/routes/claude.js +30 -0
- package/dist/mcp-server/routes/components.js +26 -0
- package/dist/mcp-server/routes/generateStory.js +289 -0
- package/dist/mcp-server/routes/memoryStories.js +141 -0
- package/dist/mcp-server/routes/storySync.js +147 -0
- package/dist/story-generator/componentDiscovery.js +222 -0
- package/dist/story-generator/configLoader.js +482 -0
- package/dist/story-generator/generateStory.js +19 -0
- package/dist/story-generator/gitignoreManager.js +182 -0
- package/dist/story-generator/inMemoryStoryService.js +128 -0
- package/dist/story-generator/productionGitignoreManager.js +333 -0
- package/dist/story-generator/promptGenerator.js +201 -0
- package/dist/story-generator/storySync.js +201 -0
- package/dist/story-ui.config.js +114 -0
- package/dist/story-ui.config.loader.js +205 -0
- package/package.json +80 -0
- package/templates/README.md +32 -0
- package/templates/StoryUI/StoryUIPanel.stories.tsx +28 -0
- package/templates/StoryUI/StoryUIPanel.tsx +870 -0
- package/templates/StoryUI/index.tsx +2 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Discovers components from a directory structure
|
|
5
|
+
*/
|
|
6
|
+
export function discoverComponentsFromDirectory(componentsPath, componentPrefix = '') {
|
|
7
|
+
if (!fs.existsSync(componentsPath)) {
|
|
8
|
+
console.warn(`Components path does not exist: ${componentsPath}`);
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
const components = [];
|
|
12
|
+
const dirs = fs.readdirSync(componentsPath, { withFileTypes: true })
|
|
13
|
+
.filter(d => d.isDirectory() && d.name !== 'generated' && !d.name.startsWith('.'));
|
|
14
|
+
for (const dir of dirs) {
|
|
15
|
+
const componentName = componentPrefix + dir.name;
|
|
16
|
+
const componentPath = path.join(componentsPath, dir.name);
|
|
17
|
+
// Look for story files to extract component information
|
|
18
|
+
const storyFile = path.join(componentPath, `${dir.name}.stories.tsx`);
|
|
19
|
+
const componentFile = path.join(componentPath, `${dir.name}.tsx`);
|
|
20
|
+
let props = [];
|
|
21
|
+
let description = '';
|
|
22
|
+
let category = 'other';
|
|
23
|
+
let slots = [];
|
|
24
|
+
// Extract information from story file
|
|
25
|
+
if (fs.existsSync(storyFile)) {
|
|
26
|
+
const storyContent = fs.readFileSync(storyFile, 'utf-8');
|
|
27
|
+
props = extractPropsFromStory(storyContent);
|
|
28
|
+
description = extractDescriptionFromStory(storyContent);
|
|
29
|
+
}
|
|
30
|
+
// Extract information from component file
|
|
31
|
+
if (fs.existsSync(componentFile)) {
|
|
32
|
+
const componentContent = fs.readFileSync(componentFile, 'utf-8');
|
|
33
|
+
if (!description) {
|
|
34
|
+
description = extractDescriptionFromComponent(componentContent);
|
|
35
|
+
}
|
|
36
|
+
slots = extractSlotsFromComponent(componentContent);
|
|
37
|
+
}
|
|
38
|
+
// Categorize component based on name and content
|
|
39
|
+
category = categorizeComponent(componentName, description);
|
|
40
|
+
components.push({
|
|
41
|
+
name: componentName,
|
|
42
|
+
filePath: componentPath,
|
|
43
|
+
props,
|
|
44
|
+
description: description || `${componentName} component`,
|
|
45
|
+
category,
|
|
46
|
+
slots,
|
|
47
|
+
examples: []
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return components;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Discovers components from custom-elements.json (Web Components)
|
|
54
|
+
*/
|
|
55
|
+
export function discoverComponentsFromCustomElements(customElementsPath, componentPrefix = '') {
|
|
56
|
+
if (!fs.existsSync(customElementsPath)) {
|
|
57
|
+
console.warn(`Custom elements file does not exist: ${customElementsPath}`);
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const customElements = JSON.parse(fs.readFileSync(customElementsPath, 'utf-8'));
|
|
62
|
+
const components = [];
|
|
63
|
+
if (customElements.modules) {
|
|
64
|
+
for (const module of customElements.modules) {
|
|
65
|
+
if (module.declarations) {
|
|
66
|
+
for (const declaration of module.declarations) {
|
|
67
|
+
if (declaration.kind === 'class' && declaration.customElement) {
|
|
68
|
+
const componentName = componentPrefix + declaration.name;
|
|
69
|
+
const props = declaration.members
|
|
70
|
+
?.filter((m) => m.kind === 'field' && m.privacy !== 'private')
|
|
71
|
+
?.map((m) => m.name) || [];
|
|
72
|
+
components.push({
|
|
73
|
+
name: componentName,
|
|
74
|
+
filePath: module.path || '',
|
|
75
|
+
props,
|
|
76
|
+
description: declaration.description || `${componentName} component`,
|
|
77
|
+
category: categorizeComponent(componentName, declaration.description || ''),
|
|
78
|
+
slots: declaration.slots?.map((s) => s.name) || [],
|
|
79
|
+
examples: []
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return components;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error('Error parsing custom-elements.json:', error);
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Discovers components from package.json exports or index files
|
|
95
|
+
*/
|
|
96
|
+
export function discoverComponentsFromPackage(packagePath, componentPrefix = '') {
|
|
97
|
+
const components = [];
|
|
98
|
+
// Try to find index file
|
|
99
|
+
const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
|
|
100
|
+
let indexPath = null;
|
|
101
|
+
for (const indexFile of indexFiles) {
|
|
102
|
+
const fullPath = path.join(packagePath, indexFile);
|
|
103
|
+
if (fs.existsSync(fullPath)) {
|
|
104
|
+
indexPath = fullPath;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (indexPath) {
|
|
109
|
+
const indexContent = fs.readFileSync(indexPath, 'utf-8');
|
|
110
|
+
const exportMatches = indexContent.match(/export\s+{\s*([^}]+)\s*}/g);
|
|
111
|
+
if (exportMatches) {
|
|
112
|
+
for (const match of exportMatches) {
|
|
113
|
+
const exports = match.match(/export\s+{\s*([^}]+)\s*}/)?.[1];
|
|
114
|
+
if (exports) {
|
|
115
|
+
const exportItems = exports
|
|
116
|
+
.split(',')
|
|
117
|
+
.map(item => item.trim())
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
for (const item of exportItems) {
|
|
120
|
+
// Handle export aliases like "Input as TextInput"
|
|
121
|
+
if (item.includes(' as ')) {
|
|
122
|
+
const [, alias] = item.split(' as ').map(part => part.trim());
|
|
123
|
+
if (alias && (!componentPrefix || alias.startsWith(componentPrefix))) {
|
|
124
|
+
components.push({
|
|
125
|
+
name: alias,
|
|
126
|
+
filePath: indexPath,
|
|
127
|
+
props: [],
|
|
128
|
+
description: `${alias} component`,
|
|
129
|
+
category: categorizeComponent(alias, ''),
|
|
130
|
+
examples: []
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Regular export
|
|
136
|
+
if (!componentPrefix || item.startsWith(componentPrefix)) {
|
|
137
|
+
components.push({
|
|
138
|
+
name: item,
|
|
139
|
+
filePath: indexPath,
|
|
140
|
+
props: [],
|
|
141
|
+
description: `${item} component`,
|
|
142
|
+
category: categorizeComponent(item, ''),
|
|
143
|
+
examples: []
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return components;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Main discovery function that tries multiple methods
|
|
156
|
+
*/
|
|
157
|
+
export function discoverComponents(config) {
|
|
158
|
+
let components = [];
|
|
159
|
+
// Try directory-based discovery first
|
|
160
|
+
if (config.componentsPath) {
|
|
161
|
+
components = discoverComponentsFromDirectory(config.componentsPath, config.componentPrefix);
|
|
162
|
+
}
|
|
163
|
+
// Fallback to custom elements if no components found
|
|
164
|
+
if (components.length === 0 && config.componentsMetadataPath) {
|
|
165
|
+
components = discoverComponentsFromCustomElements(config.componentsMetadataPath, config.componentPrefix);
|
|
166
|
+
}
|
|
167
|
+
// Fallback to package-based discovery
|
|
168
|
+
if (components.length === 0 && config.componentsPath) {
|
|
169
|
+
components = discoverComponentsFromPackage(config.componentsPath, config.componentPrefix);
|
|
170
|
+
}
|
|
171
|
+
return components;
|
|
172
|
+
}
|
|
173
|
+
// Helper functions
|
|
174
|
+
function extractPropsFromStory(content) {
|
|
175
|
+
const argTypesMatch = content.match(/argTypes:\s*{([\s\S]*?)}[,\n]/);
|
|
176
|
+
if (argTypesMatch) {
|
|
177
|
+
const argTypesBlock = argTypesMatch[1];
|
|
178
|
+
return Array.from(argTypesBlock.matchAll(/([a-zA-Z0-9_]+):/g)).map(m => m[1]);
|
|
179
|
+
}
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
function extractDescriptionFromStory(content) {
|
|
183
|
+
const descMatch = content.match(/\/\*\*([\s\S]*?)\*\//);
|
|
184
|
+
if (descMatch) {
|
|
185
|
+
return descMatch[1].replace(/\*/g, '').trim();
|
|
186
|
+
}
|
|
187
|
+
return '';
|
|
188
|
+
}
|
|
189
|
+
function extractDescriptionFromComponent(content) {
|
|
190
|
+
const descMatch = content.match(/\/\*\*([\s\S]*?)\*\//);
|
|
191
|
+
if (descMatch) {
|
|
192
|
+
return descMatch[1].replace(/\*/g, '').trim();
|
|
193
|
+
}
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
196
|
+
function extractSlotsFromComponent(content) {
|
|
197
|
+
const slotMatches = content.match(/slot=['"]([^'"]+)['"]/g);
|
|
198
|
+
if (slotMatches) {
|
|
199
|
+
return slotMatches.map(match => match.match(/slot=['"]([^'"]+)['"]/)?.[1]).filter(Boolean);
|
|
200
|
+
}
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
function categorizeComponent(name, description) {
|
|
204
|
+
const lowerName = name.toLowerCase();
|
|
205
|
+
const lowerDesc = description.toLowerCase();
|
|
206
|
+
if (lowerName.includes('layout') || lowerName.includes('grid') || lowerName.includes('container') || lowerName.includes('section')) {
|
|
207
|
+
return 'layout';
|
|
208
|
+
}
|
|
209
|
+
if (lowerName.includes('input') || lowerName.includes('form') || lowerName.includes('field') || lowerName.includes('select')) {
|
|
210
|
+
return 'form';
|
|
211
|
+
}
|
|
212
|
+
if (lowerName.includes('nav') || lowerName.includes('menu') || lowerName.includes('breadcrumb') || lowerName.includes('tab')) {
|
|
213
|
+
return 'navigation';
|
|
214
|
+
}
|
|
215
|
+
if (lowerName.includes('toast') || lowerName.includes('alert') || lowerName.includes('notification') || lowerName.includes('modal')) {
|
|
216
|
+
return 'feedback';
|
|
217
|
+
}
|
|
218
|
+
if (lowerName.includes('button') || lowerName.includes('card') || lowerName.includes('heading') || lowerName.includes('text')) {
|
|
219
|
+
return 'content';
|
|
220
|
+
}
|
|
221
|
+
return 'other';
|
|
222
|
+
}
|