svger-cli 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CODE_OF_CONDUCT.md +79 -0
  2. package/CONTRIBUTING.md +146 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1862 -73
  5. package/TESTING.md +143 -0
  6. package/cli-framework.test.js +16 -0
  7. package/cli-test-angular/Arrowbenddownleft.component.ts +27 -0
  8. package/cli-test-angular/Vite.component.ts +27 -0
  9. package/cli-test-angular/index.ts +25 -0
  10. package/{my-icons/ArrowBendDownLeft.tsx → cli-test-output/Arrowbenddownleft.vue} +28 -12
  11. package/{my-icons/Vite.tsx → cli-test-output/Vite.vue} +28 -12
  12. package/cli-test-output/index.ts +25 -0
  13. package/cli-test-react/Arrowbenddownleft.tsx +39 -0
  14. package/cli-test-react/Vite.tsx +39 -0
  15. package/cli-test-react/index.ts +25 -0
  16. package/cli-test-svelte/Arrowbenddownleft.svelte +22 -0
  17. package/cli-test-svelte/Vite.svelte +22 -0
  18. package/cli-test-svelte/index.ts +25 -0
  19. package/dist/builder.js +12 -13
  20. package/dist/clean.js +3 -3
  21. package/dist/cli.js +139 -61
  22. package/dist/config.d.ts +1 -1
  23. package/dist/config.js +5 -7
  24. package/dist/lock.js +1 -1
  25. package/dist/templates/ComponentTemplate.d.ts +15 -0
  26. package/dist/templates/ComponentTemplate.js +15 -0
  27. package/dist/watch.d.ts +2 -1
  28. package/dist/watch.js +30 -33
  29. package/docs/ADR-SVG-INTRGRATION-METHODS-002.adr.md +550 -0
  30. package/docs/FRAMEWORK-GUIDE.md +768 -0
  31. package/docs/IMPLEMENTATION-SUMMARY.md +376 -0
  32. package/frameworks.test.js +170 -0
  33. package/package.json +7 -10
  34. package/src/builder.ts +12 -13
  35. package/src/clean.ts +3 -3
  36. package/src/cli.ts +148 -59
  37. package/src/config.ts +5 -6
  38. package/src/core/error-handler.ts +303 -0
  39. package/src/core/framework-templates.ts +428 -0
  40. package/src/core/logger.ts +104 -0
  41. package/src/core/performance-engine.ts +327 -0
  42. package/src/core/plugin-manager.ts +228 -0
  43. package/src/core/style-compiler.ts +605 -0
  44. package/src/core/template-manager.ts +619 -0
  45. package/src/index.ts +235 -0
  46. package/src/lock.ts +1 -1
  47. package/src/processors/svg-processor.ts +288 -0
  48. package/src/services/config.ts +241 -0
  49. package/src/services/file-watcher.ts +218 -0
  50. package/src/services/svg-service.ts +468 -0
  51. package/src/types/index.ts +169 -0
  52. package/src/utils/native.ts +352 -0
  53. package/src/watch.ts +36 -36
  54. package/test-output-mulit/TestIcon-angular-module.component.ts +26 -0
  55. package/test-output-mulit/TestIcon-angular-standalone.component.ts +27 -0
  56. package/test-output-mulit/TestIcon-lit.ts +35 -0
  57. package/test-output-mulit/TestIcon-preact.tsx +38 -0
  58. package/test-output-mulit/TestIcon-react.tsx +35 -0
  59. package/test-output-mulit/TestIcon-solid.tsx +27 -0
  60. package/test-output-mulit/TestIcon-svelte.svelte +22 -0
  61. package/test-output-mulit/TestIcon-vanilla.ts +37 -0
  62. package/test-output-mulit/TestIcon-vue-composition.vue +33 -0
  63. package/test-output-mulit/TestIcon-vue-options.vue +31 -0
  64. package/tsconfig.json +11 -9
