yummaui 0.0.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 +111 -0
  2. package/dist/cli.mjs +28 -0
  3. package/package.json +49 -0
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ [![Yumma UI](https://yummacss.com/ui-og.png)](https://yummacss.com/ui)
2
+
3
+ # Yumma UI
4
+
5
+ Add [Yumma UI](https://yummacss.com/ui) components to your project, from the terminal.
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
+ ## Usage
12
+
13
+ There is nothing to install.
14
+
15
+ ```bash
16
+ pnpm dlx yummaui init
17
+ ```
18
+
19
+ ```bash
20
+ pnpm dlx yummaui add button
21
+ ```
22
+
23
+ That writes `components/ui/button.tsx` and offers to install anything the component needs that you do not already have.
24
+
25
+ ## Commands
26
+
27
+ ### `init`
28
+
29
+ Detects your framework, package manager and import alias, then writes a `yummaui.json`:
30
+
31
+ ```json
32
+ {
33
+ "componentsDir": "components/ui",
34
+ "alias": "@/components/ui",
35
+ "registry": "https://yummacss.com/ui/r"
36
+ }
37
+ ```
38
+
39
+ Pass `--force` to overwrite an existing config.
40
+
41
+ ### `add <component...>`
42
+
43
+ ```bash
44
+ pnpm dlx yummaui add button
45
+ pnpm dlx yummaui add dialog tooltip
46
+ pnpm dlx yummaui add dialog-sign-in
47
+ ```
48
+
49
+ 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.
50
+
51
+ 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.
52
+
53
+ | Option | |
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.
59
+
60
+ ### `list [component]`
61
+
62
+ ```bash
63
+ pnpm dlx yummaui list
64
+ pnpm dlx yummaui list button
65
+ ```
66
+
67
+ ## Dependencies
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.
70
+
71
+ 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.
72
+
73
+ Installs run through whichever package manager your lockfile indicates.
74
+
75
+ ## Requirements
76
+
77
+ - Node 18 or newer
78
+ - React 19
79
+ - [Yumma CSS](https://yummacss.com) configured in your project, since the components are styled with its utilities
80
+
81
+ ## Registry
82
+
83
+ The CLI reads a static JSON registry published by the docs site:
84
+
85
+ ```
86
+ https://yummacss.com/ui/r/index.json every component and block
87
+ https://yummacss.com/ui/r/<id>.json one component's source and dependencies
88
+ ```
89
+
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
+ ## License
110
+
111
+ MIT
package/dist/cli.mjs ADDED
@@ -0,0 +1,28 @@
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.0.1`;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={overwrite:!1,yes:!1};for(let r=0;r<e.length;r++){let i=e[r];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`}async function M(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)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=[];for(let t of l){let n=C.components.find(e=>e.component===t);if(n){E.push(n.base);continue}let r=C.blocks.find(e=>e.id===t);if(r){E.push(r.id);continue}return u.log.error(`Unknown component or block ${e.bold(t)}.`),P(C,t),1}let M=[],N=new Map,F=new Set;async function I(e,t){if(F.has(e))return!0;F.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 I(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),M.push(f);for(let e of i.dependencies)N.set(e.name,e.version);return!0}for(let e of E)if(!await I(e,!0))return 1;if(M.length===0)return u.outro(`Nothing written.`),0;u.log.success(`Added\n${M.map(e=>` ${e}`).join(`
3
+ `)}`);let L=D(m,[...N].map(([e,t])=>({name:e,version:t}))),R=[...N.keys()].filter(e=>!L.some(t=>t.name===e));if(N.size>0){let t=[...R.map(t=>` ${e.green(`✔`)} ${t} ${e.dim(`already installed`)}`),...L.map(t=>` ${e.yellow(`+`)} ${t.name}@${t.version}`)];u.log.info(`Dependencies\n${t.join(`
4
+ `)}`)}if(L.length>0){let n=x(m),r=L.map(e=>`${e.name}@${e.version}`),i=d.yes;if(!i){let t=await u.confirm({message:`Install ${L.length} missing package${L.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 N(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 P(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:N(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 F(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 I();let c=!1;if(o){let t=await u.confirm({message:`Use the ${e.bold(`"${o}/"`)} import alias?`});if(u.isCancel(t))return I();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 I(){return u.cancel(`Cancelled.`),1}async function L(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 R=`
6
+ ${e.bold(`yummaui`)} ${e.dim(`v${d}`)}
7
+
8
+ Copies Yumma UI components into your project. Never a dependency.
9
+
10
+ ${e.bold(`Usage`)}
11
+ npx yummaui <command> [options]
12
+
13
+ ${e.bold(`Commands`)}
14
+ init Set up yummaui.json in this project
15
+ add <component...> Copy a component in
16
+ list [component] Browse what is available
17
+
18
+ ${e.bold(`Options`)}
19
+ --overwrite Replace files that already exist
20
+ -y, --yes Skip prompts, take the defaults
21
+ -h, --help Show this
22
+ --version Show the version
23
+
24
+ ${e.bold(`Examples`)}
25
+ npx yummaui add button
26
+ npx yummaui add dialog tooltip
27
+ npx yummaui list button
28
+ `;async function z(){let t=process.argv.slice(2),n=t[0];if(!n||n===`-h`||n===`--help`)return console.log(R),0;if(n===`--version`||n===`-V`)return console.log(d),0;let r=t.slice(1);switch(n){case`init`:return F(r);case`add`:return M(r);case`list`:case`ls`:return L(r);default:return console.error(e.red(`Unknown command "${n}".`)),console.log(R),1}}z().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 ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "yummaui",
3
+ "version": "0.0.1",
4
+ "description": "Add Yumma UI components to your project.",
5
+ "keywords": [
6
+ "yummacss",
7
+ "yumma-ui",
8
+ "components",
9
+ "base-ui",
10
+ "cli"
11
+ ],
12
+ "homepage": "https://yummacss.com/ui",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/yummacss/ui.git"
16
+ },
17
+ "license": "MIT",
18
+ "author": "Renildo Pereira",
19
+ "type": "module",
20
+ "bin": {
21
+ "yummaui": "./dist/cli.mjs"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "dependencies": {
27
+ "@clack/prompts": "^0.11.0",
28
+ "picocolors": "^1.1.1"
29
+ },
30
+ "devDependencies": {
31
+ "@biomejs/biome": "^2.4.1",
32
+ "@types/node": "^26.1.2",
33
+ "tsdown": "^0.16.5",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^4.1.10"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public",
39
+ "provenance": true
40
+ },
41
+ "scripts": {
42
+ "build": "tsdown",
43
+ "check-types": "tsc --noEmit",
44
+ "dev": "tsdown --watch",
45
+ "lint": "biome check --write",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest"
48
+ }
49
+ }