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 CHANGED
@@ -8,7 +8,7 @@ Tree-shakeable 24px outline SVG icons for JavaScript, with raw assets and metada
8
8
  [![downloads](https://img.shields.io/npm/dw/vadivam?style=flat-square&color=666)](https://www.npmjs.com/package/vadivam)
9
9
  [![license](https://img.shields.io/npm/l/vadivam?style=flat-square&color=111)](https://github.com/praveenjuge/vadivam/blob/main/LICENSE)
10
10
 
11
- ![All Vadivam icons](https://raw.githubusercontent.com/praveenjuge/vadivam/main/apps/docs/public/preview.png?v=0.0.63)
11
+ ![All Vadivam icons](https://raw.githubusercontent.com/praveenjuge/vadivam/main/apps/docs/public/preview.png?v=0.0.65)
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
+ }
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.63",
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 [Usage](https://vadivam.praveenjuge.com/docs/usage.md#accessibility).
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.