@iconia/react 0.2.0 → 0.3.0-dev.3fa17d4

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
@@ -39,12 +39,12 @@ iconia pull
39
39
  import type { IconiaConfig } from "@iconia/react";
40
40
 
41
41
  export default {
42
- apiKey: process.env.ICONIA_API_KEY,
43
42
  collections: ["my-icons", "team-brand"],
43
+ uploadBatchSize: 50, // icons per upload request, 1–100 (default: 50)
44
44
  } satisfies IconiaConfig;
45
45
  ```
46
46
 
47
- Set `ICONIA_API_KEY` to an API key generated in your [iconia.io dashboard](https://iconia.io/settings/api-keys).
47
+ The CLI reads the API key from the `ICONIA_API_KEY` environment variable. You can also set `apiKey` directly in the config file, but using an env var is recommended. Generate a key in your [iconia.io dashboard](https://iconia.io/settings/api-keys).
48
48
 
49
49
  ---
50
50
 
@@ -113,17 +113,19 @@ Output shows a diff per collection:
113
113
 
114
114
  ### `iconia upload <path>`
115
115
 
116
- Upload a single SVG file or an entire folder of SVGs to a collection on iconia.io.
116
+ Upload a single SVG file or an entire folder of SVGs to a collection on iconia.io. Sends icons in batches (configurable via `uploadBatchSize`). Automatically retries on rate limiting.
117
117
 
118
118
  ```bash
119
119
  npx @iconia/react upload ./icons/arrow.svg --collection my-icons
120
120
  npx @iconia/react upload ./icons/ --collection my-icons
121
121
  npx @iconia/react upload ./icons/ --collection my-icons --tags ui,navigation
