@idealyst/tooling 1.2.3 → 1.2.6
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/package.json +1 -1
- package/src/analyzer/component-analyzer.ts +139 -0
- package/src/analyzer/types.ts +27 -0
- package/src/index.ts +2 -0
- package/src/vite-plugin.ts +25 -85
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
ComponentAnalyzerOptions,
|
|
19
19
|
ThemeValues,
|
|
20
20
|
ComponentCategory,
|
|
21
|
+
SampleProps,
|
|
21
22
|
} from './types';
|
|
22
23
|
import { analyzeTheme } from './theme-analyzer';
|
|
23
24
|
|
|
@@ -191,15 +192,153 @@ function analyzeComponentDir(
|
|
|
191
192
|
// Determine category
|
|
192
193
|
const category = inferCategory(componentName);
|
|
193
194
|
|
|
195
|
+
// Look for docs.ts to extract sample props
|
|
196
|
+
const sampleProps = extractSampleProps(dir);
|
|
197
|
+
|
|
194
198
|
return {
|
|
195
199
|
name: componentName,
|
|
196
200
|
description,
|
|
197
201
|
props,
|
|
198
202
|
category,
|
|
199
203
|
filePath: path.relative(process.cwd(), dir),
|
|
204
|
+
sampleProps,
|
|
200
205
|
};
|
|
201
206
|
}
|
|
202
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Extract sample props from docs.ts file if it exists.
|
|
210
|
+
* The docs.ts file should export a `sampleProps` object.
|
|
211
|
+
*/
|
|
212
|
+
function extractSampleProps(dir: string): SampleProps | undefined {
|
|
213
|
+
const docsPath = path.join(dir, 'docs.ts');
|
|
214
|
+
|
|
215
|
+
if (!fs.existsSync(docsPath)) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const content = fs.readFileSync(docsPath, 'utf-8');
|
|
221
|
+
|
|
222
|
+
// Create a simple TypeScript program to extract the sampleProps export
|
|
223
|
+
const sourceFile = ts.createSourceFile(
|
|
224
|
+
'docs.ts',
|
|
225
|
+
content,
|
|
226
|
+
ts.ScriptTarget.ES2020,
|
|
227
|
+
true,
|
|
228
|
+
ts.ScriptKind.TS
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
let samplePropsNode: ts.ObjectLiteralExpression | null = null;
|
|
232
|
+
|
|
233
|
+
// Find the sampleProps export
|
|
234
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
235
|
+
// Handle: export const sampleProps = { ... }
|
|
236
|
+
if (ts.isVariableStatement(node)) {
|
|
237
|
+
const isExported = node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
238
|
+
if (isExported) {
|
|
239
|
+
for (const decl of node.declarationList.declarations) {
|
|
240
|
+
if (ts.isIdentifier(decl.name) && decl.name.text === 'sampleProps' && decl.initializer) {
|
|
241
|
+
if (ts.isObjectLiteralExpression(decl.initializer)) {
|
|
242
|
+
samplePropsNode = decl.initializer;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (!samplePropsNode) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Extract the object literal as JSON-compatible structure
|
|
255
|
+
// This is a simplified extraction - it handles basic literals
|
|
256
|
+
const result: SampleProps = {};
|
|
257
|
+
|
|
258
|
+
for (const prop of samplePropsNode.properties) {
|
|
259
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
260
|
+
const propName = prop.name.text;
|
|
261
|
+
|
|
262
|
+
if (propName === 'props' && ts.isObjectLiteralExpression(prop.initializer)) {
|
|
263
|
+
result.props = extractObjectLiteral(prop.initializer, content);
|
|
264
|
+
} else if (propName === 'children') {
|
|
265
|
+
// For children, we store the raw source text
|
|
266
|
+
result.children = prop.initializer.getText(sourceFile);
|
|
267
|
+
} else if (propName === 'state' && ts.isObjectLiteralExpression(prop.initializer)) {
|
|
268
|
+
// Extract state configuration for controlled components
|
|
269
|
+
result.state = extractObjectLiteral(prop.initializer, content);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
275
|
+
} catch (e) {
|
|
276
|
+
console.warn(`[component-analyzer] Error reading docs.ts in ${dir}:`, e);
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Extract an object literal to a plain object (for simple literal values).
|
|
283
|
+
*/
|
|
284
|
+
function extractObjectLiteral(node: ts.ObjectLiteralExpression, sourceContent: string): Record<string, any> {
|
|
285
|
+
const result: Record<string, any> = {};
|
|
286
|
+
|
|
287
|
+
for (const prop of node.properties) {
|
|
288
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
289
|
+
const key = prop.name.text;
|
|
290
|
+
const init = prop.initializer;
|
|
291
|
+
|
|
292
|
+
if (ts.isStringLiteral(init)) {
|
|
293
|
+
result[key] = init.text;
|
|
294
|
+
} else if (ts.isNumericLiteral(init)) {
|
|
295
|
+
result[key] = Number(init.text);
|
|
296
|
+
} else if (init.kind === ts.SyntaxKind.TrueKeyword) {
|
|
297
|
+
result[key] = true;
|
|
298
|
+
} else if (init.kind === ts.SyntaxKind.FalseKeyword) {
|
|
299
|
+
result[key] = false;
|
|
300
|
+
} else if (ts.isArrayLiteralExpression(init)) {
|
|
301
|
+
result[key] = extractArrayLiteral(init, sourceContent);
|
|
302
|
+
} else if (ts.isObjectLiteralExpression(init)) {
|
|
303
|
+
result[key] = extractObjectLiteral(init, sourceContent);
|
|
304
|
+
} else {
|
|
305
|
+
// For complex expressions (JSX, functions), store the raw source
|
|
306
|
+
result[key] = init.getText();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Extract an array literal to a plain array.
|
|
316
|
+
*/
|
|
317
|
+
function extractArrayLiteral(node: ts.ArrayLiteralExpression, sourceContent: string): any[] {
|
|
318
|
+
const result: any[] = [];
|
|
319
|
+
|
|
320
|
+
for (const element of node.elements) {
|
|
321
|
+
if (ts.isStringLiteral(element)) {
|
|
322
|
+
result.push(element.text);
|
|
323
|
+
} else if (ts.isNumericLiteral(element)) {
|
|
324
|
+
result.push(Number(element.text));
|
|
325
|
+
} else if (element.kind === ts.SyntaxKind.TrueKeyword) {
|
|
326
|
+
result.push(true);
|
|
327
|
+
} else if (element.kind === ts.SyntaxKind.FalseKeyword) {
|
|
328
|
+
result.push(false);
|
|
329
|
+
} else if (ts.isObjectLiteralExpression(element)) {
|
|
330
|
+
result.push(extractObjectLiteral(element, sourceContent));
|
|
331
|
+
} else if (ts.isArrayLiteralExpression(element)) {
|
|
332
|
+
result.push(extractArrayLiteral(element, sourceContent));
|
|
333
|
+
} else {
|
|
334
|
+
// For complex expressions, store raw source
|
|
335
|
+
result.push(element.getText());
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
|
|
203
342
|
/**
|
|
204
343
|
* Analyze a single property symbol.
|
|
205
344
|
*/
|
package/src/analyzer/types.ts
CHANGED
|
@@ -28,6 +28,30 @@ export interface PropDefinition {
|
|
|
28
28
|
required: boolean;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Configuration for a controlled state binding
|
|
33
|
+
*/
|
|
34
|
+
export interface ControlledState {
|
|
35
|
+
/** Initial value for the state */
|
|
36
|
+
initial: any;
|
|
37
|
+
/** The prop name that receives the onChange callback */
|
|
38
|
+
onChangeProp: string;
|
|
39
|
+
/** If true, the callback toggles a boolean value instead of receiving a new value */
|
|
40
|
+
toggle?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sample props for rendering the component in documentation
|
|
45
|
+
*/
|
|
46
|
+
export interface SampleProps {
|
|
47
|
+
/** Props needed to render the component (required props, sample data) */
|
|
48
|
+
props?: Record<string, any>;
|
|
49
|
+
/** Default children for the component */
|
|
50
|
+
children?: any;
|
|
51
|
+
/** Controlled state bindings - key is the prop name, value is the state config */
|
|
52
|
+
state?: Record<string, ControlledState>;
|
|
53
|
+
}
|
|
54
|
+
|
|
31
55
|
/**
|
|
32
56
|
* Definition of a component in the registry
|
|
33
57
|
*/
|
|
@@ -46,6 +70,9 @@ export interface ComponentDefinition {
|
|
|
46
70
|
|
|
47
71
|
/** Path to the component file (relative) */
|
|
48
72
|
filePath?: string;
|
|
73
|
+
|
|
74
|
+
/** Sample props for rendering in documentation (from docs.ts) */
|
|
75
|
+
sampleProps?: SampleProps;
|
|
49
76
|
}
|
|
50
77
|
|
|
51
78
|
/**
|
package/src/index.ts
CHANGED
package/src/vite-plugin.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Usage:
|
|
8
8
|
* ```ts
|
|
9
9
|
* // vite.config.ts
|
|
10
|
-
* import { idealystDocsPlugin } from '@idealyst/tooling
|
|
10
|
+
* import { idealystDocsPlugin } from '@idealyst/tooling';
|
|
11
11
|
*
|
|
12
12
|
* export default defineConfig({
|
|
13
13
|
* plugins: [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* });
|
|
20
20
|
* ```
|
|
21
21
|
*
|
|
22
|
-
* Then in your app
|
|
22
|
+
* Then in your app:
|
|
23
23
|
* ```ts
|
|
24
24
|
* import { componentRegistry, componentNames, getComponentsByCategory } from '@idealyst/tooling';
|
|
25
25
|
* ```
|
|
@@ -31,13 +31,6 @@ import * as path from 'path';
|
|
|
31
31
|
import { analyzeComponents } from './analyzer';
|
|
32
32
|
import type { IdealystDocsPluginOptions, ComponentRegistry } from './analyzer/types';
|
|
33
33
|
|
|
34
|
-
// Virtual module for backwards compatibility
|
|
35
|
-
const VIRTUAL_MODULE_ID = 'virtual:idealyst-docs';
|
|
36
|
-
const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
|
|
37
|
-
|
|
38
|
-
// Marker for the tooling package that needs transformation
|
|
39
|
-
const TOOLING_REGISTRY_MARKER = '@idealyst/tooling';
|
|
40
|
-
|
|
41
34
|
/**
|
|
42
35
|
* Create the Idealyst Docs Vite plugin.
|
|
43
36
|
*/
|
|
@@ -45,7 +38,7 @@ export function idealystDocsPlugin(options: IdealystDocsPluginOptions): Plugin {
|
|
|
45
38
|
let registry: ComponentRegistry | null = null;
|
|
46
39
|
let server: ViteDevServer | null = null;
|
|
47
40
|
|
|
48
|
-
const { debug = false, output
|
|
41
|
+
const { debug = false, output, outputPath } = options;
|
|
49
42
|
|
|
50
43
|
const log = (...args: any[]) => {
|
|
51
44
|
if (debug) console.log('[idealyst-docs]', ...args);
|
|
@@ -56,10 +49,15 @@ export function idealystDocsPlugin(options: IdealystDocsPluginOptions): Plugin {
|
|
|
56
49
|
*/
|
|
57
50
|
function generateRegistry(): ComponentRegistry {
|
|
58
51
|
log('Generating component registry...');
|
|
52
|
+
log('Component paths:', options.componentPaths);
|
|
53
|
+
log('Theme path:', options.themePath);
|
|
59
54
|
|
|
60
55
|
try {
|
|
61
56
|
registry = analyzeComponents(options);
|
|
62
57
|
log(`Generated registry with ${Object.keys(registry).length} components`);
|
|
58
|
+
if (debug) {
|
|
59
|
+
log('Components found:', Object.keys(registry));
|
|
60
|
+
}
|
|
63
61
|
|
|
64
62
|
// Optionally write to file
|
|
65
63
|
if (output === 'file' && outputPath) {
|
|
@@ -82,51 +80,6 @@ export function idealystDocsPlugin(options: IdealystDocsPluginOptions): Plugin {
|
|
|
82
80
|
}
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
/**
|
|
86
|
-
* Generate the virtual module code.
|
|
87
|
-
*/
|
|
88
|
-
function generateModuleCode(): string {
|
|
89
|
-
if (!registry) {
|
|
90
|
-
registry = generateRegistry();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return `
|
|
94
|
-
// Auto-generated by @idealyst/tooling
|
|
95
|
-
// This module provides component metadata extracted from TypeScript source
|
|
96
|
-
|
|
97
|
-
export const componentRegistry = ${JSON.stringify(registry, null, 2)};
|
|
98
|
-
|
|
99
|
-
// Helper to get component names
|
|
100
|
-
export const componentNames = Object.keys(componentRegistry);
|
|
101
|
-
|
|
102
|
-
// Helper to get components by category
|
|
103
|
-
export function getComponentsByCategory(category) {
|
|
104
|
-
return Object.entries(componentRegistry)
|
|
105
|
-
.filter(([_, def]) => def.category === category)
|
|
106
|
-
.map(([name]) => name);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Helper to get prop config for playground
|
|
110
|
-
export function getPropConfig(componentName) {
|
|
111
|
-
const def = componentRegistry[componentName];
|
|
112
|
-
if (!def) return {};
|
|
113
|
-
|
|
114
|
-
return Object.entries(def.props).reduce((acc, [key, prop]) => {
|
|
115
|
-
if (prop.values && prop.values.length > 0) {
|
|
116
|
-
acc[key] = { type: 'select', options: prop.values, default: prop.default };
|
|
117
|
-
} else if (prop.type === 'boolean') {
|
|
118
|
-
acc[key] = { type: 'boolean', default: prop.default ?? false };
|
|
119
|
-
} else if (prop.type === 'string') {
|
|
120
|
-
acc[key] = { type: 'text', default: prop.default ?? '' };
|
|
121
|
-
} else if (prop.type === 'number') {
|
|
122
|
-
acc[key] = { type: 'number', default: prop.default ?? 0 };
|
|
123
|
-
}
|
|
124
|
-
return acc;
|
|
125
|
-
}, {});
|
|
126
|
-
}
|
|
127
|
-
`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
83
|
return {
|
|
131
84
|
name: 'idealyst-docs',
|
|
132
85
|
|
|
@@ -134,18 +87,6 @@ export function getPropConfig(componentName) {
|
|
|
134
87
|
server = _server;
|
|
135
88
|
},
|
|
136
89
|
|
|
137
|
-
resolveId(id) {
|
|
138
|
-
if (id === VIRTUAL_MODULE_ID) {
|
|
139
|
-
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
load(id) {
|
|
144
|
-
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
145
|
-
return generateModuleCode();
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
|
|
149
90
|
// Transform @idealyst/tooling to inject the actual registry
|
|
150
91
|
transform(code, id) {
|
|
151
92
|
// Check if this is the tooling package's index file
|
|
@@ -153,7 +94,7 @@ export function getPropConfig(componentName) {
|
|
|
153
94
|
const isToolingSrc = id.includes('/packages/tooling/src/index.ts');
|
|
154
95
|
|
|
155
96
|
if (isToolingIndex || isToolingSrc) {
|
|
156
|
-
log(`Transforming tooling index: ${id}`);
|
|
97
|
+
console.log(`[idealyst-docs] Transforming tooling index: ${id}`);
|
|
157
98
|
|
|
158
99
|
if (!registry) {
|
|
159
100
|
registry = generateRegistry();
|
|
@@ -162,21 +103,26 @@ export function getPropConfig(componentName) {
|
|
|
162
103
|
// Replace the placeholder exports with actual values
|
|
163
104
|
let transformed = code;
|
|
164
105
|
|
|
165
|
-
// Replace: export const componentRegistry
|
|
106
|
+
// Replace: export const componentRegistry = {};
|
|
107
|
+
// Note: TypeScript types are stripped before transform, so we match JS not TS
|
|
166
108
|
transformed = transformed.replace(
|
|
167
|
-
/export const componentRegistry
|
|
109
|
+
/export const componentRegistry\s*=\s*\{\s*\};?/,
|
|
168
110
|
`export const componentRegistry = ${JSON.stringify(registry, null, 2)};`
|
|
169
111
|
);
|
|
170
112
|
|
|
171
|
-
|
|
113
|
+
log(`Code transformed: ${code !== transformed}`);
|
|
114
|
+
|
|
115
|
+
// Replace: export const componentNames = [];
|
|
116
|
+
// Note: TypeScript types are stripped before transform
|
|
172
117
|
transformed = transformed.replace(
|
|
173
|
-
/export const componentNames
|
|
118
|
+
/export const componentNames\s*=\s*\[\s*\];?/,
|
|
174
119
|
`export const componentNames = ${JSON.stringify(Object.keys(registry))};`
|
|
175
120
|
);
|
|
176
121
|
|
|
177
122
|
// Replace getComponentsByCategory with actual implementation that uses the real registry
|
|
123
|
+
// Match JS version (no type annotations)
|
|
178
124
|
transformed = transformed.replace(
|
|
179
|
-
/export function getComponentsByCategory\(category
|
|
125
|
+
/export function getComponentsByCategory\(category\)\s*\{[\s\S]*?^\}/m,
|
|
180
126
|
`export function getComponentsByCategory(category) {
|
|
181
127
|
return Object.entries(componentRegistry)
|
|
182
128
|
.filter(([_, def]) => def.category === category)
|
|
@@ -185,8 +131,9 @@ export function getPropConfig(componentName) {
|
|
|
185
131
|
);
|
|
186
132
|
|
|
187
133
|
// Replace getPropConfig with actual implementation
|
|
134
|
+
// Match JS version (no type annotations)
|
|
188
135
|
transformed = transformed.replace(
|
|
189
|
-
/export function getPropConfig\(componentName
|
|
136
|
+
/export function getPropConfig\(componentName\)\s*\{[\s\S]*?^\}/m,
|
|
190
137
|
`export function getPropConfig(componentName) {
|
|
191
138
|
const def = componentRegistry[componentName];
|
|
192
139
|
if (!def) return {};
|
|
@@ -230,21 +177,14 @@ export function getPropConfig(componentName) {
|
|
|
230
177
|
// Clear cached registry
|
|
231
178
|
registry = null;
|
|
232
179
|
|
|
233
|
-
// Invalidate the
|
|
234
|
-
const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
|
|
235
|
-
if (mod) {
|
|
236
|
-
server.moduleGraph.invalidateModule(mod);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Also invalidate the tooling module if it's loaded
|
|
180
|
+
// Invalidate the tooling module to trigger re-transform
|
|
240
181
|
const toolingMods = Array.from(server.moduleGraph.idToModuleMap.values())
|
|
241
182
|
.filter(m => m.id && (m.id.includes('@idealyst/tooling') || m.id.includes('/packages/tooling/src/index')));
|
|
242
183
|
|
|
243
|
-
|
|
244
|
-
modsToInvalidate.forEach(m => server.moduleGraph.invalidateModule(m));
|
|
184
|
+
toolingMods.forEach(m => server.moduleGraph.invalidateModule(m));
|
|
245
185
|
|
|
246
|
-
if (
|
|
247
|
-
return
|
|
186
|
+
if (toolingMods.length > 0) {
|
|
187
|
+
return toolingMods;
|
|
248
188
|
}
|
|
249
189
|
}
|
|
250
190
|
},
|