@stdout-design/cli 0.2.6 → 0.2.8

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 +2 -2
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -23,9 +23,9 @@ const config: StudioConfig = {
23
23
  };
24
24
 
25
25
  export default config;
26
- `,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":`^0.2.0`,"@stdout-design/web-ui":`^0.2.0`,"@types/react":`^19.2.17`}}),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}}},DEPRECATED_PRESET_IDS=new Set,serializeConfig=config=>`import type { StudioConfig } from "@stdout-design/cli";
26
+ `,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`}}),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}}},DEPRECATED_PRESET_IDS=new Set,serializeConfig=config=>`import type { StudioConfig } from "@stdout-design/cli";
27
27
 
28
28
  const config: StudioConfig = ${JSON.stringify(config,null,2)};
29
29
 
30
30
  export default config;
31
- `,handlers={cache:cacheStats,cacheClean,cacheStats,dev,init,lint,render,update:async(projectDir,options)=>{let cwd=process.cwd(),dir=path.resolve(cwd,projectDir??`.`),configPath=path.resolve(dir,`studio.config.ts`);if(!existsSync(configPath))throw Error("No studio project found. Run `studio init` first.");let existingModule=await import(pathToFileURL(configPath).href),existing=existingModule.default??existingModule,existingConfig={defaultPreset:existing.defaultPreset,outDir:existing.outDir,presets:existing.presets??[],scaffoldVersion:existing.scaffoldVersion,templates:existing.templates??{}},defaults=getDefaults(),currentVersion=getScaffoldVersion(),{config:merged,summary}=mergeConfig(existingConfig,defaults,currentVersion,DEPRECATED_PRESET_IDS),{intro,outro,confirm}=await import(`@clack/prompts`);if(intro(`studio update`),!(summary.addedPresets.length>0||summary.deprecatedPresets.length>0||summary.addedTemplates.length>0||summary.oldVersion!==summary.newVersion)){console.log(`Project is already up to date.`),outro(`Done`);return}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}`);if(console.log(` assets tsconfig.json, .gitignore, types.d.ts (overwrite)`),console.log(` deps @stdout-design/cli ^x -> ^${currentVersion}`),console.log(``),!options?.yes&&!await confirm({initialValue:!0,message:`Apply these changes?`})){console.log(`Cancelled.`),outro(`Done`);return}await writeFile(configPath,serializeConfig(merged)),await copyStaticAssets(dir);let pkgPath=path.resolve(dir,`package.json`);if(existsSync(pkgPath)){let{updated,changed}=updatePackageJsonDeps(readFileSync(pkgPath,`utf-8`),currentVersion);changed&&await writeFile(pkgPath,updated)}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$1(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, -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{};
31
+ `,handlers={cache:cacheStats,cacheClean,cacheStats,dev,init,lint,render,update:async(projectDir,options)=>{let cwd=process.cwd(),dir=path.resolve(cwd,projectDir??`.`),configPath=path.resolve(dir,`studio.config.ts`);if(!existsSync(configPath))throw Error("No studio project found. Run `studio init` first.");let existingModule=await import(pathToFileURL(configPath).href),existing=existingModule.default??existingModule,existingConfig={defaultPreset:existing.defaultPreset,outDir:existing.outDir,presets:existing.presets??[],scaffoldVersion:existing.scaffoldVersion,templates:existing.templates??{}},defaults=getDefaults(),currentVersion=getScaffoldVersion(),{config:merged,summary}=mergeConfig(existingConfig,defaults,currentVersion,DEPRECATED_PRESET_IDS),{intro,outro,confirm}=await import(`@clack/prompts`);if(intro(`studio update`),!(summary.addedPresets.length>0||summary.deprecatedPresets.length>0||summary.addedTemplates.length>0||summary.oldVersion!==summary.newVersion)){console.log(`Project is already up to date.`),outro(`Done`);return}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}`);if(console.log(` assets tsconfig.json, .gitignore, types.d.ts (overwrite)`),console.log(` deps @stdout-design/cli ^x -> ^${currentVersion}`),console.log(``),!options?.yes&&!await confirm({initialValue:!0,message:`Apply these changes?`})){console.log(`Cancelled.`),outro(`Done`);return}await writeFile(configPath,serializeConfig(merged)),await copyStaticAssets(dir);let pkgPath=path.resolve(dir,`package.json`);if(existsSync(pkgPath)){let{updated,changed}=updatePackageJsonDeps(readFileSync(pkgPath,`utf-8`),currentVersion);changed&&await writeFile(pkgPath,updated)}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$1(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{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stdout-design/cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "stdout-design CLI — shallow front door to @stdout-design/core and @stdout-design/dev-server",
5
5
  "keywords": [
6
6
  "cli",