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

308 lines
5.7 KiB
Markdown

# Plugin CLI Example
Complete example of an oclif CLI with plugin support.
## Overview
This example shows:
- Main CLI with core commands
- Plugin system for extensibility
- Plugin installation and management
- Shared hooks between CLI and plugins
## Main CLI Structure
```
mycli/
├── package.json
├── src/
│ ├── commands/
│ │ └── core.ts
│ └── hooks/
│ └── init.ts
└── plugins/
└── plugin-deploy/
├── package.json
└── src/
└── commands/
└── deploy.ts
```
## Step 1: Create Main CLI
### Main CLI package.json
```json
{
"name": "mycli",
"version": "1.0.0",
"oclif": {
"bin": "mycli",
"commands": "./lib/commands",
"plugins": [
"@oclif/plugin-help",
"@oclif/plugin-plugins"
],
"hooks": {
"init": "./lib/hooks/init"
}
},
"dependencies": {
"@oclif/core": "^3.0.0",
"@oclif/plugin-help": "^6.0.0",
"@oclif/plugin-plugins": "^4.0.0"
}
}
```
### Core Command
File: `src/commands/core.ts`
```typescript
import { Command, Flags } from '@oclif/core'
export default class Core extends Command {
static description = 'Core CLI functionality'
static flags = {
version: Flags.boolean({
char: 'v',
description: 'Show CLI version',
}),
}
async run(): Promise<void> {
const { flags } = await this.parse(Core)
if (flags.version) {
this.log(`Version: ${this.config.version}`)
return
}
this.log('Core CLI is running')
// List installed plugins
const plugins = this.config.plugins
this.log(`\nInstalled plugins: ${plugins.length}`)
plugins.forEach(p => {
this.log(` - ${p.name} (${p.version})`)
})
}
}
```
### Init Hook
File: `src/hooks/init.ts`
```typescript
import { Hook } from '@oclif/core'
const hook: Hook<'init'> = async function (opts) {
// Initialize CLI
this.debug('Initializing mycli...')
// Check for updates, load config, etc.
}
export default hook
```
## Step 2: Create Plugin
### Plugin package.json
```json
{
"name": "@mycli/plugin-deploy",
"version": "1.0.0",
"description": "Deployment plugin for mycli",
"oclif": {
"bin": "mycli",
"commands": "./lib/commands",
"topics": {
"deploy": {
"description": "Deployment commands"
}
}
},
"dependencies": {
"@oclif/core": "^3.0.0"
}
}
```
### Deploy Command
File: `plugins/plugin-deploy/src/commands/deploy.ts`
```typescript
import { Command, Flags } from '@oclif/core'
export default class Deploy extends Command {
static description = 'Deploy application'
static examples = [
'<%= config.bin %> deploy --env production',
]
static flags = {
env: Flags.string({
char: 'e',
description: 'Environment to deploy to',
options: ['development', 'staging', 'production'],
required: true,
}),
force: Flags.boolean({
char: 'f',
description: 'Force deployment',
default: false,
}),
}
async run(): Promise<void> {
const { flags } = await this.parse(Deploy)
this.log(`Deploying to ${flags.env}...`)
if (flags.force) {
this.log('Force deployment enabled')
}
// Deployment logic here
this.log('✓ Deployment successful')
}
}
```
## Step 3: Build and Link Plugin
```bash
# Build main CLI
cd mycli
npm run build
# Build plugin
cd plugins/plugin-deploy
npm run build
# Link plugin to main CLI
cd ../../
mycli plugins:link ./plugins/plugin-deploy
```
## Step 4: Use Plugin Commands
```bash
# List plugins
mycli plugins
# Use plugin command
mycli deploy --env production
# Get help for plugin command
mycli deploy --help
```
## Step 5: Install Plugin from npm
### Publish Plugin
```bash
cd plugins/plugin-deploy
npm publish
```
### Install Plugin
```bash
mycli plugins:install @mycli/plugin-deploy
```
## Plugin Management Commands
```bash
# List installed plugins
mycli plugins
# Install plugin
mycli plugins:install @mycli/plugin-name
# Update plugin
mycli plugins:update @mycli/plugin-name
# Uninstall plugin
mycli plugins:uninstall @mycli/plugin-name
# Link local plugin (development)
mycli plugins:link /path/to/plugin
```
## Advanced: Plugin with Hooks
File: `plugins/plugin-deploy/src/hooks/prerun.ts`
```typescript
import { Hook } from '@oclif/core'
const hook: Hook<'prerun'> = async function (opts) {
// Check deployment prerequisites
if (opts.Command.id === 'deploy') {
this.log('Checking deployment prerequisites...')
// Check environment, credentials, etc.
}
}
export default hook
```
Register in plugin package.json:
```json
{
"oclif": {
"hooks": {
"prerun": "./lib/hooks/prerun"
}
}
}
```
## Key Concepts Demonstrated
1. **Plugin System**: @oclif/plugin-plugins integration
2. **Plugin Discovery**: Automatic command loading from plugins
3. **Plugin Management**: Install, update, uninstall commands
4. **Local Development**: plugins:link for local plugin development
5. **Hooks**: Shared hooks between main CLI and plugins
6. **Topic Commands**: Organized plugin commands (deploy:*)
7. **Plugin Metadata**: Package.json oclif configuration
8. **Plugin Distribution**: Publishing to npm
## Testing Plugins
File: `plugins/plugin-deploy/test/commands/deploy.test.ts`
```typescript
import { expect, test } from '@oclif/test'
describe('deploy', () => {
test
.stdout()
.command(['deploy', '--env', 'production'])
.it('deploys to production', ctx => {
expect(ctx.stdout).to.contain('Deploying to production')
expect(ctx.stdout).to.contain('successful')
})
test
.command(['deploy'])
.catch(error => {
expect(error.message).to.contain('Missing required flag')
})
.it('requires env flag')
})
```