svgfusion 1.4.0 → 1.5.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.
package/README.md CHANGED
@@ -28,9 +28,20 @@ A powerful Node.js CLI tool and library that converts SVG files into optimized R
28
28
  - **Batch Processing**: Convert entire directories of SVG files
29
29
  - **Production Ready**: Robust output with proper error handling
30
30
  - **Zero Configuration**: Works out of the box with sensible defaults
31
+ - **Simple CLI**: Direct, intuitive command structure without subcommands
31
32
 
32
33
  ## Quick Start
33
34
 
35
+ ### Simple as One Command
36
+
37
+ ```bash
38
+ # Convert all SVG files in a directory to React components
39
+ npx svgfusion ./icons --output ./components
40
+
41
+ # Add prefixes, suffixes, and generate index file
42
+ npx svgfusion ./icons --prefix Icon --suffix Component --index
43
+ ```
44
+
34
45
  ### Installation
35
46
 
36
47
  ```bash
@@ -38,12 +49,9 @@ A powerful Node.js CLI tool and library that converts SVG files into optimized R
38
49
  npm install -g svgfusion
39
50
 
40
51
  # Or use npx (no installation needed)
41
- npx svgfusion convert ./icons --output ./components
52
+ npx svgfusion ./icons --output ./components
42
53
 
43
54
  # Or install locally for programmatic usage
44
-
45
- # Add prefix and suffix to component names
46
- svgfusion convert ./icons --output ./components --prefix Icon --suffix Svg
47
55
  npm install svgfusion
48
56
  # or
49
57
  yarn add svgfusion
@@ -55,12 +63,12 @@ pnpm add svgfusion
55
63
 
56
64
  <img src="https://i.ibb.co/8n2b5mtp/cli.png" alt="SVGFusion CLI" width="512" >
57
65
 
58
- svgfusion convert ./icons --output ./components --prefix Icon --suffix Svg
66
+ svgfusion ./icons --output ./components --prefix Icon --suffix Svg
59
67
 
60
68
  You can add a prefix and/or suffix to the generated component names using the `--prefix` and `--suffix` options:
61
69
 
62
70
  ```sh
63
- npx svgfusion convert ./svgs --prefix Icon --suffix Svg
71
+ npx svgfusion ./svgs --prefix Icon --suffix Svg
64
72
  ```
65
73
 
66
74
  This will generate components like `IconStarSvg`, `IconUserSvg`, etc.
@@ -70,7 +78,7 @@ Both options sanitize input to remove symbols and spaces. If omitted, no prefix/
70
78
  #### Example
71
79
 
72
80
  ```sh
73
- npx svgfusion convert ./svgs --prefix App --suffix Widget
81
+ npx svgfusion ./svgs --prefix App --suffix Widget
74
82
  # Output: AppStarWidget, AppUserWidget, ...
75
83
  ```
76
84
 
@@ -84,30 +92,40 @@ npx svgfusion --help
84
92
 
85
93
  ```bash
86
94
  # Convert to React components (default)
87
- svgfusion convert ./icons --output ./components
95
+ svgfusion ./icons --output ./components
88
96
 
89
97
  # Convert to Vue 3 components
90
- svgfusion convert ./icons --output ./components --framework vue
98
+ svgfusion ./icons --output ./components --framework vue
91
99
 
92
100
  # Single file conversion with TypeScript
93
- svgfusion convert ./star.svg --output ./components --typescript
101
+ svgfusion ./star.svg --output ./components --typescript
102
+
103
+ # Batch processing with recursive directory scanning
104
+ svgfusion ./icons --output ./components --recursive
105
+
106
+ # Generate index file for tree-shaking
107
+ svgfusion ./icons --output ./components --index
94
108
 
95
109
  # Skip optimization
96
- svgfusion convert ./icons --output ./components --no-optimize
110
+ svgfusion ./icons --output ./components --no-optimize
97
111
 
98
112
  # Using npx (no global install needed)
99
- npx svgfusion convert ./icons --output ./components --framework react
113
+ npx svgfusion ./icons --output ./components --framework react
100
114
  ```
101
115
 
102
116
  ### CLI Options
103
117
 
104
118
  ```bash
105
- svgfusion convert <input> [options]
119
+ svgfusion <input> [options]
106
120
 
107
121
  Options:
108
122
  -o, --output <output> Output directory (default: "./components")
109
123
  -f, --framework <framework> Target framework (react|vue) (default: "react")
110
124
  -t, --typescript Generate TypeScript files
125
+ -r, --recursive Recursively scan input directory for SVG files
126
+ --index Generate index file for tree-shaking
127
+ --index-format <format> Index file format (ts|js) (default: "ts")
128
+ --export-type <type> Export type (named|default) (default: "named")
111
129
  --no-optimize Skip SVG optimization
112
130
  --prefix <prefix> Add prefix to component name (sanitized)
113
131
  --suffix <suffix> Add suffix to component name (sanitized)
@@ -120,17 +138,25 @@ Perfect for trying out SVGFusion or one-time conversions:
120
138
 
121
139
  ```bash
122
140
  # Convert React components
123
- npx svgfusion convert ./assets/icons --output ./src/components/icons
141
+ npx svgfusion ./assets/icons --output ./src/components/icons
124
142
 
125
143
  # Convert Vue components with TypeScript
126
- npx svgfusion convert ./assets/icons --output ./src/components --framework vue --typescript
144
+ npx svgfusion ./assets/icons --output ./src/components --framework vue --typescript
127
145
 
128
146
  # Convert single file
129
- npx svgfusion convert ./logo.svg --output ./src/components --framework react
147
+ npx svgfusion ./logo.svg --output ./src/components --framework react
148
+
149
+ # Batch convert with index generation
150
+ npx svgfusion ./assets/icons --output ./src/components --recursive --index
151
+
152
+ # Convert with custom naming
153
+ npx svgfusion ./assets/icons --output ./src/components --prefix Icon --suffix Component --index
130
154
  ```
131
155
 
132
156
  ### Programmatic Usage
133
157
 
158
+ #### Single File Conversion
159
+
134
160
  ```typescript
135
161
  import { convertToReact, convertToVue, readSvgFile } from 'svgfusion';
136
162
 
@@ -156,6 +182,41 @@ console.log(reactResult.code); // Generated React component
156
182
  console.log(vueResult.code); // Generated Vue component
157
183
  ```
158
184
 
185
+ #### Batch Processing
186
+
187
+ ```typescript
188
+ import { BatchConverter } from 'svgfusion';
189
+
190
+ const batchConverter = new BatchConverter();
191
+
192
+ // Convert entire directory
193
+ const result = await batchConverter.convertBatch({
194
+ inputDir: './icons',
195
+ outputDir: './components',
196
+ framework: 'react',
197
+ recursive: true,
198
+ generateIndex: true,
199
+ typescript: true,
200
+ prefix: 'Icon',
201
+ suffix: 'Component',
202
+ indexFormat: 'ts',
203
+ exportType: 'named',
204
+ });
205
+
206
+ // Check results
207
+ console.log(`Processed ${result.summary.total} files`);
208
+ console.log(`Successful: ${result.summary.successful}`);
209
+ console.log(`Failed: ${result.summary.failed}`);
210
+
211
+ // Get component names
212
+ const componentNames = batchConverter.getComponentNames(result);
213
+ console.log('Generated components:', componentNames);
214
+
215
+ // Generate summary report
216
+ const report = batchConverter.generateSummaryReport(result);
217
+ console.log(report);
218
+ ```
219
+
159
220
  ## API Reference
160
221
 
161
222
  ### `convertToReact(svgContent, options)`
@@ -186,6 +247,39 @@ Convert SVG to Vue 3 component. **Note: This is a synchronous function.**
186
247
  - `compositionApi?: boolean` - Use Composition API (default: `true`)
187
248
  - `optimize?: boolean` - Apply SVGO optimization (default: `true`)
