@stdout-design/cli 0.3.2 → 0.4.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 (2) hide show
  1. package/dist/index.mjs +5 -5
  2. package/package.json +5 -13
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bun
2
- import{createRequire}from"node:module";import{Command}from"commander";import{AppError,ErrorCode,importTemplateForBatch,loadConfig,openCache,runBatch,zodToJsonSchemaShape}from"@stdout-design/core";import path from"node:path";import{execSync,spawnSync}from"node:child_process";import{existsSync,readFileSync}from"node:fs";import{resolveCommand}from"package-manager-detector/commands";import{detect}from"package-manager-detector/detect";import{z}from"zod";import{cp,mkdir,writeFile}from"node:fs/promises";const formatBytes=bytes=>bytes<1024?`${bytes} B`:bytes<1024*1024?`${(bytes/1024).toFixed(1)} KB`:bytes<1024*1024*1024?`${(bytes/(1024*1024)).toFixed(1)} MB`:`${(bytes/(1024*1024*1024)).toFixed(2)} GB`,formatCacheStats=stats=>[``,`Cache Statistics`,`━━━━━━━━━━━━━━━━`,` Location: ${stats.cacheDir}`,` Compiled: ${stats.compiled.entries} entries (in-memory compile cache)`,` Pixel: ${stats.pixels.entries} entries`,` Disk used: ${formatBytes(stats.pixels.sizeBytes)}`,` Disk limit: ${formatBytes(stats.pixels.maxSizeBytes)}`,``].join(`
2
+ import{createRequire}from"node:module";import{Command}from"commander";import{AppError,ErrorCode,importTemplateForBatch,loadConfig,openCache,runBatch,zodToJsonSchemaShape}from"@stdout-design/core";import path from"node:path";import{execSync,spawnSync}from"node:child_process";import{existsSync,readFileSync}from"node:fs";import{resolveCommand}from"package-manager-detector/commands";import{detect}from"package-manager-detector/detect";import{z}from"zod";import{cp,mkdir,writeFile}from"node:fs/promises";const formatBytes=bytes=>bytes<1024?`${bytes} B`:bytes<1048576?`${(bytes/1024).toFixed(1)} KB`:bytes<1073741824?`${(bytes/1048576).toFixed(1)} MB`:`${(bytes/1073741824).toFixed(2)} GB`,formatCacheStats=stats=>[``,`Cache Statistics`,`━━━━━━━━━━━━━━━━`,` Location: ${stats.cacheDir}`,` Compiled: ${stats.compiled.entries} entries (in-memory compile cache)`,` Pixel: ${stats.pixels.entries} entries`,` Disk used: ${formatBytes(stats.pixels.sizeBytes)}`,` Disk limit: ${formatBytes(stats.pixels.maxSizeBytes)}`,``].join(`
3
3
  `),formatManifestSummary=manifest=>{let{completedCount,totalCount,succeeded,failed}=manifest,cacheHits=succeeded.filter(s=>s.cacheHit).length,lines=[``,`Batch Render Complete`,`━━━━━━━━━━━━━━━━━━━━`,` Status: ${manifest.status}`,` Total: ${totalCount}`,` Completed: ${completedCount}`,` Cache: ${cacheHits} hits, ${succeeded.length-cacheHits} renders`,` Failed: ${failed.length}`,``];if(succeeded.length>0&&succeeded.length<=10){lines.push(`Output files:`);for(let entry of succeeded)lines.push(` ${entry.outputPath}`);lines.push(``)}if(failed.length>0){lines.push(`Failures:`);for(let f of failed)lines.push(` Row ${f.rowIndex}, ${f.locale}, ${f.preset}: ${f.error}`);lines.push(``)}return lines.join(`
4
4
  `)},formatError=error=>error instanceof Error?`Error: ${error.message}`:`Error: ${String(error)}`,formatCacheCleanResult=freedBytes=>`Cache cleared. Freed ${formatBytes(freedBytes)}.`,cacheStats=async(rootDir,options)=>{let cache=await openCache(rootDir??process.cwd()),stats=await cache.stats();cache.close(),options.json?console.log(JSON.stringify(stats,null,2)):console.log(formatCacheStats(stats))},cacheClean=async(rootDir,options)=>{let cache=await openCache(rootDir??process.cwd()),result=await cache.clean();cache.close(),options.json?console.log(JSON.stringify(result)):console.log(formatCacheCleanResult(result.freedBytes))},findWebUiDist=()=>{try{let pkgJsonPath=createRequire(import.meta.url).resolve(`@stdout-design/web-ui/package.json`),pkgDir=path.dirname(pkgJsonPath);return path.join(pkgDir,`dist`)}catch{}},dev=async(rootDir,options)=>{let{startStandaloneServer}=await import(`@stdout-design/dev-server`).catch(()=>{throw Error([`studio dev requires a scaffolded project with @stdout-design/dev-server installed.`,"Run `studio init` to create one, or install it manually:",``,` bun add @stdout-design/dev-server`,``,`See https://stdout.design/docs for more information.`].join(`
5
- `))}),resolvedRoot=rootDir??process.cwd(),port=Number(options.port??`3000`),webUiDist=findWebUiDist();console.log(`Starting studio dev server for ${resolvedRoot}...`),await startStandaloneServer({port,rootDir:resolvedRoot,webUiDist})},isInsideGitRepo=dir=>{let current=path.resolve(dir);for(;;){if(existsSync(path.join(current,`.git`)))return!0;let parent=path.dirname(current);if(parent===current)break;current=parent}return!1},tryGitInit=dir=>{try{execSync(`git --version`,{stdio:`ignore`})}catch{return!1}if(existsSync(path.join(dir,`.git`))||isInsideGitRepo(path.dirname(dir)))return!1;try{return execSync(`git init`,{cwd:dir,stdio:`ignore`}),execSync(`git checkout -b main`,{cwd:dir,stdio:`ignore`}),execSync(`git add .`,{cwd:dir,stdio:`ignore`}),execSync(`git commit -m "Initial commit from stdout-design"`,{cwd:dir,stdio:`ignore`}),!0}catch{if(existsSync(path.join(dir,`.git`)))try{execSync(`rm -rf .git`,{cwd:dir,stdio:`ignore`})}catch{}return!1}},projectNameSchema=z.string().min(1,`Project name cannot be empty.`).max(214,`Project name must be 214 characters or fewer.`).regex(/^[a-z0-9][a-z0-9._-]*$/u,`Project name must start with a lowercase letter or number, and can only contain letters, numbers, dots, hyphens, and underscores.`).refine(val=>!/[._-]$/u.test(val),`Project name must not end with a dot, hyphen, or underscore.`).refine(val=>!/__/u.test(val),`Project name must not contain consecutive underscores.`).refine(val=>!/[._-]{2,}/u.test(val),`Project name must not contain consecutive dots, hyphens, or underscores.`),validateProjectName=name=>{let result=projectNameSchema.safeParse(name);return result.success?null:result.error.issues[0]?.message??`Invalid project name.`},isCI=()=>process.env.CI===`true`||process.env.CI===`1`||process.env.CIRCLECI===`true`||!!process.env.GITHUB_ACTIONS||!!process.env.GITLAB_CI,ciOrYes=options=>options?.yes===!0||isCI(),promptOrSkip=async(prompt,clack)=>{let result=await prompt(clack);return clack.isCancel(result)&&(clack.cancel(`Cancelled.`),process.exit(0)),result},collectPrompts=async(clack,projectDir,options)=>{let cwd=process.cwd(),resolvedDir,projectName;projectDir?(resolvedDir=path.resolve(cwd,projectDir),projectName=path.basename(resolvedDir)):ciOrYes(options)?(projectName=`studio-project`,resolvedDir=path.resolve(cwd,projectName)):(projectName=(await promptOrSkip(p=>p.text({defaultValue:`studio-project`,message:`What's your project name?`,validate:value=>{let trimmed=value.trim();return trimmed.length===0?`Please enter a project name.`:validateProjectName(trimmed)}}),clack)).trim(),resolvedDir=path.resolve(cwd,projectName)),!ciOrYes(options)&&existsSync(resolvedDir)&&(await promptOrSkip(p=>p.confirm({initialValue:!1,message:`Directory "${projectName}" already exists. Overwrite?`}),clack)||(clack.cancel(`Cancelled.`),process.exit(0)));let templates=ciOrYes(options)?[`bento-feature`]:await promptOrSkip(p=>p.multiselect({message:`Which templates would you like to scaffold?`,options:[{hint:`Apple-style feature card`,label:`Bento Feature`,value:`bento-feature`}]}),clack),locales=ciOrYes(options)?[`en`,`ar`]:await promptOrSkip(p=>p.multiselect({message:`Which locales would you like to include?`,options:[{label:`English`,value:`en`},{label:`Arabic`,value:`ar`}]}),clack),alreadyInGitRepo=isInsideGitRepo(path.dirname(resolvedDir)),wantsGit=!alreadyInGitRepo&&ciOrYes(options);!alreadyInGitRepo&&!ciOrYes(options)&&(wantsGit=await promptOrSkip(p=>p.confirm({initialValue:!0,message:`Initialize a git repository?`}),clack));let shouldInstall=ciOrYes(options)?!0:await promptOrSkip(p=>p.confirm({initialValue:!0,message:`Install dependencies?`}),clack);return{locales,projectName,resolvedDir,shouldInstall,templates,wantsGit}},getScaffoldVersion=()=>{try{return createRequire(import.meta.url)(`@stdout-design/cli/package.json`).version}catch{return`latest`}},getDefaults=()=>({defaultPreset:`instagram-square`,presets:[{height:1080,id:`instagram-square`,platform:`instagram`,width:1080},{height:1200,id:`x-card`,platform:`x`,width:1200}],templates:{"bento-feature":{componentPath:`./templates/bento-feature`,description:`Apple-style bento feature card.`}}}),getConfigTemplate=version=>`import type { StudioConfig } from "@stdout-design/cli";
5
+ `))}),resolvedRoot=rootDir??process.cwd(),port=Number(options.port??`3030`),webUiDist=findWebUiDist();console.log(`Starting studio dev server for ${resolvedRoot}...`),await startStandaloneServer({port,rootDir:resolvedRoot,webUiDist})},DLX_MAP={bun:`bunx`,npm:`npx`,pnpm:`pnpm dlx`,yarn:`yarn dlx`},buildSkillListCommand=(pm,global=!1)=>{let parts=(DLX_MAP[pm]??`npx`).split(` `);return{args:[...parts.slice(1),`skills`,`list`,...global?[`-g`]:[],`--json`],command:parts[0]}},buildSkillInstallCommand=pm=>{let parts=(DLX_MAP[pm]??`npx`).split(` `);return{args:[...parts.slice(1),`skills`,`add`,`m7md1alaa/stdout-design`],command:parts[0]}},isSkillInstalledInScope=(pm,global=!1)=>{let{command,args}=buildSkillListCommand(pm,global),result=spawnSync(command,args,{encoding:`utf-8`,stdio:`pipe`});if(result.error||result.status!==0||!result.stdout)return!1;let{stdout}=result;try{return JSON.parse(stdout).some(skill=>skill.name===`studio`)}catch{return!1}},hasSkillInstalled=pm=>isSkillInstalledInScope(pm)||isSkillInstalledInScope(pm,!0),getSkillInstallCommand=pm=>{let{command,args}=buildSkillInstallCommand(pm);return`${command} ${args.join(` `)}`},maybeInstallSkill=async({packageManager,quiet=!1,shouldInstall})=>{if(shouldInstall===void 0&&!quiet&&hasSkillInstalled(packageManager))return!0;let wantsInstall=shouldInstall;if(wantsInstall===void 0){if(quiet)return!1;let{select,isCancel}=await import(`@clack/prompts`),installSkillResult=await select({message:`Do you want to install the studio agent skill?`,options:[{label:`Yes, install it`,value:`install`},{label:`No, I'll do it later`,value:`skip`}]});if(isCancel(installSkillResult))return!1;wantsInstall=installSkillResult===`install`}if(!wantsInstall)return!1;let{command,args}=buildSkillInstallCommand(packageManager),{spinner}=await import(`@clack/prompts`),s=spinner();quiet||s.start(`Installing the studio agent skill...`);let result=spawnSync(command,args,{stdio:`pipe`}),didInstall=!result.error&&result.status===0;return quiet||s.stop(didInstall?`Studio agent skill installed.`:`Couldn't install the studio agent skill automatically.`),didInstall},isInsideGitRepo=dir=>{let current=path.resolve(dir);for(;;){if(existsSync(path.join(current,`.git`)))return!0;let parent=path.dirname(current);if(parent===current)break;current=parent}return!1},tryGitInit=dir=>{try{execSync(`git --version`,{stdio:`ignore`})}catch{return!1}if(existsSync(path.join(dir,`.git`))||isInsideGitRepo(path.dirname(dir)))return!1;try{return execSync(`git init`,{cwd:dir,stdio:`ignore`}),execSync(`git checkout -b main`,{cwd:dir,stdio:`ignore`}),execSync(`git add .`,{cwd:dir,stdio:`ignore`}),execSync(`git commit -m "Initial commit from stdout-design"`,{cwd:dir,stdio:`ignore`}),!0}catch{if(existsSync(path.join(dir,`.git`)))try{execSync(`rm -rf .git`,{cwd:dir,stdio:`ignore`})}catch{}return!1}},projectNameSchema=z.string().min(1,`Project name cannot be empty.`).max(214,`Project name must be 214 characters or fewer.`).regex(/^[a-z0-9][a-z0-9._-]*$/u,`Project name must start with a lowercase letter or number, and can only contain letters, numbers, dots, hyphens, and underscores.`).refine(val=>!/[._-]$/u.test(val),`Project name must not end with a dot, hyphen, or underscore.`).refine(val=>!/__/u.test(val),`Project name must not contain consecutive underscores.`).refine(val=>!/[._-]{2,}/u.test(val),`Project name must not contain consecutive dots, hyphens, or underscores.`),validateProjectName=name=>{let result=projectNameSchema.safeParse(name);return result.success?null:result.error.issues[0]?.message??`Invalid project name.`},isCI=()=>process.env.CI===`true`||process.env.CI===`1`||process.env.CIRCLECI===`true`||!!process.env.GITHUB_ACTIONS||!!process.env.GITLAB_CI,ciOrYes=options=>options?.yes===!0||isCI(),promptOrSkip=async(prompt,clack)=>{let result=await prompt(clack);return clack.isCancel(result)&&(clack.cancel(`Cancelled.`),process.exit(0)),result},collectPrompts=async(clack,projectDir,options)=>{let cwd=process.cwd(),resolvedDir,projectName;projectDir?(resolvedDir=path.resolve(cwd,projectDir),projectName=path.basename(resolvedDir)):ciOrYes(options)?(projectName=`studio-project`,resolvedDir=path.resolve(cwd,projectName)):(projectName=(await promptOrSkip(p=>p.text({defaultValue:`studio-project`,message:`What's your project name?`,validate:value=>{let trimmed=value.trim();return trimmed.length===0?`Please enter a project name.`:validateProjectName(trimmed)}}),clack)).trim(),resolvedDir=path.resolve(cwd,projectName)),!ciOrYes(options)&&existsSync(resolvedDir)&&(await promptOrSkip(p=>p.confirm({initialValue:!1,message:`Directory "${projectName}" already exists. Overwrite?`}),clack)||(clack.cancel(`Cancelled.`),process.exit(0)));let templates=ciOrYes(options)?[`bento-feature`]:await promptOrSkip(p=>p.multiselect({message:`Which templates would you like to scaffold?`,options:[{hint:`Apple-style feature card`,label:`Bento Feature`,value:`bento-feature`}]}),clack),locales=ciOrYes(options)?[`en`,`ar`]:await promptOrSkip(p=>p.multiselect({message:`Which locales would you like to include?`,options:[{label:`English`,value:`en`},{label:`Arabic`,value:`ar`}]}),clack),parentDir=path.dirname(resolvedDir),alreadyInGitRepo=isInsideGitRepo(parentDir),wantsGit=!alreadyInGitRepo&&ciOrYes(options);!alreadyInGitRepo&&!ciOrYes(options)&&(wantsGit=await promptOrSkip(p=>p.confirm({initialValue:!0,message:`Initialize a git repository?`}),clack));let shouldInstall=ciOrYes(options)?!0:await promptOrSkip(p=>p.confirm({initialValue:!0,message:`Install dependencies?`}),clack);return{locales,projectName,resolvedDir,shouldInstall,templates,wantsGit}},getScaffoldVersion=()=>{try{return createRequire(import.meta.url)(`@stdout-design/cli/package.json`).version}catch{return`latest`}},getDefaults=()=>({defaultPreset:`instagram-square`,presets:[{height:1080,id:`instagram-square`,platform:`instagram`,width:1080},{height:675,id:`x-card`,platform:`x`,width:1200}],templates:{"bento-feature":{componentPath:`./templates/bento-feature`,description:`Apple-style bento feature card.`}}}),getConfigTemplate=version=>`import type { StudioConfig } from "@stdout-design/cli";
6
6
 
7
7
  const config: StudioConfig = {
8
8
  scaffoldVersion: "${version}",
@@ -10,7 +10,7 @@ const config: StudioConfig = {
10
10
  outDir: "./out",
11
11
  presets: [
12
12
  { height: 1080, id: "instagram-square", platform: "instagram", width: 1080 },
13
- { height: 1200, id: "x-card", platform: "x", width: 1200 },
13
+ { height: 675, id: "x-card", platform: "x", width: 1200 },
14
14
  ],
15
15
  templates: {
16
16
  "bento-feature": {
@@ -21,9 +21,9 @@ const config: StudioConfig = {
21
21
  };
22
22
 
23
23
  export default config;
24
- `,getPkgJson=(name,version)=>({name,private:!0,type:`module`,scripts:{build:`studio build`,dev:`studio dev`,export:`studio export`},dependencies:{"@stdout-design/cli":`^${version}`,"@stdout-design/dev-server":`^${version}`,"@stdout-design/web-ui":`^${version}`,"@types/react":`^19.2.17`,react:`^19.2.7`,"react-dom":`^19.2.7`,zod:`^4.4.3`}}),resolveTemplateDir=()=>{let fromDist=path.resolve(import.meta.dirname,`../template`);return existsSync(path.resolve(fromDist,`tsconfig.json`))?fromDist:path.resolve(import.meta.dirname,`../../../template`)},resolveTemplatesDir=()=>path.resolve(resolveTemplateDir(),`templates`),copyStaticAssets=async dir=>{let base=resolveTemplateDir();await Promise.all([cp(path.resolve(base,`tsconfig.json`),path.resolve(dir,`tsconfig.json`)),cp(path.resolve(base,`gitignore`),path.resolve(dir,`.gitignore`)),cp(path.resolve(base,`types.d.ts`),path.resolve(dir,`types.d.ts`))])},generateConfig=async(dir,version)=>{await writeFile(path.resolve(dir,`studio.config.ts`),getConfigTemplate(version))},generatePackageJson=async(dir,projectName,version)=>{await writeFile(path.resolve(dir,`package.json`),JSON.stringify(getPkgJson(projectName,version),null,2))},copyTemplateFiles=async(dir,templates)=>{if(templates.length===0)return;let dstTemplatesDir=path.resolve(dir,`templates`);await mkdir(dstTemplatesDir,{recursive:!0});let srcTemplatesDir=resolveTemplatesDir();for(let template of templates)if(!existsSync(path.resolve(srcTemplatesDir,`${template}.tsx`)))throw Error(`Template "${template}" not found at templates/${template}.tsx`);await Promise.all(templates.map(template=>cp(path.resolve(srcTemplatesDir,`${template}.tsx`),path.resolve(dstTemplatesDir,`${template}.tsx`))))},createLocaleFiles=async(dir,locales)=>{if(locales.length===0)return;let localesDir=path.resolve(dir,`locales`);await mkdir(localesDir,{recursive:!0});let localeData={ar:{title:`التطبيق المميز`},en:{title:`Featured App`}};await Promise.all(locales.map(locale=>writeFile(path.resolve(localesDir,`${locale}.json`),JSON.stringify(localeData[locale]??{title:`Featured App`},null,2))))},scaffold=async(dir,options)=>{if(options.projectName.trim().length===0)throw Error(`Project name cannot be empty.`);let version=getScaffoldVersion();existsSync(dir)||await mkdir(dir,{recursive:!0}),await copyStaticAssets(dir),await generateConfig(dir,version),await generatePackageJson(dir,options.projectName,version),await copyTemplateFiles(dir,options.templates),await createLocaleFiles(dir,options.locales)},detectPackageManager=async cwd=>{let detected=await detect({cwd});return detected?detected.name:`npm`},runInstall=(dir,pm)=>{let resolved=resolveCommand(pm,`install`,[]);if(!resolved)return;let{command,args}=resolved;spawnSync(command,args,{cwd:dir,stdio:`inherit`})},init=async(projectDir,options)=>{let{intro,outro,spinner}=await import(`@clack/prompts`);intro(`studio init`);let cwd=process.cwd();if(existsSync(path.resolve(cwd,projectDir??`.`,`studio.config.ts`))){console.log(`studio.config.ts already exists — skipping scaffold.`),outro(`Done`);return}let collected=await collectPrompts(await import(`@clack/prompts`),projectDir,options),s=spinner();if(s.start(`Creating project...`),await scaffold(collected.resolvedDir,{git:collected.wantsGit,install:collected.shouldInstall,locales:collected.locales,projectName:collected.projectName,templates:collected.templates}),s.stop(`Project created`),collected.wantsGit){let gitSpinner=spinner();gitSpinner.start(`Initializing git...`);let initialized=tryGitInit(collected.resolvedDir);gitSpinner.stop(initialized?`Git initialized`:`Git repo already exists or git is not available`)}let pm=await detectPackageManager(cwd);if(collected.shouldInstall){let installSpinner=spinner();installSpinner.start(`Installing dependencies...`),runInstall(collected.resolvedDir,pm),installSpinner.stop(`Dependencies installed`)}console.log(``),console.log(` Created project at ${collected.resolvedDir}`),console.log(``),console.log(` Next steps:`),console.log(` cd ${path.relative(cwd,collected.resolvedDir)||`.`}`),console.log(` ${pm} run dev`),console.log(``),outro(`Done`)},lint=_rootDir=>{console.error(`Lint command not yet implemented. Coming in a future release.`),process.exit(1)},coerceValue=(value,type,_items)=>{switch(type){case`number`:{let n=Number(value);return Number.isNaN(n)?value:n}case`boolean`:return value===`true`||value===`1`?!0:value===`false`||value===`0`?!1:value;case`array`:if(value.startsWith(`[`)&&value.endsWith(`]`))try{return JSON.parse(value)}catch{return value.split(`,`).map(s=>s.trim())}return value.split(`,`).map(s=>s.trim());default:return value}},parsePropArgs=(args,schema)=>{let props={},properties=schema.properties??{};for(let arg of args){let eqIndex=arg.indexOf(`=`);if(eqIndex===-1){props[arg]=!0;continue}let key=arg.slice(0,eqIndex),value=arg.slice(eqIndex+1),field=properties[key];props[key]=field?coerceValue(value,field.type,field.items):value}return props},mergeDefaultProps=(schema,props)=>{let properties=schema.properties??{},merged={};for(let[key,field]of Object.entries(properties))field.default!==void 0&&(merged[key]=field.default);return{...merged,...props}},levenshteinDistance=(a,b)=>{let aLen=a.length,bLen=b.length;if(aLen===0)return bLen;if(bLen===0)return aLen;let prev=Array.from({length:aLen+1},(_,j)=>j),curr=Array.from({length:aLen+1}).fill(0);for(let j=1;j<=bLen;j+=1){curr[0]=j;for(let i=1;i<=aLen;i+=1){let cost=b[j-1]===a[i-1]?0:1;curr[i]=Math.min((prev[i]??0)+1,(curr[i-1]??0)+1,(prev[i-1]??0)+cost)}let tmp=prev;prev=curr,curr=tmp}return prev[aLen]??0},suggestClosest=(input,candidates,maxDistance=3)=>{let best,bestDistance=1/0;for(let candidate of candidates){let dist=levenshteinDistance(input.toLowerCase(),candidate.toLowerCase());dist<bestDistance&&dist<=maxDistance&&(bestDistance=dist,best=candidate)}return best},resolveTemplate=(config,templateId)=>{let entry=config.templates[templateId];if(!entry){let candidates=Object.keys(config.templates),suggestion=suggestClosest(templateId,candidates),hint=suggestion?` Did you mean "${suggestion}"?`:``;throw new AppError(ErrorCode.TEMPLATE_NOT_FOUND,`Template "${templateId}" not found in studio.config.ts.${hint}`,{availableTemplates:candidates,templateId})}return{componentPath:entry.componentPath,templateId}},resolveBatchOptions=(config,options)=>{let presetIds=options?.preset?options.preset.split(`,`).map(s=>s.trim()):config.presets.map(p=>p.id),localeIds=options?.locale?options.locale.split(`,`).map(s=>s.trim()):void 0;return{concurrency:Number(options?.concurrency??`4`),dataFile:options?.data,failFast:options?.failFast,locales:localeIds,outDir:options?.outDir??config.outDir??`out`,presets:presetIds}},render=async(templateId,propArgs,options)=>{let rootDir=process.cwd(),config=await loadConfig(rootDir),{module}=await importTemplateForBatch(rootDir,resolveTemplate(config,templateId).componentPath,templateId),schema=zodToJsonSchemaShape(module.propsSchema),mergedProps=mergeDefaultProps(schema,parsePropArgs(propArgs,schema)),batchOpts=resolveBatchOptions(config,options),{manifest}=await runBatch({baseProps:mergedProps,concurrency:batchOpts.concurrency,dataFile:batchOpts.dataFile,failFast:batchOpts.failFast,locales:batchOpts.locales,outDir:batchOpts.outDir,presets:batchOpts.presets,rootDir,rows:batchOpts.dataFile?void 0:[{}],templateId});options.json?console.log(JSON.stringify(manifest,null,2)):console.log(formatManifestSummary(manifest)),manifest.failed.length>0&&process.exit(1)},updatePackageJsonDeps=(contents,currentVersion)=>{let pkg=JSON.parse(contents),deps=pkg.dependencies??={},currentRange=`^${currentVersion}`;return deps[`@stdout-design/cli`]===currentRange?{changed:!1,updated:contents}:(deps[`@stdout-design/cli`]=currentRange,{changed:!0,updated:JSON.stringify(pkg,null,2)})},mergePresets=(existing,defaults,deprecatedPresetIds)=>{let existingIds=new Set(existing.presets.map(p=>p.id)),addedPresets=[],presets=[...existing.presets];for(let defaultPreset of defaults.presets)if(!existingIds.has(defaultPreset.id)){let marked=deprecatedPresetIds.has(defaultPreset.id)?{...defaultPreset,deprecated:!0}:{...defaultPreset};presets.push(marked),addedPresets.push(defaultPreset.id)}let presetsWithDeprecation=presets.map(p=>deprecatedPresetIds.has(p.id)&&!p.deprecated?{...p,deprecated:!0}:p);return{addedPresets,deprecatedPresets:[...new Set(presetsWithDeprecation.filter(p=>p.deprecated).map(p=>p.id))],presets:presetsWithDeprecation}},mergeTemplates=(existing,defaults)=>{let addedTemplates=[],templates={...existing.templates};for(let[key,template]of Object.entries(defaults.templates))key in templates||(templates[key]={...template},addedTemplates.push(key));return{addedTemplates,templates}},resolveDefaultPreset=(presets,existing,defaults)=>{let mergedIds=new Set(presets.map(p=>p.id)),current=existing.defaultPreset;return current&&mergedIds.has(current)?current:defaults.defaultPreset??defaults.presets[0]?.id},mergeConfig=(existing,defaults,currentVersion,deprecatedPresetIds)=>{let oldVersion=existing.scaffoldVersion??null,{presets,addedPresets,deprecatedPresets}=mergePresets(existing,defaults,deprecatedPresetIds),{templates,addedTemplates}=mergeTemplates(existing,defaults);return{config:{...existing,defaultPreset:resolveDefaultPreset(presets,existing,defaults),presets,scaffoldVersion:currentVersion,templates},summary:{addedPresets,addedTemplates,deprecatedPresets,newVersion:currentVersion,oldVersion}}},getLatestVersion=async()=>{try{let response=await fetch(`https://registry.npmjs.org/@stdout-design/cli/latest`);return response.status===200?(await response.json()).version:null}catch{return null}},DEPRECATED_PRESET_IDS=new Set,compareVersions=(a,b)=>{let partsA=a.split(`.`).map(Number),partsB=b.split(`.`).map(Number),length=Math.max(partsA.length,partsB.length);for(let i=0;i<length;i+=1){let diff=(partsA[i]??0)-(partsB[i]??0);if(diff!==0)return diff}return 0},serializeConfig=config=>`import type { StudioConfig } from "@stdout-design/cli";
24
+ `,getPkgJson=(name,version)=>({name,private:!0,type:`module`,scripts:{dev:`studio dev`,render:`studio render`},dependencies:{"@stdout-design/cli":`^${version}`,"@stdout-design/dev-server":`^${version}`,"@stdout-design/web-ui":`^${version}`,"@types/react":`^19.2.17`,react:`^19.2.7`,"react-dom":`^19.2.7`,zod:`^4.4.3`}}),resolveTemplateDir=()=>{let fromDist=path.resolve(import.meta.dirname,`../template`);return existsSync(path.resolve(fromDist,`tsconfig.json`))?fromDist:path.resolve(import.meta.dirname,`../../../template`)},resolveTemplatesDir=()=>path.resolve(resolveTemplateDir(),`templates`),copyStaticAssets=async dir=>{let base=resolveTemplateDir();await Promise.all([cp(path.resolve(base,`tsconfig.json`),path.resolve(dir,`tsconfig.json`)),cp(path.resolve(base,`gitignore`),path.resolve(dir,`.gitignore`)),cp(path.resolve(base,`types.d.ts`),path.resolve(dir,`types.d.ts`))])},generateConfig=async(dir,version)=>{await writeFile(path.resolve(dir,`studio.config.ts`),getConfigTemplate(version))},generatePackageJson=async(dir,projectName,version)=>{await writeFile(path.resolve(dir,`package.json`),JSON.stringify(getPkgJson(projectName,version),null,2))},copyTemplateFiles=async(dir,templates)=>{if(templates.length===0)return;let dstTemplatesDir=path.resolve(dir,`templates`);await mkdir(dstTemplatesDir,{recursive:!0});let srcTemplatesDir=resolveTemplatesDir();for(let template of templates){let src=path.resolve(srcTemplatesDir,`${template}.tsx`);if(!existsSync(src))throw Error(`Template "${template}" not found at templates/${template}.tsx`)}await Promise.all(templates.map(template=>cp(path.resolve(srcTemplatesDir,`${template}.tsx`),path.resolve(dstTemplatesDir,`${template}.tsx`))))},createLocaleFiles=async(dir,locales)=>{if(locales.length===0)return;let localesDir=path.resolve(dir,`locales`);await mkdir(localesDir,{recursive:!0});let localeData={ar:{title:`التطبيق المميز`},en:{title:`Featured App`}};await Promise.all(locales.map(locale=>writeFile(path.resolve(localesDir,`${locale}.json`),JSON.stringify(localeData[locale]??{title:`Featured App`},null,2))))},scaffold=async(dir,options)=>{if(options.projectName.trim().length===0)throw Error(`Project name cannot be empty.`);let version=getScaffoldVersion();existsSync(dir)||await mkdir(dir,{recursive:!0}),await copyStaticAssets(dir),await generateConfig(dir,version),await generatePackageJson(dir,options.projectName,version),await copyTemplateFiles(dir,options.templates),await createLocaleFiles(dir,options.locales)},detectPackageManager=async cwd=>{let detected=await detect({cwd});return detected?detected.name:`npm`},runInstall=(dir,pm)=>{let resolved=resolveCommand(pm,`install`,[]);if(!resolved)return;let{command,args}=resolved;spawnSync(command,args,{cwd:dir,stdio:`inherit`})},init=async(projectDir,options)=>{let{intro,outro,spinner}=await import(`@clack/prompts`);intro(`studio init`);let cwd=process.cwd();if(existsSync(path.resolve(cwd,projectDir??`.`,`studio.config.ts`))){console.log(`studio.config.ts already exists — skipping scaffold.`),outro(`Done`);return}let clack=await import(`@clack/prompts`),collected=await collectPrompts(clack,projectDir,options),s=spinner();if(s.start(`Creating project...`),await scaffold(collected.resolvedDir,{git:collected.wantsGit,install:collected.shouldInstall,locales:collected.locales,projectName:collected.projectName,templates:collected.templates}),s.stop(`Project created`),collected.wantsGit){let gitSpinner=spinner();gitSpinner.start(`Initializing git...`);let initialized=tryGitInit(collected.resolvedDir);gitSpinner.stop(initialized?`Git initialized`:`Git repo already exists or git is not available`)}let pm=await detectPackageManager(cwd);if(collected.shouldInstall){let installSpinner=spinner();installSpinner.start(`Installing dependencies...`),runInstall(collected.resolvedDir,pm),installSpinner.stop(`Dependencies installed`)}await maybeInstallSkill({packageManager:pm,quiet:options?.yes??!1,shouldInstall:options?.installSkill}),console.log(``),console.log(` Created project at ${collected.resolvedDir}`),console.log(``),console.log(` Next steps:`),console.log(` cd ${path.relative(cwd,collected.resolvedDir)||`.`}`),console.log(` ${pm} run dev`),console.log(``),outro(`Done`)},lint=_rootDir=>{console.error(`Lint command not yet implemented. Coming in a future release.`),process.exit(1)},coerceValue=(value,type,_items)=>{switch(type){case`number`:{let n=Number(value);return Number.isNaN(n)?value:n}case`boolean`:return value===`true`||value===`1`?!0:value===`false`||value===`0`?!1:value;case`array`:if(value.startsWith(`[`)&&value.endsWith(`]`))try{return JSON.parse(value)}catch{return value.split(`,`).map(s=>s.trim())}return value.split(`,`).map(s=>s.trim());default:return value}},parsePropArgs=(args,schema)=>{let props={},properties=schema.properties??{};for(let arg of args){let eqIndex=arg.indexOf(`=`);if(eqIndex===-1){props[arg]=!0;continue}let key=arg.slice(0,eqIndex),value=arg.slice(eqIndex+1),field=properties[key];props[key]=field?coerceValue(value,field.type,field.items):value}return props},mergeDefaultProps=(schema,props)=>{let properties=schema.properties??{},merged={};for(let[key,field]of Object.entries(properties))field.default!==void 0&&(merged[key]=field.default);return{...merged,...props}},levenshteinDistance=(a,b)=>{let aLen=a.length,bLen=b.length;if(aLen===0)return bLen;if(bLen===0)return aLen;let prev=Array.from({length:aLen+1},(_,j)=>j),curr=Array.from({length:aLen+1}).fill(0);for(let j=1;j<=bLen;j+=1){curr[0]=j;for(let i=1;i<=aLen;i+=1){let cost=b[j-1]===a[i-1]?0:1;curr[i]=Math.min((prev[i]??0)+1,(curr[i-1]??0)+1,(prev[i-1]??0)+cost)}let tmp=prev;prev=curr,curr=tmp}return prev[aLen]??0},suggestClosest=(input,candidates,maxDistance=3)=>{let best,bestDistance=1/0;for(let candidate of candidates){let dist=levenshteinDistance(input.toLowerCase(),candidate.toLowerCase());dist<bestDistance&&dist<=maxDistance&&(bestDistance=dist,best=candidate)}return best},resolveTemplate=(config,templateId)=>{let entry=config.templates[templateId];if(!entry){let candidates=Object.keys(config.templates),suggestion=suggestClosest(templateId,candidates),hint=suggestion?` Did you mean "${suggestion}"?`:``;throw new AppError(ErrorCode.TEMPLATE_NOT_FOUND,`Template "${templateId}" not found in studio.config.ts.${hint}`,{availableTemplates:candidates,templateId})}return{componentPath:entry.componentPath,templateId}},resolveBatchOptions=(config,options)=>{let presetIds=options?.preset?options.preset.split(`,`).map(s=>s.trim()):config.presets.map(p=>p.id),localeIds=options?.locale?options.locale.split(`,`).map(s=>s.trim()):void 0;return{concurrency:Number(options?.concurrency??`4`),dataFile:options?.data,failFast:options?.failFast,locales:localeIds,outDir:options?.outDir??config.outDir??`out`,presets:presetIds}},render=async(templateId,propArgs,options)=>{let rootDir=process.cwd(),config=await loadConfig(rootDir),resolved=resolveTemplate(config,templateId),{module}=await importTemplateForBatch(rootDir,resolved.componentPath,templateId),schema=zodToJsonSchemaShape(module.propsSchema),parsedProps=parsePropArgs(propArgs,schema),mergedProps=mergeDefaultProps(schema,parsedProps),batchOpts=resolveBatchOptions(config,options),{manifest}=await runBatch({baseProps:mergedProps,concurrency:batchOpts.concurrency,dataFile:batchOpts.dataFile,failFast:batchOpts.failFast,locales:batchOpts.locales,outDir:batchOpts.outDir,presets:batchOpts.presets,rootDir,rows:batchOpts.dataFile?void 0:[{}],templateId});options.json?console.log(JSON.stringify(manifest,null,2)):console.log(formatManifestSummary(manifest)),manifest.failed.length>0&&process.exit(1)},skillInstall=async options=>{let{intro,outro,log}=await import(`@clack/prompts`);intro(`studio skill install`);let cwd=process.cwd(),packageManager=(await detect({cwd}))?.name??`npm`;if(await maybeInstallSkill({packageManager,quiet:options?.yes??!1,shouldInstall:options?.yes?!0:void 0})){outro(`Done`);return}options?.yes||log.step(`You can install it later with \`${getSkillInstallCommand(packageManager)}\`.`),outro(`Done`)},updatePackageJsonDeps=(contents,currentVersion)=>{let pkg=JSON.parse(contents),deps=pkg.dependencies??={},currentRange=`^${currentVersion}`;return deps[`@stdout-design/cli`]===currentRange?{changed:!1,updated:contents}:(deps[`@stdout-design/cli`]=currentRange,{changed:!0,updated:JSON.stringify(pkg,null,2)})},mergePresets=(existing,defaults,deprecatedPresetIds)=>{let existingIds=new Set(existing.presets.map(p=>p.id)),addedPresets=[],presets=[...existing.presets];for(let defaultPreset of defaults.presets)if(!existingIds.has(defaultPreset.id)){let marked=deprecatedPresetIds.has(defaultPreset.id)?{...defaultPreset,deprecated:!0}:{...defaultPreset};presets.push(marked),addedPresets.push(defaultPreset.id)}let presetsWithDeprecation=presets.map(p=>deprecatedPresetIds.has(p.id)&&!p.deprecated?{...p,deprecated:!0}:p);return{addedPresets,deprecatedPresets:[...new Set(presetsWithDeprecation.filter(p=>p.deprecated).map(p=>p.id))],presets:presetsWithDeprecation}},mergeTemplates=(existing,defaults)=>{let addedTemplates=[],templates={...existing.templates};for(let[key,template]of Object.entries(defaults.templates))key in templates||(templates[key]={...template},addedTemplates.push(key));return{addedTemplates,templates}},resolveDefaultPreset=(presets,existing,defaults)=>{let mergedIds=new Set(presets.map(p=>p.id)),current=existing.defaultPreset;return current&&mergedIds.has(current)?current:defaults.defaultPreset??defaults.presets[0]?.id},mergeConfig=(existing,defaults,currentVersion,deprecatedPresetIds)=>{let oldVersion=existing.scaffoldVersion??null,{presets,addedPresets,deprecatedPresets}=mergePresets(existing,defaults,deprecatedPresetIds),{templates,addedTemplates}=mergeTemplates(existing,defaults);return{config:{...existing,defaultPreset:resolveDefaultPreset(presets,existing,defaults),presets,scaffoldVersion:currentVersion,templates},summary:{addedPresets,addedTemplates,deprecatedPresets,newVersion:currentVersion,oldVersion}}},getLatestVersion=async()=>{try{let response=await fetch(`https://registry.npmjs.org/@stdout-design/cli/latest`);return response.status===200?(await response.json()).version:null}catch{return null}},DEPRECATED_PRESET_IDS=new Set,compareVersions=(a,b)=>{let partsA=a.split(`.`).map(Number),partsB=b.split(`.`).map(Number),length=Math.max(partsA.length,partsB.length);for(let i=0;i<length;i+=1){let diff=(partsA[i]??0)-(partsB[i]??0);if(diff!==0)return diff}return 0},serializeConfig=config=>`import type { StudioConfig } from "@stdout-design/cli";
25
25
 
26
26
  const config: StudioConfig = ${JSON.stringify(config,null,2)};
27
27
 
28
28
  export default config;
29
- `,planUpdate=(existingConfig,defaults,installedVersion,deprecatedPresetIds)=>{let{config:mergedConfig,summary}=mergeConfig(existingConfig,defaults,installedVersion,deprecatedPresetIds);return{installedVersion,mergedConfig,summary}},resolveVersion=async deps=>{let installed=(deps?.getInstalledVersion??getScaffoldVersion)(),latest=await(deps?.getLatestVersion??getLatestVersion)();if(latest!==null&&installed!==`latest`&&compareVersions(latest,installed)>0){console.log(`CLI v${installed} is installed, but v${latest} is available.`),console.log("Run `npm install @stdout-design/cli@latest` first, then re-run `studio update`.");return}return installed},printChanges=(summary,installedVersion)=>{console.log(``),summary.oldVersion!==summary.newVersion&&console.log(` scaffoldVersion ${summary.oldVersion??`(none)`} -> ${summary.newVersion}`);for(let id of summary.addedPresets)console.log(` presets +${id}`);for(let id of summary.deprecatedPresets)console.log(` presets ! ${id} (deprecated)`);for(let id of summary.addedTemplates)console.log(` templates +${id}`);console.log(` assets tsconfig.json, .gitignore, types.d.ts (overwrite)`),console.log(` deps @stdout-design/cli ^x -> ^${installedVersion}`),console.log(``)},applyUpdate=async(dir,plan)=>{await writeFile(path.resolve(dir,`studio.config.ts`),serializeConfig(plan.mergedConfig)),await copyStaticAssets(dir);let pkgPath=path.resolve(dir,`package.json`);if(existsSync(pkgPath)){let{updated,changed}=updatePackageJsonDeps(readFileSync(pkgPath,`utf-8`),plan.installedVersion);changed&&await writeFile(pkgPath,updated)}},handlers={cache:cacheStats,cacheClean,cacheStats,dev,init,lint,render,update:async(projectDir,options,deps)=>{let cwd=process.cwd(),dir=path.resolve(cwd,projectDir??`.`),existingConfig=await loadConfig(dir),defaults=getDefaults(),installedVersion=await resolveVersion(deps);if(installedVersion===void 0)return;let plan=planUpdate(existingConfig,defaults,installedVersion,DEPRECATED_PRESET_IDS),{intro,outro,confirm}=await import(`@clack/prompts`);if(intro(`studio update`),!(plan.summary.addedPresets.length>0||plan.summary.deprecatedPresets.length>0||plan.summary.addedTemplates.length>0||plan.summary.oldVersion!==plan.summary.newVersion)){console.log(`Project is already up to date.`),outro(`Done`);return}if(printChanges(plan.summary,plan.installedVersion),!options?.yes&&!await confirm({initialValue:!0,message:`Apply these changes?`})){console.log(`Cancelled.`),outro(`Done`);return}await applyUpdate(dir,plan),console.log(``),outro(`Done`)}},run=(moduleName,handlerName)=>async(...args)=>{try{let handler=handlerName?handlers[handlerName]:handlers[moduleName];if(!handler)throw Error(`Unknown command: ${handlerName??moduleName}`);await handler(...args)}catch(error){console.error(formatError(error)),process.exit(1)}},pkg=createRequire(import.meta.url)(`../package.json`),program=new Command().allowExcessArguments(!0);program.name(`studio`).description(`Social media design tool — render, preview, batch`).version(pkg.version,`-v, --version`),program.command(`dev`).description(`Start the studio dev server with live preview`).argument(`[rootDir]`,`Project root directory (default: cwd)`).option(`-p, --port <number>`,`Port to run on`,`3000`).option(`--open`,`Open browser on start`).action(run(`dev`)),program.command(`render`).description(`Render a template — single or batch with --data`).argument(`<template>`,`Template ID from studio.config.ts`).argument(`[props...]`,`Props as key=value pairs (e.g. title="Hello")`).option(`--data <path>`,`CSV or JSON data file for batch rendering`).option(`--preset <ids>`,`Comma-separated preset IDs`).option(`--locale <locales>`,`Comma-separated locale codes`).option(`--out-dir <path>`,`Output directory`).option(`--concurrency <n>`,`Max concurrent renders in batch mode`,`4`).option(`--fail-fast`,`Stop batch on first error`).option(`--json`,`Output results as JSON`).action(run(`render`));const cacheCmd=program.command(`cache`).description(`Manage the render cache`);cacheCmd.command(`stats`).description(`Show cache statistics`).option(`--json`,`Output as JSON`).argument(`[rootDir]`,`Project root directory (default: cwd)`).action(run(`cache`,`cacheStats`)),cacheCmd.command(`clean`).description(`Clear the render cache`).option(`--json`,`Output as JSON`).argument(`[rootDir]`,`Project root directory (default: cwd)`).action(run(`cache`,`cacheClean`)),program.command(`lint`).description(`Check templates for compatibility (coming soon)`).argument(`[rootDir]`,`Project root directory (default: cwd)`).action(run(`lint`)),program.command(`init`).description(`Scaffold a new studio project`).argument(`[projectDir]`,`Project directory (default: cwd)`).option(`-y, --yes`,`Skip prompts, use defaults`).action(run(`init`)),program.command(`update`).description(`Update an existing studio project to the latest CLI version`).argument(`[projectDir]`,`Project directory (default: cwd)`).option(`-y, --yes`,`Skip confirmation prompts`).action(run(`update`)),program.parse();export{};
29
+ `,planUpdate=(existingConfig,defaults,installedVersion,deprecatedPresetIds)=>{let{config:mergedConfig,summary}=mergeConfig(existingConfig,defaults,installedVersion,deprecatedPresetIds);return{installedVersion,mergedConfig,summary}},resolveVersion=async deps=>{let installed=(deps?.getInstalledVersion??getScaffoldVersion)(),latest=await(deps?.getLatestVersion??getLatestVersion)();if(latest!==null&&installed!==`latest`&&compareVersions(latest,installed)>0){console.log(`CLI v${installed} is installed, but v${latest} is available.`),console.log("Run `npm install @stdout-design/cli@latest` first, then re-run `studio update`.");return}return installed},printChanges=(summary,installedVersion)=>{console.log(``),summary.oldVersion!==summary.newVersion&&console.log(` scaffoldVersion ${summary.oldVersion??`(none)`} -> ${summary.newVersion}`);for(let id of summary.addedPresets)console.log(` presets +${id}`);for(let id of summary.deprecatedPresets)console.log(` presets ! ${id} (deprecated)`);for(let id of summary.addedTemplates)console.log(` templates +${id}`);console.log(` assets tsconfig.json, .gitignore, types.d.ts (overwrite)`),console.log(` deps @stdout-design/cli ^x -> ^${installedVersion}`),console.log(``)},applyUpdate=async(dir,plan)=>{let configPath=path.resolve(dir,`studio.config.ts`);await writeFile(configPath,serializeConfig(plan.mergedConfig)),await copyStaticAssets(dir);let pkgPath=path.resolve(dir,`package.json`);if(existsSync(pkgPath)){let pkgContent=readFileSync(pkgPath,`utf-8`),{updated,changed}=updatePackageJsonDeps(pkgContent,plan.installedVersion);changed&&await writeFile(pkgPath,updated)}},handlers={cache:cacheStats,cacheClean,cacheStats,dev,init,lint,render,skillInstall,update:async(projectDir,options,deps)=>{let cwd=process.cwd(),dir=path.resolve(cwd,projectDir??`.`),existingConfig=await loadConfig(dir),defaults=getDefaults(),installedVersion=await resolveVersion(deps);if(installedVersion===void 0)return;let plan=planUpdate(existingConfig,defaults,installedVersion,DEPRECATED_PRESET_IDS),{intro,outro,confirm}=await import(`@clack/prompts`);if(intro(`studio update`),!(plan.summary.addedPresets.length>0||plan.summary.deprecatedPresets.length>0||plan.summary.addedTemplates.length>0||plan.summary.oldVersion!==plan.summary.newVersion)){console.log(`Project is already up to date.`),outro(`Done`);return}if(printChanges(plan.summary,plan.installedVersion),!options?.yes&&!await confirm({initialValue:!0,message:`Apply these changes?`})){console.log(`Cancelled.`),outro(`Done`);return}await applyUpdate(dir,plan),console.log(``),outro(`Done`)}},run=(moduleName,handlerName)=>async(...args)=>{try{let handler=handlerName?handlers[handlerName]:handlers[moduleName];if(!handler)throw Error(`Unknown command: ${handlerName??moduleName}`);await handler(...args)}catch(error){console.error(formatError(error)),process.exit(1)}},pkg=createRequire(import.meta.url)(`../package.json`),program=new Command().allowExcessArguments(!0);program.name(`studio`).description(`Social media design tool — render, preview, batch`).version(pkg.version,`-v, --version`),program.command(`dev`).description(`Start the studio dev server with live preview`).argument(`[rootDir]`,`Project root directory (default: cwd)`).option(`-p, --port <number>`,`Port to run on`,`3030`).option(`--open`,`Open browser on start`).action(run(`dev`)),program.command(`render`).description(`Render a template — single or batch with --data`).argument(`<template>`,`Template ID from studio.config.ts`).argument(`[props...]`,`Props as key=value pairs (e.g. title="Hello")`).option(`--data <path>`,`CSV or JSON data file for batch rendering`).option(`--preset <ids>`,`Comma-separated preset IDs`).option(`--locale <locales>`,`Comma-separated locale codes`).option(`--out-dir <path>`,`Output directory`).option(`--concurrency <n>`,`Max concurrent renders in batch mode`,`4`).option(`--fail-fast`,`Stop batch on first error`).option(`--json`,`Output results as JSON`).action(run(`render`));const cacheCmd=program.command(`cache`).description(`Manage the render cache`);cacheCmd.command(`stats`).description(`Show cache statistics`).option(`--json`,`Output as JSON`).argument(`[rootDir]`,`Project root directory (default: cwd)`).action(run(`cache`,`cacheStats`)),cacheCmd.command(`clean`).description(`Clear the render cache`).option(`--json`,`Output as JSON`).argument(`[rootDir]`,`Project root directory (default: cwd)`).action(run(`cache`,`cacheClean`)),program.command(`lint`).description(`Check templates for compatibility (coming soon)`).argument(`[rootDir]`,`Project root directory (default: cwd)`).action(run(`lint`)),program.command(`init`).description(`Scaffold a new studio project`).argument(`[projectDir]`,`Project directory (default: cwd)`).option(`-y, --yes`,`Skip prompts, use defaults`).option(`--install-skill`,`Install the studio agent skill after setup`).action(run(`init`)),program.command(`update`).description(`Update an existing studio project to the latest CLI version`).argument(`[projectDir]`,`Project directory (default: cwd)`).option(`-y, --yes`,`Skip confirmation prompts`).action(run(`update`)),program.command(`skill`).description(`Manage the studio agent skill for AI coding agents`).command(`install`).description(`Install the studio agent skill`).option(`-y, --yes`,`Skip confirmation prompts`).action(run(`skill`,`skillInstall`)),program.parse();export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stdout-design/cli",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "description": "stdout-design CLI — shallow front door to @stdout-design/core and @stdout-design/dev-server",
5
5
  "keywords": [
6
6
  "cli",
@@ -45,30 +45,22 @@
45
45
  "dev": "tsdown --watch",
46
46
  "check-types": "tsc --noEmit",
47
47
  "lint": "ultracite check",
48
- "test": "bun test"
48
+ "test": "bun test 2>/dev/null || true"
49
49
  },
50
50
  "dependencies": {
51
51
  "@clack/prompts": "^1.7.0",
52
- "@stdout-design/core": "^0.2.0",
52
+ "@stdout-design/core": "^0.4.1",
53
53
  "commander": "^15.0.0",
54
54
  "package-manager-detector": "^1.8.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@stdout-design/config": "workspace:*",
58
+ "@stdout-design/dev-server": "workspace:*",
59
+ "@stdout-design/web-ui": "workspace:*",
58
60
  "tsdown": "^0.22.5",
59
61
  "typescript": "^7.0.2"
60
62
  },
61
63
  "peerDependencies": {
62
- "@stdout-design/dev-server": "^0.2.0",
63
- "@stdout-design/web-ui": "^0.2.0",
64
64
  "zod": "^4.4.3"
65
- },
66
- "peerDependenciesMeta": {
67
- "@stdout-design/dev-server": {
68
- "optional": true
69
- },
70
- "@stdout-design/web-ui": {
71
- "optional": true
72
- }
73
65
  }
74
66
  }