@number10/jsx-icon-generator 5.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,502 @@
1
+ #!/usr/bin/env node
2
+ import { watch } from "node:fs";
3
+ import { access, readFile, readdir, writeFile } from "node:fs/promises";
4
+ import { basename, dirname, extname, join, relative, resolve } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { parseArgs } from "node:util";
7
+ //#region src/generate-icons.ts
8
+ /**
9
+ * Unified icon generator
10
+ * Can generate types and/or loaders based on configuration
11
+ *
12
+ * Usage:
13
+ * generate-icons --config ./icon-generator.config.ts
14
+ * generate-icons --types-only
15
+ * generate-icons --loaders-only
16
+ * generate-icons --config ./icon-generator.config.ts --watch
17
+ */
18
+ var TS_CONFIG_EXTENSIONS = new Set([
19
+ ".ts",
20
+ ".mts",
21
+ ".cts",
22
+ ".tsx"
23
+ ]);
24
+ /**
25
+ * Find icon package in node_modules
26
+ */
27
+ async function findIconPackage(packageName, cwd) {
28
+ const fs = await import("node:fs");
29
+ const pnpmBase = join(cwd, "node_modules/.pnpm");
30
+ if (fs.existsSync(pnpmBase)) {
31
+ const entries = await readdir(pnpmBase);
32
+ for (const entry of entries) if (entry.startsWith(`${packageName}@`)) {
33
+ const packagePath = join(pnpmBase, entry, "node_modules", packageName);
34
+ if (fs.existsSync(packagePath)) return packagePath;
35
+ }
36
+ }
37
+ const standardPath = join(cwd, "node_modules", packageName);
38
+ if (fs.existsSync(standardPath)) return standardPath;
39
+ throw new Error(`Could not find package: ${packageName}`);
40
+ }
41
+ /**
42
+ * Recursively find all TypeScript files
43
+ */
44
+ async function findTsFiles(dir, exclude = []) {
45
+ const files = [];
46
+ const entries = await readdir(dir, { withFileTypes: true });
47
+ for (const entry of entries) {
48
+ const fullPath = join(dir, entry.name);
49
+ if (exclude.some((pattern) => fullPath.includes(pattern))) continue;
50
+ if (entry.isDirectory()) {
51
+ if (entry.name === "node_modules" || entry.name === "dist") continue;
52
+ files.push(...await findTsFiles(fullPath, exclude));
53
+ } else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) files.push(fullPath);
54
+ }
55
+ return files;
56
+ }
57
+ /**
58
+ * Extract icon names from file content
59
+ */
60
+ function escapeRegExp(value) {
61
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62
+ }
63
+ function addStringLiteralIcons(expression, iconNames) {
64
+ const stringLiteralPattern = /(["'])([^"'\r\n]+)\1/g;
65
+ let match;
66
+ while ((match = stringLiteralPattern.exec(expression)) !== null) {
67
+ const iconName = match[2];
68
+ if (iconName) iconNames.add(iconName);
69
+ }
70
+ }
71
+ function addIconTypeDeclarationIcons(content, iconNames) {
72
+ const iconTypeLiteralDeclarationPattern = /\b(?:const|let|var)\s+\w+\s*:\s*[^=;\r\n]*\bIconType\b[^=;\r\n]*=\s*(["'])([^"']+)\1/g;
73
+ let match;
74
+ while ((match = iconTypeLiteralDeclarationPattern.exec(content)) !== null) {
75
+ const iconName = match[2];
76
+ if (iconName) iconNames.add(iconName);
77
+ }
78
+ }
79
+ function extractIconNames(content, componentNames, customPatterns) {
80
+ const iconNames = /* @__PURE__ */ new Set();
81
+ let match;
82
+ for (const componentName of componentNames) {
83
+ const escapedComponentName = escapeRegExp(componentName);
84
+ const typePattern = new RegExp(`<${escapedComponentName}\\s+[^>]*\\btype=(?:["']([^"']+)["']|\\{["']([^"']+)["']\\})`, "g");
85
+ while ((match = typePattern.exec(content)) !== null) {
86
+ const iconName = match[1] || match[2];
87
+ if (iconName) iconNames.add(iconName);
88
+ }
89
+ const typeExpressionPattern = new RegExp(`<${escapedComponentName}\\s+[^>]*\\btype=\\{([^}]*)\\}`, "g");
90
+ while ((match = typeExpressionPattern.exec(content)) !== null) {
91
+ const expression = match[1];
92
+ if (expression) addStringLiteralIcons(expression, iconNames);
93
+ }
94
+ }
95
+ addIconTypeDeclarationIcons(content, iconNames);
96
+ const themedPattern = /themed\.(\w*[Ii]con)\s*\?\?\s*["']([^"']+)["']/g;
97
+ while ((match = themedPattern.exec(content)) !== null) if (match[2]) iconNames.add(match[2]);
98
+ const iconPropPattern = /(?:icon|iconType):\s*["']([^"']+)["']/g;
99
+ while ((match = iconPropPattern.exec(content)) !== null) if (match[1]) iconNames.add(match[1]);
100
+ if (customPatterns) for (const customPattern of customPatterns) {
101
+ const pattern = new RegExp(customPattern.pattern, "g");
102
+ while ((match = pattern.exec(content)) !== null) {
103
+ const iconName = match[customPattern.captureGroup];
104
+ if (iconName) iconNames.add(iconName);
105
+ }
106
+ }
107
+ return iconNames;
108
+ }
109
+ /**
110
+ * Load available icons from types file
111
+ */
112
+ async function loadAvailableIcons(typesFile) {
113
+ try {
114
+ const match = (await readFile(typesFile, "utf-8")).match(/(?:export\s+)?type\s+\w+\s*=\s*([\s\S]+?)(?=\n\nexport|\n\/\/|$)/s);
115
+ if (!match?.[1]) return null;
116
+ const typeContent = match[1];
117
+ const icons = /* @__PURE__ */ new Set();
118
+ const iconPattern = /'([^']+)'/g;
119
+ let iconMatch;
120
+ while ((iconMatch = iconPattern.exec(typeContent)) !== null) {
121
+ const iconName = iconMatch[1];
122
+ if (iconName) icons.add(iconName);
123
+ }
124
+ return icons;
125
+ } catch {
126
+ return null;
127
+ }
128
+ }
129
+ /**
130
+ * Generate icon types
131
+ * Exported for use by Vite plugin
132
+ */
133
+ async function generateTypes(config, cwd) {
134
+ if (!config.types?.enabled) return null;
135
+ console.log("\n📝 Generating icon types...");
136
+ const sources = Array.isArray(config.source) ? config.source : [config.source];
137
+ const allIconNames = [];
138
+ const sourceInfos = [];
139
+ const sourceIconSets = /* @__PURE__ */ new Map();
140
+ for (let i = 0; i < sources.length; i++) {
141
+ const source = sources[i];
142
+ if (!source) continue;
143
+ let iconsDir;
144
+ let sourceName;
145
+ if (source.directory) {
146
+ iconsDir = resolve(cwd, source.directory);
147
+ sourceName = source.label || source.directory;
148
+ console.log(`📁 Scanning directory: ${source.directory}`);
149
+ } else if (source.package) {
150
+ iconsDir = join(await findIconPackage(source.package, cwd), source.iconsPath || "icons");
151
+ sourceName = source.label || source.package;
152
+ console.log(`📦 Scanning package: ${source.package}`);
153
+ } else throw new Error("Either source.package or source.directory must be specified");
154
+ const iconNames = (await readdir(iconsDir)).filter((file) => file.endsWith(".svg")).map((file) => basename(file, ".svg"));
155
+ console.log(`✓ Found ${iconNames.length} icons from ${sourceName}`);
156
+ allIconNames.push(...iconNames);
157
+ sourceInfos.push({
158
+ name: sourceName,
159
+ count: iconNames.length
160
+ });
161
+ sourceIconSets.set(i, new Set(iconNames));
162
+ }
163
+ const uniqueIconNames = [...new Set(allIconNames)].sort();
164
+ console.log(`✓ Total unique icons: ${uniqueIconNames.length}`);
165
+ const typeDefinition = uniqueIconNames.map((name) => ` | '${name}'`).join("\n");
166
+ const typeName = config.types.typeName || "IconType";
167
+ const output = `/**
168
+ * Auto-generated icon type definitions
169
+ * Sources: ${sourceInfos.length === 1 && sourceInfos[0] ? sourceInfos[0].name : sourceInfos.map((s) => `${s.name} (${s.count})`).join(", ")}
170
+ * Total unique icons: ${uniqueIconNames.length}
171
+ *
172
+ * @generated by @number10/jsx-icon-generator/generate-icons
173
+ * @cspell: disable
174
+ */
175
+
176
+ /**
177
+ * All available icon names
178
+ * Type-only definition - no runtime imports, zero bundle impact
179
+ */
180
+ export type ${typeName} =
181
+ ${typeDefinition}
182
+ `;
183
+ const outputPath = resolve(cwd, config.types.output);
184
+ await writeFile(outputPath, output, "utf-8");
185
+ console.log(`✓ Generated: ${outputPath}`);
186
+ console.log(`✓ Type name: ${typeName}`);
187
+ return {
188
+ iconNames: uniqueIconNames,
189
+ sourceIconSets
190
+ };
191
+ }
192
+ /**
193
+ * Scan icon sources without generating the types file
194
+ * Used by Vite plugin to get sourceIconSets for loader generation without full type regeneration
195
+ * @param config - Icon generator configuration
196
+ * @param cwd - Current working directory
197
+ * @returns Icon names and source mapping
198
+ */
199
+ async function scanIconSources(config, cwd) {
200
+ const sources = Array.isArray(config.source) ? config.source : [config.source];
201
+ const allIconNames = [];
202
+ const sourceIconSets = /* @__PURE__ */ new Map();
203
+ for (let i = 0; i < sources.length; i++) {
204
+ const source = sources[i];
205
+ if (!source) continue;
206
+ let iconsDir;
207
+ if (source.directory) iconsDir = resolve(cwd, source.directory);
208
+ else if (source.package) iconsDir = join(await findIconPackage(source.package, cwd), source.iconsPath || "icons");
209
+ else throw new Error("Either source.package or source.directory must be specified");
210
+ const iconNames = (await readdir(iconsDir)).filter((file) => file.endsWith(".svg")).map((file) => basename(file, ".svg"));
211
+ allIconNames.push(...iconNames);
212
+ sourceIconSets.set(i, new Set(iconNames));
213
+ }
214
+ return {
215
+ iconNames: [...new Set(allIconNames)].sort(),
216
+ sourceIconSets
217
+ };
218
+ }
219
+ /**
220
+ * Generate icon loaders
221
+ * Exported for use by Vite plugin
222
+ */
223
+ async function generateLoaders(config, cwd, typesResult) {
224
+ if (!config.loaders?.enabled) return;
225
+ console.log("\n⚡ Generating icon loaders...");
226
+ const scanDir = resolve(cwd, config.loaders.scanDir);
227
+ const componentNames = config.loaders.componentNames || ["Icon"];
228
+ console.log(`📁 Scanning directory: ${scanDir}`);
229
+ console.log(`🔍 Looking for components: ${componentNames.join(", ")}`);
230
+ let availableIconsSet = null;
231
+ let sourceIconSets = null;
232
+ if (typesResult) {
233
+ availableIconsSet = new Set(typesResult.iconNames);
234
+ sourceIconSets = typesResult.sourceIconSets;
235
+ console.log(`✓ Loaded ${availableIconsSet.size} available icons for validation`);
236
+ } else if (config.loaders.validate && config.types?.output) {
237
+ availableIconsSet = await loadAvailableIcons(resolve(cwd, config.types.output));
238
+ if (availableIconsSet) console.log(`✓ Loaded ${availableIconsSet.size} available icons for validation`);
239
+ }
240
+ const files = await findTsFiles(scanDir, config.exclude);
241
+ console.log(`📄 Found ${files.length} TypeScript files`);
242
+ const allIconNames = /* @__PURE__ */ new Set();
243
+ for (const file of files) extractIconNames(await readFile(file, "utf-8"), componentNames, config.customPatterns).forEach((icon) => allIconNames.add(icon));
244
+ let iconArray = Array.from(allIconNames).sort();
245
+ if (availableIconsSet) {
246
+ const invalidIcons = iconArray.filter((icon) => !availableIconsSet.has(icon));
247
+ if (invalidIcons.length > 0) {
248
+ console.warn(`⚠️ Found ${invalidIcons.length} icons not in types file:`);
249
+ console.warn(` ${invalidIcons.slice(0, 5).join(", ")}${invalidIcons.length > 5 ? "..." : ""}`);
250
+ }
251
+ iconArray = iconArray.filter((icon) => availableIconsSet.has(icon));
252
+ }
253
+ console.log(`✓ Found ${iconArray.length} unique icons`);
254
+ if (iconArray.length > 0) console.log(` Icons: ${iconArray.slice(0, 10).join(", ")}${iconArray.length > 10 ? "..." : ""}`);
255
+ const sources = Array.isArray(config.source) ? config.source : [config.source];
256
+ const iconSourceMap = /* @__PURE__ */ new Map();
257
+ for (const icon of iconArray) if (sourceIconSets) for (let i = 0; i < sources.length; i++) {
258
+ const source = sources[i];
259
+ const iconsForThisSource = sourceIconSets.get(i);
260
+ if (!source || !iconsForThisSource) continue;
261
+ if (iconsForThisSource.has(icon)) {
262
+ let iconPath = "";
263
+ if (source.package) iconPath = `${source.package}/${source.iconsPath || "icons"}/${icon}.svg`;
264
+ else if (source.directory) iconPath = `${source.directory}/${icon}.svg`;
265
+ if (iconPath) {
266
+ if (source.directory && config.loaders.output) {
267
+ iconPath = relative(dirname(resolve(cwd, config.loaders.output)), resolve(cwd, iconPath));
268
+ iconPath = "./" + iconPath.replace(/\\/g, "/");
269
+ }
270
+ iconSourceMap.set(icon, {
271
+ path: iconPath,
272
+ isPackage: !!source.package
273
+ });
274
+ break;
275
+ }
276
+ }
277
+ }
278
+ else for (const source of sources) {
279
+ let iconPath = "";
280
+ let exists = false;
281
+ if (source.package) {
282
+ iconPath = `${source.package}/${source.iconsPath || "icons"}/${icon}.svg`;
283
+ exists = availableIconsSet ? availableIconsSet.has(icon) : true;
284
+ } else if (source.directory) {
285
+ const dirPath = source.directory;
286
+ iconPath = `${dirPath}/${icon}.svg`;
287
+ try {
288
+ await access(resolve(cwd, dirPath, `${icon}.svg`));
289
+ exists = true;
290
+ } catch {
291
+ exists = false;
292
+ }
293
+ }
294
+ if (exists && iconPath) {
295
+ if (source.directory && config.loaders.output) {
296
+ iconPath = relative(dirname(resolve(cwd, config.loaders.output)), resolve(cwd, iconPath));
297
+ iconPath = "./" + iconPath.replace(/\\/g, "/");
298
+ }
299
+ iconSourceMap.set(icon, {
300
+ path: iconPath,
301
+ isPackage: !!source.package
302
+ });
303
+ break;
304
+ }
305
+ }
306
+ const loaders = Array.from(iconSourceMap.entries()).map(([icon, { path, isPackage }]) => {
307
+ if (isPackage) return ` '${icon}': () => import('${path}?raw'),`;
308
+ else return ` '${icon}': () => import('${path}?raw'),`;
309
+ }).join("\n");
310
+ const outputContent = `/**
311
+ * Auto-generated icon loaders
312
+ * Generated by scanning: ${config.loaders.scanDir}
313
+ *
314
+ * @generated by @number10/jsx-icon-generator/generate-icons
315
+ */
316
+ export type IconLoaderFn = () => Promise<{ default: string }>
317
+
318
+ export const iconLoaders: Record<string, IconLoaderFn> = {
319
+ ${loaders}
320
+ }
321
+ `;
322
+ const outputPath = resolve(cwd, config.loaders.output);
323
+ await writeFile(outputPath, outputContent, "utf-8");
324
+ console.log(`✓ Generated: ${outputPath}`);
325
+ console.log(`✓ Registered ${iconArray.length} icon loaders`);
326
+ }
327
+ /**
328
+ * Load config file
329
+ */
330
+ /**
331
+ * Load icon generator config file
332
+ * Exported for use by Vite plugin and other tools
333
+ */
334
+ async function loadConfig(configPath, cwd = process.cwd()) {
335
+ const absolutePath = resolve(cwd, configPath);
336
+ const fileUrl = pathToFileURL(absolutePath).href;
337
+ const extension = extname(absolutePath).toLowerCase();
338
+ try {
339
+ const module = await import(fileUrl);
340
+ return module.default || module;
341
+ } catch (error) {
342
+ const errorMessage = error instanceof Error ? error.message : String(error);
343
+ if (TS_CONFIG_EXTENSIONS.has(extension) || errorMessage.includes("Unknown file extension")) try {
344
+ const { tsImport } = await import("tsx/esm/api");
345
+ const module = await tsImport(fileUrl, import.meta.url);
346
+ return module.default || module;
347
+ } catch (tsxError) {
348
+ const tsxMessage = tsxError instanceof Error ? tsxError.message : String(tsxError);
349
+ throw new Error(`Failed to load config from ${configPath}: ${tsxMessage} (after tsx fallback)`, { cause: tsxError });
350
+ }
351
+ throw new Error(`Failed to load config from ${configPath}: ${errorMessage}`, { cause: error });
352
+ }
353
+ }
354
+ /**
355
+ * Run generation with config
356
+ */
357
+ async function runGeneration(config, cwd) {
358
+ console.log("🎨 Icon Generator");
359
+ console.log("─".repeat(50));
360
+ await generateLoaders(config, cwd, await generateTypes(config, cwd) || void 0);
361
+ console.log("\n✨ Done!\n");
362
+ }
363
+ /**
364
+ * Watch mode - monitor files and regenerate on changes
365
+ */
366
+ async function watchMode(config, cwd) {
367
+ console.log("👁️ Watch mode enabled");
368
+ console.log("📁 Monitoring for changes...\n");
369
+ await runGeneration(config, cwd);
370
+ const watchDirs = /* @__PURE__ */ new Set();
371
+ if (config.loaders?.enabled) {
372
+ const scanDir = resolve(cwd, config.loaders.scanDir);
373
+ watchDirs.add(scanDir);
374
+ }
375
+ const sources = Array.isArray(config.source) ? config.source : [config.source];
376
+ for (const source of sources) if (source.directory) watchDirs.add(resolve(cwd, source.directory));
377
+ let regenerateTimeout = null;
378
+ const scheduleRegenerate = () => {
379
+ if (regenerateTimeout) clearTimeout(regenerateTimeout);
380
+ regenerateTimeout = setTimeout(async () => {
381
+ console.log("\n🔄 Changes detected, regenerating...");
382
+ try {
383
+ await runGeneration(config, cwd);
384
+ } catch (error) {
385
+ console.error("❌ Error during regeneration:", error);
386
+ }
387
+ }, 300);
388
+ };
389
+ for (const dir of watchDirs) watch(dir, { recursive: true }, (eventType, filename) => {
390
+ if (!filename) return;
391
+ if (filename.includes("node_modules") || filename.includes(".git") || filename.endsWith(".generated.ts") || filename.startsWith(".")) return;
392
+ if (/\.(tsx?|svg)$/.test(filename)) {
393
+ console.log(`📝 ${eventType}: ${filename}`);
394
+ scheduleRegenerate();
395
+ }
396
+ }).on("error", (error) => {
397
+ console.error(`❌ Watch error for ${dir}:`, error);
398
+ });
399
+ console.log(`👀 Watching ${watchDirs.size} directories`);
400
+ console.log("Press Ctrl+C to stop\n");
401
+ await new Promise(() => {});
402
+ }
403
+ /**
404
+ * Main function
405
+ */
406
+ async function main() {
407
+ const { values } = parseArgs({ options: {
408
+ config: {
409
+ type: "string",
410
+ short: "c"
411
+ },
412
+ typesOnly: { type: "boolean" },
413
+ loadersOnly: { type: "boolean" },
414
+ watch: {
415
+ type: "boolean",
416
+ short: "w"
417
+ },
418
+ help: {
419
+ type: "boolean",
420
+ short: "h"
421
+ }
422
+ } });
423
+ if (values.help) {
424
+ console.log(`
425
+ Usage: generate-icons [options]
426
+
427
+ Options:
428
+ -c, --config <path> Path to config file (e.g., ./icon-generator.config.ts)
429
+ --typesOnly Only generate types (skip loaders)
430
+ --loadersOnly Only generate loaders (skip types)
431
+ -h, --help Show this help message
432
+
433
+ Config File Example:
434
+ // icon-generator.config.ts
435
+ import { defineIconConfig } from '@number10/jsx-icon-generator/icon-generator-config'
436
+
437
+ export default defineIconConfig({
438
+ source: {
439
+ package: 'bootstrap-icons',
440
+ iconsPath: 'icons',
441
+ },
442
+ types: {
443
+ enabled: true,
444
+ output: './src/icon-types.generated.ts',
445
+ typeName: 'IconType',
446
+ },
447
+ loaders: {
448
+ enabled: true,
449
+ output: './src/icon-loaders.generated.ts',
450
+ scanDir: './src',
451
+ componentNames: ['Icon', 'BootstrapIcon'],
452
+ validate: true,
453
+ },
454
+ })
455
+
456
+ Examples:
457
+ # Using config file
458
+ generate-icons --config ./icon-generator.config.ts
459
+
460
+ # Generate only types
461
+ generate-icons --config ./icon-generator.config.ts --typesOnly
462
+
463
+ # Generate only loaders
464
+ generate-icons --config ./icon-generator.config.ts --loadersOnly
465
+
466
+ # Watch mode - regenerate on file changes
467
+ generate-icons --config ./icon-generator.config.ts --watch
468
+ `);
469
+ process.exit(0);
470
+ }
471
+ if (!values.config) {
472
+ console.error("❌ Error: --config is required");
473
+ console.error("Run with --help for usage information");
474
+ process.exit(1);
475
+ }
476
+ try {
477
+ const cwd = process.cwd();
478
+ let config = await loadConfig(values.config);
479
+ if (values.typesOnly && config.loaders) config = {
480
+ ...config,
481
+ loaders: {
482
+ ...config.loaders,
483
+ enabled: false
484
+ }
485
+ };
486
+ if (values.loadersOnly && config.types) config = {
487
+ ...config,
488
+ types: {
489
+ ...config.types,
490
+ enabled: false
491
+ }
492
+ };
493
+ if (values.watch) await watchMode(config, cwd);
494
+ else await runGeneration(config, cwd);
495
+ } catch (error) {
496
+ console.error("❌ Error:", error);
497
+ process.exit(1);
498
+ }
499
+ }
500
+ if (import.meta.url === `file://${process.argv[1]}`) main();
501
+ //#endregion
502
+ export { generateLoaders, generateTypes, loadConfig, scanIconSources };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Configuration schema for icon generators
3
+ * This file defines the TypeScript types for icon-generator.config.ts
4
+ */
5
+ /**
6
+ * Source configuration - where to find icons
7
+ */
8
+ export interface IconSource {
9
+ /** Icon package name from node_modules (e.g., 'bootstrap-icons') */
10
+ package?: string;
11
+ /** Local directory with SVG files (alternative to package) */
12
+ directory?: string;
13
+ /** Relative path to icons within package (default: 'icons') */
14
+ iconsPath?: string;
15
+ /** Optional label for this source (used in logs for multi-source) */
16
+ label?: string;
17
+ }
18
+ /**
19
+ * Type generation configuration
20
+ */
21
+ export interface TypesConfig {
22
+ /** Enable types generation */
23
+ enabled: boolean;
24
+ /** Output file path for generated types */
25
+ output: string;
26
+ /** TypeScript type name (default: 'IconType') */
27
+ typeName?: string;
28
+ /**
29
+ * When to scan icon directories (Vite plugin only)
30
+ * - 'never': Never scan (types must be generated manually)
31
+ * - 'start': Scan once at startup
32
+ * - 'watch': Scan at startup and on SVG file changes (default)
33
+ */
34
+ scanIconDirectory?: 'never' | 'start' | 'watch';
35
+ }
36
+ /**
37
+ * Loader generation configuration
38
+ */
39
+ export interface LoadersConfig {
40
+ /** Enable loaders generation */
41
+ enabled: boolean;
42
+ /** Output file path for generated loaders */
43
+ output: string;
44
+ /** Source directory to scan for icon usage */
45
+ scanDir: string;
46
+ /** Component names to search for (e.g., ['Icon', 'BootstrapIcon']) */
47
+ componentNames?: string[];
48
+ /** Validate against types file (enables warnings for invalid icons) */
49
+ validate?: boolean;
50
+ /**
51
+ * When to generate loaders (Vite plugin only)
52
+ * - 'start': Generate once at startup
53
+ * - 'watch': Generate at startup and on code file changes (default)
54
+ */
55
+ generateLoaders?: 'start' | 'watch';
56
+ }
57
+ /**
58
+ * Custom pattern for detecting icon usage
59
+ */
60
+ export interface CustomPattern {
61
+ /** Pattern name/description */
62
+ name: string;
63
+ /** Regex pattern as string (will be compiled with 'g' flag) */
64
+ pattern: string;
65
+ /** Capture group index for icon name (1-based) */
66
+ captureGroup: number;
67
+ }
68
+ /**
69
+ * Main configuration for icon generator
70
+ */
71
+ export interface IconGeneratorConfig {
72
+ /** Icon source configuration - single source or array of sources */
73
+ source: IconSource | IconSource[];
74
+ /** Type generation configuration */
75
+ types?: TypesConfig;
76
+ /** Loader generation configuration */
77
+ loaders?: LoadersConfig;
78
+ /** Custom patterns for detecting icon usage in loaders */
79
+ customPatterns?: CustomPattern[];
80
+ /** Exclude patterns (glob) when scanning for icons */
81
+ exclude?: string[];
82
+ }
83
+ /**
84
+ * Helper function to define config with type safety
85
+ */
86
+ export declare function defineIconConfig(config: IconGeneratorConfig): IconGeneratorConfig;
@@ -0,0 +1,9 @@
1
+ //#region src/icon-generator-config.ts
2
+ /**
3
+ * Helper function to define config with type safety
4
+ */
5
+ function defineIconConfig(config) {
6
+ return config;
7
+ }
8
+ //#endregion
9
+ export { defineIconConfig };
@@ -0,0 +1,28 @@
1
+ import type { Plugin } from 'vite';
2
+ export interface IconGeneratorPluginOptions {
3
+ /** Path to icon generator config file */
4
+ configPath: string;
5
+ /**
6
+ * When to scan icon directories and generate types
7
+ * - 'never': Don't generate in plugin (use CLI manually)
8
+ * - 'start': Only at server start (default)
9
+ * - 'watch': At start + when SVG files change
10
+ */
11
+ scanIconDirectory?: 'never' | 'start' | 'watch';
12
+ /**
13
+ * When to scan code and generate loaders
14
+ * - 'start': Only at server start
15
+ * - 'watch': At start + when code changes (default)
16
+ */
17
+ generateLoaders?: 'start' | 'watch';
18
+ /** @deprecated Use scanIconDirectory instead */
19
+ watch?: boolean;
20
+ /** @deprecated Use scanIconDirectory='never' + generateLoaders='start' instead */
21
+ typesOnly?: boolean;
22
+ /** @deprecated Use scanIconDirectory='start' instead */
23
+ loadersOnly?: boolean;
24
+ }
25
+ /**
26
+ * Vite plugin for icon generation
27
+ */
28
+ export declare function iconGeneratorPlugin(options: IconGeneratorPluginOptions): Plugin;