rdicons 0.0.0-stage → 0.1.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.
Files changed (4) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +37 -2
  3. package/dist/cli.js +275 -0
  4. package/package.json +24 -4
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rdicons contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ The icon sets bundled at build time are third-party works under their own
24
+ licenses (see THIRD_PARTY_NOTICES.md). The MIT license above covers this
25
+ repository's source code only.
package/README.md CHANGED
@@ -1,3 +1,38 @@
1
- # Temporary Holding Version
1
+ # rdicons (CLI)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Add open-source icons to any project from the terminal. No install needed:
4
+
5
+ ```bash
6
+ npx rdicons add lucide:house
7
+ ```
8
+
9
+ That writes `icons/lucide-house.svg`. Want a React component instead?
10
+
11
+ ```bash
12
+ npx rdicons add tabler:bell ph:heart --format tsx --out src/icons --color "#4f46e5" --size 20
13
+ ```
14
+
15
+ ## Commands
16
+
17
+ ```
18
+ rdicons add <set:name> [more...] download icons into a folder
19
+ rdicons search <query> [--set s] find icons by name
20
+ rdicons info <set:name> show the code for one icon (every install option)
21
+ rdicons sets list the 19 libraries with their licenses
22
+ ```
23
+
24
+ | Option (add) | Default | |
25
+ |---|---|---|
26
+ | `--format` | `svg` | `svg`, `jsx` or `tsx` |
27
+ | `--out` | `./icons` | output folder |
28
+ | `--color` | keeps `currentColor` | any CSS color |
29
+ | `--size` | `24` | px |
30
+ | `--stroke` | the icon's own | stroke width (outline icons) |
31
+ | `--padding` | `0` | extra space around the icon |
32
+ | `--background` | none | solid background |
33
+
34
+ Icons come from https://rdicons.vimalbhatt.com (set `RDICONS_URL` to use another address). Needs Node 18+.
35
+
36
+ Each library keeps its own license (MIT, ISC, Apache-2.0, and CC BY 4.0 for Solar, which requires credit). Run `rdicons sets` to see them.
37
+
38
+ Prefer components in React? See [`@rdicons/react`](https://www.npmjs.com/package/@rdicons/react). MIT licensed.
package/dist/cli.js ADDED
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { mkdir, writeFile } from "fs/promises";
5
+ import { join, resolve } from "path";
6
+
7
+ // ../core/dist/index.js
8
+ var STROKE_WIDTH_RE = /stroke-width="[^"]*"/g;
9
+ function supportsStrokeWidth(body) {
10
+ return /stroke-width="/.test(body);
11
+ }
12
+ function buildSvg(icon, opts = {}) {
13
+ const { color = "#000000", strokeWidth = null, size = 24, background = null, padding = 0, keepCurrentColor = false } = opts;
14
+ const w = icon.width || 24;
15
+ const h = icon.height || 24;
16
+ let body = icon.body;
17
+ if (!keepCurrentColor) body = body.replace(/currentColor/g, color);
18
+ if (strokeWidth != null && supportsStrokeWidth(body)) {
19
+ body = body.replace(STROKE_WIDTH_RE, `stroke-width="${strokeWidth}"`);
20
+ }
21
+ const vbX = -padding;
22
+ const vbY = -padding;
23
+ const vbW = w + padding * 2;
24
+ const vbH = h + padding * 2;
25
+ const px = Math.round(size * vbW / Math.max(vbW, vbH));
26
+ const py = Math.round(size * vbH / Math.max(vbW, vbH));
27
+ const bg = background ? `<rect x="${vbX}" y="${vbY}" width="${vbW}" height="${vbH}" fill="${background}"/>` : "";
28
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${px}" height="${py}" viewBox="${vbX} ${vbY} ${vbW} ${vbH}">${bg}${body}</svg>`;
29
+ }
30
+ var JSX_ATTRS = {
31
+ class: "className",
32
+ "stroke-width": "strokeWidth",
33
+ "stroke-linecap": "strokeLinecap",
34
+ "stroke-linejoin": "strokeLinejoin",
35
+ "stroke-miterlimit": "strokeMiterlimit",
36
+ "stroke-opacity": "strokeOpacity",
37
+ "stroke-dasharray": "strokeDasharray",
38
+ "stroke-dashoffset": "strokeDashoffset",
39
+ "fill-rule": "fillRule",
40
+ "fill-opacity": "fillOpacity",
41
+ "clip-rule": "clipRule",
42
+ "clip-path": "clipPath",
43
+ "stop-color": "stopColor",
44
+ "stop-opacity": "stopOpacity",
45
+ "xmlns:xlink": "xmlnsXlink",
46
+ "xlink:href": "xlinkHref"
47
+ };
48
+ function svgToJsx(svg, componentName2 = "Icon") {
49
+ const jsx = svg.replace(/\s([a-zA-Z:-]+)=/g, (_m, attr) => ` ${JSX_ATTRS[attr] ?? attr}=`);
50
+ return `export function ${componentName2}(props: React.SVGProps<SVGSVGElement>) {
51
+ return (
52
+ ${jsx.replace("<svg ", "<svg {...props} ")}
53
+ )
54
+ }`;
55
+ }
56
+ function toPascalCase(name) {
57
+ const pascal = name.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((p) => p[0].toUpperCase() + p.slice(1)).join("");
58
+ if (!pascal) return "Icon";
59
+ return /^[0-9]/.test(pascal) ? "Icon" + pascal : pascal;
60
+ }
61
+ var libPackage = (set) => `@rdicons/react-${set}`;
62
+ function propsString(o = {}) {
63
+ const p = [];
64
+ if (o.size && o.size !== 24) p.push(`size={${o.size}}`);
65
+ if (o.color) p.push(`color="${o.color}"`);
66
+ if (o.strokeWidth != null) p.push(`strokeWidth={${o.strokeWidth}}`);
67
+ if (o.animate) p.push(o.animate === "hover" ? "animate" : `animate="${o.animate}"`);
68
+ return p.length ? " " + p.join(" ") : "";
69
+ }
70
+ function componentName(icon) {
71
+ if (icon.c) return icon.c;
72
+ const p = icon.n.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((x) => x[0].toUpperCase() + x.slice(1)).join("");
73
+ return /^[0-9]/.test(p) ? "Icon" + p : p || "Icon";
74
+ }
75
+ function cliFlags(o = {}) {
76
+ const f = [];
77
+ if (o.color) f.push(`--color "${o.color}"`);
78
+ if (o.size && o.size !== 24) f.push(`--size ${o.size}`);
79
+ if (o.strokeWidth != null) f.push(`--stroke ${o.strokeWidth}`);
80
+ return f.length ? " " + f.join(" ") : "";
81
+ }
82
+ function snippetsFor(icon, o = {}) {
83
+ const C = componentName(icon);
84
+ const props = propsString(o);
85
+ const pkg = libPackage(icon.s);
86
+ return [
87
+ {
88
+ id: "lib",
89
+ title: `React: only ${icon.s}`,
90
+ note: "Smallest install: just this one library.",
91
+ install: `npm install ${pkg}`,
92
+ lang: "tsx",
93
+ code: `import { ${C} } from '${pkg}'
94
+
95
+ export function Example() {
96
+ return <${C}${props} />
97
+ }`
98
+ },
99
+ {
100
+ id: "all",
101
+ title: "React: all libraries",
102
+ note: "One package with every library. Only the icons you import are bundled.",
103
+ install: "npm install @rdicons/react",
104
+ lang: "tsx",
105
+ code: `import { ${C} } from '@rdicons/react/${icon.s}'
106
+
107
+ export function Example() {
108
+ return <${C}${props} />
109
+ }`
110
+ },
111
+ {
112
+ id: "cli",
113
+ title: "Just this one icon",
114
+ note: "Copies the icon into your project as a file. Nothing to install, works with any framework.",
115
+ install: `npx rdicons add ${icon.s}:${icon.n} --format tsx --out src/icons${cliFlags(o)}`,
116
+ lang: "tsx",
117
+ code: `import { ${C} } from './icons/${C}'
118
+
119
+ export function Example() {
120
+ return <${C} />
121
+ }`
122
+ }
123
+ ];
124
+ }
125
+
126
+ // src/cli.ts
127
+ var VERSION = "0.1.0";
128
+ var BASE = (process.env.RDICONS_URL || "https://rdicons.vimalbhatt.com").replace(/\/$/, "");
129
+ var HELP = `rdicons ${VERSION}: add open-source icons to your project
130
+
131
+ Usage
132
+ rdicons add <set:name> [more...] [options] download icons into a folder
133
+ rdicons search <query> [--set lucide] find icons by name
134
+ rdicons info <set:name> show the code to use one icon (every install option)
135
+ rdicons sets list the 19 icon libraries
136
+
137
+ Options for "add"
138
+ --format svg|jsx|tsx output type (default svg)
139
+ --out <dir> folder to write into (default ./icons)
140
+ --color <css color> recolor the icon (default: keeps currentColor)
141
+ --size <px> width/height (default 24)
142
+ --stroke <number> stroke width for outline icons
143
+ --padding <number> extra space around the icon (viewBox units)
144
+ --background <color> solid background behind the icon
145
+
146
+ Examples
147
+ npx rdicons add lucide:house
148
+ npx rdicons add tabler:bell ph:heart --format tsx --out src/icons --color "#4f46e5"
149
+ npx rdicons search "arrow right" --set lucide
150
+
151
+ Icons keep their original license, shown by "rdicons sets". Site: ${BASE}
152
+ `;
153
+ var CliError = class extends Error {
154
+ };
155
+ function fail(msg) {
156
+ throw new CliError(msg);
157
+ }
158
+ async function getJson(path) {
159
+ let res;
160
+ try {
161
+ res = await fetch(BASE + path, { headers: { "user-agent": `rdicons-cli/${VERSION}` }, signal: AbortSignal.timeout(15e3) });
162
+ } catch (e) {
163
+ fail(`Could not reach ${BASE} (${e instanceof Error ? e.message : e}). Check your internet connection, or set RDICONS_URL.`);
164
+ }
165
+ if (!res.ok) fail(`${BASE}${path} answered ${res.status}`);
166
+ return await res.json();
167
+ }
168
+ function parseArgs(argv) {
169
+ const flags = {};
170
+ const positional = [];
171
+ for (let i = 0; i < argv.length; i++) {
172
+ const a = argv[i];
173
+ if (a.startsWith("--")) {
174
+ const [k, inline] = a.slice(2).split("=");
175
+ if (inline !== void 0) flags[k] = inline;
176
+ else if (argv[i + 1] !== void 0 && !argv[i + 1].startsWith("--")) flags[k] = argv[++i];
177
+ else flags[k] = "true";
178
+ } else positional.push(a);
179
+ }
180
+ return { flags, positional };
181
+ }
182
+ async function listSets() {
183
+ const data = await getJson("/api/sets");
184
+ return Array.isArray(data) ? data : data.sets;
185
+ }
186
+ async function cmdSets() {
187
+ const sets = await listSets();
188
+ const pad = Math.max(...sets.map((s) => s.prefix.length));
189
+ for (const s of sets) {
190
+ const note = s.license.startsWith("CC BY") ? " (credit the author)" : "";
191
+ console.log(`${s.prefix.padEnd(pad)} ${s.label.padEnd(24)} ${String(s.total).padStart(6)} icons ${s.license}${note}`);
192
+ }
193
+ }
194
+ async function cmdSearch(positional, flags) {
195
+ const q = positional.join(" ");
196
+ if (!q) fail('Tell me what to search for, e.g. rdicons search "arrow right"');
197
+ const limit = Math.min(Number(flags.limit ?? 20) || 20, 120);
198
+ const data = await getJson(
199
+ `/api/icons?q=${encodeURIComponent(q)}&set=${encodeURIComponent(flags.set ?? "")}&limit=${limit}`
200
+ );
201
+ if (!data.items.length) return console.log("No icons found.");
202
+ for (const it of data.items) console.log(`${it.s}:${it.n}`);
203
+ console.log(`
204
+ ${data.total.toLocaleString()} match${data.total === 1 ? "" : "es"}. Add one with: rdicons add ${data.items[0].s}:${data.items[0].n}`);
205
+ }
206
+ async function fetchIcon(ref) {
207
+ const i = ref.indexOf(":");
208
+ if (i < 1) fail(`"${ref}" should look like set:name, for example lucide:house`);
209
+ const set = ref.slice(0, i);
210
+ const name = ref.slice(i + 1);
211
+ const data = await getJson(`/api/icons?set=${encodeURIComponent(set)}&q=${encodeURIComponent(name)}&limit=120`);
212
+ const hit = data.items.find((x) => x.n === name);
213
+ if (!hit) {
214
+ const near = data.items.slice(0, 5).map((x) => `${x.s}:${x.n}`);
215
+ fail(`No icon "${ref}".${near.length ? ` Did you mean: ${near.join(", ")}?` : ` Try: rdicons search ${name}`}`);
216
+ }
217
+ return hit;
218
+ }
219
+ async function cmdInfo(positional) {
220
+ if (!positional[0]) fail("Which icon? For example: rdicons info lucide:house");
221
+ const it = await fetchIcon(positional[0]);
222
+ const [lib, all, one] = snippetsFor(it);
223
+ console.log(`${it.s}:${it.n} (React component: ${it.c ?? toPascalCase(it.n)})
224
+ `);
225
+ for (const [i, sn] of [lib, all, one].entries()) {
226
+ console.log(`${i + 1}) ${sn.title}${sn.note ? " - " + sn.note : ""}`);
227
+ if (sn.install) console.log(` $ ${sn.install}`);
228
+ console.log(sn.code.split("\n").map((l) => " " + l).join("\n") + "\n");
229
+ }
230
+ console.log('Run "rdicons sets" to see the license of this library (some need attribution).');
231
+ }
232
+ async function cmdAdd(positional, flags) {
233
+ if (!positional.length) fail("Which icon? For example: rdicons add lucide:house");
234
+ const format = flags.format ?? "svg";
235
+ if (!["svg", "jsx", "tsx"].includes(format)) fail("--format must be svg, jsx or tsx");
236
+ const out = resolve(flags.out ?? "icons");
237
+ await mkdir(out, { recursive: true });
238
+ for (const ref of positional) {
239
+ const it = await fetchIcon(ref);
240
+ const svg = buildSvg(
241
+ { body: it.b, width: it.w ?? 24, height: it.h ?? 24 },
242
+ {
243
+ color: flags.color,
244
+ keepCurrentColor: flags.color === void 0,
245
+ size: flags.size ? Number(flags.size) : 24,
246
+ strokeWidth: flags.stroke ? Number(flags.stroke) : null,
247
+ padding: flags.padding ? Number(flags.padding) : 0,
248
+ background: flags.background ?? null
249
+ }
250
+ );
251
+ const comp = it.c ?? toPascalCase(it.n);
252
+ const file = format === "svg" ? `${it.s}-${it.n}.svg` : `${comp}.${format}`;
253
+ const text = format === "svg" ? svg + "\n" : svgToJsx(svg, comp) + "\n";
254
+ await writeFile(join(out, file), text, "utf8");
255
+ console.log(`\u2713 ${it.s}:${it.n} -> ${join(flags.out ?? "icons", file)}`);
256
+ }
257
+ console.log('\nTip: run "rdicons sets" to see each library license (some need attribution).');
258
+ }
259
+ async function main() {
260
+ const [cmd, ...rest] = process.argv.slice(2);
261
+ const { flags, positional } = parseArgs(rest);
262
+ if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h" || flags.help) return console.log(HELP);
263
+ if (cmd === "--version" || cmd === "-v" || cmd === "version") return console.log(VERSION);
264
+ if (cmd === "sets") return cmdSets();
265
+ if (cmd === "search") return cmdSearch(positional, flags);
266
+ if (cmd === "info") return cmdInfo(positional);
267
+ if (cmd === "add") return cmdAdd(positional, flags);
268
+ fail(`Unknown command "${cmd}". Run rdicons --help`);
269
+ }
270
+ main().catch((e) => {
271
+ console.error(`
272
+ Error: ${e instanceof Error ? e.message : String(e)}
273
+ `);
274
+ process.exitCode = 1;
275
+ });
package/package.json CHANGED
@@ -1,6 +1,26 @@
1
1
  {
2
2
  "name": "rdicons",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
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
+ "keywords": ["icons", "cli", "svg", "react", "tsx", "icon-generator", "lucide", "tabler", "rdicons"],
6
+ "license": "MIT",
7
+ "author": "rdicons contributors",
8
+ "homepage": "https://rdicons.vimalbhatt.com/docs/cli",
9
+ "repository": { "type": "git", "url": "git+https://github.com/DevHydeOut/rdicons-lib.git" },
10
+ "bugs": { "url": "https://github.com/DevHydeOut/rdicons-lib/issues" },
11
+ "type": "module",
12
+ "bin": { "rdicons": "./dist/cli.js" },
13
+ "main": "./dist/cli.js",
14
+ "files": ["dist", "README.md", "LICENSE"],
15
+ "engines": { "node": ">=18" },
16
+ "publishConfig": { "access": "public" },
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "prepack": "npm run build"
20
+ },
21
+ "devDependencies": {
22
+ "@rdicons/core": "*",
23
+ "tsup": "^8.5.0",
24
+ "typescript": "^5.6.0"
25
+ }
26
+ }