@sugarcube-org/cli
Version:
A CLI for scaffolding sugarcube applications
51 lines (44 loc) • 31.8 kB
JavaScript
var Te=Object.defineProperty;var a=(e,t)=>Te(e,"name",{value:t,configurable:!0});import{Command as j}from"commander";import{existsSync as k,readFileSync as re}from"node:fs";import{relative as E,join as h,basename as U,resolve as F,dirname as Ie,normalize as Fe}from"pathe";import{select as $e,isCancel as M,log as v,multiselect as Re,text as xe,confirm as Pe,spinner as K,intro as ie,cancel as je,outro as _}from"@clack/prompts";import{GLOBAL_DIR as _e,VARIABLES_FILE_SUFFIX as Ne,TOKENS_FILE_SUFFIX as ae,tokenProcessingPipeline as Ae,SUGARCUBE_CONFIG_FILE as b,loadConfig as L,DEFAULT_STYLES_PATH as V,DEFAULT_DESIGN_TOKENS_PATH as Oe,generateCSSVariables as H,writeCSSVariablesToDisk as q,generateIndex as B,generateCSSUtilityClasses as ce,writeCSSUtilitiesToDisk as le,SUGARCUBE_FILE as Ue,validateConfig as Le,DEFAULT_CONFIG as ue,normalizeConfig as Ge}from"@sugarcube-org/core";import w from"picocolors";import We from"node-fetch";import{z as d}from"zod";import J,{mkdir as $,writeFile as N}from"node:fs/promises";import{execa as Me}from"execa";import{detect as Be}from"@antfu/ni";import I,{resolve as Je}from"node:path";import Q from"fast-glob";var ze="0.0.0-alpha.16",Ke={version:ze};class m extends Error{static{a(this,"CLIError")}constructor(t,n){super(t),this.name="CLIError",this.cause=n}}class z extends m{static{a(this,"ValidationError")}constructor(t,n,o,s){super(t),this.field=n,this.value=o,this.suggestion=s,this.name="ValidationError"}}class X extends m{static{a(this,"FileOperationError")}constructor(t,n,o,s){super(t),this.filePath=n,this.operation=o,this.cause=s,this.name="FileOperationError"}}const Y=d.enum(["react","astro-native","astro-enhanced","web-components"]),pe=d.object({path:d.string(),type:d.string()}),Ve=pe.extend({framework:Y});d.object({themes:d.record(d.string(),d.array(d.string())).optional()}).strict();const He=d.object({name:d.string(),type:d.string(),description:d.string().optional(),frameworks:d.array(d.string()).optional(),files:d.array(d.union([Ve,pe])),tokens:d.record(d.object({type:d.string(),mapping:d.string()})).optional(),dependencies:d.record(Y,d.array(d.string())).optional(),registryDependencies:d.record(Y,d.array(d.string())).optional(),tokenDependencies:d.array(d.string()).optional()}),qe=d.array(He),Qe=d.object({content:d.string()}),C={error:w.red,warn:w.yellow,info:w.cyan,success:w.green,bold:w.bold,path:w.cyan},fe=process.env.REGISTRY_URL??"https://sugarcube.sh/r";async function me(e){try{const t=await We(e);if(!t.ok){if(t.status===401)throw new m(`Registry access denied: Authentication required
URL: ${C.info(e)}`);if(t.status===403)throw new m(`Registry access denied: Invalid or missing token
URL: ${C.info(e)}`);if(t.status===404)throw new m(`Registry resource not found
URL: ${C.info(e)}`);const n=await t.json().catch(()=>null),o=n&&typeof n=="object"&&"error"in n?n.error:t.statusText;throw new m(`Registry request failed: ${o}
URL: ${C.info(e)}`)}return t.json()}catch(t){throw t instanceof m?t:new m(`Failed to connect to registry
URL: ${C.info(e)}
Check your internet connection`)}}a(me,"fetchRegistry");async function A(){const e=`${fe}/index.json`,t=await me(e);try{return qe.parse(t)}catch{throw new m(`Invalid registry data received
URL: ${C.info(e)}`)}}a(A,"getRegistryIndex");async function Z({type:e,name:t,framework:n}){const o=await A(),s=o.find(c=>c.type===e&&c.name===t);if(!s){const c=o.filter(i=>i.type===e).map(i=>i.name);throw new m(`${e} '${C.info(t)}' not found in registry
Available ${e}s: ${c.join(", ")}`)}let r=s.files;e==="component"&&n&&(r=s.files.filter(c=>"framework"in c&&c.framework===n));const l=await Promise.all(r.map(async c=>{const i=`${fe}/${c.path}.json`,p=await me(i);try{const g=Qe.parse(p);return{path:c.path,type:c.type,framework:"framework"in c?c.framework:void 0,content:g.content}}catch{throw new m(`Invalid file content received
File: ${C.info(c.path)}`)}}));return{item:s,files:l}}a(Z,"getRegistryFiles");async function de(e,t,n){const o=[],s=new Set;async function r(l){if(s.has(l))return;s.add(l);const c=e.find(p=>p.name===l);if(!c){const p=e.filter(g=>g.type==="component").map(g=>g.name).join(", ");throw new m(`Component '${l}' not found in registry
Available components: ${p}`)}const i=c.registryDependencies?.[n]||[];for(const p of i)await r(p);o.push(c)}a(r,"resolveComponent");for(const l of t)await r(l);return o}a(de,"resolveTree");async function Xe({cssOutputDirectory:e,selectedComponents:t,componentType:n,componentsOutputDirectory:o,tokensOutputDirectory:s}){const r={variableCSS:[],utilityCSS:[],componentFiles:[],componentCSS:[],cubeCSS:[],indexFiles:[]},l=await A(),i=(await de(l,t,n)).map(f=>f.name),p=l.filter(f=>f.type==="component").filter(f=>i.includes(f.name)),g=p.flatMap(f=>f.files.filter(u=>(u.type==="tsx"||u.type==="astro"||u.type==="njk")&&"framework"in u&&u.framework===n).map(u=>E(process.cwd(),h(o,f.name,`${f.name}.${u.type}`))));r.componentFiles=g.filter(f=>k(h(process.cwd(),f)));const y=p.flatMap(f=>{const u=f.name;return[E(process.cwd(),h(o,f.name,`${u}.css`)),E(process.cwd(),h(e,_e,`${u}${Ne}`))]});r.componentCSS=y.filter(f=>k(h(process.cwd(),f)));const S=p.flatMap(f=>{const u=f.name;return[E(process.cwd(),h(s,`${u}${ae}`)),E(process.cwd(),h(s,"components",`${u}${ae}`))]});return r.componentCSS.push(...S.filter(f=>k(h(process.cwd(),f)))),r}a(Xe,"collectComponentOverwriteWarnings");async function ge({cubeDirectory:e}){const t={variableCSS:[],utilityCSS:[],componentFiles:[],componentCSS:[],cubeCSS:[],indexFiles:[]},n=await A();if(!n)throw new m("Failed to fetch registry index");const o=n.filter(s=>s.type==="cube").flatMap(s=>s.files).map(s=>{const r=s.path.replace(/^styles\//,"");return E(process.cwd(),h(e,r))});return t.cubeCSS=o.filter(s=>k(h(process.cwd(),s))),t}a(ge,"collectCubeOverwriteWarnings");const ee={PROJECT_REQUIRED:`This command requires you to initialize a sugarcube project. Run ${w.cyan("make-sugarcube init")} first.
For more information, visit: ${w.cyan("https://sugarcube.sh/docs/initialize")}`};function te(e){const t=[];if(e.variableCSS.length>0&&t.push(`CSS variables files:
${e.variableCSS.map(n=>` - ${n}`).join(`
`)}`),e.utilityCSS.length>0&&t.push(`CSS utility files:
${e.utilityCSS.map(n=>` - ${n}`).join(`
`)}`),e.cubeCSS.length>0&&t.push(`CUBE CSS files:
${e.cubeCSS.map(n=>` - ${n}`).join(`
`)}`),e.componentFiles.length>0||e.componentCSS.length>0){const n=[...e.componentFiles,...e.componentCSS];t.push(`Component files:
${n.map(o=>` - ${o}`).join(`
`)}`)}if(e.indexFiles.length>0&&t.push(`Index files:
${e.indexFiles.map(n=>` - ${n}`).join(`
`)}`),t.length!==0)return w.yellow(`WARNING: The following file(s) already exist and will be OVERWRITTEN:
${w.dim(t.join(`
`))}`)}a(te,"formatOverwriteWarnings");function Ye(e){return typeof e.tokens=="object"&&!("source"in e.tokens)}a(Ye,"hasCollections$1");async function Ze(e){if(Ye(e)){const t=e.tokens;return"components"in t?!1:(t.components={source:["components/*.tokens.json"]},!0)}return!1}a(Ze,"handleComponentTokenIntegration");const D={error(...e){console.log(C.error(e.join(" ")))},warn(...e){console.log(C.warn(e.join(" ")))},info(...e){console.log(C.info(e.join(" ")))},success(...e){console.log(C.success(e.join(" ")))},log(...e){console.log(e.join(" "))},break(){console.log("")}};function et(...e){process.env.DEBUG==="true"&&process.stderr.write(`[sugarcube:debug] ${e.map(String).join(" ")}
`)}a(et,"debugLog");function G(e){if(D.break(),e instanceof m){const t=e.message.split(`
`);if(t.length>1){D.error(t[0]),D.break();for(const n of t.slice(1))D.error(` ${n.trim()}`)}else D.error(e.message);process.env.DEBUG&&e.cause&&D.info(`
Caused by:`,e.cause)}else D.error(`An unexpected error occurred: ${e instanceof Error?e.message:String(e)}
If this issue persists, please report it: ${w.cyanBright("https://github.com/sugarcube-org/sugarcube/issues")}`),process.env.DEBUG&&D.info(`
Error details:`,e);D.break(),process.exit(0)}a(G,"handleError");async function tt(e,{withFallback:t=!1}={}){const n=await Be({programmatic:!0,cwd:e});if(n?.startsWith("yarn@"))return"yarn";if(n?.startsWith("pnpm@"))return"pnpm";if(n==="bun")return"bun";if(!t)return n?.split("@")[0]??"npm";const o=process.env.npm_config_user_agent||"";return o.startsWith("yarn")?"yarn":o.startsWith("pnpm")?"pnpm":o.startsWith("bun")?"bun":"npm"}a(tt,"getPackageManager");async function we(e,t){const n=await tt(t,{withFallback:!0}),o=n==="npm"?"install":"add";try{await Me(n,[o,...e],{cwd:t})}catch{const r=`Failed to install dependencies using ${n}.
This might be because you're using 'npx' with a pnpm project. Try:
pnpm dlx make-sugarcube components
Or use the appropriate package runner for your project:
npm: npx make-sugarcube components
pnpm: pnpm dlx make-sugarcube components
yarn: yarn dlx make-sugarcube components
bun: bunx make-sugarcube components`;throw new Error(r)}}a(we,"installDependencies");function nt(e){const t=h(e,"blocks");return k(t)}a(nt,"hasBlocksDirectory");function st(e){return typeof e.tokens=="object"&&!("source"in e.tokens)}a(st,"hasCollections");async function ot(e,t,n){const{hasCollectionsMode:o,useBlocksDir:s,componentsOutputDirectory:r,cssOutputDirectory:l,tokensOutputDirectory:c}=n;if(e.path.endsWith(".tokens.json")){const g=o?h(c,"components",`${t.name}.tokens.json`):h(c,`${t.name}.tokens.json`);return await N(g,e.content),g}if(e.path.endsWith(".css")){if(s){const S=h(l,"blocks",`${t.name}.css`);return await N(S,e.content),S}const g=h(r,t.name);await $(g,{recursive:!0});const y=h(g,`${t.name}.css`);return await N(y,e.content),y}const i=h(r,t.name);await $(i,{recursive:!0});const p=h(i,U(e.path));return await N(p,e.content),p}a(ot,"writeComponentFile");async function rt({registryIndex:e,selectedComponents:t,componentType:n,componentsOutputDirectory:o,cssOutputDirectory:s,tokensOutputDirectory:r,config:l,overwrite:c}){const i=[],p=new Set,g=nt(s),y=st(l);if(await $(o,{recursive:!0}),await $(r,{recursive:!0}),y){const f=h(r,"components");await $(f,{recursive:!0})}const S=await de(e,t,n);for(const f of S){const u=await Z({type:"component",name:f.name,framework:n});for(const x of u.files)if(x)try{const P=await ot(x,f,{hasCollectionsMode:y,useBlocksDir:g,componentsOutputDirectory:o,cssOutputDirectory:s,tokensOutputDirectory:r});i.push(P)}catch(P){const be=P instanceof Error?`: ${P.message}`:"";throw new m(`Failed to write component file for "${f.name}"${be}`)}const W=f.dependencies?.[n]||[];for(const x of W)p.add(x)}if(p.size>0)try{await we(Array.from(p),process.cwd())}catch(f){const u=f instanceof Error?`: ${f.message}`:"";throw new m(`Failed to install component dependencies${u}`)}return{createdFiles:i,npmDependencies:p}}a(rt,"installComponents");async function it(e="src/ui/components"){const t=await xe({message:"Save components to",placeholder:e,validate:a(n=>{if(n.trim().replace(/['"]/g,"").length===0)return"Output directory cannot be empty"},"validate")});return M(t)&&process.exit(0),I.relative(process.cwd(),I.resolve(process.cwd(),t))}a(it,"promptComponentDirectory");async function he(e=!0){const t=[{label:"React",value:"react",hint:".tsx"},{label:"Astro (Native)",value:"astro-native",hint:"Minimal, standards-based .astro; uses native HTML features"},{label:"Astro (Enhanced)",value:"astro-enhanced",hint:"Advanced features, richer client-side logic - coming soon"},{label:"Web Components",value:"web-components",hint:"Native web components using TypeScript - coming soon"}];e&&t.push({label:"Skip",value:"skip",hint:"continue without components"});const n=await $e({message:"Build with",options:t});return M(n)&&process.exit(0),n==="astro-enhanced"||n==="web-components"?(v.info(w.blue("Sorry, this framework is coming soon! Please choose 'Astro (Native)' or 'React' for now.")),he(e)):n}a(he,"promptComponentFramework");async function at(e,t){const n=e.filter(s=>s.type==="component"&&s.frameworks?.includes(t)),o=await Re({message:"Select components to add",options:n.map(s=>({label:s.name,value:s.name,hint:s.description})),required:!0});return M(o)&&process.exit(0),o}a(at,"promptComponentSelectionFiltered");async function ne(e,t=!1){const n=await Pe({message:e,initialValue:t});return(!n||M(n))&&process.exit(0),!0}a(ne,"confirmOverwrite");function se(e){const t=e.generated,o=[...new Set(t)].map(r=>I.relative(process.cwd(),r)),s=K();s.start(`Writing ${o.length} files:`),s.stop(`Wrote ${o.length} files:`);for(const r of o)D.log(` - ${r}`);D.break()}a(se,"showSummary");async function O(e,t){const n=t?{type:"memory",data:t,config:e}:{type:"files",config:e},{trees:o,resolved:s,errors:r}=await Ae(n);if(r.load.length>0)throw new m(`Failed to load token files:
${r.load.map(l=>`${l.file}: ${l.message}`).join(`
`)}`);if(r.validation.length>0||r.flatten.length>0||r.resolution.length>0){D.break();const l=[...r.flatten,...r.validation,...r.resolution],c=new Map;for(const i of l)if(i.source?.sourcePath){const p=c.get(i.source.sourcePath)||[];p.push(i.message),c.set(i.source.sourcePath,p)}for(const[i,p]of c){D.error(`Error(s) in ${C.path(i)}:`);for(const g of p)D.error(` - ${g}`);D.break()}throw new m(`Token validation failed. See ${C.info("https://sugarcube.sh/docs/design-tokens")} for valid token formats`)}return{trees:o,resolved:s}}a(O,"processTokensForCLI");async function ye(e){const t={$schema:"https://sugarcube.sh/r/schema.json",...e};try{const n=JSON.stringify(t,null,2);await N(b,n)}catch(n){const o=n instanceof Error?`: ${n.message}`:"";throw new m(`Failed to write config file${o}`)}}a(ye,"writeConfig");async function ct(e,t,n,o,s,r,l){const c=await A(),{createdFiles:i}=await rt({registryIndex:c,selectedComponents:e,componentType:t,componentsOutputDirectory:n,cssOutputDirectory:o,tokensOutputDirectory:s,config:r,overwrite:l});return i}a(ct,"installComponentFiles");const lt=new j().name("components").description("Add components to your project").argument("[components...]","Components to add (e.g., button card)").option("-f, --framework <type>","Framework to use (react, astro-native)").option("-s, --silent","Suppress logs and prompts").option("-o, --overwrite","Overwrite existing files").action(async(e,t)=>{try{if(t.silent||ie(w.inverse(" Add components ")),!k(b))throw new m(ee.PROJECT_REQUIRED);const{config:n}=await L();let o,s=[],r;if(e.length>0||t.framework){if(!t.framework)throw new m(`Framework is required when specifying components. Use --framework to specify a framework (react, astro-native) or run without arguments for interactive mode.
See ${w.cyan("https://docs.sugarcube.sh/cli/components")} for more information.`);if(!["react","astro-native"].includes(t.framework))throw new m("Invalid framework. Must be one of: react, astro-native.");if(r=t.framework,s=e,!n.output?.components)throw new m("Components directory must be configured in non-interactive mode. Please run the 'components' command without arguments first.");o=F(process.cwd(),n.output.components)}else{r=await he(!1);const y=await A();if(y||(je("Failed to fetch component list"),process.exit(1)),s=await at(y,r),n.output?.components)o=F(process.cwd(),n.output.components);else{const S=await it();o=F(process.cwd(),S),n.output.components=E(process.cwd(),o),await ye(n)}}const l=F(process.cwd(),n.output?.css||V),c=F(process.cwd(),n.output?.tokens||Oe),i=await Xe({cssOutputDirectory:l,selectedComponents:s,componentType:r,componentsOutputDirectory:o,tokensOutputDirectory:c}),p=te(i);p&&!t.force&&!t.silent&&(v.warn(p),await ne("Continue?",!1));const g=K();g.start("Installing components...");try{const y=await ct(s,r,o,l,c,n,t.overwrite||!1);g.stop("Components installed successfully"),await Ze(n);const{trees:S,resolved:f}=await O(n),{output:u}=await H(S,f,n);await q(u),await B(l,n),t.silent||(se({generated:y}),_(w.greenBright("Success! Components installed \u2728")))}catch(y){throw g.stop("Installation failed"),y}}catch(n){G(n)}});async function ke(e){const t=[],n=F(process.cwd(),e);await $(n,{recursive:!0});const s=(await A()).filter(r=>r.type==="cube").map(r=>r.name);for(const r of s){const l=await Z({type:"cube",name:r});for(const c of l.files){if(!c)continue;const i=c.path.replace(/^styles\//,""),p=h(n,i),g=Ie(p);try{await $(g,{recursive:!0}),await N(p,c.content),t.push(p)}catch(y){const S=y instanceof Error?`: ${y.message}`:"";throw new m(`Failed to write CUBE module "${r}"${S}`)}}}return t}a(ke,"installCUBE");const ut=new j().name("cube").description("Add CUBE CSS to your project").option("-s, --silent","Suppress logs and prompts").option("-f, --force","Skip overwrite confirmation").action(async e=>{try{if(!k(b))throw new m(ee.PROJECT_REQUIRED);const{config:t}=await L(),n=F(process.cwd(),t.output?.css||V);try{await $(n,{recursive:!0})}catch(l){const c=l instanceof Error?`: ${l.message}`:"";throw new m(`Failed to create output directory${c}`)}const o=await ge({cubeDirectory:n}),s=te(o);s&&!e.force&&!e.silent&&(v.warn(s),await ne("Continue?",!1));const r=await ke(n);await B(n,t),e.silent||(se({generated:r}),_(w.greenBright("Success! CUBE CSS added successfully. \u2728")))}catch(t){G(t)}});async function pt(e,t,n){const o=[],{output:s}=await H(e,t,n);await q(s),o.push(...s);const{output:r}=ce(t,n);await le(r),o.push(...r);const l=await B(n.output?.css||V,n);return o.push({path:l,css:Ue}),o}a(pt,"generateAllCSS");const ft=new j().name("generate").description("Generate CSS from your design tokens").option("--force","Skip overwrite confirmation").option("-s, --silent","Suppress logs and prompts").action(async e=>{try{if(!k(b))throw new m(ee.PROJECT_REQUIRED);const{config:t}=await L(),{trees:n,resolved:o}=await O(t),s=await pt(n,o,t);e.silent||(se({generated:s.map(r=>r.path)}),_(w.greenBright("Success! CSS generated successfully. \u2728")))}catch(t){G(t)}}),mt=["**/node_modules/**",".next","public","dist","build",".astro",".nuxt",".output",".svelte-kit"],R={"next-app":{tokensDir:"app/design-tokens",stylesDir:"app/styles",componentDir:"app/components"},"next-pages":{tokensDir:"src/design-tokens",stylesDir:"src/styles",componentDir:"src/components"},astro:{tokensDir:"src/design-tokens",stylesDir:"src/styles",componentDir:"src/components"},vite:{tokensDir:"src/design-tokens",stylesDir:"src/styles",componentDir:"src/components"},nuxt:{tokensDir:"assets/design-tokens",stylesDir:"assets/styles",componentDir:"components"},sveltekit:{tokensDir:"src/lib/design-tokens",stylesDir:"src/lib/styles",componentDir:"src/lib/components"},remix:{tokensDir:"app/design-tokens",stylesDir:"app/styles",componentDir:"app/components"},eleventy:{tokensDir:"src/_data/design-tokens",stylesDir:"src/styles",componentDir:"src/components"},none:{tokensDir:"src/design-tokens",stylesDir:"src/styles",componentDir:"src/components"}};function dt(e){try{const t=I.resolve(e,"package.json");if(!k(t))return null;const n=re(t,"utf-8");return JSON.parse(n)}catch{return null}}a(dt,"getPackageJson");function gt(e){const t=["design-tokens","tokens","src/design-tokens","src/tokens","app/design-tokens","assets/design-tokens","src/_data/design-tokens","src/lib/design-tokens"],n=["styles","css","src/styles","src/css","app/styles","assets/styles","assets/css","src/lib/styles"],o=["components","src/components","app/components","src/lib/components"],s={};for(const r of t)if(k(I.resolve(e,r))){s.existingTokensDir=r;break}for(const r of n)if(k(I.resolve(e,r))){s.existingStylesDir=r;break}for(const r of o)if(k(I.resolve(e,r))){s.existingComponentsDir=r;break}return s}a(gt,"findExistingPaths");async function wt(e){const[t,n,o]=await Promise.all([Q.glob("**/{next,vite,astro,nuxt,svelte,remix,eleventy}.config.*|.eleventy.js",{cwd:e,deep:3,ignore:mt}),k(I.resolve(e,"src")),dt(e)]),s=gt(e),r=k(I.resolve(e,`${n?"src/":""}app`)),l={framework:"none",isSrcDir:n,tokensDir:"src/design-tokens",stylesDir:"src/styles",componentDir:"src/components"};if(t.find(i=>i.startsWith("next.config."))?.length){const i=r?"next-app":"next-pages",p=R[i];return{...l,framework:i,tokensDir:s.existingTokensDir||p.tokensDir,stylesDir:s.existingStylesDir||p.stylesDir,componentDir:s.existingComponentsDir||p.componentDir}}if(t.find(i=>i.startsWith("astro.config."))?.length){const i=R.astro;return{...l,framework:"astro",tokensDir:s.existingTokensDir||i.tokensDir,stylesDir:s.existingStylesDir||i.stylesDir,componentDir:s.existingComponentsDir||i.componentDir}}if(t.find(i=>i.startsWith("nuxt.config."))?.length){const i=R.nuxt;return{...l,framework:"nuxt",tokensDir:s.existingTokensDir||i.tokensDir,stylesDir:s.existingStylesDir||i.stylesDir,componentDir:s.existingComponentsDir||i.componentDir}}if(t.find(i=>i.startsWith("svelte.config."))?.length){const i=R.sveltekit;return{...l,framework:"sveltekit",tokensDir:s.existingTokensDir||i.tokensDir,stylesDir:s.existingStylesDir||i.stylesDir,componentDir:s.existingComponentsDir||i.componentDir}}if(Object.keys(o?.dependencies??{}).find(i=>i.startsWith("@remix-run/"))){const i=R.remix;return{...l,framework:"remix",tokensDir:s.existingTokensDir||i.tokensDir,stylesDir:s.existingStylesDir||i.stylesDir,componentDir:s.existingComponentsDir||i.componentDir}}if(t.find(i=>i.startsWith("eleventy.config.")||i.startsWith(".eleventy."))?.length){const i=R.eleventy;return{...l,framework:"eleventy",tokensDir:s.existingTokensDir||i.tokensDir,stylesDir:s.existingStylesDir||i.stylesDir,componentDir:s.existingComponentsDir||i.componentDir}}if(t.find(i=>i.startsWith("vite.config."))?.length){const i=R.vite;return{...l,framework:"vite",tokensDir:s.existingTokensDir||i.tokensDir,stylesDir:s.existingStylesDir||i.stylesDir,componentDir:s.existingComponentsDir||i.componentDir}}const c=R.none;return{...l,framework:"none",tokensDir:s.existingTokensDir||(n?c.tokensDir:"design-tokens"),stylesDir:s.existingStylesDir||(n?c.stylesDir:"styles"),componentDir:s.existingComponentsDir||(n?c.componentDir:"components")}}a(wt,"getProjectInfo");function ht(e){return{"next-app":"Next.js (App Router)","next-pages":"Next.js (Pages Router)",astro:"Astro",vite:"Vite",nuxt:"Nuxt",sveltekit:"SvelteKit",remix:"Remix",eleventy:"11ty (Eleventy)",none:"No framework"}[e]}a(ht,"getFrameworkDisplayName");async function yt(){k(b)&&(v.error(`A ${w.cyan(b)} file already exists at ${w.cyan(process.cwd())}.
To start over, remove the ${w.cyan(b)} file and run ${w.cyan("init")} again.
`),process.exit(1))}a(yt,"preflightInit");function kt(e,t=process.cwd()){try{const n=Je(t,"package.json");if(!k(n))return!1;const o=re(n,"utf-8"),s=JSON.parse(o),r={...s.dependencies,...s.devDependencies,...s.peerDependencies};return e in r}catch{return!1}}a(kt,"isPackageInstalled");const Dt=a(async(e,t,n)=>{const o=await Z({type:"tokens",name:e});if(!o.files[0])throw new m(`Starter kit '${e}' does not contain any token files`);const s=F(process.cwd(),t),r=o.files.find(u=>U(u.path)==="config.json");let l={};if(r)try{l=JSON.parse(r.content)}catch(u){et(`Could not parse config.json for starter kit '${e}':`,u)}const c=o.files.filter(u=>U(u.path)!=="config.json").map(u=>({path:h(s,U(u.path)),content:u.content})),i=Object.fromEntries(c.map(u=>[U(u.path),E(process.cwd(),u.path)])),p={};if(l.themes)for(const[u,W]of Object.entries(l.themes))p[u]=W.map(x=>{const P=i[x];if(!P)throw new m(`Theme file '${x}' not found in starter kit '${e}'`);return P});const g={...n};Object.keys(p).length>0&&typeof g.tokens=="object"&&!Array.isArray(g.tokens)&&"source"in g.tokens&&(g.tokens.themes=p);const y=Le(g),{trees:S,resolved:f}=await O(y,Object.fromEntries(c.map(u=>[u.path,{collection:"default",content:u.content}])));return{config:y,trees:S,resolved:f,tokensDir:s,tokenFiles:c,createdTokenPaths:c.map(u=>E(process.cwd(),u.path))}},"initializeFromStarterKit"),St="https://sugarcube.sh/r/schema.json",oe="tailwind-fluid",De="**/*.json",Se="@sugarcube-org/vite",ve="@sugarcube-org/postcss",vt=["node_modules",".git",".next","dist","build"],Ct=/^[a-zA-Z][a-zA-Z0-9_-]*$/,Et=5,T={LAUNCH_INITIATED:"Launch sequence initiated.",PREFLIGHT_COMPLETE:"\u2714 Preflight checks",PROJECT_DETECTED:a(e=>`\u2714 Project detected (${e})`,"PROJECT_DETECTED"),TOKENS_INSTALLED:"\u2714 Design tokens installed",TOKENS_CONFIGURED:"\u2714 Design tokens configured",CUBE_INSTALLED:"\u2714 CUBE CSS installed",CSS_VARS_GENERATED:"\u2714 CSS variables generated",UTILITIES_GENERATED:"\u2714 Utility classes generated",DEPS_INSTALLED:"\u2714 Dependencies installed",CONFIG_WRITTEN:a(e=>`\u2714 Config file written (${e})`,"CONFIG_WRITTEN"),SUGARCUBE_ENTRY_CREATED:a(e=>`\u2714 Sugarcube entry point created (${e})`,"SUGARCUBE_ENTRY_CREATED"),SUCCESS:"Success! Project initialized \u2728",CONTINUING_WITHOUT_PLUGIN:"\u2714 Continuing without plugin..."};async function bt(e){if(e.setupResult=await Dt(e.starterKit??"",e.tokensDir,e.config),e.setupResult.tokenFiles)for(const t of e.setupResult.tokenFiles)try{await J.writeFile(t.path,t.content),e.createdFiles.push(t.path)}catch(n){throw new X("Failed to write token file",t.path,"writeFile",n instanceof Error?n:void 0)}}a(bt,"downloadAndWriteTokenFiles");async function Tt(e){await bt(e),v.success(T.TOKENS_INSTALLED)}a(Tt,"installFromStarterKit");async function It(e){try{await J.mkdir(e.tokensDir,{recursive:!0}),e.createdDirectories.push(e.tokensDir),await J.mkdir(e.stylesDir,{recursive:!0}),e.createdDirectories.push(e.stylesDir)}catch(t){throw new X("Failed to create directories",e.tokensDir,"mkdir",t instanceof Error?t:void 0)}}a(It,"createProjectDirectories");async function Ft(e,t,n,o,s){return{options:e,...t,config:n,minimalUserConfig:o,...s,createdFiles:[],createdDirectories:[],installedDependencies:[]}}a(Ft,"buildInitContext");function Ce(e,t){if(!e||e.trim()==="")throw new z(`${t} cannot be empty`,t,e,'Provide a valid directory path like "src/styles"');const n=Fe(e);if(n.includes("../")||n.startsWith("/"))throw new z(`${t} should be a relative path within your project`,t,e,`Use a relative path like "src/styles" instead of "${e}"`);const o=n.split("/")[0];if(o&&vt.includes(o))throw new z(`${t} cannot be in a reserved directory`,t,e,`Avoid using ${o}. Try "src/styles" or "assets/styles" instead`)}a(Ce,"validateDirectoryPath");function $t(e){if(e.tokensDir&&Ce(e.tokensDir,"tokens-dir"),e.stylesDir&&Ce(e.stylesDir,"styles-dir"),e.kit&&!Ct.test(e.kit))throw new z("Kit name contains invalid characters","kit",e.kit,'Use only letters, numbers, hyphens, and underscores. Try "tailwind-fluid" or "tailwind-static"')}a($t,"validateOptions");function Ee(e,t=0){if(typeof e!="object"||e===null||t>Et)return!1;for(const[n,o]of Object.entries(e))if(!n.startsWith("$")&&typeof o=="object"&&o!==null&&("$value"in o||Ee(o,t+1)))return!0;return!1}a(Ee,"hasAnyToken");async function Rt(e){try{const t=await Q(De,{cwd:e,absolute:!0});for(const n of t)try{const o=await J.readFile(n,"utf-8"),s=JSON.parse(o);if(Ee(s))return!0}catch{}return!1}catch{return!1}}a(Rt,"detectExistingTokens");function xt(e){return{source:[h(E(process.cwd(),e.actualTokensDir),De)]}}a(xt,"createSingleTokenCollection");async function Pt(e){return xt(e)}a(Pt,"buildTokensConfig");async function jt(e){const t=await Pt(e),n=E(process.cwd(),e.actualStylesDir),o=E(process.cwd(),e.actualTokensDir),s={$schema:St,tokens:t},r={};return n!==ue.output.css&&(r.css=n),o!==ue.output.tokens&&(r.tokens=o),Object.keys(r).length>0&&(s.output=r),s}a(jt,"generateMinimalConfig");function _t(e){return e==="next-app"||e==="next-pages"?ve:Se}a(_t,"selectPluginForFramework");async function Nt(e){await yt();const t=await wt(process.cwd());v.success(T.PREFLIGHT_COMPLETE),v.success(T.PROJECT_DETECTED(ht(t.framework)));const n=e.tokensDir||t.tokensDir,o=e.stylesDir||t.stylesDir,s=await Rt(n);return{projectInfo:t,tokensDir:n,stylesDir:o,hasExistingTokens:s}}a(Nt,"initializeProjectContext");function At(e,t,n){let o=null;n||(o=e.kit||oe);let s=null;e.plugin==="auto"||!e.plugin?s=_t(t.framework):e.plugin==="vite"?s=Se:e.plugin==="postcss"&&(s=ve);const r=e.cube!==!1;return{starterKit:o,pluginToInstall:s,installCube:r}}a(At,"determineInstallationTargets");async function Ot(e){if(!e.installCube)return;const t=await ge({cubeDirectory:e.stylesDir}),n=te(t);n&&(v.warn(n),await ne("Continue and overwrite existing files?",!1))}a(Ot,"checkConflicts");async function Ut(e){const{trees:t,resolved:n}=await O(e.config);e.setupResult={config:e.config,createdTokenPaths:[],trees:t,resolved:n,tokenFiles:[],tokensDir:e.tokensDir},v.success(T.TOKENS_CONFIGURED)}a(Ut,"processExistingTokens");async function Lt(e){!e.hasExistingTokens&&e.starterKit?await Tt(e):await Ut(e)}a(Lt,"setupDesignTokens");async function Gt(e){e.installCube&&(await ke(e.stylesDir),v.success(T.CUBE_INSTALLED))}a(Gt,"installCubeFiles");async function Wt(e){if(!e.setupResult)throw new m("Initialization incomplete. Please re-run the `init` command.");const{trees:t,resolved:n}=e.setupResult,{output:o}=await H(t,n,e.config);await q(o);const{output:s}=ce(n,e.config);await le(s),await B(e.stylesDir,e.config)}a(Wt,"writeStyles");async function Mt(e){if(!e.pluginToInstall)return;if(kt(e.pluginToInstall)){v.success(T.DEPS_INSTALLED);return}const t=K();t.start(`Installing ${e.pluginToInstall.split("/")[1]} plugin...`);try{await we([e.pluginToInstall],process.cwd()),e.installedDependencies.push(e.pluginToInstall),t.stop(T.DEPS_INSTALLED)}catch{t.stop(""),v.warn(w.yellow(`Failed to install ${e.pluginToInstall}. Install manually: npm install ${e.pluginToInstall}`)),v.info(T.CONTINUING_WITHOUT_PLUGIN)}}a(Mt,"installPlugins");async function Bt(e){try{await ye(e.minimalUserConfig),e.createdFiles.push(b),v.success(T.CONFIG_WRITTEN(b))}catch(t){throw new X("Failed to write configuration file",b,"writeFile",t instanceof Error?t:void 0)}_(w.greenBright(T.SUCCESS))}a(Bt,"finalize");const Jt=new j().name("init").description("Initialize a new sugarcube project").option("--kit <kit>",`Starter kit to use (default: ${oe})`,oe).option("--tokens-dir <dir>","Design tokens directory (e.g., 'src/design-tokens')").option("--styles-dir <dir>","Styles output directory (e.g., 'src/styles')").option("--plugin <type>","Plugin to install: vite, postcss, none, or auto (default: auto)","auto").option("--no-cube","Don't install CUBE CSS").action(async e=>{let t;try{$t(e);const n=await Nt(e),o=await jt({actualTokensDir:n.tokensDir,actualStylesDir:n.stylesDir}),r={...Ge(o),$schema:o.$schema},l=o,c=At(e,n.projectInfo,n.hasExistingTokens);t=await Ft(e,n,r,l,c),await Ot(t),await It(t),await Lt(t),await Gt(t),await Mt(t),await Wt(t),await Bt(t)}catch(n){G(n)}}),zt=new j().name("validate").description("Validate design token files").argument("[paths...]","Token files or directories to validate (e.g., src/design-tokens)").action(async e=>{try{if(ie(w.inverse(" Validate design tokens ")),k(b)&&!e.length){const{config:o}=await L();await O(o),_(w.greenBright("All tokens are valid \u2728"));return}if(!e.length)throw new m("No paths specified. Please provide files/directories to validate, or run this in a sugarcube project directory.");for(const o of e)if(!k(o))throw new m(`Path not found: ${o}
Please check that the specified files or directories exist`);const t=await Q(e.map(o=>o.endsWith(".json")?o:h(o,"**/*.json")));if(t.length===0)throw new m(`No JSON files found in the specified paths
Please ensure the paths contain .json files`);const n=k(b)?(await L()).config:{tokens:{source:[]},output:{tokens:".",css:".",separate:!1}};await O({...n,tokens:{source:t.map(o=>E(process.cwd(),o))}}),_(w.greenBright("All tokens are valid \u2728"))}catch(t){G(t)}});process.on("SIGINT",()=>process.exit(0)),process.on("SIGTERM",()=>process.exit(0));async function Kt(){const e=new j().name("sugarcube").description("CLI for scaffolding sugarcube projects"),t=process.argv[2];(!t||!["init","generate","validate","components","cube"].includes(t))&&e.version(Ke.version,"-v, --version","display the version number"),e.addCommand(Jt).addCommand(ft).addCommand(zt).addCommand(lt).addCommand(ut),e.parse()}a(Kt,"main"),Kt();