@idealyst/tooling 1.2.3
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/README.md +185 -0
- package/package.json +89 -0
- package/src/analyzer/component-analyzer.ts +418 -0
- package/src/analyzer/index.ts +16 -0
- package/src/analyzer/theme-analyzer.ts +473 -0
- package/src/analyzer/types.ts +132 -0
- package/src/analyzers/index.ts +1 -0
- package/src/analyzers/platformImports.ts +395 -0
- package/src/index.ts +142 -0
- package/src/rules/index.ts +2 -0
- package/src/rules/reactDomPrimitives.ts +217 -0
- package/src/rules/reactNativePrimitives.ts +363 -0
- package/src/types.ts +173 -0
- package/src/utils/fileClassifier.ts +135 -0
- package/src/utils/importParser.ts +235 -0
- package/src/utils/index.ts +2 -0
- package/src/vite-plugin.ts +264 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnalyzerOptions,
|
|
3
|
+
AnalysisResult,
|
|
4
|
+
FileInput,
|
|
5
|
+
FileType,
|
|
6
|
+
ImportInfo,
|
|
7
|
+
Severity,
|
|
8
|
+
Violation,
|
|
9
|
+
ViolationType,
|
|
10
|
+
} from '../types';
|
|
11
|
+
import { classifyFile } from '../utils/fileClassifier';
|
|
12
|
+
import { parseImports } from '../utils/importParser';
|
|
13
|
+
import {
|
|
14
|
+
REACT_NATIVE_PRIMITIVE_NAMES,
|
|
15
|
+
REACT_NATIVE_SOURCES,
|
|
16
|
+
isReactNativePrimitive,
|
|
17
|
+
} from '../rules/reactNativePrimitives';
|
|
18
|
+
import {
|
|
19
|
+
REACT_DOM_PRIMITIVE_NAMES,
|
|
20
|
+
REACT_DOM_SOURCES,
|
|
21
|
+
isReactDomPrimitive,
|
|
22
|
+
} from '../rules/reactDomPrimitives';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default analyzer options
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_OPTIONS: Required<AnalyzerOptions> = {
|
|
28
|
+
severity: 'error',
|
|
29
|
+
additionalNativePrimitives: [],
|
|
30
|
+
additionalDomPrimitives: [],
|
|
31
|
+
ignoredPrimitives: [],
|
|
32
|
+
ignoredPatterns: [],
|
|
33
|
+
additionalNativeSources: [],
|
|
34
|
+
additionalDomSources: [],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if a file path matches any of the ignored patterns
|
|
39
|
+
*/
|
|
40
|
+
function matchesIgnoredPattern(
|
|
41
|
+
filePath: string,
|
|
42
|
+
patterns: string[]
|
|
43
|
+
): boolean {
|
|
44
|
+
if (patterns.length === 0) return false;
|
|
45
|
+
|
|
46
|
+
for (const pattern of patterns) {
|
|
47
|
+
// Simple glob matching - convert glob to regex
|
|
48
|
+
const regexPattern = pattern
|
|
49
|
+
.replace(/\*\*/g, '{{GLOBSTAR}}')
|
|
50
|
+
.replace(/\*/g, '[^/]*')
|
|
51
|
+
.replace(/{{GLOBSTAR}}/g, '.*')
|
|
52
|
+
.replace(/\?/g, '.');
|
|
53
|
+
|
|
54
|
+
const regex = new RegExp(regexPattern);
|
|
55
|
+
if (regex.test(filePath)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates a violation object
|
|
65
|
+
*/
|
|
66
|
+
function createViolation(
|
|
67
|
+
type: ViolationType,
|
|
68
|
+
primitive: string,
|
|
69
|
+
source: string,
|
|
70
|
+
filePath: string,
|
|
71
|
+
line: number,
|
|
72
|
+
column: number,
|
|
73
|
+
severity: Severity
|
|
74
|
+
): Violation {
|
|
75
|
+
const messages: Record<ViolationType, string> = {
|
|
76
|
+
'native-in-shared': `React Native primitive '${primitive}' from '${source}' should not be used in shared files. Use a .native.tsx file instead.`,
|
|
77
|
+
'dom-in-shared': `React DOM primitive '${primitive}' from '${source}' should not be used in shared files. Use a .web.tsx file instead.`,
|
|
78
|
+
'native-in-web': `React Native primitive '${primitive}' from '${source}' should not be used in web-specific files.`,
|
|
79
|
+
'dom-in-native': `React DOM primitive '${primitive}' from '${source}' should not be used in native-specific files.`,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
type,
|
|
84
|
+
primitive,
|
|
85
|
+
source,
|
|
86
|
+
filePath,
|
|
87
|
+
line,
|
|
88
|
+
column,
|
|
89
|
+
message: messages[type],
|
|
90
|
+
severity,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if an import should be flagged as a React Native primitive
|
|
96
|
+
*/
|
|
97
|
+
function isNativePrimitive(
|
|
98
|
+
importInfo: ImportInfo,
|
|
99
|
+
additionalPrimitives: string[]
|
|
100
|
+
): boolean {
|
|
101
|
+
const name = importInfo.originalName ?? importInfo.name;
|
|
102
|
+
|
|
103
|
+
// Check built-in primitives
|
|
104
|
+
if (isReactNativePrimitive(name)) return true;
|
|
105
|
+
|
|
106
|
+
// Check additional primitives
|
|
107
|
+
if (additionalPrimitives.includes(name)) return true;
|
|
108
|
+
|
|
109
|
+
// Check if the source is a known React Native source
|
|
110
|
+
const nativeSources: Set<string> = new Set([...REACT_NATIVE_SOURCES]);
|
|
111
|
+
if (nativeSources.has(importInfo.source)) {
|
|
112
|
+
// Any import from react-native that's a component (starts with uppercase)
|
|
113
|
+
if (/^[A-Z]/.test(name)) return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if an import should be flagged as a React DOM primitive
|
|
121
|
+
*/
|
|
122
|
+
function isDomPrimitive(
|
|
123
|
+
importInfo: ImportInfo,
|
|
124
|
+
additionalPrimitives: string[]
|
|
125
|
+
): boolean {
|
|
126
|
+
const name = importInfo.originalName ?? importInfo.name;
|
|
127
|
+
|
|
128
|
+
// Check built-in primitives
|
|
129
|
+
if (isReactDomPrimitive(name)) return true;
|
|
130
|
+
|
|
131
|
+
// Check additional primitives
|
|
132
|
+
if (additionalPrimitives.includes(name)) return true;
|
|
133
|
+
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Analyze a single file for platform import violations
|
|
139
|
+
*
|
|
140
|
+
* @param filePath - Path to the file being analyzed
|
|
141
|
+
* @param sourceCode - The source code content
|
|
142
|
+
* @param options - Analyzer options
|
|
143
|
+
* @returns Analysis result with violations
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* const result = analyzePlatformImports(
|
|
148
|
+
* 'src/components/Button.tsx',
|
|
149
|
+
* sourceCode,
|
|
150
|
+
* { severity: 'error' }
|
|
151
|
+
* );
|
|
152
|
+
*
|
|
153
|
+
* if (result.violations.length > 0) {
|
|
154
|
+
* for (const v of result.violations) {
|
|
155
|
+
* console.error(`${v.filePath}:${v.line}:${v.column} - ${v.message}`);
|
|
156
|
+
* }
|
|
157
|
+
* }
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export function analyzePlatformImports(
|
|
161
|
+
filePath: string,
|
|
162
|
+
sourceCode: string,
|
|
163
|
+
options?: AnalyzerOptions
|
|
164
|
+
): AnalysisResult {
|
|
165
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
166
|
+
const fileType = classifyFile(filePath);
|
|
167
|
+
const violations: Violation[] = [];
|
|
168
|
+
|
|
169
|
+
// Skip ignored files
|
|
170
|
+
if (matchesIgnoredPattern(filePath, opts.ignoredPatterns)) {
|
|
171
|
+
return {
|
|
172
|
+
filePath,
|
|
173
|
+
fileType,
|
|
174
|
+
violations: [],
|
|
175
|
+
imports: [],
|
|
176
|
+
passed: true,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Skip non-component files
|
|
181
|
+
if (fileType === 'other' || fileType === 'styles' || fileType === 'types') {
|
|
182
|
+
return {
|
|
183
|
+
filePath,
|
|
184
|
+
fileType,
|
|
185
|
+
violations: [],
|
|
186
|
+
imports: [],
|
|
187
|
+
passed: true,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Parse imports
|
|
192
|
+
const imports = parseImports(sourceCode, filePath, {
|
|
193
|
+
additionalNativeSources: opts.additionalNativeSources,
|
|
194
|
+
additionalDomSources: opts.additionalDomSources,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Build ignored primitives set
|
|
198
|
+
const ignoredPrimitives = new Set(opts.ignoredPrimitives);
|
|
199
|
+
|
|
200
|
+
// Analyze each import
|
|
201
|
+
for (const imp of imports) {
|
|
202
|
+
// Skip type-only imports
|
|
203
|
+
if (imp.isTypeOnly) continue;
|
|
204
|
+
|
|
205
|
+
// Skip ignored primitives
|
|
206
|
+
const primitiveName = imp.originalName ?? imp.name;
|
|
207
|
+
if (ignoredPrimitives.has(primitiveName)) continue;
|
|
208
|
+
|
|
209
|
+
// Check for violations based on file type
|
|
210
|
+
switch (fileType) {
|
|
211
|
+
case 'shared':
|
|
212
|
+
// Shared files should not use platform-specific imports
|
|
213
|
+
if (isNativePrimitive(imp, opts.additionalNativePrimitives)) {
|
|
214
|
+
violations.push(
|
|
215
|
+
createViolation(
|
|
216
|
+
'native-in-shared',
|
|
217
|
+
primitiveName,
|
|
218
|
+
imp.source,
|
|
219
|
+
filePath,
|
|
220
|
+
imp.line,
|
|
221
|
+
imp.column,
|
|
222
|
+
opts.severity
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
if (isDomPrimitive(imp, opts.additionalDomPrimitives)) {
|
|
227
|
+
violations.push(
|
|
228
|
+
createViolation(
|
|
229
|
+
'dom-in-shared',
|
|
230
|
+
primitiveName,
|
|
231
|
+
imp.source,
|
|
232
|
+
filePath,
|
|
233
|
+
imp.line,
|
|
234
|
+
imp.column,
|
|
235
|
+
opts.severity
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
case 'web':
|
|
242
|
+
// Web files should not use React Native imports
|
|
243
|
+
if (isNativePrimitive(imp, opts.additionalNativePrimitives)) {
|
|
244
|
+
violations.push(
|
|
245
|
+
createViolation(
|
|
246
|
+
'native-in-web',
|
|
247
|
+
primitiveName,
|
|
248
|
+
imp.source,
|
|
249
|
+
filePath,
|
|
250
|
+
imp.line,
|
|
251
|
+
imp.column,
|
|
252
|
+
opts.severity
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
|
|
258
|
+
case 'native':
|
|
259
|
+
// Native files should not use React DOM imports
|
|
260
|
+
if (isDomPrimitive(imp, opts.additionalDomPrimitives)) {
|
|
261
|
+
violations.push(
|
|
262
|
+
createViolation(
|
|
263
|
+
'dom-in-native',
|
|
264
|
+
primitiveName,
|
|
265
|
+
imp.source,
|
|
266
|
+
filePath,
|
|
267
|
+
imp.line,
|
|
268
|
+
imp.column,
|
|
269
|
+
opts.severity
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
filePath,
|
|
279
|
+
fileType,
|
|
280
|
+
violations,
|
|
281
|
+
imports,
|
|
282
|
+
passed: violations.length === 0,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Analyze multiple files for platform import violations
|
|
288
|
+
*
|
|
289
|
+
* @param files - Array of files to analyze
|
|
290
|
+
* @param options - Analyzer options
|
|
291
|
+
* @returns Array of analysis results
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```typescript
|
|
295
|
+
* const results = analyzeFiles(
|
|
296
|
+
* [
|
|
297
|
+
* { path: 'Button.tsx', content: buttonSource },
|
|
298
|
+
* { path: 'Button.web.tsx', content: webSource },
|
|
299
|
+
* { path: 'Button.native.tsx', content: nativeSource },
|
|
300
|
+
* ],
|
|
301
|
+
* { severity: 'warning' }
|
|
302
|
+
* );
|
|
303
|
+
*
|
|
304
|
+
* const failed = results.filter(r => !r.passed);
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
export function analyzeFiles(
|
|
308
|
+
files: FileInput[],
|
|
309
|
+
options?: AnalyzerOptions
|
|
310
|
+
): AnalysisResult[] {
|
|
311
|
+
return files.map((file) =>
|
|
312
|
+
analyzePlatformImports(file.path, file.content, options)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get a summary of analysis results
|
|
318
|
+
*/
|
|
319
|
+
export interface AnalysisSummary {
|
|
320
|
+
totalFiles: number;
|
|
321
|
+
passedFiles: number;
|
|
322
|
+
failedFiles: number;
|
|
323
|
+
totalViolations: number;
|
|
324
|
+
violationsByType: Record<ViolationType, number>;
|
|
325
|
+
violationsBySeverity: Record<Severity, number>;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Summarize analysis results
|
|
330
|
+
*
|
|
331
|
+
* @param results - Array of analysis results
|
|
332
|
+
* @returns Summary statistics
|
|
333
|
+
*/
|
|
334
|
+
export function summarizeResults(results: AnalysisResult[]): AnalysisSummary {
|
|
335
|
+
const violationsByType: Record<ViolationType, number> = {
|
|
336
|
+
'native-in-shared': 0,
|
|
337
|
+
'dom-in-shared': 0,
|
|
338
|
+
'native-in-web': 0,
|
|
339
|
+
'dom-in-native': 0,
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const violationsBySeverity: Record<Severity, number> = {
|
|
343
|
+
error: 0,
|
|
344
|
+
warning: 0,
|
|
345
|
+
info: 0,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
let totalViolations = 0;
|
|
349
|
+
|
|
350
|
+
for (const result of results) {
|
|
351
|
+
for (const violation of result.violations) {
|
|
352
|
+
totalViolations++;
|
|
353
|
+
violationsByType[violation.type]++;
|
|
354
|
+
violationsBySeverity[violation.severity]++;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
totalFiles: results.length,
|
|
360
|
+
passedFiles: results.filter((r) => r.passed).length,
|
|
361
|
+
failedFiles: results.filter((r) => !r.passed).length,
|
|
362
|
+
totalViolations,
|
|
363
|
+
violationsByType,
|
|
364
|
+
violationsBySeverity,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Format a violation for console output
|
|
370
|
+
*/
|
|
371
|
+
export function formatViolation(violation: Violation): string {
|
|
372
|
+
const severityPrefix =
|
|
373
|
+
violation.severity === 'error'
|
|
374
|
+
? 'ERROR'
|
|
375
|
+
: violation.severity === 'warning'
|
|
376
|
+
? 'WARN'
|
|
377
|
+
: 'INFO';
|
|
378
|
+
|
|
379
|
+
return `${severityPrefix}: ${violation.filePath}:${violation.line}:${violation.column} - ${violation.message}`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Format all violations from results for console output
|
|
384
|
+
*/
|
|
385
|
+
export function formatViolations(results: AnalysisResult[]): string[] {
|
|
386
|
+
const lines: string[] = [];
|
|
387
|
+
|
|
388
|
+
for (const result of results) {
|
|
389
|
+
for (const violation of result.violations) {
|
|
390
|
+
lines.push(formatViolation(violation));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return lines;
|
|
395
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @idealyst/tooling
|
|
3
|
+
*
|
|
4
|
+
* Code analysis and validation utilities for Idealyst Framework.
|
|
5
|
+
* Provides tools for babel plugins, CLI, and MCP to validate cross-platform code.
|
|
6
|
+
*
|
|
7
|
+
* Also provides component documentation generation:
|
|
8
|
+
* - analyzeComponents(): Generate a component registry from TypeScript source
|
|
9
|
+
* - analyzeTheme(): Extract theme values (intents, sizes, etc.)
|
|
10
|
+
* - idealystDocsPlugin(): Vite plugin for virtual module support
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Types
|
|
14
|
+
export * from './types';
|
|
15
|
+
|
|
16
|
+
// Component Documentation (also available via @idealyst/tooling/docs)
|
|
17
|
+
export {
|
|
18
|
+
analyzeComponents,
|
|
19
|
+
analyzeTheme,
|
|
20
|
+
// Babel plugin compatibility
|
|
21
|
+
loadThemeKeys,
|
|
22
|
+
resetThemeCache,
|
|
23
|
+
type BabelThemeKeys,
|
|
24
|
+
// Types
|
|
25
|
+
type ComponentRegistry,
|
|
26
|
+
type ComponentDefinition,
|
|
27
|
+
type PropDefinition,
|
|
28
|
+
type ThemeValues,
|
|
29
|
+
type ComponentAnalyzerOptions,
|
|
30
|
+
} from './analyzer';
|
|
31
|
+
|
|
32
|
+
// Vite Plugin (also available via @idealyst/tooling/vite)
|
|
33
|
+
export { idealystDocsPlugin, generateComponentRegistry } from './vite-plugin';
|
|
34
|
+
export type { IdealystDocsPluginOptions } from './analyzer/types';
|
|
35
|
+
|
|
36
|
+
// Analyzers
|
|
37
|
+
export {
|
|
38
|
+
analyzePlatformImports,
|
|
39
|
+
analyzeFiles,
|
|
40
|
+
summarizeResults,
|
|
41
|
+
formatViolation,
|
|
42
|
+
formatViolations,
|
|
43
|
+
type AnalysisSummary,
|
|
44
|
+
} from './analyzers';
|
|
45
|
+
|
|
46
|
+
// Rules
|
|
47
|
+
export {
|
|
48
|
+
// React Native
|
|
49
|
+
REACT_NATIVE_SOURCES,
|
|
50
|
+
REACT_NATIVE_PRIMITIVES,
|
|
51
|
+
REACT_NATIVE_PRIMITIVE_NAMES,
|
|
52
|
+
REACT_NATIVE_RULE_SET,
|
|
53
|
+
isReactNativePrimitive,
|
|
54
|
+
getReactNativePrimitive,
|
|
55
|
+
// React DOM
|
|
56
|
+
REACT_DOM_SOURCES,
|
|
57
|
+
REACT_DOM_PRIMITIVES,
|
|
58
|
+
REACT_DOM_PRIMITIVE_NAMES,
|
|
59
|
+
REACT_DOM_RULE_SET,
|
|
60
|
+
HTML_INTRINSIC_ELEMENTS,
|
|
61
|
+
HTML_ELEMENT_NAMES,
|
|
62
|
+
isReactDomPrimitive,
|
|
63
|
+
isHtmlElement,
|
|
64
|
+
getReactDomPrimitive,
|
|
65
|
+
} from './rules';
|
|
66
|
+
|
|
67
|
+
// Utilities
|
|
68
|
+
export {
|
|
69
|
+
classifyFile,
|
|
70
|
+
isComponentFile,
|
|
71
|
+
isSharedFile,
|
|
72
|
+
isPlatformSpecificFile,
|
|
73
|
+
getExpectedPlatform,
|
|
74
|
+
getBaseName,
|
|
75
|
+
parseImports,
|
|
76
|
+
getPlatformForSource,
|
|
77
|
+
filterPlatformImports,
|
|
78
|
+
getUniqueSources,
|
|
79
|
+
groupImportsBySource,
|
|
80
|
+
type ImportParserOptions,
|
|
81
|
+
} from './utils';
|
|
82
|
+
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// Runtime Placeholders - These get replaced by the Vite plugin at build time
|
|
85
|
+
// =============================================================================
|
|
86
|
+
|
|
87
|
+
import type { ComponentRegistry, ComponentDefinition } from './analyzer/types';
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Component registry placeholder.
|
|
91
|
+
* This empty object is replaced at build time by the idealystDocsPlugin
|
|
92
|
+
* with the actual component metadata extracted from your codebase.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* import { componentRegistry } from '@idealyst/tooling';
|
|
97
|
+
*
|
|
98
|
+
* // Access component definitions
|
|
99
|
+
* const buttonDef = componentRegistry['Button'];
|
|
100
|
+
* console.log(buttonDef.description);
|
|
101
|
+
* console.log(buttonDef.props);
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export const componentRegistry: ComponentRegistry = {};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* List of all component names in the registry.
|
|
108
|
+
* Replaced at build time by the Vite plugin.
|
|
109
|
+
*/
|
|
110
|
+
export const componentNames: string[] = [];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get components filtered by category.
|
|
114
|
+
* Replaced at build time by the Vite plugin.
|
|
115
|
+
*/
|
|
116
|
+
export function getComponentsByCategory(category: string): string[] {
|
|
117
|
+
return Object.entries(componentRegistry)
|
|
118
|
+
.filter(([_, def]) => def.category === category)
|
|
119
|
+
.map(([name]) => name);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get prop configuration for a component (useful for playgrounds).
|
|
124
|
+
* Replaced at build time by the Vite plugin.
|
|
125
|
+
*/
|
|
126
|
+
export function getPropConfig(componentName: string): Record<string, any> {
|
|
127
|
+
const def = componentRegistry[componentName];
|
|
128
|
+
if (!def) return {};
|
|
129
|
+
|
|
130
|
+
return Object.entries(def.props).reduce((acc, [key, prop]) => {
|
|
131
|
+
if (prop.values && prop.values.length > 0) {
|
|
132
|
+
acc[key] = { type: 'select', options: prop.values, default: prop.default };
|
|
133
|
+
} else if (prop.type === 'boolean') {
|
|
134
|
+
acc[key] = { type: 'boolean', default: prop.default ?? false };
|
|
135
|
+
} else if (prop.type === 'string') {
|
|
136
|
+
acc[key] = { type: 'text', default: prop.default ?? '' };
|
|
137
|
+
} else if (prop.type === 'number') {
|
|
138
|
+
acc[key] = { type: 'number', default: prop.default ?? 0 };
|
|
139
|
+
}
|
|
140
|
+
return acc;
|
|
141
|
+
}, {} as Record<string, any>);
|
|
142
|
+
}
|