@getvitops/utils 0.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +217 -0
- package/dist/color/index.d.mts +74 -3
- package/dist/color/index.d.mts.map +1 -1
- package/dist/color/index.mjs +127 -123
- package/dist/color/index.mjs.map +1 -1
- package/dist/favicon.d.mts +13 -0
- package/dist/favicon.d.mts.map +1 -1
- package/dist/favicon.mjs +40 -28
- package/dist/favicon.mjs.map +1 -1
- package/dist/index.d.mts +216 -21
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +369 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { faviconLinks, faviconManifest, generateFavicons, writeFaviconManifest } from "./favicon.mjs";
|
|
2
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { extname, join } from "node:path";
|
|
2
4
|
import { parseFragment, serialize as serialize$1 } from "parse5";
|
|
3
5
|
|
|
4
6
|
//#region src/content.ts
|
|
@@ -327,8 +329,26 @@ const prefixToMapKey = {
|
|
|
327
329
|
"fa7-brands": "fa7-brands",
|
|
328
330
|
"simple-icons": "simple-icons",
|
|
329
331
|
"material-symbols": "material-symbols",
|
|
330
|
-
lucide: "lucide"
|
|
332
|
+
lucide: "lucide",
|
|
333
|
+
ph: "ph"
|
|
331
334
|
};
|
|
335
|
+
/**
|
|
336
|
+
* Sets whose weights are a name SUFFIX inside one collection rather than
|
|
337
|
+
* separate prefixed collections. Phosphor is the case that forced this:
|
|
338
|
+
* `ph:lightning`, `ph:lightning-bold`, `ph:lightning-fill` all live in `ph`,
|
|
339
|
+
* whereas Font Awesome splits the same idea across `fa7-solid`/`fa7-regular`/…
|
|
340
|
+
* (which is what `prefixToMapKey` collapses).
|
|
341
|
+
*
|
|
342
|
+
* The first entry is the unsuffixed default — asking for it adds nothing.
|
|
343
|
+
*/
|
|
344
|
+
const WEIGHTED_SETS = { ph: [
|
|
345
|
+
"regular",
|
|
346
|
+
"bold",
|
|
347
|
+
"duotone",
|
|
348
|
+
"fill",
|
|
349
|
+
"light",
|
|
350
|
+
"thin"
|
|
351
|
+
] };
|
|
332
352
|
const iconMap = {
|
|
333
353
|
fa7: {
|
|
334
354
|
menu: "bars",
|
|
@@ -336,12 +356,14 @@ const iconMap = {
|
|
|
336
356
|
home: "home",
|
|
337
357
|
settings: "gear",
|
|
338
358
|
search: "magnifying-glass",
|
|
339
|
-
"expand-more": "chevron-down",
|
|
340
|
-
"expand-less": "chevron-up",
|
|
341
|
-
"chevron-right": "chevron-right",
|
|
342
359
|
"chevron-left": "chevron-left",
|
|
343
|
-
"
|
|
344
|
-
"
|
|
360
|
+
"chevron-right": "chevron-right",
|
|
361
|
+
"chevron-up": "chevron-up",
|
|
362
|
+
"chevron-down": "chevron-down",
|
|
363
|
+
"arrow-left": "arrow-left",
|
|
364
|
+
"arrow-right": "arrow-right",
|
|
365
|
+
"arrow-up": "arrow-up",
|
|
366
|
+
"arrow-down": "arrow-down",
|
|
345
367
|
"external-link": "arrow-up-right-from-square",
|
|
346
368
|
"more-vert": "ellipsis-vertical",
|
|
347
369
|
"more-horiz": "ellipsis",
|
|
@@ -359,8 +381,8 @@ const iconMap = {
|
|
|
359
381
|
bookmark: "bookmark",
|
|
360
382
|
favorite: "heart",
|
|
361
383
|
star: "star",
|
|
362
|
-
login: "
|
|
363
|
-
logout: "
|
|
384
|
+
login: "right-to-bracket",
|
|
385
|
+
logout: "right-from-bracket",
|
|
364
386
|
email: "envelope",
|
|
365
387
|
phone: "phone",
|
|
366
388
|
location: "location-dot",
|
|
@@ -379,7 +401,7 @@ const iconMap = {
|
|
|
379
401
|
image: "image",
|
|
380
402
|
export: "file-export",
|
|
381
403
|
import: "file-import",
|
|
382
|
-
backup: "
|
|
404
|
+
backup: "cloud-arrow-up",
|
|
383
405
|
compress: "compress",
|
|
384
406
|
check: "check",
|
|
385
407
|
error: "circle-exclamation",
|
|
@@ -413,7 +435,8 @@ const iconMap = {
|
|
|
413
435
|
print: "print",
|
|
414
436
|
code: "code",
|
|
415
437
|
palette: "palette",
|
|
416
|
-
lightbulb: "lightbulb"
|
|
438
|
+
lightbulb: "lightbulb",
|
|
439
|
+
lightning: "bolt"
|
|
417
440
|
},
|
|
418
441
|
"fa7-brands": {
|
|
419
442
|
facebook: "facebook",
|
|
@@ -504,12 +527,14 @@ const iconMap = {
|
|
|
504
527
|
home: "home",
|
|
505
528
|
settings: "settings",
|
|
506
529
|
search: "search",
|
|
507
|
-
"expand-more": "expand-more",
|
|
508
|
-
"expand-less": "expand-less",
|
|
509
|
-
"chevron-right": "chevron-right",
|
|
510
530
|
"chevron-left": "chevron-left",
|
|
511
|
-
"
|
|
512
|
-
"
|
|
531
|
+
"chevron-right": "chevron-right",
|
|
532
|
+
"chevron-up": "expand-less",
|
|
533
|
+
"chevron-down": "expand-more",
|
|
534
|
+
"arrow-left": "arrow-back",
|
|
535
|
+
"arrow-right": "arrow-forward",
|
|
536
|
+
"arrow-up": "arrow-upward",
|
|
537
|
+
"arrow-down": "arrow-downward",
|
|
513
538
|
"external-link": "open-in-new",
|
|
514
539
|
"more-vert": "more-vert",
|
|
515
540
|
"more-horiz": "more-horiz",
|
|
@@ -581,7 +606,8 @@ const iconMap = {
|
|
|
581
606
|
print: "print",
|
|
582
607
|
code: "code",
|
|
583
608
|
palette: "palette",
|
|
584
|
-
lightbulb: "lightbulb"
|
|
609
|
+
lightbulb: "lightbulb",
|
|
610
|
+
lightning: "bolt"
|
|
585
611
|
},
|
|
586
612
|
lucide: {
|
|
587
613
|
menu: "menu",
|
|
@@ -589,12 +615,14 @@ const iconMap = {
|
|
|
589
615
|
home: "house",
|
|
590
616
|
settings: "settings",
|
|
591
617
|
search: "search",
|
|
592
|
-
"expand-more": "chevron-down",
|
|
593
|
-
"expand-less": "chevron-up",
|
|
594
|
-
"chevron-right": "chevron-right",
|
|
595
618
|
"chevron-left": "chevron-left",
|
|
596
|
-
"
|
|
597
|
-
"
|
|
619
|
+
"chevron-right": "chevron-right",
|
|
620
|
+
"chevron-up": "chevron-up",
|
|
621
|
+
"chevron-down": "chevron-down",
|
|
622
|
+
"arrow-left": "arrow-left",
|
|
623
|
+
"arrow-right": "arrow-right",
|
|
624
|
+
"arrow-up": "arrow-up",
|
|
625
|
+
"arrow-down": "arrow-down",
|
|
598
626
|
"external-link": "external-link",
|
|
599
627
|
"more-vert": "ellipsis-vertical",
|
|
600
628
|
"more-horiz": "ellipsis",
|
|
@@ -666,7 +694,96 @@ const iconMap = {
|
|
|
666
694
|
print: "printer",
|
|
667
695
|
code: "code",
|
|
668
696
|
palette: "palette",
|
|
669
|
-
lightbulb: "lightbulb"
|
|
697
|
+
lightbulb: "lightbulb",
|
|
698
|
+
lightning: "zap"
|
|
699
|
+
},
|
|
700
|
+
ph: {
|
|
701
|
+
menu: "list",
|
|
702
|
+
close: "x",
|
|
703
|
+
home: "house",
|
|
704
|
+
settings: "gear",
|
|
705
|
+
search: "magnifying-glass",
|
|
706
|
+
"chevron-left": "caret-left",
|
|
707
|
+
"chevron-right": "caret-right",
|
|
708
|
+
"chevron-up": "caret-up",
|
|
709
|
+
"chevron-down": "caret-down",
|
|
710
|
+
"arrow-left": "arrow-left",
|
|
711
|
+
"arrow-right": "arrow-right",
|
|
712
|
+
"arrow-up": "arrow-up",
|
|
713
|
+
"arrow-down": "arrow-down",
|
|
714
|
+
"external-link": "arrow-square-out",
|
|
715
|
+
"more-vert": "dots-three-vertical",
|
|
716
|
+
"more-horiz": "dots-three",
|
|
717
|
+
add: "plus",
|
|
718
|
+
remove: "minus",
|
|
719
|
+
delete: "trash",
|
|
720
|
+
edit: "pencil",
|
|
721
|
+
save: "floppy-disk",
|
|
722
|
+
copy: "copy",
|
|
723
|
+
paste: "clipboard",
|
|
724
|
+
attach: "paperclip",
|
|
725
|
+
upload: "upload",
|
|
726
|
+
download: "download",
|
|
727
|
+
share: "share",
|
|
728
|
+
bookmark: "bookmark",
|
|
729
|
+
favorite: "heart",
|
|
730
|
+
star: "star",
|
|
731
|
+
login: "sign-in",
|
|
732
|
+
logout: "sign-out",
|
|
733
|
+
email: "envelope",
|
|
734
|
+
phone: "phone",
|
|
735
|
+
location: "map-pin",
|
|
736
|
+
globe: "globe",
|
|
737
|
+
link: "link",
|
|
738
|
+
clock: "clock",
|
|
739
|
+
calendar: "calendar",
|
|
740
|
+
schedule: "calendar-dots",
|
|
741
|
+
person: "person",
|
|
742
|
+
group: "users",
|
|
743
|
+
file: "file",
|
|
744
|
+
"file-add": "file-plus",
|
|
745
|
+
folder: "folder",
|
|
746
|
+
"folder-open": "folder-open",
|
|
747
|
+
"folder-add": "folder-plus",
|
|
748
|
+
image: "image",
|
|
749
|
+
export: "upload-simple",
|
|
750
|
+
import: "download-simple",
|
|
751
|
+
backup: "cloud-arrow-up",
|
|
752
|
+
compress: "arrows-in",
|
|
753
|
+
check: "check",
|
|
754
|
+
error: "warning-circle",
|
|
755
|
+
warning: "warning",
|
|
756
|
+
info: "info",
|
|
757
|
+
help: "question",
|
|
758
|
+
cart: "shopping-cart",
|
|
759
|
+
tag: "tag",
|
|
760
|
+
"credit-card": "credit-card",
|
|
761
|
+
receipt: "receipt",
|
|
762
|
+
leaf: "leaf",
|
|
763
|
+
tree: "tree",
|
|
764
|
+
seedling: "plant",
|
|
765
|
+
trowel: "shovel",
|
|
766
|
+
droplet: "drop",
|
|
767
|
+
snowflake: "snowflake",
|
|
768
|
+
sun: "sun",
|
|
769
|
+
cloud: "cloud",
|
|
770
|
+
grid: "squares-four",
|
|
771
|
+
list: "list",
|
|
772
|
+
filter: "funnel",
|
|
773
|
+
sort: "arrows-down-up",
|
|
774
|
+
fullscreen: "corners-out",
|
|
775
|
+
collapse: "corners-in",
|
|
776
|
+
building: "building",
|
|
777
|
+
video: "video",
|
|
778
|
+
lock: "lock",
|
|
779
|
+
unlock: "lock-open",
|
|
780
|
+
eye: "eye",
|
|
781
|
+
"eye-off": "eye-slash",
|
|
782
|
+
print: "printer",
|
|
783
|
+
code: "code",
|
|
784
|
+
palette: "palette",
|
|
785
|
+
lightbulb: "lightbulb",
|
|
786
|
+
lightning: "lightning"
|
|
670
787
|
}
|
|
671
788
|
};
|
|
672
789
|
/**
|
|
@@ -674,15 +791,27 @@ const iconMap = {
|
|
|
674
791
|
* Names containing ':' are returned as-is (pass-through for fully-qualified names).
|
|
675
792
|
*
|
|
676
793
|
* @param name - Semantic name ('email') or fully-qualified ('fa7-solid:envelope')
|
|
677
|
-
* @param prefix - astro-icon prefix, e.g. 'fa7-solid', 'material-symbols', 'lucide'
|
|
794
|
+
* @param prefix - astro-icon prefix, e.g. 'fa7-solid', 'material-symbols', 'lucide', 'ph'
|
|
795
|
+
* @param opts.weight - Weight for suffix-weighted sets (see `WEIGHTED_SETS`), e.g.
|
|
796
|
+
* `resolveIcon('menu', 'ph', { weight: 'bold' })` → `'ph:list-bold'`. Ignored for
|
|
797
|
+
* sets that aren't suffix-weighted; an unknown weight throws rather than silently
|
|
798
|
+
* resolving to the regular glyph, which would be indistinguishable from a typo.
|
|
678
799
|
*/
|
|
679
|
-
function resolveIcon(name, prefix) {
|
|
800
|
+
function resolveIcon(name, prefix, opts) {
|
|
680
801
|
if (name.includes(":")) return name;
|
|
681
802
|
const mapKey = prefixToMapKey[prefix];
|
|
682
803
|
if (!mapKey) throw new Error(`Unknown icon prefix "${prefix}". Valid: ${Object.keys(prefixToMapKey).join(", ")}`);
|
|
683
804
|
const resolved = iconMap[mapKey][name];
|
|
684
805
|
if (!resolved) throw new Error(`Icon "${name}" not found in set "${prefix}" (map key: "${mapKey}")`);
|
|
685
|
-
return `${prefix}:${resolved}`;
|
|
806
|
+
return `${prefix}:${applyWeight(resolved, prefix, opts?.weight)}`;
|
|
807
|
+
}
|
|
808
|
+
/** Append a weight suffix for suffix-weighted sets. The first weight is the bare default. */
|
|
809
|
+
function applyWeight(name, prefix, weight) {
|
|
810
|
+
if (!weight) return name;
|
|
811
|
+
const weights = WEIGHTED_SETS[prefix];
|
|
812
|
+
if (!weights) return name;
|
|
813
|
+
if (!weights.includes(weight)) throw new Error(`Unknown weight "${weight}" for icon set "${prefix}". Valid: ${weights.join(", ")}.`);
|
|
814
|
+
return weight === weights[0] ? name : `${name}-${weight}`;
|
|
686
815
|
}
|
|
687
816
|
/**
|
|
688
817
|
* Resolve a brand icon name to a fully-qualified astro-icon string.
|
|
@@ -719,7 +848,7 @@ function generateIconInclude(iconsConfig) {
|
|
|
719
848
|
const addToInclude = (prefix, names) => {
|
|
720
849
|
include[prefix] = [...new Set([...include[prefix] ?? [], ...names])];
|
|
721
850
|
};
|
|
722
|
-
for (const [key, val] of Object.entries(iconsConfig ?? {})) if (key !== "ui" && key !== "brand" && key !== "semantic" && Array.isArray(val)) addToInclude(key, val);
|
|
851
|
+
for (const [key, val] of Object.entries(iconsConfig ?? {})) if (key !== "ui" && key !== "brand" && key !== "weight" && key !== "semantic" && Array.isArray(val)) addToInclude(key, val);
|
|
723
852
|
const uiMapKey = prefixToMapKey[uiPrefix];
|
|
724
853
|
const brandMapKey = prefixToMapKey[brandPrefix];
|
|
725
854
|
const unknownPrefix = [];
|
|
@@ -730,8 +859,8 @@ function generateIconInclude(iconsConfig) {
|
|
|
730
859
|
for (const name of iconsConfig?.semantic ?? []) {
|
|
731
860
|
const ui = iconMap[uiMapKey][name];
|
|
732
861
|
const brand = iconMap[brandMapKey][name];
|
|
733
|
-
if (ui) addToInclude(uiPrefix, [ui]);
|
|
734
|
-
if (brand) addToInclude(brandPrefix, [brand]);
|
|
862
|
+
if (ui) addToInclude(uiPrefix, [applyWeight(ui, uiPrefix, iconsConfig?.weight)]);
|
|
863
|
+
if (brand) addToInclude(brandPrefix, [applyWeight(brand, brandPrefix, iconsConfig?.weight)]);
|
|
735
864
|
if (!ui && !brand) unresolved.push(name);
|
|
736
865
|
}
|
|
737
866
|
if (unresolved.length) throw new Error(`[vitops icons] ${unresolved.length} semantic icon name(s) not found in ui set '${uiPrefix}' or brand set '${brandPrefix}': ${unresolved.join(", ")}.\nEither add them to iconMap in @getvitops/core, or drop them from the \`semantic\` list. Available in '${uiPrefix}': ${Object.keys(iconMap[uiMapKey]).slice(0, 12).join(", ")}…`);
|
|
@@ -739,5 +868,216 @@ function generateIconInclude(iconsConfig) {
|
|
|
739
868
|
}
|
|
740
869
|
|
|
741
870
|
//#endregion
|
|
742
|
-
|
|
871
|
+
//#region src/scan.ts
|
|
872
|
+
/**
|
|
873
|
+
* Consumer source discovery.
|
|
874
|
+
*
|
|
875
|
+
* Lifted verbatim out of `@getvitops/cli`, where it was a private helper behind
|
|
876
|
+
* `vitops lint`. It lives here now because a second caller needs it: the Astro
|
|
877
|
+
* integration scans for icon usage during `astro:config:setup`, and it cannot
|
|
878
|
+
* import the CLI — `@getvitops/cli` exposes only a `bin`, and its entry calls
|
|
879
|
+
* `main()` at module scope, so importing it would run the CLI.
|
|
880
|
+
*
|
|
881
|
+
* Deliberately hand-rolled rather than a glob dependency: the whole job is one
|
|
882
|
+
* `readdirSync` recursion with two allow/deny sets, and it runs synchronously
|
|
883
|
+
* inside a config hook where an async glob would have to be awaited anyway.
|
|
884
|
+
*/
|
|
885
|
+
/**
|
|
886
|
+
* Extensions worth scanning: every template language the framework's classes
|
|
887
|
+
* and icon names can appear in, plus the markdown that can embed them.
|
|
888
|
+
*/
|
|
889
|
+
const SCAN_EXT = new Set([
|
|
890
|
+
".astro",
|
|
891
|
+
".html",
|
|
892
|
+
".htm",
|
|
893
|
+
".js",
|
|
894
|
+
".jsx",
|
|
895
|
+
".ts",
|
|
896
|
+
".tsx",
|
|
897
|
+
".vue",
|
|
898
|
+
".svelte",
|
|
899
|
+
".php",
|
|
900
|
+
".md",
|
|
901
|
+
".mdx"
|
|
902
|
+
]);
|
|
903
|
+
/** Directories that only ever hold dependencies or build output. */
|
|
904
|
+
const SCAN_SKIP = new Set([
|
|
905
|
+
"node_modules",
|
|
906
|
+
"dist",
|
|
907
|
+
".git",
|
|
908
|
+
".astro",
|
|
909
|
+
"build",
|
|
910
|
+
"coverage"
|
|
911
|
+
]);
|
|
912
|
+
/**
|
|
913
|
+
* Recursively read every scannable file under `dir`.
|
|
914
|
+
*
|
|
915
|
+
* Dotfiles and dot-directories are skipped wholesale — they hold tooling config,
|
|
916
|
+
* not templates, and `.astro`/`.git` would otherwise be walked twice over.
|
|
917
|
+
*/
|
|
918
|
+
function scanFiles(dir, opts) {
|
|
919
|
+
const exts = opts?.exts ? new Set(opts.exts) : SCAN_EXT;
|
|
920
|
+
const skip = opts?.skipDirs ? new Set(opts.skipDirs) : SCAN_SKIP;
|
|
921
|
+
const acc = [];
|
|
922
|
+
walk(dir, exts, skip, acc);
|
|
923
|
+
return acc;
|
|
924
|
+
}
|
|
925
|
+
function walk(dir, exts, skip, acc) {
|
|
926
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
927
|
+
if (e.name.startsWith(".") && e.name !== ".") continue;
|
|
928
|
+
const p = join(dir, e.name);
|
|
929
|
+
if (e.isDirectory()) {
|
|
930
|
+
if (!skip.has(e.name)) walk(p, exts, skip, acc);
|
|
931
|
+
} else if (exts.has(extname(e.name))) acc.push({
|
|
932
|
+
path: p,
|
|
933
|
+
text: readFileSync(p, "utf8")
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
//#endregion
|
|
939
|
+
//#region src/icons-scan.ts
|
|
940
|
+
/**
|
|
941
|
+
* Props that carry an icon name. `icon` covers the component convention
|
|
942
|
+
* (`Popover`/`Drawer`/`Details` triggers); the start/end pair covers adornments
|
|
943
|
+
* on links and buttons.
|
|
944
|
+
*/
|
|
945
|
+
const DEFAULT_PROPS = [
|
|
946
|
+
"icon",
|
|
947
|
+
"startIcon",
|
|
948
|
+
"endIcon",
|
|
949
|
+
"leadingIcon",
|
|
950
|
+
"trailingIcon"
|
|
951
|
+
];
|
|
952
|
+
/**
|
|
953
|
+
* `<Icon …>` and any capitalised component whose name ends in `Icon`.
|
|
954
|
+
* Requires a capital or the literal `Icon`, so `<iconButton>` and plain HTML
|
|
955
|
+
* never match.
|
|
956
|
+
*/
|
|
957
|
+
const COMPONENT = /<([A-Z][\w.]*Icon|Icon)\b([^>]*?)\/?>/g;
|
|
958
|
+
/** Sprite references: `<use href="…/icons.svg#menu">`, incl. legacy `xlink:href`. */
|
|
959
|
+
const SPRITE_USE = /<use\b[^>]*?\b(?:xlink:)?href\s*=\s*(?:"([^"#]*)#([^"]*)"|'([^'#]*)#([^']*)')/g;
|
|
960
|
+
/**
|
|
961
|
+
* An attribute value: a quoted literal, a braced single literal, or a braced
|
|
962
|
+
* expression. The final alternative is what marks a value as dynamic — it is
|
|
963
|
+
* matched on purpose so the reference can be REPORTED rather than skipped.
|
|
964
|
+
*
|
|
965
|
+
* That last alternative stops at the first `}`, so a reported expression
|
|
966
|
+
* containing its own braces (`{\`a:${b}\`}`) is truncated in the warning text.
|
|
967
|
+
* Deliberate: balancing braces with a regex buys nothing here, because the
|
|
968
|
+
* actionable part of the report is the file and line, not the expression.
|
|
969
|
+
*/
|
|
970
|
+
const VALUE = String.raw`(?:"([^"]*)"|'([^']*)'|\{\s*(?:"([^"]*)"|'([^']*)'|\`([^\`$]*)\`)\s*\}|(\{[^}]*\}))`;
|
|
971
|
+
/**
|
|
972
|
+
* `\b` is not enough of a left boundary here: `-` is a non-word character, so
|
|
973
|
+
* `\bicon` happily matches inside `data-icon=` and `aria-icon=`. Requiring that
|
|
974
|
+
* the previous character be neither a word character nor a hyphen keeps the
|
|
975
|
+
* match to a genuine standalone prop.
|
|
976
|
+
*/
|
|
977
|
+
const LEFT = String.raw`(?<![\w-])`;
|
|
978
|
+
const nameAttr = () => new RegExp(String.raw`${LEFT}name\s*=\s*${VALUE}`);
|
|
979
|
+
const iconProp = (props) => new RegExp(String.raw`${LEFT}(${props.join("|")})\s*=\s*${VALUE}`, "g");
|
|
980
|
+
/** Object-literal form: `trigger={{ icon: 'bars' }}`, or a data array of `{ icon: '…' }`. */
|
|
981
|
+
const iconKey = (props) => new RegExp(String.raw`${LEFT}(${props.join("|")})\s*:\s*(?:"([^"]*)"|'([^']*)'|\`([^\`$]*)\`)`, "g");
|
|
982
|
+
const lineOf = (src, index) => src.slice(0, index).split("\n").length;
|
|
983
|
+
/**
|
|
984
|
+
* Pull the literal out of a VALUE match, or report it as dynamic.
|
|
985
|
+
* Groups run: "double", 'single', {"double"}, {'single'}, {`template`}, {expr}.
|
|
986
|
+
*/
|
|
987
|
+
function readValue(groups) {
|
|
988
|
+
const [dq, sq, bdq, bsq, btpl, expr] = groups;
|
|
989
|
+
if (expr !== void 0) return { expr };
|
|
990
|
+
const literal = dq ?? sq ?? bdq ?? bsq ?? btpl;
|
|
991
|
+
if (literal === void 0) return {};
|
|
992
|
+
if (literal.includes("${")) return { expr: literal };
|
|
993
|
+
return { literal };
|
|
994
|
+
}
|
|
995
|
+
/** Extract every icon reference from one file's text. */
|
|
996
|
+
function extractIconRefs(src, file = "<source>", opts) {
|
|
997
|
+
const props = [...new Set([...DEFAULT_PROPS, ...opts?.props ?? []])];
|
|
998
|
+
const refs = [];
|
|
999
|
+
const dynamic = [];
|
|
1000
|
+
const NAME = nameAttr();
|
|
1001
|
+
for (const m of src.matchAll(COMPONENT)) {
|
|
1002
|
+
const attrs = m[2] ?? "";
|
|
1003
|
+
const hit = NAME.exec(attrs);
|
|
1004
|
+
if (!hit) continue;
|
|
1005
|
+
const line = lineOf(src, m.index);
|
|
1006
|
+
const { literal, expr } = readValue(hit.slice(1));
|
|
1007
|
+
if (literal) refs.push({
|
|
1008
|
+
name: literal,
|
|
1009
|
+
kind: "component",
|
|
1010
|
+
attr: "name",
|
|
1011
|
+
file,
|
|
1012
|
+
line
|
|
1013
|
+
});
|
|
1014
|
+
else if (expr) dynamic.push({
|
|
1015
|
+
expr,
|
|
1016
|
+
attr: "name",
|
|
1017
|
+
file,
|
|
1018
|
+
line
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
for (const m of src.matchAll(iconProp(props))) {
|
|
1022
|
+
const line = lineOf(src, m.index);
|
|
1023
|
+
const { literal, expr } = readValue(m.slice(2));
|
|
1024
|
+
if (literal) refs.push({
|
|
1025
|
+
name: literal,
|
|
1026
|
+
kind: "prop",
|
|
1027
|
+
attr: m[1],
|
|
1028
|
+
file,
|
|
1029
|
+
line
|
|
1030
|
+
});
|
|
1031
|
+
else if (expr) dynamic.push({
|
|
1032
|
+
expr,
|
|
1033
|
+
attr: m[1],
|
|
1034
|
+
file,
|
|
1035
|
+
line
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
for (const m of src.matchAll(iconKey(props))) {
|
|
1039
|
+
const literal = m[2] ?? m[3] ?? m[4];
|
|
1040
|
+
if (!literal || literal.includes("${")) continue;
|
|
1041
|
+
refs.push({
|
|
1042
|
+
name: literal,
|
|
1043
|
+
kind: "object",
|
|
1044
|
+
attr: m[1],
|
|
1045
|
+
file,
|
|
1046
|
+
line: lineOf(src, m.index)
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
for (const m of src.matchAll(SPRITE_USE)) {
|
|
1050
|
+
const id = m[2] ?? m[4];
|
|
1051
|
+
if (!id) continue;
|
|
1052
|
+
refs.push({
|
|
1053
|
+
name: id,
|
|
1054
|
+
kind: "sprite",
|
|
1055
|
+
attr: "href",
|
|
1056
|
+
file,
|
|
1057
|
+
line: lineOf(src, m.index)
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
return {
|
|
1061
|
+
refs,
|
|
1062
|
+
dynamic
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
/** Run `extractIconRefs` across a file set and collect unique names. */
|
|
1066
|
+
function collectIconRefs(files, opts) {
|
|
1067
|
+
const refs = [];
|
|
1068
|
+
const dynamic = [];
|
|
1069
|
+
for (const f of files) {
|
|
1070
|
+
const r = extractIconRefs(f.text, f.path, opts);
|
|
1071
|
+
refs.push(...r.refs);
|
|
1072
|
+
dynamic.push(...r.dynamic);
|
|
1073
|
+
}
|
|
1074
|
+
return {
|
|
1075
|
+
refs,
|
|
1076
|
+
dynamic,
|
|
1077
|
+
names: [...new Set(refs.map((r) => r.name))]
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
//#endregion
|
|
1082
|
+
export { SCAN_EXT, SCAN_SKIP, WEIGHTED_SETS, articleGraph, breadcrumbGraph, collectIconRefs, extractIconRefs, faqGraph, faviconLinks, faviconManifest, generateFavicons, generateIconInclude, getAttribute, iconMap, isElmnt, isImageLink, isLink, isTextLink, nodesToHtml, organizationGraph, parseRenderedSlots, partAttrs, prefixToMapKey, resolveBrandIcon, resolveIcon, scanFiles, serialize, styleList, t, toHtml, writeFaviconManifest };
|
|
743
1083
|
//# sourceMappingURL=index.mjs.map
|