@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,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idealyst Docs Vite Plugin
|
|
3
|
+
*
|
|
4
|
+
* Generates a component registry at build time by analyzing TypeScript source.
|
|
5
|
+
* Replaces placeholder exports from @idealyst/tooling with actual generated values.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* // vite.config.ts
|
|
10
|
+
* import { idealystDocsPlugin } from '@idealyst/tooling/vite';
|
|
11
|
+
*
|
|
12
|
+
* export default defineConfig({
|
|
13
|
+
* plugins: [
|
|
14
|
+
* idealystDocsPlugin({
|
|
15
|
+
* componentPaths: ['../../packages/components/src'],
|
|
16
|
+
* themePath: '../../packages/theme/src/lightTheme.ts',
|
|
17
|
+
* }),
|
|
18
|
+
* ],
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Then in your app (no virtual module setup needed!):
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { componentRegistry, componentNames, getComponentsByCategory } from '@idealyst/tooling';
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { Plugin, ViteDevServer } from 'vite';
|
|
29
|
+
import * as fs from 'fs';
|
|
30
|
+
import * as path from 'path';
|
|
31
|
+
import { analyzeComponents } from './analyzer';
|
|
32
|
+
import type { IdealystDocsPluginOptions, ComponentRegistry } from './analyzer/types';
|
|
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
|
+
/**
|
|
42
|
+
* Create the Idealyst Docs Vite plugin.
|
|
43
|
+
*/
|
|
44
|
+
export function idealystDocsPlugin(options: IdealystDocsPluginOptions): Plugin {
|
|
45
|
+
let registry: ComponentRegistry | null = null;
|
|
46
|
+
let server: ViteDevServer | null = null;
|
|
47
|
+
|
|
48
|
+
const { debug = false, output = 'virtual', outputPath } = options;
|
|
49
|
+
|
|
50
|
+
const log = (...args: any[]) => {
|
|
51
|
+
if (debug) console.log('[idealyst-docs]', ...args);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate the registry by analyzing components.
|
|
56
|
+
*/
|
|
57
|
+
function generateRegistry(): ComponentRegistry {
|
|
58
|
+
log('Generating component registry...');
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
registry = analyzeComponents(options);
|
|
62
|
+
log(`Generated registry with ${Object.keys(registry).length} components`);
|
|
63
|
+
|
|
64
|
+
// Optionally write to file
|
|
65
|
+
if (output === 'file' && outputPath) {
|
|
66
|
+
const outputDir = path.dirname(outputPath);
|
|
67
|
+
if (!fs.existsSync(outputDir)) {
|
|
68
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
fs.writeFileSync(
|
|
71
|
+
outputPath,
|
|
72
|
+
`// Auto-generated by @idealyst/tooling - DO NOT EDIT\n` +
|
|
73
|
+
`export const componentRegistry = ${JSON.stringify(registry, null, 2)} as const;\n`
|
|
74
|
+
);
|
|
75
|
+
log(`Wrote registry to ${outputPath}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return registry;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('[idealyst-docs] Error generating registry:', error);
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
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
|
+
return {
|
|
131
|
+
name: 'idealyst-docs',
|
|
132
|
+
|
|
133
|
+
configureServer(_server) {
|
|
134
|
+
server = _server;
|
|
135
|
+
},
|
|
136
|
+
|
|
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
|
+
// Transform @idealyst/tooling to inject the actual registry
|
|
150
|
+
transform(code, id) {
|
|
151
|
+
// Check if this is the tooling package's index file
|
|
152
|
+
const isToolingIndex = id.includes('@idealyst/tooling') && id.endsWith('index.ts');
|
|
153
|
+
const isToolingSrc = id.includes('/packages/tooling/src/index.ts');
|
|
154
|
+
|
|
155
|
+
if (isToolingIndex || isToolingSrc) {
|
|
156
|
+
log(`Transforming tooling index: ${id}`);
|
|
157
|
+
|
|
158
|
+
if (!registry) {
|
|
159
|
+
registry = generateRegistry();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Replace the placeholder exports with actual values
|
|
163
|
+
let transformed = code;
|
|
164
|
+
|
|
165
|
+
// Replace: export const componentRegistry: ComponentRegistry = {};
|
|
166
|
+
transformed = transformed.replace(
|
|
167
|
+
/export const componentRegistry:\s*ComponentRegistry\s*=\s*\{\};?/,
|
|
168
|
+
`export const componentRegistry = ${JSON.stringify(registry, null, 2)};`
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Replace: export const componentNames: string[] = [];
|
|
172
|
+
transformed = transformed.replace(
|
|
173
|
+
/export const componentNames:\s*string\[\]\s*=\s*\[\];?/,
|
|
174
|
+
`export const componentNames = ${JSON.stringify(Object.keys(registry))};`
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Replace getComponentsByCategory with actual implementation that uses the real registry
|
|
178
|
+
transformed = transformed.replace(
|
|
179
|
+
/export function getComponentsByCategory\(category: string\): string\[\] \{[\s\S]*?^\}/m,
|
|
180
|
+
`export function getComponentsByCategory(category) {
|
|
181
|
+
return Object.entries(componentRegistry)
|
|
182
|
+
.filter(([_, def]) => def.category === category)
|
|
183
|
+
.map(([name]) => name);
|
|
184
|
+
}`
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Replace getPropConfig with actual implementation
|
|
188
|
+
transformed = transformed.replace(
|
|
189
|
+
/export function getPropConfig\(componentName: string\): Record<string, any> \{[\s\S]*?^\}/m,
|
|
190
|
+
`export function getPropConfig(componentName) {
|
|
191
|
+
const def = componentRegistry[componentName];
|
|
192
|
+
if (!def) return {};
|
|
193
|
+
return Object.entries(def.props).reduce((acc, [key, prop]) => {
|
|
194
|
+
if (prop.values && prop.values.length > 0) {
|
|
195
|
+
acc[key] = { type: 'select', options: prop.values, default: prop.default };
|
|
196
|
+
} else if (prop.type === 'boolean') {
|
|
197
|
+
acc[key] = { type: 'boolean', default: prop.default ?? false };
|
|
198
|
+
} else if (prop.type === 'string') {
|
|
199
|
+
acc[key] = { type: 'text', default: prop.default ?? '' };
|
|
200
|
+
} else if (prop.type === 'number') {
|
|
201
|
+
acc[key] = { type: 'number', default: prop.default ?? 0 };
|
|
202
|
+
}
|
|
203
|
+
return acc;
|
|
204
|
+
}, {});
|
|
205
|
+
}`
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
code: transformed,
|
|
210
|
+
map: null,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return null;
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// Regenerate on component file changes
|
|
218
|
+
handleHotUpdate({ file, server }) {
|
|
219
|
+
// Check if the changed file is a component file
|
|
220
|
+
const isComponentFile =
|
|
221
|
+
options.componentPaths.some(p => file.includes(path.resolve(p))) &&
|
|
222
|
+
(file.endsWith('.ts') || file.endsWith('.tsx'));
|
|
223
|
+
|
|
224
|
+
// Check if the changed file is the theme file
|
|
225
|
+
const isThemeFile = file.includes(path.resolve(options.themePath));
|
|
226
|
+
|
|
227
|
+
if (isComponentFile || isThemeFile) {
|
|
228
|
+
log(`File changed: ${file}, regenerating registry...`);
|
|
229
|
+
|
|
230
|
+
// Clear cached registry
|
|
231
|
+
registry = null;
|
|
232
|
+
|
|
233
|
+
// Invalidate the virtual module (for backwards compatibility)
|
|
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
|
|
240
|
+
const toolingMods = Array.from(server.moduleGraph.idToModuleMap.values())
|
|
241
|
+
.filter(m => m.id && (m.id.includes('@idealyst/tooling') || m.id.includes('/packages/tooling/src/index')));
|
|
242
|
+
|
|
243
|
+
const modsToInvalidate = mod ? [mod, ...toolingMods] : toolingMods;
|
|
244
|
+
modsToInvalidate.forEach(m => server.moduleGraph.invalidateModule(m));
|
|
245
|
+
|
|
246
|
+
if (modsToInvalidate.length > 0) {
|
|
247
|
+
return modsToInvalidate;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
// Generate on build
|
|
253
|
+
buildStart() {
|
|
254
|
+
registry = generateRegistry();
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Standalone function to generate registry (for MCP server or CLI).
|
|
261
|
+
*/
|
|
262
|
+
export function generateComponentRegistry(options: IdealystDocsPluginOptions): ComponentRegistry {
|
|
263
|
+
return analyzeComponents(options);
|
|
264
|
+
}
|