A flexible CLI framework for scaffolding projects with templates.
TinyCreate is a powerful templating engine and CLI framework designed for creating project generators. It combines interactive prompts, Handlebars templates, and smart post-processing to generate complete projects from templates with conditional logic, automatic imports, and code formatting.
- Interactive Prompts - Built-in support for text, select, and confirm questions
- Smart Templates - Handlebars with custom helpers for common patterns
- Automatic Import Management - Colocate imports with conditional logic
- Intelligent Transpilation - Convert TypeScript to JavaScript on demand
- Code Formatting - Built-in Prettier support with import organization
- File Inclusion System - Compose templates from reusable components
- Non-Interactive Mode - Full CLI argument support for CI/CD
- Package Manager Detection - Automatically uses npm, yarn, pnpm, or bun
npm install tinycreateimport{createCLI}from'tinycreate';import{dirname,join}from'path';import{fileURLToPath}from'url';const__dirname=dirname(fileURLToPath(import.meta.url));constconfig={welcomeMessage: '🎉 Welcome to My Generator!\n',questions: [{type: 'text',name: 'projectName',message: 'Project name:',initial: 'my-app',},{type: 'select',name: 'language',message: 'Language:',choices: [{title: 'TypeScript',value: 'typescript'},{title: 'JavaScript',value: 'javascript'},],},],createContext: (answers)=>({projectName: answers.projectName,isTypescript: answers.language==='typescript',ext: answers.language==='typescript' ? 'ts' : 'js',}),getFiles: (context)=>[{template: 'templates/App.tsx.hbs',output: `src/App.${context.ext}`,prettier: true,transpile: context.isTypescript===false,},],templateRoot: __dirname,onSuccess: (projectName)=>{console.log(`✅ Created ${projectName}!`);console.log(`\nNext steps:`);console.log(` cd ${projectName}`);console.log(` npm install`);console.log(` npm run dev`);},};awaitcreateCLI(config);TinyCreate extends Handlebars with powerful custom helpers designed for code generation:
Automatically manage imports at the top of files, keeping them colocated with conditional logic:
Output (when both conditions are true):
importReactfrom'react';importtype{FC}from'react';exportconstComponent=()=>{return<div>Hello</div>;};All imports added with {{addImport}} are automatically:
- Deduplicated
- Moved to the top of the file
- Preserved in their original order
Generate comma-separated lists with automatic formatting:
Output (when includeRouter is true):
constdependencies={react: '^18.0.0','react-router': '^6.0.0',lodash: '^4.17.21',};The {{#list}} helper:
- Automatically adds commas between items
- Removes trailing comma from last item
- Handles conditional items gracefully
- Filters out empty lines and comments
Compare values in conditional blocks:
Compose templates from other templates and track file dependencies:
This directive:
- Signals that
Button.tsxshould be generated - Allows the parent file to import it
- Processes the included template with the same context
- Supports prettier and transpile options per file
Main entry point for creating a CLI generator.
Config Options:
interfaceProjectConfig{// Optional welcome message shown before promptswelcomeMessage?: string;// Array of question objects (text, select, or confirm)questions: Question[];// Transform user answers into template contextcreateContext: (answers: Record<string,unknown>)=>TemplateContext;// Return array of files to generategetFiles: (context: TemplateContext)=>FileConfig[]|Promise<FileConfig[]>;// Optional: Process included files before renderingprocessIncludedFile?: (file: FileConfig,context: TemplateContext,)=>FileConfig;// Root directory containing templatestemplateRoot: string;// Optional: Create custom directoriescreateDirectories?: (targetDir: string,context: TemplateContext,)=>Promise<void>;// Optional: Custom install command (default: auto-detected package manager)installCommand?: string;// Optional: Custom dev commanddevCommand?: string;// Optional: Success callback after project creationonSuccess?: (projectName: string,context: TemplateContext,)=>void|Promise<void>;}CLI Options:
interfaceCLIOptions{// Enable non-interactive mode for CI/CDnonInteractive?: boolean;// Custom CLI arguments (defaults to process.argv.slice(2))args?: string[];}interfaceQuestion{// Question type (or function returning type based on previous answers)type:
|'text'|'select'|'confirm'|((prev: unknown,answers: Record<string,unknown>,)=>'text'|'select'|'confirm'|null);// Answer key namename: string;// Question promptmessage: string;// Default valueinitial?: string|number|boolean;// Choices for 'select' typechoices?: Array<{title: string;value: string|boolean}>;// Validation function for 'text' typevalidate?: (value: string)=>boolean|string;}interfaceFileConfig{// Path to Handlebars template (relative to templateRoot)template: string;// Output path (supports template variables like {{projectName}})output: string;// Enable Prettier formatting (default: false)prettier?: boolean;// Enable TypeScript to JavaScript transpilation (default: false)transpile?: boolean;// Internal: processed content (set by engine)processedContent?: string;}import{postProcessFile,postProcessProject}from'tinycreate';// Process a single fileconst{filePath, content}=awaitpostProcessFile('src/App.tsx',fileContent,{prettier: true,transpileToJS: true,});// Process multiple filesconstprocessedFiles=awaitpostProcessProject('/path/to/project',filesMap,{prettier: true,transpileToJS: false,});TinyCreate supports non-interactive mode for use in CI/CD or automated scripts:
node cli.js --non-interactive \
--projectName my-app \
--language typescript \
--framework reactAny question without a provided argument will use its initial value.
Questions can be conditional based on previous answers:
{type: (prev,answers)=>answers.language==='typescript' ? 'confirm' : null,name: 'strictMode',message: 'Enable strict mode?',initial: true,}If the function returns null, the question is skipped.
Generate files based on context:
getFiles: (context)=>{constfiles=[{template: 'App.tsx.hbs',output: 'src/App.tsx'}];if(context.includeTests){files.push({template: 'App.test.tsx.hbs',output: 'src/App.test.tsx',});}returnfiles;};Create custom directories before file generation:
createDirectories: async(targetDir,context)=>{const{mkdir}=awaitimport('fs/promises');awaitmkdir(join(targetDir,'src','components'),{recursive: true});awaitmkdir(join(targetDir,'public'),{recursive: true});if(context.includeServer){awaitmkdir(join(targetDir,'server'),{recursive: true});}};Modify included files before rendering:
processIncludedFile: (file,context)=>{// Force all component files to be formattedif(file.output.includes('components/')){return{...file,prettier: true};}returnfile;};See the create-tinybase project for a complete real-world example that uses TinyCreate to generate TinyBase applications with multiple frameworks, languages, and configurations.
npm testTinyCreate includes comprehensive tests covering:
- Template engine functionality
- Import management
- List formatting
- File inclusion
- Post-processing
- TypeScript transpilation
- Node.js >= 18.0.0
- handlebars - Template rendering
- prompts - Interactive CLI prompts
- esbuild - Fast TypeScript transpilation
- prettier - Code formatting
MIT License - see LICENSE file for details.