Examples
Task Runner CLI
Task automation CLI with validation and interactivity
Task Runner CLI Example
A practical task automation CLI showcasing validation and interactivity patterns. Demonstrates real-world patterns for build, test, deploy, and setup workflows.
Overview
This example consolidates validation and interactive patterns into a cohesive task automation tool:
- Schema validation with Zod for type-safe options
- Interactive prompts and confirmations for user input
- Progress indicators and spinners for long-running tasks
- Conditional flows based on command options
- Error handling and user feedback
Commands
build - Build Project
bun run cli.ts build --target production --minify --sourcemapBuilds the project with validation and transformation:
- Target selection (development/production)
- Minification and source map generation
- Progress tracking with spinners
- Validation of build configuration
test - Run Tests
bun run cli.ts test --filter unit --coverage --watchRuns tests with filtering and coverage:
- Test filtering by type (unit, integration, e2e)
- Coverage reporting
- Watch mode for development
- Complex validation patterns
deploy - Deploy Application
bun run cli.ts deploy --env production --confirmDeploys with interactive confirmation:
- Environment selection
- Interactive confirmation prompts
- Deployment validation
- Rollback options
setup - Interactive Setup
bun run cli.ts setupInteractive setup wizard:
- Multi-step configuration
- Dynamic prompts based on previous answers
- Validation of user input
- Configuration file generation
Key Features Demonstrated
1. Schema Validation
import { defineCommand, option } from '@bunli/core'
import { z } from 'zod'
export const buildCommand = defineCommand({
name: 'build',
options: {
target: option(
z.enum(['development', 'production']).default('production'),
{ description: 'Build target' }
),
minify: option(
z.boolean().default(true),
{ description: 'Minify output', short: 'm' }
),
sourcemap: option(
z.boolean().default(false),
{ description: 'Generate source maps', short: 's' }
)
},
handler: async ({ flags, spinner, colors }) => {
// Type-safe access to validated options
const { target, minify, sourcemap } = flags
// ... implementation
}
})2. Interactive Prompts
import { prompt } from '@bunli/core'
// Text input
const name = await prompt.text('Project name?')
// Selection
const framework = await prompt.select('Framework?', [
'React', 'Vue', 'Svelte', 'Solid'
])
// Confirmation
const confirmed = await prompt.confirm('Deploy to production?')
// Multi-select
const features = await prompt.multiselect('Features?', [
'TypeScript', 'ESLint', 'Prettier', 'Testing'
])3. Progress Indicators
import { spinner } from '@bunli/core'
spinner.start('Building project...')
// Simulate build steps
const steps = ['Compiling', 'Bundling', 'Optimizing', 'Writing']
for (const step of steps) {
spinner.text = step
await new Promise(resolve => setTimeout(resolve, 500))
}
spinner.succeed('Build completed!')4. Conditional Flows
handler: async ({ flags, prompt, colors }) => {
if (flags.env === 'production') {
const confirmed = await prompt.confirm(
colors.red('⚠️ Deploy to production? This cannot be undone!')
)
if (!confirmed) {
console.log(colors.yellow('Deployment cancelled'))
return
}
}
// Continue with deployment
}Project Structure
task-runner/
├── cli.ts # Main CLI file
├── commands/
│ ├── build.ts # Build command with validation
│ ├── test.ts # Test command with filtering
│ ├── deploy.ts # Deploy command with prompts
│ └── setup.ts # Interactive setup wizard
├── bunli.config.ts # Build configuration
├── package.json # Dependencies and scripts
└── README.md # Example documentationRunning the Example
# Navigate to the example
cd examples/task-runner
# Install dependencies
bun install
# Run in development mode
bun run dev
# Try the commands
bun run cli.ts build --help
bun run cli.ts test --filter unit --coverage
bun run cli.ts deploy --env staging
bun run cli.ts setupValidation Patterns
Complex Option Validation
options: {
filter: option(
z.enum(['unit', 'integration', 'e2e']).array().optional(),
{ description: 'Test types to run' }
),
coverage: option(
z.boolean().default(false),
{ description: 'Generate coverage report' }
),
threshold: option(
z.number().min(0).max(100).default(80),
{ description: 'Coverage threshold percentage' }
)
}Runtime Validation
handler: async ({ flags, colors }) => {
// Validate test files exist
const testFiles = await glob('**/*.test.ts')
if (testFiles.length === 0) {
console.error(colors.red('No test files found'))
process.exit(1)
}
// Validate coverage threshold
if (flags.coverage && flags.threshold < 50) {
console.warn(colors.yellow('Low coverage threshold'))
}
}Interactive Patterns
Multi-step Setup
handler: async ({ prompt, colors }) => {
console.log(colors.cyan('Welcome to the setup wizard!'))
// Step 1: Project details
const name = await prompt.text('Project name?')
const description = await prompt.text('Description?')
// Step 2: Framework selection
const framework = await prompt.select('Framework?', [
'React', 'Vue', 'Svelte', 'Solid'
])
// Step 3: Features
const features = await prompt.multiselect('Features?', [
'TypeScript', 'ESLint', 'Prettier', 'Testing'
])
// Step 4: Confirmation
console.log(colors.cyan('\nConfiguration:'))
console.log(`Name: ${name}`)
console.log(`Framework: ${framework}`)
console.log(`Features: ${features.join(', ')}`)
const confirmed = await prompt.confirm('Create project?')
if (confirmed) {
// Create project files
spinner.succeed('Project created successfully!')
}
}Key Takeaways
- Schema Validation: Type-safe options with runtime validation
- Interactive UX: User-friendly prompts and confirmations
- Progress Feedback: Spinners and progress indicators
- Conditional Logic: Smart flows based on user input
- Error Handling: Graceful error handling and user feedback
- Real-world Patterns: Practical patterns for task automation
Next Steps
- Git Tool Example - Learn command organization
- Dev Server Example - Learn plugin system
- Schema Validation Guide - Deep dive into validation
- Interactive Prompts Guide - Advanced prompt patterns