vadivam 0.0.63 → 0.0.65
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 +2 -1
- package/dist/cli.js +44 -0
- package/dist/font/vadivam.woff2 +0 -0
- package/dist/lucide-aliases.js +2 -0
- package/dist/migrate-lucide.js +279 -0
- package/package.json +4 -1
- package/skills/vadivam-icons/SKILL.md +5 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Tree-shakeable 24px outline SVG icons for JavaScript, with raw assets and metada
|
|
|
8
8
|
[](https://www.npmjs.com/package/vadivam)
|
|
9
9
|
[](https://github.com/praveenjuge/vadivam/blob/main/LICENSE)
|
|
10
10
|
|
|
11
|
-

|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
14
|
npm install vadivam
|
|
@@ -38,6 +38,7 @@ createIcons({ icons });
|
|
|
38
38
|
- Per-icon SVG strings, a complete SVG sprite, and an optional WOFF2 icon font
|
|
39
39
|
- DOM replacement with configurable attributes, roots, and templates
|
|
40
40
|
- Complete icon metadata from `vadivam/manifest`
|
|
41
|
+
- A codemod that moves a Lucide project to Vadivam: `npx vadivam migrate-lucide --write` ([guide](https://vadivam.praveenjuge.com/docs/migrate-from-lucide))
|
|
41
42
|
|
|
42
43
|
Package family: [React](https://www.npmjs.com/package/vadivam-react) · [React Native](https://www.npmjs.com/package/vadivam-react-native) · [Vue](https://www.npmjs.com/package/vadivam-vue) · [Svelte](https://www.npmjs.com/package/vadivam-svelte) · [Solid](https://www.npmjs.com/package/vadivam-solid) · [Angular](https://www.npmjs.com/package/vadivam-angular) · [Astro](https://www.npmjs.com/package/vadivam-astro) · [Preact](https://www.npmjs.com/package/vadivam-preact) · [MCP server](https://www.npmjs.com/package/vadivam-mcp)
|
|
43
44
|
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { guideUrl, migrateLucide } from "./migrate-lucide.js";
|
|
5
|
+
|
|
6
|
+
const { version } = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
7
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
8
|
+
const usage = `vadivam ${version}
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
npx vadivam migrate-lucide [paths...] [--write]
|
|
12
|
+
|
|
13
|
+
Rewrites Lucide imports, identifiers, dependencies, data-lucide attributes, and
|
|
14
|
+
.lucide CSS classes to Vadivam. Without --write it only lists what would change.
|
|
15
|
+
Paths default to the current directory.
|
|
16
|
+
|
|
17
|
+
Guide: ${guideUrl}`;
|
|
18
|
+
|
|
19
|
+
if (command === "--version" || command === "-v") {
|
|
20
|
+
console.log(version);
|
|
21
|
+
} else if (command !== "migrate-lucide" || rest.includes("--help") || rest.includes("-h")) {
|
|
22
|
+
console.log(usage);
|
|
23
|
+
// An unknown command is an error; asking for help is not.
|
|
24
|
+
if (command && command !== "migrate-lucide" && command !== "--help" && command !== "-h") process.exitCode = 1;
|
|
25
|
+
} else {
|
|
26
|
+
const write = rest.includes("--write");
|
|
27
|
+
const targets = rest.filter((arg) => !arg.startsWith("-"));
|
|
28
|
+
const files = await migrateLucide(targets.length > 0 ? targets : ["."], { write, version });
|
|
29
|
+
const changed = files.filter((entry) => entry.changes > 0);
|
|
30
|
+
for (const { file, changes, warnings } of files) {
|
|
31
|
+
const relative = path.relative(process.cwd(), file) || file;
|
|
32
|
+
if (changes > 0) console.log(`${write ? "updated" : "would update"} ${relative} (${changes} change${changes === 1 ? "" : "s"})`);
|
|
33
|
+
for (const warning of warnings) console.log(` check ${relative}: ${warning}`);
|
|
34
|
+
}
|
|
35
|
+
const warningCount = files.reduce((total, entry) => total + entry.warnings.length, 0);
|
|
36
|
+
if (files.length === 0) {
|
|
37
|
+
console.log("No Lucide usage found.");
|
|
38
|
+
} else {
|
|
39
|
+
console.log(
|
|
40
|
+
`\n${changed.length} file${changed.length === 1 ? "" : "s"} ${write ? "updated" : "to update"}, ${warningCount} item${warningCount === 1 ? "" : "s"} to check by hand.`,
|
|
41
|
+
);
|
|
42
|
+
console.log(write ? "Reinstall dependencies with your package manager to update the lockfile." : "Run again with --write to apply.");
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/font/vadivam.woff2
CHANGED
|
Binary file
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// Deprecated Lucide names and the canonical icon each one now points to.
|
|
2
|
+
export const lucideAliases = {"activity-square":"square-activity","alarm-check":"alarm-clock-check","alarm-minus":"alarm-clock-minus","alarm-plus":"alarm-clock-plus","album":"square-bookmark","alert-circle":"circle-alert","alert-octagon":"octagon-alert","alert-triangle":"triangle-alert","align-center":"text-align-center","align-justify":"text-align-justify","align-left":"text-align-start","align-right":"text-align-end","angry":"face-angry","annoyed":"face-expressionless","area-chart":"chart-area","arrow-down-01":"arrow-down-0-1","arrow-down-10":"arrow-down-1-0","arrow-down-az":"arrow-down-a-z","arrow-down-circle":"circle-arrow-down","arrow-down-left-from-circle":"circle-arrow-out-down-left","arrow-down-left-from-square":"square-arrow-out-down-left","arrow-down-left-square":"square-arrow-down-left","arrow-down-right-from-circle":"circle-arrow-out-down-right","arrow-down-right-from-square":"square-arrow-out-down-right","arrow-down-right-square":"square-arrow-down-right","arrow-down-square":"square-arrow-down","arrow-down-za":"arrow-down-z-a","arrow-left-circle":"circle-arrow-left","arrow-left-square":"square-arrow-left","arrow-right-circle":"circle-arrow-right","arrow-right-square":"square-arrow-right","arrow-up-01":"arrow-up-0-1","arrow-up-10":"arrow-up-1-0","arrow-up-az":"arrow-up-a-z","arrow-up-circle":"circle-arrow-up","arrow-up-left-from-circle":"circle-arrow-out-up-left","arrow-up-left-from-square":"square-arrow-out-up-left","arrow-up-left-square":"square-arrow-up-left","arrow-up-right-from-circle":"circle-arrow-out-up-right","arrow-up-right-from-square":"square-arrow-out-up-right","arrow-up-right-square":"square-arrow-up-right","arrow-up-square":"square-arrow-up","arrow-up-za":"arrow-up-z-a","asterisk-square":"square-asterisk","axis-3-d":"axis-3d","badge-help":"badge-question-mark","bar-chart":"chart-no-axes-column-increasing","bar-chart-2":"chart-no-axes-column","bar-chart-3":"chart-column","bar-chart-4":"chart-column-increasing","bar-chart-big":"chart-column-big","bar-chart-horizontal":"chart-bar","bar-chart-horizontal-big":"chart-bar-big","between-horizonal-end":"between-horizontal-end","between-horizonal-start":"between-horizontal-start","book-marked":"book-bookmark","book-template":"book-dashed","box-select":"square-dashed","building-2":"building-complex","candlestick-chart":"chart-candlestick","check-circle":"circle-check-big","check-circle-2":"circle-check","check-square":"square-check-big","check-square-2":"square-check","chevron-down-circle":"circle-chevron-down","chevron-down-square":"square-chevron-down","chevron-left-circle":"circle-chevron-left","chevron-left-square":"square-chevron-left","chevron-right-circle":"circle-chevron-right","chevron-right-square":"square-chevron-right","chevron-up-circle":"circle-chevron-up","chevron-up-square":"square-chevron-up","circle-euro-sign":"circle-euro","circle-help":"circle-question-mark","circle-slashed":"circle-slash-2","clipboard-edit":"clipboard-pen","clipboard-signature":"clipboard-pen-line","code-2":"code-xml","code-square":"square-code","columns":"columns-2","columns-settings":"columns-3-cog","contact-2":"contact-round","curly-braces":"braces","divide-circle":"circle-divide","divide-square":"square-divide","dot-square":"square-dot","download-cloud":"cloud-download","edit":"square-pen","edit-2":"pen","edit-3":"pen-line","equal-square":"square-equal","file-audio":"file-headphone","file-audio-2":"file-headphone","file-axis-3-d":"file-axis-3d","file-badge-2":"file-badge","file-bar-chart":"file-chart-column-increasing","file-bar-chart-2":"file-chart-column","file-check-2":"file-check-corner","file-code-2":"file-code-corner","file-cog-2":"file-cog","file-edit":"file-pen","file-json":"file-braces","file-json-2":"file-braces-corner","file-key-2":"file-key","file-line-chart":"file-chart-line","file-lock-2":"file-lock","file-minus-2":"file-minus-corner","file-pie-chart":"file-chart-pie","file-plus-2":"file-plus-corner","file-question":"file-question-mark","file-search-2":"file-search-corner","file-signature":"file-pen-line","file-type-2":"file-type-corner","file-video":"file-play","file-video-2":"file-video-camera","file-volume-2":"file-signal","file-warning":"file-exclamation-point","file-x-2":"file-x-corner","filter":"funnel","filter-x":"funnel-x","fingerprint":"fingerprint-pattern","flip-horizontal":"square-centerline-dashed-horizontal","flip-horizontal-2":"triangles-centerline-dashed-horizontal","flip-vertical":"square-centerline-dashed-vertical","flip-vertical-2":"triangles-centerline-dashed-vertical","folder-cog-2":"folder-cog","folder-edit":"folder-pen","fork-knife":"utensils","fork-knife-crossed":"utensils-crossed","form-input":"rectangle-ellipsis","frown":"face-slightly-frowning","function-square":"square-function","gantt-chart":"chart-no-axes-gantt","gantt-chart-square":"square-chart-gantt","gauge-circle":"circle-gauge","git-commit":"git-commit-horizontal","globe-2":"earth","grab":"hand-grab","grid":"grid-3x3","grid-2-x-2":"grid-2x2","grid-2-x-2-check":"grid-2x2-check","grid-2-x-2-plus":"grid-2x2-plus","grid-2-x-2-x":"grid-2x2-x","grid-3-x-3":"grid-3x3","help-circle":"circle-question-mark","helping-hand":"hand-helping","history":"rotate-ccw-clock","home":"house","ice-cream":"ice-cream-cone","ice-cream-2":"ice-cream-bowl","indent":"list-indent-increase","indent-decrease":"list-indent-decrease","indent-increase":"list-indent-increase","inspect":"square-mouse-pointer","kanban-square":"square-kanban","kanban-square-dashed":"square-dashed-kanban","laptop-2":"laptop-minimal","laugh":"face-grinning","layers-3":"layers","layout":"panels-top-left","letter-text":"text-initial","library-square":"square-library","line-chart":"chart-line","loader-2":"loader-circle","location-edit":"map-pin-pen","m-square":"square-m","mail-question":"mail-question-mark","meh":"face-neutral","menu-square":"square-menu","message-circle-question":"message-circle-question-mark","mic-2":"mic-vocal","minus-circle":"circle-minus","minus-square":"square-minus","more-horizontal":"ellipsis","more-vertical":"ellipsis-vertical","mouse-pointer-square-dashed":"square-dashed-mouse-pointer","move-3-d":"move-3d","music":"eighth-notes","music-2":"eighth-note","music-3":"half-note","music-4":"sixteenth-notes","outdent":"list-indent-decrease","paintbrush-2":"paintbrush-vertical","palmtree":"tree-palm","panel-bottom-inactive":"panel-bottom-dashed","panel-left-inactive":"panel-left-dashed","panel-right-inactive":"panel-right-dashed","panel-top-inactive":"panel-top-dashed","panels-left-right":"columns-3","panels-top-bottom":"rows-3","parking-circle":"circle-parking","parking-circle-off":"circle-parking-off","parking-square":"square-parking","parking-square-off":"square-parking-off","pause-circle":"circle-pause","pause-octagon":"octagon-pause","pen-box":"square-pen","pen-square":"square-pen","percent-circle":"circle-percent","percent-diamond":"diamond-percent","percent-square":"square-percent","pi-square":"square-pi","pie-chart":"chart-pie","pilcrow-square":"square-pilcrow","play-circle":"circle-play","play-square":"square-play","plug-zap-2":"plug-zap","plus-circle":"circle-plus","plus-square":"square-plus","podcast":"mic-signal","power-circle":"circle-power","power-square":"square-power","rotate-3-d":"rotate-3d","rows":"rows-2","scale-3-d":"scale-3d","scatter-chart":"chart-scatter","school-2":"university","scissors-square":"square-scissors","scissors-square-dashed-bottom":"square-bottom-dashed-scissors","send-horizonal":"send-horizontal","shield-close":"shield-x","shield-question":"shield-question-mark","sidebar":"panel-left","sidebar-close":"panel-left-close","sidebar-open":"panel-left-open","sigma-square":"square-sigma","slash-square":"square-slash","sliders":"sliders-vertical","smile":"face-slightly-smiling","smile-plus":"face-slightly-smiling-plus","sort-asc":"arrow-up-narrow-wide","sort-desc":"arrow-down-wide-narrow","split-square-horizontal":"square-split-horizontal","split-square-vertical":"square-split-vertical","square-gantt-chart":"square-chart-gantt","stars":"sparkles","stop-circle":"circle-stop","subtitles":"captions","table-config":"columns-3-cog","terminal-square":"square-terminal","test-tube-2":"test-tube-diagonal","text":"text-align-start","text-select":"square-dashed-text","text-selection":"square-dashed-text","train":"tram-front","trash-2":"trash","tv-2":"tv-minimal","unlock":"lock-open","unlock-keyhole":"lock-keyhole-open","upload-cloud":"cloud-upload","user-2":"user-round","user-check-2":"user-round-check","user-circle":"circle-user","user-circle-2":"circle-user-round","user-cog-2":"user-round-cog","user-minus-2":"user-round-minus","user-plus-2":"user-round-plus","user-square":"square-user","user-square-2":"square-user-round","user-x-2":"user-round-x","users-2":"users-round","verified":"badge-check","wallet-2":"wallet-minimal","wand-2":"wand-sparkles","waves":"waves-horizontal","wrap-text":"text-wrap","x-circle":"circle-x","x-octagon":"octagon-x","x-square":"square-x"};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { lucideAliases } from "./lucide-aliases.js";
|
|
4
|
+
import { icons } from "./manifest.js";
|
|
5
|
+
|
|
6
|
+
// Every Vadivam icon keeps its Lucide name, and each framework package mirrors
|
|
7
|
+
// the matching Lucide package's API with Lucide renamed to Vadivam. So moving
|
|
8
|
+
// over is a package rename plus `Lucide` -> `Vadivam` in imported identifiers.
|
|
9
|
+
export const packageMap = {
|
|
10
|
+
lucide: "vadivam",
|
|
11
|
+
"lucide-react": "vadivam-react",
|
|
12
|
+
"lucide-react-native": "vadivam-react-native",
|
|
13
|
+
"lucide-vue-next": "vadivam-vue",
|
|
14
|
+
"@lucide/vue": "vadivam-vue",
|
|
15
|
+
"lucide-svelte": "vadivam-svelte",
|
|
16
|
+
"@lucide/svelte": "vadivam-svelte",
|
|
17
|
+
"lucide-solid": "vadivam-solid",
|
|
18
|
+
"lucide-preact": "vadivam-preact",
|
|
19
|
+
"@lucide/astro": "vadivam-astro",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Packages whose API differs enough that they need a hand migration.
|
|
23
|
+
const manualPackages = {
|
|
24
|
+
"lucide-angular": "vadivam-angular uses one standalone directive per icon",
|
|
25
|
+
"@lucide/angular": "vadivam-angular uses one standalone directive per icon",
|
|
26
|
+
"lucide-static": "use vadivam/icons/<name>.svg, vadivam/strings/<name>, the sprite, or the font",
|
|
27
|
+
"lucide-vue": "Vue 2 is not supported; vadivam-vue targets Vue 3",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const guideUrl = "https://vadivam.praveenjuge.com/docs/migrate-from-lucide";
|
|
31
|
+
|
|
32
|
+
const sourceExtensions = new Set([
|
|
33
|
+
".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts",
|
|
34
|
+
".vue", ".svelte", ".astro", ".mdx", ".html",
|
|
35
|
+
]);
|
|
36
|
+
const styleExtensions = new Set([".css", ".scss", ".sass", ".less"]);
|
|
37
|
+
const skippedDirectories = new Set([
|
|
38
|
+
"node_modules", ".git", "dist", "build", "out", "coverage",
|
|
39
|
+
".next", ".nuxt", ".output", ".svelte-kit", ".astro", ".turbo", ".vercel", ".expo",
|
|
40
|
+
]);
|
|
41
|
+
const dependencyFields = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
42
|
+
|
|
43
|
+
const iconNames = new Set(icons.map((icon) => icon.name));
|
|
44
|
+
const componentNames = new Map(icons.map((icon) => [icon.name, icon.componentName]));
|
|
45
|
+
// Deprecated Lucide component names (AlertCircle) and the canonical ones (CircleAlert).
|
|
46
|
+
const pascalCase = (name) => name.split("-").map((part) => part[0].toUpperCase() + part.slice(1)).join("");
|
|
47
|
+
const componentAliases = new Map(
|
|
48
|
+
Object.entries(lucideAliases).map(([alias, name]) => [pascalCase(alias), componentNames.get(name)]),
|
|
49
|
+
);
|
|
50
|
+
const iconExports = new Set(
|
|
51
|
+
icons.flatMap(({ componentName }) => [componentName, `${componentName}Icon`, `Vadivam${componentName}`]),
|
|
52
|
+
);
|
|
53
|
+
// The non-icon exports across the core and framework packages.
|
|
54
|
+
const apiExports = new Set([
|
|
55
|
+
"Icon", "createIcon", "createVadivamIcon", "icons", "iconNames", "dynamicIconImports", "DynamicIcon",
|
|
56
|
+
"VadivamProvider", "useVadivamContext", "provideVadivam", "VadivamContext", "VADIVAM_CONTEXT",
|
|
57
|
+
"getVadivamContext", "setVadivamProps", "createElement", "createIcons", "manifest", "iconsByName",
|
|
58
|
+
"categories", "IconName", "IconNode", "Icons", "SVGElementType", "SVGAttributes", "SVGProps",
|
|
59
|
+
"VadivamProps", "VadivamIcon", "IconDefinition", "CreateIconsOptions", "IconCategoryId",
|
|
60
|
+
"VadivamIconCategory", "VadivamIconMetadata", "default",
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const specifierPattern = /(\bfrom\s*|\bimport\s*\(\s*|\bimport\s+|\brequire\s*\(\s*|\.mock\s*\(\s*)(["'])([^"'\n]+)\2/g;
|
|
64
|
+
// Named bindings taken from a Vadivam package: imports, re-exports, and
|
|
65
|
+
// destructured require() calls. `separator` joins a name to its local name.
|
|
66
|
+
const bindingLists = [
|
|
67
|
+
{ kind: "import", separator: " as ", pattern: /\bimport\s+(?:type\s+)?(?:[\w$]+\s*,\s*)?\{([^}]*)\}\s*from\s*["'](vadivam(?:-[a-z-]+)?)(?:\/dynamic)?["']/g },
|
|
68
|
+
{ kind: "export", separator: " as ", pattern: /\bexport\s+(?:type\s+)?\{([^}]*)\}\s*from\s*["'](vadivam(?:-[a-z-]+)?)(?:\/dynamic)?["']/g },
|
|
69
|
+
{ kind: "require", separator: ": ", pattern: /\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*require\s*\(\s*["'](vadivam(?:-[a-z-]+)?)(?:\/dynamic)?["']\s*\)/g },
|
|
70
|
+
];
|
|
71
|
+
const bindingEntry = /((?:type\s+)?)([\w$]+)(?:\s*(?:\bas\b|:)\s*([\w$]+))?/g;
|
|
72
|
+
|
|
73
|
+
function splitSpecifier(specifier) {
|
|
74
|
+
const parts = specifier.split("/");
|
|
75
|
+
const length = specifier.startsWith("@") ? 2 : 1;
|
|
76
|
+
return { name: parts.slice(0, length).join("/"), subpath: parts.slice(length).join("/") };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The Vadivam specifier for a Lucide one, or a reason it cannot be mapped. */
|
|
80
|
+
export function mapSpecifier(specifier) {
|
|
81
|
+
const { name, subpath } = splitSpecifier(specifier);
|
|
82
|
+
if (manualPackages[name]) return { manual: `${name}: ${manualPackages[name]}` };
|
|
83
|
+
const target = packageMap[name];
|
|
84
|
+
if (!target) return null;
|
|
85
|
+
if (subpath === "") return { specifier: target };
|
|
86
|
+
if (target !== "vadivam" && (subpath === "dynamic" || subpath === "dynamicIconImports")) {
|
|
87
|
+
return { specifier: `${target}/${subpath}` };
|
|
88
|
+
}
|
|
89
|
+
const iconPath = subpath.match(/^(?:dist\/esm\/)?icons\/([a-z0-9-]+?)(?:\.m?js)?$/)?.[1];
|
|
90
|
+
const icon = lucideAliases[iconPath] ?? iconPath;
|
|
91
|
+
if (icon) {
|
|
92
|
+
if (!iconNames.has(icon)) return { manual: `${specifier}: Vadivam has no "${icon}" icon` };
|
|
93
|
+
return { specifier: `${target}/icons/${icon}` };
|
|
94
|
+
}
|
|
95
|
+
return { manual: `${specifier}: no Vadivam equivalent for this subpath` };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The canonical export for a deprecated Lucide one: AlertCircleIcon -> CircleAlertIcon. */
|
|
99
|
+
function canonicalExport(name) {
|
|
100
|
+
const [, prefix, base, suffix] = name.match(/^(Vadivam)?(\w+?)(Icon)?$/) ?? [];
|
|
101
|
+
const canonical = base && componentAliases.get(base);
|
|
102
|
+
return canonical ? `${prefix ?? ""}${canonical}${suffix ?? ""}` : name;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const renameIdentifier = (identifier) =>
|
|
106
|
+
identifier !== "Lucide" && identifier !== "LUCIDE" && /Lucide|LUCIDE/.test(identifier)
|
|
107
|
+
? identifier.replace(/Lucide/g, "Vadivam").replace(/LUCIDE/g, "VADIVAM")
|
|
108
|
+
: identifier;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Rewrite one source file. Returns the new text, how many edits it made, and
|
|
112
|
+
* warnings for anything left to migrate by hand.
|
|
113
|
+
*/
|
|
114
|
+
export function migrateSource(text, { styles = false } = {}) {
|
|
115
|
+
const warnings = [];
|
|
116
|
+
let changes = 0;
|
|
117
|
+
// Lucide renders `lucide lucide-<name>` classes; Vadivam renders `vadivam vadivam-<name>`.
|
|
118
|
+
const migrateCss = (css) =>
|
|
119
|
+
css.replace(/\.lucide(?=[\s.,:#[>+~{)-]|$)/g, () => {
|
|
120
|
+
changes += 1;
|
|
121
|
+
return ".vadivam";
|
|
122
|
+
});
|
|
123
|
+
if (styles) return { text: migrateCss(text), changes, warnings };
|
|
124
|
+
let migrated = false;
|
|
125
|
+
// Vue, Svelte, Astro, and HTML files carry their own <style> blocks.
|
|
126
|
+
let output = text.replace(/(<style\b[^>]*>)([\s\S]*?)(<\/style>)/gi, (match, open, css, close) => `${open}${migrateCss(css)}${close}`);
|
|
127
|
+
output = output.replace(specifierPattern, (match, prefix, quote, specifier) => {
|
|
128
|
+
const mapped = mapSpecifier(specifier);
|
|
129
|
+
if (!mapped) return match;
|
|
130
|
+
if (mapped.manual) {
|
|
131
|
+
warnings.push(mapped.manual);
|
|
132
|
+
return match;
|
|
133
|
+
}
|
|
134
|
+
migrated = true;
|
|
135
|
+
changes += 1;
|
|
136
|
+
return `${prefix}${quote}${mapped.specifier}${quote}`;
|
|
137
|
+
});
|
|
138
|
+
if (migrated) {
|
|
139
|
+
// Only names taken from Lucide are renamed. An imported `LucideBell` becomes
|
|
140
|
+
// `VadivamBell` here and in the file's uses; a deprecated name imports the
|
|
141
|
+
// canonical icon under the old local name (`House as Home`) so the file's
|
|
142
|
+
// strings and JSX text stay as written. A re-export keeps its public name
|
|
143
|
+
// (`VadivamBell as LucideBell`) so the module's importers keep working.
|
|
144
|
+
const renames = new Map();
|
|
145
|
+
for (const { kind, separator, pattern } of bindingLists) {
|
|
146
|
+
output = output.replace(pattern, (statement, names) =>
|
|
147
|
+
statement.replace(names, (list) =>
|
|
148
|
+
list.replace(bindingEntry, (entry, type, imported, local) => {
|
|
149
|
+
const binding = local ?? (kind === "export" ? imported : renameIdentifier(imported));
|
|
150
|
+
const exported = canonicalExport(renameIdentifier(imported));
|
|
151
|
+
if (!local && binding !== imported) renames.set(imported, binding);
|
|
152
|
+
const next = `${type}${exported}${binding === exported ? "" : `${separator}${binding}`}`;
|
|
153
|
+
if (next !== entry) changes += 1;
|
|
154
|
+
return next;
|
|
155
|
+
}),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (renames.size > 0) {
|
|
160
|
+
// Rewritten import and export statements, quoted strings, and object or
|
|
161
|
+
// destructuring keys (`{ LucideBell: x }`) stay as they are. A local
|
|
162
|
+
// `export { LucideBell }` keeps its public name (`VadivamBell as
|
|
163
|
+
// LucideBell`); other identifier uses, but not property reads, take the
|
|
164
|
+
// new name.
|
|
165
|
+
output = output.replace(
|
|
166
|
+
/\bimport\s+(?:type\s+)?(?:[\w$]+\s*,?\s*)?(?:\*\s+as\s+[\w$]+|\{[^}]*\})?\s*from\s*["'][^"']*["']|\bexport\s+(?:type\s+)?(?:\*(?:\s+as\s+[\w$]+)?|\{[^}]*\})\s*from\s*["'][^"']*["']|\bexport\s*(?:type\s*)?\{([^}]*)\}|"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|(?<=[{,]\s*)[A-Za-z_$][\w$]*(?=\s*:(?!:))|(?<![\w$.])([A-Za-z_$][\w$]*)/g,
|
|
167
|
+
(match, exportList, identifier) => {
|
|
168
|
+
if (exportList !== undefined) {
|
|
169
|
+
return match.replace(exportList, (list) =>
|
|
170
|
+
list.replace(/([\w$]+)(\s+as\s+[\w$]+)?/g, (entry, name, alias) => {
|
|
171
|
+
const renamed = renames.get(name);
|
|
172
|
+
if (!renamed) return entry;
|
|
173
|
+
changes += 1;
|
|
174
|
+
return `${renamed}${alias ?? ` as ${name}`}`;
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const renamed = identifier && renames.get(identifier);
|
|
179
|
+
if (!renamed) return match;
|
|
180
|
+
changes += 1;
|
|
181
|
+
return renamed;
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
// A mock factory lists the module's export names, which this can't safely rewrite.
|
|
186
|
+
if (/\.mock\s*\(\s*["']vadivam[^"']*["']\s*,/.test(output)) {
|
|
187
|
+
warnings.push("a test mock factory for a Vadivam package: check that its keys use Vadivam export names");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// The core package's createIcons() reads data-vadivam instead of data-lucide,
|
|
191
|
+
// and only knows current names (`home` is `house`).
|
|
192
|
+
output = output.replace(/\bdata-lucide=(?:(["'])([a-z0-9-]+)\1)?/g, (match, quote, name) => {
|
|
193
|
+
changes += 1;
|
|
194
|
+
return name ? `data-vadivam=${quote}${lucideAliases[name] ?? name}${quote}` : "data-vadivam=";
|
|
195
|
+
});
|
|
196
|
+
for (const { pattern } of bindingLists) {
|
|
197
|
+
for (const [, names, packageName] of output.matchAll(pattern)) {
|
|
198
|
+
if (packageName === "vadivam-angular") continue;
|
|
199
|
+
for (const [, , imported] of names.matchAll(bindingEntry)) {
|
|
200
|
+
if (iconExports.has(imported) || apiExports.has(imported)) continue;
|
|
201
|
+
warnings.push(`${imported} from ${packageName}: not a Vadivam export; search https://vadivam.praveenjuge.com/?q=${encodeURIComponent(imported)}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { text: output, changes, warnings };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Rename Lucide dependencies in a package.json, keeping key order and formatting. */
|
|
209
|
+
export function migratePackageJson(text, version) {
|
|
210
|
+
let manifest;
|
|
211
|
+
try {
|
|
212
|
+
manifest = JSON.parse(text);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return { text, changes: 0, warnings: [`not valid JSON, skipped: ${error.message}`] };
|
|
215
|
+
}
|
|
216
|
+
const warnings = [];
|
|
217
|
+
let changes = 0;
|
|
218
|
+
for (const field of dependencyFields) {
|
|
219
|
+
const dependencies = manifest[field];
|
|
220
|
+
if (!dependencies || typeof dependencies !== "object") continue;
|
|
221
|
+
const entries = [];
|
|
222
|
+
for (const [name, range] of Object.entries(dependencies)) {
|
|
223
|
+
if (manualPackages[name]) warnings.push(`${field}.${name}: ${manualPackages[name]}`);
|
|
224
|
+
const target = packageMap[name];
|
|
225
|
+
if (!target) {
|
|
226
|
+
entries.push([name, range]);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
changes += 1;
|
|
230
|
+
if (!(target in dependencies) && !entries.some(([existing]) => existing === target)) {
|
|
231
|
+
entries.push([target, `^${version}`]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
manifest[field] = Object.fromEntries(entries);
|
|
235
|
+
}
|
|
236
|
+
if (changes === 0) return { text, changes, warnings };
|
|
237
|
+
const indent = text.match(/^[ \t]+(?=")/m)?.[0] ?? " ";
|
|
238
|
+
return { text: `${JSON.stringify(manifest, null, indent)}${text.endsWith("\n") ? "\n" : ""}`, changes, warnings };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function* walk(target) {
|
|
242
|
+
const entries = await readdir(target, { withFileTypes: true }).catch((error) => {
|
|
243
|
+
if (error.code === "ENOTDIR") return null;
|
|
244
|
+
throw error;
|
|
245
|
+
});
|
|
246
|
+
if (!entries) {
|
|
247
|
+
yield target;
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
const entryPath = path.join(target, entry.name);
|
|
252
|
+
if (entry.isDirectory()) {
|
|
253
|
+
if (!skippedDirectories.has(entry.name)) yield* walk(entryPath);
|
|
254
|
+
} else if (entry.isFile()) {
|
|
255
|
+
yield entryPath;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Migrate every supported file under `targets`. Writes only when `write` is true. */
|
|
261
|
+
export async function migrateLucide(targets, { write = false, version }) {
|
|
262
|
+
const files = [];
|
|
263
|
+
for (const target of targets) {
|
|
264
|
+
for await (const file of walk(target)) {
|
|
265
|
+
const extension = path.extname(file);
|
|
266
|
+
const isManifest = path.basename(file) === "package.json";
|
|
267
|
+
if (!isManifest && !sourceExtensions.has(extension) && !styleExtensions.has(extension)) continue;
|
|
268
|
+
const text = await readFile(file, "utf8");
|
|
269
|
+
if (!/lucide/i.test(text)) continue;
|
|
270
|
+
const result = isManifest
|
|
271
|
+
? migratePackageJson(text, version)
|
|
272
|
+
: migrateSource(text, { styles: styleExtensions.has(extension) });
|
|
273
|
+
if (result.changes === 0 && result.warnings.length === 0) continue;
|
|
274
|
+
if (write && result.changes > 0) await writeFile(file, result.text);
|
|
275
|
+
files.push({ file, changes: result.changes, warnings: result.warnings });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return files;
|
|
279
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vadivam",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.65",
|
|
4
4
|
"description": "Pixel-perfect 24px outline SVG icons with tree-shakeable ESM imports and metadata.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Praveen Juge",
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"javascript"
|
|
27
27
|
],
|
|
28
28
|
"type": "module",
|
|
29
|
+
"bin": {
|
|
30
|
+
"vadivam": "dist/cli.js"
|
|
31
|
+
},
|
|
29
32
|
"sideEffects": [
|
|
30
33
|
"./dist/font/vadivam.css"
|
|
31
34
|
],
|
|
@@ -44,7 +44,7 @@ Peer ranges: React 18 or newer; React Native 0.71+ with React 18–19 and `react
|
|
|
44
44
|
|
|
45
45
|
Never guess an icon name. Names follow Lucide (`trash`, `arrow-right`, `circle-check`), and every icon carries Lucide's search `tags` (synonyms) and `categories`.
|
|
46
46
|
|
|
47
|
-
- With the MCP server connected, call `search_icons` with a few words (`{ "query": "delete" }`, optionally `"category": "arrows"`), then `get_icon` with the chosen name and a `format` such as `react` or `svg`. `list_categories` lists category ids.
|
|
47
|
+
- With the MCP server connected, call `search_icons` with a few words (`{ "query": "delete" }`, optionally `"category": "arrows"`), then `get_icon` with the chosen name and a `format` such as `react` or `svg`. `list_categories` lists category ids. To pick icons for a whole screen, call `suggest_icons` with a short description such as `"settings sidebar with search, profile, and logout"`.
|
|
48
48
|
- Without it, search the manifest:
|
|
49
49
|
|
|
50
50
|
```ts
|
|
@@ -60,7 +60,7 @@ Never guess an icon name. Names follow Lucide (`trash`, `arrow-right`, `circle-c
|
|
|
60
60
|
|
|
61
61
|
### Connect the MCP server
|
|
62
62
|
|
|
63
|
-
The MCP server exposes `search_icons`, `get_icon`, `list_categories`, and `get_usage`. Use the hosted endpoint, or run the `vadivam-mcp` package over stdio (it bundles the icon data and works offline):
|
|
63
|
+
The MCP server exposes `search_icons`, `suggest_icons`, `get_icon`, `list_categories`, and `get_usage`. Use the hosted endpoint, or run the `vadivam-mcp` package over stdio (it bundles the icon data and works offline):
|
|
64
64
|
|
|
65
65
|
```sh
|
|
66
66
|
claude mcp add --transport http vadivam https://vadivam.praveenjuge.com/mcp
|
|
@@ -145,7 +145,7 @@ Replace `activity` with any icon name. Prefer the `vadivam-react` package when t
|
|
|
145
145
|
- Icon-only button: name the button, `<button aria-label="Search"><Search aria-hidden="true" /></button>`.
|
|
146
146
|
- React Native: `<Activity accessibilityLabel="Service is active" accessible />`.
|
|
147
147
|
|
|
148
|
-
See [
|
|
148
|
+
See [Accessibility](https://vadivam.praveenjuge.com/docs/accessibility.md).
|
|
149
149
|
|
|
150
150
|
## Gotchas
|
|
151
151
|
|
|
@@ -163,7 +163,9 @@ See [Usage](https://vadivam.praveenjuge.com/docs/usage.md#accessibility).
|
|
|
163
163
|
- [Icon catalog](https://vadivam.praveenjuge.com/): browse every icon; each is also served at `https://vadivam.praveenjuge.com/icons/<kebab-name>.svg`.
|
|
164
164
|
- [shadcn/ui](https://vadivam.praveenjuge.com/docs/shadcn.md): `npx shadcn@latest add https://vadivam.praveenjuge.com/r/<kebab-name>.json` copies a self-contained React icon component into the project.
|
|
165
165
|
- [AI agents](https://vadivam.praveenjuge.com/docs/ai-agents.md): how agents should consume Vadivam.
|
|
166
|
+
- [Categories and naming](https://vadivam.praveenjuge.com/docs/categories.md): name forms per package, tags, and every category with its icon count.
|
|
166
167
|
- [Design guide](https://vadivam.praveenjuge.com/docs/design-guide.md): the drawing rules every icon follows.
|
|
168
|
+
- [How icons are built](https://vadivam.praveenjuge.com/docs/how-icons-are-built.md): the icon pipeline and every rule an SVG must pass.
|
|
167
169
|
- [MCP server](https://vadivam.praveenjuge.com/docs/mcp.md): tools, hosted endpoint, and local setup.
|
|
168
170
|
- [Contributing](https://vadivam.praveenjuge.com/docs/contributing.md): add icons or change packages.
|
|
169
171
|
- [Changelog](https://vadivam.praveenjuge.com/changelog.md): release notes, newest first.
|