# Enterprise CLI Example Complete production-ready oclif CLI with all best practices. ## Overview This example demonstrates: - Custom base command with common functionality - Configuration management - Logging system - Error handling - Plugin support - Comprehensive testing - CI/CD integration - Auto-update capability ## Project Structure ``` enterprise-cli/ ├── package.json ├── tsconfig.json ├── .eslintrc.json ├── .github/ │ └── workflows/ │ ├── test.yml │ └── release.yml ├── src/ │ ├── base-command.ts │ ├── config/ │ │ ├── manager.ts │ │ └── schema.ts │ ├── commands/ │ │ ├── deploy.ts │ │ ├── status.ts │ │ └── config/ │ │ ├── get.ts │ │ └── set.ts │ ├── hooks/ │ │ ├── init.ts │ │ └── prerun.ts │ ├── utils/ │ │ ├── logger.ts │ │ └── error-handler.ts │ └── index.ts ├── test/ │ ├── commands/ │ ├── helpers/ │ └── fixtures/ └── docs/ └── commands/ ``` ## Base Command Implementation File: `src/base-command.ts` ```typescript import { Command, Flags } from '@oclif/core' import { ConfigManager } from './config/manager' import { Logger } from './utils/logger' import { ErrorHandler } from './utils/error-handler' export default abstract class BaseCommand extends Command { protected configManager!: ConfigManager protected logger!: Logger protected errorHandler!: ErrorHandler static baseFlags = { config: Flags.string({ char: 'c', description: 'Path to config file', env: 'CLI_CONFIG', }), 'log-level': Flags.string({ description: 'Set log level', options: ['error', 'warn', 'info', 'debug'], default: 'info', env: 'LOG_LEVEL', }), json: Flags.boolean({ description: 'Output as JSON', default: false, }), 'no-color': Flags.boolean({ description: 'Disable colors', default: false, }), } async init(): Promise { await super.init() // Initialize logger const { flags } = await this.parse(this.constructor as typeof BaseCommand) this.logger = new Logger(flags['log-level'], !flags['no-color']) // Initialize config manager this.configManager = new ConfigManager(flags.config) await this.configManager.load() // Initialize error handler this.errorHandler = new ErrorHandler(this.logger) // Log initialization this.logger.debug(`Initialized ${this.id}`) } protected async catch(err: Error & { exitCode?: number }): Promise { return this.errorHandler.handle(err) } protected output(data: any, humanMessage?: string): void { const { flags } = this.parse(this.constructor as typeof BaseCommand) if (flags.json) { this.log(JSON.stringify(data, null, 2)) } else if (humanMessage) { this.log(humanMessage) } else { this.log(JSON.stringify(data, null, 2)) } } } ``` ## Configuration Manager File: `src/config/manager.ts` ```typescript import * as fs from 'fs-extra' import * as path from 'path' import * as os from 'os' export class ConfigManager { private config: any = {} private configPath: string constructor(customPath?: string) { this.configPath = customPath || this.getDefaultConfigPath() } async load(): Promise { if (await fs.pathExists(this.configPath)) { this.config = await fs.readJson(this.configPath) } else { // Create default config this.config = this.getDefaultConfig() await this.save() } } async save(): Promise { await fs.ensureDir(path.dirname(this.configPath)) await fs.writeJson(this.configPath, this.config, { spaces: 2 }) } get(key: string, defaultValue?: any): any { const keys = key.split('.') let value = this.config for (const k of keys) { if (value && typeof value === 'object' && k in value) { value = value[k] } else { return defaultValue } } return value } set(key: string, value: any): void { const keys = key.split('.') const lastKey = keys.pop()! let current = this.config for (const k of keys) { if (!(k in current) || typeof current[k] !== 'object') { current[k] = {} } current = current[k] } current[lastKey] = value } private getDefaultConfigPath(): string { return path.join(os.homedir(), '.config', 'mycli', 'config.json') } private getDefaultConfig(): any { return { version: '1.0.0', defaults: { region: 'us-east-1', timeout: 30000, }, } } } ``` ## Logger Implementation File: `src/utils/logger.ts` ```typescript import chalk from 'chalk' export class Logger { constructor( private level: string = 'info', private color: boolean = true ) {} debug(message: string): void { if (this.shouldLog('debug')) { this.output('DEBUG', message, chalk.gray) } } info(message: string): void { if (this.shouldLog('info')) { this.output('INFO', message, chalk.blue) } } warn(message: string): void { if (this.shouldLog('warn')) { this.output('WARN', message, chalk.yellow) } } error(message: string, error?: Error): void { if (this.shouldLog('error')) { this.output('ERROR', message, chalk.red) if (error && error.stack) { console.error(chalk.red(error.stack)) } } } success(message: string): void { if (this.shouldLog('info')) { this.output('SUCCESS', message, chalk.green) } } private shouldLog(level: string): boolean { const levels = ['error', 'warn', 'info', 'debug'] const currentIndex = levels.indexOf(this.level) const messageIndex = levels.indexOf(level) return messageIndex <= currentIndex } private output(level: string, message: string, colorFn: any): void { const timestamp = new Date().toISOString() const prefix = this.color ? colorFn(`[${level}]`) : `[${level}]` console.log(`${timestamp} ${prefix} ${message}`) } } ``` ## Enterprise Deploy Command File: `src/commands/deploy.ts` ```typescript import BaseCommand from '../base-command' import { Flags, Args, ux } from '@oclif/core' export default class Deploy extends BaseCommand { static description = 'Deploy application to environment' static examples = [ '<%= config.bin %> deploy myapp --env production', '<%= config.bin %> deploy myapp --env staging --auto-approve', ] static flags = { ...BaseCommand.baseFlags, env: Flags.string({ char: 'e', description: 'Environment to deploy to', options: ['development', 'staging', 'production'], required: true, }), 'auto-approve': Flags.boolean({ description: 'Skip confirmation prompt', default: false, }), 'rollback-on-failure': Flags.boolean({ description: 'Automatically rollback on failure', default: true, }), } static args = { app: Args.string({ description: 'Application name', required: true, }), } async run(): Promise { const { args, flags } = await this.parse(Deploy) this.logger.info(`Starting deployment of ${args.app} to ${flags.env}`) // Confirmation prompt (skip in CI or with auto-approve) if (!flags['auto-approve'] && !process.env.CI) { const confirmed = await ux.confirm( `Deploy ${args.app} to ${flags.env}? (y/n)` ) if (!confirmed) { this.logger.info('Deployment cancelled') return } } // Pre-deployment checks ux.action.start('Running pre-deployment checks') await this.runPreDeploymentChecks(args.app, flags.env) ux.action.stop('passed') // Deploy ux.action.start(`Deploying ${args.app}`) try { const result = await this.deploy(args.app, flags.env) ux.action.stop('done') this.logger.success(`Deployed ${args.app} to ${flags.env}`) this.output(result, `Deployment URL: ${result.url}`) } catch (error) { ux.action.stop('failed') if (flags['rollback-on-failure']) { this.logger.warn('Deployment failed, rolling back...') await this.rollback(args.app, flags.env) } throw error } } private async runPreDeploymentChecks( app: string, env: string ): Promise { // Check credentials // Validate app exists // Check environment health // Verify dependencies await new Promise(resolve => setTimeout(resolve, 1000)) } private async deploy(app: string, env: string): Promise { // Actual deployment logic await new Promise(resolve => setTimeout(resolve, 3000)) return { app, env, version: '1.2.3', url: `https://${app}.${env}.example.com`, deployedAt: new Date().toISOString(), } } private async rollback(app: string, env: string): Promise { // Rollback logic await new Promise(resolve => setTimeout(resolve, 2000)) this.logger.info('Rollback complete') } } ``` ## Testing Setup File: `test/helpers/test-context.ts` ```typescript import * as path from 'path' import * as fs from 'fs-extra' import { Config } from '@oclif/core' export class TestContext { testDir: string config: Config constructor() { this.testDir = path.join(__dirname, '../fixtures/test-run') } async setup(): Promise { await fs.ensureDir(this.testDir) this.config = await Config.load() } async teardown(): Promise { await fs.remove(this.testDir) } async createConfigFile(config: any): Promise { const configPath = path.join(this.testDir, 'config.json') await fs.writeJson(configPath, config) return configPath } } ``` ## Key Enterprise Features 1. **Base Command**: Shared functionality across all commands 2. **Configuration**: Centralized config management 3. **Logging**: Structured logging with levels 4. **Error Handling**: Consistent error handling 5. **Confirmation Prompts**: Interactive confirmations 6. **Rollback**: Automatic rollback on failure 7. **Pre-deployment Checks**: Validation before operations 8. **CI Detection**: Different behavior in CI environments 9. **JSON Output**: Machine-readable output option 10. **Environment Variables**: Config via env vars