@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.
@@ -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
+ }