@coolicons/cli 5.1.0 → 5.3.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 CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  <a href="https://coolicons.cool"><img src="https://raw.githubusercontent.com/krystonschwarze/coolicons/main/.github/assets/banner.png" alt="coolicons: Every icon for your next project. Free, wherever you build." width="100%"></a>
4
4
 
5
- Search [coolicons](https://coolicons.cool) and copy them into your project from the terminal: 1,258 icons in five styles and three corner radii. No install, no dependencies, no network. The whole set ships inside the package.
5
+ Search [coolicons](https://coolicons.cool) and copy them into your project from the terminal: 1,550 icons in five styles and three corner radii. No install, no dependencies, no network. The whole set ships inside the package.
6
6
 
7
7
  ```sh
8
- npx @coolicons/cli search delete
9
- npx @coolicons/cli show trash --style fill > trash.svg
10
- npx @coolicons/cli add trash bell --style bulk --radius round --out src/icons
8
+ npx @coolicons/cli search date
9
+ npx @coolicons/cli show calendar --style fill > calendar.svg
10
+ npx @coolicons/cli add calendar bell --style bulk --radius round --out src/icons
11
11
  ```
12
12
 
13
13
  ## Commands
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
- import { component, findIcon, iconSet, LookupError, RADII, search, STYLES, svg } from "./core.js";
4
+ import { component, findIcon, iconSet, LookupError, RADII, search, searchPartial, STYLES, svg } from "./core.js";
5
5
 
6
6
  const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
7
7
 
@@ -28,9 +28,9 @@ Options
28
28
  -v, --version Show the version
29
29
 
30
30
  Examples
31
- npx @coolicons/cli search delete
32
- npx @coolicons/cli show trash --style fill > trash.svg
33
- npx @coolicons/cli add trash bell --style bulk --radius round --out src/icons
31
+ npx @coolicons/cli search date
32
+ npx @coolicons/cli show calendar --style fill > calendar.svg
33
+ npx @coolicons/cli add calendar bell --style bulk --radius round --out src/icons
34
34
  `;
35
35
 
36
36
  const SHORT = { s: "style", r: "radius", o: "out", l: "limit", c: "category", h: "help", v: "version" };
@@ -69,27 +69,37 @@ function run({ command, words, options }) {
69
69
 
70
70
  if (command === "search") {
71
71
  const query = words.join(" ");
72
- if (!query) throw new LookupError("search needs a query, like: coolicons search delete");
72
+ if (!query) throw new LookupError("search needs a query, like: coolicons search date");
73
73
  const results = search(query, Number(options.limit ?? 20));
74
74
  if (options.json) return out(JSON.stringify(results.map((icon) => ({ ...icon, component: component(icon) })), null, 2));
75
+ const rows = (icons) => {
76
+ const width = Math.max(...icons.map((icon) => icon.name.length));
77
+ for (const icon of icons) out(`${icon.name.padEnd(width)} ${component(icon)} ${icon.category}${icon.missing?.length ? ` weak: no ${icon.missing.join(", ")}` : ""}${icon.keywords.length ? ` ${icon.keywords.join(", ")}` : ""}`);
78
+ };
75
79
  if (!results.length) {
76
- say(`No icon matches "${query}".`);
80
+ const partial = searchPartial(query, Number(options.limit ?? 20));
81
+ if (partial.length && !partial[0].missing.length) {
82
+ rows(partial);
83
+ say(`${partial.length} of ${iconSet().icons.length} icons`);
84
+ return;
85
+ }
86
+ say(`No icon matches "${query}".${partial.length ? " Weak matches by some words, check that they fit:" : ""}`);
87
+ if (partial.length) rows(partial);
77
88
  process.exitCode = 1;
78
89
  return;
79
90
  }
80
- const width = Math.max(...results.map((icon) => icon.name.length));
81
- for (const icon of results) out(`${icon.name.padEnd(width)} ${component(icon)} ${icon.category}${icon.keywords.length ? ` ${icon.keywords.join(", ")}` : ""}`);
91
+ rows(results);
82
92
  say(`${results.length} of ${iconSet().icons.length} icons`);
83
93
  return;
84
94
  }
85
95
 
86
96
  if (command === "show") {
87
- if (words.length !== 1) throw new LookupError("show needs exactly one name, like: coolicons show trash");
97
+ if (words.length !== 1) throw new LookupError("show needs exactly one name, like: coolicons show calendar");
88
98
  return out(svg(words[0], variant(options)));
89
99
  }
90
100
 
91
101
  if (command === "add") {
92
- if (!words.length) throw new LookupError("add needs at least one name, like: coolicons add trash bell");
102
+ if (!words.length) throw new LookupError("add needs at least one name, like: coolicons add calendar bell");
93
103
  const icons = words.map(findIcon);
94
104
  const folder = path.resolve(options.out ?? "icons");
95
105
  mkdirSync(folder, { recursive: true });
package/dist/core.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { matchIcons } from "./search.js";
2
+ import { matchIcons, scoreIcon, searchTerms } from "./search.js";
3
3
 
4
4
  export const STYLES = ["line", "duocolor", "duotone", "fill", "bulk"];
5
5
  export const RADII = ["sharp", "regular", "round"];
@@ -59,6 +59,22 @@ export function search(query, limit = 10) {
59
59
  return matchIcons(iconSet().icons, query).slice(0, limit);
60
60
  }
61
61
 
62
+ const FILLER = new Set(["a", "an", "and", "as", "for", "icon", "icons", "in", "of", "on", "or", "the", "to", "with"]);
63
+
64
+ export function searchPartial(query, limit = 10) {
65
+ const terms = searchTerms(query).filter((term) => !FILLER.has(term));
66
+ if (!terms.length) return [];
67
+ return iconSet()
68
+ .icons.map((icon) => {
69
+ const scores = terms.map((term) => scoreIcon(icon, [term]));
70
+ return { icon, matched: terms.filter((_, i) => scores[i]), rank: scores.reduce((sum, score) => sum + score, 0) };
71
+ })
72
+ .filter((entry) => entry.matched.length)
73
+ .sort((a, b) => b.matched.length - a.matched.length || b.rank - a.rank || a.icon.name.length - b.icon.name.length || a.icon.name.localeCompare(b.icon.name))
74
+ .slice(0, limit)
75
+ .map(({ icon, matched }) => ({ ...icon, missing: terms.filter((term) => !matched.includes(term)) }));
76
+ }
77
+
62
78
  function distance(a, b) {
63
79
  const d = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
64
80
  for (let i = 1; i <= a.length; i++) {
@@ -126,7 +142,7 @@ export function usage(inputs, { framework = "react", style = "line", radius = "r
126
142
  };
127
143
  }
128
144
  if (framework === "shadcn") {
129
- const items = icons.map((icon) => `${SITE}/r/${registryItem(icon, style, radius)}.json`);
145
+ const items = icons.map((icon) => `@coolicons/${registryItem(icon, style, radius)}`);
130
146
  const files = icons.map((icon) => `components/icons/${registryItem(icon, style, radius)}.tsx`);
131
147
  return { install: `npx shadcn@latest add ${items.join(" ")}`, code: `Adds ${files.join(", ")} exporting ${names.join(", ")}.` };
132
148
  }