var fs = require('fs'),
util = require('util'),
path = require('path'),
flatiron = require('flatiron'),
cliff = require('cliff'),
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',
' 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`',
' 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`',
' 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',
' --pidFile The pid file',
' --sourceDir The source directory for which SCRIPT is relative to',
' --minUptime Minimum uptime (millis) for a script to not be considered "spinning"',
' --spinSleepTime Time to wait (millis) between launches of a spinning script.',
' --plain Disable command line 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',
' -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',
'stopall',
'restart',
'restartall',
'list',
'config',
'set',
'clear',
'logs',
'columns',
'cleanlogs'
];
var argvOptions = cli.argvOptions = {
'command': {alias: 'c'},
'errFile': {alias: 'e'},
'logFile': {alias: 'l'},
'append': {alias: 'a', boolean: true},
'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}
};
app.use(flatiron.plugins.cli, {
argv: argvOptions,
usage: help
});
var reserved = ['root', 'pidPath'];
function tryStart(file, options, callback) {
var fullLog, fullScript;
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();
});
}
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);
});
}
function checkColumn(name) {
if (!forever.columns[name]) {
forever.log.error('Unknown column: ' + name.magenta);
return false;
}
return true;
}
var getOptions = cli.getOptions = function (file) {
var options = {};
options.options = process.argv.splice(process.argv.indexOf(file) + 1);
app.config.stores.argv.store = {};
app.config.use('argv', argvOptions);
[
'pidFile', 'logFile', 'errFile', 'watch', 'minUptime', 'append',
'silent', 'outFile', 'max', 'command', 'path', 'spinSleepTime',
'sourceDir', 'uid', 'watchDirectory'
].forEach(function (key) {
options[key] = app.config.get(key);
});
options.sourceDir = options.sourceDir || (file && file[0] !== '/' ? process.cwd() : '/');
if (options.sourceDir) {
options.spawnWith = {cwd: options.sourceDir};
}
return options;
}
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.');
});
});
app.cmd(/start (.+)/, cli.startDaemon = function () {
var file = app.argv._[1],
options = getOptions(file);
forever.log.info('Forever processing file: ' + file.grey);
tryStart(file, options, function () {
forever.startDaemon(file, options);
});
});
app.cmd(/stop (.+)/, cli.stop = function (file) {
var runner = forever.stop(file, true);
runner.on('stop', function (process) {
forever.log.info('Forever stopped process:');
forever.log.data(process);
});
runner.on('error', function (err) {
forever.log.error('Forever cannot find process with index: ' + file);
});
});
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');
});
});
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');
}
});
});
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);
});
});
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');
}
});
});
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);
});
});
app.cmd(/set ([\w-_]+) (.+)/, cli.set = function (key, value) {
updateConfig(function () {
forever.log.info('Setting forever config: ' + key.grey);
forever.config.set(key, value);
});
});
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);
});
});
app.cmd('logs :index', cli.logs = function (index) {
forever.tail(index, function (err, logs) {
if (err) {
return forever.log.error(err.message);
}
logs.forEach(function (proc) {
forever.log.info('Showing logs for ' + proc.file.magenta);
proc.logs.forEach(function (line) {
forever.log.data(line);
});
});
});
});
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('help', cli.help = function () {
util.puts(help.join('\n'));
});
cli.run = function () {
var file = app.argv._[0],
options = getOptions(file);
tryStart(file, options, function () {
var monitor = forever.start(file, options);
monitor.on('start', function () {
forever.startServer(monitor);
});
});
};
cli.start = function () {
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();
});
};