vectify 2.0.5 → 2.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/README.md +12 -13
- package/README.zh-CN.md +12 -13
- package/dist/{chunk-CIKTK6HI.mjs → chunk-FS34P27H.mjs} +8 -0
- package/dist/{chunk-GY4VNET5.mjs → chunk-QYE23M3E.mjs} +625 -65
- package/dist/cli.js +676 -117
- package/dist/cli.mjs +3 -3
- package/dist/{helpers-UPZEBRGK.mjs → helpers-ZOR3OD66.mjs} +1 -1
- package/dist/index.d.mts +33 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +669 -110
- package/dist/index.mjs +2 -2
- package/dist/templates/angular/component.ts.hbs +11 -2
- package/dist/templates/angular/createIcon.ts.hbs +11 -2
- package/dist/templates/astro/component.astro.hbs +12 -2
- package/dist/templates/astro/createIcon.astro.hbs +12 -2
- package/dist/templates/lit/component.js.hbs +1 -0
- package/dist/templates/lit/component.ts.hbs +1 -0
- package/dist/templates/lit/createIcon.js.hbs +12 -2
- package/dist/templates/lit/createIcon.ts.hbs +12 -2
- package/dist/templates/preact/component.jsx.hbs +1 -1
- package/dist/templates/preact/component.tsx.hbs +1 -1
- package/dist/templates/preact/createIcon.jsx.hbs +12 -3
- package/dist/templates/preact/createIcon.tsx.hbs +13 -3
- package/dist/templates/qwik/component.jsx.hbs +1 -1
- package/dist/templates/qwik/component.tsx.hbs +1 -1
- package/dist/templates/qwik/createIcon.jsx.hbs +12 -3
- package/dist/templates/qwik/createIcon.tsx.hbs +13 -3
- package/dist/templates/react/component.jsx.hbs +1 -1
- package/dist/templates/react/component.tsx.hbs +1 -1
- package/dist/templates/react/createIcon.jsx.hbs +12 -3
- package/dist/templates/react/createIcon.tsx.hbs +13 -3
- package/dist/templates/solid/component.tsx.hbs +1 -1
- package/dist/templates/solid/createIcon.jsx.hbs +13 -3
- package/dist/templates/solid/createIcon.tsx.hbs +14 -3
- package/dist/templates/svelte/component.js.svelte.hbs +4 -1
- package/dist/templates/svelte/component.ts.svelte.hbs +4 -1
- package/dist/templates/svelte/icon.js.svelte.hbs +23 -2
- package/dist/templates/svelte/icon.ts.svelte.hbs +23 -2
- package/dist/templates/vanilla/component.ts.hbs +1 -1
- package/dist/templates/vanilla/createIcon.js.hbs +12 -3
- package/dist/templates/vanilla/createIcon.ts.hbs +13 -3
- package/dist/templates/vue/component.js.vue.hbs +5 -2
- package/dist/templates/vue/component.ts.vue.hbs +5 -2
- package/dist/templates/vue/icon.js.vue.hbs +26 -2
- package/dist/templates/vue/icon.ts.vue.hbs +26 -2
- package/dist/templates/vue2/component.js.vue.hbs +4 -2
- package/dist/templates/vue2/component.ts.vue.hbs +5 -2
- package/dist/templates/vue2/icon.js.vue.hbs +25 -2
- package/dist/templates/vue2/icon.ts.vue.hbs +25 -2
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
__require,
|
|
2
3
|
ensureDir,
|
|
3
4
|
fileExists,
|
|
4
5
|
findProjectRoot,
|
|
@@ -6,7 +7,7 @@ import {
|
|
|
6
7
|
getSvgFiles,
|
|
7
8
|
readFile,
|
|
8
9
|
writeFile
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-FS34P27H.mjs";
|
|
10
11
|
|
|
11
12
|
// src/parsers/svg-parser.ts
|
|
12
13
|
import * as cheerio from "cheerio";
|
|
@@ -28,6 +29,7 @@ function parseSvg(svgContent) {
|
|
|
28
29
|
if (svgElement.length === 0) {
|
|
29
30
|
throw new Error("Invalid SVG: No <svg> tag found");
|
|
30
31
|
}
|
|
32
|
+
const viewBox = svgElement.attr("viewBox") || "0 0 24 24";
|
|
31
33
|
const iconNodes = [];
|
|
32
34
|
svgElement.children().each((_, element) => {
|
|
33
35
|
const node = parseElement($, element);
|
|
@@ -35,7 +37,29 @@ function parseSvg(svgContent) {
|
|
|
35
37
|
iconNodes.push(node);
|
|
36
38
|
}
|
|
37
39
|
});
|
|
38
|
-
|
|
40
|
+
const isMultiColor = detectMultiColor(iconNodes);
|
|
41
|
+
return { iconNodes, viewBox, isMultiColor };
|
|
42
|
+
}
|
|
43
|
+
function detectMultiColor(nodes) {
|
|
44
|
+
const colors = /* @__PURE__ */ new Set();
|
|
45
|
+
function collectColors(node) {
|
|
46
|
+
const [, attrs, children] = node;
|
|
47
|
+
if (attrs.fill && attrs.fill !== "none" && attrs.fill !== "transparent") {
|
|
48
|
+
colors.add(String(attrs.fill).toLowerCase());
|
|
49
|
+
}
|
|
50
|
+
if (attrs.stroke && attrs.stroke !== "none" && attrs.stroke !== "transparent") {
|
|
51
|
+
colors.add(String(attrs.stroke).toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
if (children && children.length > 0) {
|
|
54
|
+
children.forEach(collectColors);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
nodes.forEach(collectColors);
|
|
58
|
+
const realColors = Array.from(colors).filter((color) => {
|
|
59
|
+
const c = color.toLowerCase();
|
|
60
|
+
return c !== "currentcolor";
|
|
61
|
+
});
|
|
62
|
+
return realColors.length >= 2;
|
|
39
63
|
}
|
|
40
64
|
function parseElement($, element) {
|
|
41
65
|
if (element.type !== "tag") {
|
|
@@ -164,14 +188,15 @@ function getQwikTemplatePath(typescript, type) {
|
|
|
164
188
|
function getAngularTemplatePath(type) {
|
|
165
189
|
return `angular/${type}.ts.hbs`;
|
|
166
190
|
}
|
|
167
|
-
function generateAngularComponent(componentName, iconNode, typescript, keepColors = false) {
|
|
191
|
+
function generateAngularComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
|
|
168
192
|
const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
|
|
169
193
|
const templatePath = getAngularTemplatePath("component");
|
|
170
194
|
return renderTemplate(templatePath, {
|
|
171
195
|
componentName,
|
|
172
196
|
formattedNodes,
|
|
173
197
|
typescript,
|
|
174
|
-
keepColors
|
|
198
|
+
keepColors,
|
|
199
|
+
viewBox
|
|
175
200
|
});
|
|
176
201
|
}
|
|
177
202
|
function generateAngularBaseComponent(typescript) {
|
|
@@ -186,14 +211,15 @@ function generateAngularBaseComponent(typescript) {
|
|
|
186
211
|
function getAstroTemplatePath(type) {
|
|
187
212
|
return `astro/${type}.astro.hbs`;
|
|
188
213
|
}
|
|
189
|
-
function generateAstroComponent(componentName, iconNode, typescript, keepColors = false) {
|
|
214
|
+
function generateAstroComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
|
|
190
215
|
const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
|
|
191
216
|
const templatePath = getAstroTemplatePath("component");
|
|
192
217
|
return renderTemplate(templatePath, {
|
|
193
218
|
componentName,
|
|
194
219
|
formattedNodes,
|
|
195
220
|
typescript,
|
|
196
|
-
keepColors
|
|
221
|
+
keepColors,
|
|
222
|
+
viewBox
|
|
197
223
|
});
|
|
198
224
|
}
|
|
199
225
|
function generateAstroBaseComponent(typescript) {
|
|
@@ -209,14 +235,15 @@ function getLitTemplatePath(typescript, type) {
|
|
|
209
235
|
const ext = typescript ? "ts" : "js";
|
|
210
236
|
return `lit/${type}.${ext}.hbs`;
|
|
211
237
|
}
|
|
212
|
-
function generateLitComponent(componentName, iconNode, typescript, keepColors = false) {
|
|
238
|
+
function generateLitComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
|
|
213
239
|
const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
|
|
214
240
|
const templatePath = getLitTemplatePath(typescript, "component");
|
|
215
241
|
return renderTemplate(templatePath, {
|
|
216
242
|
componentName,
|
|
217
243
|
formattedNodes,
|
|
218
244
|
typescript,
|
|
219
|
-
keepColors
|
|
245
|
+
keepColors,
|
|
246
|
+
viewBox
|
|
220
247
|
});
|
|
221
248
|
}
|
|
222
249
|
function generateLitBaseComponent(typescript) {
|
|
@@ -228,14 +255,15 @@ function generateLitBaseComponent(typescript) {
|
|
|
228
255
|
}
|
|
229
256
|
|
|
230
257
|
// src/generators/react.ts
|
|
231
|
-
function generateReactComponent(componentName, iconNode, typescript, keepColors = false) {
|
|
258
|
+
function generateReactComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
|
|
232
259
|
const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
|
|
233
260
|
const templatePath = getReactTemplatePath(typescript, "component");
|
|
234
261
|
return renderTemplate(templatePath, {
|
|
235
262
|
typescript,
|
|
236
263
|
componentName,
|
|
237
264
|
formattedNodes,
|
|
238
|
-
keepColors
|
|
265
|
+
keepColors,
|
|
266
|
+
viewBox
|
|
239
267
|
});
|
|
240
268
|
}
|
|
241
269
|
function generateCreateIcon(typescript) {
|
|
@@ -244,7 +272,7 @@ function generateCreateIcon(typescript) {
|
|
|
244
272
|
}
|
|
245
273
|
|
|
246
274
|
// src/generators/react-like.ts
|
|
247
|
-
function generateReactLikeComponent(componentName, iconNode, typescript, keepColors, framework) {
|
|
275
|
+
function generateReactLikeComponent(componentName, iconNode, typescript, keepColors, viewBox, framework) {
|
|
248
276
|
const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
|
|
249
277
|
let templatePath;
|
|
250
278
|
if (framework === "solid") {
|
|
@@ -260,7 +288,8 @@ function generateReactLikeComponent(componentName, iconNode, typescript, keepCol
|
|
|
260
288
|
componentName,
|
|
261
289
|
formattedNodes,
|
|
262
290
|
typescript,
|
|
263
|
-
keepColors
|
|
291
|
+
keepColors,
|
|
292
|
+
viewBox
|
|
264
293
|
});
|
|
265
294
|
}
|
|
266
295
|
function generateReactLikeBaseComponent(typescript, framework) {
|
|
@@ -281,12 +310,14 @@ function generateReactLikeBaseComponent(typescript, framework) {
|
|
|
281
310
|
}
|
|
282
311
|
|
|
283
312
|
// src/generators/svelte.ts
|
|
284
|
-
function generateSvelteComponent(_componentName, iconNode, typescript) {
|
|
313
|
+
function generateSvelteComponent(_componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
|
|
285
314
|
const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
|
|
286
315
|
const templatePath = getSvelteTemplatePath(typescript, "component");
|
|
287
316
|
return renderTemplate(templatePath, {
|
|
288
317
|
typescript,
|
|
289
|
-
formattedNodes
|
|
318
|
+
formattedNodes,
|
|
319
|
+
keepColors,
|
|
320
|
+
viewBox
|
|
290
321
|
});
|
|
291
322
|
}
|
|
292
323
|
function generateSvelteIcon(typescript) {
|
|
@@ -295,14 +326,15 @@ function generateSvelteIcon(typescript) {
|
|
|
295
326
|
}
|
|
296
327
|
|
|
297
328
|
// src/generators/vanilla.ts
|
|
298
|
-
function generateVanillaComponent(componentName, iconNode, typescript, keepColors) {
|
|
329
|
+
function generateVanillaComponent(componentName, iconNode, typescript, keepColors, viewBox) {
|
|
299
330
|
const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
|
|
300
331
|
const templatePath = getVanillaTemplatePath(typescript, "component");
|
|
301
332
|
return renderTemplate(templatePath, {
|
|
302
333
|
componentName,
|
|
303
334
|
formattedNodes,
|
|
304
335
|
typescript,
|
|
305
|
-
keepColors
|
|
336
|
+
keepColors,
|
|
337
|
+
viewBox
|
|
306
338
|
});
|
|
307
339
|
}
|
|
308
340
|
function generateVanillaBaseComponent(typescript) {
|
|
@@ -314,13 +346,15 @@ function generateVanillaBaseComponent(typescript) {
|
|
|
314
346
|
}
|
|
315
347
|
|
|
316
348
|
// src/generators/vue.ts
|
|
317
|
-
function generateVueComponent(componentName, iconNode, typescript) {
|
|
349
|
+
function generateVueComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
|
|
318
350
|
const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
|
|
319
351
|
const templatePath = getVueTemplatePath(typescript, "component");
|
|
320
352
|
return renderTemplate(templatePath, {
|
|
321
353
|
typescript,
|
|
322
354
|
componentName,
|
|
323
|
-
formattedNodes
|
|
355
|
+
formattedNodes,
|
|
356
|
+
keepColors,
|
|
357
|
+
viewBox
|
|
324
358
|
});
|
|
325
359
|
}
|
|
326
360
|
function generateVueIcon(typescript) {
|
|
@@ -329,13 +363,15 @@ function generateVueIcon(typescript) {
|
|
|
329
363
|
}
|
|
330
364
|
|
|
331
365
|
// src/generators/vue2.ts
|
|
332
|
-
function generateVue2Component(componentName, iconNode, typescript) {
|
|
366
|
+
function generateVue2Component(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
|
|
333
367
|
const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
|
|
334
368
|
const templatePath = getVue2TemplatePath(typescript, "component");
|
|
335
369
|
return renderTemplate(templatePath, {
|
|
336
370
|
typescript,
|
|
337
371
|
componentName,
|
|
338
|
-
formattedNodes
|
|
372
|
+
formattedNodes,
|
|
373
|
+
keepColors,
|
|
374
|
+
viewBox
|
|
339
375
|
});
|
|
340
376
|
}
|
|
341
377
|
function generateVue2Icon(typescript) {
|
|
@@ -353,8 +389,8 @@ var ReactStrategy = class {
|
|
|
353
389
|
this.getIndexExtension = (typescript) => {
|
|
354
390
|
return typescript ? "ts" : "js";
|
|
355
391
|
};
|
|
356
|
-
this.generateComponent = (componentName, iconNode, typescript, keepColors = false) => {
|
|
357
|
-
return generateReactComponent(componentName, iconNode, typescript, keepColors);
|
|
392
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
|
|
393
|
+
return generateReactComponent(componentName, iconNode, typescript, keepColors, viewBox);
|
|
358
394
|
};
|
|
359
395
|
this.generateBaseComponent = (typescript) => {
|
|
360
396
|
return {
|
|
@@ -373,8 +409,8 @@ var VueStrategy = class {
|
|
|
373
409
|
this.getIndexExtension = (typescript) => {
|
|
374
410
|
return typescript ? "ts" : "js";
|
|
375
411
|
};
|
|
376
|
-
this.generateComponent = (componentName, iconNode, typescript) => {
|
|
377
|
-
return generateVueComponent(componentName, iconNode, typescript);
|
|
412
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
|
|
413
|
+
return generateVueComponent(componentName, iconNode, typescript, keepColors, viewBox);
|
|
378
414
|
};
|
|
379
415
|
this.generateBaseComponent = (typescript) => {
|
|
380
416
|
return {
|
|
@@ -393,8 +429,8 @@ var Vue2Strategy = class {
|
|
|
393
429
|
this.getIndexExtension = (typescript) => {
|
|
394
430
|
return typescript ? "ts" : "js";
|
|
395
431
|
};
|
|
396
|
-
this.generateComponent = (componentName, iconNode, typescript) => {
|
|
397
|
-
return generateVue2Component(componentName, iconNode, typescript);
|
|
432
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
|
|
433
|
+
return generateVue2Component(componentName, iconNode, typescript, keepColors, viewBox);
|
|
398
434
|
};
|
|
399
435
|
this.generateBaseComponent = (typescript) => {
|
|
400
436
|
return {
|
|
@@ -413,8 +449,8 @@ var SvelteStrategy = class {
|
|
|
413
449
|
this.getIndexExtension = (typescript) => {
|
|
414
450
|
return typescript ? "ts" : "js";
|
|
415
451
|
};
|
|
416
|
-
this.generateComponent = (componentName, iconNode, typescript) => {
|
|
417
|
-
return generateSvelteComponent(componentName, iconNode, typescript);
|
|
452
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
|
|
453
|
+
return generateSvelteComponent(componentName, iconNode, typescript, keepColors, viewBox);
|
|
418
454
|
};
|
|
419
455
|
this.generateBaseComponent = (typescript) => {
|
|
420
456
|
return {
|
|
@@ -433,8 +469,8 @@ var SolidStrategy = class {
|
|
|
433
469
|
this.getIndexExtension = (typescript) => {
|
|
434
470
|
return typescript ? "ts" : "js";
|
|
435
471
|
};
|
|
436
|
-
this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
|
|
437
|
-
return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "solid");
|
|
472
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
|
|
473
|
+
return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "solid");
|
|
438
474
|
};
|
|
439
475
|
this.generateBaseComponent = (typescript) => {
|
|
440
476
|
return generateReactLikeBaseComponent(typescript, "solid");
|
|
@@ -450,8 +486,8 @@ var PreactStrategy = class {
|
|
|
450
486
|
this.getIndexExtension = (typescript) => {
|
|
451
487
|
return typescript ? "ts" : "js";
|
|
452
488
|
};
|
|
453
|
-
this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
|
|
454
|
-
return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "preact");
|
|
489
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
|
|
490
|
+
return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "preact");
|
|
455
491
|
};
|
|
456
492
|
this.generateBaseComponent = (typescript) => {
|
|
457
493
|
return generateReactLikeBaseComponent(typescript, "preact");
|
|
@@ -467,8 +503,8 @@ var VanillaStrategy = class {
|
|
|
467
503
|
this.getIndexExtension = (typescript) => {
|
|
468
504
|
return typescript ? "ts" : "js";
|
|
469
505
|
};
|
|
470
|
-
this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
|
|
471
|
-
return generateVanillaComponent(componentName, iconNode, typescript, keepColors ?? false);
|
|
506
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
|
|
507
|
+
return generateVanillaComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
|
|
472
508
|
};
|
|
473
509
|
this.generateBaseComponent = (typescript) => {
|
|
474
510
|
return generateVanillaBaseComponent(typescript);
|
|
@@ -484,8 +520,8 @@ var LitStrategy = class {
|
|
|
484
520
|
this.getIndexExtension = (typescript) => {
|
|
485
521
|
return typescript ? "ts" : "js";
|
|
486
522
|
};
|
|
487
|
-
this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
|
|
488
|
-
return generateLitComponent(componentName, iconNode, typescript, keepColors ?? false);
|
|
523
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
|
|
524
|
+
return generateLitComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
|
|
489
525
|
};
|
|
490
526
|
this.generateBaseComponent = (typescript) => {
|
|
491
527
|
return generateLitBaseComponent(typescript);
|
|
@@ -501,8 +537,8 @@ var QwikStrategy = class {
|
|
|
501
537
|
this.getIndexExtension = (typescript) => {
|
|
502
538
|
return typescript ? "ts" : "js";
|
|
503
539
|
};
|
|
504
|
-
this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
|
|
505
|
-
return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "qwik");
|
|
540
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
|
|
541
|
+
return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "qwik");
|
|
506
542
|
};
|
|
507
543
|
this.generateBaseComponent = (typescript) => {
|
|
508
544
|
return generateReactLikeBaseComponent(typescript, "qwik");
|
|
@@ -518,8 +554,8 @@ var AstroStrategy = class {
|
|
|
518
554
|
this.getIndexExtension = (typescript) => {
|
|
519
555
|
return typescript ? "ts" : "js";
|
|
520
556
|
};
|
|
521
|
-
this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
|
|
522
|
-
return generateAstroComponent(componentName, iconNode, typescript, keepColors ?? false);
|
|
557
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
|
|
558
|
+
return generateAstroComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
|
|
523
559
|
};
|
|
524
560
|
this.generateBaseComponent = (typescript) => {
|
|
525
561
|
return generateAstroBaseComponent(typescript);
|
|
@@ -535,8 +571,8 @@ var AngularStrategy = class {
|
|
|
535
571
|
this.getIndexExtension = (_typescript) => {
|
|
536
572
|
return "ts";
|
|
537
573
|
};
|
|
538
|
-
this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
|
|
539
|
-
return generateAngularComponent(componentName, iconNode, typescript, keepColors ?? false);
|
|
574
|
+
this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
|
|
575
|
+
return generateAngularComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
|
|
540
576
|
};
|
|
541
577
|
this.generateBaseComponent = (typescript) => {
|
|
542
578
|
return generateAngularBaseComponent(typescript);
|
|
@@ -601,7 +637,6 @@ var DEFAULT_CONFIG = {
|
|
|
601
637
|
output: "./src/icons",
|
|
602
638
|
typescript: true,
|
|
603
639
|
optimize: true,
|
|
604
|
-
keepColors: false,
|
|
605
640
|
prefix: "",
|
|
606
641
|
suffix: "",
|
|
607
642
|
configDir: ".",
|
|
@@ -664,7 +699,7 @@ async function loadConfig(configPath) {
|
|
|
664
699
|
return mergedConfig;
|
|
665
700
|
}
|
|
666
701
|
async function findConfig() {
|
|
667
|
-
const { fileExists: fileExists2 } = await import("./helpers-
|
|
702
|
+
const { fileExists: fileExists2 } = await import("./helpers-ZOR3OD66.mjs");
|
|
668
703
|
const configFiles = [
|
|
669
704
|
"vectify.config.ts",
|
|
670
705
|
"vectify.config.js"
|
|
@@ -681,6 +716,37 @@ async function findConfig() {
|
|
|
681
716
|
// src/generators/index.ts
|
|
682
717
|
import path4 from "path";
|
|
683
718
|
|
|
719
|
+
// src/cache/hash-utils.ts
|
|
720
|
+
import { createHash } from "crypto";
|
|
721
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
722
|
+
function hashContent(content) {
|
|
723
|
+
return createHash("sha256").update(content).digest("hex");
|
|
724
|
+
}
|
|
725
|
+
async function hashFile(filePath) {
|
|
726
|
+
try {
|
|
727
|
+
const content = await readFile2(filePath, "utf-8");
|
|
728
|
+
return hashContent(content);
|
|
729
|
+
} catch (error) {
|
|
730
|
+
throw new Error(`Failed to hash file ${filePath}: ${error}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function hashConfig(config) {
|
|
734
|
+
const relevantConfig = {
|
|
735
|
+
framework: config.framework,
|
|
736
|
+
typescript: config.typescript,
|
|
737
|
+
keepColors: config.keepColors,
|
|
738
|
+
prefix: config.prefix,
|
|
739
|
+
suffix: config.suffix,
|
|
740
|
+
optimize: config.optimize,
|
|
741
|
+
svgoConfig: config.svgoConfig,
|
|
742
|
+
componentNameTransform: config.componentNameTransform?.toString()
|
|
743
|
+
};
|
|
744
|
+
return hashContent(JSON.stringify(relevantConfig));
|
|
745
|
+
}
|
|
746
|
+
function hashSvgoConfig(svgoConfig) {
|
|
747
|
+
return hashContent(JSON.stringify(svgoConfig || {}));
|
|
748
|
+
}
|
|
749
|
+
|
|
684
750
|
// src/parsers/optimizer.ts
|
|
685
751
|
import { optimize } from "svgo";
|
|
686
752
|
var DEFAULT_SVGO_CONFIG = {
|
|
@@ -864,27 +930,130 @@ async function generateIcons(config, dryRun = false) {
|
|
|
864
930
|
}
|
|
865
931
|
return stats;
|
|
866
932
|
}
|
|
867
|
-
async function
|
|
933
|
+
async function generateIconsIncremental(config, cacheManager, dryRun = false) {
|
|
934
|
+
const stats = {
|
|
935
|
+
success: 0,
|
|
936
|
+
failed: 0,
|
|
937
|
+
total: 0,
|
|
938
|
+
errors: []
|
|
939
|
+
};
|
|
940
|
+
try {
|
|
941
|
+
if (!dryRun) {
|
|
942
|
+
await ensureDir(config.output);
|
|
943
|
+
}
|
|
944
|
+
const svgFiles = await getSvgFiles(config.input);
|
|
945
|
+
stats.total = svgFiles.length;
|
|
946
|
+
if (svgFiles.length === 0) {
|
|
947
|
+
console.warn(`No SVG files found in ${config.input}`);
|
|
948
|
+
return stats;
|
|
949
|
+
}
|
|
950
|
+
if (cacheManager.hasConfigChanged(config)) {
|
|
951
|
+
console.log("\u26A0\uFE0F Configuration changed, rebuilding all icons...");
|
|
952
|
+
cacheManager.invalidateAll();
|
|
953
|
+
}
|
|
954
|
+
cacheManager.updateConfigHash(config);
|
|
955
|
+
await cacheManager.cleanStaleEntries(svgFiles);
|
|
956
|
+
if (config.generateOptions?.cleanOutput && !dryRun) {
|
|
957
|
+
await cleanOutputDirectory(svgFiles, config);
|
|
958
|
+
}
|
|
959
|
+
await generateBaseComponent(config, dryRun);
|
|
960
|
+
const strategy = getFrameworkStrategy(config.framework);
|
|
961
|
+
const typescript = config.typescript ?? true;
|
|
962
|
+
const fileExt = strategy.getComponentExtension(typescript);
|
|
963
|
+
const filesToGenerate = [];
|
|
964
|
+
const cachedFiles = [];
|
|
965
|
+
for (const svgFile of svgFiles) {
|
|
966
|
+
const fileName = path4.basename(svgFile);
|
|
967
|
+
const componentName = getComponentName(
|
|
968
|
+
fileName,
|
|
969
|
+
config.prefix,
|
|
970
|
+
config.suffix,
|
|
971
|
+
config.componentNameTransform
|
|
972
|
+
);
|
|
973
|
+
const componentPath = path4.join(config.output, `${componentName}.${fileExt}`);
|
|
974
|
+
const needsRegen = await cacheManager.needsRegeneration(
|
|
975
|
+
svgFile,
|
|
976
|
+
componentPath,
|
|
977
|
+
config
|
|
978
|
+
);
|
|
979
|
+
if (needsRegen) {
|
|
980
|
+
filesToGenerate.push(svgFile);
|
|
981
|
+
} else {
|
|
982
|
+
cachedFiles.push(svgFile);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (cachedFiles.length > 0) {
|
|
986
|
+
console.log(`\u{1F4E6} Cache: ${cachedFiles.length} cached, ${filesToGenerate.length} to generate`);
|
|
987
|
+
}
|
|
988
|
+
for (const svgFile of filesToGenerate) {
|
|
989
|
+
try {
|
|
990
|
+
await generateIconComponent(svgFile, config, dryRun, cacheManager);
|
|
991
|
+
stats.success++;
|
|
992
|
+
} catch (error) {
|
|
993
|
+
stats.failed++;
|
|
994
|
+
stats.errors.push({
|
|
995
|
+
file: svgFile,
|
|
996
|
+
error: error.message
|
|
997
|
+
});
|
|
998
|
+
console.error(`Failed to generate ${svgFile}: ${error.message}`);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
stats.success += cachedFiles.length;
|
|
1002
|
+
if (config.generateOptions?.index) {
|
|
1003
|
+
await generateIndexFile(svgFiles, config, dryRun);
|
|
1004
|
+
}
|
|
1005
|
+
if (config.generateOptions?.preview && !dryRun) {
|
|
1006
|
+
await generatePreviewHtml(svgFiles, config);
|
|
1007
|
+
}
|
|
1008
|
+
if (!dryRun) {
|
|
1009
|
+
await cacheManager.save();
|
|
1010
|
+
const cacheStats = cacheManager.getStats();
|
|
1011
|
+
if (cacheStats.total > 0) {
|
|
1012
|
+
const hitRate = cacheManager.getHitRate();
|
|
1013
|
+
const timeSaved = (cacheStats.timeSaved / 1e3).toFixed(1);
|
|
1014
|
+
console.log(`\u26A1 Cache saved ~${timeSaved}s (${hitRate.toFixed(1)}% hit rate)`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (config.format && !dryRun) {
|
|
1018
|
+
const formatResult = await formatOutput(config.output, config.format);
|
|
1019
|
+
if (formatResult.success && formatResult.tool) {
|
|
1020
|
+
console.log(`Formatted with ${formatResult.tool}`);
|
|
1021
|
+
} else if (formatResult.error) {
|
|
1022
|
+
console.warn(formatResult.error);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (config.hooks?.onComplete) {
|
|
1026
|
+
await config.hooks.onComplete(stats);
|
|
1027
|
+
}
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
throw new Error(`Generation failed: ${error.message}`);
|
|
1030
|
+
}
|
|
1031
|
+
return stats;
|
|
1032
|
+
}
|
|
1033
|
+
async function generateIconComponent(svgFile, config, dryRun = false, cacheManager) {
|
|
868
1034
|
let svgContent = await readFile(svgFile);
|
|
869
1035
|
const fileName = path4.basename(svgFile);
|
|
870
1036
|
if (config.hooks?.beforeParse) {
|
|
871
1037
|
svgContent = await config.hooks.beforeParse(svgContent, fileName);
|
|
872
1038
|
}
|
|
1039
|
+
const { isMultiColor: isMultiColorBeforeOptimization } = parseSvg(svgContent);
|
|
873
1040
|
svgContent = await optimizeSvg(svgContent, config);
|
|
874
|
-
const
|
|
1041
|
+
const { iconNodes, viewBox } = parseSvg(svgContent);
|
|
875
1042
|
const componentName = getComponentName(
|
|
876
1043
|
fileName,
|
|
877
1044
|
config.prefix,
|
|
878
1045
|
config.suffix,
|
|
879
|
-
config.
|
|
1046
|
+
config.componentNameTransform
|
|
880
1047
|
);
|
|
881
1048
|
const strategy = getFrameworkStrategy(config.framework);
|
|
882
1049
|
const typescript = config.typescript ?? true;
|
|
1050
|
+
const keepColors = config.keepColors !== void 0 ? config.keepColors : isMultiColorBeforeOptimization;
|
|
883
1051
|
let code = strategy.generateComponent(
|
|
884
1052
|
componentName,
|
|
885
|
-
|
|
1053
|
+
iconNodes,
|
|
886
1054
|
typescript,
|
|
887
|
-
|
|
1055
|
+
keepColors,
|
|
1056
|
+
viewBox
|
|
888
1057
|
);
|
|
889
1058
|
if (config.hooks?.afterGenerate) {
|
|
890
1059
|
code = await config.hooks.afterGenerate(code, componentName);
|
|
@@ -895,6 +1064,16 @@ async function generateIconComponent(svgFile, config, dryRun = false) {
|
|
|
895
1064
|
console.log(` ${componentName}.${fileExt}`);
|
|
896
1065
|
} else {
|
|
897
1066
|
await writeFile(outputPath, code);
|
|
1067
|
+
if (cacheManager) {
|
|
1068
|
+
const componentHash = hashContent(code);
|
|
1069
|
+
await cacheManager.updateEntry(
|
|
1070
|
+
svgFile,
|
|
1071
|
+
outputPath,
|
|
1072
|
+
componentName,
|
|
1073
|
+
componentHash,
|
|
1074
|
+
config
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
898
1077
|
}
|
|
899
1078
|
}
|
|
900
1079
|
async function generateBaseComponent(config, dryRun = false) {
|
|
@@ -912,19 +1091,22 @@ async function generateIndexFile(svgFiles, config, dryRun = false) {
|
|
|
912
1091
|
const typescript = config.typescript ?? true;
|
|
913
1092
|
const strategy = getFrameworkStrategy(config.framework);
|
|
914
1093
|
const ext = strategy.getIndexExtension(typescript);
|
|
1094
|
+
const componentExt = strategy.getComponentExtension(typescript);
|
|
915
1095
|
const usesDefaultExport = ["vue", "vue2", "svelte", "react", "preact"].includes(config.framework);
|
|
1096
|
+
const needsExtension = ["vue", "vue2", "svelte"].includes(config.framework);
|
|
916
1097
|
const exports = svgFiles.map((svgFile) => {
|
|
917
1098
|
const fileName = path4.basename(svgFile);
|
|
918
1099
|
const componentName = getComponentName(
|
|
919
1100
|
fileName,
|
|
920
1101
|
config.prefix,
|
|
921
1102
|
config.suffix,
|
|
922
|
-
config.
|
|
1103
|
+
config.componentNameTransform
|
|
923
1104
|
);
|
|
1105
|
+
const importPath = needsExtension ? `./${componentName}.${componentExt}` : `./${componentName}`;
|
|
924
1106
|
if (usesDefaultExport) {
|
|
925
|
-
return `export { default as ${componentName} } from '
|
|
1107
|
+
return `export { default as ${componentName} } from '${importPath}'`;
|
|
926
1108
|
} else {
|
|
927
|
-
return `export { ${componentName} } from '
|
|
1109
|
+
return `export { ${componentName} } from '${importPath}'`;
|
|
928
1110
|
}
|
|
929
1111
|
}).join("\n");
|
|
930
1112
|
const indexPath = path4.join(config.output, `index.${ext}`);
|
|
@@ -942,7 +1124,7 @@ async function generatePreviewHtml(svgFiles, config) {
|
|
|
942
1124
|
fileName,
|
|
943
1125
|
config.prefix,
|
|
944
1126
|
config.suffix,
|
|
945
|
-
config.
|
|
1127
|
+
config.componentNameTransform
|
|
946
1128
|
);
|
|
947
1129
|
});
|
|
948
1130
|
const svgContents = await Promise.all(
|
|
@@ -1123,7 +1305,7 @@ async function cleanOutputDirectory(svgFiles, config) {
|
|
|
1123
1305
|
fileName,
|
|
1124
1306
|
config.prefix,
|
|
1125
1307
|
config.suffix,
|
|
1126
|
-
config.
|
|
1308
|
+
config.componentNameTransform
|
|
1127
1309
|
);
|
|
1128
1310
|
return `${componentName}.${fileExt}`;
|
|
1129
1311
|
})
|
|
@@ -1158,6 +1340,255 @@ async function cleanOutputDirectory(svgFiles, config) {
|
|
|
1158
1340
|
// src/commands/generate.ts
|
|
1159
1341
|
import chalk from "chalk";
|
|
1160
1342
|
import ora from "ora";
|
|
1343
|
+
|
|
1344
|
+
// src/cache/cache-manager.ts
|
|
1345
|
+
import { existsSync } from "fs";
|
|
1346
|
+
import { mkdir, readFile as readFile3, stat, writeFile as writeFile2 } from "fs/promises";
|
|
1347
|
+
import { dirname, join } from "path";
|
|
1348
|
+
var CACHE_VERSION = "1.0.0";
|
|
1349
|
+
var CacheManager = class {
|
|
1350
|
+
constructor(outputDir, cacheDir = ".vectify") {
|
|
1351
|
+
this.cache = {
|
|
1352
|
+
version: CACHE_VERSION,
|
|
1353
|
+
configHash: "",
|
|
1354
|
+
entries: {},
|
|
1355
|
+
baseComponentHash: ""
|
|
1356
|
+
};
|
|
1357
|
+
this.cacheFilePath = join(outputDir, cacheDir, "cache.json");
|
|
1358
|
+
this.stats = {
|
|
1359
|
+
hits: 0,
|
|
1360
|
+
misses: 0,
|
|
1361
|
+
total: 0,
|
|
1362
|
+
timeSaved: 0
|
|
1363
|
+
};
|
|
1364
|
+
this.isDirty = false;
|
|
1365
|
+
process.on("beforeExit", () => {
|
|
1366
|
+
if (this.isDirty) {
|
|
1367
|
+
this.saveSync();
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Load cache from disk
|
|
1373
|
+
*/
|
|
1374
|
+
async load() {
|
|
1375
|
+
try {
|
|
1376
|
+
if (!existsSync(this.cacheFilePath)) {
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
const content = await readFile3(this.cacheFilePath, "utf-8");
|
|
1380
|
+
const loadedCache = JSON.parse(content);
|
|
1381
|
+
if (loadedCache.version !== CACHE_VERSION) {
|
|
1382
|
+
console.log("\u26A0\uFE0F Cache version mismatch, rebuilding cache...");
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
this.cache = loadedCache;
|
|
1386
|
+
} catch (error) {
|
|
1387
|
+
console.warn("\u26A0\uFE0F Failed to load cache, starting fresh:", error);
|
|
1388
|
+
this.cache = {
|
|
1389
|
+
version: CACHE_VERSION,
|
|
1390
|
+
configHash: "",
|
|
1391
|
+
entries: {},
|
|
1392
|
+
baseComponentHash: ""
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Save cache to disk (atomic write)
|
|
1398
|
+
*/
|
|
1399
|
+
async save() {
|
|
1400
|
+
try {
|
|
1401
|
+
const cacheDir = dirname(this.cacheFilePath);
|
|
1402
|
+
await mkdir(cacheDir, { recursive: true });
|
|
1403
|
+
const tempPath = `${this.cacheFilePath}.tmp`;
|
|
1404
|
+
await writeFile2(tempPath, JSON.stringify(this.cache, null, 2), "utf-8");
|
|
1405
|
+
await writeFile2(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
|
|
1406
|
+
this.isDirty = false;
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
console.warn("\u26A0\uFE0F Failed to save cache:", error);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Synchronous save for process exit
|
|
1413
|
+
*/
|
|
1414
|
+
saveSync() {
|
|
1415
|
+
try {
|
|
1416
|
+
const fs2 = __require("fs");
|
|
1417
|
+
const cacheDir = dirname(this.cacheFilePath);
|
|
1418
|
+
if (!fs2.existsSync(cacheDir)) {
|
|
1419
|
+
fs2.mkdirSync(cacheDir, { recursive: true });
|
|
1420
|
+
}
|
|
1421
|
+
fs2.writeFileSync(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
|
|
1422
|
+
this.isDirty = false;
|
|
1423
|
+
} catch {
|
|
1424
|
+
console.warn("\u26A0\uFE0F Failed to save cache on exit");
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Check if a file needs regeneration
|
|
1429
|
+
*/
|
|
1430
|
+
async needsRegeneration(svgPath, componentPath, config) {
|
|
1431
|
+
this.stats.total++;
|
|
1432
|
+
if (!existsSync(componentPath)) {
|
|
1433
|
+
this.stats.misses++;
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
const entry = this.cache.entries[svgPath];
|
|
1437
|
+
if (!entry) {
|
|
1438
|
+
this.stats.misses++;
|
|
1439
|
+
return true;
|
|
1440
|
+
}
|
|
1441
|
+
try {
|
|
1442
|
+
const stats = await stat(svgPath);
|
|
1443
|
+
const currentMtime = stats.mtimeMs;
|
|
1444
|
+
if (currentMtime === entry.svgMtime) {
|
|
1445
|
+
if (!this.isConfigMatching(entry, config)) {
|
|
1446
|
+
this.stats.misses++;
|
|
1447
|
+
return true;
|
|
1448
|
+
}
|
|
1449
|
+
this.stats.hits++;
|
|
1450
|
+
this.stats.timeSaved += 50;
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
const currentHash = await hashFile(svgPath);
|
|
1454
|
+
if (currentHash === entry.svgHash) {
|
|
1455
|
+
entry.svgMtime = currentMtime;
|
|
1456
|
+
this.isDirty = true;
|
|
1457
|
+
this.stats.hits++;
|
|
1458
|
+
this.stats.timeSaved += 50;
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
this.stats.misses++;
|
|
1462
|
+
return true;
|
|
1463
|
+
} catch {
|
|
1464
|
+
this.stats.misses++;
|
|
1465
|
+
return true;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Check if config matches cache entry
|
|
1470
|
+
*/
|
|
1471
|
+
isConfigMatching(entry, config) {
|
|
1472
|
+
const snapshot = entry.configSnapshot;
|
|
1473
|
+
return snapshot.framework === config.framework && snapshot.typescript === config.typescript && snapshot.keepColors === config.keepColors && snapshot.prefix === (config.prefix || "") && snapshot.suffix === (config.suffix || "") && snapshot.optimize === config.optimize && snapshot.svgoConfigHash === hashSvgoConfig(config.svgoConfig);
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Update cache entry after successful generation
|
|
1477
|
+
*/
|
|
1478
|
+
async updateEntry(svgPath, componentPath, componentName, componentHash, config) {
|
|
1479
|
+
try {
|
|
1480
|
+
const svgHash = await hashFile(svgPath);
|
|
1481
|
+
const stats = await stat(svgPath);
|
|
1482
|
+
this.cache.entries[svgPath] = {
|
|
1483
|
+
svgPath,
|
|
1484
|
+
svgHash,
|
|
1485
|
+
svgMtime: stats.mtimeMs,
|
|
1486
|
+
componentName,
|
|
1487
|
+
componentPath,
|
|
1488
|
+
componentHash,
|
|
1489
|
+
configSnapshot: {
|
|
1490
|
+
framework: config.framework,
|
|
1491
|
+
typescript: config.typescript ?? true,
|
|
1492
|
+
keepColors: config.keepColors ?? false,
|
|
1493
|
+
prefix: config.prefix || "",
|
|
1494
|
+
suffix: config.suffix || "",
|
|
1495
|
+
optimize: config.optimize ?? true,
|
|
1496
|
+
svgoConfigHash: hashSvgoConfig(config.svgoConfig)
|
|
1497
|
+
},
|
|
1498
|
+
generatedAt: Date.now()
|
|
1499
|
+
};
|
|
1500
|
+
this.isDirty = true;
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
console.warn(`\u26A0\uFE0F Failed to update cache entry for ${svgPath}:`, error);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Remove cache entry
|
|
1507
|
+
*/
|
|
1508
|
+
removeEntry(svgPath) {
|
|
1509
|
+
if (this.cache.entries[svgPath]) {
|
|
1510
|
+
delete this.cache.entries[svgPath];
|
|
1511
|
+
this.isDirty = true;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Update config hash
|
|
1516
|
+
*/
|
|
1517
|
+
updateConfigHash(config) {
|
|
1518
|
+
const newHash = hashConfig(config);
|
|
1519
|
+
if (this.cache.configHash !== newHash) {
|
|
1520
|
+
this.cache.configHash = newHash;
|
|
1521
|
+
this.isDirty = true;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Update base component hash
|
|
1526
|
+
*/
|
|
1527
|
+
updateBaseComponentHash(hash) {
|
|
1528
|
+
if (this.cache.baseComponentHash !== hash) {
|
|
1529
|
+
this.cache.baseComponentHash = hash;
|
|
1530
|
+
this.isDirty = true;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Check if config has changed (invalidates all cache)
|
|
1535
|
+
*/
|
|
1536
|
+
hasConfigChanged(config) {
|
|
1537
|
+
const currentHash = hashConfig(config);
|
|
1538
|
+
return this.cache.configHash !== "" && this.cache.configHash !== currentHash;
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Invalidate all cache entries
|
|
1542
|
+
*/
|
|
1543
|
+
invalidateAll() {
|
|
1544
|
+
this.cache.entries = {};
|
|
1545
|
+
this.isDirty = true;
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Clean stale entries (files that no longer exist)
|
|
1549
|
+
*/
|
|
1550
|
+
async cleanStaleEntries(existingSvgPaths) {
|
|
1551
|
+
const existingSet = new Set(existingSvgPaths);
|
|
1552
|
+
let cleaned = 0;
|
|
1553
|
+
for (const svgPath of Object.keys(this.cache.entries)) {
|
|
1554
|
+
if (!existingSet.has(svgPath)) {
|
|
1555
|
+
delete this.cache.entries[svgPath];
|
|
1556
|
+
cleaned++;
|
|
1557
|
+
this.isDirty = true;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
if (cleaned > 0) {
|
|
1561
|
+
console.log(`\u{1F9F9} Cleaned ${cleaned} stale cache entries`);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Get cache statistics
|
|
1566
|
+
*/
|
|
1567
|
+
getStats() {
|
|
1568
|
+
return { ...this.stats };
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Reset statistics
|
|
1572
|
+
*/
|
|
1573
|
+
resetStats() {
|
|
1574
|
+
this.stats = {
|
|
1575
|
+
hits: 0,
|
|
1576
|
+
misses: 0,
|
|
1577
|
+
total: 0,
|
|
1578
|
+
timeSaved: 0
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Get cache hit rate
|
|
1583
|
+
*/
|
|
1584
|
+
getHitRate() {
|
|
1585
|
+
if (this.stats.total === 0)
|
|
1586
|
+
return 0;
|
|
1587
|
+
return this.stats.hits / this.stats.total * 100;
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
// src/commands/generate.ts
|
|
1161
1592
|
async function generate(options = {}) {
|
|
1162
1593
|
const spinner = ora("Loading configuration...").start();
|
|
1163
1594
|
try {
|
|
@@ -1179,9 +1610,19 @@ async function generate(options = {}) {
|
|
|
1179
1610
|
${chalk.bold("Files that would be generated:")}
|
|
1180
1611
|
`);
|
|
1181
1612
|
}
|
|
1613
|
+
const useIncremental = config.incremental?.enabled !== false && !options.force && !options.dryRun;
|
|
1614
|
+
let cacheManager = null;
|
|
1615
|
+
if (useIncremental) {
|
|
1616
|
+
const cacheDir = config.incremental?.cacheDir || ".vectify";
|
|
1617
|
+
cacheManager = new CacheManager(config.output, cacheDir);
|
|
1618
|
+
await cacheManager.load();
|
|
1619
|
+
}
|
|
1620
|
+
if (options.force) {
|
|
1621
|
+
spinner.info(chalk.yellow("Force mode - ignoring cache, regenerating all icons"));
|
|
1622
|
+
}
|
|
1182
1623
|
const actionText = options.dryRun ? "Analyzing icons..." : "Generating icon components...";
|
|
1183
1624
|
spinner.start(actionText);
|
|
1184
|
-
const stats = await generateIcons(config, options.dryRun);
|
|
1625
|
+
const stats = cacheManager ? await generateIconsIncremental(config, cacheManager, options.dryRun) : await generateIcons(config, options.dryRun);
|
|
1185
1626
|
if (stats.failed > 0) {
|
|
1186
1627
|
spinner.warn(`${options.dryRun ? "Analyzed" : "Generated"} ${chalk.green(stats.success)} icons, ${chalk.red(stats.failed)} failed`);
|
|
1187
1628
|
if (stats.errors.length > 0) {
|
|
@@ -1373,6 +1814,89 @@ import path6 from "path";
|
|
|
1373
1814
|
import chalk3 from "chalk";
|
|
1374
1815
|
import chokidar from "chokidar";
|
|
1375
1816
|
import ora3 from "ora";
|
|
1817
|
+
|
|
1818
|
+
// src/cache/svg-validator.ts
|
|
1819
|
+
import { readFile as readFile4, stat as stat2 } from "fs/promises";
|
|
1820
|
+
import { load as load2 } from "cheerio";
|
|
1821
|
+
var MIN_SVG_SIZE = 20;
|
|
1822
|
+
var DRAWABLE_ELEMENTS = [
|
|
1823
|
+
"path",
|
|
1824
|
+
"circle",
|
|
1825
|
+
"rect",
|
|
1826
|
+
"ellipse",
|
|
1827
|
+
"line",
|
|
1828
|
+
"polyline",
|
|
1829
|
+
"polygon",
|
|
1830
|
+
"text",
|
|
1831
|
+
"image",
|
|
1832
|
+
"use"
|
|
1833
|
+
];
|
|
1834
|
+
async function validateSVGFile(filePath) {
|
|
1835
|
+
try {
|
|
1836
|
+
const stats = await stat2(filePath);
|
|
1837
|
+
if (stats.size < MIN_SVG_SIZE) {
|
|
1838
|
+
return {
|
|
1839
|
+
isValid: false,
|
|
1840
|
+
isEmpty: true,
|
|
1841
|
+
reason: "File is too small to be a valid SVG"
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
const content = await readFile4(filePath, "utf-8");
|
|
1845
|
+
if (!content.trim()) {
|
|
1846
|
+
return {
|
|
1847
|
+
isValid: false,
|
|
1848
|
+
isEmpty: true,
|
|
1849
|
+
reason: "File is empty"
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
if (!content.includes("<svg")) {
|
|
1853
|
+
return {
|
|
1854
|
+
isValid: false,
|
|
1855
|
+
isEmpty: false,
|
|
1856
|
+
reason: "File does not contain <svg> tag"
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
if (!hasDrawableContent(content)) {
|
|
1860
|
+
return {
|
|
1861
|
+
isValid: false,
|
|
1862
|
+
isEmpty: true,
|
|
1863
|
+
reason: "SVG has no drawable content"
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
try {
|
|
1867
|
+
const $ = load2(content, { xmlMode: true });
|
|
1868
|
+
const svgElement = $("svg");
|
|
1869
|
+
if (svgElement.length === 0) {
|
|
1870
|
+
return {
|
|
1871
|
+
isValid: false,
|
|
1872
|
+
isEmpty: false,
|
|
1873
|
+
reason: "Failed to parse SVG element"
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
return {
|
|
1877
|
+
isValid: true,
|
|
1878
|
+
isEmpty: false
|
|
1879
|
+
};
|
|
1880
|
+
} catch (parseError) {
|
|
1881
|
+
return {
|
|
1882
|
+
isValid: false,
|
|
1883
|
+
isEmpty: false,
|
|
1884
|
+
reason: `Failed to parse SVG: ${parseError}`
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
} catch (error) {
|
|
1888
|
+
return {
|
|
1889
|
+
isValid: false,
|
|
1890
|
+
isEmpty: false,
|
|
1891
|
+
reason: `Failed to read file: ${error}`
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
function hasDrawableContent(content) {
|
|
1896
|
+
return DRAWABLE_ELEMENTS.some((element) => content.includes(`<${element}`));
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// src/commands/watch.ts
|
|
1376
1900
|
async function watch(options = {}) {
|
|
1377
1901
|
const spinner = ora3("Loading configuration...").start();
|
|
1378
1902
|
try {
|
|
@@ -1388,16 +1912,24 @@ async function watch(options = {}) {
|
|
|
1388
1912
|
}
|
|
1389
1913
|
const config = await loadConfig(configPath);
|
|
1390
1914
|
spinner.succeed(`Config loaded from ${chalk3.green(configPath)}`);
|
|
1915
|
+
const useIncremental = config.incremental?.enabled !== false;
|
|
1916
|
+
const cacheDir = config.incremental?.cacheDir || ".vectify";
|
|
1917
|
+
const cacheManager = useIncremental ? new CacheManager(config.output, cacheDir) : null;
|
|
1918
|
+
if (cacheManager) {
|
|
1919
|
+
await cacheManager.load();
|
|
1920
|
+
}
|
|
1391
1921
|
spinner.start("Generating icon components...");
|
|
1392
|
-
const initialStats = await generateIcons(config);
|
|
1922
|
+
const initialStats = useIncremental && cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
|
|
1393
1923
|
spinner.succeed(`Generated ${chalk3.green(initialStats.success)} icon components`);
|
|
1394
1924
|
const watchPath = path6.join(config.input, "**/*.svg");
|
|
1395
1925
|
const debounce = config.watch?.debounce ?? 300;
|
|
1396
1926
|
const ignore = config.watch?.ignore ?? ["**/node_modules/**", "**/.git/**"];
|
|
1927
|
+
const emptyFileRetryDelay = config.watch?.emptyFileRetryDelay ?? 2e3;
|
|
1397
1928
|
console.log(chalk3.bold("\nWatching for changes..."));
|
|
1398
1929
|
console.log(` ${chalk3.cyan(watchPath)}`);
|
|
1399
1930
|
console.log(` ${chalk3.gray("Press Ctrl+C to stop")}
|
|
1400
1931
|
`);
|
|
1932
|
+
const pendingChanges = /* @__PURE__ */ new Map();
|
|
1401
1933
|
const debounceTimer = null;
|
|
1402
1934
|
const watcher = chokidar.watch(watchPath, {
|
|
1403
1935
|
ignored: ignore,
|
|
@@ -1405,12 +1937,12 @@ async function watch(options = {}) {
|
|
|
1405
1937
|
ignoreInitial: true
|
|
1406
1938
|
});
|
|
1407
1939
|
watcher.on("add", (filePath) => {
|
|
1408
|
-
handleChange("added", filePath, config, debounce, debounceTimer);
|
|
1940
|
+
handleChange("added", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
|
|
1409
1941
|
}).on("change", (filePath) => {
|
|
1410
|
-
handleChange("changed", filePath, config, debounce, debounceTimer);
|
|
1942
|
+
handleChange("changed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
|
|
1411
1943
|
}).on("unlink", (filePath) => {
|
|
1412
1944
|
console.log(chalk3.yellow(`SVG file removed: ${path6.basename(filePath)}`));
|
|
1413
|
-
handleChange("removed", filePath, config, debounce, debounceTimer);
|
|
1945
|
+
handleChange("removed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
|
|
1414
1946
|
}).on("error", (error) => {
|
|
1415
1947
|
console.error(chalk3.red(`Watcher error: ${error.message}`));
|
|
1416
1948
|
});
|
|
@@ -1427,23 +1959,51 @@ ${chalk3.yellow("Stopping watch mode...")}`);
|
|
|
1427
1959
|
throw error;
|
|
1428
1960
|
}
|
|
1429
1961
|
}
|
|
1430
|
-
function handleChange(event, filePath, config, debounce, timer) {
|
|
1431
|
-
|
|
1962
|
+
function handleChange(event, filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer) {
|
|
1963
|
+
pendingChanges.set(filePath, event);
|
|
1432
1964
|
if (timer) {
|
|
1433
1965
|
clearTimeout(timer);
|
|
1434
1966
|
}
|
|
1435
1967
|
timer = setTimeout(async () => {
|
|
1436
|
-
const
|
|
1968
|
+
const changes = Array.from(pendingChanges.entries());
|
|
1969
|
+
pendingChanges.clear();
|
|
1970
|
+
const validChanges = [];
|
|
1971
|
+
const invalidFiles = [];
|
|
1972
|
+
for (const [file, changeEvent] of changes) {
|
|
1973
|
+
if (changeEvent === "removed") {
|
|
1974
|
+
validChanges.push([file, changeEvent]);
|
|
1975
|
+
continue;
|
|
1976
|
+
}
|
|
1977
|
+
const validation = await validateSVGFile(file);
|
|
1978
|
+
if (!validation.isValid) {
|
|
1979
|
+
if (validation.isEmpty) {
|
|
1980
|
+
console.log(chalk3.yellow(`\u23F3 Waiting for content: ${path6.basename(file)}`));
|
|
1981
|
+
setTimeout(() => {
|
|
1982
|
+
handleChange(changeEvent, file, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer);
|
|
1983
|
+
}, emptyFileRetryDelay);
|
|
1984
|
+
} else {
|
|
1985
|
+
console.error(chalk3.red(`\u274C Invalid SVG: ${path6.basename(file)} - ${validation.reason}`));
|
|
1986
|
+
invalidFiles.push(file);
|
|
1987
|
+
}
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
validChanges.push([file, changeEvent]);
|
|
1991
|
+
}
|
|
1992
|
+
if (validChanges.length === 0) {
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
const spinner = ora3(`Processing ${validChanges.length} change(s)...`).start();
|
|
1437
1996
|
try {
|
|
1438
|
-
const stats = await generateIcons(config);
|
|
1997
|
+
const stats = cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
|
|
1439
1998
|
if (stats.failed > 0) {
|
|
1440
1999
|
spinner.warn(
|
|
1441
2000
|
`Regenerated ${chalk3.green(stats.success)} icons, ${chalk3.red(stats.failed)} failed`
|
|
1442
2001
|
);
|
|
1443
2002
|
} else {
|
|
1444
|
-
spinner.succeed(
|
|
2003
|
+
spinner.succeed(`\u2713 Updated ${chalk3.green(stats.success)} icon components`);
|
|
1445
2004
|
}
|
|
1446
|
-
|
|
2005
|
+
const triggerFiles = validChanges.map(([file, evt]) => `${path6.basename(file)} (${evt})`).join(", ");
|
|
2006
|
+
console.log(chalk3.gray(` Triggered by: ${triggerFiles}
|
|
1447
2007
|
`));
|
|
1448
2008
|
} catch (error) {
|
|
1449
2009
|
spinner.fail("Regeneration failed");
|