Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:04:14 +08:00
commit 70c36b5eff
248 changed files with 47482 additions and 0 deletions

View File

@@ -0,0 +1,212 @@
# CLI Patterns Examples Index
Comprehensive examples demonstrating urfave/cli patterns in production-ready applications.
## Example Applications
### 1. Database CLI Tool (`db-cli/`)
**Purpose**: Complete database management CLI with categories, hooks, and connection handling.
**Features**:
- Command categories (Schema, Data, Admin)
- Before hook for connection validation
- After hook for cleanup
- Required and optional flags
- Environment variable fallbacks
**Commands**:
- `migrate` - Run migrations with direction and steps
- `rollback` - Rollback last migration
- `seed` - Seed database with test data
- `backup` - Create database backup
- `restore` - Restore from backup
- `status` - Check database status
- `vacuum` - Optimize database
**Key Patterns**:
```go
// Connection validation in Before hook
Before: func(c *cli.Context) error {
conn := c.String("connection")
// Validate connection
return nil
}
// Cleanup in After hook
After: func(c *cli.Context) error {
// Close connections
return nil
}
```
---
### 2. Deployment CLI Tool (`deploy-cli/`)
**Purpose**: Deployment automation with context management and environment validation.
**Features**:
- Context management with shared state
- Environment validation
- Confirmation prompts for destructive actions
- AWS region configuration
- Build, deploy, and monitor workflows
**Commands**:
- `build` - Build application with tags
- `test` - Run test suite
- `deploy` - Deploy to environment (with confirmation)
- `rollback` - Rollback to previous version
- `logs` - View deployment logs
- `status` - Check deployment status
**Key Patterns**:
```go
// Shared context across commands
type DeployContext struct {
Environment string
AWSRegion string
Verbose bool
}
// Store context in Before hook
ctx := &DeployContext{...}
c.App.Metadata["ctx"] = ctx
// Retrieve in command
ctx := c.App.Metadata["ctx"].(*DeployContext)
```
---
### 3. API Client CLI Tool (`api-cli/`)
**Purpose**: REST API client with HTTP client sharing and authentication.
**Features**:
- HTTP client sharing via context
- Authentication in Before hook
- Multiple HTTP methods (GET, POST, PUT, DELETE)
- Request timeout configuration
- Token masking for security
**Commands**:
- `get` - GET request with headers
- `post` - POST request with data
- `put` - PUT request with data
- `delete` - DELETE request
- `auth-test` - Test authentication
**Key Patterns**:
```go
// HTTP client in context
type APIContext struct {
BaseURL string
Token string
HTTPClient *http.Client
}
// Initialize in Before hook
client := &http.Client{Timeout: timeout}
ctx := &APIContext{
HTTPClient: client,
...
}
// Use in commands
ctx := c.App.Metadata["ctx"].(*APIContext)
resp, err := ctx.HTTPClient.Get(url)
```
---
## Pattern Summary
### Context Management
All three examples demonstrate different context patterns:
- **db-cli**: Connection validation and cleanup
- **deploy-cli**: Shared deployment configuration
- **api-cli**: HTTP client and authentication sharing
### Before/After Hooks
- **Before**: Setup, validation, authentication, connection establishment
- **After**: Cleanup, resource release, connection closing
### Command Categories
Organized command groups for better UX:
- **db-cli**: Schema, Data, Admin
- **deploy-cli**: Build, Deploy, Monitor
- **api-cli**: No categories (simple HTTP verbs)
### Flag Patterns
- Required flags: `--connection`, `--env`, `--token`
- Environment variables: All support env var fallbacks
- Aliases: Short forms (-v, -e, -t)
- Multiple values: StringSlice for headers
- Custom types: Duration for timeouts
### Error Handling
All examples demonstrate:
- Validation in Before hooks
- Proper error returns
- User-friendly error messages
- Exit code handling
## Running the Examples
### Database CLI
```bash
export DATABASE_URL="postgres://user:pass@localhost/mydb"
cd examples/db-cli
go build -o dbctl .
./dbctl migrate
./dbctl backup --output backup.sql
```
### Deployment CLI
```bash
export DEPLOY_ENV=staging
export AWS_REGION=us-east-1
cd examples/deploy-cli
go build -o deploy .
./deploy build --tag v1.0.0
./deploy deploy
```
### API Client CLI
```bash
export API_URL=https://api.example.com
export API_TOKEN=your_token_here
cd examples/api-cli
go build -o api .
./api get /users
./api post /users '{"name":"John"}'
```
## Learning Path
**Beginner**:
1. Start with `db-cli` - demonstrates basic categories and hooks
2. Study Before/After hook patterns
3. Learn flag types and validation
**Intermediate**:
4. Study `deploy-cli` - context management and shared state
5. Learn environment validation
6. Understand confirmation prompts
**Advanced**:
7. Study `api-cli` - HTTP client sharing and authentication
8. Learn complex context patterns
9. Understand resource lifecycle management
## Cross-Language Comparison
Each example can be implemented in other languages:
- **TypeScript**: Use commander.js (see templates/)
- **Python**: Use click or typer (see templates/)
- **Ruby**: Use thor
- **Rust**: Use clap
The patterns translate directly across languages with similar CLI frameworks.

