Files
bun.sh/docs/cli-parser-design.md
Claude Bot 94c24d708e Add Bun.CLI flag parser and interactive system
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>
2025-09-15 12:52:06 +00:00

287 lines
7.0 KiB
Markdown

# 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
```typescript
// 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
```zig
// 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
```typescript
// 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)
```typescript
// 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)
```typescript
// 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)
```typescript
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
// 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
1. **No TTY**: Graceful fallback to simple prompts or defaults
2. **Piped input**: Detect and handle stdin/stdout pipes
3. **CI environment**: Auto-detect CI and disable interactive
4. **Windows Terminal**: Special handling for Windows console
5. **SSH sessions**: Detect and adapt rendering
6. **Screen readers**: Accessibility mode with plain text
7. **Partial args**: Handle incomplete flag values
8. **Unicode**: Full UTF-8 support in prompts
9. **Signals**: Proper cleanup on SIGINT/SIGTERM
10. **Large inputs**: Stream processing for huge arg lists
## API Examples
### Basic Usage
```typescript
// 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
```typescript
// 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
```typescript
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
1. **Unit tests**: Each parser component
2. **Integration tests**: Full CLI flows
3. **Performance tests**: Benchmark suite
4. **TTY simulation**: Mock terminal tests
5. **Cross-platform**: Windows, macOS, Linux
6. **Fuzzing**: Random input generation
7. **Memory tests**: Leak detection
8. **Stress tests**: Large arg counts