122
+ npx @iconia/react upload ./solid/ --collection my-icons --variant solid
122
123
  ```
123
124
 
124
125
  Options:
125
126
 
126
127
  - `-c, --collection <slug>` — target collection **(required)**
128
+ - `--variant <slug>` — assign all uploaded icons to a named variant (e.g. `solid`, `outline`)
127
129
  - `--tags <tags>` — comma-separated tags applied to all uploaded icons
128
130
 
129
131
  ---
@@ -148,6 +150,21 @@ export function App() {
148
150
 
149
151
  All icons accept standard `SVGProps<SVGSVGElement>` and forward refs.
150
152
 
153
+ ### Variants
154
+
155
+ Collections can contain multiple variants of the same icon set (e.g. solid, outline, filled). Each variant is available as a separate sub-path:
156
+
157
+ ```tsx
158
+ import { ArrowRight } from "@iconia/react/my-icons/solid";
159
+ import { ArrowRight as ArrowRightOutline } from "@iconia/react/my-icons/outline";
160
+ ```
161
+
162
+ Icons without a variant are imported directly from the collection path:
163
+
164
+ ```tsx
165
+ import { ArrowRight } from "@iconia/react/my-icons";
166
+ ```
167
+
151
168
  ---
152
169
 
153
170
  ## Lockfile
package/dist/cli/index.js CHANGED
@@ -1,27 +1,26 @@
1
1
  #!/usr/bin/env node
2
- var E=new AbortController;process.on("SIGINT",()=>{E.abort(),process.stderr.write(`
3
- `),process.exit(130)});import{Command as Ho}from"commander";import{Command as Ro}from"commander";import N from"fs";import ho from"path";import v from"picocolors";var jo=`import type { IconiaConfig } from '@iconia/react';
2
+ var W=new AbortController;process.on("SIGINT",()=>{W.abort(),process.stderr.write(`
3
+ `),process.exit(130)});import{Command as No}from"commander";import{Command as Jo}from"commander";import ro from"fs";import _o from"path";import M from"picocolors";var Ko=`import type { IconiaConfig } from '@iconia/react';
4
4
 
5
5
  const config: IconiaConfig = {
6
- apiKey: process.env.ICONIA_API_KEY ?? '',
7
6
  collections: [],
8
7
  };
9
8
 
10
9
  export default config;
11
- `,X=new Ro("init").description("Create an iconia.config.ts file in the current directory").action(()=>{let o=ho.resolve(process.cwd(),"iconia.config.ts");if(N.existsSync(o)){console.log(v.yellow("iconia.config.ts already exists. Skipping."));return}N.writeFileSync(o,jo),console.log(v.green("✓ Created iconia.config.ts")),console.log(`
12
- Next steps:`),console.log(` 1. Set ${v.cyan("ICONIA_API_KEY")} in your environment`),console.log(` 2. Edit ${v.cyan("iconia.config.ts")} to add your collections`),console.log(` 3. Run ${v.cyan("npx iconia pull")} to fetch icons`),console.log(`
13
- Import icons: ${v.cyan("import { MyIcon } from '@iconia/react/my-collection'")}`)});import{Command as bo}from"commander";import b from"picocolors";import vo from"ora";import{z as P}from"zod";import D from"path";var Fo=P.object({apiKey:P.string().min(1,"apiKey is required"),collections:P.array(P.string()).default([]),uploadBatchSize:P.number().int().min(1).max(100).default(50)}),Uo="https://api.iconia.io";async function j(){let o=D.resolve(process.cwd(),"iconia.config.ts"),r=D.resolve(process.cwd(),"iconia.config.js"),i;try{let a=await import(o);i=a.default??a}catch{try{let a=await import(r);i=a.default??a}catch{throw Error("Could not find iconia.config.ts or iconia.config.js\nRun `npx @iconia/react init` to create one.")}}let t=Fo.safeParse(i);if(!t.success){let a=t.error.issues.map((f)=>` - ${f.path.join(".")}: ${f.message}`).join(`
10
+ `,io=new Jo("init").description("Create an iconia.config.ts file in the current directory").action(()=>{let o=_o.resolve(process.cwd(),"iconia.config.ts");if(ro.existsSync(o)){console.log(M.yellow("iconia.config.ts already exists. Skipping."));return}ro.writeFileSync(o,Ko),console.log(M.green("✓ Created iconia.config.ts")),console.log(`
11
+ Next steps:`),console.log(` 1. Set ${M.cyan("ICONIA_API_KEY")} in your environment`),console.log(` 2. Edit ${M.cyan("iconia.config.ts")} to add your collections`),console.log(` 3. Run ${M.cyan("npx iconia pull")} to fetch icons`),console.log(`
12
+ Import icons: ${M.cyan("import { MyIcon } from '@iconia/react/my-collection'")}`)});import{Command as Zo}from"commander";import Z from"picocolors";import Io from"ora";import{z as S}from"zod";import to from"path";var qo=S.object({apiKey:S.string().optional(),collections:S.array(S.string()).default([]),uploadBatchSize:S.number().int().min(1).max(100).default(50)}),Ho="https://api.iconia.io";async function H(){let o=to.resolve(process.cwd(),"iconia.config.ts"),r=to.resolve(process.cwd(),"iconia.config.js"),t;try{let y=await import(o);t=y.default??y}catch{try{let y=await import(r);t=y.default??y}catch{throw Error("Could not find iconia.config.ts or iconia.config.js\nRun `npx @iconia/react init` to create one.")}}let i=qo.safeParse(t);if(!i.success){let y=i.error.issues.map((d)=>` - ${d.path.join(".")}: ${d.message}`).join(`
14
13
  `);throw Error(`Invalid iconia config:
15
- ${a}`)}let n=i.__internal?.endpoint??Uo;return{...t.data,apiUrl:n}}function W(o){return{Authorization:`ApiKey ${o.apiKey}`,"Content-Type":"application/json"}}async function Z(o,r=3){for(let i=0;i<=r;i++){let t=await o(E.signal);if(t.status!==429||i===r)return t;let c=parseInt(t.headers.get("Retry-After")??"60",10);process.stderr.write(`
16
- ⏳ Rate limited — waiting ${c}s before retry ${i+1}/${r}...
17
- `),await new Promise((n)=>{let a=()=>{clearTimeout(f),process.exit(130)},f=setTimeout(()=>{process.removeListener("SIGINT",a),n()},c*1000);process.once("SIGINT",a),E.signal.addEventListener("abort",a,{once:!0})})}return o(E.signal)}async function x(o){let r=await Z((t)=>fetch(new URL("/v1/collections",o.apiUrl).toString(),{headers:W(o),signal:t}));if(!r.ok){let t={};try{t=await r.json()}catch{}throw Error(`API error ${r.status}: ${t.error??r.statusText}`)}return(await r.json()).collections}async function K(o,r){let i=new URL("/v1/collections/icons",o.apiUrl);i.searchParams.set("collections",r.join(","));let t=await Z((n)=>fetch(i.toString(),{headers:W(o),signal:n}));if(!t.ok){let n={};try{n=await t.json()}catch{}throw Error(`API error ${t.status}: ${n.error??t.statusText}`)}return(await t.json()).icons}async function g(o,r,i){let t=await Z((n)=>fetch(new URL("/v1/icons",o.apiUrl).toString(),{method:"POST",headers:W(o),body:JSON.stringify({collectionSlug:r,icons:i}),signal:n}));if(!t.ok){let n={};try{n=await t.json()}catch{}throw Error(`API error ${t.status}: ${n.error??t.statusText}`)}return(await t.json()).results}import U from"fs";import l from"path";import{fileURLToPath as Bo}from"url";import Co from"picocolors";import{optimize as Lo}from"svgo";function _o(o){return Lo(o,{plugins:["removeDoctype","removeXMLProcInst","removeComments","removeMetadata","removeTitle","removeDesc"]}).data.replace(/<svg([^>]*)>/i,(i,t)=>`<svg${t.replace(/\s+(width|height)=['"][^'"]*['"]/gi,"")}>`)}function oo(o){let r={},i=/([\w:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g,t;while((t=i.exec(o))!==null){let c=t[1].replace(/^xlink:/,"").replace(/-([a-z])/g,(n,a)=>a.toUpperCase());r[c]=t[2]??t[3]??""}return r}function ro(o){let r=[],i=0;while(i<o.length){let t=o.indexOf("<",i);if(t===-1)break;let c=o[t+1];if(c==="/"||c==="!"||c==="?"){let u=o.indexOf(">",t);i=u===-1?o.length:u+1;continue}let n=t+1,a=!1,f="";while(n<o.length){let u=o[n];if(a){if(u===f)a=!1}else if(u==='"'||u==="'")a=!0,f=u;else if(u===">")break;n++}if(n>=o.length)break;let y=o.slice(t+1,n),e=y.trimEnd().endsWith("/"),w=e?y.slice(0,y.lastIndexOf("/")).trim():y.trim(),m=w.search(/\s/),s=m===-1?w:w.slice(0,m);if(!s){i=n+1;continue}let d=m===-1?"":w.slice(m+1),B=oo(d);if(i=n+1,e)r.push([s,B]);else{let u=`<${s}`,C=`</${s}>`,I=1,R=i;while(I>0&&R<o.length){let h=o.indexOf(u,R),$=o.indexOf(C,R);if($===-1)break;if(h!==-1&&h<$)I++,R=h+u.length;else if(I--,I===0){let po=o.slice(i,$),k=ro(po),Io=k.length>0?[s,B,k]:[s,B];r.push(Io),i=$+C.length}else R=$+C.length}if(I>0)r.push([s,B])}}return r}function io(o){let r=_o(o),i=r.match(/<svg([^>]*)>/i),t=oo(i?.[1]??"");delete t.xmlns,delete t.xmlnsXlink;let{xmlns:c,xmlnsXlink:n,...a}=t,y=r.match(/<svg[^>]*>([\s\S]*)<\/svg>/i)?.[1]?.trim()??"";return{iconNode:ro(y),svgAttrs:a}}function z(o){return o.replace(/[-_\s]+(.)/g,(r,i)=>i.toUpperCase()).replace(/^(.)/,(r,i)=>i.toUpperCase())}function to(o){if(o.length===0)return"";let r=["import { forwardRef, createElement } from 'react';","","const _r = (n) => n.map(([t, a, c], i) => createElement(t, { key: i, ...a }, ...(c ? _r(c) : [])));",""];for(let{name:i,iconNode:t,svgAttrs:c}of o){let n=z(i),a={viewBox:"0 0 24 24",...c},f=JSON.stringify(t),y=JSON.stringify(a);r.push(`export const ${n} = /*#__PURE__*/forwardRef(({ children, ...props }, ref) =>`,` createElement('svg', { ref, xmlns: 'http://www.w3.org/2000/svg', ...${y}, ...props },`,` ..._r(${f}),`," children"," )",");",`${n}.displayName = '${n}';`,"")}return r.join(`
18
- `)}function no(o){let r=["import type { ForwardRefExoticComponent, SVGProps, RefAttributes } from 'react';","","type IconComponent = ForwardRefExoticComponent<SVGProps<SVGSVGElement> & RefAttributes<SVGSVGElement>>;",""];for(let i of o)r.push(`export declare const ${z(i)}: IconComponent;`);return r.push(""),r.join(`
19
- `)}function G(){return l.resolve(l.dirname(Bo(import.meta.url)),"..")}function O(o,r){let i=[];for(let c of r)try{let{iconNode:n,svgAttrs:a}=io(c.svgContent);i.push({name:c.name,iconNode:n,svgAttrs:a})}catch(n){console.warn(Co.yellow(` ⚠ Skipped ${c.name}: ${n.message}`))}if(i.length===0)return 0;let t=G();return U.writeFileSync(l.join(t,`${o}.js`),to(i),"utf-8"),U.writeFileSync(l.join(t,`${o}.d.ts`),no(i.map((c)=>c.name)),"utf-8"),i.length}function q(){let o=l.join(G(),"..","package.json");try{let r=JSON.parse(U.readFileSync(o,"utf-8")),i=r.exports??{};if(i["./*"]?.import?.startsWith("./dist/"))return;r.exports={".":i["."]??{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"},"./*":{import:"./dist/*.js",types:"./dist/*.d.ts"}},U.writeFileSync(o,JSON.stringify(r,null,2)+`
20
- `,"utf-8")}catch{}}function co(o){let r=G(),i=l.join(r,`${o}.js`),t=l.join(r,`${o}.d.ts`);if(U.existsSync(i))U.unlinkSync(i);if(U.existsSync(t))U.unlinkSync(t)}import ao from"fs";import Eo from"path";var lo=".iconia-lock.json";function fo(){return Eo.resolve(process.cwd(),lo)}function L(){try{let o=ao.readFileSync(fo(),"utf-8");return JSON.parse(o)}catch{return{version:1,collections:{}}}}function _(o){ao.writeFileSync(fo(),JSON.stringify(o,null,2),"utf-8")}function S(o,r,i){return{...o,collections:{...o.collections,[r]:{icons:Object.fromEntries(i.map((t)=>[t.slug,t.fingerprint]))}}}}function eo(o,r){let{[r]:i,...t}=o.collections;return{...o,collections:t}}var mo=new bo("pull").description("Fetch icons and regenerate all collection files (replaces existing)").option("-c, --collection <slug>","Pull only this collection").action(async(o)=>{let r=vo("Loading config...").start(),i;try{i=await j()}catch(e){r.fail(b.red(e.message)),process.exit(1)}let t=o.collection?[o.collection]:i.collections;if(t.length===0){r.warn(b.yellow("No collections in config. Add one with `iconia add <slug>`."));return}if(o.collection&&!i.collections.includes(o.collection))r.fail(b.red(`Collection '${o.collection}' is not in your config.`)),process.exit(1);r.text=`Fetching icons for ${t.join(", ")}...`;let c;try{c=await K(i,t)}catch(e){r.fail(b.red(`Failed to fetch icons: ${e.message}`)),process.exit(1)}if(c.length===0){r.warn(b.yellow("No icons found."));return}r.text=`Generating ${c.length} icon${c.length!==1?"s":""}...`;let n=new Map;for(let e of c){let w=n.get(e.collectionSlug)??[];w.push(e),n.set(e.collectionSlug,w)}let a=L(),f=0,y=[];for(let[e,w]of n){let m=O(e,w);if(m===0)continue;f+=m,y.push(e),a=S(a,e,w.map((s)=>({slug:s.slug,fingerprint:s.fingerprint})))}if(_(a),q(),r.succeed(b.green(`Generated ${f} icon${f!==1?"s":""} across ${y.length} collection${y.length!==1?"s":""}`)),y.length>0){let e=y[0]??"",w=z(n.get(e)?.[0]?.name??"MyIcon");console.log(`
14
+ ${y}`)}let f=process.env.ICONIA_API_KEY||i.data.apiKey;if(!f)throw Error("No API key found. Set the ICONIA_API_KEY environment variable or add apiKey to iconia.config.ts.");let c=t.__internal?.endpoint??Ho;return{...i.data,apiKey:f,apiUrl:c}}function D(o){return{Authorization:`ApiKey ${o.apiKey}`,"Content-Type":"application/json"}}async function a(o,r=3){for(let t=0;t<=r;t++){let i=await o(W.signal);if(i.status!==429||t===r)return i;let f=parseInt(i.headers.get("Retry-After")??"60",10);process.stderr.write(`
15
+ ⏳ Rate limited — waiting ${f}s before retry ${t+1}/${r}...
16
+ `),await new Promise((m)=>{let c=()=>{clearTimeout(y),process.exit(130)},y=setTimeout(()=>{process.removeListener("SIGINT",c),m()},f*1000);process.once("SIGINT",c),W.signal.addEventListener("abort",c,{once:!0})})}return o(W.signal)}async function fo(o){let r=await a((i)=>fetch(new URL("/v1/collections",o.apiUrl).toString(),{headers:D(o),signal:i}));if(!r.ok){let i={};try{i=await r.json()}catch{}throw Error(`API error ${r.status}: ${i.error??r.statusText}`)}return(await r.json()).collections}async function e(o,r){let t=new URL("/v1/collections/icons",o.apiUrl);t.searchParams.set("collections",r.join(","));let i=await a((m)=>fetch(t.toString(),{headers:D(o),signal:m}));if(!i.ok){let m={};try{m=await i.json()}catch{}throw Error(`API error ${i.status}: ${m.error??i.statusText}`)}return(await i.json()).icons}async function mo(o,r,t,i){let f=await a((c)=>fetch(new URL("/v1/icons",o.apiUrl).toString(),{method:"POST",headers:D(o),body:JSON.stringify({collectionSlug:r,variant:i,icons:t}),signal:c}));if(!f.ok){let c={};try{c=await f.json()}catch{}throw Error(`API error ${f.status}: ${c.error??f.statusText}`)}return(await f.json()).results}import J from"fs";import T from"path";import{fileURLToPath as Ao}from"url";import wo from"picocolors";import{optimize as To}from"svgo";function Yo(o){return To(o,{plugins:["removeDoctype","removeXMLProcInst","removeComments","removeMetadata","removeTitle","removeDesc"]}).data.replace(/<svg([^>]*)>/i,(t,i)=>`<svg${i.replace(/\s+(width|height)=['"][^'"]*['"]/gi,"")}>`)}function co(o){let r={},t=/([\w:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g,i;while((i=t.exec(o))!==null){let f=i[1].replace(/^xlink:/,"").replace(/-([a-z])/g,(m,c)=>c.toUpperCase());r[f]=i[2]??i[3]??""}return r}function yo(o){let r=[],t=0;while(t<o.length){let i=o.indexOf("<",t);if(i===-1)break;let f=o[i+1];if(f==="/"||f==="!"||f==="?"){let F=o.indexOf(">",i);t=F===-1?o.length:F+1;continue}let m=i+1,c=!1,y="";while(m<o.length){let F=o[m];if(c){if(F===y)c=!1}else if(F==='"'||F==="'")c=!0,y=F;else if(F===">")break;m++}if(m>=o.length)break;let d=o.slice(i+1,m),$=d.trimEnd().endsWith("/"),B=$?d.slice(0,d.lastIndexOf("/")).trim():d.trim(),w=B.search(/\s/),n=w===-1?B:B.slice(0,w);if(!n){t=m+1;continue}let R=w===-1?"":B.slice(w+1),U=co(R);if(t=m+1,$)r.push([n,U]);else{let F=`<${n}`,_=`</${n}>`,j=1,O=t;while(j>0&&O<o.length){let q=o.indexOf(F,O),u=o.indexOf(_,O);if(u===-1)break;if(q!==-1&&q<u)j++,O=q+F.length;else if(j--,j===0){let G=o.slice(t,u),K=yo(G),I=K.length>0?[n,U,K]:[n,U];r.push(I),t=u+_.length}else O=u+_.length}if(j>0)r.push([n,U])}}return r}function x(o){let r=Yo(o),t=r.match(/<svg([^>]*)>/i),i=co(t?.[1]??"");delete i.xmlns,delete i.xmlnsXlink;let{xmlns:f,xmlnsXlink:m,...c}=i,d=r.match(/<svg[^>]*>([\s\S]*)<\/svg>/i)?.[1]?.trim()??"";return{iconNode:yo(d),svgAttrs:c}}function z(o){return o.replace(/[-_\s]+(.)/g,(r,t)=>t.toUpperCase()).replace(/^(.)/,(r,t)=>t.toUpperCase())}function l(o){if(o.length===0)return"";let r=["import { forwardRef, createElement } from 'react';","","const _r = (n) => n.map(([t, a, c], i) => createElement(t, { key: i, ...a }, ...(c ? _r(c) : [])));",""];for(let{name:t,iconNode:i,svgAttrs:f}of o){let m=z(t),c={viewBox:"0 0 24 24",...f},y=JSON.stringify(i),d=JSON.stringify(c);r.push(`export const ${m} = /*#__PURE__*/forwardRef(({ children, ...props }, ref) =>`,` createElement('svg', { ref, xmlns: 'http://www.w3.org/2000/svg', ...${d}, ...props },`,` ..._r(${y}),`," children"," )",");",`${m}.displayName = '${m}';`,"")}return r.join(`
17
+ `)}function g(o){let r=["import type { ForwardRefExoticComponent, SVGProps, RefAttributes } from 'react';","","type IconComponent = ForwardRefExoticComponent<SVGProps<SVGSVGElement> & RefAttributes<SVGSVGElement>>;",""];for(let t of o)r.push(`export declare const ${z(t)}: IconComponent;`);return r.push(""),r.join(`
18
+ `)}function N(){return T.resolve(T.dirname(Ao(import.meta.url)),"..")}function E(o,r){let t=[];for(let f of r)try{let{iconNode:m,svgAttrs:c}=x(f.svgContent);t.push({name:f.name,iconNode:m,svgAttrs:c})}catch(m){console.warn(wo.yellow(` ⚠ Skipped ${f.name}: ${m.message}`))}if(t.length===0)return 0;let i=N();return J.writeFileSync(T.join(i,`${o}.js`),l(t),"utf-8"),J.writeFileSync(T.join(i,`${o}.d.ts`),g(t.map((f)=>f.name)),"utf-8"),t.length}function b(){let o=T.join(N(),"..","package.json");try{let r=JSON.parse(J.readFileSync(o,"utf-8")),t=r.exports??{};if(t["./*"]?.import?.startsWith("./dist/"))return;r.exports={".":t["."]??{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"},"./*":{import:"./dist/*.js",types:"./dist/*.d.ts"}},J.writeFileSync(o,JSON.stringify(r,null,2)+`
19
+ `,"utf-8")}catch{}}function C(o,r,t){let i=[];for(let c of t)try{let{iconNode:y,svgAttrs:d}=x(c.svgContent);i.push({name:c.name,iconNode:y,svgAttrs:d})}catch(y){console.warn(wo.yellow(` ⚠ Skipped ${c.name}: ${y.message}`))}if(i.length===0)return 0;let f=N(),m=T.join(f,o);if(!J.existsSync(m))J.mkdirSync(m,{recursive:!0});return J.writeFileSync(T.join(m,`${r}.js`),l(i),"utf-8"),J.writeFileSync(T.join(m,`${r}.d.ts`),g(i.map((c)=>c.name)),"utf-8"),i.length}function no(o){let r=N(),t=T.join(r,`${o}.js`),i=T.join(r,`${o}.d.ts`);if(J.existsSync(t))J.unlinkSync(t);if(J.existsSync(i))J.unlinkSync(i)}import $o from"fs";import Qo from"path";var Wo=".iconia-lock.json";function Ro(){return Qo.resolve(process.cwd(),Wo)}function A(){try{let o=$o.readFileSync(Ro(),"utf-8");return JSON.parse(o)}catch{return{version:1,collections:{}}}}function Q(o){$o.writeFileSync(Ro(),JSON.stringify(o,null,2),"utf-8")}function v(o,r,t){return{...o,collections:{...o.collections,[r]:{icons:Object.fromEntries(t.map((i)=>[i.slug,i.fingerprint]))}}}}function Fo(o,r){let{[r]:t,...i}=o.collections;return{...o,collections:i}}var uo=new Zo("pull").description("Fetch icons and regenerate all collection files (replaces existing)").option("-c, --collection <slug>","Pull only this collection").action(async(o)=>{let r=Io("Loading config...").start(),t;try{t=await H()}catch(w){r.fail(Z.red(w.message)),process.exit(1)}let i=o.collection?[o.collection]:t.collections;if(i.length===0){r.warn(Z.yellow("No collections in config. Add one with `iconia add <slug>`."));return}if(o.collection&&!t.collections.includes(o.collection))r.fail(Z.red(`Collection '${o.collection}' is not in your config.`)),process.exit(1);r.text=`Fetching icons for ${i.join(", ")}...`;let f;try{f=await e(t,i)}catch(w){r.fail(Z.red(`Failed to fetch icons: ${w.message}`)),process.exit(1)}if(f.length===0){r.warn(Z.yellow("No icons found."));return}r.text=`Generating ${f.length} icon${f.length!==1?"s":""}...`;let m=new Map;for(let w of f){if(!m.has(w.collectionSlug))m.set(w.collectionSlug,new Map);let n=m.get(w.collectionSlug),R=w.variantSlug||null,U=n.get(R)??[];U.push(w),n.set(R,U)}let c=A(),y=0,d=[],$,B;for(let[w,n]of m){let R=0,U=n.get(null)??[];if(U.length>0){let F=E(w,U);if(R+=F,!$&&U[0])$=U[0].name,B=w}for(let[F,_]of n){if(F===null)continue;let j=C(w,F,_);if(R+=j,!$&&_[0])$=_[0].name,B=`${w}/${F}`}if(R===0)continue;y+=R,d.push(w),c=v(c,w,[...n.values()].flat().map((F)=>({slug:F.slug,fingerprint:F.fingerprint})))}if(Q(c),b(),r.succeed(Z.green(`Generated ${y} icon${y!==1?"s":""} across ${d.length} collection${d.length!==1?"s":""}`)),B&&$){let w=z($);console.log(`
21
20
  Import icons:
22
- ${b.cyan(`import { ${w} } from '@iconia/react/${e}'`)}`)}});import{Command as Ko}from"commander";import F from"picocolors";import zo from"ora";import V from"fs";import so from"path";function Y(){let o=so.resolve(process.cwd(),"iconia.config.ts"),r=so.resolve(process.cwd(),"iconia.config.js");if(V.existsSync(o))return o;if(V.existsSync(r))return r;return null}function H(o){let t=V.readFileSync(o,"utf-8").match(/collections:\s*\[([\s\S]*?)\]/)?.[1];if(!t)return null;let c=[],n=/['"]([^'"]+)['"]/g,a;while((a=n.exec(t))!==null){let f=a[1];if(f)c.push(f)}return c}function T(o,r){let i=V.readFileSync(o,"utf-8"),t=r.length===0?"[]":`[
23
- ${r.map((n)=>` '${n}'`).join(`,
21
+ ${Z.cyan(`import { ${w} } from '@iconia/react/${B}'`)}`)}});import{Command as Mo}from"commander";import Y from"picocolors";import eo from"ora";import X from"fs";import Bo from"path";function P(){let o=Bo.resolve(process.cwd(),"iconia.config.ts"),r=Bo.resolve(process.cwd(),"iconia.config.js");if(X.existsSync(o))return o;if(X.existsSync(r))return r;return null}function V(o){let i=X.readFileSync(o,"utf-8").match(/collections:\s*\[([\s\S]*?)\]/)?.[1];if(!i)return null;let f=[],m=/['"]([^'"]+)['"]/g,c;while((c=m.exec(i))!==null){let y=c[1];if(y)f.push(y)}return f}function p(o,r){let t=X.readFileSync(o,"utf-8"),i=r.length===0?"[]":`[
22
+ ${r.map((m)=>` '${m}'`).join(`,
24
23
  `)},
25
- ]`,c=i.replace(/collections:\s*\[[\s\S]*?\]/,`collections: ${t}`);if(c===i)return!1;return V.writeFileSync(o,c,"utf-8"),!0}var yo=new Ko("add").description("Add a collection to your project (updates config and downloads icons)").argument("<slug>","Collection slug").action(async(o)=>{let r=zo("Loading config...").start(),i;try{i=await j()}catch(f){r.fail(F.red(f.message)),process.exit(1)}if(i.collections.includes(o))r.info(F.yellow(`Collection '${o}' is already in your config. Running pull...`));else{r.text="Verifying collection...";let f;try{f=await x(i)}catch(d){r.fail(F.red(`Failed to fetch collections: ${d.message}`)),process.exit(1)}if(!f.find((d)=>d.slug===o))r.fail(F.red(`Collection '${o}' not found. Available: ${f.map((d)=>d.slug).join(", ")||"none"}`)),process.exit(1);let e=Y();if(!e)r.fail(F.red("Config file not found. Run `npx @iconia/react init` first.")),process.exit(1);let m=[...H(e)??i.collections,o];if(!T(e,m))r.warn(F.yellow(`Could not update config automatically. Add '${o}' to collections in iconia.config.ts manually.`));else r.text=`Added '${o}' to config. Fetching icons...`}r.text=`Fetching icons for '${o}'...`;let t;try{t=await K(i,[o])}catch(f){r.fail(F.red(`Failed to fetch icons: ${f.message}`)),process.exit(1)}if(t.length===0){r.warn(F.yellow(`No icons in collection '${o}'.`));return}r.text=`Generating ${t.length} icon${t.length!==1?"s":""}...`;let c=O(o,t),n=L();n=S(n,o,t.map((f)=>({slug:f.slug,fingerprint:f.fingerprint}))),_(n),r.succeed(F.green(`Added '${o}' — ${c} icon${c!==1?"s":""} generated`));let a=z(t[0]?.name??"Icon");console.log(`
24
+ ]`,f=t.replace(/collections:\s*\[[\s\S]*?\]/,`collections: ${i}`);if(f===t)return!1;return X.writeFileSync(o,f,"utf-8"),!0}var Uo=new Mo("add").description("Add a collection to your project (updates config and downloads icons)").argument("<slug>","Collection slug").action(async(o)=>{let r=eo("Loading config...").start(),t;try{t=await H()}catch(y){r.fail(Y.red(y.message)),process.exit(1)}if(t.collections.includes(o))r.info(Y.yellow(`Collection '${o}' is already in your config. Running pull...`));else{r.text="Verifying collection...";let y;try{y=await fo(t)}catch(R){r.fail(Y.red(`Failed to fetch collections: ${R.message}`)),process.exit(1)}if(!y.find((R)=>R.slug===o))r.fail(Y.red(`Collection '${o}' not found. Available: ${y.map((R)=>R.slug).join(", ")||"none"}`)),process.exit(1);let $=P();if(!$)r.fail(Y.red("Config file not found. Run `npx @iconia/react init` first.")),process.exit(1);let w=[...V($)??t.collections,o];if(!p($,w))r.warn(Y.yellow(`Could not update config automatically. Add '${o}' to collections in iconia.config.ts manually.`));else r.text=`Added '${o}' to config. Fetching icons...`}r.text=`Fetching icons for '${o}'...`;let i;try{i=await e(t,[o])}catch(y){r.fail(Y.red(`Failed to fetch icons: ${y.message}`)),process.exit(1)}if(i.length===0){r.warn(Y.yellow(`No icons in collection '${o}'.`));return}r.text=`Generating ${i.length} icon${i.length!==1?"s":""}...`;let f=E(o,i),m=A();m=v(m,o,i.map((y)=>({slug:y.slug,fingerprint:y.fingerprint}))),Q(m),r.succeed(Y.green(`Added '${o}' — ${f} icon${f!==1?"s":""} generated`));let c=z(i[0]?.name??"Icon");console.log(`
26
25
  Import icons:
27
- ${F.cyan(`import { ${a} } from '@iconia/react/${o}'`)}`)});import{Command as Oo}from"commander";import Q from"picocolors";var wo=new Oo("remove").description("Remove a collection from your project (updates config and deletes generated files)").argument("<slug>","Collection slug").action(async(o)=>{let r;try{r=await j()}catch(t){console.error(Q.red(t.message)),process.exit(1)}if(!r.collections.includes(o))console.warn(Q.yellow(`Collection '${o}' is not in your config.`));else{let t=Y();if(t){let n=(H(t)??r.collections).filter((f)=>f!==o);if(!T(t,n))console.warn(Q.yellow(`Could not update config automatically. Remove '${o}' from collections in iconia.config.ts manually.`))}}co(o);let i=L();i=eo(i,o),_(i),console.log(Q.green(`✓ Removed collection '${o}'`))});import{Command as So}from"commander";import p from"picocolors";import Ao from"ora";var $o=new So("sync").description("Sync icons: add new, update changed, remove deleted — without full re-fetch").option("-c, --collection <slug>","Sync only this collection").action(async(o)=>{let r=Ao("Loading config...").start(),i;try{i=await j()}catch(m){r.fail(p.red(m.message)),process.exit(1)}let t=o.collection?[o.collection]:i.collections;if(t.length===0){r.warn(p.yellow("No collections in config. Add one with `iconia add <slug>`."));return}if(o.collection&&!i.collections.includes(o.collection))r.fail(p.red(`Collection '${o.collection}' is not in your config.`)),process.exit(1);r.text=`Fetching icons for ${t.join(", ")}...`;let c;try{c=await K(i,t)}catch(m){r.fail(p.red(`Failed to fetch icons: ${m.message}`)),process.exit(1)}let n=new Map;for(let m of c){let s=n.get(m.collectionSlug)??[];s.push(m),n.set(m.collectionSlug,s)}let a=L(),f=0,y=0,e=0,w=0;for(let m of t){let s=n.get(m)??[],d=a.collections[m]?.icons??{},B=new Map(s.map(($)=>[$.slug,$])),u=new Set(Object.keys(d)),C=new Set(B.keys()),I=[...C].filter(($)=>!u.has($)),R=[...u].filter(($)=>!C.has($)),h=[...C].filter(($)=>u.has($)&&d[$]!==B.get($).fingerprint);if(I.length===0&&R.length===0&&h.length===0){console.log(p.dim(` ${m}: up to date`));continue}if(I.length>0)console.log(p.green(` ${m}: +${I.length} added`)+p.dim(` (${I.slice(0,5).join(", ")}${I.length>5?"…":""})`));if(h.length>0)console.log(p.blue(` ${m}: ~${h.length} updated`)+p.dim(` (${h.slice(0,5).join(", ")}${h.length>5?"…":""})`));if(R.length>0)console.log(p.red(` ${m}: -${R.length} removed`)+p.dim(` (${R.slice(0,5).join(", ")}${R.length>5?"…":""})`));if(s.length>0)O(m,s);a=S(a,m,s.map(($)=>({slug:$.slug,fingerprint:$.fingerprint}))),f+=I.length,y+=h.length,e+=R.length,w++}if(_(a),w>0)q();if(w===0)r.succeed(p.green("Everything is up to date."));else r.succeed(p.green(`Sync complete: ${f} added, ${y} updated, ${e} removed across ${w} collection${w!==1?"s":""}`))});import{Command as Po}from"commander";import M from"fs";import J from"path";import A from"picocolors";import Vo from"ora";function Jo(o){return J.basename(o,J.extname(o)).toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")}function qo(o){return o.split("-").map((r)=>r.charAt(0).toUpperCase()+r.slice(1)).join(" ")}function Yo(o){let r=M.statSync(o);if(r.isFile()){if(!o.endsWith(".svg"))throw Error(`File '${o}' is not an SVG.`);return[o]}if(r.isDirectory())return M.readdirSync(o).filter((i)=>i.endsWith(".svg")).map((i)=>J.join(o,i));throw Error(`'${o}' is not a file or directory.`)}var uo=new Po("upload").description("Upload SVG file(s) to an Iconia collection").argument("<path>","SVG file or directory of SVGs").requiredOption("-c, --collection <slug>","Target collection slug").option("--tags <tags>","Comma-separated tags to apply to all uploaded icons").action(async(o,r)=>{let i=Vo({text:"Loading config...",discardStdin:!1}).start(),t;try{t=await j()}catch(e){i.fail(A.red(e.message)),process.exit(1)}let c;try{c=Yo(J.resolve(process.cwd(),o))}catch(e){i.fail(A.red(e.message)),process.exit(1)}if(c.length===0){i.warn(A.yellow("No SVG files found."));return}let n=r.tags?r.tags.split(",").map((e)=>e.trim()).filter(Boolean):[];i.text=`Uploading ${c.length} file${c.length!==1?"s":""} to '${r.collection}'...`;let a=0,f=0,y=[];for(let e=0;e<c.length;e+=t.uploadBatchSize){if(E.signal.aborted)break;let w=c.slice(e,e+t.uploadBatchSize),m=[];for(let s of w){let d=Jo(s);if(!d){y.push(`${J.basename(s)}: could not derive a valid slug`),f++;continue}m.push({slug:d,name:qo(d),svgContent:M.readFileSync(s,"utf-8"),tags:n})}if(m.length===0)continue;try{let s=await g(t,r.collection,m);for(let d of s)if(d.status==="uploaded"||d.status==="duplicate")a++;else y.push(`${d.slug}: ${d.error??"upload failed"}`),f++}catch(s){for(let d of m)y.push(`${d.slug}: ${s.message}`),f++}i.text=`Uploading... (${a+f}/${c.length})`}if(f===0)i.succeed(A.green(`Uploaded ${a} icon${a!==1?"s":""} to '${r.collection}'`));else{i.warn(A.yellow(`Uploaded ${a}, failed ${f}`));for(let e of y)console.log(A.dim(` ✗ ${e}`))}});var To=new Ho("iconia").version("0.1.0").description("CLI for fetching and generating React icon components from Iconia").addCommand(X).addCommand(yo).addCommand(wo).addCommand(mo).addCommand($o).addCommand(uo);To.parse(process.argv);
26
+ ${Y.cyan(`import { ${c} } from '@iconia/react/${o}'`)}`)});import{Command as zo}from"commander";import s from"picocolors";var jo=new zo("remove").description("Remove a collection from your project (updates config and deletes generated files)").argument("<slug>","Collection slug").action(async(o)=>{let r;try{r=await H()}catch(i){console.error(s.red(i.message)),process.exit(1)}if(!r.collections.includes(o))console.warn(s.yellow(`Collection '${o}' is not in your config.`));else{let i=P();if(i){let m=(V(i)??r.collections).filter((y)=>y!==o);if(!p(i,m))console.warn(s.yellow(`Could not update config automatically. Remove '${o}' from collections in iconia.config.ts manually.`))}}no(o);let t=A();t=Fo(t,o),Q(t),console.log(s.green(`✓ Removed collection '${o}'`))});import{Command as Eo}from"commander";import L from"picocolors";import vo from"ora";var Lo=new Eo("sync").description("Sync icons: add new, update changed, remove deleted — without full re-fetch").option("-c, --collection <slug>","Sync only this collection").action(async(o)=>{let r=vo("Loading config...").start(),t;try{t=await H()}catch(w){r.fail(L.red(w.message)),process.exit(1)}let i=o.collection?[o.collection]:t.collections;if(i.length===0){r.warn(L.yellow("No collections in config. Add one with `iconia add <slug>`."));return}if(o.collection&&!t.collections.includes(o.collection))r.fail(L.red(`Collection '${o.collection}' is not in your config.`)),process.exit(1);r.text=`Fetching icons for ${i.join(", ")}...`;let f;try{f=await e(t,i)}catch(w){r.fail(L.red(`Failed to fetch icons: ${w.message}`)),process.exit(1)}let m=new Map;for(let w of f){let n=m.get(w.collectionSlug)??[];n.push(w),m.set(w.collectionSlug,n)}let c=A(),y=0,d=0,$=0,B=0;for(let w of i){let n=m.get(w)??[],R=c.collections[w]?.icons??{},U=new Map(n.map((u)=>[u.slug,u])),F=new Set(Object.keys(R)),_=new Set(U.keys()),j=[..._].filter((u)=>!F.has(u)),O=[...F].filter((u)=>!_.has(u)),q=[..._].filter((u)=>F.has(u)&&R[u]!==U.get(u).fingerprint);if(j.length===0&&O.length===0&&q.length===0){console.log(L.dim(` ${w}: up to date`));continue}if(j.length>0)console.log(L.green(` ${w}: +${j.length} added`)+L.dim(` (${j.slice(0,5).join(", ")}${j.length>5?"…":""})`));if(q.length>0)console.log(L.blue(` ${w}: ~${q.length} updated`)+L.dim(` (${q.slice(0,5).join(", ")}${q.length>5?"…":""})`));if(O.length>0)console.log(L.red(` ${w}: -${O.length} removed`)+L.dim(` (${O.slice(0,5).join(", ")}${O.length>5?"…":""})`));if(n.length>0){let u=n.filter((K)=>!K.variantSlug);if(u.length>0)E(w,u);let G=new Map;for(let K of n){if(!K.variantSlug)continue;let I=G.get(K.variantSlug)??[];I.push(K),G.set(K.variantSlug,I)}for(let[K,I]of G)C(w,K,I)}c=v(c,w,n.map((u)=>({slug:u.slug,fingerprint:u.fingerprint}))),y+=j.length,d+=q.length,$+=O.length,B++}if(Q(c),B>0)b();if(B===0)r.succeed(L.green("Everything is up to date."));else r.succeed(L.green(`Sync complete: ${y} added, ${d} updated, ${$} removed across ${B} collection${B!==1?"s":""}`))});import{Command as ho}from"commander";import oo from"fs";import k from"path";import h from"picocolors";import Go from"ora";function So(o){return k.basename(o,k.extname(o)).toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")}function Xo(o){return o.split("-").map((r)=>r.charAt(0).toUpperCase()+r.slice(1)).join(" ")}function ko(o){let r=oo.statSync(o);if(r.isFile()){if(!o.endsWith(".svg"))throw Error(`File '${o}' is not an SVG.`);return[o]}if(r.isDirectory())return oo.readdirSync(o).filter((t)=>t.endsWith(".svg")).map((t)=>k.join(o,t));throw Error(`'${o}' is not a file or directory.`)}var Oo=new ho("upload").description("Upload SVG file(s) to an Iconia collection").argument("<path>","SVG file or directory of SVGs").requiredOption("-c, --collection <slug>","Target collection slug").option("--variant <slug>","Target variant within the collection").option("--tags <tags>","Comma-separated tags to apply to all uploaded icons").action(async(o,r)=>{let t=Go({text:"Loading config...",discardStdin:!1}).start(),i;try{i=await H()}catch($){t.fail(h.red($.message)),process.exit(1)}let f;try{f=ko(k.resolve(process.cwd(),o))}catch($){t.fail(h.red($.message)),process.exit(1)}if(f.length===0){t.warn(h.yellow("No SVG files found."));return}let m=r.tags?r.tags.split(",").map(($)=>$.trim()).filter(Boolean):[];t.text=`Uploading ${f.length} file${f.length!==1?"s":""} to '${r.collection}'...`;let c=0,y=0,d=[];for(let $=0;$<f.length;$+=i.uploadBatchSize){if(W.signal.aborted)break;let B=f.slice($,$+i.uploadBatchSize),w=[];for(let n of B){let R=So(n);if(!R){d.push(`${k.basename(n)}: could not derive a valid slug`),y++;continue}w.push({slug:R,name:Xo(R),svgContent:oo.readFileSync(n,"utf-8"),tags:m})}if(w.length===0)continue;try{let n=await mo(i,r.collection,w,r.variant||void 0);for(let R of n)if(R.status==="uploaded"||R.status==="duplicate")c++;else d.push(`${R.slug}: ${R.error??"upload failed"}`),y++}catch(n){for(let R of w)d.push(`${R.slug}: ${n.message}`),y++}t.text=`Uploading... (${c+y}/${f.length})`}if(y===0)t.succeed(h.green(`Uploaded ${c} icon${c!==1?"s":""} to '${r.collection}'`));else{t.warn(h.yellow(`Uploaded ${c}, failed ${y}`));for(let $ of d)console.log(h.dim(` ✗ ${$}`))}});var bo=new No("iconia").version("0.3.0").description("CLI for fetching and generating React icon components from Iconia").addCommand(io).addCommand(Uo).addCommand(jo).addCommand(uo).addCommand(Lo).addCommand(Oo);bo.parse(process.argv);
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
3
  import { ForwardRefExoticComponent, RefAttributes, SVGProps } from 'react';
4
- import { z } from 'zod';
5
4
 
6
5
  export type IconNodeElement = [
7
6
  tagName: string,
@@ -9,13 +8,10 @@ export type IconNodeElement = [
9
8
  children?: IconNodeElement[]
10
9
  ];
11
10
  export type IconNode = IconNodeElement[];
12
- declare const configSchema: z.ZodObject<{
13
- apiKey: z.ZodString;
14
- collections: z.ZodDefault<z.ZodArray<z.ZodString>>;
15
- uploadBatchSize: z.ZodDefault<z.ZodNumber>;
16
- }, z.core.$strip>;
17
- export type IconiaConfig = z.infer<typeof configSchema> & {
18
- apiUrl?: string;
11
+ export type IconiaConfig = {
12
+ apiKey?: string;
13
+ collections?: string[];
14
+ uploadBatchSize?: number;
19
15
  };
20
16
  export type IconComponent = ForwardRefExoticComponent<SVGProps<SVGSVGElement> & RefAttributes<SVGSVGElement>>;
21
17
  export declare function createIconiaIcon(displayName: string, iconNode: IconNode, svgAttrs?: Record<string, string>): IconComponent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iconia/react",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-dev.3fa17d4",
4
4
  "description": "Fetch and generate React icon components from your Iconia collections",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",