Initial commit
This commit is contained in:
307
skills/oclif-patterns/examples/plugin-cli-example.md
Normal file
307
skills/oclif-patterns/examples/plugin-cli-example.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# 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')
|
||||
})
|
||||
```
|
||||
Reference in New Issue
Block a user