yummaui 0.0.1 → 0.2.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 +22 -45
- package/dist/cli.mjs +13 -6
- 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
|
|
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`
|
|
@@ -44,18 +38,14 @@ Pass `--force` to overwrite an existing config.
|
|
|
44
38
|
pnpm dlx yummaui add button
|
|
45
39
|
pnpm dlx yummaui add dialog tooltip
|
|
46
40
|
pnpm dlx yummaui add dialog-sign-in
|
|
41
|
+
pnpm dlx yummaui add --all
|
|
47
42
|
```
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
|
54
|
-
| --- | --- |
|
|
55
|
-
| `--overwrite` | Replace files that already exist |
|
|
56
|
-
| `-y, --yes` | Skip prompts and take the defaults |
|
|
57
|
-
|
|
58
|
-
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 |
|
|
59
49
|
|
|
60
50
|
### `list [component]`
|
|
61
51
|
|
|
@@ -64,19 +54,25 @@ pnpm dlx yummaui list
|
|
|
64
54
|
pnpm dlx yummaui list button
|
|
65
55
|
```
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
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.
|
|
57
|
+
### `prune`
|
|
70
58
|
|
|
71
|
-
|
|
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.
|
|
72
61
|
|
|
73
|
-
|
|
62
|
+
```bash
|
|
63
|
+
pnpm dlx yummaui prune # list them
|
|
64
|
+
pnpm dlx yummaui prune --write # delete them, after confirming
|
|
65
|
+
```
|
|
74
66
|
|
|
75
|
-
|
|
67
|
+
| Option | |
|
|
68
|
+
|-------------|-------------------------------------|
|
|
69
|
+
| `--write` | Delete, instead of only listing |
|
|
70
|
+
| `-y, --yes` | Skip the confirmation |
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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.
|
|
80
76
|
|
|
81
77
|
## Registry
|
|
82
78
|
|
|
@@ -87,25 +83,6 @@ https://yummacss.com/ui/r/index.json every component and block
|
|
|
87
83
|
https://yummacss.com/ui/r/<id>.json one component's source and dependencies
|
|
88
84
|
```
|
|
89
85
|
|
|
90
|
-
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.
|
|
91
|
-
|
|
92
|
-
## Contributing
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
# install dependencies
|
|
96
|
-
pnpm i
|
|
97
|
-
# build the CLI
|
|
98
|
-
pnpm build
|
|
99
|
-
# rebuild on change
|
|
100
|
-
pnpm dev
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Then run it against a local checkout:
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
node dist/cli.mjs list
|
|
107
|
-
```
|
|
108
|
-
|
|
109
86
|
## License
|
|
110
87
|
|
|
111
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
|
|
3
|
-
`)}`);let
|
|
4
|
-
`)}`)}if(
|
|
5
|
-
`));let s=await
|
|
6
|
-
${
|
|
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.0`;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 y(e,t){return _(`${e}/${t}.json`)}const b=`yummaui.json`,x={"pnpm-lock.yaml":`pnpm`,"bun.lockb":`bun`,"bun.lock":`bun`,"yarn.lock":`yarn`,"package-lock.json":`npm`};function S(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 C(e){try{return JSON.parse(i(l(e,`package.json`),`utf8`))}catch{return{}}}function w(e){for(let[t,r]of Object.entries(x))if(n(l(e,t)))return r;let t=C(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 T(e){let t=C(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 E(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 D(e){return l(e,b)}function O(e){let t=D(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 k(e,t){s(D(e),`${JSON.stringify(t,null,` `)}\n`)}function A(e,t){let n=C(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 j(e,t){return{command:e,args:e===`npm`?[`install`,...t]:[`add`,...t]}}function M(e=S()){switch(e?w(e):`npm`){case`pnpm`:return`pnpm dlx yummaui`;case`yarn`:return`yarn dlx yummaui`;case`bun`:return`bunx yummaui`;default:return`npx yummaui`}}function N(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 ee(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}=N(i),d=S();if(!d)return p.log.error(`No package.json found. Run this inside a project.`),1;let f=d,m=O(f);if(!m)return p.log.error(`No ${b} found. Run ${e.cyan(`${M(f)} init`)} first.`),1;let{registry:h,componentsDir:_}=m;if(a.length===0&&!o.all)return p.log.error(`Nothing to add. Try: ${M(f)} add button`),1;p.intro(e.bgCyan(e.black(` Yumma UI `)));let x,C=p.spinner();C.start(`Fetching registry`);try{x=await v(m.registry)}catch(e){return C.stop(`Registry unavailable`,1),p.log.error(e instanceof g?e.message:String(e)),1}C.stop(`${x.components.length} components, ${x.blocks.length} blocks available`);let T=P(x,a,{all:o.all});if(`unknown`in T)return p.log.error(`Unknown component or block ${e.bold(T.unknown)}.`),T.unknown===`all`?p.log.info(`Every component is ${e.cyan(`--all`)}.`):L(x,T.unknown),1;let E=T.ids,D=[],k=new Map,F=new Set;async function I(e,t){if(F.has(e))return!0;F.add(e);let i;try{i=await y(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,_,ee(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),D.push(d);for(let e of i.dependencies)k.set(e.name,e.version);return!0}for(let e of E)if(!await I(e,!0))return 1;if(D.length===0)return p.outro(`Nothing written.`),0;p.log.success(`Added\n${D.map(e=>` ${e}`).join(`
|
|
3
|
+
`)}`);let R=A(f,[...k].map(([e,t])=>({name:e,version:t}))),z=[...k.keys()].filter(e=>!R.some(t=>t.name===e));if(k.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=w(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}=j(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 ${w(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 ${M()} list to see everything.`)}async function R(t){let n=t.includes(`--force`),r=S();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 `))),O(r)&&!n)return p.log.warn(`${b} already exists. Use --force to overwrite.`),p.outro(`Nothing to do.`),0;let i=T(r),a=w(r),o=E(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 k(r,{componentsDir:String(s),alias:c&&o?`${o}/${s}`:null,registry:h}),p.log.success(`Wrote ${e.bold(D(r))}`),p.outro(`Next: ${e.cyan(`${M(r)} add button`)}`),0}function z(){return p.cancel(`Cancelled.`),1}async function B(t){let n=t.find(e=>!e.startsWith(`-`)),r=S(),i=r&&O(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(`${M(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(`${M(r)} list <component>`)} to see its blocks\n`),0}const V=new Set([`.astro`,`.cjs`,`.cts`,`.js`,`.jsx`,`.md`,`.mdx`,`.mjs`,`.mts`,`.svelte`,`.ts`,`.tsx`,`.vue`]),H=[`.tsx`,`.ts`,`.jsx`,`.js`,`.mjs`,`.cjs`,`.mts`,`.cts`],U=new Set([`.cache`,`.git`,`.next`,`.output`,`.svelte-kit`,`.turbo`,`.vercel`,`build`,`coverage`,`dist`,`node_modules`,`out`]),W=[/\bfrom\s*["']([^"'\n]+)["']/g,/\bimport\s*\(\s*["']([^"'\n]+)["']/g,/\brequire\s*\(\s*["']([^"'\n]+)["']/g,/\bimport\s+["']([^"'\n]+)["']/g];function G(e){let t=new Set;for(let n of W){n.lastIndex=0;for(let r of e.matchAll(n))r[1]&&t.add(r[1])}return[...t]}const K=/\b(?:from|import\s*\(|require\s*\()\s*`/;function q(e){return K.test(e)}function J(e){let t=e.split(f).join(`/`).replace(/^\.\//,``);for(let e of H)if(t.endsWith(e))return t.slice(0,-e.length);return t}function Y(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?J(n):`index`}}if(!e.startsWith(`.`))return null;let i=u(r,d(c(t),e));return i?i===`..`||i.startsWith(`..${f}`)?null:J(i):`index`}function X(e,t){return J(u(d(t.root,t.componentsDir),e))}function Z(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(U.has(i.name)||r===t)continue;Z(r,t,n)}else if(i.isFile()){let e=i.name.lastIndexOf(`.`);e>0&&V.has(i.name.slice(e))&&n.push(r)}}return n}function Q(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 te(e,t){let n=d(e.root,e.componentsDir),r=Z(n,``),a=Z(e.root,n),o=new Map,s=new Set,c=new Map,l=[];for(let n of r){let r=X(n,e);n.endsWith(`.tsx`)&&!r.includes(`/`)&&t.has(`${r}.tsx`)?o.set(r,n):s.add(r);let a=i(n,`utf8`);q(a)&&l.push(n);let u=new Set;for(let t of G(a)){let r=Y(t,n,e);r&&u.add(r)}c.set(r,u)}for(let t of a){let n=i(t,`utf8`);q(n)&&l.push(t);for(let r of G(n)){let n=Y(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 ne(e){return{write:e.includes(`--write`),yes:e.includes(`--yes`)||e.includes(`-y`)}}async function re(t){let n=ne(t),r=S();if(!r)return p.log.error(`No package.json found. Run this inside a project.`),1;let i=O(r);if(!i)return p.log.error(`No ${b} found. Run ${e.cyan(`${M(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=te({root:r,componentsDir:i.componentsDir,alias:i.alias},Q(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(`${M(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,9 +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`)}
|
|
22
|
+
-a, --all Add every component
|
|
19
23
|
--overwrite Replace files that already exist
|
|
24
|
+
--write prune: delete, instead of only listing
|
|
20
25
|
-y, --yes Skip prompts, take the defaults
|
|
21
26
|
-h, --help Show this
|
|
22
27
|
--version Show the version
|
|
@@ -24,5 +29,7 @@ ${e.bold(`Options`)}
|
|
|
24
29
|
${e.bold(`Examples`)}
|
|
25
30
|
npx yummaui add button
|
|
26
31
|
npx yummaui add dialog tooltip
|
|
32
|
+
npx yummaui add --all
|
|
27
33
|
npx yummaui list button
|
|
28
|
-
|
|
34
|
+
npx yummaui prune
|
|
35
|
+
`;async function ie(){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 re(r);default:return console.error(e.red(`Unknown command "${n}".`)),console.log($),1}}ie().then(e=>{process.exitCode=e}).catch(t=>{console.error(e.red(t instanceof Error?t.message:String(t))),process.exitCode=1});export{};
|