svger-cli 1.0.6 → 1.0.8

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.
Files changed (64) hide show
  1. package/CODE_OF_CONDUCT.md +79 -0
  2. package/CONTRIBUTING.md +146 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1862 -73
  5. package/TESTING.md +143 -0
  6. package/cli-framework.test.js +16 -0
  7. package/cli-test-angular/Arrowbenddownleft.component.ts +27 -0
  8. package/cli-test-angular/Vite.component.ts +27 -0
  9. package/cli-test-angular/index.ts +25 -0
  10. package/{my-icons/ArrowBendDownLeft.tsx → cli-test-output/Arrowbenddownleft.vue} +28 -12
  11. package/{my-icons/Vite.tsx → cli-test-output/Vite.vue} +28 -12
  12. package/cli-test-output/index.ts +25 -0
  13. package/cli-test-react/Arrowbenddownleft.tsx +39 -0
  14. package/cli-test-react/Vite.tsx +39 -0
  15. package/cli-test-react/index.ts +25 -0
  16. package/cli-test-svelte/Arrowbenddownleft.svelte +22 -0
  17. package/cli-test-svelte/Vite.svelte +22 -0
  18. package/cli-test-svelte/index.ts +25 -0
  19. package/dist/builder.js +12 -13
  20. package/dist/clean.js +3 -3
  21. package/dist/cli.js +139 -61
  22. package/dist/config.d.ts +1 -1
  23. package/dist/config.js +5 -7
  24. package/dist/lock.js +1 -1
  25. package/dist/templates/ComponentTemplate.d.ts +15 -0
  26. package/dist/templates/ComponentTemplate.js +15 -0
  27. package/dist/watch.d.ts +2 -1
  28. package/dist/watch.js +30 -33
  29. package/docs/ADR-SVG-INTRGRATION-METHODS-002.adr.md +550 -0
  30. package/docs/FRAMEWORK-GUIDE.md +768 -0
  31. package/docs/IMPLEMENTATION-SUMMARY.md +376 -0
  32. package/frameworks.test.js +170 -0
  33. package/package.json +8 -10
  34. package/src/builder.ts +12 -13
  35. package/src/clean.ts +3 -3
  36. package/src/cli.ts +148 -59
  37. package/src/config.ts +5 -6
  38. package/src/core/error-handler.ts +303 -0
  39. package/src/core/framework-templates.ts +428 -0
  40. package/src/core/logger.ts +104 -0
  41. package/src/core/performance-engine.ts +327 -0
  42. package/src/core/plugin-manager.ts +228 -0
  43. package/src/core/style-compiler.ts +605 -0
  44. package/src/core/template-manager.ts +619 -0
  45. package/src/index.ts +235 -0
  46. package/src/lock.ts +1 -1
  47. package/src/processors/svg-processor.ts +288 -0
  48. package/src/services/config.ts +241 -0
  49. package/src/services/file-watcher.ts +218 -0
  50. package/src/services/svg-service.ts +468 -0
  51. package/src/types/index.ts +169 -0
  52. package/src/utils/native.ts +352 -0
  53. package/src/watch.ts +36 -36
  54. package/test-output-mulit/TestIcon-angular-module.component.ts +26 -0
  55. package/test-output-mulit/TestIcon-angular-standalone.component.ts +27 -0
  56. package/test-output-mulit/TestIcon-lit.ts +35 -0
  57. package/test-output-mulit/TestIcon-preact.tsx +38 -0
  58. package/test-output-mulit/TestIcon-react.tsx +35 -0
  59. package/test-output-mulit/TestIcon-solid.tsx +27 -0
  60. package/test-output-mulit/TestIcon-svelte.svelte +22 -0
  61. package/test-output-mulit/TestIcon-vanilla.ts +37 -0
  62. package/test-output-mulit/TestIcon-vue-composition.vue +33 -0
  63. package/test-output-mulit/TestIcon-vue-options.vue +31 -0
  64. package/tsconfig.json +11 -9
