Examples
Dev Server CLI
Development server with advanced plugin system and configuration management
Dev Server CLI Example
A development server CLI showcasing advanced plugin system, configuration management, and long-running processes. Demonstrates production-ready patterns for complex CLI applications.
Overview
This example consolidates plugin system and configuration patterns:
- Plugin system with lifecycle hooks and type-safe stores
- Configuration management from multiple sources
- Long-running processes with graceful shutdown
- Real-time operations like log following
- Type-safe plugin context for command access
- Advanced patterns for production CLIs
Commands
start - Start Development Server
bun run cli.ts start --port 3000 --host localhost --watch --openStarts a development server with hot reload:
- Configurable port and host
- File watching and hot reload
- Browser auto-opening
- Graceful shutdown handling
- Plugin context access
build - Build for Production
bun run cli.ts build --output dist --minify --sourcemap --target nodeBuilds the project for production:
- Multi-step progress indicators
- Plugin metrics recording
- Configuration access
- Build optimization options
env - Environment Management
bun run cli.ts env --set API_KEY=abc123
bun run cli.ts env --get API_KEY
bun run cli.ts env --listManages environment variables:
- Set, get, and list environment variables
- File-based configuration
- Plugin event recording
- Conditional command flows
logs - View Server Logs
bun run cli.ts logs --follow --lines 100 --level info --service serverViews and follows server logs:
- Real-time log streaming
- Log filtering and formatting
- Process signal handling
- Service-specific filtering
Plugin System
Built-in Plugins
import { configMergerPlugin } from '@bunli/plugin-config'
import { aiAgentPlugin } from '@bunli/plugin-ai-detect'
const cli = await createCLI({
plugins: [
configMergerPlugin({
sources: ['.devserverrc.json', 'devserver.config.json']
}),
aiAgentPlugin({ verbose: true })
]
})Custom Metrics Plugin
// plugins/metrics.ts
import { createPlugin } from '@bunli/core/plugin'
interface MetricsStore {
metrics: {
events: Array<{
name: string
timestamp: Date
data: Record<string, any>
}>
recordEvent: (name: string, data?: Record<string, any>) => void
getEvents: (name?: string) => Array<{ name: string; timestamp: Date; data: Record<string, any> }>
clearEvents: () => void
}
}
export const metricsPlugin = createPlugin<MetricsStore>({
name: 'metrics',
store: {
metrics: {
events: [],
recordEvent(name: string, data: Record<string, any> = {}) {
this.events.push({
name,
timestamp: new Date(),
data
})
// Keep only last 100 events to prevent memory leaks
if (this.events.length > 100) {
this.events = this.events.slice(-100)
}
},
getEvents(name?: string) {
if (name) {
return this.events.filter(event => event.name === name)
}
return [...this.events]
},
clearEvents() {
this.events = []
}
}
},
beforeCommand({ store, flags }) {
// Record command start
store.metrics.recordEvent('command_started', {
command: flags._[0] || 'unknown',
timestamp: new Date().toISOString()
})
},
afterCommand({ store, flags }) {
// Record command completion
store.metrics.recordEvent('command_completed', {
command: flags._[0] || 'unknown',
timestamp: new Date().toISOString()
})
},
onError({ store, error, flags }) {
// Record command errors
store.metrics.recordEvent('command_error', {
command: flags._[0] || 'unknown',
error: error.message,
timestamp: new Date().toISOString()
})
}
})Key Features Demonstrated
1. Plugin Context Access
handler: async ({ flags, context, spinner, colors }) => {
const { port, host, watch, open } = flags
spinner.start('Starting development server...')
// Access plugin context
if (context?.store.metrics) {
context.store.metrics.recordEvent('server_started', { port, host })
}
if (context?.store.config) {
console.log(colors.dim(`Config loaded: ${JSON.stringify(context.store.config, null, 2)}`))
}
// Continue with server startup
}2. Long-running Processes
handler: async ({ flags, colors, context }) => {
// Start server
console.log(colors.green(`Server started on http://${host}:${port}`))
// Keep the process alive
process.on('SIGINT', () => {
console.log(colors.yellow('\nShutting down server...'))
// Record shutdown event
if (context?.store.metrics) {
context.store.metrics.recordEvent('server_shutdown', {
timestamp: new Date().toISOString()
})
}
process.exit(0)
})
// Simulate server running
await new Promise(() => {}) // Never resolves
}3. Real-time Operations
handler: async ({ flags, colors, context }) => {
const { follow, lines, level, service } = flags
if (follow) {
console.log(colors.cyan('Following logs (Press Ctrl+C to stop)...'))
// Simulate log streaming
const interval = setInterval(() => {
const timestamp = new Date().toISOString()
const logLevel = getRandomLogLevel()
const message = getRandomMessage()
console.log(`${colors.dim(timestamp)} ${levelColor(logLevel)} ${message}`)
}, 1000)
// Handle Ctrl+C
process.on('SIGINT', () => {
clearInterval(interval)
console.log(colors.yellow('\nStopped following logs'))
process.exit(0)
})
}
}4. Configuration Management
// Configuration is loaded automatically by the config plugin
// Access it through plugin context
if (context?.store.config) {
const config = context.store.config
// Use configuration values
const defaultPort = config.port || 3000
const defaultHost = config.host || 'localhost'
console.log(colors.dim(`Using config: ${JSON.stringify(config, null, 2)}`))
}Project Structure
dev-server/
├── cli.ts # Main CLI file
├── commands/
│ ├── start.ts # Start development server
│ ├── build.ts # Build for production
│ ├── env.ts # Environment management
│ └── logs.ts # Log viewing and following
├── plugins/
│ └── metrics.ts # Custom metrics plugin
├── bunli.config.ts # Build configuration
├── package.json # Dependencies and scripts
└── README.md # Example documentationRunning the Example
# Navigate to the example
cd examples/dev-server
# Install dependencies
bun install
# Run in development mode
bun run dev
# Try the commands
bun run cli.ts start --port 3000 --watch
bun run cli.ts build --minify --sourcemap
bun run cli.ts env --set DEBUG=true
bun run cli.ts logs --followPlugin System Deep Dive
1. Plugin Lifecycle Hooks
export const myPlugin = createPlugin({
name: 'my-plugin',
store: { count: 0 },
// Called before any command runs
beforeCommand({ store, flags }) {
store.count++
console.log(`Command ${flags._[0]} starting...`)
},
// Called after command completes successfully
afterCommand({ store, flags }) {
console.log(`Command ${flags._[0]} completed`)
},
// Called when command throws an error
onError({ store, error, flags }) {
console.error(`Command ${flags._[0]} failed:`, error.message)
}
})2. Type-safe Plugin Stores
interface MyStore {
data: {
users: string[]
settings: Record<string, any>
}
addUser: (user: string) => void
getSettings: () => Record<string, any>
}
export const myPlugin = createPlugin<MyStore>({
name: 'my-plugin',
store: {
data: {
users: [],
settings: {}
},
addUser(user: string) {
this.data.users.push(user)
},
getSettings() {
return this.data.settings
}
}
})3. Plugin Configuration
// Plugin with configuration
export const configurablePlugin = createPlugin({
name: 'configurable',
store: { enabled: false },
// Plugin can accept configuration
configure(config: { enabled: boolean }) {
this.store.enabled = config.enabled
}
})
// Use with configuration
const cli = await createCLI({
plugins: [
configurablePlugin.configure({ enabled: true })
]
})Configuration Management
1. Multiple Configuration Sources
import { configMergerPlugin } from '@bunli/plugin-config'
const cli = await createCLI({
plugins: [
configMergerPlugin({
sources: [
'.devserverrc.json', // Project-specific config
'devserver.config.json', // Alternative config file
'package.json' // Package.json config section
]
})
]
})2. Configuration Access
// Access configuration in commands
handler: async ({ context, colors }) => {
if (context?.store.config) {
const config = context.store.config
// Use configuration values
const port = config.port || 3000
const host = config.host || 'localhost'
console.log(colors.dim(`Config: ${JSON.stringify(config, null, 2)}`))
}
}Key Takeaways
- Plugin System: Extensible architecture with lifecycle hooks
- Type Safety: Full TypeScript support for plugin stores
- Configuration: Multi-source configuration management
- Long-running Processes: Server management with graceful shutdown
- Real-time Operations: Log following and live updates
- Production Patterns: Advanced patterns for complex CLIs
Next Steps
- Plugin System Guide - Deep dive into plugins
- Configuration Management - Advanced config patterns
- Long-running Processes - Server and daemon patterns
- Real-time CLI - Live updates and streaming