package/dist/cli.js CHANGED
@@ -1,91 +1,158 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import { buildAll, generateSVG } from "./builder.js";
4
- import { lockFiles, unlockFiles } from "./lock.js";
5
- import { initConfig, setConfig, showConfig } from "./config.js";
6
- import { watchSVGs } from "./watch.js";
7
- import { clean } from "./clean.js";
8
- const program = new Command();
2
+ import { CLI } from "./utils/native.js";
3
+ import { svgService } from "./services/svg-service.js";
4
+ import { configService } from "./services/config.js";
5
+ import { logger } from "./core/logger.js";
6
+ const program = new CLI();
9
7
  /**
10
8
  * svger-cli CLI
11
- * Custom SVG to React component converter.
9
+ * Custom SVG to Angular, React, Vue, Svelte, Solid, and other component converter.
12
10
  */
13
11
  program
14
12
  .name("svger-cli")
15
- .description("Custom SVG to React component converter")
16
- .version("1.0.0");
13
+ .description("Custom SVG to Angular, React, Vue, Svelte, Solid, and other component converter")
14
+ .version("2.0.0");
17
15
  // -------- Build Command --------
18
16
  /**
19
17
  * Build all SVGs from a source folder to an output folder.
20
- *
21
- * @param {string} src - Source folder containing SVG files.
22
- * @param {string} out - Output folder for generated React components.
23
18
  */
24
19
  program
25
20
  .command("build <src> <out>")
26
21
  .description("Build all SVGs from source to output")