@@ -0,0 +1,619 @@
1
+ import { Template, ComponentGenerationOptions, TemplateConfig } from '../types/index.js';
2
+ import { logger } from '../core/logger.js';
3
+ import { FileSystem } from '../utils/native.js';
4
+ import path from 'path';
5
+
6
+ /**
7
+ * Template management system for customizable component generation
8
+ */
9
+ export class TemplateManager {
10
+ private static instance: TemplateManager;
11
+ private templates: Map<string, Template> = new Map();
12
+ private defaultTemplate = 'react-functional';
13
+
14
+ private constructor() {
15
+ this.loadBuiltinTemplates();
16
+ }
17
+
18
+ public static getInstance(): TemplateManager {
19
+ if (!TemplateManager.instance) {
20
+ TemplateManager.instance = new TemplateManager();
21
+ }
22
+ return TemplateManager.instance;
23
+ }
24
+
25
+ /**
26
+ * Load built-in templates
27
+ */
28
+ private loadBuiltinTemplates(): void {
29
+ // Standard React Functional Component
30
+ this.registerTemplate({
31
+ name: 'react-functional',
32
+ generate: (options: ComponentGenerationOptions) => this.generateReactFunctional(options),
33
+ validate: (options: ComponentGenerationOptions) => !!options.componentName && !!options.svgContent
34
+ });
35
+
36
+ // React Functional Component with forwardRef
37
+ this.registerTemplate({
38
+ name: 'react-forwardref',
39
+ generate: (options: ComponentGenerationOptions) => this.generateReactForwardRef(options),
40
+ validate: (options: ComponentGenerationOptions) => !!options.componentName && !!options.svgContent
41
+ });
42
+
43
+ // React Class Component (legacy support)
44
+ this.registerTemplate({
45
+ name: 'react-class',
46
+ generate: (options: ComponentGenerationOptions) => this.generateReactClass(options),
47
+ validate: (options: ComponentGenerationOptions) => !!options.componentName && !!options.svgContent
48
+ });
49
+
50
+ // Styled Components Template
51
+ this.registerTemplate({
52
+ name: 'styled-components',
53
+ generate: (options: ComponentGenerationOptions) => this.generateStyledComponents(options),
54
+ validate: (options: ComponentGenerationOptions) => !!options.componentName && !!options.svgContent
55
+ });
56
+
57
+ // TypeScript Native (no React)
58
+ this.registerTemplate({
59
+ name: 'typescript-native',
60
+ generate: (options: ComponentGenerationOptions) => this.generateTypeScriptNative(options),
61
+ validate: (options: ComponentGenerationOptions) => !!options.componentName && !!options.svgContent
62
+ });
63
+
64
+ // Enhanced Styled Template
65
+ this.registerTemplate({
66
+ name: 'enhanced-styled',
67
+ generate: (options: ComponentGenerationOptions) => this.generateEnhancedStyled(options),
68
+ validate: (options: ComponentGenerationOptions) => !!options.componentName && !!options.svgContent
69
+ });
70
+
71
+ logger.debug('Built-in templates loaded');
72
+ }
73
+
74
+ /**
75
+ * Register a new template
76
+ */
77
+ public registerTemplate(template: Template): void {
78
+ this.templates.set(template.name, template);
79
+ logger.debug(`Template registered: ${template.name}`);
80
+ }
81
+
82
+ /**
83
+ * Generate component using specified template
84
+ */
85
+ public generateComponent(
86
+ templateName: string,
87
+ options: ComponentGenerationOptions
88
+ ): string {
89
+ const template = this.templates.get(templateName);
90
+
91
+ if (!template) {
92
+ logger.warn(`Template not found: ${templateName}, using default`);
93
+ return this.generateComponent(this.defaultTemplate, options);
94
+ }
95
+
96
+ if (template.validate && !template.validate(options)) {
97
+ throw new Error(`Invalid options for template: ${templateName}`);
98
+ }
99
+
100
+ return template.generate(options);
101
+ }
102
+
103
+ /**
104
+ * Load custom template from file
105
+ */
106
+ public async loadCustomTemplate(templatePath: string): Promise<void> {
107
+ try {
108
+ const resolvedPath = path.resolve(templatePath);
109
+
110
+ if (!(await FileSystem.exists(resolvedPath))) {
111
+ throw new Error(`Template file not found: ${resolvedPath}`);
112
+ }
113
+
114
+ const templateContent = await FileSystem.readFile(resolvedPath, 'utf-8');
115
+
116
+ // Parse template (assuming it's a JavaScript module)
117
+ const templateName = path.basename(templatePath, path.extname(templatePath));
118
+
119
+ // For now, we'll treat it as a simple string template
120
+ this.registerTemplate({
121
+ name: templateName,
122
+ generate: (options: ComponentGenerationOptions) => {
123
+ return this.processStringTemplate(templateContent, options);
124
+ }
125
+ });
126
+
127
+ logger.info(`Custom template loaded: ${templateName}`);
128
+ } catch (error) {
129
+ logger.error(`Failed to load template from ${templatePath}:`, error);
130
+ throw error;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Get list of available templates
136
+ */
137
+ public getAvailableTemplates(): string[] {
138
+ return Array.from(this.templates.keys());
139
+ }
140
+
141
+ /**
142
+ * Set default template
143
+ */
144
+ public setDefaultTemplate(templateName: string): void {
145
+ if (!this.templates.has(templateName)) {
146
+ throw new Error(`Template not found: ${templateName}`);
147
+ }
148
+ this.defaultTemplate = templateName;
149
+ logger.info(`Default template set to: ${templateName}`);
150
+ }
151
+
152
+ // ---- Built-in Template Generators ----
153
+
154
+ /**
155
+ * Standard React Functional Component
156
+ */
157
+ private generateReactFunctional(options: ComponentGenerationOptions): string {
158
+ const {
159
+ componentName,
160
+ svgContent,
161
+ defaultWidth = 24,
162
+ defaultHeight = 24,
163
+ defaultFill = 'currentColor'
164
+ } = options;
165
+
166
+ return `import React from "react";
167
+ import type { SVGProps } from "react";
168
+
169
+ export interface ${componentName}Props extends SVGProps<SVGSVGElement> {
170
+ size?: number | string;
171
+ }
172
+
173
+ /**
174
+ * ${componentName} SVG Component
175
+ * Generated by svger-cli
176
+ */
177
+ const ${componentName} = React.forwardRef<SVGSVGElement, ${componentName}Props>(
178
+ ({ size, className, style, ...props }, ref) => {
179
+ const dimensions = size ? { width: size, height: size } : {
180
+ width: props.width || ${defaultWidth},
181
+ height: props.height || ${defaultHeight}
182
+ };
183
+
184
+ return (
185
+ <svg
186
+ ref={ref}
187
+ viewBox="0 0 ${defaultWidth} ${defaultHeight}"
188
+ xmlns="http://www.w3.org/2000/svg"
189
+ width={dimensions.width}
190
+ height={dimensions.height}
191
+ fill={props.fill || "${defaultFill}"}
192
+ className={className}
193
+ style={style}
194
+ {...props}
195
+ >
196
+ ${svgContent}
197
+ </svg>
198
+ );
199
+ }
200
+ );
201
+
202
+ ${componentName}.displayName = "${componentName}";
203
+
204
+ export default ${componentName};
205
+ `;
206
+ }
207
+
208
+ /**
209
+ * React Functional Component with forwardRef
210
+ */
211
+ private generateReactForwardRef(options: ComponentGenerationOptions): string {
212
+ const {
213
+ componentName,
214
+ svgContent,
215
+ defaultWidth = 24,
216
+ defaultHeight = 24,
217
+ defaultFill = 'currentColor'
218
+ } = options;
219
+
220
+ return `import { forwardRef } from "react";
221
+ import type { SVGProps } from "react";
222
+
223
+ /**
224
+ * ${componentName} SVG Component with forwardRef
225
+ * Generated by svger-cli
226
+ */
227
+ const ${componentName} = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
228
+ (props, ref) => (
229
+ <svg
230
+ ref={ref}
231
+ viewBox="0 0 ${defaultWidth} ${defaultHeight}"
232
+ xmlns="http://www.w3.org/2000/svg"
233
+ width={props.width || ${defaultWidth}}
234
+ height={props.height || ${defaultHeight}}
235
+ fill={props.fill || "${defaultFill}"}
236
+ className={props.className}
237
+ style={props.style}
238
+ {...props}
239
+ >
240
+ ${svgContent}
241
+ </svg>
242
+ )
243
+ );
244
+
245
+ ${componentName}.displayName = "${componentName}";
246
+
247
+ export default ${componentName};
248
+ `;
249
+ }
250
+
251
+ /**
252
+ * React Class Component (legacy)
253
+ */
254
+ private generateReactClass(options: ComponentGenerationOptions): string {
255
+ const {
256
+ componentName,
257
+ svgContent,
258
+ defaultWidth = 24,
259
+ defaultHeight = 24,
260
+ defaultFill = 'currentColor'
261
+ } = options;
262
+
263
+ return `import { Component } from "react";
264
+ import type { SVGProps } from "react";
265
+
266
+ /**
267
+ * ${componentName} SVG Component (Class-based)
268
+ * Generated by svger-cli
269
+ */
270
+ class ${componentName} extends Component<SVGProps<SVGSVGElement>> {
271
+ render() {
272
+ const { width = ${defaultWidth}, height = ${defaultHeight}, fill = "${defaultFill}", ...props } = this.props;
273
+
274
+ return (
275
+ <svg
276
+ viewBox="0 0 ${defaultWidth} ${defaultHeight}"
277
+ xmlns="http://www.w3.org/2000/svg"
278
+ width={width}
279
+ height={height}
280
+ fill={fill}
281
+ {...props}
282
+ >
283
+ ${svgContent}
284
+ </svg>
285
+ );
286
+ }
287
+ }
288
+
289
+ export default ${componentName};
290
+ `;
291
+ }
292
+
293
+ /**
294
+ * Styled Components Template
295
+ */
296
+ private generateStyledComponents(options: ComponentGenerationOptions): string {
297
+ const {
298
+ componentName,
299
+ svgContent,
300
+ defaultWidth = 24,
301
+ defaultHeight = 24,
302
+ defaultFill = 'currentColor'
303
+ } = options;
304
+
305
+ return `import styled from "styled-components";
306
+ import type { SVGProps } from "react";
307
+
308
+ /**
309
+ * ${componentName} SVG Component with Styled Components
310
+ * Generated by svger-cli
311
+ */
312
+ const StyledSVG = styled.svg<SVGProps<SVGSVGElement>>\`
313
+ width: \${props => props.width || '${defaultWidth}px'};
314
+ height: \${props => props.height || '${defaultHeight}px'};
315
+ fill: \${props => props.fill || '${defaultFill}'};
316
+ \`;
317
+
318
+ const ${componentName} = (props: SVGProps<SVGSVGElement>) => (
319
+ <StyledSVG
320
+ viewBox="0 0 ${defaultWidth} ${defaultHeight}"
321
+ xmlns="http://www.w3.org/2000/svg"
322
+ {...props}
323
+ >
324
+ ${svgContent}
325
+ </StyledSVG>
326
+ );
327
+
328
+ ${componentName}.displayName = "${componentName}";
329
+
330
+ export default ${componentName};
331
+ `;
332
+ }
333
+
334
+ /**
335
+ * TypeScript Native (no React dependencies)
336
+ */
337
+ private generateTypeScriptNative(options: ComponentGenerationOptions): string {
338
+ const {
339
+ componentName,
340
+ svgContent,
341
+ defaultWidth = 24,
342
+ defaultHeight = 24,
343
+ defaultFill = 'currentColor'
344
+ } = options;
345
+
346
+ return `/**
347
+ * ${componentName} SVG Icon (Native TypeScript)
348
+ * Generated by svger-cli
349
+ */
350
+
351
+ export interface ${componentName}Options {
352
+ width?: number | string;
353
+ height?: number | string;
354
+ fill?: string;
355
+ className?: string;
356
+ style?: Record<string, any>;
357
+ }
358
+
359
+ export function ${componentName}(options: ${componentName}Options = {}): string {
360
+ const {
361
+ width = ${defaultWidth},
362
+ height = ${defaultHeight},
363
+ fill = "${defaultFill}",
364
+ className = "",
365
+ style = {}
366
+ } = options;
367
+
368
+ const styleString = Object.entries(style)
369
+ .map(([key, value]) => \`\${key}: \${value}\`)
370
+ .join("; ");
371
+
372
+ return \`
373
+ <svg
374
+ viewBox="0 0 ${defaultWidth} ${defaultHeight}"
375
+ xmlns="http://www.w3.org/2000/svg"
376
+ width="\${width}"
377
+ height="\${height}"
378
+ fill="\${fill}"
379
+ class="\${className}"
380
+ style="\${styleString}"
381
+ >
382
+ ${svgContent}
383
+ </svg>
384
+ \`.trim();
385
+ }
386
+
387
+ export default ${componentName};
388
+ `;
389
+ }
390
+
391
+ /**
392
+ * Process string template with variable substitution
393
+ */
394
+ private processStringTemplate(
395
+ template: string,
396
+ options: ComponentGenerationOptions
397
+ ): string {
398
+ const variables = {
399
+ ...options,
400
+ defaultWidth: options.defaultWidth || 24,
401
+ defaultHeight: options.defaultHeight || 24,
402
+ defaultFill: options.defaultFill || 'currentColor'
403
+ };
404
+
405
+ let processed = template;
406
+
407
+ // Replace variables in the format {{variableName}}
408
+ for (const [key, value] of Object.entries(variables)) {
409
+ const regex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
410
+ processed = processed.replace(regex, String(value));
411
+ }
412
+
413
+ return processed;
414
+ }
415
+
416
+ /**
417
+ * Enhanced Styled Template with comprehensive styling support
418
+ */
419
+ private generateEnhancedStyled(options: ComponentGenerationOptions): string {
420
+ const {
421
+ componentName,
422
+ svgContent,
423
+ defaultWidth = 24,
424
+ defaultHeight = 24,
425
+ defaultFill = 'currentColor'
426
+ } = options;
427
+
428
+ return `import React from "react";
429
+ import type { SVGProps } from "react";
430
+
431
+ /**
432
+ * ${componentName} SVG Component with Enhanced Styling
433
+ * Generated by svger-cli with comprehensive styling support
434
+ */
435
+
436
+ export interface ${componentName}Props extends SVGProps<SVGSVGElement> {
437
+ // Size variants
438
+ size?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
439
+
440
+ // Color variants
441
+ variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info';
442
+
443
+ // Animation options
444
+ animate?: boolean | 'spin' | 'pulse' | 'bounce' | 'fade';
445
+
446
+ // Theme support
447
+ theme?: 'light' | 'dark' | 'auto';
448
+
449
+ // Responsive behavior
450
+ responsive?: boolean;
451
+
452
+ // Interaction states
453
+ loading?: boolean;
454
+ disabled?: boolean;
455
+ }
456
+
457
+ const sizeMap = {
458
+ xs: 12,
459
+ sm: 16,
460
+ md: 24,
461
+ lg: 32,
462
+ xl: 48,
463
+ } as const;
464
+
465
+ const colorMap = {
466
+ primary: '#007bff',
467
+ secondary: '#6c757d',
468
+ success: '#28a745',
469
+ warning: '#ffc107',
470
+ danger: '#dc3545',
471
+ info: '#17a2b8',
472
+ } as const;
473
+
474
+ const ${componentName} = React.forwardRef<SVGSVGElement, ${componentName}Props>(
475
+ ({
476
+ size = 'md',
477
+ variant,
478
+ animate = false,
479
+ theme = 'auto',
480
+ responsive = false,
481
+ loading = false,
482
+ disabled = false,
483
+ style,
484
+ className,
485
+ ...props
486
+ }, ref) => {
487
+
488
+ // Calculate size
489
+ const dimensions = React.useMemo(() => {
490
+ if (typeof size === 'number') return { width: size, height: size };
491
+ if (typeof size === 'string' && !isNaN(Number(size))) return { width: Number(size), height: Number(size) };
492
+ const mappedSize = sizeMap[size as keyof typeof sizeMap] || sizeMap.md;
493
+ return { width: mappedSize, height: mappedSize };
494
+ }, [size]);
495
+
496
+ // Generate styles
497
+ const computedStyles = React.useMemo(() => {
498
+ const baseStyles: React.CSSProperties = {
499
+ display: 'inline-block',
500
+ verticalAlign: 'middle',
501
+ transition: 'all 0.2s ease-in-out',
502
+ };
503
+
504
+ // Apply color variant
505
+ if (variant) {
506
+ baseStyles.color = colorMap[variant];
507
+ }
508
+
509
+ // Apply theme
510
+ if (theme === 'dark') {
511
+ baseStyles.filter = 'invert(1)';
512
+ } else if (theme === 'auto') {
513
+ baseStyles.filter = 'var(--svger-theme-filter, none)';
514
+ }
515
+
516
+ // Apply animation
517
+ if (animate) {
518
+ if (animate === true || animate === 'spin') {
519
+ baseStyles.animation = 'svger-spin 2s linear infinite';
520
+ } else if (animate === 'pulse') {
521
+ baseStyles.animation = 'svger-pulse 2s ease-in-out infinite';
522
+ } else if (animate === 'bounce') {
523
+ baseStyles.animation = 'svger-bounce 1s infinite';
524
+ } else if (animate === 'fade') {
525
+ baseStyles.animation = 'svger-fade 2s ease-in-out infinite alternate';
526
+ }
527
+ }
528
+
529
+ // Apply loading state
530
+ if (loading) {
531
+ baseStyles.animation = 'svger-spin 1s linear infinite';
532
+ baseStyles.opacity = 0.7;
533
+ }
534
+
535
+ // Apply disabled state
536
+ if (disabled) {
537
+ baseStyles.opacity = 0.5;
538
+ baseStyles.pointerEvents = 'none';
539
+ }
540
+
541
+ // Apply responsive behavior
542
+ if (responsive) {
543
+ baseStyles.width = '100%';
544
+ baseStyles.height = 'auto';
545
+ baseStyles.maxWidth = \`\${dimensions.width}px\`;
546
+ }
547
+
548
+ return { ...baseStyles, ...style };
549
+ }, [variant, theme, animate, loading, disabled, responsive, dimensions, style]);
550
+
551
+ // Generate class names
552
+ const classNames = React.useMemo(() => {
553
+ const classes = ['svger-icon'];
554
+
555
+ if (typeof size === 'string' && sizeMap[size as keyof typeof sizeMap]) {
556
+ classes.push(\`svger-size-\${size}\`);
557
+ }
558
+
559
+ if (variant) {
560
+ classes.push(\`svger-variant-\${variant}\`);
561
+ }
562
+
563
+ if (theme) {
564
+ classes.push(\`svger-theme-\${theme}\`);
565
+ }
566
+
567
+ if (animate) {
568
+ const animationType = animate === true ? 'spin' : animate;
569
+ classes.push(\`svger-animate-\${animationType}\`);
570
+ }
571
+
572
+ if (responsive) {
573
+ classes.push('svger-responsive');
574
+ }
575
+
576
+ if (loading) {
577
+ classes.push('svger-loading');
578
+ }
579
+
580
+ if (disabled) {
581
+ classes.push('svger-disabled');
582
+ }
583
+
584
+ if (className) {
585
+ classes.push(className);
586
+ }
587
+
588
+ return classes.join(' ');
589
+ }, [size, variant, theme, animate, responsive, loading, disabled, className]);
590
+
591
+ return (
592
+ <svg
593
+ ref={ref}
594
+ viewBox="0 0 ${defaultWidth} ${defaultHeight}"
595
+ xmlns="http://www.w3.org/2000/svg"
596
+ width={responsive ? '100%' : dimensions.width}
597
+ height={responsive ? 'auto' : dimensions.height}
598
+ fill={props.fill || "${defaultFill}"}
599
+ className={classNames}
600
+ style={computedStyles}
601
+ aria-hidden={props['aria-hidden'] || (disabled ? 'true' : undefined)}
602
+ aria-busy={loading ? 'true' : undefined}
603
+ {...props}
604
+ >
605
+ ${svgContent}
606
+ </svg>
607
+ );
608
+ }
609
+ );
610
+
611
+ ${componentName}.displayName = "${componentName}";
612
+
613
+ export default ${componentName};
614
+ `;
615
+ }
616
+ }
617
+
618
+ // Export singleton instance
619
+ export const templateManager = TemplateManager.getInstance();