188
249
 
250
+ ### `BatchConverter`
251
+
252
+ Process multiple SVG files in batch operations.
253
+
254
+ #### `convertBatch(options: BatchConversionOptions)`
255
+
256
+ Convert multiple SVG files to framework components.
257
+
258
+ **Options:**
259
+
260
+ - `inputDir: string` - Input directory path
261
+ - `outputDir: string` - Output directory path
262
+ - `framework?: 'react' | 'vue'` - Target framework (default: 'react')
263
+ - `recursive?: boolean` - Recursively scan directories (default: false)
264
+ - `extensions?: string[]` - File extensions to process (default: ['.svg'])
265
+ - `generateIndex?: boolean` - Generate index file (default: false)
266
+ - `indexFormat?: 'ts' | 'js'` - Index file format (default: 'ts')
267
+ - `exportType?: 'named' | 'default'` - Export type (default: 'named')
268
+ - `prefix?: string` - Add prefix to component names
269
+ - `suffix?: string` - Add suffix to component names
270
+ - `typescript?: boolean` - Generate TypeScript components (default: true)
271
+ - All other conversion options from `convertToReact`/`convertToVue`
272
+
273
+ **Returns:** `Promise<BatchConversionResult>`
274
+
275
+ #### `getComponentNames(results: BatchConversionResult)`
276
+
277
+ Get array of generated component names from batch results.
278
+
279
+ #### `generateSummaryReport(results: BatchConversionResult)`
280
+
281
+ Generate a detailed summary report of the conversion process.
282
+
189
283
  ### `optimizeSvg(svgContent, config?)`
190
284
 
191
285
  Optimize SVG content using SVGO. **Note: This is a synchronous function.**
