0
# Extension System
1
2
Plugin architecture for importing command libraries and extending Vorpal functionality.
3
4
## Capabilities
5
6
### Extension Loading
7
8
Imports a library of Vorpal API commands.
9
10
```javascript { .api }
11
/**
12
* Imports a library of Vorpal API commands
13
* @param commands - Extension module (function, string path, array, or object)
14
* @param options - Optional configuration for the extension
15
* @returns Vorpal instance for chaining
16
*/
17
function use(commands: ExtensionInput, options?: ExtensionOptions): Vorpal;
18
19
type ExtensionInput =
20
| ((vorpal: Vorpal) => void) // Function that extends vorpal
21
| string // Module path to require
22
| ExtensionInput[] // Array of extensions
23
| { [key: string]: any }; // Extension object
24
25
interface ExtensionOptions {
26
[key: string]: any; // Extension-specific options
27
}
28
```
29
30
**Usage Examples:**
31
32
```javascript
33
const vorpal = require('vorpal')();
34
35
// Load extension by function
36
function myExtension(vorpal) {
37
vorpal
38
.command('hello', 'Says hello')
39
.action(function(args, callback) {
40
this.log('Hello from extension!');
41
callback();
42
});
43
44
vorpal
45
.command('goodbye', 'Says goodbye')
46
.action(function(args, callback) {
47
this.log('Goodbye from extension!');
48
callback();
49
});
50
}
51
52
vorpal.use(myExtension);
53
54
// Load extension by module path
55
vorpal.use('./my-extension'); // Requires local file
56
vorpal.use('vorpal-grep'); // Requires npm module
57
58
// Load multiple extensions
59
vorpal.use([
60
'./commands/file-commands',
61
'./commands/network-commands',
62
myExtension
63
]);
64
65
// Load extension with options
66
vorpal.use(myExtension, {
67
prefix: 'ext:',
68
verbose: true
69
});
70
```
71
72
## Creating Extensions
73
74
### Function-Based Extensions
75
76
The most common way to create extensions:
77
78
```javascript
79
// my-extension.js
80
function myExtension(vorpal, options) {
81
const prefix = options?.prefix || '';
82
83
vorpal
84
.command(`${prefix}status`, 'Shows application status')
85
.action(function(args, callback) {
86
this.log('Status: Running');
87
this.log('Uptime:', process.uptime());
88
callback();
89
});
90
91
vorpal
92
.command(`${prefix}restart`, 'Restarts the application')
93
.option('-f, --force', 'Force restart without confirmation')
94
.action(function(args, callback) {
95
if (args.options.force) {
96
this.log('Force restarting...');
97
callback();
98
} else {
99
this.prompt({
100
type: 'confirm',
101
name: 'confirm',
102
message: 'Are you sure you want to restart?'
103
}, (result) => {
104
if (result.confirm) {
105
this.log('Restarting...');
106
} else {
107
this.log('Restart cancelled');
108
}
109
callback();
110
});
111
}
112
});
113
}
114
115
module.exports = myExtension;
116
117
// Usage
118
const vorpal = require('vorpal')();
119
vorpal.use(require('./my-extension'), { prefix: 'app:' });
120
```
121
122
### Object-Based Extensions
123
124
Extensions can also be objects with specific structure:
125
126
```javascript
127
// extension-object.js
128
const extensionObject = {
129
// Optional initialization function
130
init: function(vorpal, options) {
131
console.log('Extension initialized with options:', options);
132
},
133
134
// Commands definition
135
commands: [
136
{
137
name: 'list-files [directory]',
138
description: 'Lists files in directory',
139
action: function(args, callback) {
140
const fs = require('fs');
141
const dir = args.directory || process.cwd();
142
143
fs.readdir(dir, (err, files) => {
144
if (err) {
145
this.log('Error:', err.message);
146
} else {
147
this.log('Files in', dir + ':');
148
files.forEach(file => this.log('-', file));
149
}
150
callback();
151
});
152
}
153
},
154
{
155
name: 'current-time',
156
description: 'Shows current time',
157
action: function(args, callback) {
158
this.log('Current time:', new Date().toLocaleString());
159
callback();
160
}
161
}
162
]
163
};
164
165
module.exports = extensionObject;
166
```
167
168
### Class-Based Extensions
169
170
More complex extensions using classes:
171
172
```javascript
173
// database-extension.js
174
class DatabaseExtension {
175
constructor(options = {}) {
176
this.connectionString = options.connectionString;
177
this.timeout = options.timeout || 5000;
178
}
179
180
install(vorpal) {
181
const self = this;
182
183
vorpal
184
.command('db:connect', 'Connect to database')
185
.action(function(args, callback) {
186
self.connect(this, callback);
187
});
188
189
vorpal
190
.command('db:query <sql>', 'Execute SQL query')
191
.action(function(args, callback) {
192
self.query(this, args.sql, callback);
193
});
194
195
vorpal
196
.command('db:status', 'Show database status')
197
.action(function(args, callback) {
198
self.status(this, callback);
199
});
200
}
201
202
connect(context, callback) {
203
context.log('Connecting to database...');
204
// Database connection logic
205
setTimeout(() => {
206
context.log('Connected successfully');
207
callback();
208
}, 1000);
209
}
210
211
query(context, sql, callback) {
212
context.log(`Executing: ${sql}`);
213
// Query execution logic
214
setTimeout(() => {
215
context.log('Query completed');
216
callback();
217
}, 500);
218
}
219
220
status(context, callback) {
221
context.log('Database Status: Connected');
222
context.log('Connection:', this.connectionString);
223
callback();
224
}
225
}
226
227
// Export function that creates and installs the extension
228
module.exports = function(options) {
229
return function(vorpal) {
230
const extension = new DatabaseExtension(options);
231
extension.install(vorpal);
232
};
233
};
234
235
// Usage
236
const vorpal = require('vorpal')();
237
const dbExtension = require('./database-extension');
238
239
vorpal.use(dbExtension({
240
connectionString: 'mongodb://localhost:27017/mydb',
241
timeout: 10000
242
}));
243
```
244
245
## Built-in Extensions
246
247
### Loading Common Extensions
248
249
Many community extensions are available:
250
251
```javascript
252
const vorpal = require('vorpal')();
253
254
// File system commands
255
vorpal.use('vorpal-less'); // less command for viewing files
256
vorpal.use('vorpal-grep'); // grep command for searching
257
258
// Network utilities
259
vorpal.use('vorpal-tour'); // Interactive tours/tutorials
260
261
// Development tools
262
vorpal.use('vorpal-watch'); // File watching commands
263
```
264
265
### Creating Modular Extensions
266
267
Split complex functionality across multiple extension files:
268
269
```javascript
270
// commands/index.js - Main extension loader
271
const fileCommands = require('./file-commands');
272
const networkCommands = require('./network-commands');
273
const systemCommands = require('./system-commands');
274
275
module.exports = function(options = {}) {
276
return function(vorpal) {
277
if (options.includeFiles !== false) {
278
vorpal.use(fileCommands);
279
}
280
281
if (options.includeNetwork !== false) {
282
vorpal.use(networkCommands);
283
}
284
285
if (options.includeSystem !== false) {
286
vorpal.use(systemCommands);
287
}
288
};
289
};
290
291
// Usage
292
const vorpal = require('vorpal')();
293
const myCommands = require('./commands');
294
295
vorpal.use(myCommands({
296
includeNetwork: false // Skip network commands
297
}));
298
```
299
300
### Extension with Shared State
301
302
Extensions can maintain shared state:
303
304
```javascript
305
// stateful-extension.js
306
function createStatefulExtension() {
307
// Shared state across commands
308
const state = {
309
users: [],
310
currentUser: null,
311
settings: {}
312
};
313
314
return function(vorpal) {
315
vorpal
316
.command('user:add <name>', 'Add a user')
317
.action(function(args, callback) {
318
state.users.push({
319
name: args.name,
320
id: Date.now(),
321
created: new Date()
322
});
323
this.log(`User ${args.name} added`);
324
callback();
325
});
326
327
vorpal
328
.command('user:list', 'List all users')
329
.action(function(args, callback) {
330
if (state.users.length === 0) {
331
this.log('No users found');
332
} else {
333
this.log('Users:');
334
state.users.forEach(user => {
335
this.log(`- ${user.name} (ID: ${user.id})`);
336
});
337
}
338
callback();
339
});
340
341
vorpal
342
.command('user:select <name>', 'Select current user')
343
.action(function(args, callback) {
344
const user = state.users.find(u => u.name === args.name);
345
if (user) {
346
state.currentUser = user;
347
this.log(`Selected user: ${user.name}`);
348
} else {
349
this.log(`User ${args.name} not found`);
350
}
351
callback();
352
});
353
354
vorpal
355
.command('user:current', 'Show current user')
356
.action(function(args, callback) {
357
if (state.currentUser) {
358
this.log('Current user:', state.currentUser.name);
359
} else {
360
this.log('No user selected');
361
}
362
callback();
363
});
364
};
365
}
366
367
module.exports = createStatefulExtension;
368
369
// Usage
370
const vorpal = require('vorpal')();
371
const statefulExtension = require('./stateful-extension');
372
373
vorpal.use(statefulExtension());
374
```
375
376
## Extension Best Practices
377
378
### Namespace Commands
379
380
Prefix commands to avoid conflicts:
381
382
```javascript
383
function myExtension(vorpal, options) {
384
const prefix = options?.prefix || 'ext:';
385
386
vorpal
387
.command(`${prefix}command1`, 'Description')
388
.action(function(args, callback) {
389
// Command logic
390
callback();
391
});
392
}
393
```
394
395
### Provide Configuration Options
396
397
Make extensions configurable:
398
399
```javascript
400
function configurableExtension(vorpal, options = {}) {
401
const config = {
402
timeout: options.timeout || 5000,
403
verbose: options.verbose || false,
404
prefix: options.prefix || '',
405
...options
406
};
407
408
// Use config throughout the extension
409
}
410
```
411
412
### Error Handling
413
414
Handle errors gracefully in extensions:
415
416
```javascript
417
function robustExtension(vorpal) {
418
vorpal
419
.command('risky-command', 'Might fail')
420
.action(function(args, callback) {
421
try {
422
// Risky operation
423
this.log('Success!');
424
callback();
425
} catch (error) {
426
this.log('Error:', error.message);
427
callback(error);
428
}
429
});
430
}
431
```
432
433
## Complete Extension Example
434
435
```javascript
436
// file-manager-extension.js
437
const fs = require('fs');
438
const path = require('path');
439
440
function fileManagerExtension(vorpal, options = {}) {
441
const config = {
442
showHidden: options.showHidden || false,
443
defaultPath: options.defaultPath || process.cwd(),
444
...options
445
};
446
447
// Shared state
448
let currentPath = config.defaultPath;
449
450
vorpal
451
.command('fm:pwd', 'Show current directory')
452
.action(function(args, callback) {
453
this.log('Current directory:', currentPath);
454
callback();
455
});
456
457
vorpal
458
.command('fm:cd [directory]', 'Change directory')
459
.action(function(args, callback) {
460
const newPath = args.directory
461
? path.resolve(currentPath, args.directory)
462
: config.defaultPath;
463
464
fs.access(newPath, fs.constants.F_OK, (err) => {
465
if (err) {
466
this.log('Directory not found:', newPath);
467
} else {
468
currentPath = newPath;
469
this.log('Changed to:', currentPath);
470
}
471
callback();
472
});
473
});
474
475
vorpal
476
.command('fm:ls [pattern]', 'List files')
477
.option('-l, --long', 'Long format')
478
.option('-a, --all', 'Show hidden files')
479
.action(function(args, callback) {
480
const showHidden = args.options.all || config.showHidden;
481
const longFormat = args.options.long;
482
483
fs.readdir(currentPath, (err, files) => {
484
if (err) {
485
this.log('Error reading directory:', err.message);
486
callback();
487
return;
488
}
489
490
let filteredFiles = files;
491
492
if (!showHidden) {
493
filteredFiles = files.filter(file => !file.startsWith('.'));
494
}
495
496
if (args.pattern) {
497
const regex = new RegExp(args.pattern, 'i');
498
filteredFiles = filteredFiles.filter(file => regex.test(file));
499
}
500
501
if (longFormat) {
502
filteredFiles.forEach(file => {
503
const fullPath = path.join(currentPath, file);
504
fs.stat(fullPath, (err, stats) => {
505
if (!err) {
506
const size = stats.size;
507
const modified = stats.mtime.toLocaleDateString();
508
const type = stats.isDirectory() ? 'DIR' : 'FILE';
509
this.log(`${type.padEnd(4)} ${size.toString().padStart(8)} ${modified} ${file}`);
510
}
511
});
512
});
513
} else {
514
this.log(filteredFiles.join(' '));
515
}
516
517
callback();
518
});
519
});
520
}
521
522
module.exports = fileManagerExtension;
523
524
// Usage in main application
525
const vorpal = require('vorpal')();
526
const fileManager = require('./file-manager-extension');
527
528
vorpal.use(fileManager, {
529
showHidden: true,
530
defaultPath: '/home/user'
531
});
532
533
vorpal
534
.delimiter('app$')
535
.show();
536
```