rdicons 0.2.0 → 0.4.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/README.md +16 -0
- package/dist/cli.js +271 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ npx rdicons add tabler:bell ph:heart --format tsx --out src/icons --color "#4f46
|
|
|
18
18
|
rdicons add <set:name> [more...] download icons into a folder
|
|
19
19
|
rdicons search <query> [--set s] find icons by name
|
|
20
20
|
rdicons info <set:name> show the code for one icon (every install option)
|
|
21
|
+
rdicons migrate <path...> --to <set> switch a project's icon imports to another library
|
|
21
22
|
rdicons sets list the 19 libraries with their licenses
|
|
22
23
|
```
|
|
23
24
|
|
|
@@ -30,6 +31,21 @@ rdicons sets list the 19 libraries with their licenses
|
|
|
30
31
|
| `--stroke` | the icon's own | stroke width (outline icons) |
|
|
31
32
|
| `--padding` | `0` | extra space around the icon |
|
|
32
33
|
| `--background` | none | solid background |
|
|
34
|
+
| `--animated [mode]` | off | rd icons only: `hover` (default), `parent`, `play`, `loop` |
|
|
35
|
+
| `--speed` | `1` | animation speed (with `--animated`) |
|
|
36
|
+
|
|
37
|
+
## Switch icon library: `migrate`
|
|
38
|
+
|
|
39
|
+
Moving a project from one icon library to another usually means editing every import by hand. `migrate` does it for you:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx rdicons migrate src --to tabler # preview: nothing is changed
|
|
43
|
+
npx rdicons migrate src --to rd --write # apply it (commit your work first)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
It reads the imports of `lucide-react`, `@tabler/icons-react`, `@phosphor-icons/react`, `@heroicons/react` and `react-icons` (Feather, Lucide, Tabler, Phosphor, Remix, Bootstrap, Octicons, Radix, Heroicons 2, Material) and points them at `@rdicons/react/<library>` (or `@rdicons/vue/<library>` for Vue). Icons are matched by name and by common alternative names (`home` and `house`); the names you use in your JSX stay the same. Icons with no match stay on the old import and are listed, so the project keeps building. Tabler's `stroke` prop becomes `strokeWidth`. `--ext` chooses the file types, `node_modules` and build folders are skipped.
|
|
47
|
+
| `--animated [mode]` | off | add the icon's animation (rd icons): `hover` (default), `parent`, `play` or `loop`. The CSS is inside the file. Other libraries have no animation and get the still icon |
|
|
48
|
+
| `--speed <n>` | `1` | animation speed, `2` is twice as fast (needs `--animated`) |
|
|
33
49
|
|
|
34
50
|
Icons come from https://rdicons.vimalbhatt.com (set `RDICONS_URL` to use another address). Needs Node 18+.
|
|
35
51
|
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
5
|
-
import { join, resolve } from "path";
|
|
4
|
+
import { mkdir, writeFile as writeFile2 } from "fs/promises";
|
|
5
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
6
6
|
|
|
7
7
|
// ../core/dist/index.js
|
|
8
8
|
var STROKE_WIDTH_RE = /stroke-width="[^"]*"/g;
|
|
@@ -124,8 +124,214 @@ export function Example() {
|
|
|
124
124
|
];
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// src/migrate.ts
|
|
128
|
+
import { readdir, readFile, stat, writeFile } from "fs/promises";
|
|
129
|
+
import { join, relative, resolve } from "path";
|
|
130
|
+
var kebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-zA-Z])(\d)/g, "$1-$2").toLowerCase();
|
|
131
|
+
var stripSuffix = (s, suffix) => s.length > suffix.length && s.endsWith(suffix) ? s.slice(0, -suffix.length) : s;
|
|
132
|
+
var stripPrefix = (s, prefix) => s.length > prefix.length && s.startsWith(prefix) ? s.slice(prefix.length) : s;
|
|
133
|
+
var lucide = (framework) => ({ set: "lucide", framework, slug: (n) => kebab(stripSuffix(stripPrefix(n, "Lucide"), "Icon")) });
|
|
134
|
+
var REACT_ICONS = {
|
|
135
|
+
fi: { set: "feather" },
|
|
136
|
+
lu: { set: "lucide" },
|
|
137
|
+
tb: { set: "tabler" },
|
|
138
|
+
pi: { set: "ph" },
|
|
139
|
+
ri: { set: "ri" },
|
|
140
|
+
bs: { set: "bi" },
|
|
141
|
+
go: { set: "octicon" },
|
|
142
|
+
rx: { set: "radix-icons" },
|
|
143
|
+
hi2: { set: "heroicons" },
|
|
144
|
+
md: { set: "material-symbols" }
|
|
145
|
+
};
|
|
146
|
+
function sourceFor(spec) {
|
|
147
|
+
if (spec === "lucide-react") return lucide("react");
|
|
148
|
+
if (spec === "lucide-vue-next") return lucide("vue");
|
|
149
|
+
if (spec === "@tabler/icons-react") return { set: "tabler", framework: "react", slug: (n) => kebab(stripPrefix(n, "Icon")), warn: 'Tabler uses the prop "stroke"; rdicons uses "strokeWidth" (fixed for you where it is a plain prop)' };
|
|
150
|
+
if (spec === "@tabler/icons-vue") return { set: "tabler", framework: "vue", slug: (n) => kebab(stripPrefix(n, "Icon")) };
|
|
151
|
+
if (spec === "@phosphor-icons/react") return { set: "ph", framework: "react", slug: (n) => kebab(stripSuffix(stripPrefix(n, "Ph"), "Icon")), warn: 'Phosphor uses the prop "weight" (regular, bold, fill...); rdicons has no such prop, so pick the style by name (house-bold) instead' };
|
|
152
|
+
const hero = spec.match(/^@heroicons\/(react|vue)\/(?:\d\d\/)?(outline|solid)$/);
|
|
153
|
+
if (hero) return { set: "heroicons", framework: hero[1], slug: (n) => kebab(stripSuffix(n, "Icon")), style: hero[2] === "solid" ? "filled" : "outline" };
|
|
154
|
+
const ri = spec.match(/^react-icons\/([a-z0-9]+)$/);
|
|
155
|
+
if (ri && REACT_ICONS[ri[1]]) {
|
|
156
|
+
const pack = ri[1];
|
|
157
|
+
const prefix = pack === "hi2" ? "Hi" : pack[0].toUpperCase() + pack.slice(1);
|
|
158
|
+
return {
|
|
159
|
+
set: REACT_ICONS[pack].set,
|
|
160
|
+
framework: "react",
|
|
161
|
+
slug: (n) => {
|
|
162
|
+
if (!n.startsWith(prefix)) return null;
|
|
163
|
+
let s = kebab(n.slice(prefix.length));
|
|
164
|
+
if (pack === "hi2") s = s.replace(/^(outline|solid|mini)-/, "");
|
|
165
|
+
if (pack === "md") s = s.replace(/-outline$/, "");
|
|
166
|
+
return s;
|
|
167
|
+
},
|
|
168
|
+
style: void 0
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
var ALIASES = [
|
|
174
|
+
["home", "house"],
|
|
175
|
+
["trash", "trash-2", "bin", "delete"],
|
|
176
|
+
["settings", "cog", "gear", "setting"],
|
|
177
|
+
["search", "magnifier", "magnifying-glass"],
|
|
178
|
+
["mail", "envelope", "email"],
|
|
179
|
+
["user", "person", "account"],
|
|
180
|
+
["edit", "pencil", "pen"],
|
|
181
|
+
["x", "close", "times", "cross"],
|
|
182
|
+
["check", "tick"],
|
|
183
|
+
["plus", "add"],
|
|
184
|
+
["minus", "remove"],
|
|
185
|
+
["bell", "notification"],
|
|
186
|
+
["star", "favorite"],
|
|
187
|
+
["share", "share-2"],
|
|
188
|
+
["lock", "padlock"],
|
|
189
|
+
["calendar", "calendar-days"],
|
|
190
|
+
["camera", "photo-camera"],
|
|
191
|
+
["image", "picture", "photo"],
|
|
192
|
+
["eye", "visibility"],
|
|
193
|
+
["link", "chain"],
|
|
194
|
+
["folder", "directory"],
|
|
195
|
+
["file", "document"],
|
|
196
|
+
["message", "chat", "comment"],
|
|
197
|
+
["phone", "telephone", "call"],
|
|
198
|
+
["heart", "favourite"],
|
|
199
|
+
["arrow-left", "arrow-small-left", "left-arrow"],
|
|
200
|
+
["arrow-right", "arrow-small-right", "right-arrow"],
|
|
201
|
+
["chevron-down", "angle-down", "caret-down"],
|
|
202
|
+
["chevron-up", "angle-up", "caret-up"],
|
|
203
|
+
["chevron-left", "angle-left", "caret-left"],
|
|
204
|
+
["chevron-right", "angle-right", "caret-right"]
|
|
205
|
+
];
|
|
206
|
+
var STYLE_SUFFIX = /-(fill|filled|solid|line|bold|thin|light|duotone|regular|outline|sharp)$/;
|
|
207
|
+
var styleOf = (slug) => /-(fill|filled|solid)$/.test(slug) ? "filled" : "outline";
|
|
208
|
+
function findIcon(slug, target, forceStyle) {
|
|
209
|
+
const style = forceStyle ?? styleOf(slug);
|
|
210
|
+
const base = slug.replace(STYLE_SUFFIX, "");
|
|
211
|
+
const group = ALIASES.find((g) => g.includes(base));
|
|
212
|
+
const bases = [base, ...group ? group.filter((x) => x !== base) : []];
|
|
213
|
+
const variants = style === "filled" ? ["-fill", "-filled", "-solid", "-fill-24"] : ["", "-line", "-regular", "-outline", "-24-regular"];
|
|
214
|
+
const candidates = [slug, ...bases.flatMap((b) => variants.map((v) => b + v)), ...bases];
|
|
215
|
+
for (const c of candidates) {
|
|
216
|
+
const comp = target.get(c);
|
|
217
|
+
if (comp !== void 0) return { name: c, comp };
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".nuxt", ".svelte-kit", "coverage", "out", ".turbo"]);
|
|
222
|
+
async function* walk(path, exts) {
|
|
223
|
+
const s = await stat(path);
|
|
224
|
+
if (s.isFile()) {
|
|
225
|
+
yield path;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
for (const e of await readdir(path, { withFileTypes: true })) {
|
|
229
|
+
if (e.isDirectory()) {
|
|
230
|
+
if (!SKIP_DIRS.has(e.name)) yield* walk(join(path, e.name), exts);
|
|
231
|
+
} else if (exts.has(e.name.slice(e.name.lastIndexOf(".") + 1).toLowerCase())) yield join(path, e.name);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
var IMPORT = /import\s+(type\s+)?\{([^}]*)\}\s*from\s*(['"])([^'"]+)\3([ \t]*;?)([ \t]*\r?\n)?/g;
|
|
235
|
+
async function migrate(paths, opts, deps) {
|
|
236
|
+
const targetNames = new Map(await deps.names(opts.to));
|
|
237
|
+
const exts = new Set(opts.exts);
|
|
238
|
+
const files = [];
|
|
239
|
+
const frameworks = /* @__PURE__ */ new Set();
|
|
240
|
+
let changed = 0;
|
|
241
|
+
const seen = /* @__PURE__ */ new Set();
|
|
242
|
+
for (const p of paths) {
|
|
243
|
+
for await (const file of walk(resolve(p), exts)) {
|
|
244
|
+
if (seen.has(file)) continue;
|
|
245
|
+
seen.add(file);
|
|
246
|
+
const text = await readFile(file, "utf8");
|
|
247
|
+
if (!/import\s+(type\s+)?\{/.test(text)) continue;
|
|
248
|
+
const report = { file, migrated: [], unmatched: [], warnings: [] };
|
|
249
|
+
const jsxFixes = [];
|
|
250
|
+
const merged = /* @__PURE__ */ new Map();
|
|
251
|
+
const out = text.replace(IMPORT, (whole, typeKw, list, quote, spec, semi, nl) => {
|
|
252
|
+
const eol = nl ?? "";
|
|
253
|
+
const source = sourceFor(spec);
|
|
254
|
+
if (!source) return whole;
|
|
255
|
+
const kept = [];
|
|
256
|
+
const moved = [];
|
|
257
|
+
const hit = [];
|
|
258
|
+
for (const raw of list.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
259
|
+
const isType = /^type\s+/.test(raw);
|
|
260
|
+
const clean = raw.replace(/^type\s+/, "");
|
|
261
|
+
const [exported, local] = clean.split(/\s+as\s+/).map((x) => x.trim());
|
|
262
|
+
const slug = source.slug(exported);
|
|
263
|
+
const found = slug ? findIcon(slug, targetNames, source.style) : null;
|
|
264
|
+
if (!found || isType) {
|
|
265
|
+
kept.push(raw);
|
|
266
|
+
if (!isType) report.unmatched.push(`${exported} (${spec})`);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const localName = local ?? exported;
|
|
270
|
+
moved.push(found.comp === localName ? found.comp : `${found.comp} as ${localName}`);
|
|
271
|
+
hit.push(`${exported} -> ${found.name}`);
|
|
272
|
+
if (spec === "@tabler/icons-react") jsxFixes.push(localName);
|
|
273
|
+
}
|
|
274
|
+
if (!moved.length) return whole;
|
|
275
|
+
frameworks.add(source.framework);
|
|
276
|
+
report.migrated.push(...hit.map((h) => {
|
|
277
|
+
const [from, to] = h.split(" -> ");
|
|
278
|
+
return { from: `${source.set}:${from}`, to: `${opts.to}:${to}` };
|
|
279
|
+
}));
|
|
280
|
+
if (source.warn) report.warnings.push(source.warn);
|
|
281
|
+
const pkg = `@rdicons/${source.framework}/${opts.to}`;
|
|
282
|
+
const q = quote;
|
|
283
|
+
const key = `${typeKw ?? ""}|${pkg}`;
|
|
284
|
+
const keptLine = kept.length ? `import ${typeKw ?? ""}{ ${kept.join(", ")} } from ${q}${spec}${q}${semi}${eol || "\n"}` : "";
|
|
285
|
+
const existing = merged.get(key);
|
|
286
|
+
if (existing) {
|
|
287
|
+
existing.specs.push(...moved);
|
|
288
|
+
return keptLine;
|
|
289
|
+
}
|
|
290
|
+
merged.set(key, { specs: [...moved], line: (specs) => `import ${typeKw ?? ""}{ ${specs.join(", ")} } from ${q}${pkg}${q}${semi}${eol}` });
|
|
291
|
+
return `${keptLine}\0RD:${key}\0`;
|
|
292
|
+
}).replace(/\u0000RD:([^\u0000]*)\u0000/g, (_, key) => {
|
|
293
|
+
const m = merged.get(key);
|
|
294
|
+
return m.line([...new Set(m.specs)]);
|
|
295
|
+
});
|
|
296
|
+
if (out === text) {
|
|
297
|
+
if (report.unmatched.length) files.push(report);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
let final = out;
|
|
301
|
+
for (const name of new Set(jsxFixes)) final = final.replace(new RegExp(`(<${name}\\b[^>]*?)\\sstroke=`, "g"), "$1 strokeWidth=");
|
|
302
|
+
changed++;
|
|
303
|
+
files.push(report);
|
|
304
|
+
if (opts.write) await writeFile(file, final, "utf8");
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
files,
|
|
309
|
+
changed,
|
|
310
|
+
written: opts.write,
|
|
311
|
+
installHint: [...frameworks].map((f) => `npm install @rdicons/${f}`)
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function formatReport(r, root) {
|
|
315
|
+
const lines = [];
|
|
316
|
+
let moved = 0;
|
|
317
|
+
const left = [];
|
|
318
|
+
for (const f of r.files) {
|
|
319
|
+
moved += f.migrated.length;
|
|
320
|
+
if (f.migrated.length) lines.push(`${r.written ? "\u2713" : "\xB7"} ${relative(root, f.file) || f.file}: ${f.migrated.length} icon${f.migrated.length === 1 ? "" : "s"}`);
|
|
321
|
+
for (const u of f.unmatched) left.push(` ${relative(root, f.file) || f.file}: ${u}`);
|
|
322
|
+
}
|
|
323
|
+
lines.push("");
|
|
324
|
+
lines.push(`${moved} icon${moved === 1 ? "" : "s"} in ${r.changed} file${r.changed === 1 ? "" : "s"} ${r.written ? "rewritten" : "can be rewritten"}.`);
|
|
325
|
+
if (left.length) lines.push("", `No match in the target library (left on the old import, pick a replacement by hand):`, ...left);
|
|
326
|
+
const warnings = [...new Set(r.files.flatMap((f) => f.warnings))];
|
|
327
|
+
if (warnings.length) lines.push("", "Check:", ...warnings.map((w) => " - " + w));
|
|
328
|
+
if (r.installHint.length && r.changed) lines.push("", `Then install the new package: ${r.installHint.join(" and ")}`);
|
|
329
|
+
if (!r.written && r.changed) lines.push("", "This was a preview. Nothing was changed: run the same command with --write to apply it (commit your work first).");
|
|
330
|
+
return lines.join("\n");
|
|
331
|
+
}
|
|
332
|
+
|
|
127
333
|
// src/cli.ts
|
|
128
|
-
var VERSION = "0.
|
|
334
|
+
var VERSION = "0.4.0";
|
|
129
335
|
var BASE = (process.env.RDICONS_URL || "https://rdicons.vimalbhatt.com").replace(/\/$/, "");
|
|
130
336
|
var HELP = `rdicons ${VERSION}: add open-source icons to your project
|
|
131
337
|
|
|
@@ -133,6 +339,7 @@ Usage
|
|
|
133
339
|
rdicons add <set:name> [more...] [options] download icons into a folder
|
|
134
340
|
rdicons search <query> [--set lucide] find icons by name
|
|
135
341
|
rdicons info <set:name> show the code to use one icon (every install option)
|
|
342
|
+
rdicons migrate <path...> --to <library> switch a project's icon imports to another library (preview first)
|
|
136
343
|
rdicons sets list the 19 icon libraries
|
|
137
344
|
|
|
138
345
|
Options for "add"
|
|
@@ -143,14 +350,48 @@ Options for "add"
|
|
|
143
350
|
--stroke <number> stroke width for outline icons
|
|
144
351
|
--padding <number> extra space around the icon (viewBox units)
|
|
145
352
|
--background <color> solid background behind the icon
|
|
353
|
+
--animated [mode] add the icon's animation (rd icons): hover (default), parent, play or loop
|
|
354
|
+
--speed <number> animation speed, 2 is twice as fast (default 1; needs --animated)
|
|
355
|
+
|
|
356
|
+
Options for "migrate"
|
|
357
|
+
--to <library> the library to move to, e.g. lucide, tabler, ph, rd (see "rdicons sets")
|
|
358
|
+
--write apply the changes (without it you only get a preview)
|
|
359
|
+
--ext js,ts,tsx,... which files to look at (default js,jsx,ts,tsx,mjs,cjs,vue)
|
|
146
360
|
|
|
147
361
|
Examples
|
|
362
|
+
npx rdicons migrate src --to tabler preview: lucide-react imports -> @rdicons/react/tabler
|
|
363
|
+
npx rdicons migrate src --to rd --write apply it
|
|
148
364
|
npx rdicons add lucide:house
|
|
149
365
|
npx rdicons add tabler:bell ph:heart --format tsx --out src/icons --color "#4f46e5"
|
|
366
|
+
npx rdicons add rd:bell --animated loop --speed 1.5 --format tsx
|
|
150
367
|
npx rdicons search "arrow right" --set lucide
|
|
151
368
|
|
|
152
369
|
Icons keep their original license, shown by "rdicons sets". Site: ${BASE}
|
|
153
370
|
`;
|
|
371
|
+
var MODES = ["hover", "parent", "play", "loop"];
|
|
372
|
+
var animCache = /* @__PURE__ */ new Map();
|
|
373
|
+
async function fetchAnimation(set, name) {
|
|
374
|
+
if (!animCache.has(set)) animCache.set(set, await getJson(`/api/animations?set=${encodeURIComponent(set)}`));
|
|
375
|
+
return animCache.get(set)?.[name] ?? null;
|
|
376
|
+
}
|
|
377
|
+
function animatedSvg(name, a, o) {
|
|
378
|
+
const css = [o.color ? `color:${o.color}` : "", o.speed !== 1 ? `--rd-speed:${+(1 / o.speed).toFixed(2)}` : ""].filter(Boolean).join(";");
|
|
379
|
+
const body = o.stroke == null ? a.b : a.b.replace(/stroke-width="[^"]*"/g, `stroke-width="${o.stroke}"`);
|
|
380
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="${o.size}" height="${o.size}" class="rd-a-${name}" data-rd-animate="${o.mode}"${css ? ` style="${css}"` : ""}>
|
|
381
|
+
<style>${a.c}</style>
|
|
382
|
+
${body}
|
|
383
|
+
</svg>`;
|
|
384
|
+
}
|
|
385
|
+
function jsxifyAnimated(jsx, ts) {
|
|
386
|
+
const cast = ts ? " as React.CSSProperties" : "";
|
|
387
|
+
return jsx.replace(/<style>([\s\S]*?)<\/style>/, (_, css) => "<style>{`" + css.replace(/[`\\]|\$\{/g, (m) => "\\" + m) + "`}</style>").replace(/ style="([^"]*)"/, (_, decl) => {
|
|
388
|
+
const props = decl.split(";").filter(Boolean).map((d) => {
|
|
389
|
+
const [k, ...v] = d.split(":");
|
|
390
|
+
return `'${k.trim()}': '${v.join(":").trim()}'`;
|
|
391
|
+
});
|
|
392
|
+
return ` style={{ ${props.join(", ")} }${cast}}`;
|
|
393
|
+
});
|
|
394
|
+
}
|
|
154
395
|
var CliError = class extends Error {
|
|
155
396
|
};
|
|
156
397
|
function fail(msg) {
|
|
@@ -174,6 +415,8 @@ function parseArgs(argv) {
|
|
|
174
415
|
if (a.startsWith("--")) {
|
|
175
416
|
const [k, inline] = a.slice(2).split("=");
|
|
176
417
|
if (inline !== void 0) flags[k] = inline;
|
|
418
|
+
else if (k === "write") flags[k] = "true";
|
|
419
|
+
else if (k === "animated") flags[k] = MODES.includes(argv[i + 1]) ? argv[++i] : "true";
|
|
177
420
|
else if (argv[i + 1] !== void 0 && !argv[i + 1].startsWith("--")) flags[k] = argv[++i];
|
|
178
421
|
else flags[k] = "true";
|
|
179
422
|
} else positional.push(a);
|
|
@@ -234,11 +477,19 @@ async function cmdAdd(positional, flags) {
|
|
|
234
477
|
if (!positional.length) fail("Which icon? For example: rdicons add lucide:house");
|
|
235
478
|
const format = flags.format ?? "svg";
|
|
236
479
|
if (!["svg", "jsx", "tsx"].includes(format)) fail("--format must be svg, jsx or tsx");
|
|
237
|
-
const out =
|
|
480
|
+
const out = resolve2(flags.out ?? "icons");
|
|
481
|
+
const mode = flags.animated === void 0 ? null : flags.animated === "true" ? "hover" : flags.animated;
|
|
482
|
+
if (mode && !MODES.includes(mode)) fail(`--animated must be one of ${MODES.join(", ")} (or just --animated for hover)`);
|
|
483
|
+
const speed = flags.speed === void 0 ? 1 : Number(flags.speed);
|
|
484
|
+
if (!(speed > 0 && speed <= 10)) fail("--speed must be a number between 0 and 10, for example --speed 1.5");
|
|
485
|
+
if (flags.speed !== void 0 && !mode) fail("--speed only works together with --animated");
|
|
486
|
+
if (mode && (flags.padding !== void 0 || flags.background !== void 0)) fail("--animated cannot be combined with --padding or --background");
|
|
238
487
|
await mkdir(out, { recursive: true });
|
|
239
488
|
for (const ref of positional) {
|
|
240
489
|
const it = await fetchIcon(ref);
|
|
241
|
-
const
|
|
490
|
+
const anim = mode ? await fetchAnimation(it.s, it.n) : null;
|
|
491
|
+
if (mode && !anim) console.log(` note: ${it.s}:${it.n} has no animation (only rd icons move), so the still icon is written.`);
|
|
492
|
+
const svg = anim ? animatedSvg(it.n, anim, { size: flags.size ? Number(flags.size) : 24, color: flags.color, stroke: flags.stroke ? Number(flags.stroke) : null, mode, speed }) : buildSvg(
|
|
242
493
|
{ body: it.b, width: it.w ?? 24, height: it.h ?? 24 },
|
|
243
494
|
{
|
|
244
495
|
color: flags.color,
|
|
@@ -251,12 +502,23 @@ async function cmdAdd(positional, flags) {
|
|
|
251
502
|
);
|
|
252
503
|
const comp = it.c ?? toPascalCase(it.n);
|
|
253
504
|
const file = format === "svg" ? `${it.s}-${it.n}.svg` : `${comp}.${format}`;
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
505
|
+
const jsx = format === "svg" ? "" : svgToJsx(svg, comp);
|
|
506
|
+
const text = format === "svg" ? svg + "\n" : (anim ? jsxifyAnimated(jsx, format === "tsx") : jsx) + "\n";
|
|
507
|
+
await writeFile2(join2(out, file), text, "utf8");
|
|
508
|
+
console.log(`\u2713 ${it.s}:${it.n} -> ${join2(flags.out ?? "icons", file)}`);
|
|
257
509
|
}
|
|
258
510
|
console.log('\nTip: run "rdicons sets" to see each library license (some need attribution).');
|
|
259
511
|
}
|
|
512
|
+
async function cmdMigrate(positional, flags) {
|
|
513
|
+
if (!positional.length) fail("Which folder or file? For example: rdicons migrate src --to tabler");
|
|
514
|
+
const to = flags.to;
|
|
515
|
+
if (!to || to === "true") fail('Which library do you want to move to? For example: --to tabler (run "rdicons sets" for the list)');
|
|
516
|
+
const sets = await listSets();
|
|
517
|
+
if (!sets.some((s) => s.prefix === to)) fail(`Unknown library "${to}". Choose one of: ${sets.map((s) => s.prefix).join(", ")}`);
|
|
518
|
+
const exts = (flags.ext && flags.ext !== "true" ? flags.ext : "js,jsx,ts,tsx,mjs,cjs,vue").split(",").map((x) => x.trim().replace(/^\./, "").toLowerCase());
|
|
519
|
+
const result = await migrate(positional, { to, write: flags.write === "true", exts }, { names: (set) => getJson(`/api/names?set=${encodeURIComponent(set)}`) });
|
|
520
|
+
console.log(formatReport(result, process.cwd()));
|
|
521
|
+
}
|
|
260
522
|
async function main() {
|
|
261
523
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
262
524
|
const { flags, positional } = parseArgs(rest);
|
|
@@ -266,6 +528,7 @@ async function main() {
|
|
|
266
528
|
if (cmd === "search") return cmdSearch(positional, flags);
|
|
267
529
|
if (cmd === "info") return cmdInfo(positional);
|
|
268
530
|
if (cmd === "add") return cmdAdd(positional, flags);
|
|
531
|
+
if (cmd === "migrate") return cmdMigrate(positional, flags);
|
|
269
532
|
fail(`Unknown command "${cmd}". Run rdicons --help`);
|
|
270
533
|
}
|
|
271
534
|
main().catch((e) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rdicons",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "rdicons command line: search 78,000+ open-source icons and add them to your project as SVG, JSX or TSX. Try: npx rdicons add lucide:house",
|
|
5
5
|
"keywords": ["icons", "cli", "svg", "react", "tsx", "icon-generator", "lucide", "tabler", "rdicons"],
|
|
6
6
|
"license": "MIT",
|