Examples
Task Runner CLI
Task automation CLI with validation and interactivity
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 --env productionBuilds the project with validation and transformation:
- Environment selection (development/production)
- Output directory configuration
- Progress tracking with spinners
- Validation of build configuration
test - Run Tests
bun run cli.ts test --pattern "**/*.test.ts" --coverage 80 --watchRuns tests with filtering and coverage:
- Test file pattern specification
- Coverage reporting
- Watch mode for development
- Complex validation patterns
deploy - Deploy Application
bun run cli.ts deploy --environment productionDeploys 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" as const,
options: {
env: option(z.enum(["development", "staging", "production"]).default("development"), {
short: "e",
description: "Build environment",
}),
outdir: option(z.string().min(1).default("dist"), {
short: "o",
description: "Output directory",
}),
config: option(
z
.string()
.transform((val) => JSON.parse(val))
.optional(),
{ short: "c", description: "JSON configuration object" },
),
memory: option(
z
.string()
.regex(/^\d+[kmg]?$/i)
.optional()
.transform((val) => {
const raw = val ?? "512m";
const num = parseInt(raw);
const unit = raw.slice(-1).toLowerCase();
const multipliers = { k: 1024, m: 1024 * 1024, g: 1024 * 1024 * 1024 };
return num * (multipliers[unit as keyof typeof multipliers] || 1);
}),
{ short: "m", description: "Memory limit (e.g., 512m, 2g)" },
),
variables: option(
z
.string()
.transform((val) => {
const vars: Record<string, string> = {};
val.split(",").forEach((pair) => {
const [key, value] = pair.split("=");
if (key && value) vars[key.trim()] = value.trim();
});
return vars;
})
.optional(),
{ short: "v", description: "Environment variables (key1=value1,key2=value2)" },
),
watch: option(z.coerce.boolean().default(false), {
short: "w",
description: "Watch for changes",
}),
},
handler: async ({ flags, spinner, colors }) => {
// Type-safe access to validated options
const { env, outdir, memory, variables, watch } = flags;
// ... implementation
},
});2. Interactive Prompts
// prompt is provided via handler args by Bunli
// Text input
const name = await prompt.text("Project name?");
// Selection
const framework = await prompt.select("Runtime framework?", {
options: [
{ label: "Bun", value: "bun" },
{ label: "Node.js", value: "node" },
{ label: "Deno", value: "deno" },
],
});
// Confirmation
const confirmed = await prompt.confirm("Deploy to production?");
// Multi-select
const features = await prompt.multiselect("Features?", {
options: [
{ label: "TypeScript", value: "typescript" },
{ label: "ESLint", value: "eslint" },
{ label: "Prettier", value: "prettier" },
{ label: "Testing", value: "testing" },
],
});3. Progress Indicators
const spin = spinner("Building project...");
// Simulate build steps
const steps = ["Compiling", "Bundling", "Optimizing", "Writing"];
for (const step of steps) {
spin.update(step);
await new Promise((resolve) => setTimeout(resolve, 500));
}
spin.succeed("Build completed!");4. Conditional Flows
handler: async ({ flags, prompt, colors }) => {
if (flags.environment === "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 --pattern "**/*.test.ts" --coverage 80
bun run cli.ts deploy --environment staging
bun run cli.ts setupValidation Patterns
Complex Option Validation
options: {
pattern: option(
z.string().min(1).default('**/*.test.ts'),
{ short: 'p', description: 'Test file pattern' }
),
coverage: option(
z.coerce.number().min(0).max(100).default(80),
{ short: 'c', description: 'Minimum coverage percentage' }
),
watch: option(
z.coerce.boolean().default(false),
{ short: 'w', description: 'Watch for changes' }
)
}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 < 50) {
console.warn(colors.yellow("Low coverage threshold"));
}
};Interactive Patterns
Multi-step Setup
handler: async ({ prompt, colors, spinner }) => {
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: Runtime selection
const framework = await prompt.select("Runtime framework?", {
options: [
{ label: "Bun", value: "bun" },
{ label: "Node.js", value: "node" },
{ label: "Deno", value: "deno" },
],
});
// Step 3: Features
const features = await prompt.multiselect("Features?", {
options: [
{ label: "TypeScript", value: "typescript" },
{ label: "ESLint", value: "eslint" },
{ label: "Prettier", value: "prettier" },
{ label: "Testing", value: "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
const spin = spinner("Creating project...");
spin.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