Instruction file imported from Vakacharla-Lokesh/buildkit-cli (
.github/instructions/project-scaffolding.instructions.md). Copyright stays with the author.
CLI Project Scaffolding Instructions
This workspace implements a modular, extensible CLI that scaffolds complete projects interactively. These instructions ensure consistent, production-ready code across all components.
Architecture & Organization
The project uses a strict separation of concerns:
src/
index.ts → Entry point and commander setup
cli/ → Interactive prompting logic
prompts/ → Individual question modules
flows/ → Sequential question flows
generators/ → File generation and setup
templates/ → Boilerplate files organized by stack
utils/ → Reusable helpers
types/ → Centralized type definitions
Core Principles
- Separation of concerns: Prompts ask questions → Generators create files → Utils provide helpers
- Single responsibility: Each file should have one clear purpose
- Type safety: No
anytypes; use discriminated unions for conditional logic - Modularity: New stacks added without modifying existing code
- Async/await: Use async/await, not nested promises
File Size & Structure
- Keep modules under ~150 lines
- Keep functions small, focused, and testable
- Use named exports when possible (prefer exports over default)
- Keep imports organized: node → external → internal
Code Style Rules
TypeScript
- No
anytypes—use unknown, discriminated unions, or generics - Export types and interfaces at the top of files
- Use strong typing for CLI answers and configurations
- Prefer
constoverlet
Functions
- Use
async/awaitinstead of nested.then()chains - Add clear comments only for non-obvious logic
- Handle errors explicitly—don't silently fail
- Use early returns to reduce nesting
Naming
- Prompts:
ask<Feature>(e.g.,askPackageManager) - Generators:
generate<Asset>orcopy<Template>(e.g.,copyTemplate,generatePackageJson) - Utilities: Descriptive verbs (e.g.,
resolveTemplate,injectVariables,validateProjectName) - Types: PascalCase (e.g.,
ProjectAnswers,TemplateConfig)
Example Code Structure
import { promises as fs } from 'fs'
import chalk from 'chalk'
import { ProjectAnswers } from '../types'
export async function copyTemplate(
source: string,
dest: string,
answers: ProjectAnswers
): Promise<void> {
try {
// Implementation
} catch (error) {
throw new Error(`Failed to copy template: ${error.message}`)
}
}
Prompting Rules
Conditional Flow
- Only ask frontend framework questions if
projectTypeincludes'frontend' - Only ask database questions if user selects a data-driven stack
- Only ask ORM questions after confirming database selection
- Only ask auth provider questions if authentication is enabled
- Skip irrelevant questions entirely
Question Quality
- Keep questions clear and concise
- Provide sensible defaults where appropriate
- Validate input without silent failures
- Show helpful error messages for invalid inputs
Example Pattern
export async function askFrontendFramework(
projectType: string
): Promise<'react' | 'next' | undefined> {
if (!projectType.includes('frontend')) {
return undefined
}
const answer = await prompts({
type: 'select',
name: 'framework',
message: 'Which frontend framework?',
choices: [
{ title: 'React', value: 'react' },
{ title: 'Next.js', value: 'next' },
],
})
return answer.framework
}
Types - Central Source of Truth
All prompt results must map into ProjectAnswers. Define once in types/answers.ts:
export interface ProjectAnswers {
packageManager: 'npm' | 'bun'
projectType: 'frontend' | 'backend' | 'fullstack'
language: 'js' | 'ts'
projectName: string
// Conditional
frontendFramework?: 'react' | 'next'
backendFramework?: 'express'
styling?: 'tailwind'
auth?: 'jwt'
database?: 'mongodb' | 'postgresql'
orm?: 'mongoose' | 'prisma'
// Options
installDependencies: boolean
initializeGit: boolean
}
Use discriminated unions to ensure only valid combinations are possible.
Template Management
Template Resolution
Create a single resolver in generators/templateResolver.ts that converts answers to template paths:
export function resolveTemplate(answers: ProjectAnswers): string {
const { projectType, frontendFramework, backendFramework, database, orm } = answers
if (projectType === 'frontend') {
return `templates/frontend/${frontendFramework}-${answers.language}`
}
// ... other cases
}
Adding new stacks: Modify only templateResolver.ts and add template folders—no other files affected.
Variable Injection
Build a utilities that recursively scans copied files and replaces placeholders:
// templates/frontend/react-ts/package.json
{
"name": "{{PROJECT_NAME}}",
"packageManager": "{{PACKAGE_MANAGER}}"
}
Inject values after copying templates, not before.
Dependency Installation
Install Strategy
- Use
execato runnpm installorbun install - Install only dependencies required by selected features
- Build dependency lists based on
ProjectAnswers
Feature Dependencies
- Tailwind:
tailwindcss - JWT:
jsonwebtoken - Mongoose:
mongoose - Prisma:
prisma,@prisma/client - Express:
express - React:
react,react-dom - Next.js:
next,react,react-dom
Track these in a centralized config file.
UX & Output
Polish & Feedback
- Use
chalkfor colored headings and emphasis - Use
oraspinners during long operations - Provide clear success messages with next steps
- Always include error context when operations fail
Output Example
✔ Project created successfully!
Next steps:
cd my-app
npm install
npm run dev
Happy coding!
Error Handling
Always handle these cases:
- Invalid project names (empty, special chars, reserved words)
- Existing directory conflicts
- Missing template folders
- Failed dependency installation
- Unsupported stack combinations
Provide specific, actionable error messages:
throw new Error(
`The combination ${projectType} + ${framework} is not yet supported. ` +
`Try React + TypeScript or Express + TypeScript.`
)
Never silently ignore errors.
Future Extensibility
Write code assuming these will be added later:
- Angular, Vue, Svelte frameworks
- NestJS, Fastify backends
- OAuth, Passport authentication
- Docker, Redis, Turborepo
- Plugin system
- Presets (e.g.,
--preset next-prisma-auth) - Non-interactive flags
Design for Extension
- Template system: Add template folder + entry in
templateResolver.ts - Prompts: Add new prompt file + integrate into flow
- Dependencies: Add entries to dependency config
- Frameworks: Use discriminated unions to add new options
- Environments: Support environment-specific template variations
Avoid hardcoding values. Use centralized config where possible.
Implementation Order
When adding features, follow this order:
- Add types to
types/answers.ts - Create prompt in
cli/prompts/ - Create generator in
generators/ - Update
templateResolver.ts - Integrate into flow in
cli/flows/ - Add templates to
templates/ - Refactor for reusability
Agent Behavior
When suggesting code:
- Prefer modular patterns that don't require refactoring later
- Keep files under 150 lines; split if necessary
- Use utility functions instead of duplicating logic
- Maintain strong typing throughout
- Generate production-ready code
- Preserve naming conventions consistently
- Always consider how the code will scale when new stacks are added
If choosing between implementations, prefer the more scalable and maintainable option, even if slightly more verbose.
MVP Feature Support
Build and test in this order:
- Package managers: npm, bun
- Project types: frontend, backend, fullstack
- Languages: JavaScript, TypeScript
- Frontend: React, Next.js
- Backend: Express
- Styling: Tailwind CSS
- Auth: JWT
- Databases: MongoDB, PostgreSQL
- ORMs: Prisma, Mongoose
Start with one complete flow (e.g., React + TypeScript + Tailwind) and expand incrementally.