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