View File

@@ -0,0 +1,69 @@
# API Client CLI Tool Example
Complete REST API client CLI demonstrating:
- HTTP client sharing via context
- Authentication in Before hook
- Multiple HTTP methods (GET, POST, PUT, DELETE)
- Headers and request configuration
- Arguments handling
## Usage
```bash
# Set environment variables
export API_URL=https://api.example.com
export API_TOKEN=your_token_here
# GET request
api get /users
api get /users/123
# POST request
api post /users '{"name": "John", "email": "john@example.com"}'
api post /posts '{"title": "Hello", "body": "World"}' --content-type application/json
# PUT request
api put /users/123 '{"name": "Jane"}'
# DELETE request
api delete /users/123
# Test authentication
api auth-test
# Custom timeout
api --timeout 60s get /slow-endpoint
# Additional headers
api get /users -H "Accept:application/json" -H "X-Custom:value"
```
## Features Demonstrated
1. **Context Management**: Shared HTTPClient and auth across requests
2. **Before Hook**: Authenticates and sets up HTTP client
3. **Arguments**: Commands accept endpoint and data as arguments
4. **Required Flags**: --url and --token are required
5. **Environment Variables**: API_URL, API_TOKEN, API_TIMEOUT fallbacks
6. **Duration Flags**: --timeout uses time.Duration type
7. **Multiple Values**: --header can be specified multiple times
8. **Helper Functions**: maskToken() for secure token display
## HTTP Client Pattern
```go
type APIContext struct {
BaseURL string
Token string
HTTPClient *http.Client
}
// Initialize in Before hook
client := &http.Client{Timeout: timeout}
ctx := &APIContext{...}
c.App.Metadata["ctx"] = ctx
// Use in commands
ctx := c.App.Metadata["ctx"].(*APIContext)
resp, err := ctx.HTTPClient.Get(url)
```

View File

