Gluegun Plugin System Example
Demonstrates how to build an extensible CLI with plugin architecture.
Structure
plugin-system/
├── src/
│ ├── cli.ts # CLI with plugin loading
│ ├── commands/
│ │ └── plugin.ts # Plugin management command
│ └── plugins/
│ ├── custom-plugin.ts # Example plugin
│ └── validator-plugin.ts # Validation plugin
├── package.json
└── README.md
Features
- Plugin discovery and loading
- Custom toolbox extensions via plugins
- Plugin-provided commands
- Plugin configuration management
Installation
npm install
npm run build
Usage
Load Plugins
# CLI automatically loads plugins from:
# - ./plugins/
# - ./node_modules/mycli-*
Use Plugin Commands
# Commands added by plugins
./bin/cli validate
./bin/cli custom-action
Use Plugin Extensions
// In any command
const { myPlugin } = toolbox
myPlugin.doSomething()
Creating a Plugin
Basic Plugin Structure
module.exports = (toolbox) => {
const { print } = toolbox
// Add to toolbox
toolbox.myPlugin = {
version: '1.0.0',
doSomething: () => {
print.info('Plugin action executed')
}
}
}
Plugin with Commands
module.exports = (toolbox) => {
const { runtime } = toolbox
runtime.addPlugin({
name: 'my-plugin',
commands: [
{
name: 'my-command',
run: async (toolbox) => {
// Command implementation
}
}
]
})
}
Plugin Discovery
The CLI looks for plugins in:
- Local plugins directory:
./plugins/*.js - Node modules:
./node_modules/mycli-* - Scoped packages:
@scope/mycli-*
Plugin Naming Convention
- Local plugins: Any
.jsor.tsfile inplugins/ - NPM plugins: Must match pattern
mycli-* - Example:
mycli-validator,@myorg/mycli-helper
Key Patterns
1. Load Plugins from Directory
cli.plugins('./plugins', { matching: '*.js' })
2. Load NPM Plugins
cli.plugins('./node_modules', {
matching: 'mycli-*',
hidden: true
})
3. Add Toolbox Extension
toolbox.validator = {
validate: (data) => { /* ... */ }
}
4. Register Commands
runtime.addPlugin({
name: 'my-plugin',
commands: [/* commands */]
})
Example Plugins
custom-plugin.ts
Adds custom utilities to toolbox.
toolbox.custom = {
formatDate: (date) => { /* ... */ },
parseConfig: (file) => { /* ... */ }
}
validator-plugin.ts
Adds validation command and utilities.
toolbox.validator = {
validateFile: (path) => { /* ... */ },
validateSchema: (data) => { /* ... */ }
}
Publishing Plugins
1. Create NPM Package
{
"name": "mycli-myplugin",
"main": "dist/index.js",
"keywords": ["mycli", "plugin"]
}
2. Export Plugin
module.exports = (toolbox) => {
// Plugin implementation
}
3. Publish
npm publish
4. Install and Use
npm install mycli-myplugin
# Automatically loaded by CLI
Advanced Patterns
Conditional Plugin Loading
if (config.enablePlugin) {
cli.plugins('./plugins/optional')
}
Plugin Configuration
toolbox.myPlugin = {
config: await filesystem.read('.mypluginrc', 'json'),
// Plugin methods
}
Plugin Dependencies
module.exports = (toolbox) => {
// Check for required plugins
if (!toolbox.otherPlugin) {
throw new Error('Requires other-plugin')
}
}
Best Practices
- Namespace Extensions: Use unique names for toolbox extensions
- Document APIs: Provide clear documentation for plugin methods
- Handle Errors: Validate inputs and handle failures gracefully
- Version Plugins: Use semantic versioning
- Test Plugins: Write tests for plugin functionality
Testing Plugins
import { build } from 'gluegun'
test('plugin loads correctly', async () => {
const cli = build().src(__dirname).plugins('./plugins').create()
const toolbox = await cli.run()
expect(toolbox.myPlugin).toBeDefined()
})