yummaui 0.1.0 → 0.2.1

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 (3) hide show
  1. package/README.md +21 -48
  2. package/dist/cli.mjs +11 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,10 +4,6 @@
4
4
 
5
5
  Add [Yumma UI](https://yummacss.com/ui) components to your project, from the terminal.
6
6
 
7
- Components are **copied into your repository**, not installed as a dependency. There is no version to track, no breaking change to absorb, and no wrapper API between you and the markup. Once a component is in your project it is yours to edit.
8
-
9
- [Components](https://yummacss.com/ui/components/button) • [Yumma CSS](https://yummacss.com) • [Playground](https://play.yummacss.com) • [X](https://x.com/yummacss)
10
-
11
7
  ## Usage
12
8
 
13
9
  There is nothing to install.
@@ -17,11 +13,9 @@ pnpm dlx yummaui init
17
13
  ```
18
14
 
19
15
  ```bash
20
- pnpm dlx yummaui add button
16
+ pnpm dlx yummaui add <component...>
21
17
  ```
22
18
 
23
- That writes `components/ui/button.tsx` and offers to install anything the component needs that you do not already have.
24
-
25
19
  ## Commands
26
20
 
27
21
  ### `init`
@@ -47,19 +41,11 @@ pnpm dlx yummaui add dialog-sign-in
47
41
  pnpm dlx yummaui add --all
48
42
  ```
49
43
 
50
- Components and blocks share one namespace. A component lands under its own name, so `add button` gives you `button.tsx`; a block keeps its whole id, so `add dialog-sign-in` gives you `dialog-sign-in.tsx` and the file still says what it is.
51
-
52
- `--all` copies every component in one go. It is a flag rather than a name, because `add all` would read as though the registry contained a component called "all". Components only: a block is a specific composition, and pulls the components it is built from anyway, so name a block when you want one.
53
-
54
- There is no way to ask for an example, deliberately. The difference between `autocomplete` and its large demo is `size="lg"` — a prop you pass, not a second file to own and keep in sync.
55
-
56
- | Option | |
57
- | --- | --- |
58
- | `-a, --all` | Add every component |
59
- | `--overwrite` | Replace files that already exist |
60
- | `-y, --yes` | Skip prompts and take the defaults |
61
-
62
- Existing files are never replaced without asking, because these files are meant to be edited after you copy them.
44
+ | Option | |
45
+ |---------------|------------------------------------|
46
+ | `-a, --all` | Add every component |
47
+ | `--overwrite` | Replace files that already exist |
48
+ | `-y, --yes` | Skip prompts and take the defaults |
63
49
 
64
50
  ### `list [component]`
65
51
 
@@ -68,19 +54,25 @@ pnpm dlx yummaui list
68
54
  pnpm dlx yummaui list button
69
55
  ```
70
56
 
71
- ## Dependencies
57
+ ### `prune`
72
58
 
73
- A component is a single file that imports only from npm, never from a shared helper. A block is built from components and says so: `add dialog-sign-in` writes the block plus the checkbox, dialog and field it composes, whose `./` imports resolve beside it.
59
+ Finds component files nothing in your project reaches, and only ever considers
60
+ files `add` wrote — your own components in the same folder are left alone.
74
61
 
75
- What a component needs varies. `button` needs only [Base UI](https://base-ui.com); `accordion` also needs `motion` and `iconoir-react`. `add` reads the requirements per component, compares them against your `package.json`, and offers to install only what is missing. You will not end up with an animation library because you wanted a button.
76
-
77
- Installs run through whichever package manager your lockfile indicates.
62
+ ```bash
63
+ pnpm dlx yummaui prune # list them
64
+ pnpm dlx yummaui prune --write # delete them, after confirming
65
+ ```
78
66
 
79
- ## Requirements
67
+ | Option | |
68
+ |-------------|-------------------------------------|
69
+ | `--write` | Delete, instead of only listing |
70
+ | `-y, --yes` | Skip the confirmation |
80
71
 
81
- - Node 18 or newer
82
- - React 19
83
- - [Yumma CSS](https://yummacss.com) configured in your project, since the components are styled with its utilities
72
+ A block imports the component it is built on, so "is anything importing this
73
+ file" would keep a whole unused chain alive. `prune` asks whether a file is
74
+ reachable from outside `componentsDir` instead: adding `dialog-sign-in` and
75
+ never using it makes `dialog`, `checkbox` and `field` unused too.
84
76
 
85
77
  ## Registry
86
78
 
@@ -91,25 +83,6 @@ https://yummacss.com/ui/r/index.json every component and block
91
83
  https://yummacss.com/ui/r/<id>.json one component's source and dependencies
92
84
  ```
93
85
 
94
- Because the registry is fetched rather than bundled, new components appear without a CLI release. Point `registry` in `yummaui.json` somewhere else to test against a local docs server.
95
-
96
- ## Contributing
97
-
98
- ```bash
99
- # install dependencies
100
- pnpm i
101
- # build the CLI
102
- pnpm build
103
- # rebuild on change
104
- pnpm dev
105
- ```
106
-
107
- Then run it against a local checkout:
108
-
109
- ```bash
110
- node dist/cli.mjs list
111
- ```
112
-
113
86
  ## License
114
87
 
115
88
  MIT
package/dist/cli.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import e from"picocolors";import{spawnSync as t}from"node:child_process";import{existsSync as n,mkdirSync as r,readFileSync as i,writeFileSync as a}from"node:fs";import{dirname as o,join as s,relative as c,resolve as l}from"node:path";import*as u from"@clack/prompts";var d=`0.1.0`;const f=`https://yummacss.com/ui/r`;var p=class extends Error{};async function m(e){let t;try{t=await fetch(e,{headers:{accept:`application/json`}})}catch(t){throw new p(`Could not reach the registry at ${e}. Are you online?`,{cause:t})}if(t.status===404)throw new p(`Not found: ${e}`);if(!t.ok)throw new p(`Registry returned ${t.status} for ${e}`);try{return await t.json()}catch(t){throw new p(`Registry sent invalid JSON from ${e}`,{cause:t})}}function h(e){return m(`${e}/index.json`)}function g(e,t){return m(`${e}/${t}.json`)}const _=`yummaui.json`,v={"pnpm-lock.yaml":`pnpm`,"bun.lockb":`bun`,"bun.lock":`bun`,"yarn.lock":`yarn`,"package-lock.json":`npm`};function y(e=process.cwd()){let t=l(e);for(;;){if(n(s(t,`package.json`)))return t;let e=o(t);if(e===t)return null;t=e}}function b(e){try{return JSON.parse(i(s(e,`package.json`),`utf8`))}catch{return{}}}function x(e){for(let[t,r]of Object.entries(v))if(n(s(e,t)))return r;let t=b(e).packageManager;if(typeof t==`string`){let e=t.split(`@`)[0];if(e===`pnpm`||e===`npm`||e===`yarn`||e===`bun`)return e}return`npm`}function S(e){let t=b(e),r={...t.dependencies,...t.devDependencies};return r.next?[`app`,`src/app`].some(t=>n(s(e,t)))?`Next.js (App Router)`:`Next.js (Pages Router)`:r.astro?`Astro`:r[`react-router`]||r[`@remix-run/react`]?`React Router`:r.vite?`Vite`:null}function C(e){for(let t of[`tsconfig.json`,`jsconfig.json`]){let r=s(e,t);if(n(r))try{let e=i(r,`utf8`).replace(/\/\*[\s\S]*?\*\//g,``).replace(/(^|\s)\/\/.*$/gm,`$1`);if(JSON.parse(e)?.compilerOptions?.paths?.[`@/*`])return`@`}catch{}}return null}function w(e){return s(e,_)}function T(e){let t=w(e);if(!n(t))return null;try{let e=JSON.parse(i(t,`utf8`));return{componentsDir:e.componentsDir??`components/ui`,alias:e.alias??null,registry:e.registry??f}}catch{return null}}function E(e,t){a(w(e),`${JSON.stringify(t,null,` `)}\n`)}function D(e,t){let n=b(e),r=new Set([...Object.keys(n.dependencies??{}),...Object.keys(n.devDependencies??{}),...Object.keys(n.peerDependencies??{})]);return t.filter(e=>!r.has(e.name))}function O(e,t){return{command:e,args:e===`npm`?[`install`,...t]:[`add`,...t]}}function k(e=y()){switch(e?x(e):`npm`){case`pnpm`:return`pnpm dlx yummaui`;case`yarn`:return`yarn dlx yummaui`;case`bun`:return`bunx yummaui`;default:return`npx yummaui`}}function A(e){let t=[],n={all:!1,overwrite:!1,yes:!1};for(let r=0;r<e.length;r++){let i=e[r];i===`--all`||i===`-a`?n.all=!0:i===`--overwrite`?n.overwrite=!0:i===`--yes`||i===`-y`?n.yes=!0:i.startsWith(`-`)||t.push(i)}return{names:t,options:n}}function j(e,t,n){return n===`base`?`${t}.tsx`:`${e}.tsx`}function M(e,t,n={}){let r=[];n.all&&r.push(...e.components.map(e=>e.base));for(let n of t){let t=e.components.find(e=>e.component===n);if(t){r.push(t.base);continue}let i=e.blocks.find(e=>e.id===n);if(i){r.push(i.id);continue}return{unknown:n}}return{ids:[...new Set(r)]}}async function N(i){let{names:l,options:d}=A(i),f=y();if(!f)return u.log.error(`No package.json found. Run this inside a project.`),1;let m=f,v=T(m);if(!v)return u.log.error(`No ${_} found. Run ${e.cyan(`${k(m)} init`)} first.`),1;let{registry:b,componentsDir:S}=v;if(l.length===0&&!d.all)return u.log.error(`Nothing to add. Try: ${k(m)} add button`),1;u.intro(e.bgCyan(e.black(` Yumma UI `)));let C,w=u.spinner();w.start(`Fetching registry`);try{C=await h(v.registry)}catch(e){return w.stop(`Registry unavailable`,1),u.log.error(e instanceof p?e.message:String(e)),1}w.stop(`${C.components.length} components, ${C.blocks.length} blocks available`);let E=M(C,l,{all:d.all});if(`unknown`in E)return u.log.error(`Unknown component or block ${e.bold(E.unknown)}.`),E.unknown===`all`?u.log.info(`Every component is ${e.cyan(`--all`)}.`):F(C,E.unknown),1;let N=E.ids,P=[],I=new Map,L=new Set;async function R(e,t){if(L.has(e))return!0;L.add(e);let i;try{i=await g(b,e)}catch(e){return u.log.error(e instanceof p?e.message:String(e)),!1}for(let e of i.registryDependencies)if(!await R(e,!1))return!1;let l=s(m,S,j(i.id,i.component,i.variant)),f=c(m,l).replace(/\\/g,`/`);if(n(l)&&!d.overwrite){if(!t||d.yes)return u.log.warn(`Skipped ${f} (already exists)`),!0;let e=await u.confirm({message:`${f} already exists. Overwrite?`,initialValue:!1});if(u.isCancel(e))return u.cancel(`Cancelled.`),!1;if(!e)return u.log.warn(`Skipped ${f}`),!0}let h=i.files[0];if(!h)return u.log.error(`${e} has no files.`),!1;r(o(l),{recursive:!0}),a(l,h.content),P.push(f);for(let e of i.dependencies)I.set(e.name,e.version);return!0}for(let e of N)if(!await R(e,!0))return 1;if(P.length===0)return u.outro(`Nothing written.`),0;u.log.success(`Added\n${P.map(e=>` ${e}`).join(`
3
- `)}`);let z=D(m,[...I].map(([e,t])=>({name:e,version:t}))),B=[...I.keys()].filter(e=>!z.some(t=>t.name===e));if(I.size>0){let t=[...B.map(t=>` ${e.green(`✔`)} ${t} ${e.dim(`already installed`)}`),...z.map(t=>` ${e.yellow(`+`)} ${t.name}@${t.version}`)];u.log.info(`Dependencies\n${t.join(`
4
- `)}`)}if(z.length>0){let n=x(m),r=z.map(e=>`${e.name}@${e.version}`),i=d.yes;if(!i){let t=await u.confirm({message:`Install ${z.length} missing package${z.length>1?`s`:``} with ${e.bold(n)}?`});if(u.isCancel(t))return u.cancel(`Cancelled.`),1;i=t}if(i){let{command:e,args:i}=O(n,r),a=t(e,i,{cwd:m,stdio:`inherit`,shell:process.platform===`win32`});if(a.status!==0)return u.log.error(`${e} exited with ${a.status??`an error`}.`),1}else u.log.info(`Install manually:\n ${x(m)} add ${r.join(` `)}`)}return u.outro(`Done.`),0}function P(e,t){let n=Array.from({length:t.length+1},(e,t)=>t);for(let r=1;r<=e.length;r++){let i=[r];for(let a=1;a<=t.length;a++){let o=e[r-1]===t[a-1]?0:1;i[a]=Math.min(i[a-1]+1,n[a]+1,n[a-1]+o)}n=i}return n[t.length]}function F(e,t){let n=[...e.components.map(e=>e.component),...e.blocks.map(e=>e.id)].map(e=>({name:e,score:e.includes(t)||t.includes(e)?0:P(e,t)})).filter(e=>e.score<=2).sort((e,t)=>e.score-t.score).slice(0,5).map(e=>e.name);n.length?u.log.info(`Did you mean: ${n.join(`, `)}`):u.log.info(`Run ${k()} list to see everything.`)}async function I(t){let n=t.includes(`--force`),r=y();if(!r)return u.log.error(`No package.json found. Run this inside a project.`),1;if(u.intro(e.bgCyan(e.black(` Yumma UI `))),T(r)&&!n)return u.log.warn(`${_} already exists. Use --force to overwrite.`),u.outro(`Nothing to do.`),0;let i=S(r),a=x(r),o=C(r);u.log.step([i?`Detected ${e.bold(i)}`:`No framework detected`,`Detected ${e.bold(a)}`].join(`
5
- `));let s=await u.text({message:`Where should components go?`,placeholder:`components/ui`,defaultValue:`components/ui`});if(u.isCancel(s))return L();let c=!1;if(o){let t=await u.confirm({message:`Use the ${e.bold(`"${o}/"`)} import alias?`});if(u.isCancel(t))return L();c=t}return E(r,{componentsDir:String(s),alias:c&&o?`${o}/${s}`:null,registry:f}),u.log.success(`Wrote ${e.bold(w(r))}`),u.outro(`Next: ${e.cyan(`${k(r)} add button`)}`),0}function L(){return u.cancel(`Cancelled.`),1}async function R(t){let n=t.find(e=>!e.startsWith(`-`)),r=y(),i=r&&T(r)?.registry||f,a;try{a=await h(i)}catch(t){return console.error(e.red(t instanceof p?t.message:String(t))),1}if(n){let t=a.components.find(e=>e.component===n);if(!t)return console.error(e.red(`Unknown component ${n}.`)),1;let i=a.blocks.filter(e=>e.component===n);if(console.log(`\n${e.bold(t.title)} ${e.dim(t.component)}\n`),console.log(` ${e.dim(`${k(r)} add ${t.component}`)}\n`),console.log(` ${e.dim(`Every variation is a prop. Read its API at`)} ${e.cyan(`https://yummacss.com/ui/components/${t.component}`)}\n`),i.length){console.log(` ${e.bold(`${i.length} block${i.length>1?`s`:``} built on it`)}`);for(let t of i)console.log(` ${t.id.padEnd(26)}${e.dim(`add ${t.id}`)}`);console.log()}return 0}console.log(`\n${e.bold(`${a.components.length} components`)}\n`);let o=a.components.map(e=>e.component);for(let e=0;e<o.length;e+=2)console.log(` ${(o[e]??``).padEnd(24)}${o[e+1]??``}`);console.log(`\n${e.bold(`${a.blocks.length} blocks`)}\n`);let s=a.blocks.map(e=>e.id);for(let e=0;e<s.length;e+=2)console.log(` ${(s[e]??``).padEnd(30)}${s[e+1]??``}`);return console.log(`\n ${e.dim(`${k(r)} list <component>`)} to see its blocks\n`),0}const z=`
6
- ${e.bold(`yummaui`)} ${e.dim(`v${d}`)}
2
+ import e from"picocolors";import{spawnSync as t}from"node:child_process";import{existsSync as n,mkdirSync as r,readFileSync as i,readdirSync as a,rmSync as o,writeFileSync as s}from"node:fs";import{dirname as c,join as l,relative as u,resolve as d,sep as f}from"node:path";import*as p from"@clack/prompts";var m=`0.2.1`;const h=`https://yummacss.com/ui/r`;var g=class extends Error{};async function _(e){let t;try{t=await fetch(e,{headers:{accept:`application/json`}})}catch(t){throw new g(`Could not reach the registry at ${e}. Are you online?`,{cause:t})}if(t.status===404)throw new g(`Not found: ${e}`);if(!t.ok)throw new g(`Registry returned ${t.status} for ${e}`);try{return await t.json()}catch(t){throw new g(`Registry sent invalid JSON from ${e}`,{cause:t})}}function v(e){return _(`${e}/index.json`)}function ee(e,t){return _(`${e}/${t}.json`)}const y=`yummaui.json`,b={"pnpm-lock.yaml":`pnpm`,"bun.lockb":`bun`,"bun.lock":`bun`,"yarn.lock":`yarn`,"package-lock.json":`npm`};function x(e=process.cwd()){let t=d(e);for(;;){if(n(l(t,`package.json`)))return t;let e=c(t);if(e===t)return null;t=e}}function S(e){try{return JSON.parse(i(l(e,`package.json`),`utf8`))}catch{return{}}}function C(e){for(let[t,r]of Object.entries(b))if(n(l(e,t)))return r;let t=S(e).packageManager;if(typeof t==`string`){let e=t.split(`@`)[0];if(e===`pnpm`||e===`npm`||e===`yarn`||e===`bun`)return e}return`npm`}function w(e){let t=S(e),r={...t.dependencies,...t.devDependencies};return r.next?[`app`,`src/app`].some(t=>n(l(e,t)))?`Next.js (App Router)`:`Next.js (Pages Router)`:r.astro?`Astro`:r[`react-router`]||r[`@remix-run/react`]?`React Router`:r.vite?`Vite`:null}function T(e){for(let t of[`tsconfig.json`,`jsconfig.json`]){let r=l(e,t);if(n(r))try{let e=i(r,`utf8`).replace(/\/\*[\s\S]*?\*\//g,``).replace(/(^|\s)\/\/.*$/gm,`$1`);if(JSON.parse(e)?.compilerOptions?.paths?.[`@/*`])return`@`}catch{}}return null}function E(e){return l(e,y)}function D(e){let t=E(e);if(!n(t))return null;try{let e=JSON.parse(i(t,`utf8`));return{componentsDir:e.componentsDir??`components/ui`,alias:e.alias??null,registry:e.registry??h}}catch{return null}}function O(e,t){s(E(e),`${JSON.stringify(t,null,` `)}\n`)}function k(e,t){let n=S(e),r=new Set([...Object.keys(n.dependencies??{}),...Object.keys(n.devDependencies??{}),...Object.keys(n.peerDependencies??{})]);return t.filter(e=>!r.has(e.name))}function A(e,t){return{command:e,args:e===`npm`?[`install`,...t]:[`add`,...t]}}function j(e=x()){switch(e?C(e):`npm`){case`pnpm`:return`pnpm dlx yummaui`;case`yarn`:return`yarn dlx yummaui`;case`bun`:return`bunx yummaui`;default:return`npx yummaui`}}function M(e){let t=[],n={all:!1,overwrite:!1,yes:!1};for(let r=0;r<e.length;r++){let i=e[r];i===`--all`||i===`-a`?n.all=!0:i===`--overwrite`?n.overwrite=!0:i===`--yes`||i===`-y`?n.yes=!0:i.startsWith(`-`)||t.push(i)}return{names:t,options:n}}function N(e,t,n){return n===`base`?`${t}.tsx`:`${e}.tsx`}function P(e,t,n={}){let r=[];n.all&&r.push(...e.components.map(e=>e.base));for(let n of t){let t=e.components.find(e=>e.component===n);if(t){r.push(t.base);continue}let i=e.blocks.find(e=>e.id===n);if(i){r.push(i.id);continue}return{unknown:n}}return{ids:[...new Set(r)]}}async function F(i){let{names:a,options:o}=M(i),d=x();if(!d)return p.log.error(`No package.json found. Run this inside a project.`),1;let f=d,m=D(f);if(!m)return p.log.error(`No ${y} found. Run ${e.cyan(`${j(f)} init`)} first.`),1;let{registry:h,componentsDir:_}=m;if(a.length===0&&!o.all)return p.log.error(`Nothing to add. Try: ${j(f)} add button`),1;p.intro(e.bgCyan(e.black(` Yumma UI `)));let b,S=p.spinner();S.start(`Fetching registry`);try{b=await v(m.registry)}catch(e){return S.stop(`Registry unavailable`,1),p.log.error(e instanceof g?e.message:String(e)),1}S.stop(`${b.components.length} components, ${b.blocks.length} blocks available`);let w=P(b,a,{all:o.all});if(`unknown`in w)return p.log.error(`Unknown component or block ${e.bold(w.unknown)}.`),w.unknown===`all`?p.log.info(`Every component is ${e.cyan(`--all`)}.`):L(b,w.unknown),1;let T=w.ids,E=[],O=new Map,F=new Set;async function I(e,t){if(F.has(e))return!0;F.add(e);let i;try{i=await ee(h,e)}catch(e){return p.log.error(e instanceof g?e.message:String(e)),!1}for(let e of i.registryDependencies)if(!await I(e,!1))return!1;let a=l(f,_,N(i.id,i.component,i.variant)),d=u(f,a).replace(/\\/g,`/`);if(n(a)&&!o.overwrite){if(!t||o.yes)return p.log.warn(`Skipped ${d} (already exists)`),!0;let e=await p.confirm({message:`${d} already exists. Overwrite?`,initialValue:!1});if(p.isCancel(e))return p.cancel(`Cancelled.`),!1;if(!e)return p.log.warn(`Skipped ${d}`),!0}let m=i.files[0];if(!m)return p.log.error(`${e} has no files.`),!1;r(c(a),{recursive:!0}),s(a,m.content),E.push(d);for(let e of i.dependencies)O.set(e.name,e.version);return!0}for(let e of T)if(!await I(e,!0))return 1;if(E.length===0)return p.outro(`Nothing written.`),0;p.log.success(`Added\n${E.map(e=>` ${e}`).join(`
3
+ `)}`);let R=k(f,[...O].map(([e,t])=>({name:e,version:t}))),z=[...O.keys()].filter(e=>!R.some(t=>t.name===e));if(O.size>0){let t=[...z.map(t=>` ${e.green(`✔`)} ${t} ${e.dim(`already installed`)}`),...R.map(t=>` ${e.yellow(`+`)} ${t.name}@${t.version}`)];p.log.info(`Dependencies\n${t.join(`
4
+ `)}`)}if(R.length>0){let n=C(f),r=R.map(e=>`${e.name}@${e.version}`),i=o.yes;if(!i){let t=await p.confirm({message:`Install ${R.length} missing package${R.length>1?`s`:``} with ${e.bold(n)}?`});if(p.isCancel(t))return p.cancel(`Cancelled.`),1;i=t}if(i){let{command:e,args:i}=A(n,r),a=t(e,i,{cwd:f,stdio:`inherit`,shell:process.platform===`win32`});if(a.status!==0)return p.log.error(`${e} exited with ${a.status??`an error`}.`),1}else p.log.info(`Install manually:\n ${C(f)} add ${r.join(` `)}`)}return p.outro(`Done.`),0}function I(e,t){let n=Array.from({length:t.length+1},(e,t)=>t);for(let r=1;r<=e.length;r++){let i=[r];for(let a=1;a<=t.length;a++){let o=e[r-1]===t[a-1]?0:1;i[a]=Math.min(i[a-1]+1,n[a]+1,n[a-1]+o)}n=i}return n[t.length]}function L(e,t){let n=[...e.components.map(e=>e.component),...e.blocks.map(e=>e.id)].map(e=>({name:e,score:e.includes(t)||t.includes(e)?0:I(e,t)})).filter(e=>e.score<=2).sort((e,t)=>e.score-t.score).slice(0,5).map(e=>e.name);n.length?p.log.info(`Did you mean: ${n.join(`, `)}`):p.log.info(`Run ${j()} list to see everything.`)}async function R(t){let n=t.includes(`--force`),r=x();if(!r)return p.log.error(`No package.json found. Run this inside a project.`),1;if(p.intro(e.bgCyan(e.black(` Yumma UI `))),D(r)&&!n)return p.log.warn(`${y} already exists. Use --force to overwrite.`),p.outro(`Nothing to do.`),0;let i=w(r),a=C(r),o=T(r);p.log.step([i?`Detected ${e.bold(i)}`:`No framework detected`,`Detected ${e.bold(a)}`].join(`
5
+ `));let s=await p.text({message:`Where should components go?`,placeholder:`components/ui`,defaultValue:`components/ui`});if(p.isCancel(s))return z();let c=!1;if(o){let t=await p.confirm({message:`Use the ${e.bold(`"${o}/"`)} import alias?`});if(p.isCancel(t))return z();c=t}return O(r,{componentsDir:String(s),alias:c&&o?`${o}/${s}`:null,registry:h}),p.log.success(`Wrote ${e.bold(E(r))}`),p.outro(`Next: ${e.cyan(`${j(r)} add button`)}`),0}function z(){return p.cancel(`Cancelled.`),1}async function B(t){let n=t.find(e=>!e.startsWith(`-`)),r=x(),i=r&&D(r)?.registry||h,a;try{a=await v(i)}catch(t){return console.error(e.red(t instanceof g?t.message:String(t))),1}if(n){let t=a.components.find(e=>e.component===n);if(!t)return console.error(e.red(`Unknown component ${n}.`)),1;let i=a.blocks.filter(e=>e.component===n);if(console.log(`\n${e.bold(t.title)} ${e.dim(t.component)}\n`),console.log(` ${e.dim(`${j(r)} add ${t.component}`)}\n`),console.log(` ${e.dim(`Every variation is a prop. Read its API at`)} ${e.cyan(`https://yummacss.com/ui/components/${t.component}`)}\n`),i.length){console.log(` ${e.bold(`${i.length} block${i.length>1?`s`:``} built on it`)}`);for(let t of i)console.log(` ${t.id.padEnd(26)}${e.dim(`add ${t.id}`)}`);console.log()}return 0}console.log(`\n${e.bold(`${a.components.length} components`)}\n`);let o=a.components.map(e=>e.component);for(let e=0;e<o.length;e+=2)console.log(` ${(o[e]??``).padEnd(24)}${o[e+1]??``}`);console.log(`\n${e.bold(`${a.blocks.length} blocks`)}\n`);let s=a.blocks.map(e=>e.id);for(let e=0;e<s.length;e+=2)console.log(` ${(s[e]??``).padEnd(30)}${s[e+1]??``}`);return console.log(`\n ${e.dim(`${j(r)} list <component>`)} to see its blocks\n`),0}const V=[`.astro`,`.cjs`,`.cts`,`.js`,`.jsx`,`.md`,`.mdx`,`.mjs`,`.mts`,`.svelte`,`.ts`,`.tsx`,`.vue`],H=new Set([`node_modules`,`.git`]),U=[/\bfrom\s*["']([^"'\n]+)["']/g,/\bimport\s*\(\s*["']([^"'\n]+)["']/g,/\brequire\s*\(\s*["']([^"'\n]+)["']/g,/\bimport\s+["']([^"'\n]+)["']/g];function W(e){let t=new Set;for(let n of U){n.lastIndex=0;for(let r of e.matchAll(n))r[1]&&t.add(r[1])}return[...t]}const G=/\b(?:from|import\s*\(|require\s*\()\s*`/;function K(e){return G.test(e)}function q(e){let t=e.split(f).join(`/`).replace(/^\.\//,``);for(let e of V)if(t.endsWith(e))return t.slice(0,-e.length);return t}function J(e,t,n){let r=d(n.root,n.componentsDir);if(n.alias){let t=`${n.alias}/`;if(e===n.alias)return`index`;if(e.startsWith(t)){let n=e.slice(t.length);return n?q(n):`index`}}if(!e.startsWith(`.`))return null;let i=u(r,d(c(t),e));return i?i===`..`||i.startsWith(`..${f}`)?null:q(i):`index`}function Y(e,t){return q(u(d(t.root,t.componentsDir),e))}function X(e,t,n=[]){let r;try{r=a(e,{withFileTypes:!0})}catch{return n}for(let i of r){let r=l(e,i.name);if(i.isDirectory()){if(H.has(i.name)||r===t)continue;X(r,t,n)}else if(i.isFile()){let e=i.name.lastIndexOf(`.`);e>0&&V.includes(i.name.slice(e))&&n.push(r)}}return n}function Z(e){let t=new Set;for(let n of e.components)t.add(`${n.component}.tsx`);for(let n of e.blocks)t.add(`${n.id}.tsx`);return t}function Q(e,t){let n=d(e.root,e.componentsDir),r=X(n,``),a=X(e.root,n),o=new Map,s=new Set,c=new Map,l=[];for(let n of r){let r=Y(n,e);n.endsWith(`.tsx`)&&!r.includes(`/`)&&t.has(`${r}.tsx`)?o.set(r,n):s.add(r);let a=i(n,`utf8`);K(a)&&l.push(n);let u=new Set;for(let t of W(a)){let r=J(t,n,e);r&&u.add(r)}c.set(r,u)}for(let t of a){let n=i(t,`utf8`);K(n)&&l.push(t);for(let r of W(n)){let n=J(r,t,e);n&&s.add(n)}}let u=new Set,f=[...s];for(;f.length>0;){let e=f.pop();if(!u.has(e)){u.add(e);for(let t of c.get(e)??[])u.has(t)||f.push(t)}}let p=[];for(let[e,t]of o)u.has(e)||p.push(t);return{unused:p.sort(),kept:o.size-p.length,foreign:r.length-o.size,scanned:r.length+a.length,computed:l}}function te(e){return{write:e.includes(`--write`),yes:e.includes(`--yes`)||e.includes(`-y`)}}async function ne(t){let n=te(t),r=x();if(!r)return p.log.error(`No package.json found. Run this inside a project.`),1;let i=D(r);if(!i)return p.log.error(`No ${y} found. Run ${e.cyan(`${j(r)} init`)} first.`),1;p.intro(e.bgCyan(e.black(` Yumma UI `)));let a,s=p.spinner();s.start(`Fetching registry`);try{a=await v(i.registry)}catch(e){return s.stop(`Registry unavailable`,1),p.log.error(e instanceof g?e.message:String(e)),1}s.stop(`Reading the project`);let c=Q({root:r,componentsDir:i.componentsDir,alias:i.alias},Z(a)),l=e=>u(r,e).replace(/\\/g,`/`);if(c.computed.length>0&&p.log.warn(`Imports built at runtime in ${c.computed.length} file${c.computed.length>1?`s`:``}; anything they name is invisible here:\n${c.computed.slice(0,5).map(e=>` ${l(e)}`).join(`
6
+ `)}`),c.unused.length===0)return p.log.info(`${c.kept} installed, all reachable. ${c.scanned} files scanned.`),p.outro(`Nothing to prune.`),0;if(p.log.info(`Unreachable from outside ${e.bold(i.componentsDir)}\n${c.unused.map(t=>` ${e.yellow(l(t))}`).join(`
7
+ `)}`),p.log.step(`${c.unused.length} unused, ${c.kept} in use`+(c.foreign>0?`, ${c.foreign} not ours`:``)),!n.write)return p.outro(`Delete them with ${e.cyan(`${j(r)} prune --write`)}`),0;if(!n.yes){let e=await p.confirm({message:`Delete ${c.unused.length} file${c.unused.length>1?`s`:``}?`,initialValue:!1});if(p.isCancel(e))return p.cancel(`Cancelled.`),1;if(!e)return p.outro(`Nothing deleted.`),0}for(let e of c.unused)o(e,{force:!0});return p.log.success(`Deleted ${c.unused.length}`),p.outro(`Done.`),0}const $=`
8
+ ${e.bold(`yummaui`)} ${e.dim(`v${m}`)}
7
9
 
8
10
  Copies Yumma UI components into your project. Never a dependency.
9
11
 
@@ -14,10 +16,12 @@ ${e.bold(`Commands`)}
14
16
  init Set up yummaui.json in this project
15
17
  add <component...> Copy a component in
16
18
  list [component] Browse what is available
19
+ prune Find components nothing uses
17
20
 
18
21
  ${e.bold(`Options`)}
19
22
  -a, --all Add every component
20
23
  --overwrite Replace files that already exist
24
+ --write prune: delete, instead of only listing
21
25
  -y, --yes Skip prompts, take the defaults
22
26
  -h, --help Show this
23
27
  --version Show the version
@@ -27,4 +31,5 @@ ${e.bold(`Examples`)}
27
31
  npx yummaui add dialog tooltip
28
32
  npx yummaui add --all
29
33
  npx yummaui list button
30
- `;async function B(){let t=process.argv.slice(2),n=t[0];if(!n||n===`-h`||n===`--help`)return console.log(z),0;if(n===`--version`||n===`-V`)return console.log(d),0;let r=t.slice(1);switch(n){case`init`:return I(r);case`add`:return N(r);case`list`:case`ls`:return R(r);default:return console.error(e.red(`Unknown command "${n}".`)),console.log(z),1}}B().then(e=>{process.exitCode=e}).catch(t=>{console.error(e.red(t instanceof Error?t.message:String(t))),process.exitCode=1});export{};
34
+ npx yummaui prune
35
+ `;async function re(){let t=process.argv.slice(2),n=t[0];if(!n||n===`-h`||n===`--help`)return console.log($),0;if(n===`--version`||n===`-V`)return console.log(m),0;let r=t.slice(1);switch(n){case`init`:return R(r);case`add`:return F(r);case`list`:case`ls`:return B(r);case`prune`:return ne(r);default:return console.error(e.red(`Unknown command "${n}".`)),console.log($),1}}re().then(e=>{process.exitCode=e}).catch(t=>{console.error(e.red(t instanceof Error?t.message:String(t))),process.exitCode=1});export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yummaui",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Add Yumma UI components to your project.",
5
5
  "keywords": [
6
6
  "yummacss",