27
- .action(async (src, out) => {
28
- console.log("🛠️ Building SVGs...");
29
- console.log("Source:", src);
30
- console.log("Output:", out);
31
- await buildAll({ src, out });
22
+ .option("--framework <type>", "Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla)")
23
+ .option("--typescript", "Generate TypeScript components (default: true)")
24
+ .option("--no-typescript", "Generate JavaScript components")
25
+ .option("--composition", "Use Vue Composition API with <script setup>")
26
+ .option("--standalone", "Generate Angular standalone components")
27
+ .option("--signals", "Use Angular signals for reactive state")
28
+ .action(async (args, opts) => {
29
+ try {
30
+ const [src, out] = args;
31
+ // Build config from CLI options
32
+ const buildConfig = { src, out };
33
+ if (opts.framework) {
34
+ buildConfig.framework = opts.framework;
35
+ }
36
+ if (opts.typescript !== undefined) {
37
+ buildConfig.typescript = opts.typescript;
38
+ }
39
+ // Framework-specific options
40
+ const frameworkOptions = {};
41
+ if (opts.composition !== undefined) {
42
+ frameworkOptions.scriptSetup = opts.composition;
43
+ }
44
+ if (opts.standalone !== undefined) {
45
+ frameworkOptions.standalone = opts.standalone;
46
+ }
47
+ if (opts.signals !== undefined) {
48
+ frameworkOptions.signals = opts.signals;
49
+ }
50
+ if (Object.keys(frameworkOptions).length > 0) {
51
+ buildConfig.frameworkOptions = frameworkOptions;
52
+ }
53
+ await svgService.buildAll(buildConfig);
54
+ }
55
+ catch (error) {
56
+ logger.error('Build failed:', error);
57
+ process.exit(1);
58
+ }
32
59
  });
33
60
  // -------- Watch Command --------
34
61
  /**
35
62
  * Watch a source folder and rebuild SVGs automatically on changes.
36
- *
37
- * @param {string} src - Source folder to watch.
38
- * @param {string} out - Output folder for generated components.
39
63
  */
40
64
  program
41
65
  .command("watch <src> <out>")
42
66
  .description("Watch source folder and rebuild SVGs automatically")
43
- .action((src, out) => {
44
- console.log("🚀 Starting watch mode...");
45
- watchSVGs({ src, out });
67
+ .action(async (args) => {
68
+ try {
69
+ const [src, out] = args;
70
+ await svgService.startWatching({ src, out });
71
+ // Keep the process running
72
+ process.on('SIGINT', () => {
73
+ logger.info('Shutting down watch mode...');
74
+ svgService.shutdown();
75
+ process.exit(0);
76
+ });
77
+ }
78
+ catch (error) {
79
+ logger.error('Watch mode failed:', error);
80
+ process.exit(1);
81
+ }
46
82
  });
47
83
  // -------- Generate Single SVG --------
48
84
  /**
49
- * Generate a React component from a single SVG file.
50
- *
51
- * @param {string} svgFile - Path to the SVG file.
52
- * @param {string} out - Output folder for the generated component.
85
+ * Generate a component from a single SVG file.
53
86
  */
54
87
  program
55
88
  .command("generate <svgFile> <out>")
56
- .description("Convert a single SVG file into a React component")
57
- .action(async (svgFile, out) => {
58
- await generateSVG({ svgFile, outDir: out });
89
+ .description("Convert a single SVG file into a component")
90
+ .option("--framework <type>", "Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla)")
91
+ .option("--typescript", "Generate TypeScript component (default: true)")
92
+ .option("--no-typescript", "Generate JavaScript component")
93
+ .option("--composition", "Use Vue Composition API with <script setup>")
94
+ .option("--standalone", "Generate Angular standalone component")
95
+ .action(async (args, opts) => {
96
+ try {
97
+ const [svgFile, out] = args;
98
+ const generateConfig = { svgFile, outDir: out };
99
+ if (opts.framework) {
100
+ generateConfig.framework = opts.framework;
101
+ }
102
+ if (opts.typescript !== undefined) {
103
+ generateConfig.typescript = opts.typescript;
104
+ }
105
+ const frameworkOptions = {};
106
+ if (opts.composition !== undefined) {
107
+ frameworkOptions.scriptSetup = opts.composition;
108
+ }
109
+ if (opts.standalone !== undefined) {
110
+ frameworkOptions.standalone = opts.standalone;
111
+ }
112
+ if (Object.keys(frameworkOptions).length > 0) {
113
+ generateConfig.frameworkOptions = frameworkOptions;
114
+ }
115
+ await svgService.generateSingle(generateConfig);
116
+ }
117
+ catch (error) {
118
+ logger.error('Generation failed:', error);
119
+ process.exit(1);
120
+ }
59
121
  });
60
122
  // -------- Lock / Unlock --------
61
123
  /**
62
124
  * Lock one or more SVG files to prevent accidental overwrites.
63
- *
64
- * @param {string[]} files - Paths to SVG files to lock.
65
125
  */
66
126
  program
67
127
  .command("lock <files...>")
68
128
  .description("Lock one or more SVG files")
69
- .action((files) => lockFiles(files));
129
+ .action((args) => {
130
+ try {
131
+ svgService.lockService.lockFiles(args);
132
+ }
133
+ catch (error) {
134
+ logger.error('Lock operation failed:', error);
135
+ process.exit(1);
136
+ }
137
+ });
70
138
  /**
71
139
  * Unlock one or more SVG files to allow modifications.
72
- *
73
- * @param {string[]} files - Paths to SVG files to unlock.
74
140
  */
75
141
  program
76
142
  .command("unlock <files...>")
77
143
  .description("Unlock one or more SVG files")
78
- .action((files) => unlockFiles(files));
144
+ .action((args) => {
145
+ try {
146
+ svgService.lockService.unlockFiles(args);
147
+ }
148
+ catch (error) {
149
+ logger.error('Unlock operation failed:', error);
150
+ process.exit(1);
151
+ }
152
+ });
79
153
  // -------- Config --------
80
154
  /**
81
155
  * Manage svger-cli configuration.
82
- *
83
- * Options:
84
- * --init: Create default .svgconfig.json
85
- * --set key=value: Set a configuration value
86
- * --show: Show current configuration
87
- *
88
- * @param {Object} opts - CLI options
89
156
  */
90
157
  program
91
158
  .command("config")
@@ -93,32 +160,43 @@ program
93
160
  .option("--init", "Create default .svgconfig.json")
94
161
  .option("--set <keyValue>", "Set config key=value")
95
162
  .option("--show", "Show current config")
96
- .action((opts) => {
97
- if (opts.init)
98
- return initConfig();
99
- if (opts.set) {
100
- const [key, value] = opts.set.split("=");
101
- if (!key || value === undefined) {
102
- console.error("❌ Invalid format. Use key=value");
103
- process.exit(1);
163
+ .action(async (args, opts) => {
164
+ try {
165
+ if (opts.init)
166
+ return await configService.initConfig();
167
+ if (opts.set) {
168
+ const [key, value] = opts.set.split("=");
169
+ if (!key || value === undefined) {
170
+ logger.error("Invalid format. Use key=value");
171
+ process.exit(1);
172
+ }
173
+ const parsedValue = !isNaN(Number(value)) ? Number(value) : value;
174
+ return configService.setConfig(key, parsedValue);
104
175
  }
105
- const parsedValue = !isNaN(Number(value)) ? Number(value) : value;
106
- return setConfig(key, parsedValue);
176
+ if (opts.show)
177
+ return configService.showConfig();
178
+ logger.error("No option provided. Use --init, --set, or --show");
179
+ }
180
+ catch (error) {
181
+ logger.error('Config operation failed:', error);
182
+ process.exit(1);
107
183
  }
108
- if (opts.show)
109
- return showConfig();
110
- console.log("❌ No option provided. Use --init, --set, or --show");
111
184
  });
112
185
  // -------- Clean Command --------
113
186
  /**
114
187
  * Remove all generated SVG React components from an output folder.
115
- *
116
- * @param {string} out - Output folder to clean.
117
188
  */
118
189
  program
119
190
  .command("clean <out>")
120
191
  .description("Remove all generated SVG React components from output folder")
121
- .action(async (out) => {
122
- await clean(out);
192
+ .action(async (args) => {
193
+ try {
194
+ const [out] = args;
195
+ await svgService.clean(out);
196
+ }
197
+ catch (error) {
198
+ logger.error('Clean operation failed:', error);
199
+ process.exit(1);
200
+ }
123
201
  });
124
202
  program.parse();
package/dist/config.d.ts CHANGED
@@ -14,7 +14,7 @@ export declare function writeConfig(config: Record<string, any>): void;
14
14
  * Initialize the svger-cli configuration with default values.
15
15
  * If a config file already exists, this function will not overwrite it.
16
16
  */
17
- export declare function initConfig(): void;
17
+ export declare function initConfig(): Promise<void>;
18
18
  /**
19
19
  * Set a specific configuration key to a new value.
20
20
  *
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
- import fs from "fs-extra";
2
1
  import path from "path";
2
+ import { FileSystem } from "./utils/native.js";
3
3
  const CONFIG_FILE = ".svgconfig.json";
4
4
  /**
5
5
  * Get the absolute path to the configuration file.
@@ -15,9 +15,7 @@ function getConfigPath() {
15
15
  * @returns {Record<string, any>} Configuration object. Returns an empty object if no config file exists.
16
16
  */
17
17
  export function readConfig() {
18
- if (!fs.existsSync(getConfigPath()))
19
- return {};
20
- return fs.readJSONSync(getConfigPath());
18
+ return FileSystem.readJSONSync(getConfigPath());
21
19
  }
22
20
  /**
23
21
  * Write a configuration object to the config file.
@@ -25,14 +23,14 @@ export function readConfig() {
25
23
  * @param {Record<string, any>} config - Configuration object to write.
26
24
  */
27
25
  export function writeConfig(config) {
28
- fs.writeJSONSync(getConfigPath(), config, { spaces: 2 });
26
+ FileSystem.writeJSONSync(getConfigPath(), config, { spaces: 2 });
29
27
  }
30
28
  /**
31
29
  * Initialize the svger-cli configuration with default values.
32
30
  * If a config file already exists, this function will not overwrite it.
33
31
  */
34
- export function initConfig() {
35
- if (fs.existsSync(getConfigPath())) {
32
+ export async function initConfig() {
33
+ if (await FileSystem.exists(getConfigPath())) {
36
34
  console.log("⚠️ Config file already exists:", getConfigPath());
37
35
  return;
38
36
  }
package/dist/lock.js CHANGED
@@ -1,4 +1,4 @@
1
- import fs from "fs-extra";
1
+ import fs from "fs";
2
2
  import path from "path";
3
3
  const LOCK_FILE = ".svg-lock";
4
4
  /**
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Generates a React SVG component template from provided SVG content.
3
+ *
4
+ * This function processes raw SVG content by cleaning it (removing XML declarations,
5
+ * DOCTYPE, extra whitespace, inline styles, and xmlns attributes), then wraps it in
6
+ * a React functional component that accepts standard SVGProps for flexibility.
7
+ *
8
+ * @param options - Configuration object for generating the component.
9
+ * @param options.componentName - The name of the React component (e.g., "IconName").
10
+ * @param options.svgContent - The raw SVG markup as a string.
11
+ * @param [options.defaultWidth=24] - The default width attribute for the SVG.
12
+ * @param [options.defaultHeight=24] - The default height attribute for the SVG.
13
+ * @param [options.defaultFill="currentColor"] - The default fill color for the SVG.
14
+ * @returns A string containing the complete TypeScript code for the React SVG component.
15
+ */
1
16
  export declare function reactTemplate({ componentName, svgContent, defaultWidth, defaultHeight, defaultFill, }: {
2
17
  componentName: string;
3
18
  svgContent: string;
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Generates a React SVG component template from provided SVG content.
3
+ *
4
+ * This function processes raw SVG content by cleaning it (removing XML declarations,
5
+ * DOCTYPE, extra whitespace, inline styles, and xmlns attributes), then wraps it in
6
+ * a React functional component that accepts standard SVGProps for flexibility.
7
+ *
8
+ * @param options - Configuration object for generating the component.
9
+ * @param options.componentName - The name of the React component (e.g., "IconName").
10
+ * @param options.svgContent - The raw SVG markup as a string.
11
+ * @param [options.defaultWidth=24] - The default width attribute for the SVG.
12
+ * @param [options.defaultHeight=24] - The default height attribute for the SVG.
13
+ * @param [options.defaultFill="currentColor"] - The default fill color for the SVG.
14
+ * @returns A string containing the complete TypeScript code for the React SVG component.
15
+ */
1
16
  export function reactTemplate({ componentName, svgContent, defaultWidth = 24, defaultHeight = 24, defaultFill = "currentColor", }) {
2
17
  const cleaned = svgContent
3
18
  .replace(/<\?xml.*?\?>/g, "")
package/dist/watch.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { FileWatcher } from "./utils/native.js";
1
2
  /**
2
3
  * Watches a source folder for changes to SVG files and automatically
3
4
  * rebuilds React components when SVGs are added, modified, or deleted.
@@ -18,4 +19,4 @@
18
19
  export declare function watchSVGs(config: {
19
20
  src: string;
20
21
  out: string;
21
- }): import("chokidar").FSWatcher;
22
+ }): Promise<FileWatcher>;
package/dist/watch.js CHANGED
@@ -1,9 +1,8 @@
1
- import fs from "fs-extra";
2
1
  import path from "path";
3
- import chokidar from "chokidar";
4
2
  import { generateSVG } from "./builder.js";
5
3
  import { isLocked } from "./lock.js";
6
4
  import { readConfig } from "./config.js";
5
+ import { FileSystem, FileWatcher } from "./utils/native.js";
7
6
  /**
8
7
  * Watches a source folder for changes to SVG files and automatically
9
8
  * rebuilds React components when SVGs are added, modified, or deleted.
@@ -21,38 +20,22 @@ import { readConfig } from "./config.js";
21
20
  * // - Updates components when files change.
22
21
  * // - Removes components when SVGs are deleted.
23
22
  */
24
- export function watchSVGs(config) {
23
+ export async function watchSVGs(config) {
25
24
  const srcDir = path.resolve(config.src);
26
25
  const outDir = path.resolve(config.out);
27
26
  const svgConfig = readConfig();
28
- if (!fs.existsSync(srcDir)) {
27
+ if (!(await FileSystem.exists(srcDir))) {
29
28
  console.error("❌ Source folder not found:", srcDir);
30
29
  process.exit(1);
31
30
  }
32
31
  console.log(`👀 Watching for SVG changes in: ${srcDir}`);
33
32
  console.log("🚀 Watch mode active — waiting for file changes...");
34
- const watcher = chokidar.watch(srcDir, {
35
- persistent: true,
36
- ignoreInitial: false,
37
- depth: 0,
38
- awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 },
39
- ignored: /(^|[\/\\])\../, // Ignore hidden files
40
- });
41
- // ---- Handle new SVG files ----
42
- watcher.on("add", async (filePath) => {
43
- if (path.extname(filePath) !== ".svg")
44
- return;
45
- console.log("Detected new file:", filePath);
46
- if (isLocked(filePath)) {
47
- console.log(`⚠️ Skipped locked file: ${path.basename(filePath)}`);
48
- return;
49
- }
50
- console.log(`➕ New SVG detected: ${path.basename(filePath)}`);
51
- await generateSVG({ svgFile: filePath, outDir });
52
- });
53
- // ---- Handle modified SVG files ----
33
+ const watcher = new FileWatcher();
34
+ // Watch the directory
35
+ watcher.watch(srcDir, { recursive: false });
36
+ // Handle file changes
54
37
  watcher.on("change", async (filePath) => {
55
- if (path.extname(filePath) !== ".svg")
38
+ if (!filePath.endsWith(".svg"))
56
39
  return;
57
40
  console.log("Detected change in file:", filePath);
58
41
  if (isLocked(filePath)) {
@@ -62,15 +45,29 @@ export function watchSVGs(config) {
62
45
  console.log(`✏️ SVG updated: ${path.basename(filePath)}`);
63
46
  await generateSVG({ svgFile: filePath, outDir });
64
47
  });
65
- // ---- Handle deleted SVG files ----
66
- watcher.on("unlink", async (filePath) => {
67
- if (path.extname(filePath) !== ".svg")
48
+ // Handle new files (rename event in fs.watch can indicate new files)
49
+ watcher.on("rename", async (filePath) => {
50
+ if (!filePath.endsWith(".svg"))
68
51
  return;
69
- const componentName = path.basename(filePath, ".svg");
70
- const outFile = path.join(outDir, `${componentName}.tsx`);
71
- if (fs.existsSync(outFile)) {
72
- await fs.remove(outFile);
73
- console.log(`🗑️ Removed component: ${componentName}.tsx`);
52
+ // Check if file exists (new file) or doesn't exist (deleted file)
53
+ const exists = await FileSystem.exists(filePath);
54
+ if (exists) {
55
+ console.log("Detected new file:", filePath);
56
+ if (isLocked(filePath)) {
57
+ console.log(`⚠️ Skipped locked file: ${path.basename(filePath)}`);
58
+ return;
59
+ }
60
+ console.log(`➕ New SVG detected: ${path.basename(filePath)}`);
61
+ await generateSVG({ svgFile: filePath, outDir });
62
+ }
63
+ else {
64
+ // File was deleted
65
+ const componentName = path.basename(filePath, ".svg");
66
+ const outFile = path.join(outDir, `${componentName}.tsx`);
67
+ if (await FileSystem.exists(outFile)) {
68
+ await FileSystem.unlink(outFile);
69
+ console.log(`🗑️ Removed component: ${componentName}.tsx`);
70
+ }
74
71
  }
75
72
  });
76
73
  return watcher;