Files
gh-vanman2024-cli-builder-p…/skills/oclif-patterns/examples/quick-reference.md
2025-11-30 09:04:14 +08:00

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
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

  1. Always provide clear descriptions for flags and commands
  2. Use char flags for common options (e.g., -v for verbose)
  3. Validate inputs early in the run() method
  4. Use ux.action.start/stop for long operations
  5. Handle errors gracefully with helpful messages
  6. Test both success and failure cases
  7. Generate documentation with oclif manifest
  8. Use TypeScript strict mode
  9. Follow naming conventions (kebab-case for commands)
  10. Keep commands focused and single-purpose