@sugarcube-org/cli
Version:
A CLI for scaffolding sugarcube applications
59 lines (52 loc) • 33.8 kB
JavaScript
var Ne=Object.defineProperty;var p=(e,n)=>Ne(e,"name",{value:n,configurable:!0});import{Command as z}from"commander";import{existsSync as j}from"node:fs";import i from"node:path";import{select as H,isCancel as C,log as b,multiselect as X,text as U,confirm as W,spinner as oe,intro as V,cancel as Z,outro as L,note as ie}from"@clack/prompts";import{manageCSSIndex as Re,loadConfig as _,generationPipeline as Pe,getTokenPathsFromConfig as De,writeCSSFilesToDisk as Oe,validationPipeline as Ae,validateConfig as me}from"@sugarcube-org/core";import f from"picocolors";import Me from"node-fetch";import{z as y}from"zod";import re,{mkdir as A,writeFile as q,readFile as de}from"node:fs/promises";import{execa as Ue}from"execa";import{detect as We}from"@antfu/ni";import ge from"fast-glob";var Be="0.0.0-alpha.15",ze={version:Be};class g extends Error{static{p(this,"CLIError")}constructor(n,t){super(n),this.name="CLIError",this.cause=t}}const ae=y.enum(["react","astro-native","astro-enhanced","web-components"]),we=y.object({path:y.string(),type:y.string()}),Le=we.extend({framework:ae});y.object({themes:y.record(y.string(),y.array(y.string())).optional()}).strict();const Ge=y.object({name:y.string(),type:y.string(),description:y.string().optional(),frameworks:y.array(y.string()).optional(),files:y.array(y.union([Le,we])),tokens:y.record(y.object({type:y.string(),mapping:y.string()})).optional(),dependencies:y.record(ae,y.array(y.string())).optional(),registryDependencies:y.record(ae,y.array(y.string())).optional(),tokenDependencies:y.array(y.string()).optional()}),Je=y.array(Ge),Ve=y.object({content:y.string()}),E={error:f.red,warn:f.yellow,info:f.cyan,success:f.green,bold:f.bold,path:f.cyan},he=process.env.REGISTRY_URL??"https://sugarcube.sh/r";async function ye(e){try{const n=await Me(e);if(!n.ok){if(n.status===401)throw new g(`Registry access denied: Authentication required
URL: ${E.info(e)}`);if(n.status===403)throw new g(`Registry access denied: Invalid or missing token
URL: ${E.info(e)}`);if(n.status===404)throw new g(`Registry resource not found
URL: ${E.info(e)}`);const t=await n.json().catch(()=>null),o=t&&typeof t=="object"&&"error"in t?t.error:n.statusText;throw new g(`Registry request failed: ${o}
URL: ${E.info(e)}`)}return n.json()}catch(n){throw n instanceof g?n:new g(`Failed to connect to registry
URL: ${E.info(e)}
Check your internet connection`)}}p(ye,"fetchRegistry");async function O(){const e=`${he}/index.json`,n=await ye(e);try{return Je.parse(n)}catch{throw new g(`Invalid registry data received
URL: ${E.info(e)}`)}}p(O,"getRegistryIndex");async function ee({type:e,name:n,framework:t}){const o=await O(),s=o.find(c=>c.type===e&&c.name===n);if(!s){const c=o.filter(u=>u.type===e).map(u=>u.name);throw new g(`${e} '${E.info(n)}' not found in registry
Available ${e}s: ${c.join(", ")}`)}let a=s.files;e==="component"&&t&&(a=s.files.filter(c=>"framework"in c&&c.framework===t));const r=await Promise.all(a.map(async c=>{const u=`${he}/${c.path}.json`,m=await ye(u);try{const S=Ve.parse(m);return{path:c.path,type:c.type,framework:"framework"in c?c.framework:void 0,content:S.content}}catch{throw new g(`Invalid file content received
File: ${E.info(c.path)}`)}}));return{item:s,files:r.filter(Boolean)}}p(ee,"getRegistryFiles");async function ke(e,n,t){const o=[],s=new Set;async function a(r){if(s.has(r))return;s.add(r);const c=e.find(m=>m.name===r);if(!c){const m=e.filter(S=>S.type==="component").map(S=>S.name).join(", ");throw new g(`Component '${r}' not found in registry
Available components: ${m}`)}const u=c.registryDependencies?.[t]||[];for(const m of u)await a(m);o.push(c)}p(a,"resolveComponent");for(const r of n)await a(r);return o}p(ke,"resolveTree");async function Se({config:e,cssOutputDirectory:n}){const t={designTokenCSS:[],componentFiles:[],componentCSS:[],cubeCSS:[],indexFiles:[]};if(e.tokens.type==="starter-kit"&&Array.isArray(e.tokens.source))if(e.output.css.separate){const o=e.tokens.source.map(s=>{const a=i.basename(s,".json");return i.relative(process.cwd(),i.join(n,"global/variables",`${a}.variables.css`))});t.designTokenCSS=o.filter(s=>j(i.join(process.cwd(),s)))}else{const o=i.relative(process.cwd(),i.join(n,"global/variables/tokens.variables.css"));j(i.join(process.cwd(),o))&&(t.designTokenCSS=[o])}else{const o="source"in e.tokens?[["default",e.tokens]]:Object.entries(e.tokens);if(e.output.css.separate){const s=o.flatMap(([a,r])=>r.source.map(c=>{const u=i.basename(c,".json");return i.relative(process.cwd(),i.join(n,"global/variables",`${u}.variables.css`))}));t.designTokenCSS=s.filter(a=>j(i.join(process.cwd(),a)))}else{const s=o.map(([a])=>i.relative(process.cwd(),i.join(n,"global/variables",a,"tokens.variables.css")));t.designTokenCSS=s.filter(a=>j(i.join(process.cwd(),a)))}}if(!e.output.css.separate&&e.tokens.type!=="starter-kit"){const o=i.relative(process.cwd(),i.join(n,"global/variables/tokens.variables.css"));j(i.join(process.cwd(),o))&&t.designTokenCSS.push(o)}if(e.output.css.manageIndex){const o=[i.relative(process.cwd(),i.join(n,"index.css")),i.relative(process.cwd(),i.join(n,"index.dark.css"))];t.indexFiles=o.filter(s=>j(i.join(process.cwd(),s)))}return t}p(Se,"collectTokenCSSOverwriteWarnings");async function be({cssOutputDirectory:e,selectedComponents:n,componentType:t,componentsOutputDirectory:o}){const s={designTokenCSS:[],componentFiles:[],componentCSS:[],cubeCSS:[],indexFiles:[]},a=await O();if(!a)throw new Error("Failed to fetch registry index");const c=(await ke(a,n,t)).map(w=>w.name),u=a.filter(w=>w.type==="component").filter(w=>c.includes(w.name)),m=u.flatMap(w=>w.files.filter(k=>(k.type==="tsx"||k.type==="astro"||k.type==="njk")&&"framework"in k&&k.framework===t).map(k=>i.relative(process.cwd(),i.join(o,w.name,`${w.name}.${k.type}`))));s.componentFiles=m.filter(w=>j(i.join(process.cwd(),w)));const S=u.flatMap(w=>{const k=w.name,x=i.relative(process.cwd(),i.join(o,w.name,`${k}.css`)),l=i.relative(process.cwd(),i.join(e,"global/variables",`${k}.variables.css`));return[x,l]});return s.componentCSS=S.filter(w=>j(i.join(process.cwd(),w))),s}p(be,"collectComponentOverwriteWarnings");async function ve({cssOutputDirectory:e}){const n={designTokenCSS:[],componentFiles:[],componentCSS:[],cubeCSS:[],indexFiles:[]},t=await O();if(!t)throw new Error("Failed to fetch registry index");const o=t.filter(s=>s.type==="cube").flatMap(s=>s.files).map(s=>{const a=s.path.replace(/^styles\//,"");return i.relative(process.cwd(),i.join(e,a))});return n.cubeCSS=o.filter(s=>j(i.join(process.cwd(),s))),n}p(ve,"collectCubeOverwriteWarnings");function _e(...e){return e.reduce((n,t)=>({designTokenCSS:[...n.designTokenCSS,...t.designTokenCSS],componentFiles:[...n.componentFiles,...t.componentFiles],componentCSS:[...n.componentCSS,...t.componentCSS],cubeCSS:[...n.cubeCSS,...t.cubeCSS],indexFiles:[...n.indexFiles,...t.indexFiles]}))}p(_e,"mergeWarnings");const ce={PROJECT_REQUIRED:`This command requires you to initialize a sugarcube project. Run ${f.cyan("make-sugarcube init")} first.
For more information, visit: ${f.cyan("https://sugarcube.sh/docs/initialize")}`};function te(e){const n=[];if(e.designTokenCSS.length>0&&n.push(`Design token CSS files:
${e.designTokenCSS.map(t=>` - ${t}`).join(`
`)}`),e.cubeCSS.length>0&&n.push(`CUBE CSS files:
${e.cubeCSS.map(t=>` - ${t}`).join(`
`)}`),e.componentFiles.length>0||e.componentCSS.length>0){const t=[...e.componentFiles,...e.componentCSS];n.push(`Component files:
${t.map(o=>` - ${o}`).join(`
`)}`)}if(e.indexFiles.length>0&&n.push(`Index files:
${e.indexFiles.map(t=>` - ${t}`).join(`
`)}`),n.length!==0)return f.yellow(`WARNING: The following file(s) already exist and will be OVERWRITTEN:
${f.dim(n.join(`
`))}`)}p(te,"formatOverwriteWarnings");const $={error(...e){console.log(E.error(e.join(" ")))},warn(...e){console.log(E.warn(e.join(" ")))},info(...e){console.log(E.info(e.join(" ")))},success(...e){console.log(E.success(e.join(" ")))},log(...e){console.log(e.join(" "))},break(){console.log("")}};function K(e){if($.break(),e instanceof g){const n=e.message.split(`
`);if(n.length>1){$.error(n[0]),$.break();for(const t of n.slice(1))$.error(` ${t.trim()}`)}else $.error(e.message);process.env.DEBUG&&e.cause&&$.info(`
Caused by:`,e.cause)}else $.error(`An unexpected error occurred: ${e instanceof Error?e.message:String(e)}
If this issue persists, please report it: ${f.cyanBright("https://github.com/sugarcube-org/sugarcube/issues")}`),process.env.DEBUG&&$.info(`
Error details:`,e);$.break(),process.exit(0)}p(K,"handleError");async function qe(e,{withFallback:n=!1}={}){const t=await We({programmatic:!0,cwd:e});if(t?.startsWith("yarn@"))return"yarn";if(t?.startsWith("pnpm@"))return"pnpm";if(t==="bun")return"bun";if(!n)return t?.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"}p(qe,"getPackageManager");async function Ke(e,n){const t=await qe(n,{withFallback:!0}),o=t==="npm"?"install":"add";try{await Ue(t,[o,...e],{cwd:n})}catch{const a=`Failed to install dependencies using ${t}.
This might be because you're using 'npx' with a pnpm project. Try:
pnpm dlx @sugarcube-org/cli components
Or use the appropriate package runner for your project:
npm: npx @sugarcube-org/cli components
pnpm: pnpm dlx @sugarcube-org/cli components
yarn: yarn dlx @sugarcube-org/cli components
bun: bunx @sugarcube-org/cli components`;throw new Error(a)}}p(Ke,"installDependencies");async function Ce({registryIndex:e,selectedComponents:n,componentType:t,componentsOutputDirectory:o,cssOutputDirectory:s}){const a=[],r=new Set;await A(o,{recursive:!0});const c=i.join(s,"global","variables");await A(i.join(s,"global"),{recursive:!0}),await A(c,{recursive:!0});const u=await ke(e,n,t);for(const m of u){const S=await ee({type:"component",name:m.name,framework:t});for(const k of S.files)if(k)try{if(k.path.endsWith(".variables.css")){const x=i.join(c,`${m.name}.variables.css`);await q(x,k.content),a.push(x)}else{const x=i.join(o,m.name);await A(x,{recursive:!0});const l=i.join(x,i.basename(k.path));await q(l,k.content),a.push(l)}}catch(x){const l=x instanceof Error?`: ${x.message}`:"";throw new g(`Failed to write component file for "${m.name}"${l}`)}const w=m.dependencies?.[t]||[];for(const k of w)r.add(k)}if(r.size>0)try{await Ke(Array.from(r),process.cwd())}catch(m){const S=m instanceof Error?`: ${m.message}`:"";throw new g(`Failed to install component dependencies${S}`)}return{createdFiles:a,npmDependencies:r}}p(Ce,"installComponents");async function ne({cssOutputDirectory:e,files:n}){try{await Re({cssOutputDirectory:e,files:n})}catch(t){const o=t instanceof Error?`: ${t.message}`:"";throw new g(`Failed to manage CSS index file ${o}`)}}p(ne,"manageCSSIndexCLI");async function xe(e="src/styles"){const n=await U({message:"Save CSS to",placeholder:e,validate:p(t=>{if(t.trim().replace(/['"]/g,"").length===0)return"Output directory cannot be empty"},"validate")});return C(n)&&process.exit(0),i.relative(process.cwd(),i.resolve(process.cwd(),n))}p(xe,"promptCSSOutputDirectory");async function Qe(e="src/design-tokens"){const n=await U({message:"Save tokens to",placeholder:e,validate:p(t=>{if(t.trim().replace(/['"]/g,"").length===0)return"Output directory cannot be empty"},"validate")});return C(n)&&process.exit(0),i.relative(process.cwd(),i.resolve(process.cwd(),n))}p(Qe,"promptTokensDirectory");async function Ye(e){const n=e.filter(r=>r.type==="tokens"&&r.files[0]?.path.includes("starter-kits")),t=n.map(r=>({label:r.name,value:r.name})),o=await H({message:"Which starter kit?",options:t});C(o)&&process.exit(0);const s=n.find(r=>r.name===o);if(!s)throw new Error(`Starter kit '${o}' not found`);const a=s.files.filter(r=>i.basename(r.path)!=="config.json").map(r=>i.basename(r.path));return{name:o,tokenFilePaths:a}}p(Ye,"promptStarterKit");async function Fe(e="src/components"){const n=await U({message:"Save components to",placeholder:e,validate:p(t=>{if(t.trim().replace(/['"]/g,"").length===0)return"Output directory cannot be empty"},"validate")});return C(n)&&process.exit(0),i.relative(process.cwd(),i.resolve(process.cwd(),n))}p(Fe,"promptComponentDirectory");async function le(e=!0){const n=[{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&&n.push({label:"Skip",value:"skip",hint:"continue without components"});const t=await H({message:"Build with",options:n});return C(t)&&process.exit(0),t==="astro-enhanced"||t==="web-components"?(b.info(f.blue("Sorry, this framework is coming soon! Please choose 'Astro (Native)' or 'React' for now.")),le(e)):t}p(le,"promptComponentFramework");async function je(e,n){const t=e.filter(s=>s.type==="component"&&s.frameworks?.includes(n)),o=await X({message:"Select components to add",options:t.map(s=>({label:s.name,value:s.name,hint:s.description})),required:!0});return C(o)&&process.exit(0),o}p(je,"promptComponentSelectionFiltered");async function He(){const n=await H({message:"Set up tokens with",options:[{label:"Starter kit",value:"starter"},{label:"Own tokens",value:"existing"}]});return C(n)&&process.exit(0),n}p(He,"promptTokenSetup");async function Q(e,n=!1){const t=await W({message:e,initialValue:n});return(!t||C(t))&&process.exit(0),!0}p(Q,"confirmOverwrite");async function Xe(){const e=await W({message:"Install CUBE CSS?",active:"Yes",inactive:"No",initialValue:!0});return C(e)&&process.exit(0),e}p(Xe,"promptInstallCube");async function Ze(e="src/design-tokens"){const n=await U({message:"Path to existing tokens",placeholder:e,validate:p(t=>{if(t.trim().replace(/['"]/g,"").length===0)return"Path cannot be empty"},"validate")});return C(n)&&process.exit(0),i.relative(process.cwd(),i.resolve(process.cwd(),n))}p(Ze,"promptExistingTokensPath");async function et(){const e=await W({message:"Do you need to create collections from your token files? (e.g., core, marketing)",initialValue:!1});return C(e)&&process.exit(0),e?"collections":"simple"}p(et,"promptCollectionOrganization");async function $e(e,n=!1){if(e.length===0)return null;const t=await X({message:`Select files for your ${n?"first":"next"} collection (including any theme files)`,options:e.map(o=>({label:o,value:o}))});return C(t)&&process.exit(0),t.length===0?null:t}p($e,"promptFilesForCollection");async function pe(e){const n=e.map(o=>i.dirname(o).split(i.sep)).reduce((o,s)=>o?o.filter((a,r)=>a===s[r]):s).pop(),t=await U({message:"Name this collection:",placeholder:n||"collection"});return C(t)&&process.exit(0),t}p(pe,"promptCollectionName");async function tt(e){if(e.length<=1)return null;const n=await W({message:"Are any of these theme files?",initialValue:!1});if(C(n)&&process.exit(0),!n)return null;const t=[];let o=[...e];for(;o.length>0;){const s=await X({message:t.length===0?"Select files for first theme:":"Select files for another theme (or Enter to finish):",options:o.map(r=>({label:r,value:r}))});if(C(s)&&process.exit(0),s.length===0)break;const a=await U({message:"Name this theme:",placeholder:"light"});if(C(a)&&process.exit(0),t.push({name:a,files:s}),o=o.filter(r=>!s.includes(r)),o.length>0){const r=await W({message:"Create another theme?",initialValue:!0});if(C(r)&&process.exit(0),!r)break}}return t.length>0?t:null}p(tt,"promptIdentifyThemeFiles");async function nt(e){if(e.length<=1)return null;const n=await W({message:"Do you need to create themes from your token files? (e.g. dark, light, high contrast)",initialValue:!1});if(C(n)&&process.exit(0),!n)return null;const t=[];let o=[...e];for(;o.length>0;){const s=await X({message:t.length===0?"Select files for first theme:":"Select files for your next theme:",options:o.map(r=>({label:r,value:r}))});if(C(s)&&process.exit(0),s.length===0)break;const a=await U({message:"Name this theme:",placeholder:"dark"});if(C(a)&&process.exit(0),t.push({name:a,files:s}),o=o.filter(r=>!s.includes(r)),o.length>0){const r=await W({message:"Do you need to create another theme?",initialValue:!1});if(C(r)&&process.exit(0),!r)break}}return t.length>0?t:null}p(nt,"promptIdentifyAllThemeFiles");function se(e){const t=[...e.config?[e.config]:[],...e.tokens||[],...e.generated].map(s=>i.relative(process.cwd(),s)),o=oe();o.start(`Generating ${t.length} files:`),o.stop(`Generated ${t.length} files:`);for(const s of t)$.log(` - ${s}`);$.break()}p(se,"showSummary");async function fe(e){const n={$schema:"https://sugarcube.sh/r/schema.json",...e};try{const t=JSON.stringify(n,null,2);await q("sugarcube.config.json",t)}catch(t){const o=t instanceof Error?`: ${t.message}`:"";throw new g(`Failed to write config file${o}`)}}p(fe,"writeConfig");const st=new z().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, astro-enhanced)").action(async(e,n)=>{try{if(V(f.inverse(" Add components ")),!j("sugarcube.config.json"))throw new g(ce.PROJECT_REQUIRED);const t=await _();let o,s=[],a;if(e.length>0||n.framework){if(!n.framework)throw new g(`Framework is required when specifying components. Use --framework to specify a framework (react, astro-native, astro-enhanced) or run without arguments for interactive mode.
See ${f.cyan("https://docs.sugarcube.sh/cli/components")} for more information.`);if(!["react","astro-native","astro-enhanced","web-components"].includes(n.framework))throw new g("Invalid framework. Must be one of: React, Astro (Native) or Astro (Enhanced).");if(n.framework==="astro-enhanced"||n.framework==="web-components")throw new g("Sorry, this framework is coming soon! Please choose Astro (Native) or React for now.");if(a=n.framework,s=e,!t.output.directories.components)throw new g("Components directory must be configured in non-interactive mode. Please run the 'components' commandwithout arguments first.");o=i.resolve(process.cwd(),t.output.directories.components)}else{a=await le(!1);const u=await O();if(u||(Z("Failed to fetch component list"),process.exit(1)),s=await je(u,a),t.output.directories.components)o=i.resolve(process.cwd(),t.output.directories.components);else{const m=await Fe();o=i.resolve(process.cwd(),m),t.output.directories.components=i.relative(process.cwd(),o),await fe(t)}}const r=await be({cssOutputDirectory:i.resolve(process.cwd(),t.output.directories.css),selectedComponents:s,componentType:a,componentsOutputDirectory:o});if(r.componentFiles.length>0){const u=te(r);u&&(b.warn(u),await Q("Continue?",!1))}const c=oe();c.start("Installing components...");try{const u=await O(),{createdFiles:m,npmDependencies:S}=await Ce({registryIndex:u,selectedComponents:s,componentType:a,componentsOutputDirectory:o,cssOutputDirectory:t.output.directories.css});t.output.css.manageIndex&&await ne({cssOutputDirectory:t.output.directories.css,files:m}),c.stop("Components installed successfully"),se({generated:m}),L(f.greenBright("Success! Components installed \u2728"))}catch(u){throw c.stop("Installation failed"),u}}catch(t){K(t)}}),ot=new z().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=>{const n=[];try{if(!j("sugarcube.config.json"))throw new g(ce.PROJECT_REQUIRED);e.silent||V(f.inverse(" Add CUBE CSS "));const t=await _(),o=i.resolve(process.cwd(),t.output.directories.css);try{await A(o,{recursive:!0})}catch(c){const u=c instanceof Error?`: ${c.message}`:"";throw new g(`Failed to create output directory${u}`)}const s=await ve({cssOutputDirectory:o});if(s.cubeCSS.length>0&&!e.silent&&!e.force){const c=te(s);c&&(b.warn(c),await Q("Continue?",!1))}const r=(await O()).filter(c=>c.type==="cube").map(c=>c.name);for(const c of r){const u=await ee({type:"cube",name:c});for(const m of u.files){if(!m)continue;const S=m.path.replace(/^styles\//,""),w=i.join(o,S),k=i.dirname(w);try{await A(k,{recursive:!0}),await q(w,m.content),n.push(w)}catch(x){const l=x instanceof Error?`: ${x.message}`:"";throw new g(`Failed to write CUBE CSS file ${w}${l}`)}}}t.output.css.manageIndex&&await ne({cssOutputDirectory:o,files:n}),await fe(t),!e.silent&&n.length>0&&(se({generated:n}),L(f.greenBright("Success! CUBE CSS added successfully. \u2728")))}catch(t){K(t)}});async function Te(e,n,t){const{output:o}=await Pe(e,n,t);try{const s=De(t);return await Oe(o,!0,s)}catch(s){throw new g(`Failed to write CSS files: ${s instanceof Error?s.message:"Unknown error"}`)}}p(Te,"generateAndWriteCSSVars");async function G(e,n={type:"files",paths:e}){const{trees:t,resolved:o,errors:s}=await Ae(e,{loader:n});if(s.load.length>0)throw new g(`Failed to load token files:
${s.load.map(a=>`${a.file}: ${a.message}`).join(`
`)}`);if(s.validation.length>0||s.flatten.length>0||s.resolution.length>0){$.break();const a=[...s.flatten,...s.validation,...s.resolution],r=new Map;for(const c of a)if(c.source?.sourcePath){const u=r.get(c.source.sourcePath)||[];u.push(c.message),r.set(c.source.sourcePath,u)}for(const[c,u]of r){$.error(`Error(s) in ${E.path(c)}:`);for(const m of u)$.error(` - ${m}`);$.break()}throw new g(`Token validation failed. See ${E.info("https://docs.sugarcube.sh/w3c-token-format")} for help`)}return{trees:t,resolved:o}}p(G,"validateTokens");const it=new z().name("generate").description("Generate CSS from your design tokens").option("-s, --silent","Suppress logs and prompts").option("-f, --force","Skip overwrite confirmation").action(async e=>{try{if(e.silent||V(f.inverse(" Generate CSS variables from your design tokens ")),!j("sugarcube.config.json"))throw new g(ce.PROJECT_REQUIRED);const n=await _(),{trees:t,resolved:o}=await G(n),s=await Se({config:n,cssOutputDirectory:n.output.directories.css}),a=te(s);a&&!e.force&&!e.silent&&(b.warn(a),await Q("Continue?",!1));const r=await Te(t,o,n);n.output.css.manageIndex&&await ne({cssOutputDirectory:n.output.directories.css,files:r.map(c=>c.path)}),e.silent||(se({generated:[...r.map(c=>c.path),...n.output.css.manageIndex?["index.css"]:[]]}),L(f.greenBright("Success! CSS variables generated successfully. \u2728")))}catch(n){K(n)}}),rt=p(async({tokenFiles:e,tokensOutputDirectory:n})=>{const t=[];for(const o of e){const s=i.join(n,i.basename(o.path));j(s)&&t.push(s)}return t},"checkTokenFileExists"),at=p(async e=>{const n=await Ze(),t=await ge("**/*.json",{cwd:n,absolute:!0});if(t.length===0)throw new g(`No JSON files found in ${n}`);const o=[];for(const l of t)try{const d=await de(l,"utf-8");JSON.parse(d),o.push(l)}catch{b.warn(f.yellow(`Skipping invalid JSON file: ${l}`))}if(o.length===0)throw new g(`No valid JSON files found in ${n}
Ensure files contain valid JSON`);const s=o.map(l=>i.relative(process.cwd(),l)),a=i.resolve(process.cwd(),n),r=await xe();let c=await et();const u=new Map;for(const l of s){const d=i.basename(l),h=u.get(d)||[];u.set(d,[...h,l])}const m=Array.from(u.entries()).filter(([l,d])=>d.length>1).map(([l,d])=>({name:l,files:d}));if(c==="simple"&&m.length>0){const l=m.map(({name:d,files:h})=>{const F=h.map(v=>` - ${v}`).join(`
`);return`
${d} appears in:
${f.dim(F)}`}).join(`
`);b.warn(f.yellow(`Simple organization cannot have duplicate filenames:
${l}`)),b.message(f.cyan("Switching to collections to help organize these files...")),c="collections"}if(c==="simple"){const l={type:"custom",source:s},d=await nt(s);if(d&&d.length>0){const h=d.flatMap(F=>F.files);l.source=s.filter(F=>!h.includes(F)),l.themes=d.reduce((F,v)=>Object.assign(F,{[v.name]:v.files}),{})}e.tokens=l}else{const l={},d=new Set;let h=[...s],F=!0;for(;h.length>0;){let v=await $e(h,F);if(!v){b.message(f.cyan("No files selected, skipping collection creation..."));break}let T=await pe(v);if(!T){b.message(f.cyan("No collection name provided, skipping collection creation..."));break}let B=!1;for(;!B;)try{if(d.has(T)){b.warn(f.yellow(`Collection name "${T}" is already in use.`));const R=await pe(v);if(!R){b.message(f.cyan("No collection name provided, skipping collection creation..."));break}T=R;continue}const I={type:"custom",source:v};l[T]=I;const N=await tt(v);if(N&&N.length>0){const R=N.flatMap(D=>D.files),P=new Set;let M=!1;for(const D of N){if(P.has(D.name)){b.warn(f.yellow(`Theme name "${D.name}" is already used in this collection.`)),b.message(f.cyan("Starting theme selection over to avoid conflicts...")),M=!0;break}P.add(D.name)}if(M)continue;I.source=v.filter(D=>!R.includes(D)),I.themes=N.reduce((D,Y)=>Object.assign(D,{[Y.name]:Y.files}),{})}const J=new Map;for(const R of I.source){const P=i.basename(R),M=J.get(P)||[];J.set(P,[...M,R])}const ue=Array.from(J.entries()).filter(([R,P])=>P.length>1).map(([R,P])=>({name:R,files:P}));if(ue.length>0){const R=ue.map(({name:D,files:Y})=>{const Ee=Y.map(Ie=>` - ${Ie}`).join(`
`);return`
${D} appears in:
${f.dim(Ee)}`}).join(`
`);b.warn(f.yellow(`Found duplicate filenames in collection "${T}":
${R}`)),b.message(f.cyan("Please either split these files into different collections or mark some as theme files."));const P=await $e(h,F);if(!P){b.message(f.cyan("No files selected, skipping collection creation..."));break}v=P;const M=await pe(v);if(!M){b.message(f.cyan("No collection name provided, skipping collection creation..."));break}T=M;continue}B=!0,d.add(T)}catch(I){if(I instanceof g){b.error(`${I.message}`),b.message(f.cyan("Let's try again..."));continue}throw I}if(!B){b.message(f.cyan("Skipping collection creation..."));break}h=h.filter(I=>!v.includes(I)),h.length>0&&b.message(f.cyan(`Remaining files to organize: ${h.length}`)),F=!1}h.length>0?b.warn(f.yellow(`Warning: ${h.length} files were not assigned to any collection`)):b.message(f.cyan("Token organization complete")),e.tokens=l}if(e.output={directories:{tokens:i.relative(process.cwd(),a),css:r},css:{separate:!1,manageIndex:!0}},e.options={fluid:{min:320,max:1200}},typeof e.tokens=="object"&&!Array.isArray(e.tokens)){const l=e.tokens,d=new Set;for(const[h,F]of Object.entries(l)){if(d.has(h))throw new g(`Configuration Error: Duplicate collection name "${h}". Collection names must be unique.`);if(d.add(h),F.themes){const v=new Set;for(const T of Object.keys(F.themes)){if(v.has(T))throw new g(`Configuration Error: Duplicate theme name "${T}" in collection "${h}". Theme names must be unique within a collection.`);v.add(T)}}}}const S=await Promise.all(s.map(async l=>({path:i.resolve(process.cwd(),l),content:await de(i.resolve(process.cwd(),l),"utf-8")}))),w=me(e),{trees:k,resolved:x}=await G(w);return{config:w,tokens:s,trees:k,resolved:x,tokenPath:n,tokensDir:a,tokenFiles:S}},"initializeFromExistingTokens"),ct=p(async(e,n,t)=>{const o=await ee({type:"tokens",name:e});if(!o.files[0])throw new g(`Starter kit '${e}' does not contain any token files`);const s=i.resolve(process.cwd(),t),a=o.files.find(l=>i.basename(l.path)==="config.json");let r={};if(a)try{r=JSON.parse(a.content)}catch{console.warn(`Warning: Could not parse config.json for starter kit '${e}'`)}const c=o.files.filter(l=>i.basename(l.path)!=="config.json").map(l=>({path:i.join(s,i.basename(l.path)),content:l.content})),u=Object.fromEntries(c.map(l=>[i.basename(l.path),i.relative(process.cwd(),l.path)])),m={};if(r.themes)for(const[l,d]of Object.entries(r.themes))m[l]=d.map(h=>{const F=u[h];if(!F)throw new g(`Theme file '${h}' not found in starter kit '${e}'`);return F});const S={tokens:{source:c.map(l=>i.relative(process.cwd(),l.path)),type:"starter-kit",...Object.keys(m).length>0&&{themes:m}},options:{fluid:{min:320,max:1200}},output:{directories:{tokens:i.relative(process.cwd(),s),css:n},css:{separate:!1,manageIndex:!0}}},w=me(S),{trees:k,resolved:x}=await G(w,{type:"memory",data:Object.fromEntries(c.map(l=>[l.path,{collection:"default",content:l.content}]))});return{config:w,tokens:c.map(l=>i.relative(process.cwd(),l.path)),trees:k,resolved:x,tokenFiles:c,tokensDir:s}},"initializeFromStarterKit");async function lt(e){const n=[],t=i.resolve(process.cwd(),e);await A(t,{recursive:!0});const s=(await O()).filter(a=>a.type==="cube").map(a=>a.name);for(const a of s){const r=await ee({type:"cube",name:a});for(const c of r.files){if(!c)continue;const u=c.path.replace(/^styles\//,""),m=i.join(t,u),S=i.dirname(m);try{await A(S,{recursive:!0}),await q(m,c.content),n.push(m)}catch(w){const k=w instanceof Error?`: ${w.message}`:"";throw new g(`Failed to write CUBE module "${a}"${k}`)}}}return n}p(lt,"installCUBE");async function pt(){if(j("sugarcube.config.json")){const e=await H({message:f.yellow("WARNING: You are about to re-initialize an existing sugarcube project! This will overwrite your existing configuration file. "),options:[{label:"Overwrite existing file (start from scratch)",value:"overwrite"},{label:"Cancel initialization",value:"cancel"}]});return C(e)&&process.exit(0),{shouldProceed:e==="overwrite"}}return{shouldProceed:!0}}p(pt,"preflightInit");const ft=p(()=>{$.break(),V(" Welcome to sugarcube. The toolkit for seriously sweet front ends ")},"welcome"),ut=new z().name("init").description("Initialize a new sugarcube project").action(async()=>{try{ft(),(await pt()).shouldProceed||process.exit(0),ie("Step 1. Set up tokens");const n=await He();let t;switch(n){case"starter":{const d=await O();d||(Z("Failed to fetch starter kit list"),process.exit(1));const{name:h,tokenFilePaths:F}=await Ye(d),v=await Qe(),T=F.map(N=>({path:i.join(v,N)})),B=await rt({tokenFiles:T,tokensOutputDirectory:v});if(B.length>0){const N=B.map(J=>` - ${J}`).join(`
`);b.warn(f.yellow(`WARNING: The following token file(s) already exist in ${v} and will be OVERWRITTEN:
${f.dim(N)}`)),await Q("Continue?",!1)}const I=await xe();try{t=await ct(h,I,v)}catch(N){Z(`Failed to initialize starter kit: ${N instanceof Error?N.message:"Unknown error"}`),process.exit(1)}break}case"existing":t=await at({});break}ie("Step 2. Add style system");const o=await Xe();ie("Step 3. Add components");const s=await le();t.config.output.css.manageIndex=!1;let a,r,c;s!=="skip"&&(a=await Fe(),t.config.output.directories.components=i.relative(process.cwd(),a),c=await O(),c||(Z("Failed to fetch component list"),process.exit(1)),r=await je(c,s));const u=_e(await Se({config:t.config,cssOutputDirectory:t.config.output.directories.css}),r&&s!=="skip"&&a?await be({cssOutputDirectory:t.config.output.directories.css,selectedComponents:r,componentType:s,componentsOutputDirectory:a}):{designTokenCSS:[],componentFiles:[],componentCSS:[],cubeCSS:[],indexFiles:[]},o?await ve({cssOutputDirectory:t.config.output.directories.css}):{designTokenCSS:[],componentFiles:[],componentCSS:[],cubeCSS:[],indexFiles:[]}),m=te(u);m&&(b.warn(m),await Q("Continue?",!1));try{if(!t.tokensDir)throw new g("Tokens directory is not defined");if(await re.mkdir(t.tokensDir,{recursive:!0}),await re.mkdir(t.config.output.directories.css,{recursive:!0}),n==="starter"){if(!t.tokenFiles||t.tokenFiles.length===0)throw new g("Failed to generate token content for starter kit");for(const d of t.tokenFiles)await re.writeFile(d.path,d.content)}}catch(d){const h=d instanceof Error?`: ${d.message}`:"";throw new g(`Failed to create project files${h}`)}let S=[];const{trees:w,resolved:k}=await G(t.config);S=await Te(w,k,t.config);let x=[];o&&(x=await lt(t.config.output.directories.css));let l=[];if(r&&s!=="skip"&&a){const d=oe();d.start("Installing components...");try{const h=await O();if(!h)throw new g("Failed to fetch component list");const{createdFiles:F,npmDependencies:v}=await Ce({registryIndex:h,selectedComponents:r,componentType:s,componentsOutputDirectory:a,cssOutputDirectory:t.config.output.directories.css});l=F,d.stop("Components installed successfully")}catch(h){throw d.stop("Installation failed"),h}}t.config.output.css.manageIndex&&await ne({cssOutputDirectory:t.config.output.directories.css,files:[...S.map(d=>d.path),...l.filter(d=>d.endsWith(".css")),...x]}),await fe(t.config),se({config:"sugarcube.config.json",tokens:n==="starter"?t.tokens:[],generated:[...S.map(d=>d.path),...x,...l,...t.config.output.css.manageIndex?["index.css"]:[]]}),L(f.greenBright("Success! Project initialized \u2728"))}catch(e){K(e)}}),mt=new z().name("validate").description("Validate design token files").argument("[paths...]","Token files or directories to validate (e.g., tokens.json or ./tokens)").action(async e=>{try{if(V(f.inverse(" Validate design tokens ")),j("sugarcube.config.json")&&!e.length){const s=await _();await G(s),L(f.greenBright("All tokens are valid \u2728"));return}if(!e.length)throw new g("No paths specified. Please provide files/directories to validate, or run this in a sugarcube project directory.");const n=e.map(s=>i.normalize(s));for(const s of n)if(!j(s))throw new g(`Path not found: ${s}
Please check that the specified files or directories exist`);const t=await ge(n.map(s=>s.endsWith(".json")?s:i.join(s,"**/*.json")));if(t.length===0)throw new g(`No JSON files found in the specified paths
Please ensure the paths contain .json files`);const o=j("sugarcube.config.json")?await _():{output:{directories:{tokens:".",css:"."},css:{separate:!1}}};await G({...o,tokens:{type:"custom",source:t.map(s=>i.relative(process.cwd(),s))}}),L(f.greenBright("All tokens are valid \u2728"))}catch(n){K(n)}});process.on("SIGINT",()=>process.exit(0)),process.on("SIGTERM",()=>process.exit(0));async function dt(){const e=new z().name("sugarcube").description("CLI for scaffolding sugarcube projects").version(ze.version,"-v, --version","display the version number");e.addCommand(ut).addCommand(it).addCommand(mt).addCommand(st).addCommand(ot),e.parse()}p(dt,"main"),dt();