-
Notifications
You must be signed in to change notification settings - Fork 947
/
cli.js
647 lines (564 loc) · 18.7 KB
/
cli.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
/*
* cli.js: Handlers for the forever CLI commands.
*
* (C) 2010 Charlie Robbins & the Contributors
* MIT LICENCE
*
*/
var fs = require('fs'),
path = require('path'),
util = require('util'),
colors = require('colors'),
cliff = require('cliff'),
flatiron = require('flatiron'),
shush = require('shush'),
prettyjson = require('prettyjson'),
clone = require('clone'),
objectAssign = require('object-assign'),
forever = require('../forever');
var cli = exports;
var help = [
'usage: forever [action] [options] SCRIPT [script-options]',
'',
'Monitors the script specified in the current process or as a daemon',
'',
'actions:',
' start Start SCRIPT as a daemon',
' stop Stop the daemon SCRIPT by Id|Uid|Pid|Index|Script',
' stopall Stop all running forever scripts',
' restart Restart the daemon SCRIPT',
' restartall Restart all running forever scripts',
' list List all running forever scripts',
' config Lists all forever user configuration',
' set <key> <val> Sets the specified forever config <key>',
' clear <key> Clears the specified forever config <key>',
' logs Lists log files for all forever processes',
' logs <script|index> Tails the logs for <script|index>',
' columns add <col> Adds the specified column to the output in `forever list`. Supported columns: \'uid\', \'command\', \'script\', \'forever\', \'pid\', \'id\', \'logfile\', \'uptime\'',
' columns rm <col> Removed the specified column from the output in `forever list`',
' columns set <cols> Set all columns for the output in `forever list`',
' columns reset Resets all columns to defaults for the output in `forever list`',
' cleanlogs [CAREFUL] Deletes all historical forever log files',
'',
'options:',
' -m MAX Only run the specified script MAX times',
' -l LOGFILE Logs the forever output to LOGFILE',
' -o OUTFILE Logs stdout from child script to OUTFILE',
' -e ERRFILE Logs stderr from child script to ERRFILE',
' -p PATH Base path for all forever related files (pid files, etc.)',
' -c COMMAND COMMAND to execute (defaults to node)',
' -a, --append Append logs',
' -f, --fifo Stream logs to stdout',
' -n, --number Number of log lines to print',
' --pidFile The pid file',
' --uid Process uid, useful as a namespace for processes (must wrap in a string)',
' e.g. forever start --uid "production" app.js',
' forever stop production',
' --sourceDir The source directory for which SCRIPT is relative to',
' --workingDir The working directory in which SCRIPT will execute',
' --minUptime Minimum uptime (millis) for a script to not be considered "spinning"',
' --spinSleepTime Time to wait (millis) between launches of a spinning script.',
' --colors --no-colors will disable output coloring',
' --plain alias of --no-colors',
' -d, --debug Forces forever to log debug output',
' -v, --verbose Turns on the verbose messages from Forever',
' -s, --silent Run the child script silencing stdout and stderr',
' -w, --watch Watch for file changes',
' --watchDirectory Top-level directory to watch from',
' --watchIgnore To ignore pattern when watch is enabled (multiple option is allowed)',
' -t, --killTree Kills the entire child process tree on `stop`',
' --killSignal Support exit signal customization (default is SIGKILL)',
' used for restarting script gracefully e.g. --killSignal=SIGTERM',
' --version Print the current version',
' -h, --help You\'re staring at it',
'',
'[Long Running Process]',
' The forever process will continue to run outputting log messages to the console.',
' ex. forever -o out.log -e err.log my-script.js',
'',
'[Daemon]',
' The forever process will run as a daemon which will make the target process start',
' in the background. This is extremely useful for remote starting simple node.js scripts',
' without using nohup. It is recommended to run start with -o -l, & -e.',
' ex. forever start -l forever.log -o out.log -e err.log my-daemon.js',
' forever stop my-daemon.js',
''
];
var app = flatiron.app;
var actions = [
'start',
'stop',
'stopbypid',
'stopall',
'restart',
'restartall',
'list',
'config',
'set',
'clear',
'logs',
'columns',
'cleanlogs'
];
var argvOptions = cli.argvOptions = {
'command': {alias: 'c'},
'errFile': {alias: 'e'},
'logFile': {alias: 'l'},
'killTree': {alias: 't', boolean: true},
'append': {alias: 'a', boolean: true},
'fifo': {alias: 'f', boolean: true},
'number': {alias: 'n'},
'max': {alias: 'm'},
'outFile': {alias: 'o'},
'path': {alias: 'p'},
'help': {alias: 'h'},
'silent': {alias: 's', boolean: true},
'verbose': {alias: 'v', boolean: true},
'watch': {alias: 'w', boolean: true},
'debug': {alias: 'd', boolean: true},
'plain': {boolean: true},
'uid': {alias: 'u'}
};
app.use(flatiron.plugins.cli, {
argv: argvOptions,
usage: help
});
var reserved = ['root', 'pidPath'];
//
// ### @private function (file, options, callback)
// #### @file {string} Target script to start
// #### @options {Object} Options to start the script with
// #### @callback {function} Continuation to respond to when complete.
// Helper function that sets up the pathing for the specified `file`
// then stats the appropriate files and responds.
//
function tryStart(file, options, callback) {
var fullLog, fullScript;
if (options.path) {
forever.config.set('root', options.path);
forever.root = options.path;
}
fullLog = forever.logFilePath(options.logFile, options.uid);
fullScript = path.join(options.sourceDir, file);
forever.stat(fullLog, fullScript, options.append, function (err) {
if (err) {
forever.log.error('Cannot start forever');
forever.log.error(err.message);
process.exit(-1);
}
callback();
});
}
//
// ### @private function updateConfig (updater)
// #### @updater {function} Function which updates the forever config
// Helper which runs the specified `updater` and then saves the forever
// config to `forever.config.get('root')`.
//
function updateConfig(updater) {
updater();
forever.config.save(function (err) {
if (err) {
return forever.log.error('Error saving config: ' + err.message);
}
cli.config();
var configFile = path.join(forever.config.get('root'), 'config.json');
forever.log.info('Forever config saved: ' + configFile.yellow);
});
}
//
// ### @private function checkColumn (name)
// #### @name {string} Column to check
// Checks if column `name` exists
//
function checkColumn(name) {
if (!forever.columns[name]) {
forever.log.error('Unknown column: ' + name.magenta);
return false;
}
return true;
}
//
// ### function getOptions (file)
// #### @file {string} File to run. **Optional**
// Returns `options` object for use with `forever.start` and
// `forever.startDaemon`
//
var getOptions = cli.getOptions = function (file) {
var options = {},
absFile = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file),
configKeys = [
'pidFile', 'logFile', 'errFile', 'watch', 'minUptime', 'append',
'silent', 'outFile', 'max', 'command', 'path', 'spinSleepTime',
'sourceDir', 'workingDir', 'uid', 'watchDirectory', 'watchIgnore',
'killTree', 'killSignal', 'id'
],
specialKeys = ['script', 'args'],
configs;
//
// Load JSON configuration values
//
if (path.extname(file) === '.json') {
configs = shush(absFile);
configs = !Array.isArray(configs) ? [configs] : configs;
configs = configs.map(function (conf) {
var mut = Object.keys(conf)
.reduce(function (acc, key) {
if (~configKeys.indexOf(key) || ~specialKeys.indexOf(key)) {
acc[key] = conf[key];
}
return acc;
}, {});
if (!mut.script) {
forever.log.error('"script" option required in JSON configuration files');
console.log(prettyjson.render(mut));
process.exit(1);
}
return mut;
});
} else {
options.script = file;
}
//
// First isolate options which should be passed to file
//
options.args = process.argv.splice(process.argv.indexOf(file) + 1);
//
// Now we have to force optimist to reparse command line options because
// we've removed some before.
//
app.config.stores.argv.store = {};
app.config.use('argv', argvOptions);
configKeys.forEach(function (key) {
options[key] = app.config.get(key);
});
options.watchIgnore = options.watchIgnore || [];
options.watchIgnorePatterns = Array.isArray(options.watchIgnore)
? options.watchIgnore
: [options.watchIgnore];
if (!options.minUptime) {
forever.log.warn('--minUptime not set. Defaulting to: 1000ms');
options.minUptime = 1000;
}
if (!options.spinSleepTime) {
forever.log.warn([
'--spinSleepTime not set. Your script',
'will exit if it does not stay up for',
'at least ' + options.minUptime + 'ms'
].join(' '));
}
function assignSpawnWith(options) {
options.sourceDir = options.sourceDir || (file && file[0] !== '/' ? process.cwd() : '/');
options.workingDir = options.workingDir || options.sourceDir;
options.spawnWith = { cwd: options.workingDir };
return options;
}
if (configs && configs.length) {
return configs.map(function (conf) {
return assignSpawnWith(objectAssign(clone(options), conf));
});
}
return [assignSpawnWith(options)];
};
//
// ### function cleanLogs
// Deletes all historical forever log files
//
app.cmd('cleanlogs', cli.cleanLogs = function () {
forever.log.silly('Tidying ' + forever.config.get('root'));
forever.cleanUp(true).on('cleanUp', function () {
forever.log.silly(forever.config.get('root') + ' tidied.');
});
});
//
// ### function start (file)
// #### @file {string} Location of the script to spawn with forever
// Starts a forever process for the script located at `file` as daemon
// process.
//
app.cmd(/start (.+)/, cli.startDaemon = function () {
var file = app.argv._[1],
options = getOptions(file);
options.forEach(function (o) {
forever.log.info('Forever processing file: ' + o.script.grey);
tryStart(o.script, o, function () {
forever.startDaemon(o.script, o);
});
});
});
//
// ### function stop (file)
// #### @file {string} Target forever process to stop
// Stops the forever process specified by `file`.
//
app.cmd(/stop (.+)/, cli.stop = function (file) {
var runner = forever.stop(file, true);
runner.on('stop', function (process) {
forever.log.info('Forever stopped process:' + '\n' + process);
});
runner.on('error', function (err) {
forever.log.error('Forever cannot find process with id: ' + file);
process.exit(1);
});
});
//
// ### function stopbypid (pid)
// Stops running forever process by pid.
//
app.cmd(/stopbypid (.+)/, cli.stopbypid = function (pid) {
forever.log.warn('Deprecated, try `forever stop ' + pid + '` instead.');
cli.stop(pid);
});
//
// ### function stopall ()
// Stops all currently running forever processes.
//
app.cmd('stopall', cli.stopall = function () {
var runner = forever.stopAll(true);
runner.on('stopAll', function (processes) {
if (processes) {
forever.log.info('Forever stopped processes:');
processes.split('\n').forEach(function (line) {
forever.log.data(line);
});
}
else {
forever.log.info('No forever processes running');
}
});
runner.on('error', function () {
forever.log.info('No forever processes running');
});
});
//
// ### function restartall ()
// Restarts all currently running forever processes.
//
app.cmd('restartall', cli.restartAll = function () {
var runner = forever.restartAll(true);
runner.on('restartAll', function (processes) {
if (processes) {
forever.log.info('Forever restarted processes:');
processes.split('\n').forEach(function (line) {
forever.log.data(line);
});
}
else {
forever.log.info('No forever processes running');
}
});
runner.on('error', function () {
forever.log.info('No forever processes running');
});
});
//
// ### function restart (file)
// #### @file {string} Target process to restart
// Restarts the forever process specified by `file`.
//
app.cmd(/restart (.+)/, cli.restart = function (file) {
var runner = forever.restart(file, true);
runner.on('restart', function (processes) {
if (processes) {
forever.log.info('Forever restarted process(es):');
processes.split('\n').forEach(function (line) {
forever.log.data(line);
});
}
else {
forever.log.info('No forever processes running');
}
});
runner.on('error', function (err) {
forever.log.error('Error restarting process: ' + file.grey);
forever.log.error(err.message);
process.exit(1);
});
});
//
// ### function list ()
// Lists all currently running forever processes.
//
app.cmd('list', cli.list = function () {
forever.list(true, function (err, processes) {
if (processes) {
forever.log.info('Forever processes running');
processes.split('\n').forEach(function (line) {
forever.log.data(line);
});
}
else {
forever.log.info('No forever processes running');
}
});
});
//
// ### function config ()
// Lists all of the configuration in `~/.forever/config.json`.
//
app.cmd('config', cli.config = function () {
var keys = Object.keys(forever.config.store),
conf = cliff.inspect(forever.config.store);
if (keys.length <= 2) {
conf = conf.replace(/\{\s/, '{ \n')
.replace(/\}/, '\n}')
.replace('\\033[90m', ' \\033[90m')
.replace(/, /ig, ',\n ');
}
else {
conf = conf.replace(/\n\s{4}/ig, '\n ');
}
conf.split('\n').forEach(function (line) {
forever.log.data(line);
});
});
//
// ### function set (key, value)
// #### @key {string} Key to set in forever config
// #### @value {string} Value to set for `key`
// Sets the specified `key` / `value` pair in the
// forever user config.
//
app.cmd(/set ([\w-_]+) (.+)/, cli.set = function (key, value) {
updateConfig(function () {
forever.log.info('Setting forever config: ' + key.grey);
forever.config.set(key, value);
});
});
//
// ### function clear (key)
// #### @key {string} Key to remove from `~/.forever/config.json`
// Removes the specified `key` from the forever user config.
//
app.cmd('clear :key', cli.clear = function (key) {
if (reserved.indexOf(key) !== -1) {
forever.log.warn('Cannot clear reserved config: ' + key.grey);
forever.log.warn('Use `forever set ' + key + '` instead');
return;
}
updateConfig(function () {
forever.log.info('Clearing forever config: ' + key.grey);
forever.config.clear(key);
});
});
//
// ### function logs (target)
// #### @target {string} Target script or index to list logs for
// Displays the logs using `tail` for the specified `target`.
//
app.cmd('logs :index', cli.logs = function (index) {
var options = {
stream: app.argv.fifo,
length: app.argv.number
};
forever.tail(index, options, function (err, log) {
if (err) {
return forever.log.error(err.message);
}
forever.log.data(log.file.magenta + ':' + log.pid + ' - ' + log.line);
});
});
//
// ### function logFiles ()
// Display log files for all running forever processes.
//
app.cmd('logs', cli.logFiles = function (index) {
if (typeof index !== 'undefined') {
return;
}
var rows = [[' ', 'script', 'logfile']];
index = 0;
forever.list(false, function (err, processes) {
if (!processes) {
return forever.log.warn('No forever logfiles in ' + forever.config.get('root').magenta);
}
forever.log.info('Logs for running Forever processes');
rows = rows.concat(processes.map(function (proc) {
return ['[' + index++ + ']', proc.file.grey, proc.logFile.magenta];
}));
cliff.putRows('data', rows, ['white', 'grey', 'magenta']);
});
});
app.cmd('columns add :name', cli.addColumn = function (name) {
if (checkColumn(name)) {
var columns = forever.config.get('columns');
if (~columns.indexOf(name)) {
return forever.log.warn(name.magenta + ' already exists in forever');
}
forever.log.info('Adding column: ' + name.magenta);
columns.push(name);
forever.config.set('columns', columns);
forever.config.saveSync();
}
});
app.cmd('columns rm :name', cli.rmColumn = function (name) {
if (checkColumn(name)) {
var columns = forever.config.get('columns');
if (!~columns.indexOf(name)) {
return forever.log.warn(name.magenta + ' doesn\'t exist in forever');
}
forever.log.info('Removing column: ' + name.magenta);
columns.splice(columns.indexOf(name), 1);
forever.config.set('columns', columns);
forever.config.saveSync();
}
});
app.cmd(/columns set (.*)/, cli.setColumns = function (columns) {
forever.log.info('Setting columns: ' + columns.magenta);
forever.config.set('columns', columns.split(' '));
forever.config.saveSync();
});
app.cmd('columns reset', cli.resetColumns = function () {
var columns = 'uid command script forever pid logfile uptime';
forever.log.info('Setting columns: ' + columns.magenta);
forever.config.set('columns', columns.split(' '));
forever.config.saveSync();
});
//
// ### function help ()
// Shows help
//
app.cmd('help', cli.help = function () {
util.puts(help.join('\n'));
});
//
// ### function start (file)
// #### @file {string} Location of the script to spawn with forever
// Starts a forever process for the script located at `file` as non-daemon
// process.
//
// Remark: this regex matches everything. It has to be added at the end to
// make executing other commands possible.
//
cli.run = function () {
var file = app.argv._[0],
options = getOptions(file);
options.forEach(function (o) {
tryStart(o.script, o, function () {
var monitor = forever.start(o.script, o);
monitor.on('start', function () {
forever.startServer(monitor);
});
});
});
};
cli.start = function () {
if (app.argv.version) {
return console.log('v' + forever.version);
}
//
// Check for --no-colors/--colors and --plain option
//
if ((typeof app.argv.colors !== 'undefined' && !app.argv.colors) || app.argv.plain) {
colors.mode = 'none';
}
if (app.config.get('help')) {
return util.puts(help.join('\n'));
}
app.init(function () {
if (app.argv._.length && actions.indexOf(app.argv._[0]) === -1) {
return cli.run();
}
app.start();
});
};