svger-cli 3.0.0 → 3.1.1
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/CHANGELOG.md +243 -0
- package/README.md +222 -30
- package/dist/__tests__/fixtures.d.ts +132 -0
- package/dist/__tests__/fixtures.js +228 -0
- package/dist/cli.js +1 -1
- package/dist/integrations/babel.js +30 -11
- package/dist/integrations/rollup.js +29 -10
- package/dist/integrations/vite.js +29 -14
- package/dist/integrations/webpack.d.ts +1 -0
- package/dist/integrations/webpack.js +29 -10
- package/dist/services/svg-service.d.ts +2 -0
- package/dist/services/svg-service.js +25 -9
- package/package.json +5 -2
- package/DEVELOPMENT.md +0 -353
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Fixtures
|
|
3
|
+
* Reusable test data for unit tests
|
|
4
|
+
*/
|
|
5
|
+
export const sampleSVGs = {
|
|
6
|
+
simple: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
7
|
+
<circle cx="12" cy="12" r="10"/>
|
|
8
|
+
</svg>`,
|
|
9
|
+
withFill: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
10
|
+
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
11
|
+
</svg>`,
|
|
12
|
+
complex: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
13
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
|
14
|
+
</svg>`,
|
|
15
|
+
nested: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
16
|
+
<g>
|
|
17
|
+
<circle cx="12" cy="12" r="10"/>
|
|
18
|
+
<path d="M12 2v20"/>
|
|
19
|
+
<path d="M2 12h20"/>
|
|
20
|
+
</g>
|
|
21
|
+
</svg>`,
|
|
22
|
+
multiPath: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
23
|
+
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
24
|
+
<path d="M2 17l10 5 10-5M2 12l10 5 10-5"/>
|
|
25
|
+
</svg>`,
|
|
26
|
+
withGradient: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
27
|
+
<defs>
|
|
28
|
+
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
29
|
+
<stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
|
|
30
|
+
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
|
|
31
|
+
</linearGradient>
|
|
32
|
+
</defs>
|
|
33
|
+
<circle cx="12" cy="12" r="10" fill="url(#grad1)"/>
|
|
34
|
+
</svg>`,
|
|
35
|
+
withText: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
36
|
+
<text x="12" y="12" text-anchor="middle">SVG</text>
|
|
37
|
+
</svg>`,
|
|
38
|
+
accessibility: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-labelledby="iconTitle">
|
|
39
|
+
<title id="iconTitle">Home Icon</title>
|
|
40
|
+
<desc>An icon representing a house</desc>
|
|
41
|
+
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
|
42
|
+
</svg>`,
|
|
43
|
+
animated: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
44
|
+
<circle cx="12" cy="12" r="10">
|
|
45
|
+
<animate attributeName="r" from="10" to="5" dur="1s" repeatCount="indefinite"/>
|
|
46
|
+
</circle>
|
|
47
|
+
</svg>`,
|
|
48
|
+
invalid: `<svg><unclosed`,
|
|
49
|
+
empty: ``,
|
|
50
|
+
malformed: `<invalid>not an svg</invalid>`
|
|
51
|
+
};
|
|
52
|
+
export const sampleConfigs = {
|
|
53
|
+
react: {
|
|
54
|
+
framework: 'react',
|
|
55
|
+
typescript: true,
|
|
56
|
+
naming: 'pascal',
|
|
57
|
+
generateIndex: true
|
|
58
|
+
},
|
|
59
|
+
vue: {
|
|
60
|
+
framework: 'vue',
|
|
61
|
+
typescript: true,
|
|
62
|
+
naming: 'kebab',
|
|
63
|
+
generateIndex: true
|
|
64
|
+
},
|
|
65
|
+
angular: {
|
|
66
|
+
framework: 'angular',
|
|
67
|
+
typescript: true,
|
|
68
|
+
naming: 'kebab',
|
|
69
|
+
generateIndex: true
|
|
70
|
+
},
|
|
71
|
+
minimal: {
|
|
72
|
+
framework: 'react'
|
|
73
|
+
},
|
|
74
|
+
full: {
|
|
75
|
+
source: './icons',
|
|
76
|
+
output: './components',
|
|
77
|
+
framework: 'react',
|
|
78
|
+
typescript: true,
|
|
79
|
+
naming: 'pascal',
|
|
80
|
+
generateIndex: true,
|
|
81
|
+
watch: false,
|
|
82
|
+
clean: true,
|
|
83
|
+
parallel: true,
|
|
84
|
+
verbose: true,
|
|
85
|
+
responsive: {
|
|
86
|
+
mobile: 16,
|
|
87
|
+
tablet: 20,
|
|
88
|
+
desktop: 24
|
|
89
|
+
},
|
|
90
|
+
theme: {
|
|
91
|
+
primary: '#007bff',
|
|
92
|
+
secondary: '#6c757d'
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
export const expectedOutputs = {
|
|
97
|
+
react: {
|
|
98
|
+
js: `import React from 'react';
|
|
99
|
+
|
|
100
|
+
export const TestIcon = (props) => (
|
|
101
|
+
<svg {...props}>
|
|
102
|
+
<circle cx="12" cy="12" r="10"/>
|
|
103
|
+
</svg>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export default TestIcon;`,
|
|
107
|
+
ts: `import React from 'react';
|
|
108
|
+
|
|
109
|
+
interface TestIconProps extends React.SVGProps<SVGSVGElement> {}
|
|
110
|
+
|
|
111
|
+
export const TestIcon: React.FC<TestIconProps> = (props) => (
|
|
112
|
+
<svg {...props}>
|
|
113
|
+
<circle cx="12" cy="12" r="10"/>
|
|
114
|
+
</svg>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
export default TestIcon;`,
|
|
118
|
+
withForwardRef: `import React, { forwardRef } from 'react';
|
|
119
|
+
|
|
120
|
+
interface TestIconProps extends React.SVGProps<SVGSVGElement> {}
|
|
121
|
+
|
|
122
|
+
export const TestIcon = forwardRef<SVGSVGElement, TestIconProps>(
|
|
123
|
+
(props, ref) => (
|
|
124
|
+
<svg ref={ref} {...props}>
|
|
125
|
+
<circle cx="12" cy="12" r="10"/>
|
|
126
|
+
</svg>
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
TestIcon.displayName = 'TestIcon';
|
|
131
|
+
|
|
132
|
+
export default TestIcon;`
|
|
133
|
+
},
|
|
134
|
+
vue: {
|
|
135
|
+
composition: `<template>
|
|
136
|
+
<svg v-bind="$attrs">
|
|
137
|
+
<circle cx="12" cy="12" r="10"/>
|
|
138
|
+
</svg>
|
|
139
|
+
</template>
|
|
140
|
+
|
|
141
|
+
<script setup lang="ts">
|
|
142
|
+
</script>`,
|
|
143
|
+
options: `<template>
|
|
144
|
+
<svg v-bind="$attrs">
|
|
145
|
+
<circle cx="12" cy="12" r="10"/>
|
|
146
|
+
</svg>
|
|
147
|
+
</template>
|
|
148
|
+
|
|
149
|
+
<script lang="ts">
|
|
150
|
+
export default {
|
|
151
|
+
name: 'TestIcon'
|
|
152
|
+
};
|
|
153
|
+
</script>`
|
|
154
|
+
},
|
|
155
|
+
angular: {
|
|
156
|
+
standalone: `import { Component } from '@angular/core';
|
|
157
|
+
|
|
158
|
+
@Component({
|
|
159
|
+
selector: 'app-test-icon',
|
|
160
|
+
standalone: true,
|
|
161
|
+
template: \`
|
|
162
|
+
<svg>
|
|
163
|
+
<circle cx="12" cy="12" r="10"/>
|
|
164
|
+
</svg>
|
|
165
|
+
\`
|
|
166
|
+
})
|
|
167
|
+
export class TestIconComponent {}`
|
|
168
|
+
},
|
|
169
|
+
svelte: `<script lang="ts">
|
|
170
|
+
export let width: number = 24;
|
|
171
|
+
export let height: number = 24;
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
<svg {width} {height}>
|
|
175
|
+
<circle cx="12" cy="12" r="10"/>
|
|
176
|
+
</svg>`,
|
|
177
|
+
indexFile: `export { default as HomeIcon } from './HomeIcon';
|
|
178
|
+
export { default as UserIcon } from './UserIcon';
|
|
179
|
+
export { default as SettingsIcon } from './SettingsIcon';
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* SVG Components Index
|
|
183
|
+
* Generated by svger-cli
|
|
184
|
+
*
|
|
185
|
+
* Import individual components:
|
|
186
|
+
* import { HomeIcon } from './components';
|
|
187
|
+
*
|
|
188
|
+
* Import all components:
|
|
189
|
+
* import * as Icons from './components';
|
|
190
|
+
*/`
|
|
191
|
+
};
|
|
192
|
+
export const mockFileStructure = {
|
|
193
|
+
input: {
|
|
194
|
+
'home.svg': sampleSVGs.simple,
|
|
195
|
+
'user.svg': sampleSVGs.withFill,
|
|
196
|
+
'settings.svg': sampleSVGs.complex,
|
|
197
|
+
'nested/folder/icon.svg': sampleSVGs.nested
|
|
198
|
+
},
|
|
199
|
+
expected: {
|
|
200
|
+
'HomeIcon.tsx': expectedOutputs.react.ts,
|
|
201
|
+
'UserIcon.tsx': expectedOutputs.react.ts,
|
|
202
|
+
'SettingsIcon.tsx': expectedOutputs.react.ts,
|
|
203
|
+
'index.ts': expectedOutputs.indexFile
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
export const frameworks = [
|
|
207
|
+
'react',
|
|
208
|
+
'react-native',
|
|
209
|
+
'vue',
|
|
210
|
+
'angular',
|
|
211
|
+
'svelte',
|
|
212
|
+
'solid',
|
|
213
|
+
'lit',
|
|
214
|
+
'preact',
|
|
215
|
+
'vanilla'
|
|
216
|
+
];
|
|
217
|
+
export const namingConventions = ['pascal', 'camel', 'kebab'];
|
|
218
|
+
export const fileExtensions = {
|
|
219
|
+
react: { js: '.jsx', ts: '.tsx' },
|
|
220
|
+
'react-native': { js: '.jsx', ts: '.tsx' },
|
|
221
|
+
vue: { js: '.vue', ts: '.vue' },
|
|
222
|
+
angular: { js: '.component.ts', ts: '.component.ts' },
|
|
223
|
+
svelte: { js: '.svelte', ts: '.svelte' },
|
|
224
|
+
solid: { js: '.tsx', ts: '.tsx' },
|
|
225
|
+
lit: { js: '.ts', ts: '.ts' },
|
|
226
|
+
preact: { js: '.tsx', ts: '.tsx' },
|
|
227
|
+
vanilla: { js: '.ts', ts: '.ts' }
|
|
228
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ const program = new CLI();
|
|
|
11
11
|
program
|
|
12
12
|
.name('svger-cli')
|
|
13
13
|
.description('Custom SVG to Angular, React, Vue, Svelte, Solid, and other component converter')
|
|
14
|
-
.version('
|
|
14
|
+
.version('3.0.0');
|
|
15
15
|
// -------- Build Command --------
|
|
16
16
|
/**
|
|
17
17
|
* Build all SVGs from a source folder to an output folder.
|
|
@@ -85,9 +85,9 @@ async function processAllSVGs(options, processedFiles) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
// Generate index file if enabled
|
|
88
|
-
if (options.generateIndex
|
|
88
|
+
if (options.generateIndex) {
|
|
89
89
|
try {
|
|
90
|
-
await generateIndexFile(outputPath,
|
|
90
|
+
await generateIndexFile(outputPath, options);
|
|
91
91
|
}
|
|
92
92
|
catch (error) {
|
|
93
93
|
logger.error('Failed to generate index file:', error);
|
|
@@ -97,16 +97,35 @@ async function processAllSVGs(options, processedFiles) {
|
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
99
|
* Generate index file with all exports
|
|
100
|
+
* Scans the output directory for all existing component files
|
|
100
101
|
*/
|
|
101
|
-
async function generateIndexFile(outputDir,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
async function generateIndexFile(outputDir, options) {
|
|
103
|
+
try {
|
|
104
|
+
// Scan output directory for all component files
|
|
105
|
+
const files = await FileSystem.readDir(outputDir);
|
|
106
|
+
const extension = options.typescript ? 'tsx' : 'jsx';
|
|
107
|
+
// Filter component files (exclude index files)
|
|
108
|
+
const componentFiles = files.filter(file => (file.endsWith(`.${extension}`) ||
|
|
109
|
+
file.endsWith('.ts') ||
|
|
110
|
+
file.endsWith('.js')) &&
|
|
111
|
+
!file.startsWith('index.'));
|
|
112
|
+
if (componentFiles.length === 0) {
|
|
113
|
+
logger.warn('No component files found for index generation');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Extract component names
|
|
117
|
+
const componentNames = componentFiles.map(file => path.basename(file, path.extname(file)));
|
|
118
|
+
const indexExtension = options.typescript ? 'ts' : 'js';
|
|
119
|
+
const exports = componentNames
|
|
120
|
+
.map(name => `export { default as ${name} } from './${name}';`)
|
|
121
|
+
.join('\n');
|
|
122
|
+
const indexPath = path.join(outputDir, `index.${indexExtension}`);
|
|
123
|
+
await FileSystem.writeFile(indexPath, exports, 'utf-8');
|
|
124
|
+
logger.debug(`Generated index file: ${indexPath}`);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
logger.error('Failed to generate index file:', error);
|
|
128
|
+
}
|
|
110
129
|
}
|
|
111
130
|
/**
|
|
112
131
|
* Transform SVG import path to component path
|
|
@@ -140,7 +140,7 @@ async function processAllSVGs(options, processedFiles) {
|
|
|
140
140
|
}
|
|
141
141
|
// Generate index file
|
|
142
142
|
if (options.generateIndex) {
|
|
143
|
-
await generateIndexFile(outputDir,
|
|
143
|
+
await generateIndexFile(outputDir, options);
|
|
144
144
|
}
|
|
145
145
|
const successful = results.filter(r => r.success).length;
|
|
146
146
|
logger.info(`SVGER: Processed ${successful}/${svgFiles.length} SVG files`);
|
|
@@ -182,16 +182,35 @@ async function processSVGFile(svgPath, outputDir, options) {
|
|
|
182
182
|
}
|
|
183
183
|
/**
|
|
184
184
|
* Generate index file with all exports
|
|
185
|
+
* Scans the output directory for all existing component files
|
|
185
186
|
*/
|
|
186
|
-
async function generateIndexFile(outputDir,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
async function generateIndexFile(outputDir, options) {
|
|
188
|
+
try {
|
|
189
|
+
// Scan output directory for all component files
|
|
190
|
+
const files = await FileSystem.readDir(outputDir);
|
|
191
|
+
const extension = options.typescript ? 'tsx' : 'jsx';
|
|
192
|
+
// Filter component files (exclude index files)
|
|
193
|
+
const componentFiles = files.filter(file => (file.endsWith(`.${extension}`) ||
|
|
194
|
+
file.endsWith('.ts') ||
|
|
195
|
+
file.endsWith('.js')) &&
|
|
196
|
+
!file.startsWith('index.'));
|
|
197
|
+
if (componentFiles.length === 0) {
|
|
198
|
+
logger.warn('No component files found for index generation');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Extract component names
|
|
202
|
+
const componentNames = componentFiles.map(file => path.basename(file, path.extname(file)));
|
|
203
|
+
const indexExtension = options.typescript ? 'ts' : 'js';
|
|
204
|
+
const exports = componentNames
|
|
205
|
+
.map(name => `export { default as ${name} } from './${name}';`)
|
|
206
|
+
.join('\n');
|
|
207
|
+
const indexPath = path.join(outputDir, `index.${indexExtension}`);
|
|
208
|
+
await FileSystem.writeFile(indexPath, exports, 'utf-8');
|
|
209
|
+
logger.debug(`Generated index file: ${indexPath}`);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
logger.error('Failed to generate index file:', error);
|
|
213
|
+
}
|
|
195
214
|
}
|
|
196
215
|
// Default export
|
|
197
216
|
export default svgerRollupPlugin;
|
|
@@ -47,13 +47,9 @@ export function svgerVitePlugin(options = {}) {
|
|
|
47
47
|
svgo: options.svgo !== undefined ? options.svgo : false,
|
|
48
48
|
generateIndex: options.generateIndex !== undefined ? options.generateIndex : true,
|
|
49
49
|
};
|
|
50
|
-
let viteConfig;
|
|
51
50
|
const processedFiles = new Set();
|
|
52
51
|
return {
|
|
53
52
|
name: PLUGIN_NAME,
|
|
54
|
-
configResolved(resolvedConfig) {
|
|
55
|
-
viteConfig = resolvedConfig;
|
|
56
|
-
},
|
|
57
53
|
async buildStart() {
|
|
58
54
|
logger.info('SVGER Vite Plugin: Processing SVG files...');
|
|
59
55
|
await processAllSVGs(pluginOptions, processedFiles);
|
|
@@ -164,7 +160,7 @@ async function processAllSVGs(options, processedFiles) {
|
|
|
164
160
|
}
|
|
165
161
|
// Generate index file
|
|
166
162
|
if (options.generateIndex) {
|
|
167
|
-
await generateIndexFile(outputDir,
|
|
163
|
+
await generateIndexFile(outputDir, options);
|
|
168
164
|
}
|
|
169
165
|
const successful = results.filter(r => r.success).length;
|
|
170
166
|
logger.info(`SVGER: Processed ${successful}/${svgFiles.length} SVG files`);
|
|
@@ -206,16 +202,35 @@ async function processSVGFile(svgPath, outputDir, options) {
|
|
|
206
202
|
}
|
|
207
203
|
/**
|
|
208
204
|
* Generate index file with all exports
|
|
205
|
+
* Scans the output directory for all existing component files
|
|
209
206
|
*/
|
|
210
|
-
async function generateIndexFile(outputDir,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
207
|
+
async function generateIndexFile(outputDir, options) {
|
|
208
|
+
try {
|
|
209
|
+
// Scan output directory for all component files
|
|
210
|
+
const files = await FileSystem.readDir(outputDir);
|
|
211
|
+
const extension = options.typescript ? 'tsx' : 'jsx';
|
|
212
|
+
// Filter component files (exclude index files)
|
|
213
|
+
const componentFiles = files.filter(file => (file.endsWith(`.${extension}`) ||
|
|
214
|
+
file.endsWith('.ts') ||
|
|
215
|
+
file.endsWith('.js')) &&
|
|
216
|
+
!file.startsWith('index.'));
|
|
217
|
+
if (componentFiles.length === 0) {
|
|
218
|
+
logger.warn('No component files found for index generation');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
// Extract component names
|
|
222
|
+
const componentNames = componentFiles.map(file => path.basename(file, path.extname(file)));
|
|
223
|
+
const indexExtension = options.typescript ? 'ts' : 'js';
|
|
224
|
+
const exports = componentNames
|
|
225
|
+
.map(name => `export { default as ${name} } from './${name}';`)
|
|
226
|
+
.join('\n');
|
|
227
|
+
const indexPath = path.join(outputDir, `index.${indexExtension}`);
|
|
228
|
+
await FileSystem.writeFile(indexPath, exports, 'utf-8');
|
|
229
|
+
logger.debug(`Generated index file: ${indexPath}`);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
logger.error('Failed to generate index file:', error);
|
|
233
|
+
}
|
|
219
234
|
}
|
|
220
235
|
// Default export
|
|
221
236
|
export default svgerVitePlugin;
|
|
@@ -111,7 +111,7 @@ export class SvgerWebpackPlugin {
|
|
|
111
111
|
}
|
|
112
112
|
// Generate index file
|
|
113
113
|
if (this.options.generateIndex) {
|
|
114
|
-
await this.generateIndexFile(outputDir
|
|
114
|
+
await this.generateIndexFile(outputDir);
|
|
115
115
|
}
|
|
116
116
|
const successful = results.filter(r => r.success).length;
|
|
117
117
|
logger.info(`SVGER: Processed ${successful}/${svgFiles.length} SVG files`);
|
|
@@ -176,16 +176,35 @@ export class SvgerWebpackPlugin {
|
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
178
178
|
* Generate index file with all exports
|
|
179
|
+
* Scans the output directory for all existing component files
|
|
179
180
|
*/
|
|
180
|
-
async generateIndexFile(outputDir
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
181
|
+
async generateIndexFile(outputDir) {
|
|
182
|
+
try {
|
|
183
|
+
// Scan output directory for all component files
|
|
184
|
+
const files = await FileSystem.readDir(outputDir);
|
|
185
|
+
const extension = this.options.typescript ? 'tsx' : 'jsx';
|
|
186
|
+
// Filter component files (exclude index files)
|
|
187
|
+
const componentFiles = files.filter(file => (file.endsWith(`.${extension}`) ||
|
|
188
|
+
file.endsWith('.ts') ||
|
|
189
|
+
file.endsWith('.js')) &&
|
|
190
|
+
!file.startsWith('index.'));
|
|
191
|
+
if (componentFiles.length === 0) {
|
|
192
|
+
logger.warn('No component files found for index generation');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Extract component names
|
|
196
|
+
const componentNames = componentFiles.map(file => path.basename(file, path.extname(file)));
|
|
197
|
+
const indexExtension = this.options.typescript ? 'ts' : 'js';
|
|
198
|
+
const exports = componentNames
|
|
199
|
+
.map(name => `export { default as ${name} } from './${name}';`)
|
|
200
|
+
.join('\n');
|
|
201
|
+
const indexPath = path.join(outputDir, `index.${indexExtension}`);
|
|
202
|
+
await FileSystem.writeFile(indexPath, exports, 'utf-8');
|
|
203
|
+
logger.debug(`Generated index file: ${indexPath}`);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
logger.error('Failed to generate index file:', error);
|
|
207
|
+
}
|
|
189
208
|
}
|
|
190
209
|
/**
|
|
191
210
|
* Get changed files from webpack
|
|
@@ -42,6 +42,8 @@ export declare class SVGService {
|
|
|
42
42
|
clean(outDir: string): Promise<void>;
|
|
43
43
|
/**
|
|
44
44
|
* Generate index.ts file with all component exports
|
|
45
|
+
* Scans the output directory for all existing component files,
|
|
46
|
+
* including those from locked SVGs that weren't regenerated
|
|
45
47
|
*/
|
|
46
48
|
private generateIndexFile;
|
|
47
49
|
/**
|
|
@@ -76,7 +76,7 @@ export class SVGService {
|
|
|
76
76
|
defaultWidth: mergedConfig.defaultWidth,
|
|
77
77
|
defaultHeight: mergedConfig.defaultHeight,
|
|
78
78
|
defaultFill: mergedConfig.defaultFill,
|
|
79
|
-
namingConvention: mergedConfig.
|
|
79
|
+
namingConvention: mergedConfig.outputConfig?.naming || 'pascal',
|
|
80
80
|
styleRules: Object.fromEntries(Object.entries(mergedConfig.styleRules || {}).filter(([, v]) => v !== undefined)),
|
|
81
81
|
});
|
|
82
82
|
results.push({
|
|
@@ -107,9 +107,8 @@ export class SVGService {
|
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
// Generate index.ts file with all component exports
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
110
|
+
// This includes both newly generated components and existing locked components
|
|
111
|
+
await this.generateIndexFile(outDir, mergedConfig);
|
|
113
112
|
}
|
|
114
113
|
/**
|
|
115
114
|
* Generate a React component from a single SVG file
|
|
@@ -257,13 +256,30 @@ export class SVGService {
|
|
|
257
256
|
}
|
|
258
257
|
/**
|
|
259
258
|
* Generate index.ts file with all component exports
|
|
259
|
+
* Scans the output directory for all existing component files,
|
|
260
|
+
* including those from locked SVGs that weren't regenerated
|
|
260
261
|
*/
|
|
261
|
-
async generateIndexFile(outDir,
|
|
262
|
+
async generateIndexFile(outDir, config) {
|
|
262
263
|
try {
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
264
|
+
// Scan output directory for all component files
|
|
265
|
+
const files = await FileSystem.readDir(outDir);
|
|
266
|
+
// Get the file extension based on typescript setting
|
|
267
|
+
const typescript = config?.typescript !== false;
|
|
268
|
+
const ext = typescript ? '.tsx' : '.jsx';
|
|
269
|
+
// Filter component files (exclude index.ts/index.js)
|
|
270
|
+
const componentFiles = files.filter(file => (file.endsWith(ext) ||
|
|
271
|
+
file.endsWith('.ts') ||
|
|
272
|
+
file.endsWith('.js')) &&
|
|
273
|
+
!file.startsWith('index.'));
|
|
274
|
+
if (componentFiles.length === 0) {
|
|
275
|
+
logger.warn('No component files found in output directory for index generation');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// Extract component names from filenames
|
|
279
|
+
const componentNames = componentFiles.map(file => {
|
|
280
|
+
// Remove extension to get component name
|
|
281
|
+
const baseName = path.basename(file, path.extname(file));
|
|
282
|
+
return baseName;
|
|
267
283
|
});
|
|
268
284
|
const indexContent = this.generateIndexContent(componentNames);
|
|
269
285
|
const indexPath = path.join(outDir, 'index.ts');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svger-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Enterprise-grade, zero-dependency SVG to component converter with official Webpack, Vite, Rollup, Babel, Next.js & Jest integrations. Supporting React, React Native, Vue, Angular, Svelte, Solid, Lit, Preact & Vanilla. Features auto-generated TypeScript exports, HMR, responsive design, themes & 85% faster processing than SVGR.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -86,6 +86,8 @@
|
|
|
86
86
|
"clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"",
|
|
87
87
|
"dev": "ts-node --esm src/cli.ts",
|
|
88
88
|
"test": "npm run test:frameworks && npm run test:config && npm run test:e2e && npm run test:integrations",
|
|
89
|
+
"test:jest": "jest --passWithNoTests",
|
|
90
|
+
"test:unit": "jest --testPathPattern='src/__tests__' --passWithNoTests",
|
|
89
91
|
"test:frameworks": "node frameworks.test.js",
|
|
90
92
|
"test:config": "tsc tests/config-options.test.ts --outDir dist-tests --module es2022 --target es2022 --moduleResolution bundler && node dist-tests/config-options.test.js",
|
|
91
93
|
"test:e2e": "tsc tests/e2e-complete.test.ts --outDir dist-tests --module es2022 --target es2022 --moduleResolution bundler && node dist-tests/e2e-complete.test.js",
|
|
@@ -93,7 +95,8 @@
|
|
|
93
95
|
"test:watch": "jest --watch",
|
|
94
96
|
"test:coverage": "jest --coverage",
|
|
95
97
|
"test:integration": "jest --testPathPattern=integration",
|
|
96
|
-
"test:all": "npm run test:frameworks && npm run test:config && npm run test:e2e",
|
|
98
|
+
"test:all": "npm run test:jest && npm run test:frameworks && npm run test:config && npm run test:e2e",
|
|
99
|
+
"prepublishOnly": "npm run build",
|
|
97
100
|
"lint": "eslint src --ext .ts,.tsx",
|
|
98
101
|
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
99
102
|
"typecheck": "tsc --noEmit",
|