@@ -286,7 +380,7 @@ const customConfig = createSvgoConfig({
286
380
  const optimizedSvg = optimizeSvg(svgContent, customConfig);
287
381
  ```
288
382
 
289
- ### Batch Processing
383
+ ### Manual Batch Processing
290
384
 
291
385
  ```typescript
292
386
  import {
@@ -308,6 +402,65 @@ for (const svgFile of svgFiles) {
308
402
  }
309
403
  ```
310
404
 
405
+ ### Index File Generation
406
+
407
+ When using the `--index` flag or `generateIndex: true` option, SVGFusion creates an optimized index file for tree-shaking:
408
+
409
+ #### Named Exports (Default)
410
+
411
+ ```typescript
412
+ // Auto-generated index file for tree-shaking
413
+ // This file exports all components for optimal bundling
414
+
415
+ export { default as IconStar } from './IconStar';
416
+ export { default as IconUser } from './IconUser';
417
+ export { default as IconHome } from './IconHome';
418
+
419
+ // Barrel export for convenience
420
+ export { IconStar, IconUser, IconHome };
421
+
422
+ // TypeScript component types
423
+ export type IconComponent = React.ComponentType<React.SVGProps<SVGSVGElement>>;
424
+ export type IconComponents = {
425
+ IconStar: IconComponent;
426
+ IconUser: IconComponent;
427
+ IconHome: IconComponent;
428
+ };
429
+ ```
430
+
431
+ #### Default Exports
432
+
433
+ ```typescript
434
+ // Auto-generated index file
435
+ // Warning: Default exports are less tree-shakeable
436
+
437
+ import IconStar from './IconStar';
438
+ import IconUser from './IconUser';
439
+ import IconHome from './IconHome';
440
+
441
+ export default {
442
+ IconStar,
443
+ IconUser,
444
+ IconHome,
445
+ };
446
+
447
+ // Individual exports for flexibility
448
+ export { default as IconStar } from './IconStar';
449
+ export { default as IconUser } from './IconUser';
450
+ export { default as IconHome } from './IconHome';
451
+ ```
452
+
453
+ #### Usage
454
+
455
+ ```typescript
456
+ // Tree-shakeable named imports (recommended)
457
+ import { IconStar, IconUser } from './components';
458
+
459
+ // Default import
460
+ import * as Icons from './components';
461
+ const { IconStar, IconUser } = Icons;
462
+ ```
463
+
311
464
  ## Complex Filename Support
312
465
 
313
466
  SVGFusion handles complex filenames from design systems:
package/dist/cli.js CHANGED
@@ -44,6 +44,7 @@ var init_cjs_shims = __esm({
44
44
  // src/utils/files.ts
45
45
  var files_exports = {};
46
46
  __export(files_exports, {
47
+ ensureDir: () => ensureDir,
47
48
  ensureDirectoryExists: () => ensureDirectoryExists,
48
49
  getComponentFilename: () => getComponentFilename,
49
50
  getFileExtension: () => getFileExtension,
@@ -110,7 +111,7 @@ function getFileExtension(framework, typescript = true) {
110
111
  function getComponentFilename(_svgFilename, componentName, extension) {
111
112
  return `${componentName}${extension}`;
112
113
  }
113
- var import_promises, import_path, import_fs;
114
+ var import_promises, import_path, import_fs, ensureDir;
114
115
  var init_files = __esm({
115
116
  "src/utils/files.ts"() {
116
117
  "use strict";
@@ -118,6 +119,151 @@ var init_files = __esm({
118
119
  import_promises = require("fs/promises");
119
120
  import_path = require("path");
120
121
  import_fs = require("fs");
122
+ ensureDir = ensureDirectoryExists;
123
+ }
124
+ });
125
+
126
+ // src/utils/index-generator.ts
127
+ var index_generator_exports = {};
128
+ __export(index_generator_exports, {
129
+ generateIndexFile: () => generateIndexFile,
130
+ generateReadmeContent: () => generateReadmeContent
131
+ });
132
+ function generateIndexFile(results, options) {
133
+ const { format, exportType, typescript } = options;
134
+ const sortedResults = [...results].sort(
135
+ (a, b) => a.componentName.localeCompare(b.componentName)
136
+ );
137
+ if (exportType === "default") {
138
+ return generateDefaultExports(sortedResults, format, typescript);
139
+ } else {
140
+ return generateNamedExports(sortedResults, format, typescript);
141
+ }
142
+ }
143
+ function generateNamedExports(results, format, typescript) {
144
+ let content = "";
145
+ content += `// Auto-generated index file for tree-shaking
146
+ `;
147
+ content += `// This file exports all components for optimal bundling
148
+
149
+ `;
150
+ for (const result of results) {
151
+ const importPath = getImportPath(result.filename);
152
+ content += `export { default as ${result.componentName} } from './${importPath}';
153
+ `;
154
+ }
155
+ content += `
156
+ // Barrel export for convenience
157
+ `;
158
+ content += `export {
159
+ `;
160
+ for (const result of results) {
161
+ content += ` ${result.componentName},
162
+ `;
163
+ }
164
+ content += `};
165
+ `;
166
+ if (typescript && format === "ts") {
167
+ content += `
168
+ // TypeScript component types
169
+ `;
170
+ content += `export type IconComponent = React.ComponentType<React.SVGProps<SVGSVGElement>>;
171
+ `;
172
+ content += `export type IconComponents = {
173
+ `;
174
+ for (const result of results) {
175
+ content += ` ${result.componentName}: IconComponent;
176
+ `;
177
+ }
178
+ content += `};
179
+ `;
180
+ }
181
+ return content;
182
+ }
183
+ function generateDefaultExports(results, _format, _typescript) {
184
+ let content = "";
185
+ content += `// Auto-generated index file
186
+ `;
187
+ content += `// Warning: Default exports are less tree-shakeable
188
+
189
+ `;
190
+ for (const result of results) {
191
+ const importPath = getImportPath(result.filename);
192
+ content += `import ${result.componentName} from './${importPath}';
193
+ `;
194
+ }
195
+ content += `
196
+ export default {
197
+ `;
198
+ for (const result of results) {
199
+ content += ` ${result.componentName},
200
+ `;
201
+ }
202
+ content += `};
203
+ `;
204
+ content += `
205
+ // Individual exports for flexibility
206
+ `;
207
+ for (const result of results) {
208
+ content += `export { default as ${result.componentName} } from './${getImportPath(result.filename)}';
209
+ `;
210
+ }
211
+ return content;
212
+ }
213
+ function getImportPath(filename) {
214
+ return filename.replace(/\.(tsx?|jsx?|vue)$/, "");
215
+ }
216
+ function generateReadmeContent(results) {
217
+ const componentNames = results.map((r) => r.componentName).sort();
218
+ let content = `# Generated Components
219
+
220
+ `;
221
+ content += `This directory contains ${results.length} auto-generated components from SVG files.
222
+
223
+ `;
224
+ content += `## Usage
225
+
226
+ `;
227
+ content += `### Named imports (recommended for tree-shaking)
228
+
229
+ `;
230
+ content += `\`\`\`typescript
231
+ `;
232
+ content += `import { ${componentNames.slice(0, 3).join(", ")} } from './index';
233
+ `;
234
+ content += `\`\`\`
235
+
236
+ `;
237
+ content += `### Default import
238
+
239
+ `;
240
+ content += `\`\`\`typescript
241
+ `;
242
+ content += `import * as Icons from './index';
243
+ `;
244
+ content += `\`\`\`
245
+
246
+ `;
247
+ content += `## Available Components
248
+
249
+ `;
250
+ for (const name of componentNames) {
251
+ content += `- \`${name}\`
252
+ `;
253
+ }
254
+ content += `
255
+ ## Tree-shaking
256
+
257
+ `;
258
+ content += `This index file is optimized for tree-shaking. When using named imports, `;
259
+ content += `bundlers like webpack, Rollup, or Vite will only include the components you actually use.
260
+ `;
261
+ return content;
262
+ }
263
+ var init_index_generator = __esm({
264
+ "src/utils/index-generator.ts"() {
265
+ "use strict";
266
+ init_cjs_shims();
121
267
  }
122
268
  });
123
269
 
@@ -130,11 +276,20 @@ var import_path2 = require("path");
130
276
 
131
277
  // src/utils/name.ts
132
278
  init_cjs_shims();
133
- var import_just_pascal_case = __toESM(require("just-pascal-case"));
279
+
280
+ // src/utils/string.ts
281
+ init_cjs_shims();
282
+ var pascalCase = (str) => {
283
+ return str.match(
284
+ /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*[a-z]*|[A-Z]|[0-9]+[a-z]*/g
285
+ )?.map((x) => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase()).join("") || "";
286
+ };
287
+
288
+ // src/utils/name.ts
134
289
  function svgToComponentName(filename) {
135
290
  let baseName = filename.replace(/\.svg$/i, "");
136
291
  baseName = processComplexFilename(baseName);
137
- return (0, import_just_pascal_case.default)(baseName);
292
+ return pascalCase(baseName);
138
293
  }
139
294
  function processComplexFilename(filename) {
140
295
  let processed = filename.toLowerCase().replace(/[^a-zA-Z0-9]/g, " ");
@@ -142,9 +297,9 @@ function processComplexFilename(filename) {
142
297
  return processed;
143
298
  }
144
299
  function formatComponentName(name, prefix, suffix) {
145
- const prefixPart = prefix ? (0, import_just_pascal_case.default)(prefix) : "";
146
- const suffixPart = suffix ? (0, import_just_pascal_case.default)(suffix) : "";
147
- const baseName = (0, import_just_pascal_case.default)(name);
300
+ const prefixPart = prefix ? pascalCase(prefix) : "";
301
+ const suffixPart = suffix ? pascalCase(suffix) : "";
302
+ const baseName = pascalCase(name);
148
303
  return `${prefixPart}${baseName}${suffixPart}`;
149
304
  }
150
305
 
@@ -477,11 +632,7 @@ export default {
477
632
  <template>
478
633
  ${svg}
479
634
  </template>`;
480
- const style = `
481
- <style scoped>
482
- /* Component styles */
483
- </style>`;
484
- return [script, template, style].filter(Boolean).join("\n");
635
+ return [script, template].filter(Boolean).join("\n");
485
636
  }
486
637
 
487
638
  // src/core/vue-converter.ts
@@ -624,16 +775,35 @@ if (process.argv.length === 1 || isNpxCall && process.argv.length === 2) {
624
775
  program.name("svgfusion").description(
625
776
  "Transform SVG files into production-ready React and Vue 3 components"
626
777
  ).version(packageJson.version);
627
- program.description("Convert SVG files to React or Vue components").argument("<input>", "Input SVG file or directory").option("-o, --output <output>", "Output directory", "./components").option(
778
+ program.argument("[input]", "Input SVG file or directory").option("-o, --output <output>", "Output directory", "./components").option(
628
779
  "-f, --framework <framework>",
629
780
  "Target framework (react|vue)",
630
781
  "react"
631
- ).option("-t, --typescript", "Generate TypeScript files", false).option("--no-optimize", "Skip SVG optimization").option("--prefix <prefix>", "Add prefix to component name").option("--suffix <suffix>", "Add suffix to component name").action(
782
+ ).option("-t, --typescript", "Generate TypeScript files", false).option(
783
+ "-r, --recursive",
784
+ "Recursively scan input directory for SVG files",
785
+ false
786
+ ).option("--no-optimize", "Skip SVG optimization").option("--prefix <prefix>", "Add prefix to component name").option("--suffix <suffix>", "Add suffix to component name").option("--index", "Generate index file for tree-shaking", false).option("--index-format <format>", "Index file format (ts|js)", "ts").option("--export-type <type>", "Export type (named|default)", "named").action(
632
787
  async (input, options) => {
788
+ if (!input) {
789
+ program.outputHelp();
790
+ process.exit(0);
791
+ }
633
792
  console.log(createBanner(colors));
634
793
  console.log(`${colors.blue}\u{1F504} Processing SVG files...${colors.reset}`);
635
794
  try {
636
- const { framework, output, typescript, optimize: optimize2, prefix, suffix } = options;
795
+ const {
796
+ framework,
797
+ output,
798
+ typescript,
799
+ recursive,
800
+ optimize: optimize2,
801
+ prefix,
802
+ suffix,
803
+ index: generateIndex,
804
+ indexFormat,
805
+ exportType
806
+ } = options;
637
807
  if (framework !== "react" && framework !== "vue") {
638
808
  throw new Error('Framework must be either "react" or "vue"');
639
809
  }
@@ -646,7 +816,7 @@ program.description("Convert SVG files to React or Vue components").argument("<i
646
816
  throw new Error("Input file must be an SVG file");
647
817
  }
648
818
  } else if (inputStat.isDirectory()) {
649
- svgFiles = await readSvgDirectory(input);
819
+ svgFiles = await readSvgDirectory(input, recursive);
650
820
  } else {
651
821
  throw new Error("Input must be a file or directory");
652
822
  }
@@ -656,6 +826,7 @@ program.description("Convert SVG files to React or Vue components").argument("<i
656
826
  console.log(
657
827
  `${colors.blue}\u{1F504} Converting ${svgFiles.length} SVG file(s)...${colors.reset}`
658
828
  );
829
+ const results = [];
659
830
  for (const filePath of svgFiles) {
660
831
  const svgContent = await readSvgFile(filePath);
661
832
  const optimizedSvg = optimize2 ? optimizeSvg(svgContent) : svgContent;
@@ -674,6 +845,21 @@ program.description("Convert SVG files to React or Vue components").argument("<i
674
845
  });
675
846
  const outputPath = (0, import_path3.join)(output, result.filename);
676
847
  await writeComponentFile(outputPath, result.code);
848
+ results.push(result);
849
+ }
850
+ if (generateIndex && results.length > 0) {
851
+ const { generateIndexFile: generateIndexFile2 } = await Promise.resolve().then(() => (init_index_generator(), index_generator_exports));
852
+ const indexContent = generateIndexFile2(results, {
853
+ format: indexFormat,
854
+ exportType,
855
+ typescript
856
+ });
857
+ const indexFilename = `index.${indexFormat}`;
858
+ const indexPath = (0, import_path3.join)(output, indexFilename);
859
+ await writeComponentFile(indexPath, indexContent);
860
+ console.log(
861
+ `${colors.green}\u{1F4C4} Generated ${indexFilename} for tree-shaking${colors.reset}`
862
+ );
677
863
  }
678
864
  console.log(
679
865
  `${colors.green}\u2705 Successfully converted ${svgFiles.length} SVG file(s) to ${framework} components${colors.reset}`
@@ -681,6 +867,10 @@ program.description("Convert SVG files to React or Vue components").argument("<i
681
867
  console.log(
682
868
  `${colors.dim}\u{1F4C1} Output location: ${colors.reset}${colors.cyan}${output}${colors.reset}`
683
869
  );
870
+ const componentNames = results.map((r) => r.componentName);
871
+ console.log(
872
+ `${colors.dim}\u{1F4E6} Generated components: ${colors.reset}${componentNames.join(", ")}`
873
+ );
684
874
  } catch (error) {
685
875
  console.error(
686
876
  `${colors.red}\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}${colors.reset}`
@@ -692,10 +882,15 @@ program.description("Convert SVG files to React or Vue components").argument("<i
692
882
  "after",
693
883
  `
694
884
  ${colors.gray}Examples:${colors.reset}
695
- ${colors.blue}svgfusion convert src/icons -o src/components${colors.reset}
696
- ${colors.blue}svgfusion convert src/icons --framework vue --typescript${colors.reset}
697
- ${colors.blue}svgfusion convert src/icons --optimize --prefix My --suffix Widget${colors.reset}
698
- ${colors.blue}svgfusion convert src/icons --framework react --typescript --optimize${colors.reset}
885
+ ${colors.blue}svgfusion src/icons -o src/components${colors.reset}
886
+ ${colors.blue}svgfusion src/icons --framework vue --typescript${colors.reset}
887
+ ${colors.blue}svgfusion src/icons --recursive --index${colors.reset}
888
+ ${colors.blue}svgfusion src/icons --prefix Icon --suffix Component --index${colors.reset}
889
+ ${colors.blue}svgfusion src/icons --framework react --typescript --optimize${colors.reset}
699
890
  `
700
- ).outputHelp();
891
+ );
892
+ if (process.argv.length === 2) {
893
+ program.outputHelp();
894
+ process.exit(0);
895
+ }
701
896
  program.parse();
package/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Config } from 'svgo';
2
- export { default as pascalCase } from 'just-pascal-case';
3
2
 
4
3
  interface ConversionOptions {
5
4
  name?: string;
@@ -41,6 +40,10 @@ interface BatchConversionOptions extends ConversionOptions {
41
40
  outputDir: string;
42
41
  recursive?: boolean;
43
42
  extensions?: string[];
43
+ generateIndex?: boolean;
44
+ indexFormat?: 'ts' | 'js';
45
+ exportType?: 'named' | 'default';
46
+ framework?: Framework;
44
47
  }
45
48
  interface BatchConversionResult {
46
49
  results: ConversionResult[];
@@ -69,6 +72,9 @@ interface CliOptions {
69
72
  format?: 'esm' | 'cjs';
70
73
  recursive?: boolean;
71
74
  verbose?: boolean;
75
+ generateIndex?: boolean;
76
+ indexFormat?: 'ts' | 'js';
77
+ exportType?: 'named' | 'default';
72
78
  }
73
79
 
74
80
  /**
@@ -129,6 +135,8 @@ declare function writeComponentFile(filePath: string, content: string): Promise<
129
135
  */
130
136
  declare function readSvgDirectory(dirPath: string, recursive?: boolean): Promise<string[]>;
131
137
 
138
+ declare const pascalCase: (str: string) => string;
139
+
132
140
  /**
133
141
  * Convert SVG filename to a valid React component name
134
142
  * @param filename - The SVG filename
@@ -151,4 +159,4 @@ declare function sanitizeComponentName(name: string): string;
151
159
  */
152
160
  declare function formatComponentName(name: string, prefix?: string, suffix?: string): string;
153
161
 
154
- export { type BatchConversionOptions, type BatchConversionResult, type CliOptions, type ConversionError, type ConversionOptions, type ConversionResult, type Framework, type ReactConversionOptions, type VueConversionOptions, convertToReact, convertToVue, createSvgoConfig, formatComponentName, optimizeSvg, readSvgDirectory, readSvgFile, sanitizeComponentName, svgToComponentName, writeComponentFile, writeSvgFile };
162
+ export { type BatchConversionOptions, type BatchConversionResult, type CliOptions, type ConversionError, type ConversionOptions, type ConversionResult, type Framework, type ReactConversionOptions, type VueConversionOptions, convertToReact, convertToVue, createSvgoConfig, formatComponentName, optimizeSvg, pascalCase, readSvgDirectory, readSvgFile, sanitizeComponentName, svgToComponentName, writeComponentFile, writeSvgFile };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Config } from 'svgo';
2
- export { default as pascalCase } from 'just-pascal-case';
3
2
 
4
3
  interface ConversionOptions {
5
4
  name?: string;
@@ -41,6 +40,10 @@ interface BatchConversionOptions extends ConversionOptions {
41
40
  outputDir: string;
42
41
  recursive?: boolean;
43
42
  extensions?: string[];
43
+ generateIndex?: boolean;
44
+ indexFormat?: 'ts' | 'js';
45
+ exportType?: 'named' | 'default';
46
+ framework?: Framework;
44
47
  }
45
48
  interface BatchConversionResult {
46
49
  results: ConversionResult[];
@@ -69,6 +72,9 @@ interface CliOptions {
69
72
  format?: 'esm' | 'cjs';
70
73
  recursive?: boolean;
71
74
  verbose?: boolean;
75
+ generateIndex?: boolean;
76
+ indexFormat?: 'ts' | 'js';
77
+ exportType?: 'named' | 'default';
72
78
  }
73
79
 
74
80
  /**
@@ -129,6 +135,8 @@ declare function writeComponentFile(filePath: string, content: string): Promise<
129
135
  */
130
136
  declare function readSvgDirectory(dirPath: string, recursive?: boolean): Promise<string[]>;
131
137
 
138
+ declare const pascalCase: (str: string) => string;
139
+
132
140
  /**
133
141
  * Convert SVG filename to a valid React component name
134
142
  * @param filename - The SVG filename
@@ -151,4 +159,4 @@ declare function sanitizeComponentName(name: string): string;
151
159
  */
152
160
  declare function formatComponentName(name: string, prefix?: string, suffix?: string): string;
153
161
 
154
- export { type BatchConversionOptions, type BatchConversionResult, type CliOptions, type ConversionError, type ConversionOptions, type ConversionResult, type Framework, type ReactConversionOptions, type VueConversionOptions, convertToReact, convertToVue, createSvgoConfig, formatComponentName, optimizeSvg, readSvgDirectory, readSvgFile, sanitizeComponentName, svgToComponentName, writeComponentFile, writeSvgFile };
162
+ export { type BatchConversionOptions, type BatchConversionResult, type CliOptions, type ConversionError, type ConversionOptions, type ConversionResult, type Framework, type ReactConversionOptions, type VueConversionOptions, convertToReact, convertToVue, createSvgoConfig, formatComponentName, optimizeSvg, pascalCase, readSvgDirectory, readSvgFile, sanitizeComponentName, svgToComponentName, writeComponentFile, writeSvgFile };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- 'use strict';var promises=require('fs/promises'),path=require('path'),fs=require('fs'),core=require('@svgr/core'),svgo=require('svgo'),u=require('just-pascal-case');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var u__default=/*#__PURE__*/_interopDefault(u);var d=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var R=(e,r)=>()=>(e&&(r=e(e=0)),r);var q=(e,r)=>{for(var t in r)d(e,t,{get:r[t],enumerable:true});},_=(e,r,t,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of L(r))!k.call(e,n)&&n!==t&&d(e,n,{get:()=>r[n],enumerable:!(o=I(r,n))||o.enumerable});return e};var M=e=>_(d({},"__esModule",{value:true}),e);var i=R(()=>{});var U={};q(U,{ensureDirectoryExists:()=>h,getComponentFilename:()=>ie,getFileExtension:()=>se,readSvgDirectory:()=>x,readSvgFile:()=>O,writeComponentFile:()=>D,writeSvgFile:()=>P});async function O(e){try{return await promises.readFile(e,"utf-8")}catch(r){throw new Error(`Failed to read SVG file: ${e}. ${r}`)}}async function P(e,r){try{await h(path.dirname(e)),await promises.writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write SVG file: ${e}. ${t}`)}}async function D(e,r){try{await h(path.dirname(e)),await promises.writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write component file: ${e}. ${t}`)}}async function x(e,r=false){try{let t=await promises.readdir(e),o=[];for(let n of t){let s=path.join(e,n),a=await promises.stat(s);if(a.isDirectory()&&r){let p=await x(s,r);o.push(...p);}else a.isFile()&&path.extname(n).toLowerCase()===".svg"&&o.push(s);}return o}catch(t){throw new Error(`Failed to read directory: ${e}. ${t}`)}}async function h(e){fs.existsSync(e)||await promises.mkdir(e,{recursive:true});}function se(e,r=true){return e==="react"?r?".tsx":".jsx":".vue"}function ie(e,r,t){return `${r}${t}`}var w=R(()=>{i();});i();i();i();i();var H={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:false,removeDesc:false,removeUselessStrokeAndFill:false,convertColors:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"removeDimensions","cleanupNumericValues"]};function C(e,r=H){try{return svgo.optimize(e,r).data}catch(t){throw new Error(`Failed to optimize SVG: ${t}`)}}function J(e){let r=[{name:"preset-default",params:{overrides:{removeViewBox:!e.removeViewBox,removeTitle:!e.removeTitle,removeDesc:!e.removeDesc,removeUselessStrokeAndFill:!e.preserveClasses,convertColors:e.preserveColors?false:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"cleanupNumericValues"];return e.removeDimensions!==false&&r.push("removeDimensions"),{plugins:r}}i();function K(e){let r=e.replace(/\.svg$/i,"");return r=Q(r),u__default.default(r)}function Q(e){let r=e.toLowerCase().replace(/[^a-zA-Z0-9]/g," ");return r=r.replace(/\s+/g," ").trim(),r}function W(e){return u__default.default(e.replace(/[^a-zA-Z0-9]/g," "))}function f(e,r,t){let o=r?u__default.default(r):"",n=t?u__default.default(t):"",s=u__default.default(e);return `${o}${s}${n}`}var g=class{async processSvg(r,t){let{optimize:o=true}=t;return o?C(r):r}generateComponentName(r){let{name:t,prefix:o,suffix:n}=r;return f(t||"Icon",o,n)}generateFilename(r,t,o=true){try{let{getFileExtension:n,getComponentFilename:s}=(w(),M(U)),a=n(t,o);return s("icon.svg",r,a)}catch{return `${r}${{react:o?".tsx":".jsx",vue:".vue"}[t]}`}}};i();function A(e){let{typescript:r=true,memo:t=true,ref:o=true,titleProp:n=true,descProp:s=true,icon:a=true,dimensions:p=false,replaceAttrValues:c={"#000":"currentColor","#000000":"currentColor"},svgProps:l={},expandProps:v=false,nativeProps:z=true,ariaLabelledBy:B=false,ariaHidden:j=false,role:G="img"}=e,b={typescript:r,memo:t,ref:o,titleProp:n,descProp:s,icon:a,dimensions:p,expandProps:v,svgProps:{className:"{className}",...o&&{ref:"{ref}"},...z&&{width:"{width}",height:"{height}",style:"{style}"},...B&&n&&s&&{"aria-labelledby":"{titleId} {descId}"},...j&&{"aria-hidden":"true"},role:G,...l},replaceAttrValues:c,plugins:["@svgr/plugin-svgo","@svgr/plugin-jsx","@svgr/plugin-prettier"]};return b.svgoConfig={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:!n,removeDesc:!s,removeUselessStrokeAndFill:false,removeUnusedNS:false,removeUselessDefs:false,convertShapeToPath:false,mergePaths:false,convertColors:false}}},...a&&!p?[{name:"removeAttrs",params:{attrs:["width","height"]}}]:[],...a?["cleanupNumericValues"]:[]]},b}function E(e,r,t){let o=e;return y(o)&&(o=$(o,r)),o}function y(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function $(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}var F=class extends g{async convert(r,t={}){try{let o=this.generateComponentName(t),n=await this.processSvg(r,t);y(n)&&(n=$(n,o));let s=A(t),a=await core.transform(n,s,{componentName:o}),p=E(a,o,t),c=this.generateFilename(o,"react",t.typescript??!0);return {code:p,filename:c,componentName:o}}catch(o){throw new Error(`Failed to convert SVG to React: ${o}`)}}};async function ce(e,r={}){return new F().convert(e,r)}i();i();function T(e,r){let{name:t,prefix:o,suffix:n,props:s=true,replaceAttrValues:a={"#000":"currentColor","#000000":"currentColor"}}=r,c=f(t||"Icon",o,n),l=le(e);return l=pe(l,a),me(l)&&(l=ue(l,c)),l=ge(l,s),{code:fe(l,c,r),componentName:c}}function le(e){return e.replace(/<\?xml[^>]*\?>\s*/,"").replace(/<!--[\s\S]*?-->/g,"").replace(/xmlns="[^"]*"/g,"").trim()}function pe(e,r){let t=e;for(let[o,n]of Object.entries(r)){let s=new RegExp(o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"g");t=t.replace(s,n);}return t}function me(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function ue(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}function ge(e,r){return r?e.replace("<svg",'<svg :class="className" :style="style" v-bind="$attrs"'):e}function fe(e,r,t){let{typescript:o,compositionApi:n,props:s}=t,c=`<script${o?' lang="ts"':""}${n?" setup":""}>`;n?s&&(c+=`
1
+ 'use strict';var promises=require('fs/promises'),path=require('path'),fs=require('fs'),core=require('@svgr/core'),svgo=require('svgo');var d=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var R=(e,r)=>()=>(e&&(r=e(e=0)),r);var q=(e,r)=>{for(var t in r)d(e,t,{get:r[t],enumerable:true});},Z=(e,r,t,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of I(r))!k.call(e,n)&&n!==t&&d(e,n,{get:()=>r[n],enumerable:!(o=G(r,n))||o.enumerable});return e};var _=e=>Z(d({},"__esModule",{value:true}),e);var i=R(()=>{});var D={};q(D,{ensureDir:()=>se,ensureDirectoryExists:()=>v,getComponentFilename:()=>ae,getFileExtension:()=>ie,readSvgDirectory:()=>x,readSvgFile:()=>O,writeComponentFile:()=>A,writeSvgFile:()=>P});async function O(e){try{return await promises.readFile(e,"utf-8")}catch(r){throw new Error(`Failed to read SVG file: ${e}. ${r}`)}}async function P(e,r){try{await v(path.dirname(e)),await promises.writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write SVG file: ${e}. ${t}`)}}async function A(e,r){try{await v(path.dirname(e)),await promises.writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write component file: ${e}. ${t}`)}}async function x(e,r=false){try{let t=await promises.readdir(e),o=[];for(let n of t){let s=path.join(e,n),a=await promises.stat(s);if(a.isDirectory()&&r){let m=await x(s,r);o.push(...m);}else a.isFile()&&path.extname(n).toLowerCase()===".svg"&&o.push(s);}return o}catch(t){throw new Error(`Failed to read directory: ${e}. ${t}`)}}async function v(e){fs.existsSync(e)||await promises.mkdir(e,{recursive:true});}function ie(e,r=true){return e==="react"?r?".tsx":".jsx":".vue"}function ae(e,r,t){return `${r}${t}`}var se,h=R(()=>{i();se=v;});i();i();i();i();var H={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:false,removeDesc:false,removeUselessStrokeAndFill:false,convertColors:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"removeDimensions","cleanupNumericValues"]};function C(e,r=H){try{return svgo.optimize(e,r).data}catch(t){throw new Error(`Failed to optimize SVG: ${t}`)}}function J(e){let r=[{name:"preset-default",params:{overrides:{removeViewBox:!e.removeViewBox,removeTitle:!e.removeTitle,removeDesc:!e.removeDesc,removeUselessStrokeAndFill:!e.preserveClasses,convertColors:e.preserveColors?false:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"cleanupNumericValues"];return e.removeDimensions!==false&&r.push("removeDimensions"),{plugins:r}}i();i();var u=e=>e.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*[a-z]*|[A-Z]|[0-9]+[a-z]*/g)?.map(r=>r.charAt(0).toUpperCase()+r.slice(1).toLowerCase()).join("")||"";function K(e){let r=e.replace(/\.svg$/i,"");return r=Q(r),u(r)}function Q(e){let r=e.toLowerCase().replace(/[^a-zA-Z0-9]/g," ");return r=r.replace(/\s+/g," ").trim(),r}function W(e){return u(e.replace(/[^a-zA-Z0-9]/g," "))}function f(e,r,t){let o=r?u(r):"",n=t?u(t):"",s=u(e);return `${o}${s}${n}`}var g=class{async processSvg(r,t){let{optimize:o=true}=t;return o?C(r):r}generateComponentName(r){let{name:t,prefix:o,suffix:n}=r;return f(t||"Icon",o,n)}generateFilename(r,t,o=true){try{let{getFileExtension:n,getComponentFilename:s}=(h(),_(D)),a=n(t,o);return s("icon.svg",r,a)}catch{return `${r}${{react:o?".tsx":".jsx",vue:".vue"}[t]}`}}};i();function z(e){let{typescript:r=true,memo:t=true,ref:o=true,titleProp:n=true,descProp:s=true,icon:a=true,dimensions:m=false,replaceAttrValues:c={"#000":"currentColor","#000000":"currentColor"},svgProps:l={},expandProps:S=false,nativeProps:T=true,ariaLabelledBy:B=false,ariaHidden:L=false,role:j="img"}=e,b={typescript:r,memo:t,ref:o,titleProp:n,descProp:s,icon:a,dimensions:m,expandProps:S,svgProps:{className:"{className}",...o&&{ref:"{ref}"},...T&&{width:"{width}",height:"{height}",style:"{style}"},...B&&n&&s&&{"aria-labelledby":"{titleId} {descId}"},...L&&{"aria-hidden":"true"},role:j,...l},replaceAttrValues:c,plugins:["@svgr/plugin-svgo","@svgr/plugin-jsx","@svgr/plugin-prettier"]};return b.svgoConfig={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:!n,removeDesc:!s,removeUselessStrokeAndFill:false,removeUnusedNS:false,removeUselessDefs:false,convertShapeToPath:false,mergePaths:false,convertColors:false}}},...a&&!m?[{name:"removeAttrs",params:{attrs:["width","height"]}}]:[],...a?["cleanupNumericValues"]:[]]},b}function U(e,r,t){let o=e;return w(o)&&(o=$(o,r)),o}function w(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function $(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}var y=class extends g{async convert(r,t={}){try{let o=this.generateComponentName(t),n=await this.processSvg(r,t);w(n)&&(n=$(n,o));let s=z(t),a=await core.transform(n,s,{componentName:o}),m=U(a,o,t),c=this.generateFilename(o,"react",t.typescript??!0);return {code:m,filename:c,componentName:o}}catch(o){throw new Error(`Failed to convert SVG to React: ${o}`)}}};async function le(e,r={}){return new y().convert(e,r)}i();i();function E(e,r){let{name:t,prefix:o,suffix:n,props:s=true,replaceAttrValues:a={"#000":"currentColor","#000000":"currentColor"}}=r,c=f(t||"Icon",o,n),l=pe(e);return l=me(l,a),ue(l)&&(l=ge(l,c)),l=fe(l,s),{code:ve(l,c,r),componentName:c}}function pe(e){return e.replace(/<\?xml[^>]*\?>\s*/,"").replace(/<!--[\s\S]*?-->/g,"").replace(/xmlns="[^"]*"/g,"").trim()}function me(e,r){let t=e;for(let[o,n]of Object.entries(r)){let s=new RegExp(o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"g");t=t.replace(s,n);}return t}function ue(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function ge(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}function fe(e,r){return r?e.replace("<svg",'<svg :class="className" :style="style" v-bind="$attrs"'):e}function ve(e,r,t){let{typescript:o,compositionApi:n,props:s}=t,c=`<script${o?' lang="ts"':""}${n?" setup":""}>`;n?s&&(c+=`
2
2
  interface Props {
3
3
  className?: string;
4
4
  style?: Record<string, any>;
@@ -16,8 +16,5 @@ export default {
16
16
  `,c+="</script>";let l=`
17
17
  <template>
18
18
  ${e}
19
- </template>`;return [c,l,`
20
- <style scoped>
21
- /* Component styles */
22
- </style>`].filter(Boolean).join(`
23
- `)}var S=class extends g{async convert(r,t={}){try{let o=await this.processSvg(r,t),{code:n,componentName:s}=T(o,t),a=this.generateFilename(s,"vue",t.typescript??!0);return {code:n,filename:a,componentName:s}}catch(o){throw new Error(`Failed to convert SVG to Vue: ${o}`)}}};async function ve(e,r={}){return new S().convert(e,r)}w();Object.defineProperty(exports,"pascalCase",{enumerable:true,get:function(){return u__default.default}});exports.convertToReact=ce;exports.convertToVue=ve;exports.createSvgoConfig=J;exports.formatComponentName=f;exports.optimizeSvg=C;exports.readSvgDirectory=x;exports.readSvgFile=O;exports.sanitizeComponentName=W;exports.svgToComponentName=K;exports.writeComponentFile=D;exports.writeSvgFile=P;
19
+ </template>`;return [c,l].filter(Boolean).join(`
20
+ `)}var F=class extends g{async convert(r,t={}){try{let o=await this.processSvg(r,t),{code:n,componentName:s}=E(o,t),a=this.generateFilename(s,"vue",t.typescript??!0);return {code:n,filename:a,componentName:s}}catch(o){throw new Error(`Failed to convert SVG to Vue: ${o}`)}}};async function de(e,r={}){return new F().convert(e,r)}h();exports.convertToReact=le;exports.convertToVue=de;exports.createSvgoConfig=J;exports.formatComponentName=f;exports.optimizeSvg=C;exports.pascalCase=u;exports.readSvgDirectory=x;exports.readSvgFile=O;exports.sanitizeComponentName=W;exports.svgToComponentName=K;exports.writeComponentFile=A;exports.writeSvgFile=P;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import {dirname,join,extname}from'path';import'url';import {readFile,writeFile,readdir,stat,mkdir}from'fs/promises';import {existsSync}from'fs';import {transform}from'@svgr/core';import {optimize}from'svgo';import g from'just-pascal-case';export{default as pascalCase}from'just-pascal-case';var C=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var N=(e,r)=>()=>(e&&(r=e(e=0)),r);var q=(e,r)=>{for(var t in r)C(e,t,{get:r[t],enumerable:true});},Z=(e,r,t,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of _(r))!I.call(e,n)&&n!==t&&C(e,n,{get:()=>r[n],enumerable:!(o=L(r,n))||o.enumerable});return e};var H=e=>Z(C({},"__esModule",{value:true}),e);var i=N(()=>{});var A={};q(A,{ensureDirectoryExists:()=>w,getComponentFilename:()=>ae,getFileExtension:()=>ie,readSvgDirectory:()=>h,readSvgFile:()=>O,writeComponentFile:()=>T,writeSvgFile:()=>D});async function O(e){try{return await readFile(e,"utf-8")}catch(r){throw new Error(`Failed to read SVG file: ${e}. ${r}`)}}async function D(e,r){try{await w(dirname(e)),await writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write SVG file: ${e}. ${t}`)}}async function T(e,r){try{await w(dirname(e)),await writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write component file: ${e}. ${t}`)}}async function h(e,r=false){try{let t=await readdir(e),o=[];for(let n of t){let s=join(e,n),a=await stat(s);if(a.isDirectory()&&r){let p=await h(s,r);o.push(...p);}else a.isFile()&&extname(n).toLowerCase()===".svg"&&o.push(s);}return o}catch(t){throw new Error(`Failed to read directory: ${e}. ${t}`)}}async function w(e){existsSync(e)||await mkdir(e,{recursive:true});}function ie(e,r=true){return e==="react"?r?".tsx":".jsx":".vue"}function ae(e,r,t){return `${r}${t}`}var y=N(()=>{i();});i();i();i();i();var K={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:false,removeDesc:false,removeUselessStrokeAndFill:false,convertColors:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"removeDimensions","cleanupNumericValues"]};function x(e,r=K){try{return optimize(e,r).data}catch(t){throw new Error(`Failed to optimize SVG: ${t}`)}}function M(e){let r=[{name:"preset-default",params:{overrides:{removeViewBox:!e.removeViewBox,removeTitle:!e.removeTitle,removeDesc:!e.removeDesc,removeUselessStrokeAndFill:!e.preserveClasses,convertColors:e.preserveColors?false:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"cleanupNumericValues"];return e.removeDimensions!==false&&r.push("removeDimensions"),{plugins:r}}i();function Q(e){let r=e.replace(/\.svg$/i,"");return r=W(r),g(r)}function W(e){let r=e.toLowerCase().replace(/[^a-zA-Z0-9]/g," ");return r=r.replace(/\s+/g," ").trim(),r}function X(e){return g(e.replace(/[^a-zA-Z0-9]/g," "))}function v(e,r,t){let o=r?g(r):"",n=t?g(t):"",s=g(e);return `${o}${s}${n}`}var f=class{async processSvg(r,t){let{optimize:o=true}=t;return o?x(r):r}generateComponentName(r){let{name:t,prefix:o,suffix:n}=r;return v(t||"Icon",o,n)}generateFilename(r,t,o=true){try{let{getFileExtension:n,getComponentFilename:s}=(y(),H(A)),a=n(t,o);return s("icon.svg",r,a)}catch{return `${r}${{react:o?".tsx":".jsx",vue:".vue"}[t]}`}}};i();function E(e){let{typescript:r=true,memo:t=true,ref:o=true,titleProp:n=true,descProp:s=true,icon:a=true,dimensions:p=false,replaceAttrValues:c={"#000":"currentColor","#000000":"currentColor"},svgProps:l={},expandProps:d=false,nativeProps:G=true,ariaLabelledBy:U=false,ariaHidden:j=false,role:k="img"}=e,V={typescript:r,memo:t,ref:o,titleProp:n,descProp:s,icon:a,dimensions:p,expandProps:d,svgProps:{className:"{className}",...o&&{ref:"{ref}"},...G&&{width:"{width}",height:"{height}",style:"{style}"},...U&&n&&s&&{"aria-labelledby":"{titleId} {descId}"},...j&&{"aria-hidden":"true"},role:k,...l},replaceAttrValues:c,plugins:["@svgr/plugin-svgo","@svgr/plugin-jsx","@svgr/plugin-prettier"]};return V.svgoConfig={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:!n,removeDesc:!s,removeUselessStrokeAndFill:false,removeUnusedNS:false,removeUselessDefs:false,convertShapeToPath:false,mergePaths:false,convertColors:false}}},...a&&!p?[{name:"removeAttrs",params:{attrs:["width","height"]}}]:[],...a?["cleanupNumericValues"]:[]]},V}function z(e,r,t){let o=e;return $(o)&&(o=F(o,r)),o}function $(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function F(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}var S=class extends f{async convert(r,t={}){try{let o=this.generateComponentName(t),n=await this.processSvg(r,t);$(n)&&(n=F(n,o));let s=E(t),a=await transform(n,s,{componentName:o}),p=z(a,o,t),c=this.generateFilename(o,"react",t.typescript??!0);return {code:p,filename:c,componentName:o}}catch(o){throw new Error(`Failed to convert SVG to React: ${o}`)}}};async function le(e,r={}){return new S().convert(e,r)}i();i();function B(e,r){let{name:t,prefix:o,suffix:n,props:s=true,replaceAttrValues:a={"#000":"currentColor","#000000":"currentColor"}}=r,c=v(t||"Icon",o,n),l=pe(e);return l=me(l,a),ue(l)&&(l=ge(l,c)),l=fe(l,s),{code:ve(l,c,r),componentName:c}}function pe(e){return e.replace(/<\?xml[^>]*\?>\s*/,"").replace(/<!--[\s\S]*?-->/g,"").replace(/xmlns="[^"]*"/g,"").trim()}function me(e,r){let t=e;for(let[o,n]of Object.entries(r)){let s=new RegExp(o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"g");t=t.replace(s,n);}return t}function ue(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function ge(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}function fe(e,r){return r?e.replace("<svg",'<svg :class="className" :style="style" v-bind="$attrs"'):e}function ve(e,r,t){let{typescript:o,compositionApi:n,props:s}=t,c=`<script${o?' lang="ts"':""}${n?" setup":""}>`;n?s&&(c+=`
1
+ import {dirname,join,extname}from'path';import'url';import {readFile,writeFile,readdir,stat,mkdir}from'fs/promises';import {existsSync}from'fs';import {transform}from'@svgr/core';import {optimize}from'svgo';var d=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var N=(e,r)=>()=>(e&&(r=e(e=0)),r);var q=(e,r)=>{for(var t in r)d(e,t,{get:r[t],enumerable:true});},Z=(e,r,t,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of _(r))!I.call(e,n)&&n!==t&&d(e,n,{get:()=>r[n],enumerable:!(o=k(r,n))||o.enumerable});return e};var H=e=>Z(d({},"__esModule",{value:true}),e);var i=N(()=>{});var z={};q(z,{ensureDir:()=>ie,ensureDirectoryExists:()=>C,getComponentFilename:()=>ce,getFileExtension:()=>ae,readSvgDirectory:()=>h,readSvgFile:()=>O,writeComponentFile:()=>D,writeSvgFile:()=>A});async function O(e){try{return await readFile(e,"utf-8")}catch(r){throw new Error(`Failed to read SVG file: ${e}. ${r}`)}}async function A(e,r){try{await C(dirname(e)),await writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write SVG file: ${e}. ${t}`)}}async function D(e,r){try{await C(dirname(e)),await writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write component file: ${e}. ${t}`)}}async function h(e,r=false){try{let t=await readdir(e),o=[];for(let n of t){let s=join(e,n),a=await stat(s);if(a.isDirectory()&&r){let u=await h(s,r);o.push(...u);}else a.isFile()&&extname(n).toLowerCase()===".svg"&&o.push(s);}return o}catch(t){throw new Error(`Failed to read directory: ${e}. ${t}`)}}async function C(e){existsSync(e)||await mkdir(e,{recursive:true});}function ae(e,r=true){return e==="react"?r?".tsx":".jsx":".vue"}function ce(e,r,t){return `${r}${t}`}var ie,w=N(()=>{i();ie=C;});i();i();i();i();var K={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:false,removeDesc:false,removeUselessStrokeAndFill:false,convertColors:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"removeDimensions","cleanupNumericValues"]};function x(e,r=K){try{return optimize(e,r).data}catch(t){throw new Error(`Failed to optimize SVG: ${t}`)}}function M(e){let r=[{name:"preset-default",params:{overrides:{removeViewBox:!e.removeViewBox,removeTitle:!e.removeTitle,removeDesc:!e.removeDesc,removeUselessStrokeAndFill:!e.preserveClasses,convertColors:e.preserveColors?false:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"cleanupNumericValues"];return e.removeDimensions!==false&&r.push("removeDimensions"),{plugins:r}}i();i();var g=e=>e.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*[a-z]*|[A-Z]|[0-9]+[a-z]*/g)?.map(r=>r.charAt(0).toUpperCase()+r.slice(1).toLowerCase()).join("")||"";function Q(e){let r=e.replace(/\.svg$/i,"");return r=W(r),g(r)}function W(e){let r=e.toLowerCase().replace(/[^a-zA-Z0-9]/g," ");return r=r.replace(/\s+/g," ").trim(),r}function X(e){return g(e.replace(/[^a-zA-Z0-9]/g," "))}function v(e,r,t){let o=r?g(r):"",n=t?g(t):"",s=g(e);return `${o}${s}${n}`}var f=class{async processSvg(r,t){let{optimize:o=true}=t;return o?x(r):r}generateComponentName(r){let{name:t,prefix:o,suffix:n}=r;return v(t||"Icon",o,n)}generateFilename(r,t,o=true){try{let{getFileExtension:n,getComponentFilename:s}=(w(),H(z)),a=n(t,o);return s("icon.svg",r,a)}catch{return `${r}${{react:o?".tsx":".jsx",vue:".vue"}[t]}`}}};i();function T(e){let{typescript:r=true,memo:t=true,ref:o=true,titleProp:n=true,descProp:s=true,icon:a=true,dimensions:u=false,replaceAttrValues:c={"#000":"currentColor","#000000":"currentColor"},svgProps:l={},expandProps:b=false,nativeProps:U=true,ariaLabelledBy:G=false,ariaHidden:L=false,role:j="img"}=e,V={typescript:r,memo:t,ref:o,titleProp:n,descProp:s,icon:a,dimensions:u,expandProps:b,svgProps:{className:"{className}",...o&&{ref:"{ref}"},...U&&{width:"{width}",height:"{height}",style:"{style}"},...G&&n&&s&&{"aria-labelledby":"{titleId} {descId}"},...L&&{"aria-hidden":"true"},role:j,...l},replaceAttrValues:c,plugins:["@svgr/plugin-svgo","@svgr/plugin-jsx","@svgr/plugin-prettier"]};return V.svgoConfig={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:!n,removeDesc:!s,removeUselessStrokeAndFill:false,removeUnusedNS:false,removeUselessDefs:false,convertShapeToPath:false,mergePaths:false,convertColors:false}}},...a&&!u?[{name:"removeAttrs",params:{attrs:["width","height"]}}]:[],...a?["cleanupNumericValues"]:[]]},V}function E(e,r,t){let o=e;return $(o)&&(o=y(o,r)),o}function $(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function y(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}var F=class extends f{async convert(r,t={}){try{let o=this.generateComponentName(t),n=await this.processSvg(r,t);$(n)&&(n=y(n,o));let s=T(t),a=await transform(n,s,{componentName:o}),u=E(a,o,t),c=this.generateFilename(o,"react",t.typescript??!0);return {code:u,filename:c,componentName:o}}catch(o){throw new Error(`Failed to convert SVG to React: ${o}`)}}};async function pe(e,r={}){return new F().convert(e,r)}i();i();function B(e,r){let{name:t,prefix:o,suffix:n,props:s=true,replaceAttrValues:a={"#000":"currentColor","#000000":"currentColor"}}=r,c=v(t||"Icon",o,n),l=me(e);return l=ue(l,a),ge(l)&&(l=fe(l,c)),l=ve(l,s),{code:Ce(l,c,r),componentName:c}}function me(e){return e.replace(/<\?xml[^>]*\?>\s*/,"").replace(/<!--[\s\S]*?-->/g,"").replace(/xmlns="[^"]*"/g,"").trim()}function ue(e,r){let t=e;for(let[o,n]of Object.entries(r)){let s=new RegExp(o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"g");t=t.replace(s,n);}return t}function ge(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function fe(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}function ve(e,r){return r?e.replace("<svg",'<svg :class="className" :style="style" v-bind="$attrs"'):e}function Ce(e,r,t){let{typescript:o,compositionApi:n,props:s}=t,c=`<script${o?' lang="ts"':""}${n?" setup":""}>`;n?s&&(c+=`
2
2
  interface Props {
3
3
  className?: string;
4
4
  style?: Record<string, any>;
@@ -16,8 +16,5 @@ export default {
16
16
  `,c+="</script>";let l=`
17
17
  <template>
18
18
  ${e}
19
- </template>`;return [c,l,`
20
- <style scoped>
21
- /* Component styles */
22
- </style>`].filter(Boolean).join(`
23
- `)}var b=class extends f{async convert(r,t={}){try{let o=await this.processSvg(r,t),{code:n,componentName:s}=B(o,t),a=this.generateFilename(s,"vue",t.typescript??!0);return {code:n,filename:a,componentName:s}}catch(o){throw new Error(`Failed to convert SVG to Vue: ${o}`)}}};async function de(e,r={}){return new b().convert(e,r)}y();export{le as convertToReact,de as convertToVue,M as createSvgoConfig,v as formatComponentName,x as optimizeSvg,h as readSvgDirectory,O as readSvgFile,X as sanitizeComponentName,Q as svgToComponentName,T as writeComponentFile,D as writeSvgFile};
19
+ </template>`;return [c,l].filter(Boolean).join(`
20
+ `)}var S=class extends f{async convert(r,t={}){try{let o=await this.processSvg(r,t),{code:n,componentName:s}=B(o,t),a=this.generateFilename(s,"vue",t.typescript??!0);return {code:n,filename:a,componentName:s}}catch(o){throw new Error(`Failed to convert SVG to Vue: ${o}`)}}};async function de(e,r={}){return new S().convert(e,r)}w();export{pe as convertToReact,de as convertToVue,M as createSvgoConfig,v as formatComponentName,x as optimizeSvg,g as pascalCase,h as readSvgDirectory,O as readSvgFile,X as sanitizeComponentName,Q as svgToComponentName,D as writeComponentFile,A as writeSvgFile};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svgfusion",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "A powerful CLI tool and library that converts SVG files into production-ready React and Vue 3 components with TypeScript support and automatic optimization.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -90,13 +90,13 @@
90
90
  "chalk": "^5.0.0",
91
91
  "commander": "^11.0.0",
92
92
  "figlet": "^1.8.1",
93
- "just-pascal-case": "^3.2.0",
94
93
  "ora": "^7.0.0",
95
94
  "svgo": "^3.0.0"
96
95
  },
97
96
  "devDependencies": {
98
97
  "@commitlint/cli": "^17.8.1",
99
98
  "@commitlint/config-conventional": "^17.8.1",
99
+ "@jest/globals": "^30.0.4",
100
100
  "@semantic-release/changelog": "^6.0.3",
101
101
  "@semantic-release/git": "^10.0.1",
102
102
  "@semantic-release/github": "^11.0.3",