svgfusion 1.0.0-beta.2

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/dist/index.mjs ADDED
@@ -0,0 +1,374 @@
1
+ // src/converters/react.ts
2
+ import { transform } from "@svgr/core";
3
+
4
+ // src/utils/svgo.ts
5
+ import { optimize } from "svgo";
6
+ var defaultConfig = {
7
+ plugins: [
8
+ {
9
+ name: "preset-default",
10
+ params: {
11
+ overrides: {
12
+ removeViewBox: false,
13
+ removeTitle: false,
14
+ removeDesc: false,
15
+ removeUselessStrokeAndFill: false,
16
+ convertColors: {
17
+ currentColor: true,
18
+ names2hex: true,
19
+ rgb2hex: true,
20
+ shorthex: true,
21
+ // cspell:disable-line
22
+ shortname: true
23
+ }
24
+ }
25
+ }
26
+ },
27
+ "removeDimensions",
28
+ "cleanupNumericValues"
29
+ ]
30
+ };
31
+ function optimizeSvg(svgContent, config = defaultConfig) {
32
+ try {
33
+ const result = optimize(svgContent, config);
34
+ return result.data;
35
+ } catch (error) {
36
+ throw new Error(`Failed to optimize SVG: ${error}`);
37
+ }
38
+ }
39
+ function createSvgoConfig(options) {
40
+ const plugins = [
41
+ {
42
+ name: "preset-default",
43
+ params: {
44
+ overrides: {
45
+ removeViewBox: !options.removeViewBox,
46
+ removeTitle: !options.removeTitle,
47
+ removeDesc: !options.removeDesc,
48
+ removeUselessStrokeAndFill: !options.preserveClasses,
49
+ convertColors: options.preserveColors ? false : {
50
+ currentColor: true,
51
+ names2hex: true,
52
+ rgb2hex: true,
53
+ shorthex: true,
54
+ // cspell:disable-line
55
+ shortname: true
56
+ }
57
+ }
58
+ }
59
+ },
60
+ "cleanupNumericValues"
61
+ ];
62
+ if (options.removeDimensions !== false) {
63
+ plugins.push("removeDimensions");
64
+ }
65
+ return { plugins };
66
+ }
67
+
68
+ // src/utils/name.ts
69
+ function svgToComponentName(filename) {
70
+ let baseName = filename.replace(/\.svg$/i, "");
71
+ baseName = processComplexFilename(baseName);
72
+ return pascalCase(baseName);
73
+ }
74
+ function processComplexFilename(filename) {
75
+ let processed = filename;
76
+ const simpleMatch = processed.match(/^([^,]+),\s*Type=([^,]+)/i);
77
+ if (simpleMatch) {
78
+ processed = `${simpleMatch[1]} ${simpleMatch[2]}`;
79
+ return processed;
80
+ }
81
+ const sizeMatch = processed.match(/Size=(\w+)/i);
82
+ const colorMatch = processed.match(/Color=(\w+)/i);
83
+ const typeMatch = processed.match(/Type=(\w+)/i);
84
+ if (sizeMatch || colorMatch || typeMatch) {
85
+ const parts = [];
86
+ if (typeMatch)
87
+ parts.push(typeMatch[1]);
88
+ if (colorMatch)
89
+ parts.push(colorMatch[1]);
90
+ if (sizeMatch)
91
+ parts.push(sizeMatch[1]);
92
+ if (parts.length > 0) {
93
+ processed = parts.join(" ");
94
+ return processed;
95
+ }
96
+ }
97
+ processed = processed.replace(/\b\w+=\w+\b/g, "").replace(/,\s*/g, " ").replace(/[=]/g, " ").replace(/\s+/g, " ").trim();
98
+ if (!processed || processed.length < 2) {
99
+ processed = filename;
100
+ }
101
+ return processed;
102
+ }
103
+ function sanitizeComponentName(name) {
104
+ return pascalCase(name.replace(/[^a-zA-Z0-9]/g, " "));
105
+ }
106
+ function pascalCase(str) {
107
+ return str.replace(/[^a-zA-Z0-9\s-_]/g, " ").split(/[\s-_]+/).filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
108
+ }
109
+ function formatComponentName(name, prefix, suffix) {
110
+ const prefixPart = prefix ? pascalCase(prefix) : "";
111
+ const suffixPart = suffix ? pascalCase(suffix) : "";
112
+ const baseName = pascalCase(name);
113
+ return `${prefixPart}${baseName}${suffixPart}`;
114
+ }
115
+
116
+ // src/utils/files.ts
117
+ import { readFile, writeFile, readdir, stat, mkdir } from "fs/promises";
118
+ import { join, dirname, extname } from "path";
119
+ import { existsSync } from "fs";
120
+ async function readSvgFile(filePath) {
121
+ try {
122
+ const content = await readFile(filePath, "utf-8");
123
+ return content;
124
+ } catch (error) {
125
+ throw new Error(`Failed to read SVG file: ${filePath}. ${error}`);
126
+ }
127
+ }
128
+ async function writeSvgFile(filePath, content) {
129
+ try {
130
+ await ensureDirectoryExists(dirname(filePath));
131
+ await writeFile(filePath, content, "utf-8");
132
+ } catch (error) {
133
+ throw new Error(`Failed to write SVG file: ${filePath}. ${error}`);
134
+ }
135
+ }
136
+ async function writeComponentFile(filePath, content) {
137
+ try {
138
+ await ensureDirectoryExists(dirname(filePath));
139
+ await writeFile(filePath, content, "utf-8");
140
+ } catch (error) {
141
+ throw new Error(`Failed to write component file: ${filePath}. ${error}`);
142
+ }
143
+ }
144
+ async function readSvgDirectory(dirPath, recursive = false) {
145
+ try {
146
+ const files = await readdir(dirPath);
147
+ const svgFiles = [];
148
+ for (const file of files) {
149
+ const filePath = join(dirPath, file);
150
+ const fileStat = await stat(filePath);
151
+ if (fileStat.isDirectory() && recursive) {
152
+ const nestedFiles = await readSvgDirectory(filePath, recursive);
153
+ svgFiles.push(...nestedFiles);
154
+ } else if (fileStat.isFile() && extname(file).toLowerCase() === ".svg") {
155
+ svgFiles.push(filePath);
156
+ }
157
+ }
158
+ return svgFiles;
159
+ } catch (error) {
160
+ throw new Error(`Failed to read directory: ${dirPath}. ${error}`);
161
+ }
162
+ }
163
+ async function ensureDirectoryExists(dirPath) {
164
+ if (!existsSync(dirPath)) {
165
+ await mkdir(dirPath, { recursive: true });
166
+ }
167
+ }
168
+ function getFileExtension(framework, typescript = true) {
169
+ if (framework === "react") {
170
+ return typescript ? ".tsx" : ".jsx";
171
+ } else {
172
+ return typescript ? ".vue" : ".vue";
173
+ }
174
+ }
175
+ function getComponentFilename(_svgFilename, componentName, extension) {
176
+ return `${componentName}${extension}`;
177
+ }
178
+
179
+ // src/converters/react.ts
180
+ async function convertToReact(svgContent, options = {}) {
181
+ const {
182
+ name,
183
+ prefix,
184
+ suffix,
185
+ optimize: optimize2 = true,
186
+ typescript = true,
187
+ memo = true,
188
+ ref = true,
189
+ titleProp = true,
190
+ descProp = true
191
+ } = options;
192
+ try {
193
+ let processedSvg = svgContent;
194
+ if (optimize2) {
195
+ processedSvg = optimizeSvg(svgContent);
196
+ }
197
+ const baseName = name || "icon";
198
+ const componentName = formatComponentName(baseName, prefix, suffix);
199
+ const svgrOptions = {
200
+ typescript,
201
+ memo,
202
+ ref,
203
+ titleProp,
204
+ descProp,
205
+ svgProps: {
206
+ className: "{className}"
207
+ }
208
+ };
209
+ let result = await transform(processedSvg, svgrOptions, {
210
+ componentName
211
+ });
212
+ const typeImports = typescript ? `import { SVGProps } from 'react';
213
+ ` : "";
214
+ const memoImport = memo ? `import { memo } from 'react';
215
+ ` : "";
216
+ const refImport = ref ? `import { forwardRef } from 'react';
217
+ ` : "";
218
+ const exports = `
219
+ export default ${componentName};
220
+ export { ${componentName} };`;
221
+ const propsType = typescript ? `SVGProps<SVGSVGElement> & { className?: string; }` : "";
222
+ const componentProps = typescript ? `props: ${propsType}` : "props";
223
+ const componentFunc = memo ? `memo(` : "";
224
+ const refWrapper = ref ? `forwardRef<SVGSVGElement, ${propsType}>(` : "";
225
+ const closingWrappers = `${ref ? ")" : ""}${memo ? ")" : ""}`;
226
+ result = `${typeImports}${memoImport}${refImport}
227
+ const ${componentName} = ${componentFunc}${refWrapper}(${componentProps}) => {
228
+ return ${result};
229
+ }${closingWrappers};
230
+
231
+ ${componentName}.displayName = "${componentName}";
232
+ ${exports}`;
233
+ const extension = getFileExtension("react", typescript);
234
+ const filename = getComponentFilename("icon.svg", componentName, extension);
235
+ return {
236
+ code: result,
237
+ filename,
238
+ componentName
239
+ };
240
+ } catch (error) {
241
+ throw new Error(`Failed to convert SVG to React: ${error}`);
242
+ }
243
+ }
244
+
245
+ // src/converters/vue.ts
246
+ function convertToVue(svgContent, options = {}) {
247
+ const {
248
+ name,
249
+ prefix,
250
+ suffix,
251
+ optimize: optimize2 = true,
252
+ typescript = true,
253
+ compositionApi: _compositionApi = true,
254
+ // eslint-disable-line @typescript-eslint/no-unused-vars
255
+ scriptSetup = true
256
+ } = options;
257
+ try {
258
+ let processedSvg = svgContent;
259
+ if (optimize2) {
260
+ processedSvg = optimizeSvg(svgContent);
261
+ }
262
+ const baseName = name || "icon";
263
+ const componentName = formatComponentName(baseName, prefix, suffix);
264
+ const cleanedSvg = processedSvg.replace(/<\?xml[^>]*\?>\s*/, "").replace(/xmlns="[^"]*"/g, "").replace(/width="[^"]*"/g, "").replace(/height="[^"]*"/g, "").replace(/<svg/, '<svg v-bind="$attrs"').replace(/class="([^"]*)"/g, 'class="$1"').replace(/currentColor/g, "currentColor");
265
+ const scriptTag = scriptSetup ? generateScriptSetup(typescript, componentName) : generateCompositionScript(componentName, typescript);
266
+ const template = `<template>
267
+ ${cleanedSvg}
268
+ </template>`;
269
+ const style = `<style scoped>
270
+ /* Add component-specific styles here */
271
+ </style>`;
272
+ const vueComponent = `${scriptTag}
273
+
274
+ ${template}
275
+
276
+ ${style}`;
277
+ const extension = getFileExtension("vue", typescript);
278
+ const filename = getComponentFilename("icon.svg", componentName, extension);
279
+ return {
280
+ code: vueComponent,
281
+ filename,
282
+ componentName
283
+ };
284
+ } catch (error) {
285
+ throw new Error(`Failed to convert SVG to Vue: ${error}`);
286
+ }
287
+ }
288
+ function generateScriptSetup(typescript, componentName) {
289
+ const lang = typescript ? ' lang="ts"' : "";
290
+ const propsType = typescript ? `
291
+ interface Props {
292
+ class?: string;
293
+ style?: string | Record<string, any>;
294
+ }` : "";
295
+ const propsDefinition = typescript ? `
296
+ const props = withDefaults(defineProps<Props>(), {
297
+ class: '',
298
+ style: undefined,
299
+ });` : `
300
+ const props = withDefaults(defineProps(), {
301
+ class: '',
302
+ style: undefined,
303
+ });`;
304
+ return `<script setup${lang}>${propsType}${propsDefinition}
305
+
306
+ // Component name for debugging
307
+ const __name = '${componentName}';
308
+ </script>`;
309
+ }
310
+ function generateCompositionScript(componentName, typescript) {
311
+ const lang = typescript ? ' lang="ts"' : "";
312
+ const propsType = typescript ? `
313
+ interface Props {
314
+ class?: string;
315
+ style?: string | Record<string, any>;
316
+ }` : "";
317
+ const exportStatement = typescript ? `
318
+ const ${componentName} = defineComponent({
319
+ name: '${componentName}',
320
+ props: {
321
+ class: {
322
+ type: String,
323
+ default: '',
324
+ },
325
+ style: {
326
+ type: [String, Object],
327
+ default: undefined,
328
+ },
329
+ },
330
+ setup(props: Props) {
331
+ return {};
332
+ },
333
+ });
334
+
335
+ export default ${componentName};
336
+ export { ${componentName} };` : `
337
+ const ${componentName} = defineComponent({
338
+ name: '${componentName}',
339
+ props: {
340
+ class: {
341
+ type: String,
342
+ default: '',
343
+ },
344
+ style: {
345
+ type: [String, Object],
346
+ default: undefined,
347
+ },
348
+ },
349
+ setup(props) {
350
+ return {};
351
+ },
352
+ });
353
+
354
+ export default ${componentName};
355
+ export { ${componentName} };`;
356
+ return `<script${lang}>
357
+ import { defineComponent } from 'vue';${propsType}${exportStatement}
358
+ </script>`;
359
+ }
360
+ export {
361
+ convertToReact,
362
+ convertToVue,
363
+ createSvgoConfig,
364
+ formatComponentName,
365
+ optimizeSvg,
366
+ pascalCase,
367
+ readSvgDirectory,
368
+ readSvgFile,
369
+ sanitizeComponentName,
370
+ svgToComponentName,
371
+ writeComponentFile,
372
+ writeSvgFile
373
+ };
374
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/converters/react.ts","../src/utils/svgo.ts","../src/utils/name.ts","../src/utils/files.ts","../src/converters/vue.ts"],"sourcesContent":["import { transform } from '@svgr/core';\nimport { ReactConversionOptions, ConversionResult } from '../types/index.js';\nimport { optimizeSvg } from '../utils/svgo.js';\nimport { formatComponentName } from '../utils/name.js';\nimport { getFileExtension, getComponentFilename } from '../utils/files.js';\n\n/**\n * Convert SVG to React component\n * @param svgContent - SVG content string\n * @param options - Conversion options\n * @returns Conversion result with React component code\n */\nexport async function convertToReact(\n svgContent: string,\n options: ReactConversionOptions = {}\n): Promise<ConversionResult> {\n const {\n name,\n prefix,\n suffix,\n optimize = true,\n typescript = true,\n memo = true,\n ref = true,\n titleProp = true,\n descProp = true,\n } = options;\n\n try {\n // Optimize SVG if requested\n let processedSvg = svgContent;\n if (optimize) {\n processedSvg = optimizeSvg(svgContent);\n }\n\n // Generate component name\n const baseName = name || 'icon';\n const componentName = formatComponentName(baseName, prefix, suffix);\n\n // SVGR transformation options\n const svgrOptions = {\n typescript,\n memo,\n ref,\n titleProp,\n descProp,\n svgProps: {\n className: '{className}',\n },\n };\n\n // Transform SVG to React component\n let result = await transform(processedSvg, svgrOptions, {\n componentName,\n });\n\n // Add proper exports to the result\n const typeImports = typescript ? `import { SVGProps } from 'react';\\n` : '';\n\n const memoImport = memo ? `import { memo } from 'react';\\n` : '';\n const refImport = ref ? `import { forwardRef } from 'react';\\n` : '';\n\n const exports = `\\nexport default ${componentName};\\nexport { ${componentName} };`;\n\n // Wrap the SVGR result with proper component structure\n const propsType = typescript\n ? `SVGProps<SVGSVGElement> & { className?: string; }`\n : '';\n\n const componentProps = typescript ? `props: ${propsType}` : 'props';\n\n const componentFunc = memo ? `memo(` : '';\n const refWrapper = ref ? `forwardRef<SVGSVGElement, ${propsType}>(` : '';\n const closingWrappers = `${ref ? ')' : ''}${memo ? ')' : ''}`;\n\n result = `${typeImports}${memoImport}${refImport}\nconst ${componentName} = ${componentFunc}${refWrapper}(${componentProps}) => {\n return ${result};\n}${closingWrappers};\n\n${componentName}.displayName = \"${componentName}\";\n${exports}`;\n\n // Generate filename\n const extension = getFileExtension('react', typescript);\n const filename = getComponentFilename('icon.svg', componentName, extension);\n\n return {\n code: result,\n filename,\n componentName,\n };\n } catch (error) {\n throw new Error(`Failed to convert SVG to React: ${error}`);\n }\n}\n","import { optimize, Config } from 'svgo';\n\n/**\n * Default SVGO configuration for optimization\n */\nconst defaultConfig: Config = {\n plugins: [\n {\n name: 'preset-default',\n params: {\n overrides: {\n removeViewBox: false,\n removeTitle: false,\n removeDesc: false,\n removeUselessStrokeAndFill: false,\n convertColors: {\n currentColor: true,\n names2hex: true,\n rgb2hex: true,\n shorthex: true, // cspell:disable-line\n shortname: true,\n },\n },\n },\n },\n 'removeDimensions',\n 'cleanupNumericValues',\n ],\n};\n\n/**\n * Optimize SVG content using SVGO\n * @param svgContent - SVG content to optimize\n * @param config - Optional SVGO configuration\n * @returns Optimized SVG content\n */\nexport function optimizeSvg(\n svgContent: string,\n config: Config = defaultConfig\n): string {\n try {\n const result = optimize(svgContent, config);\n return result.data;\n } catch (error) {\n throw new Error(`Failed to optimize SVG: ${error}`);\n }\n}\n\n/**\n * Create custom SVGO configuration\n * @param options - Configuration options\n * @returns SVGO configuration\n */\nexport function createSvgoConfig(options: {\n removeViewBox?: boolean;\n removeDimensions?: boolean;\n removeTitle?: boolean;\n removeDesc?: boolean;\n preserveAspectRatio?: boolean;\n preserveColors?: boolean;\n preserveClasses?: boolean;\n}): Config {\n const plugins = [\n {\n name: 'preset-default',\n params: {\n overrides: {\n removeViewBox: !options.removeViewBox,\n removeTitle: !options.removeTitle,\n removeDesc: !options.removeDesc,\n removeUselessStrokeAndFill: !options.preserveClasses,\n convertColors: options.preserveColors\n ? false\n : {\n currentColor: true,\n names2hex: true,\n rgb2hex: true,\n shorthex: true, // cspell:disable-line\n shortname: true,\n },\n },\n },\n },\n 'cleanupNumericValues',\n ];\n\n if (options.removeDimensions !== false) {\n plugins.push('removeDimensions');\n }\n\n return { plugins: plugins as Config['plugins'] };\n}\n","/**\n * Convert SVG filename to a valid React component name\n * @param filename - The SVG filename\n * @returns PascalCase component name\n */\nexport function svgToComponentName(filename: string): string {\n // Remove file extension\n let baseName = filename.replace(/\\.svg$/i, '');\n\n // Handle complex filenames with metadata\n baseName = processComplexFilename(baseName);\n\n // Convert to PascalCase\n return pascalCase(baseName);\n}\n\n/**\n * Process complex filenames with metadata and special characters\n * @param filename - The filename to process\n * @returns Cleaned filename\n */\nfunction processComplexFilename(filename: string): string {\n // Handle files with metadata like \"User Profile Avatar, Type=Solid\" or \"Size=xl, Color=Brand, Type=Glass\"\n\n let processed = filename;\n\n // Handle simple patterns like \"User Profile Avatar, Type=Solid\" first\n const simpleMatch = processed.match(/^([^,]+),\\s*Type=([^,]+)/i);\n if (simpleMatch) {\n processed = `${simpleMatch[1]} ${simpleMatch[2]}`;\n return processed;\n }\n\n // Check for metadata patterns\n const sizeMatch = processed.match(/Size=(\\w+)/i);\n const colorMatch = processed.match(/Color=(\\w+)/i);\n const typeMatch = processed.match(/Type=(\\w+)/i);\n\n // If it has metadata, construct a meaningful name\n if (sizeMatch || colorMatch || typeMatch) {\n const parts = [];\n if (typeMatch) parts.push(typeMatch[1]);\n if (colorMatch) parts.push(colorMatch[1]);\n if (sizeMatch) parts.push(sizeMatch[1]);\n\n if (parts.length > 0) {\n processed = parts.join(' ');\n return processed;\n }\n }\n\n // If no metadata patterns found, just clean up the filename\n processed = processed\n .replace(/\\b\\w+=\\w+\\b/g, '') // Remove key=value patterns\n .replace(/,\\s*/g, ' ') // Replace commas with spaces\n .replace(/[=]/g, ' ') // Replace equals with spaces\n .replace(/\\s+/g, ' ') // Normalize multiple spaces\n .trim();\n\n // If the result is empty or too short, use original filename as fallback\n if (!processed || processed.length < 2) {\n processed = filename;\n }\n\n return processed;\n}\n\n/**\n * Sanitize component name to ensure it's valid\n * @param name - The component name to sanitize\n * @returns Sanitized component name\n */\nexport function sanitizeComponentName(name: string): string {\n // Remove invalid characters and convert to PascalCase\n return pascalCase(name.replace(/[^a-zA-Z0-9]/g, ' '));\n}\n\n/**\n * Convert a string to PascalCase\n * @param str - The string to convert\n * @returns PascalCase string\n */\nexport function pascalCase(str: string): string {\n return str\n .replace(/[^a-zA-Z0-9\\s-_]/g, ' ') // Remove any symbols, keep spaces, hyphens, underscores\n .split(/[\\s-_]+/) // Split on spaces, hyphens, underscores\n .filter(word => word.length > 0) // Remove empty strings\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join('');\n}\n\n/**\n * Add prefix/suffix to component name\n * @param name - Base component name\n * @param prefix - Optional prefix\n * @param suffix - Optional suffix\n * @returns Component name with prefix/suffix\n */\nexport function formatComponentName(\n name: string,\n prefix?: string,\n suffix?: string\n): string {\n const prefixPart = prefix ? pascalCase(prefix) : '';\n const suffixPart = suffix ? pascalCase(suffix) : '';\n const baseName = pascalCase(name);\n\n return `${prefixPart}${baseName}${suffixPart}`;\n}\n","import { readFile, writeFile, readdir, stat, mkdir } from 'fs/promises';\nimport { join, dirname, extname } from 'path';\nimport { existsSync } from 'fs';\n\n/**\n * Read SVG file content\n * @param filePath - Path to the SVG file\n * @returns SVG content as string\n */\nexport async function readSvgFile(filePath: string): Promise<string> {\n try {\n const content = await readFile(filePath, 'utf-8');\n return content;\n } catch (error) {\n throw new Error(`Failed to read SVG file: ${filePath}. ${error}`);\n }\n}\n\n/**\n * Write SVG file content\n * @param filePath - Path to write the SVG file\n * @param content - SVG content\n */\nexport async function writeSvgFile(\n filePath: string,\n content: string\n): Promise<void> {\n try {\n await ensureDirectoryExists(dirname(filePath));\n await writeFile(filePath, content, 'utf-8');\n } catch (error) {\n throw new Error(`Failed to write SVG file: ${filePath}. ${error}`);\n }\n}\n\n/**\n * Write component file content\n * @param filePath - Path to write the component file\n * @param content - Component content\n */\nexport async function writeComponentFile(\n filePath: string,\n content: string\n): Promise<void> {\n try {\n await ensureDirectoryExists(dirname(filePath));\n await writeFile(filePath, content, 'utf-8');\n } catch (error) {\n throw new Error(`Failed to write component file: ${filePath}. ${error}`);\n }\n}\n\n/**\n * Read all SVG files from a directory\n * @param dirPath - Directory path\n * @param recursive - Whether to read recursively\n * @returns Array of SVG file paths\n */\nexport async function readSvgDirectory(\n dirPath: string,\n recursive = false\n): Promise<string[]> {\n try {\n const files = await readdir(dirPath);\n const svgFiles: string[] = [];\n\n for (const file of files) {\n const filePath = join(dirPath, file);\n const fileStat = await stat(filePath);\n\n if (fileStat.isDirectory() && recursive) {\n const nestedFiles = await readSvgDirectory(filePath, recursive);\n svgFiles.push(...nestedFiles);\n } else if (fileStat.isFile() && extname(file).toLowerCase() === '.svg') {\n svgFiles.push(filePath);\n }\n }\n\n return svgFiles;\n } catch (error) {\n throw new Error(`Failed to read directory: ${dirPath}. ${error}`);\n }\n}\n\n/**\n * Ensure directory exists, create if not\n * @param dirPath - Directory path\n */\nexport async function ensureDirectoryExists(dirPath: string): Promise<void> {\n if (!existsSync(dirPath)) {\n await mkdir(dirPath, { recursive: true });\n }\n}\n\n/**\n * Get file extension for framework\n * @param framework - Target framework\n * @param typescript - Whether to use TypeScript\n * @returns File extension\n */\nexport function getFileExtension(\n framework: 'react' | 'vue',\n typescript = true\n): string {\n if (framework === 'react') {\n return typescript ? '.tsx' : '.jsx';\n } else {\n return typescript ? '.vue' : '.vue';\n }\n}\n\n/**\n * Get component filename from SVG filename\n * @param svgFilename - SVG filename\n * @param componentName - Component name\n * @param extension - File extension\n * @returns Component filename\n */\nexport function getComponentFilename(\n _svgFilename: string,\n componentName: string,\n extension: string\n): string {\n return `${componentName}${extension}`;\n}\n","import { VueConversionOptions, ConversionResult } from '../types/index.js';\nimport { optimizeSvg } from '../utils/svgo.js';\nimport { formatComponentName } from '../utils/name.js';\nimport { getFileExtension, getComponentFilename } from '../utils/files.js';\n\n/**\n * Convert SVG to Vue 3 component\n * @param svgContent - SVG content string\n * @param options - Conversion options\n * @returns Conversion result with Vue component code\n */\nexport function convertToVue(\n svgContent: string,\n options: VueConversionOptions = {}\n): ConversionResult {\n const {\n name,\n prefix,\n suffix,\n optimize = true,\n typescript = true,\n compositionApi: _compositionApi = true, // eslint-disable-line @typescript-eslint/no-unused-vars\n scriptSetup = true,\n } = options;\n\n try {\n // Optimize SVG if requested\n let processedSvg = svgContent;\n if (optimize) {\n processedSvg = optimizeSvg(svgContent);\n }\n\n // Generate component name\n const baseName = name || 'icon';\n const componentName = formatComponentName(baseName, prefix, suffix);\n\n // Clean up SVG - remove XML declaration and add props binding\n const cleanedSvg = processedSvg\n .replace(/<\\?xml[^>]*\\?>\\s*/, '')\n .replace(/xmlns=\"[^\"]*\"/g, '')\n .replace(/width=\"[^\"]*\"/g, '')\n .replace(/height=\"[^\"]*\"/g, '')\n .replace(/<svg/, '<svg v-bind=\"$attrs\"')\n .replace(/class=\"([^\"]*)\"/g, 'class=\"$1\"') // Keep class as is, don't convert to :class\n .replace(/currentColor/g, 'currentColor');\n\n // Generate Vue SFC\n const scriptTag = scriptSetup\n ? generateScriptSetup(typescript, componentName)\n : generateCompositionScript(componentName, typescript);\n\n const template = `<template>\n ${cleanedSvg}\n</template>`;\n\n const style = `<style scoped>\n/* Add component-specific styles here */\n</style>`;\n\n const vueComponent = `${scriptTag}\n\n${template}\n\n${style}`;\n\n // Generate filename\n const extension = getFileExtension('vue', typescript);\n const filename = getComponentFilename('icon.svg', componentName, extension);\n\n return {\n code: vueComponent,\n filename,\n componentName,\n };\n } catch (error) {\n throw new Error(`Failed to convert SVG to Vue: ${error}`);\n }\n}\n\n/**\n * Generate script setup block for Vue 3\n */\nfunction generateScriptSetup(\n typescript: boolean,\n componentName: string\n): string {\n const lang = typescript ? ' lang=\"ts\"' : '';\n const propsType = typescript\n ? `\ninterface Props {\n class?: string;\n style?: string | Record<string, any>;\n}`\n : '';\n\n const propsDefinition = typescript\n ? `\nconst props = withDefaults(defineProps<Props>(), {\n class: '',\n style: undefined,\n});`\n : `\nconst props = withDefaults(defineProps(), {\n class: '',\n style: undefined,\n});`;\n\n return `<script setup${lang}>${propsType}${propsDefinition}\n\n// Component name for debugging\nconst __name = '${componentName}';\n</script>`;\n}\n\n/**\n * Generate composition API script for Vue 3\n */\nfunction generateCompositionScript(\n componentName: string,\n typescript: boolean\n): string {\n const lang = typescript ? ' lang=\"ts\"' : '';\n const propsType = typescript\n ? `\ninterface Props {\n class?: string;\n style?: string | Record<string, any>;\n}`\n : '';\n\n const exportStatement = typescript\n ? `\nconst ${componentName} = defineComponent({\n name: '${componentName}',\n props: {\n class: {\n type: String,\n default: '',\n },\n style: {\n type: [String, Object],\n default: undefined,\n },\n },\n setup(props: Props) {\n return {};\n },\n});\n\nexport default ${componentName};\nexport { ${componentName} };`\n : `\nconst ${componentName} = defineComponent({\n name: '${componentName}',\n props: {\n class: {\n type: String,\n default: '',\n },\n style: {\n type: [String, Object],\n default: undefined,\n },\n },\n setup(props) {\n return {};\n },\n});\n\nexport default ${componentName};\nexport { ${componentName} };`;\n\n return `<script${lang}>\nimport { defineComponent } from 'vue';${propsType}${exportStatement}\n</script>`;\n}\n"],"mappings":"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","names":["optimize","optimize"]}
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "svgfusion",
3
+ "version": "1.0.0-beta.2",
4
+ "description": "Transform SVG files into production-ready React and Vue 3 components with TypeScript support, automatic optimization, and intelligent naming.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "svgfusion": "dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js"
15
+ },
16
+ "./cli": {
17
+ "import": "./dist/cli.mjs",
18
+ "require": "./dist/cli.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "CONTRIBUTING.md",
25
+ "LICENSE"
26
+ ],
27
+ "keywords": [
28
+ "svg",
29
+ "react",
30
+ "vue",
31
+ "vue3",
32
+ "component",
33
+ "converter",
34
+ "cli",
35
+ "jsx",
36
+ "tsx",
37
+ "typescript",
38
+ "svgr",
39
+ "batch",
40
+ "optimization",
41
+ "icon",
42
+ "frontend",
43
+ "build-tool"
44
+ ],
45
+ "author": "SVGFusion Contributors",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/lolvOid/svgfusion.git"
50
+ },
51
+ "homepage": "https://github.com/lolvoid/svgfusion#readme",
52
+ "bugs": {
53
+ "url": "https://github.com/lolvoid/svgfusion/issues"
54
+ },
55
+ "scripts": {
56
+ "build": "tsup",
57
+ "dev": "tsup --watch",
58
+ "test": "vitest",
59
+ "test:coverage": "vitest --coverage",
60
+ "test:watch": "vitest --watch",
61
+ "lint": "eslint src/**/*.ts tests/**/*.ts",
62
+ "lint:fix": "eslint src/**/*.ts tests/**/*.ts --fix",
63
+ "format": "prettier --write src/**/*.ts tests/**/*.ts *.md",
64
+ "format:check": "prettier --check src/**/*.ts tests/**/*.ts *.md",
65
+ "type-check": "tsc --noEmit",
66
+ "clean": "rm -rf dist",
67
+ "prepare": "husky install",
68
+ "prepublishOnly": "npm run clean && npm run build && npm run test",
69
+ "release": "semantic-release",
70
+ "release:dry": "semantic-release --dry-run"
71
+ },
72
+ "dependencies": {
73
+ "@svgr/core": "^8.0.0",
74
+ "chalk": "^5.0.0",
75
+ "commander": "^11.0.0",
76
+ "ora": "^7.0.0",
77
+ "svgo": "^3.0.0"
78
+ },
79
+ "devDependencies": {
80
+ "@commitlint/cli": "^17.8.1",
81
+ "@commitlint/config-conventional": "^17.8.1",
82
+ "@semantic-release/changelog": "^6.0.3",
83
+ "@semantic-release/git": "^10.0.1",
84
+ "@semantic-release/github": "^11.0.3",
85
+ "@semantic-release/npm": "^12.0.2",
86
+ "@types/node": "^20.0.0",
87
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
88
+ "@typescript-eslint/parser": "^6.21.0",
89
+ "@vitest/coverage-v8": "^3.2.4",
90
+ "eslint": "^8.57.0",
91
+ "husky": "^8.0.3",
92
+ "lint-staged": "^15.2.2",
93
+ "prettier": "^2.8.8",
94
+ "semantic-release": "^24.2.6",
95
+ "tsup": "^7.0.0",
96
+ "typescript": "^5.0.0",
97
+ "vitest": "^3.2.4"
98
+ },
99
+ "engines": {
100
+ "node": ">=18.0.0"
101
+ }
102
+ }