svger-cli 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +79 -0
- package/CONTRIBUTING.md +146 -0
- package/LICENSE +21 -0
- package/README.md +1862 -73
- package/TESTING.md +143 -0
- package/cli-framework.test.js +16 -0
- package/cli-test-angular/Arrowbenddownleft.component.ts +27 -0
- package/cli-test-angular/Vite.component.ts +27 -0
- package/cli-test-angular/index.ts +25 -0
- package/{my-icons/ArrowBendDownLeft.tsx → cli-test-output/Arrowbenddownleft.vue} +28 -12
- package/{my-icons/Vite.tsx → cli-test-output/Vite.vue} +28 -12
- package/cli-test-output/index.ts +25 -0
- package/cli-test-react/Arrowbenddownleft.tsx +39 -0
- package/cli-test-react/Vite.tsx +39 -0
- package/cli-test-react/index.ts +25 -0
- package/cli-test-svelte/Arrowbenddownleft.svelte +22 -0
- package/cli-test-svelte/Vite.svelte +22 -0
- package/cli-test-svelte/index.ts +25 -0
- package/dist/builder.js +12 -13
- package/dist/clean.js +3 -3
- package/dist/cli.js +139 -61
- package/dist/config.d.ts +1 -1
- package/dist/config.js +5 -7
- package/dist/lock.js +1 -1
- package/dist/templates/ComponentTemplate.d.ts +15 -0
- package/dist/templates/ComponentTemplate.js +15 -0
- package/dist/watch.d.ts +2 -1
- package/dist/watch.js +30 -33
- package/docs/ADR-SVG-INTRGRATION-METHODS-002.adr.md +550 -0
- package/docs/FRAMEWORK-GUIDE.md +768 -0
- package/docs/IMPLEMENTATION-SUMMARY.md +376 -0
- package/frameworks.test.js +170 -0
- package/package.json +8 -10
- package/src/builder.ts +12 -13
- package/src/clean.ts +3 -3
- package/src/cli.ts +148 -59
- package/src/config.ts +5 -6
- package/src/core/error-handler.ts +303 -0
- package/src/core/framework-templates.ts +428 -0
- package/src/core/logger.ts +104 -0
- package/src/core/performance-engine.ts +327 -0
- package/src/core/plugin-manager.ts +228 -0
- package/src/core/style-compiler.ts +605 -0
- package/src/core/template-manager.ts +619 -0
- package/src/index.ts +235 -0
- package/src/lock.ts +1 -1
- package/src/processors/svg-processor.ts +288 -0
- package/src/services/config.ts +241 -0
- package/src/services/file-watcher.ts +218 -0
- package/src/services/svg-service.ts +468 -0
- package/src/types/index.ts +169 -0
- package/src/utils/native.ts +352 -0
- package/src/watch.ts +36 -36
- package/test-output-mulit/TestIcon-angular-module.component.ts +26 -0
- package/test-output-mulit/TestIcon-angular-standalone.component.ts +27 -0
- package/test-output-mulit/TestIcon-lit.ts +35 -0
- package/test-output-mulit/TestIcon-preact.tsx +38 -0
- package/test-output-mulit/TestIcon-react.tsx +35 -0
- package/test-output-mulit/TestIcon-solid.tsx +27 -0
- package/test-output-mulit/TestIcon-svelte.svelte +22 -0
- package/test-output-mulit/TestIcon-vanilla.ts +37 -0
- package/test-output-mulit/TestIcon-vue-composition.vue +33 -0
- package/test-output-mulit/TestIcon-vue-options.vue +31 -0
- package/tsconfig.json +11 -9
package/src/cli.ts
CHANGED
|
@@ -1,99 +1,179 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { watchSVGs } from "./watch.js";
|
|
7
|
-
import { clean } from "./clean.js";
|
|
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";
|
|
8
6
|
|
|
9
|
-
const program = new
|
|
7
|
+
const program = new CLI();
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
* svger-cli CLI
|
|
13
|
-
* Custom SVG to React component converter.
|
|
11
|
+
* Custom SVG to Angular, React, Vue, Svelte, Solid, and other component converter.
|
|
14
12
|
*/
|
|
15
13
|
program
|
|
16
14
|
.name("svger-cli")
|
|
17
|
-
.description("Custom SVG to React component converter")
|
|
18
|
-
.version("
|
|
15
|
+
.description("Custom SVG to Angular, React, Vue, Svelte, Solid, and other component converter")
|
|
16
|
+
.version("2.0.0");
|
|
19
17
|
|
|
20
18
|
// -------- Build Command --------
|
|
21
19
|
/**
|
|
22
20
|
* Build all SVGs from a source folder to an output folder.
|
|
23
|
-
*
|
|
24
|
-
* @param {string} src - Source folder containing SVG files.
|
|
25
|
-
* @param {string} out - Output folder for generated React components.
|
|
26
21
|
*/
|
|
27
22
|
program
|
|
28
23
|
.command("build <src> <out>")
|
|
29
24
|
.description("Build all SVGs from source to output")
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
.option("--framework <type>", "Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla)")
|
|
26
|
+
.option("--typescript", "Generate TypeScript components (default: true)")
|
|
27
|
+
.option("--no-typescript", "Generate JavaScript components")
|
|
28
|
+
.option("--composition", "Use Vue Composition API with <script setup>")
|
|
29
|
+
.option("--standalone", "Generate Angular standalone components")
|
|
30
|
+
.option("--signals", "Use Angular signals for reactive state")
|
|
31
|
+
.action(async (args: string[], opts: Record<string, any>) => {
|
|
32
|
+
try {
|
|
33
|
+
const [src, out] = args;
|
|
34
|
+
|
|
35
|
+
// Build config from CLI options
|
|
36
|
+
const buildConfig: any = { src, out };
|
|
37
|
+
|
|
38
|
+
if (opts.framework) {
|
|
39
|
+
buildConfig.framework = opts.framework;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (opts.typescript !== undefined) {
|
|
43
|
+
buildConfig.typescript = opts.typescript;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Framework-specific options
|
|
47
|
+
const frameworkOptions: any = {};
|
|
48
|
+
|
|
49
|
+
if (opts.composition !== undefined) {
|
|
50
|
+
frameworkOptions.scriptSetup = opts.composition;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (opts.standalone !== undefined) {
|
|
54
|
+
frameworkOptions.standalone = opts.standalone;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (opts.signals !== undefined) {
|
|
58
|
+
frameworkOptions.signals = opts.signals;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (Object.keys(frameworkOptions).length > 0) {
|
|
62
|
+
buildConfig.frameworkOptions = frameworkOptions;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await svgService.buildAll(buildConfig);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.error('Build failed:', error);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
35
70
|
});
|
|
36
71
|
|
|
37
72
|
// -------- Watch Command --------
|
|
38
73
|
/**
|
|
39
74
|
* Watch a source folder and rebuild SVGs automatically on changes.
|
|
40
|
-
*
|
|
41
|
-
* @param {string} src - Source folder to watch.
|
|
42
|
-
* @param {string} out - Output folder for generated components.
|
|
43
75
|
*/
|
|
44
76
|
program
|
|
45
77
|
.command("watch <src> <out>")
|
|
46
78
|
.description("Watch source folder and rebuild SVGs automatically")
|
|
47
|
-
.action((
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
.action(async (args: string[]) => {
|
|
80
|
+
try {
|
|
81
|
+
const [src, out] = args;
|
|
82
|
+
await svgService.startWatching({ src, out });
|
|
83
|
+
|
|
84
|
+
// Keep the process running
|
|
85
|
+
process.on('SIGINT', () => {
|
|
86
|
+
logger.info('Shutting down watch mode...');
|
|
87
|
+
svgService.shutdown();
|
|
88
|
+
process.exit(0);
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logger.error('Watch mode failed:', error);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
50
94
|
});
|
|
51
95
|
|
|
52
96
|
// -------- Generate Single SVG --------
|
|
53
97
|
/**
|
|
54
|
-
* Generate a
|
|
55
|
-
*
|
|
56
|
-
* @param {string} svgFile - Path to the SVG file.
|
|
57
|
-
* @param {string} out - Output folder for the generated component.
|
|
98
|
+
* Generate a component from a single SVG file.
|
|
58
99
|
*/
|
|
59
100
|
program
|
|
60
101
|
.command("generate <svgFile> <out>")
|
|
61
|
-
.description("Convert a single SVG file into a
|
|
62
|
-
.
|
|
63
|
-
|
|
102
|
+
.description("Convert a single SVG file into a component")
|
|
103
|
+
.option("--framework <type>", "Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla)")
|
|
104
|
+
.option("--typescript", "Generate TypeScript component (default: true)")
|
|
105
|
+
.option("--no-typescript", "Generate JavaScript component")
|
|
106
|
+
.option("--composition", "Use Vue Composition API with <script setup>")
|
|
107
|
+
.option("--standalone", "Generate Angular standalone component")
|
|
108
|
+
.action(async (args: string[], opts: Record<string, any>) => {
|
|
109
|
+
try {
|
|
110
|
+
const [svgFile, out] = args;
|
|
111
|
+
|
|
112
|
+
const generateConfig: any = { svgFile, outDir: out };
|
|
113
|
+
|
|
114
|
+
if (opts.framework) {
|
|
115
|
+
generateConfig.framework = opts.framework;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (opts.typescript !== undefined) {
|
|
119
|
+
generateConfig.typescript = opts.typescript;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const frameworkOptions: any = {};
|
|
123
|
+
|
|
124
|
+
if (opts.composition !== undefined) {
|
|
125
|
+
frameworkOptions.scriptSetup = opts.composition;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (opts.standalone !== undefined) {
|
|
129
|
+
frameworkOptions.standalone = opts.standalone;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (Object.keys(frameworkOptions).length > 0) {
|
|
133
|
+
generateConfig.frameworkOptions = frameworkOptions;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await svgService.generateSingle(generateConfig);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger.error('Generation failed:', error);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
64
141
|
});
|
|
65
142
|
|
|
66
143
|
// -------- Lock / Unlock --------
|
|
67
144
|
/**
|
|
68
145
|
* Lock one or more SVG files to prevent accidental overwrites.
|
|
69
|
-
*
|
|
70
|
-
* @param {string[]} files - Paths to SVG files to lock.
|
|
71
146
|
*/
|
|
72
147
|
program
|
|
73
148
|
.command("lock <files...>")
|
|
74
149
|
.description("Lock one or more SVG files")
|
|
75
|
-
.action((
|
|
150
|
+
.action((args: string[]) => {
|
|
151
|
+
try {
|
|
152
|
+
svgService.lockService.lockFiles(args);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger.error('Lock operation failed:', error);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
76
158
|
|
|
77
159
|
/**
|
|
78
160
|
* Unlock one or more SVG files to allow modifications.
|
|
79
|
-
*
|
|
80
|
-
* @param {string[]} files - Paths to SVG files to unlock.
|
|
81
161
|
*/
|
|
82
162
|
program
|
|
83
163
|
.command("unlock <files...>")
|
|
84
164
|
.description("Unlock one or more SVG files")
|
|
85
|
-
.action((
|
|
165
|
+
.action((args: string[]) => {
|
|
166
|
+
try {
|
|
167
|
+
svgService.lockService.unlockFiles(args);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
logger.error('Unlock operation failed:', error);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
86
173
|
|
|
87
174
|
// -------- Config --------
|
|
88
175
|
/**
|
|
89
176
|
* Manage svger-cli configuration.
|
|
90
|
-
*
|
|
91
|
-
* Options:
|
|
92
|
-
* --init: Create default .svgconfig.json
|
|
93
|
-
* --set key=value: Set a configuration value
|
|
94
|
-
* --show: Show current configuration
|
|
95
|
-
*
|
|
96
|
-
* @param {Object} opts - CLI options
|
|
97
177
|
*/
|
|
98
178
|
program
|
|
99
179
|
.command("config")
|
|
@@ -101,32 +181,41 @@ program
|
|
|
101
181
|
.option("--init", "Create default .svgconfig.json")
|
|
102
182
|
.option("--set <keyValue>", "Set config key=value")
|
|
103
183
|
.option("--show", "Show current config")
|
|
104
|
-
.action((opts) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
184
|
+
.action(async (args: string[], opts: Record<string, any>) => {
|
|
185
|
+
try {
|
|
186
|
+
if (opts.init) return await configService.initConfig();
|
|
187
|
+
if (opts.set) {
|
|
188
|
+
const [key, value] = opts.set.split("=");
|
|
189
|
+
if (!key || value === undefined) {
|
|
190
|
+
logger.error("Invalid format. Use key=value");
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
const parsedValue = !isNaN(Number(value)) ? Number(value) : value;
|
|
194
|
+
return configService.setConfig(key, parsedValue);
|
|
111
195
|
}
|
|
112
|
-
|
|
113
|
-
|
|
196
|
+
if (opts.show) return configService.showConfig();
|
|
197
|
+
logger.error("No option provided. Use --init, --set, or --show");
|
|
198
|
+
} catch (error) {
|
|
199
|
+
logger.error('Config operation failed:', error);
|
|
200
|
+
process.exit(1);
|
|
114
201
|
}
|
|
115
|
-
if (opts.show) return showConfig();
|
|
116
|
-
console.log("❌ No option provided. Use --init, --set, or --show");
|
|
117
202
|
});
|
|
118
203
|
|
|
119
204
|
// -------- Clean Command --------
|
|
120
205
|
/**
|
|
121
206
|
* Remove all generated SVG React components from an output folder.
|
|
122
|
-
*
|
|
123
|
-
* @param {string} out - Output folder to clean.
|
|
124
207
|
*/
|
|
125
208
|
program
|
|
126
209
|
.command("clean <out>")
|
|
127
210
|
.description("Remove all generated SVG React components from output folder")
|
|
128
|
-
.action(async (
|
|
129
|
-
|
|
211
|
+
.action(async (args: string[]) => {
|
|
212
|
+
try {
|
|
213
|
+
const [out] = args;
|
|
214
|
+
await svgService.clean(out);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
logger.error('Clean operation failed:', error);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
130
219
|
});
|
|
131
220
|
|
|
132
221
|
program.parse();
|
package/src/config.ts
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
|
|
|
4
4
|
const CONFIG_FILE = ".svgconfig.json";
|
|
5
5
|
|
|
@@ -18,8 +18,7 @@ function getConfigPath(): string {
|
|
|
18
18
|
* @returns {Record<string, any>} Configuration object. Returns an empty object if no config file exists.
|
|
19
19
|
*/
|
|
20
20
|
export function readConfig(): Record<string, any> {
|
|
21
|
-
|
|
22
|
-
return fs.readJSONSync(getConfigPath());
|
|
21
|
+
return FileSystem.readJSONSync(getConfigPath());
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
/**
|
|
@@ -28,15 +27,15 @@ export function readConfig(): Record<string, any> {
|
|
|
28
27
|
* @param {Record<string, any>} config - Configuration object to write.
|
|
29
28
|
*/
|
|
30
29
|
export function writeConfig(config: Record<string, any>) {
|
|
31
|
-
|
|
30
|
+
FileSystem.writeJSONSync(getConfigPath(), config, { spaces: 2 });
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
34
|
* Initialize the svger-cli configuration with default values.
|
|
36
35
|
* If a config file already exists, this function will not overwrite it.
|
|
37
36
|
*/
|
|
38
|
-
export function initConfig() {
|
|
39
|
-
if (
|
|
37
|
+
export async function initConfig() {
|
|
38
|
+
if (await FileSystem.exists(getConfigPath())) {
|
|
40
39
|
console.log("⚠️ Config file already exists:", getConfigPath());
|
|
41
40
|
return;
|
|
42
41
|
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enhanced error handling system with detailed error tracking and recovery
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface SVGError {
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
11
|
+
context?: Record<string, any>;
|
|
12
|
+
timestamp: number;
|
|
13
|
+
stack?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ErrorRecoveryStrategy {
|
|
17
|
+
canRecover(error: SVGError): boolean;
|
|
18
|
+
recover(error: SVGError, context?: any): Promise<any>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class SVGErrorHandler {
|
|
22
|
+
private static instance: SVGErrorHandler;
|
|
23
|
+
private errorHistory: SVGError[] = [];
|
|
24
|
+
private recoveryStrategies: Map<string, ErrorRecoveryStrategy> = new Map();
|
|
25
|
+
private readonly maxHistorySize = 100;
|
|
26
|
+
|
|
27
|
+
private constructor() {
|
|
28
|
+
this.setupDefaultStrategies();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static getInstance(): SVGErrorHandler {
|
|
32
|
+
if (!SVGErrorHandler.instance) {
|
|
33
|
+
SVGErrorHandler.instance = new SVGErrorHandler();
|
|
34
|
+
}
|
|
35
|
+
return SVGErrorHandler.instance;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Handle an error with context and attempted recovery
|
|
40
|
+
*/
|
|
41
|
+
public async handleError(
|
|
42
|
+
error: Error | SVGError,
|
|
43
|
+
context?: Record<string, any>
|
|
44
|
+
): Promise<{ recovered: boolean; result?: any }> {
|
|
45
|
+
const svgError = this.normalizeError(error, context);
|
|
46
|
+
|
|
47
|
+
// Log error based on severity
|
|
48
|
+
this.logError(svgError);
|
|
49
|
+
|
|
50
|
+
// Add to history
|
|
51
|
+
this.addToHistory(svgError);
|
|
52
|
+
|
|
53
|
+
// Attempt recovery
|
|
54
|
+
const recoveryResult = await this.attemptRecovery(svgError, context);
|
|
55
|
+
|
|
56
|
+
return recoveryResult;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register a custom error recovery strategy
|
|
61
|
+
*/
|
|
62
|
+
public registerRecoveryStrategy(errorCode: string, strategy: ErrorRecoveryStrategy): void {
|
|
63
|
+
this.recoveryStrategies.set(errorCode, strategy);
|
|
64
|
+
logger.debug(`Recovery strategy registered for error code: ${errorCode}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get error statistics
|
|
69
|
+
*/
|
|
70
|
+
public getErrorStats(): {
|
|
71
|
+
total: number;
|
|
72
|
+
bySeverity: Record<string, number>;
|
|
73
|
+
byCode: Record<string, number>;
|
|
74
|
+
recentErrors: SVGError[];
|
|
75
|
+
} {
|
|
76
|
+
const bySeverity: Record<string, number> = {};
|
|
77
|
+
const byCode: Record<string, number> = {};
|
|
78
|
+
|
|
79
|
+
this.errorHistory.forEach(error => {
|
|
80
|
+
bySeverity[error.severity] = (bySeverity[error.severity] || 0) + 1;
|
|
81
|
+
byCode[error.code] = (byCode[error.code] || 0) + 1;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
total: this.errorHistory.length,
|
|
86
|
+
bySeverity,
|
|
87
|
+
byCode,
|
|
88
|
+
recentErrors: this.errorHistory.slice(-10)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Clear error history
|
|
94
|
+
*/
|
|
95
|
+
public clearHistory(): void {
|
|
96
|
+
this.errorHistory = [];
|
|
97
|
+
logger.debug('Error history cleared');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Private methods
|
|
101
|
+
|
|
102
|
+
private normalizeError(error: Error | SVGError, context?: Record<string, any>): SVGError {
|
|
103
|
+
if ('code' in error && 'severity' in error) {
|
|
104
|
+
return error as SVGError;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Convert regular Error to SVGError
|
|
108
|
+
const regularError = error as Error;
|
|
109
|
+
return {
|
|
110
|
+
code: this.categorizeError(regularError),
|
|
111
|
+
message: regularError.message,
|
|
112
|
+
severity: this.determineSeverity(regularError),
|
|
113
|
+
context: context || {},
|
|
114
|
+
timestamp: Date.now(),
|
|
115
|
+
stack: regularError.stack
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private categorizeError(error: Error): string {
|
|
120
|
+
const message = error.message.toLowerCase();
|
|
121
|
+
|
|
122
|
+
if (message.includes('file not found') || message.includes('enoent')) {
|
|
123
|
+
return 'FILE_NOT_FOUND';
|
|
124
|
+
}
|
|
125
|
+
if (message.includes('permission') || message.includes('eacces')) {
|
|
126
|
+
return 'PERMISSION_DENIED';
|
|
127
|
+
}
|
|
128
|
+
if (message.includes('parse') || message.includes('syntax')) {
|
|
129
|
+
return 'PARSE_ERROR';
|
|
130
|
+
}
|
|
131
|
+
if (message.includes('timeout')) {
|
|
132
|
+
return 'TIMEOUT_ERROR';
|
|
133
|
+
}
|
|
134
|
+
if (message.includes('network') || message.includes('connection')) {
|
|
135
|
+
return 'NETWORK_ERROR';
|
|
136
|
+
}
|
|
137
|
+
if (message.includes('svg') && message.includes('invalid')) {
|
|
138
|
+
return 'INVALID_SVG';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return 'UNKNOWN_ERROR';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private determineSeverity(error: Error): SVGError['severity'] {
|
|
145
|
+
const message = error.message.toLowerCase();
|
|
146
|
+
|
|
147
|
+
if (message.includes('critical') || message.includes('fatal')) {
|
|
148
|
+
return 'critical';
|
|
149
|
+
}
|
|
150
|
+
if (message.includes('file not found') || message.includes('permission')) {
|
|
151
|
+
return 'high';
|
|
152
|
+
}
|
|
153
|
+
if (message.includes('parse') || message.includes('invalid')) {
|
|
154
|
+
return 'medium';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return 'low';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private logError(error: SVGError): void {
|
|
161
|
+
const logMessage = `[${error.code}] ${error.message}`;
|
|
162
|
+
|
|
163
|
+
switch (error.severity) {
|
|
164
|
+
case 'critical':
|
|
165
|
+
logger.error('CRITICAL:', logMessage, error.context);
|
|
166
|
+
break;
|
|
167
|
+
case 'high':
|
|
168
|
+
logger.error('HIGH:', logMessage, error.context);
|
|
169
|
+
break;
|
|
170
|
+
case 'medium':
|
|
171
|
+
logger.warn('MEDIUM:', logMessage, error.context);
|
|
172
|
+
break;
|
|
173
|
+
case 'low':
|
|
174
|
+
logger.info('LOW:', logMessage, error.context);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private addToHistory(error: SVGError): void {
|
|
180
|
+
this.errorHistory.push(error);
|
|
181
|
+
|
|
182
|
+
// Maintain history size limit
|
|
183
|
+
if (this.errorHistory.length > this.maxHistorySize) {
|
|
184
|
+
this.errorHistory = this.errorHistory.slice(-this.maxHistorySize);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async attemptRecovery(
|
|
189
|
+
error: SVGError,
|
|
190
|
+
context?: any
|
|
191
|
+
): Promise<{ recovered: boolean; result?: any }> {
|
|
192
|
+
const strategy = this.recoveryStrategies.get(error.code);
|
|
193
|
+
|
|
194
|
+
if (!strategy) {
|
|
195
|
+
logger.debug(`No recovery strategy found for error code: ${error.code}`);
|
|
196
|
+
return { recovered: false };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
if (!strategy.canRecover(error)) {
|
|
201
|
+
logger.debug(`Recovery strategy declined to handle error: ${error.code}`);
|
|
202
|
+
return { recovered: false };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
logger.info(`Attempting recovery for error: ${error.code}`);
|
|
206
|
+
const result = await strategy.recover(error, context);
|
|
207
|
+
|
|
208
|
+
logger.success(`Successfully recovered from error: ${error.code}`);
|
|
209
|
+
return { recovered: true, result };
|
|
210
|
+
|
|
211
|
+
} catch (recoveryError) {
|
|
212
|
+
logger.error(`Recovery failed for error ${error.code}:`, recoveryError);
|
|
213
|
+
return { recovered: false };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private setupDefaultStrategies(): void {
|
|
218
|
+
// File not found recovery
|
|
219
|
+
this.registerRecoveryStrategy('FILE_NOT_FOUND', {
|
|
220
|
+
canRecover: (error) => error.context?.filePath && error.context?.canSkip === true,
|
|
221
|
+
recover: async (error, context) => {
|
|
222
|
+
logger.warn(`Skipping missing file: ${error.context?.filePath}`);
|
|
223
|
+
return { skipped: true, filePath: error.context?.filePath };
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Invalid SVG recovery
|
|
228
|
+
this.registerRecoveryStrategy('INVALID_SVG', {
|
|
229
|
+
canRecover: (error) => error.context?.svgContent,
|
|
230
|
+
recover: async (error, context) => {
|
|
231
|
+
logger.info('Attempting to clean invalid SVG content');
|
|
232
|
+
|
|
233
|
+
// Basic SVG cleanup
|
|
234
|
+
let cleaned = error.context?.svgContent || '';
|
|
235
|
+
|
|
236
|
+
// Remove potentially problematic content
|
|
237
|
+
cleaned = cleaned
|
|
238
|
+
.replace(/<script[\s\S]*?<\/script>/gi, '') // Remove scripts
|
|
239
|
+
.replace(/<style[\s\S]*?<\/style>/gi, '') // Remove styles
|
|
240
|
+
.replace(/on\w+="[^"]*"/gi, '') // Remove event handlers
|
|
241
|
+
.replace(/javascript:[^"']*/gi, ''); // Remove javascript: URLs
|
|
242
|
+
|
|
243
|
+
return { cleanedContent: cleaned };
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Permission denied recovery
|
|
248
|
+
this.registerRecoveryStrategy('PERMISSION_DENIED', {
|
|
249
|
+
canRecover: (error) => error.context?.alternative,
|
|
250
|
+
recover: async (error, context) => {
|
|
251
|
+
logger.warn(`Using alternative path due to permission issue: ${error.context?.alternative}`);
|
|
252
|
+
return { alternativePath: error.context?.alternative };
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
logger.debug('Default error recovery strategies loaded');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Export singleton instance and utilities
|
|
261
|
+
export const errorHandler = SVGErrorHandler.getInstance();
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Utility function to wrap async operations with error handling
|
|
265
|
+
*/
|
|
266
|
+
export async function withErrorHandling<T>(
|
|
267
|
+
operation: () => Promise<T>,
|
|
268
|
+
context?: Record<string, any>
|
|
269
|
+
): Promise<T | null> {
|
|
270
|
+
try {
|
|
271
|
+
return await operation();
|
|
272
|
+
} catch (error) {
|
|
273
|
+
const result = await errorHandler.handleError(error as Error, context);
|
|
274
|
+
|
|
275
|
+
if (result.recovered) {
|
|
276
|
+
return result.result as T;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Re-throw if not recovered and severity is high
|
|
280
|
+
const svgError = error as any;
|
|
281
|
+
if (svgError.severity === 'high' || svgError.severity === 'critical') {
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Decorator for automatic error handling
|
|
291
|
+
*/
|
|
292
|
+
export function handleErrors(context?: Record<string, any>) {
|
|
293
|
+
return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
|
|
294
|
+
const method = descriptor.value;
|
|
295
|
+
|
|
296
|
+
descriptor.value = async function (...args: any[]) {
|
|
297
|
+
return withErrorHandling(
|
|
298
|
+
() => method.apply(this, args),
|
|
299
|
+
{ method: propertyName, ...context }
|
|
300
|
+
);
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
}
|