0
# Event Handling
1
2
Event system with hooks, pipes, and signal handling for advanced CLI behaviors.
3
4
## Capabilities
5
6
### Signal Handling
7
8
Registers a handler for SIGINT (Ctrl+C) signals.
9
10
```javascript { .api }
11
/**
12
* Registers a SIGINT handler function
13
* @param fn - Function to call when SIGINT is received
14
* @returns Vorpal instance for chaining
15
*/
16
function sigint(fn: () => void): Vorpal;
17
```
18
19
**Usage Example:**
20
21
```javascript
22
const vorpal = require('vorpal')();
23
24
// Handle Ctrl+C gracefully
25
vorpal
26
.sigint(function() {
27
console.log('\nReceived SIGINT. Cleaning up...');
28
29
// Cleanup operations
30
// Save data, close connections, etc.
31
32
process.exit(0);
33
})
34
.delimiter('myapp$')
35
.show();
36
```
37
38
### Output Piping
39
40
Intercepts all stdout logging through a custom function.
41
42
```javascript { .api }
43
/**
44
* Pipes all stdout through a custom function
45
* @param fn - Function to process output before display
46
* @returns Vorpal instance for chaining
47
*/
48
function pipe(fn: (output: string) => string): Vorpal;
49
```
50
51
**Usage Examples:**
52
53
```javascript
54
const vorpal = require('vorpal')();
55
56
// Log all output to file
57
const fs = require('fs');
58
const logStream = fs.createWriteStream('app.log', { flags: 'a' });
59
60
vorpal
61
.pipe(function(output) {
62
// Log to file with timestamp
63
logStream.write(`[${new Date().toISOString()}] ${output}\n`);
64
65
// Return output to display normally
66
return output;
67
});
68
69
// Transform output (e.g., add color, formatting)
70
vorpal
71
.pipe(function(output) {
72
// Add timestamp prefix to all output
73
return `[${new Date().toLocaleTimeString()}] ${output}`;
74
});
75
76
// Filter sensitive information
77
vorpal
78
.pipe(function(output) {
79
// Remove sensitive data from logs
80
return output.replace(/password=\w+/gi, 'password=***');
81
});
82
```
83
84
### Stdout Hooking
85
86
Hooks into stdout and passes it through a function.
87
88
```javascript { .api }
89
/**
90
* Hooks stdout and passes it through a function
91
* @param fn - Optional function to process stdout
92
* @returns Vorpal instance for chaining
93
*/
94
function hook(fn?: (output: string) => void): Vorpal;
95
```
96
97
**Usage Example:**
98
99
```javascript
100
const vorpal = require('vorpal')();
101
102
// Monitor all stdout output
103
vorpal
104
.hook(function(output) {
105
// Send output to monitoring service
106
console.error(`[MONITOR] ${output}`); // Use stderr to avoid recursion
107
108
// Count output lines
109
if (!global.outputStats) {
110
global.outputStats = { lines: 0, chars: 0 };
111
}
112
global.outputStats.lines++;
113
global.outputStats.chars += output.length;
114
});
115
116
// Example command that generates output
117
vorpal
118
.command('test-output', 'Generate test output')
119
.action(function(args, callback) {
120
this.log('Line 1');
121
this.log('Line 2');
122
this.log('Line 3');
123
callback();
124
});
125
```
126
127
### Custom Help Handler
128
129
Registers a custom help handler for the application.
130
131
```javascript { .api }
132
/**
133
* Registers a custom help handler
134
* @param fn - Function to handle help requests
135
* @returns undefined
136
*/
137
function help(fn: (cmd?: string) => void): undefined;
138
```
139
140
**Usage Example:**
141
142
```javascript
143
const vorpal = require('vorpal')();
144
145
// Custom help system
146
vorpal
147
.help(function(cmd) {
148
if (cmd) {
149
// Help for specific command
150
console.log(`Custom help for command: ${cmd}`);
151
152
const command = this.find(cmd);
153
if (command) {
154
console.log(`Description: ${command.description()}`);
155
console.log(`Usage: ${command.usage()}`);
156
} else {
157
console.log(`Command '${cmd}' not found`);
158
}
159
} else {
160
// General help
161
console.log('=== My CLI Application Help ===');
162
console.log('Available commands:');
163
164
this.commands.forEach(command => {
165
if (!command._hidden) {
166
console.log(` ${command._name} - ${command.description()}`);
167
}
168
});
169
170
console.log('\nUse "help <command>" for specific command help');
171
}
172
});
173
```
174
175
### Application Exit
176
177
Exits the application with optional cleanup.
178
179
```javascript { .api }
180
/**
181
* Exits the application
182
* @param options - Exit options (code, cleanup, etc.)
183
* @returns undefined
184
*/
185
function exit(options: ExitOptions): undefined;
186
187
interface ExitOptions {
188
code?: number; // Exit code (default: 0)
189
cleanup?: boolean; // Whether to run cleanup (default: true)
190
force?: boolean; // Force exit without confirmation (default: false)
191
}
192
```
193
194
**Usage Example:**
195
196
```javascript
197
const vorpal = require('vorpal')();
198
199
vorpal
200
.command('shutdown', 'Shutdown the application')
201
.option('-f, --force', 'Force shutdown without confirmation')
202
.action(function(args, callback) {
203
const force = args.options.force;
204
205
if (force) {
206
this.log('Force shutdown initiated...');
207
this.parent.exit({ code: 0, force: true });
208
} else {
209
this.prompt({
210
type: 'confirm',
211
name: 'confirm',
212
message: 'Are you sure you want to shutdown?',
213
default: false
214
}, (result) => {
215
if (result.confirm) {
216
this.log('Shutting down...');
217
this.parent.exit({ code: 0, cleanup: true });
218
} else {
219
this.log('Shutdown cancelled');
220
callback();
221
}
222
});
223
}
224
});
225
```
226
227
## Event System
228
229
Vorpal extends EventEmitter and emits various events during operation.
230
231
### Vorpal Events
232
233
```javascript { .api }
234
// Event types emitted by Vorpal
235
interface VorpalEvents {
236
'keypress': (key: KeyPress) => void;
237
'command_registered': (command: Command) => void;
238
'client_prompt_submit': (data: PromptData) => void;
239
'client_command_error': (error: Error, command: string) => void;
240
'client_command_executed': (command: string, args: any) => void;
241
'client_command_cancelled': (command: string) => void;
242
'mode_exit': (mode: string) => void;
243
'vorpal_exit': (code: number) => void;
244
}
245
246
interface KeyPress {
247
sequence: string;
248
name: string;
249
ctrl: boolean;
250
meta: boolean;
251
shift: boolean;
252
}
253
254
interface PromptData {
255
command: string;
256
args: any;
257
}
258
```
259
260
**Event Handling Examples:**
261
262
```javascript
263
const vorpal = require('vorpal')();
264
265
// Listen for command executions
266
vorpal.on('client_command_executed', function(command, args) {
267
console.log(`Command executed: ${command}`);
268
console.log('Arguments:', args);
269
});
270
271
// Listen for errors
272
vorpal.on('client_command_error', function(error, command) {
273
console.error(`Error in command ${command}:`, error.message);
274
275
// Log to file, send to monitoring service, etc.
276
logError(command, error);
277
});
278
279
// Listen for command registrations
280
vorpal.on('command_registered', function(command) {
281
console.log(`New command registered: ${command._name}`);
282
});
283
284
// Listen for key presses
285
vorpal.on('keypress', function(key) {
286
if (key.name === 'tab') {
287
console.log('Tab key pressed for autocomplete');
288
}
289
});
290
291
// Listen for application exit
292
vorpal.on('vorpal_exit', function(code) {
293
console.log(`Application exiting with code: ${code}`);
294
295
// Cleanup operations
296
closeConnections();
297
saveState();
298
});
299
300
// Listen for mode changes
301
vorpal.on('mode_exit', function(mode) {
302
console.log(`Exited mode: ${mode}`);
303
});
304
```
305
306
### Session Events
307
308
Session instances also emit events:
309
310
```javascript
311
const vorpal = require('vorpal')();
312
313
// Listen to session events
314
vorpal.session.on('vorpal_command_cancel', function() {
315
console.log('Command was cancelled');
316
});
317
```
318
319
## Advanced Event Patterns
320
321
### Event-Driven Command Logging
322
323
```javascript
324
const vorpal = require('vorpal')();
325
const fs = require('fs');
326
327
// Setup comprehensive logging
328
const logFile = 'command-log.json';
329
const commandLog = [];
330
331
vorpal.on('client_command_executed', function(command, args) {
332
const logEntry = {
333
timestamp: new Date().toISOString(),
334
command: command,
335
args: args,
336
success: true
337
};
338
339
commandLog.push(logEntry);
340
fs.writeFileSync(logFile, JSON.stringify(commandLog, null, 2));
341
});
342
343
vorpal.on('client_command_error', function(error, command) {
344
const logEntry = {
345
timestamp: new Date().toISOString(),
346
command: command,
347
error: error.message,
348
success: false
349
};
350
351
commandLog.push(logEntry);
352
fs.writeFileSync(logFile, JSON.stringify(commandLog, null, 2));
353
});
354
355
// Command to view logs
356
vorpal
357
.command('logs [count]', 'Show recent command logs')
358
.action(function(args, callback) {
359
const count = parseInt(args.count) || 10;
360
const recentLogs = commandLog.slice(-count);
361
362
this.log(`Last ${count} commands:`);
363
recentLogs.forEach((entry, index) => {
364
const status = entry.success ? '✓' : '✗';
365
this.log(`${index + 1}. ${status} ${entry.timestamp} - ${entry.command}`);
366
if (!entry.success) {
367
this.log(` Error: ${entry.error}`);
368
}
369
});
370
371
callback();
372
});
373
```
374
375
### Plugin Event System
376
377
```javascript
378
// Create event-driven plugin system
379
function createEventPlugin(vorpal) {
380
const pluginEvents = {};
381
382
// Register plugin event listeners
383
vorpal.pluginOn = function(event, listener) {
384
if (!pluginEvents[event]) {
385
pluginEvents[event] = [];
386
}
387
pluginEvents[event].push(listener);
388
};
389
390
// Emit plugin events
391
vorpal.pluginEmit = function(event, ...args) {
392
if (pluginEvents[event]) {
393
pluginEvents[event].forEach(listener => {
394
try {
395
listener(...args);
396
} catch (error) {
397
console.error(`Plugin event error:`, error);
398
}
399
});
400
}
401
};
402
403
// Hook into command execution
404
vorpal.on('client_command_executed', function(command, args) {
405
vorpal.pluginEmit('command:executed', command, args);
406
});
407
408
vorpal.on('client_command_error', function(error, command) {
409
vorpal.pluginEmit('command:error', error, command);
410
});
411
}
412
413
// Usage
414
const vorpal = require('vorpal')();
415
createEventPlugin(vorpal);
416
417
// Plugin that listens to events
418
vorpal.pluginOn('command:executed', function(command, args) {
419
if (command.startsWith('db:')) {
420
console.log('Database command executed:', command);
421
}
422
});
423
424
vorpal.pluginOn('command:error', function(error, command) {
425
if (command.startsWith('api:')) {
426
console.log('API command failed:', error.message);
427
// Maybe retry or show helpful message
428
}
429
});
430
```
431
432
### Real-time Status Updates
433
434
```javascript
435
const vorpal = require('vorpal')();
436
437
// Status monitoring with events
438
let systemStatus = {
439
uptime: 0,
440
commandsExecuted: 0,
441
errors: 0,
442
lastCommand: null
443
};
444
445
// Update status on events
446
vorpal.on('client_command_executed', function(command, args) {
447
systemStatus.commandsExecuted++;
448
systemStatus.lastCommand = {
449
command: command,
450
timestamp: new Date(),
451
args: args
452
};
453
});
454
455
vorpal.on('client_command_error', function(error, command) {
456
systemStatus.errors++;
457
});
458
459
// Periodic status updates
460
setInterval(() => {
461
systemStatus.uptime += 1;
462
}, 1000);
463
464
vorpal
465
.command('status', 'Show system status')
466
.action(function(args, callback) {
467
this.log('System Status:');
468
this.log(` Uptime: ${systemStatus.uptime} seconds`);
469
this.log(` Commands executed: ${systemStatus.commandsExecuted}`);
470
this.log(` Errors: ${systemStatus.errors}`);
471
472
if (systemStatus.lastCommand) {
473
this.log(` Last command: ${systemStatus.lastCommand.command} at ${systemStatus.lastCommand.timestamp.toLocaleTimeString()}`);
474
}
475
476
callback();
477
});
478
```
479
480
## Complete Event-Driven Example
481
482
```javascript
483
const vorpal = require('vorpal')();
484
const fs = require('fs');
485
const path = require('path');
486
487
// Setup comprehensive event handling
488
class CLIEventSystem {
489
constructor(vorpal) {
490
this.vorpal = vorpal;
491
this.logPath = path.join(process.cwd(), 'cli-events.log');
492
this.stats = {
493
sessionsStarted: 0,
494
commandsExecuted: 0,
495
errors: 0,
496
startTime: new Date()
497
};
498
499
this.setupEventHandlers();
500
}
501
502
setupEventHandlers() {
503
// Log all events to file
504
this.vorpal.on('client_command_executed', (command, args) => {
505
this.logEvent('command_executed', { command, args });
506
this.stats.commandsExecuted++;
507
});
508
509
this.vorpal.on('client_command_error', (error, command) => {
510
this.logEvent('command_error', { command, error: error.message });
511
this.stats.errors++;
512
});
513
514
this.vorpal.on('command_registered', (command) => {
515
this.logEvent('command_registered', { name: command._name });
516
});
517
518
this.vorpal.on('keypress', (key) => {
519
if (key.ctrl && key.name === 'c') {
520
this.logEvent('sigint_received', {});
521
}
522
});
523
524
this.vorpal.on('vorpal_exit', (code) => {
525
this.logEvent('application_exit', { code, uptime: this.getUptime() });
526
});
527
528
// Custom graceful shutdown
529
this.vorpal.sigint(() => {
530
console.log('\n\nGraceful shutdown initiated...');
531
this.logEvent('graceful_shutdown', { stats: this.stats });
532
533
console.log('Event log saved to:', this.logPath);
534
process.exit(0);
535
});
536
}
537
538
logEvent(type, data) {
539
const logEntry = {
540
timestamp: new Date().toISOString(),
541
type: type,
542
data: data
543
};
544
545
fs.appendFileSync(this.logPath, JSON.stringify(logEntry) + '\n');
546
}
547
548
getUptime() {
549
return Math.floor((new Date() - this.stats.startTime) / 1000);
550
}
551
552
getStats() {
553
return {
554
...this.stats,
555
uptime: this.getUptime()
556
};
557
}
558
}
559
560
// Initialize event system
561
const eventSystem = new CLIEventSystem(vorpal);
562
563
// Commands that work with the event system
564
vorpal
565
.command('events:stats', 'Show event statistics')
566
.action(function(args, callback) {
567
const stats = eventSystem.getStats();
568
569
this.log('CLI Event Statistics:');
570
this.log(` Uptime: ${stats.uptime} seconds`);
571
this.log(` Commands executed: ${stats.commandsExecuted}`);
572
this.log(` Errors: ${stats.errors}`);
573
this.log(` Session started: ${stats.startTime.toLocaleString()}`);
574
575
callback();
576
});
577
578
vorpal
579
.command('events:log [lines]', 'Show recent event log entries')
580
.action(function(args, callback) {
581
const lines = parseInt(args.lines) || 10;
582
583
try {
584
const logContent = fs.readFileSync(eventSystem.logPath, 'utf8');
585
const logLines = logContent.trim().split('\n').slice(-lines);
586
587
this.log(`Last ${lines} events:`);
588
logLines.forEach((line, index) => {
589
try {
590
const entry = JSON.parse(line);
591
this.log(`${index + 1}. [${entry.timestamp}] ${entry.type}: ${JSON.stringify(entry.data)}`);
592
} catch (e) {
593
this.log(`${index + 1}. Invalid log entry: ${line}`);
594
}
595
});
596
} catch (error) {
597
this.log('Error reading event log:', error.message);
598
}
599
600
callback();
601
});
602
603
// Custom output piping for enhanced logging
604
vorpal.pipe(function(output) {
605
// Add session info to all output
606
const sessionInfo = `[Session ${eventSystem.stats.sessionsStarted}]`;
607
return `${sessionInfo} ${output}`;
608
});
609
610
// Start the CLI
611
eventSystem.stats.sessionsStarted++;
612
eventSystem.logEvent('session_started', { pid: process.pid });
613
614
vorpal
615
.delimiter('events$')
616
.show();
617
```