6.4 KiB
6.4 KiB
oclif Patterns Quick Reference
Fast lookup for common oclif patterns and commands.
Command Creation
Basic Command
import { Command, Flags, Args } from '@oclif/core'
export default class MyCommand extends Command {
static description = 'Description'
static flags = {
name: Flags.string({ char: 'n', required: true }),
}
async run(): Promise<void> {
const { flags } = await this.parse(MyCommand)
this.log(`Hello ${flags.name}`)
}
}
Using Scripts
# Create command from template
./scripts/create-command.sh my-command basic
# Create advanced command
./scripts/create-command.sh deploy advanced
# Create async command
./scripts/create-command.sh fetch async
Flag Patterns
String Flag
name: Flags.string({
char: 'n',
description: 'Name',
required: true,
default: 'World',
})
Boolean Flag
verbose: Flags.boolean({
char: 'v',
description: 'Verbose output',
default: false,
allowNo: true, // Enables --no-verbose
})
Integer Flag
port: Flags.integer({
char: 'p',
description: 'Port number',
min: 1024,
max: 65535,
default: 3000,
})
Option Flag (Enum)
env: Flags.string({
char: 'e',
description: 'Environment',
options: ['dev', 'staging', 'prod'],
required: true,
})
Multiple Values
tags: Flags.string({
char: 't',
description: 'Tags',
multiple: true,
})
// Usage: --tags=foo --tags=bar
Custom Flag
date: Flags.custom<Date>({
parse: async (input) => new Date(input),
})
Argument Patterns
Required Argument
static args = {
file: Args.string({
description: 'File path',
required: true,
}),
}
File Argument
static args = {
file: Args.file({
description: 'Input file',
exists: true, // Validates file exists
}),
}
Directory Argument
static args = {
dir: Args.directory({
description: 'Target directory',
exists: true,
}),
}
Output Patterns
Simple Log
this.log('Message')
Error with Exit
this.error('Error message', { exit: 1 })
Warning
this.warn('Warning message')
Spinner
import { ux } from '@oclif/core'
ux.action.start('Processing')
// ... work
ux.action.stop('done')
Progress Bar
import { ux } from '@oclif/core'
const total = 100
ux.progress.start(total)
for (let i = 0; i < total; i++) {
ux.progress.update(i)
}
ux.progress.stop()
Table Output
import { ux } from '@oclif/core'
ux.table(data, {
id: {},
name: {},
status: { extended: true },
})
Prompt
import { ux } from '@oclif/core'
const name = await ux.prompt('What is your name?')
const password = await ux.prompt('Password', { type: 'hide' })
const confirmed = await ux.confirm('Continue? (y/n)')
Testing Patterns
Basic Test
import { expect, test } from '@oclif/test'
test
.stdout()
.command(['mycommand', '--name', 'Test'])
.it('runs command', ctx => {
expect(ctx.stdout).to.contain('Test')
})
Test with Error
test
.command(['mycommand'])
.catch(error => {
expect(error.message).to.contain('Missing')
})
.it('fails without flags')
Test with Mock
test
.nock('https://api.example.com', api =>
api.get('/data').reply(200, { result: 'success' })
)
.stdout()
.command(['mycommand'])
.it('handles API call', ctx => {
expect(ctx.stdout).to.contain('success')
})
Test with Environment
test
.env({ API_KEY: 'test-key' })
.stdout()
.command(['mycommand'])
.it('reads from env')
Plugin Patterns
Create Plugin
./scripts/create-plugin.sh my-plugin
Link Plugin
mycli plugins:link ./plugin-my-plugin
Install Plugin
mycli plugins:install @mycli/plugin-name
Hook Patterns
Init Hook
import { Hook } from '@oclif/core'
const hook: Hook<'init'> = async function (opts) {
// Runs before any command
}
export default hook
Prerun Hook
const hook: Hook<'prerun'> = async function (opts) {
const { Command, argv } = opts
// Runs before each command
}
Common Commands
Generate Documentation
npm run prepack
# Generates oclif.manifest.json and updates README.md
Build
npm run build
Test
npm test
npm run test:coverage
Lint
npm run lint
npm run lint:fix
Validation
Validate Command
./scripts/validate-command.sh src/commands/mycommand.ts
Validate Plugin
./scripts/validate-plugin.sh ./my-plugin
Validate Tests
./scripts/validate-tests.sh
Configuration Patterns
Read Config
const configPath = path.join(this.config.home, '.myclirc')
const config = await fs.readJson(configPath)
Write Config
await fs.writeJson(configPath, config, { spaces: 2 })
Environment Variables
const apiKey = process.env.API_KEY || this.error('API_KEY required')
Error Handling
Try-Catch
try {
await riskyOperation()
} catch (error) {
this.error(`Operation failed: ${error.message}`, { exit: 1 })
}
Custom Error
if (!valid) {
this.error('Invalid input', {
exit: 1,
suggestions: ['Try --help for usage']
})
}
Async Patterns
Concurrent Operations
const results = await Promise.all([
operation1(),
operation2(),
operation3(),
])
Sequential Operations
for (const item of items) {
await processItem(item)
}
With Timeout
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 5000)
try {
const response = await fetch(url, { signal: controller.signal })
} finally {
clearTimeout(timeout)
}
Best Practices
- Always provide clear descriptions for flags and commands
- Use char flags for common options (e.g., -v for verbose)
- Validate inputs early in the run() method
- Use ux.action.start/stop for long operations
- Handle errors gracefully with helpful messages
- Test both success and failure cases
- Generate documentation with oclif manifest
- Use TypeScript strict mode
- Follow naming conventions (kebab-case for commands)
- Keep commands focused and single-purpose