@@ -0,0 +1,205 @@
package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/urfave/cli/v2"
)
type APIContext struct {
BaseURL string
Token string
HTTPClient *http.Client
}
func main() {
app := &cli.App{
Name: "api",
Usage: "REST API client CLI",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "url",
Usage: "API base URL",
EnvVars: []string{"API_URL"},
Required: true,
},
&cli.StringFlag{
Name: "token",
Aliases: []string{"t"},
Usage: "Authentication token",
EnvVars: []string{"API_TOKEN"},
Required: true,
},
&cli.DurationFlag{
Name: "timeout",
Usage: "Request timeout",
Value: 30 * time.Second,
EnvVars: []string{"API_TIMEOUT"},
},
},
Before: func(c *cli.Context) error {
baseURL := c.String("url")
token := c.String("token")
timeout := c.Duration("timeout")
fmt.Println("🔐 Authenticating with API...")
// Create HTTP client
client := &http.Client{
Timeout: timeout,
}
// Store context
ctx := &APIContext{
BaseURL: baseURL,
Token: token,
HTTPClient: client,
}
c.App.Metadata["ctx"] = ctx
fmt.Println("✅ Authentication successful")
return nil
},
Commands: []*cli.Command{
{
Name: "get",
Usage: "GET request",
ArgsUsage: "<endpoint>",
Flags: []cli.Flag{
&cli.StringSliceFlag{
Name: "header",
Aliases: []string{"H"},
Usage: "Additional headers (key:value)",
},
},
Action: func(c *cli.Context) error {
ctx := c.App.Metadata["ctx"].(*APIContext)
if c.NArg() < 1 {
return fmt.Errorf("endpoint required")
}
endpoint := c.Args().Get(0)
url := fmt.Sprintf("%s%s", ctx.BaseURL, endpoint)
fmt.Printf("GET %s\n", url)
fmt.Printf("Authorization: Bearer %s\n", maskToken(ctx.Token))
// In real app: make HTTP request
fmt.Println("Response: 200 OK")
return nil
},
},
{
Name: "post",
Usage: "POST request",
ArgsUsage: "<endpoint> <data>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "content-type",
Aliases: []string{"ct"},
Usage: "Content-Type header",
Value: "application/json",
},
},
Action: func(c *cli.Context) error {
ctx := c.App.Metadata["ctx"].(*APIContext)
if c.NArg() < 2 {
return fmt.Errorf("usage: post <endpoint> <data>")
}
endpoint := c.Args().Get(0)
data := c.Args().Get(1)
url := fmt.Sprintf("%s%s", ctx.BaseURL, endpoint)
contentType := c.String("content-type")
fmt.Printf("POST %s\n", url)
fmt.Printf("Content-Type: %s\n", contentType)
fmt.Printf("Data: %s\n", data)
// In real app: make HTTP POST request
return nil
},
},
{
Name: "put",
Usage: "PUT request",
ArgsUsage: "<endpoint> <data>",
Action: func(c *cli.Context) error {
ctx := c.App.Metadata["ctx"].(*APIContext)
if c.NArg() < 2 {
return fmt.Errorf("usage: put <endpoint> <data>")
}
endpoint := c.Args().Get(0)
data := c.Args().Get(1)
url := fmt.Sprintf("%s%s", ctx.BaseURL, endpoint)
fmt.Printf("PUT %s\n", url)
fmt.Printf("Data: %s\n", data)
return nil
},
},
{
Name: "delete",
Usage: "DELETE request",
ArgsUsage: "<endpoint>",
Action: func(c *cli.Context) error {
ctx := c.App.Metadata["ctx"].(*APIContext)
if c.NArg() < 1 {
return fmt.Errorf("endpoint required")
}
endpoint := c.Args().Get(0)
url := fmt.Sprintf("%s%s", ctx.BaseURL, endpoint)
fmt.Printf("DELETE %s\n", url)
return nil
},
},
{
Name: "auth-test",
Usage: "Test authentication",
Action: func(c *cli.Context) error {
ctx := c.App.Metadata["ctx"].(*APIContext)
fmt.Println("Testing authentication...")
fmt.Printf("API URL: %s\n", ctx.BaseURL)
fmt.Printf("Token: %s\n", maskToken(ctx.Token))
fmt.Println("Status: Authenticated ✅")
return nil
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
func maskToken(token string) string {
if len(token) < 8 {
return "****"
}
return token[:4] + "****" + token[len(token)-4:]
}

View File

@@ -0,0 +1,46 @@
# Database CLI Tool Example
Complete database management CLI demonstrating:
- Command categories (Schema, Data, Admin)
- Before hook for connection validation
- After hook for cleanup
- Required and optional flags
- Environment variable fallbacks
## Usage
```bash
# Set connection string
export DATABASE_URL="postgres://user:pass@localhost/mydb"
# Run migrations
dbctl migrate
dbctl migrate --direction down --steps 2
# Rollback
dbctl rollback
# Seed database
dbctl seed --file seeds/test-data.sql
# Backup and restore
dbctl backup --output backups/db-$(date +%Y%m%d).sql
dbctl restore --input backups/db-20240101.sql
# Admin tasks
dbctl status
dbctl vacuum
# Verbose output
dbctl -v migrate
```
## Features Demonstrated
1. **Command Categories**: Schema, Data, Admin
2. **Global Flags**: --connection, --verbose
3. **Before Hook**: Validates connection before any command
4. **After Hook**: Closes connections after command completes
5. **Required Flags**: backup/restore require file paths
6. **Environment Variables**: DATABASE_URL fallback
7. **Flag Aliases**: -v for --verbose, -d for --direction

View File

@@ -0,0 +1,183 @@
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "dbctl",
Usage: "Database management CLI tool",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "connection",
Aliases: []string{"conn"},
Usage: "Database connection string",
EnvVars: []string{"DATABASE_URL"},
Required: true,
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "Enable verbose output",
},
},
Before: func(c *cli.Context) error {
conn := c.String("connection")
verbose := c.Bool("verbose")
if verbose {
fmt.Println("🔗 Validating database connection...")
}
// Validate connection string
if conn == "" {
return fmt.Errorf("database connection string required")
}
if verbose {
fmt.Println("✅ Connection string validated")
}
return nil
},
After: func(c *cli.Context) error {
if c.Bool("verbose") {
fmt.Println("🔚 Closing database connections...")
}
return nil
},
Commands: []*cli.Command{
// Schema category
{
Name: "migrate",
Category: "Schema",
Usage: "Run database migrations",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "direction",
Aliases: []string{"d"},
Usage: "Migration direction (up/down)",
Value: "up",
},
&cli.IntFlag{
Name: "steps",
Usage: "Number of steps to migrate",
Value: 0,
},
},
Action: func(c *cli.Context) error {
direction := c.String("direction")
steps := c.Int("steps")
fmt.Printf("Running migrations %s", direction)
if steps > 0 {
fmt.Printf(" (%d steps)", steps)
}
fmt.Println()
return nil
},
},
{
Name: "rollback",
Category: "Schema",
Usage: "Rollback last migration",
Action: func(c *cli.Context) error {
fmt.Println("Rolling back last migration...")
return nil
},
},
// Data category
{
Name: "seed",
Category: "Data",
Usage: "Seed database with test data",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "file",
Aliases: []string{"f"},
Usage: "Seed file path",
Value: "seeds/default.sql",
},
},
Action: func(c *cli.Context) error {
file := c.String("file")
fmt.Printf("Seeding database from: %s\n", file)
return nil
},
},
{
Name: "backup",
Category: "Data",
Usage: "Backup database",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Backup output path",
Required: true,
},
},
Action: func(c *cli.Context) error {
output := c.String("output")
fmt.Printf("Backing up database to: %s\n", output)
return nil
},
},
{
Name: "restore",
Category: "Data",
Usage: "Restore database from backup",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "input",
Aliases: []string{"i"},
Usage: "Backup file path",
Required: true,
},
},
Action: func(c *cli.Context) error {
input := c.String("input")
fmt.Printf("Restoring database from: %s\n", input)
return nil
},
},
// Admin category
{
Name: "status",
Category: "Admin",
Usage: "Check database status",
Action: func(c *cli.Context) error {
fmt.Println("Database Status:")
fmt.Println(" Connection: Active")
fmt.Println(" Tables: 15")
fmt.Println(" Size: 245 MB")
return nil
},
},
{
Name: "vacuum",
Category: "Admin",
Usage: "Optimize database",
Action: func(c *cli.Context) error {
fmt.Println("Optimizing database...")
return nil
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}

View File

@@ -0,0 +1,60 @@
# Deployment CLI Tool Example
Complete deployment automation CLI demonstrating:
- Context management with shared state
- Environment validation in Before hook
- Command categories (Build, Deploy, Monitor)
- Confirmation prompts for destructive actions
## Usage
```bash
# Set environment variables
export DEPLOY_ENV=staging
export AWS_REGION=us-west-2
# Build application
deploy --env staging build
deploy -e production build --tag v1.2.3
# Run tests
deploy --env staging test
# Deploy
deploy --env staging deploy
deploy -e production deploy --auto-approve
# Rollback
deploy --env production rollback
# Monitor
deploy --env production logs --follow
deploy -e staging status
```
## Features Demonstrated
1. **Context Management**: Shared DeployContext across commands
2. **Environment Validation**: Before hook validates target environment
3. **Required Flags**: --env is required for all operations
4. **Confirmation Prompts**: Deploy asks for confirmation (unless --auto-approve)
5. **Command Categories**: Build, Deploy, Monitor
6. **Environment Variables**: DEPLOY_ENV, AWS_REGION fallbacks
7. **Shared State**: Context passed to all commands via metadata
## Context Pattern
```go
type DeployContext struct {
Environment string
AWSRegion string
Verbose bool
}
// Store in Before hook
ctx := &DeployContext{...}
c.App.Metadata["ctx"] = ctx
// Retrieve in command
ctx := c.App.Metadata["ctx"].(*DeployContext)
```

View File

@@ -0,0 +1,192 @@
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
type DeployContext struct {
Environment string
AWSRegion string
Verbose bool
}
func main() {
app := &cli.App{
Name: "deploy",
Usage: "Deployment automation CLI",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "env",
Aliases: []string{"e"},
Usage: "Target environment",
EnvVars: []string{"DEPLOY_ENV"},
Required: true,
},
&cli.StringFlag{
Name: "region",
Usage: "AWS region",
EnvVars: []string{"AWS_REGION"},
Value: "us-east-1",
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "Enable verbose output",
},
},
Before: func(c *cli.Context) error {
env := c.String("env")
region := c.String("region")
verbose := c.Bool("verbose")
if verbose {
fmt.Println("🔧 Setting up deployment context...")
}
// Validate environment
validEnvs := []string{"dev", "staging", "production"}
valid := false
for _, e := range validEnvs {
if env == e {
valid = true
break
}
}
if !valid {
return fmt.Errorf("invalid environment: %s (must be dev, staging, or production)", env)
}
// Store context
ctx := &DeployContext{
Environment: env,
AWSRegion: region,
Verbose: verbose,
}
c.App.Metadata["ctx"] = ctx
if verbose {
fmt.Printf("Environment: %s\n", env)
fmt.Printf("Region: %s\n", region)
}
return nil
},
Commands: []*cli.Command{
// Build category
{
Name: "build",
Category: "Build",
Usage: "Build application",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "tag",
Usage: "Docker image tag",
Value: "latest",
},
},
Action: func(c *cli.Context) error {
ctx := c.App.Metadata["ctx"].(*DeployContext)
tag := c.String("tag")
fmt.Printf("Building for environment: %s\n", ctx.Environment)
fmt.Printf("Image tag: %s\n", tag)
return nil
},
},
{
Name: "test",
Category: "Build",
Usage: "Run tests",
Action: func(c *cli.Context) error {
fmt.Println("Running test suite...")
return nil
},
},
// Deploy category
{
Name: "deploy",
Category: "Deploy",
Usage: "Deploy application",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "auto-approve",
Usage: "Skip confirmation prompt",
},
},
Action: func(c *cli.Context) error {
ctx := c.App.Metadata["ctx"].(*DeployContext)
autoApprove := c.Bool("auto-approve")
fmt.Printf("Deploying to %s in %s...\n", ctx.Environment, ctx.AWSRegion)
if !autoApprove {
fmt.Print("Continue? (y/n): ")
// In real app: read user input
fmt.Println("y")
}
fmt.Println("Deployment started...")
return nil
},
},
{
Name: "rollback",
Category: "Deploy",
Usage: "Rollback to previous version",
Action: func(c *cli.Context) error {
ctx := c.App.Metadata["ctx"].(*DeployContext)
fmt.Printf("Rolling back %s deployment...\n", ctx.Environment)
return nil
},
},
// Monitor category
{
Name: "logs",
Category: "Monitor",
Usage: "View deployment logs",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "follow",
Aliases: []string{"f"},
Usage: "Follow log output",
},
},
Action: func(c *cli.Context) error {
follow := c.Bool("follow")
fmt.Println("Fetching logs...")
if follow {
fmt.Println("Following logs (Ctrl+C to stop)...")
}
return nil
},
},
{
Name: "status",
Category: "Monitor",
Usage: "Check deployment status",
Action: func(c *cli.Context) error {
ctx := c.App.Metadata["ctx"].(*DeployContext)
fmt.Printf("Deployment Status (%s):\n", ctx.Environment)
fmt.Println(" Status: Running")
fmt.Println(" Instances: 3/3")
fmt.Println(" Health: Healthy")
return nil
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}