mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Implemented comprehensive CLI functionality: - Fast zero-allocation flag parser for common cases - Support for multiple flag types (boolean, string, number, array) - Interactive prompts with TTY detection and fallback - Performance-optimized with single-pass parsing - Full TypeScript API with type definitions 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
7.0 KiB
7.0 KiB
Bun CLI Parser & Interactive System Design
Three Implementation Approaches
Approach 1: TypeScript-First with Zig Backend
Pros: Familiar API, easy to extend, good DX Cons: JS overhead for parsing
// User-facing API
const cli = Bun.CLI.create({
name: "myapp",
version: "1.0.0",
flags: {
verbose: { type: "boolean", short: "v", default: false },
port: { type: "number", short: "p", default: 3000 },
files: { type: "array", of: "string" },
config: { type: "string", env: "MY_APP_CONFIG" }
}
});
const args = cli.parse();
// Interactive mode
if (!args.config && cli.isTTY) {
args.config = await cli.prompt.select({
message: "Choose config",
choices: ["dev", "prod", "test"]
});
}
Approach 2: Pure Zig with Code Generation
Pros: Zero overhead, compile-time validation, fastest possible Cons: Less flexible, harder to maintain
// Generated from schema at build time
const MyAppCLI = generateCLI(.{
.name = "myapp",
.flags = .{
.verbose = .{ .type = .boolean, .short = 'v' },
.port = .{ .type = .number, .short = 'p', .default = 3000 },
.files = .{ .type = .array, .of = .string },
},
});
// Runtime usage
const args = try MyAppCLI.parse(std.os.argv);
Approach 3: Hybrid with Smart Optimization (RECOMMENDED)
Pros: Best of both worlds, progressive enhancement Cons: More complex implementation
// Fast path for simple cases
const args = Bun.CLI.parseSimple(); // Zero alloc for basic flags
// Full featured for complex cases
const cli = new Bun.CLI({
// Schema definition
schema: {
commands: {
serve: {
flags: {
port: { type: "number", short: "p" },
host: { type: "string", short: "h" }
}
}
}
},
// Performance hints
hints: {
maxArgs: 10, // Pre-allocate buffers
commonFlags: ["v", "h", "help"], // Optimize these
lazyInteractive: true // Load interactive only when needed
}
});
Core Implementation Plan
Phase 1: Fast Flag Parser (Week 1)
// Core parser with zero allocations for common cases
export namespace Bun.CLI {
export interface ParseOptions {
// Stop at first non-flag
stopEarly?: boolean;
// Allow unknown flags
allowUnknown?: boolean;
// Parse numbers/booleans
autoType?: boolean;
}
export function parse(
args?: string[],
options?: ParseOptions
): Record<string, any>;
// Fast path for simple cases
export function parseSimple(args?: string[]): {
_: string[];
[key: string]: any;
};
}
Phase 2: Type-Safe Schema (Week 2)
// Type-safe flag definitions
export interface FlagSchema {
type: "string" | "number" | "boolean" | "array" | "enum";
short?: string;
long?: string;
default?: any;
required?: boolean;
env?: string; // Environment variable fallback
validate?: (value: any) => boolean | string;
transform?: (value: any) => any;
}
// Advanced array handling
export interface ArrayFlagSchema extends FlagSchema {
type: "array";
of: "string" | "number";
separator?: string; // For comma-separated values
accumulate?: boolean; // Multiple --flag values
}
Phase 3: Interactive System (Week 3)
export namespace Bun.CLI.Interactive {
// TTY detection with fallback
export const isTTY: boolean;
// Core prompt types
export interface PromptOptions {
message: string;
default?: any;
validate?: (input: any) => boolean | string;
// Non-TTY fallback
fallback?: () => any;
}
export async function text(options: PromptOptions): Promise<string>;
export async function confirm(options: PromptOptions): Promise<boolean>;
export async function select<T>(options: SelectOptions<T>): Promise<T>;
export async function multiselect<T>(options: MultiSelectOptions<T>): Promise<T[]>;
// Advanced: Form with multiple fields
export async function form<T>(schema: FormSchema): Promise<T>;
}
Phase 4: Performance Optimizations
// Zig backend for hot paths
pub const FastParser = struct {
allocator: std.mem.Allocator,
args_buffer: [256][]const u8, // Stack allocation for common case
pub fn parse(args: []const []const u8) ParseResult {
// Single pass parsing
// Zero-copy string slicing
// Compile-time type coercion
}
};
// Incremental renderer for interactive mode
pub const IncrementalRenderer = struct {
last_frame: []u8,
dirty_regions: std.ArrayList(Region),
pub fn render(content: []const u8) !void {
// Diff-based updates
// Batched escape sequences
// Adaptive frame rate
}
};
Performance Benchmarks Target
Simple flag parsing (10 args):
- Target: < 100ns
- Baseline (minimist): ~1μs
Complex parsing (100 args, nested commands):
- Target: < 1μs
- Baseline (yargs): ~50μs
Interactive prompt render:
- Target: < 1ms per frame
- 60fps for smooth animations
Memory usage:
- Zero allocations for < 16 args
- Single arena for complex parsing
- Reusable buffers for interactive
Edge Cases Handled
- No TTY: Graceful fallback to simple prompts or defaults
- Piped input: Detect and handle stdin/stdout pipes
- CI environment: Auto-detect CI and disable interactive
- Windows Terminal: Special handling for Windows console
- SSH sessions: Detect and adapt rendering
- Screen readers: Accessibility mode with plain text
- Partial args: Handle incomplete flag values
- Unicode: Full UTF-8 support in prompts
- Signals: Proper cleanup on SIGINT/SIGTERM
- Large inputs: Stream processing for huge arg lists
API Examples
Basic Usage
// Simple parsing
const args = Bun.CLI.parse();
console.log(args.verbose, args.port);
// With schema
const cli = Bun.CLI.create({
flags: {
verbose: { type: "boolean", short: "v" },
port: { type: "number", default: 3000 }
}
});
const { verbose, port } = cli.parse();
Interactive Usage
// Auto-detect TTY and fallback
const name = await Bun.CLI.prompt.text({
message: "Your name?",
fallback: () => process.env.USER || "anonymous"
});
// Complex form
const config = await Bun.CLI.prompt.form({
fields: {
database: { type: "select", choices: ["postgres", "mysql", "sqlite"] },
port: { type: "number", default: 5432 },
ssl: { type: "confirm", default: true }
}
});
Advanced Subcommands
const cli = Bun.CLI.create({
commands: {
serve: {
handler: (args) => startServer(args),
flags: { port: { type: "number" } }
},
build: {
handler: (args) => runBuild(args),
flags: { watch: { type: "boolean" } }
}
}
});
await cli.run();
Testing Strategy
- Unit tests: Each parser component
- Integration tests: Full CLI flows
- Performance tests: Benchmark suite
- TTY simulation: Mock terminal tests
- Cross-platform: Windows, macOS, Linux
- Fuzzing: Random input generation
- Memory tests: Leak detection
- Stress tests: Large arg counts