From da86724cf424f05240b9af72f58f586249a6d13d Mon Sep 17 00:00:00 2001 From: indexzero Date: Mon, 11 Jul 2011 02:22:23 -0400 Subject: [PATCH] [doc] Regenerate docco docs --- docs/forever.html | 628 +++++++++++++++++++++----------------- docs/forever/cli.html | 164 ++++++---- docs/forever/monitor.html | 149 +++++---- 3 files changed, 523 insertions(+), 418 deletions(-) diff --git a/docs/forever.html b/docs/forever.html index 7abd47a4..95f6ac5a 100644 --- a/docs/forever.html +++ b/docs/forever.html @@ -10,15 +10,17 @@ path = require('path'), events = require('events'), exec = require('child_process').exec, + net = require('net'), async = require('async'), colors = require('colors'), cliff = require('cliff'), daemon = require('daemon'), nconf = require('nconf'), + portfinder = require('portfinder'), timespan = require('timespan'), winston = require('winston'); -var forever = exports;

Setup forever.log to be a custom winston logger.

forever.log = new (winston.Logger)({ 
+var forever = exports;

Setup forever.log to be a custom winston logger.

forever.log = new (winston.Logger)({
   transports: [
     new (winston.transports.Console)()
   ]
@@ -30,54 +32,59 @@
 forever.root        = path.join(process.env.HOME, '.forever');
 forever.config      = new nconf.stores.File({ file: path.join(forever.root, 'config.json') });
 forever.Forever     = forever.Monitor = require('./forever/monitor').Monitor;
-forever.cli         = require('./forever/cli'); 

Expose version through pkginfo

require('pkginfo')(module, 'version');

function load (options, [callback])

+forever.cli = require('./forever/cli');

Expose version through pkginfo

require('pkginfo')(module, 'version');

function load (options, [callback])

@options {Object} Options to load into the forever module

-

Initializes configuration for forever module

forever.load = function (options) {

Setup the incoming options with default options.

  options         = options || {};
-  options.root    = options.root || forever.root;
-  options.pidPath = options.pidPath || path.join(options.root, 'pids');
-  

If forever is initalized and the config directories are identical -simply return without creating directories

  if (forever.initialized && forever.config.get('root') === options.root && 
+

Initializes configuration for forever module

forever.load = function (options) {

Setup the incoming options with default options.

  options          = options          || {};
+  options.root     = options.root     || forever.root;
+  options.pidPath  = options.pidPath  || path.join(options.root, 'pids');
+  options.sockPath = options.sockPath || path.join(options.root, 'sock');

If forever is initalized and the config directories are identical +simply return without creating directories

  if (forever.initialized && forever.config.get('root') === options.root &&
     forever.config.get('pidPath') === options.pidPath) {
     return;
   }
-  
-  forever.config = new nconf.stores.File({ file: path.join(options.root, 'config.json') });
-  

Try to load the forever config.json from + + forever.config = new nconf.stores.File({ file: path.join(options.root, 'config.json') });

Try to load the forever config.json from the specified location.

  try { forever.config.loadSync() }
   catch (ex) { }
+  

Setup the columns for forever list.

  options.columns  = options.columns  || forever.config.get('columns');
+  if (!options.columns) {
+    options.columns = [
+      'uid', 'command', 'script', 'forever', 'pid', 'logfile', 'uptime'
+    ];
+  }
   
+
   forever.config.set('root', options.root);
   forever.config.set('pidPath', options.pidPath);
-  

Attempt to see if forever has been configured to + forever.config.set('sockPath', options.sockPath); + forever.config.set('columns', options.columns);

Attempt to see if forever has been configured to run in debug mode.

  options.debug = options.debug || forever.config.get('debug') || false;
-  
-  if (options.debug) {

If we have been indicated to debug this forever process + + if (options.debug) {

If we have been indicated to debug this forever process then setup forever._debug to be an instance of winston.Logger.

    forever._debug();
-  }
-  

Syncronously create the root directory + }

Syncronously create the root directory and the pid directory for forever. Although there is an additional overhead here of the sync action. It simplifies -the setup of forever dramatically.

  function tryCreate (dir) {
+the setup of forever dramatically.

  function tryCreate (dir) {
     try { fs.mkdirSync(dir, 0755) }
     catch (ex) { }
   }
 
   tryCreate(forever.config.get('root'));
-  tryCreate(forever.config.get('pidPath'));
-  

Attempt to save the new config.json for forever

  try { forever.config.saveSync() }
+  tryCreate(forever.config.get('pidPath'));

Attempt to save the new config.json for forever

  try { forever.config.saveSync() }
   catch (ex) { }
-  
+
   forever.initialized = true;
-};

@private function _debug ()

+};

@private function _debug ()

Sets up debugging for this forever process

forever._debug = function () {
   forever.config.set('debug', true);
-  forever.log.add(winston.transports.File, { 
+  forever.log.add(winston.transports.File, {
     filename: path.join(forever.config.get('root'), 'forever.debug.log')
   });
-}

Ensure forever will always be loaded the first time it is required.

forever.load();

function stat (logFile, script, callback)

+}

Ensure forever will always be loaded the first time it is required.

forever.load();

function stat (logFile, script, callback)

@logFile {string} Path to the log file for this script

@@ -90,7 +97,7 @@

@callback {function} Continuation to pass control back to

Ensures that the logFile doesn't exist and that the target script does exist before executing callback.

forever.stat = function (logFile, script, callback) {
   var logAppend;
-      
+
   if (arguments.length === 4) {
     logAppend = callback;
     callback = arguments[3];
@@ -102,12 +109,12 @@ 

@callback {function} Continuation to pass control back to

} return logAppend ? callback(null) : fs.stat(logFile, function (err, stats) { - return !err - ? callback(new Error('log file ' + logFile + ' exists.')) + return !err + ? callback(new Error('log file ' + logFile + ' exists.')) : callback(null); }); }); -};

function start (script, options)

+};

function start (script, options)

@script {string} Location of the script to run.

@@ -115,123 +122,151 @@

@options {Object} Configuration for forever instance.

Starts a script with forever

forever.start = function (script, options) {
   return new forever.Monitor(script, options).start();
-};

function startDaemon (script, options)

+};

function startDaemon (script, options)

@script {string} Location of the script to run.

@options {Object} Configuration for forever instance.

Starts a script with forever as a daemon

forever.startDaemon = function (script, options) {
-  options.logFile = forever.logFilePath(options.logFile);
-  options.pidFile = forever.pidFilePath(options.pidFile);
-  
-  var runner = new forever.Monitor(script, options);
-  
+  options.uid     = options.uid || forever.randomString(24);
+  options.logFile = forever.logFilePath(options.logFile || options.uid + '.log');
+  options.pidFile = forever.pidFilePath(options.pidFile || options.uid + '.pid');
+
+  var monitor = new forever.Monitor(script, options);
+
   fs.open(options.logFile, options.appendLog ? 'a+' : 'w+', function (err, fd) {
     if (err) {
-      return runner.emit('error', err);
+      return monitor.emit('error', err);
     }
 
     var pid = daemon.start(fd);
-    daemon.lock(options.pidFile);

Remark: This should work, but the fd gets screwed up + daemon.lock(options.pidFile);

Remark: This should work, but the fd gets screwed up with the daemon process.

process.on('exit', function () { fs.unlinkSync(options.pidFile); });

    process.pid = pid;
-    runner.start();
+    monitor.start();
+  });
+
+  return monitor;
+};

function startServer ()

+ +

@arguments {forever.Monitor...} A list of forever.Monitor instances

+ +

Starts the forever HTTP server for communication with the forever CLI. +NOTE: This will change your process.title.

forever.startServer = function () {
+  var monitors = Array.prototype.slice.call(arguments),
+      callback = typeof monitors[monitors.length - 1] == 'function' && monitors.pop(),
+      socket = path.join(forever.config.get('sockPath'), 'forever.sock'),
+      server;
+
+  server = net.createServer(function (socket) {

Write the specified data and close the socket

    socket.end(JSON.stringify({ 
+      monitors: monitors.map(function (m) { return m.data })
+    }));
   });
-  
-  return runner;
-};

function stop (target, [format])

+ + portfinder.getSocket({ path: socket }, function (err, socket) { + if (err) {

TODO: This is really bad.

    }
+
+    server.on('error', function () {

TODO: This is really bad.

    });
+
+    server.listen(socket, function () {
+      if (callback) {
+        callback(null, server, socket);
+      }
+    });
+  });
+};

function stop (target, [format])

@target {string} Index or script name to stop

@format {boolean} Indicated if we should CLI format the returned output.

-

Stops the process(es) with the specified index or script name +

Stops the process(es) with the specified index or script name in the list of all processes

forever.stop = function (target, format, restart) {
   var emitter = new events.EventEmitter(),
-      processes = getAllProcesses(),
       results = [];
-  
-  var procs = /(\d+)/.test(target) 
-    ? forever.findByIndex(target, processes)
-    : forever.findByScript(target, processes);
-
-  if (procs && procs.length > 0) {
-    procs.forEach(function (proc) {
-      [proc.foreverPid, proc.pid].forEach(function (pid) {
-        try { process.kill(pid) }
-        catch (ex) { }
+
+  getAllProcesses(function (processes) {
+    var procs = /(\d+)/.test(target)
+      ? forever.findByIndex(target, processes)
+      : forever.findByScript(target, processes);
+
+    if (procs && procs.length > 0) {
+      procs.forEach(function (proc) {
+        [proc.foreverPid, proc.pid].forEach(function (pid) {
+          try { process.kill(pid) }
+          catch (ex) { }
+        });
       });
-    });
-    
-    process.nextTick(function () {
-      emitter.emit('stop', format ? forever.list(true, procs) : procs);
-    });
-  }
-  else {
-    process.nextTick(function () {
-      emitter.emit('error', new Error('Cannot find forever process: ' + target));
-    });
-  }
-  
+
+      process.nextTick(function () {
+        emitter.emit('stop', forever.format(format, processes));
+      });
+    }
+    else {
+      process.nextTick(function () {
+        emitter.emit('error', new Error('Cannot find forever process: ' + target));
+      });
+    }
+  });
+
   return emitter;
-};

function restart (target, format)

+};

function restart (target, format)

@target {string} Index or script name to restart

@format {boolean} Indicated if we should CLI format the returned output.

-

Restarts the process(es) with the specified index or script name +

Restarts the process(es) with the specified index or script name in the list of all processes

forever.restart = function (target, format) {
-  var emitter = new events.EventEmitter(), 
+  var emitter = new events.EventEmitter(),
       runner = forever.stop(target, false);
-      
+
   runner.on('stop', function (procs) {
     if (procs && procs.length > 0) {
-      async.forEach(procs, function (proc, next) {

We need to spawn a new process running the forever CLI + async.forEach(procs, function (proc, next) {

We need to spawn a new process running the forever CLI here because we want each process to daemonize separately without the main process running forever restart myscript.js -daemonizing itself.

        var restartCommand = [
-          'forever', 
+daemonizing itself.

        var restartCommand = [
+          'forever',
           'start',
           '-d', proc.sourceDir,
           '-l', proc.logFile,
           '--append'
         ];
-        
+
         if (proc.silent) {
           restartCommand.push('--silent');
         }
-        
+
         if (proc.outFile) {
           restartCommand.push('-o', path.join(proc.sourceDir, proc.outFile));
         }
-        
+
         if (proc.errFile) {
           restartCommand.push('-e', path.join(proc.sourceDir, proc.outFile));
         }
-        
+
         restartCommand.push(proc.file, proc.options.join(' '));
         exec(restartCommand.join(' '), function (err, stdout, stderr) {
           next();
         });
       }, function () {
-        emitter.emit('restart', format ? forever.list(true, procs) : procs);
+        emitter.emit('restart', forever.format(format, procs));
       });
     }
     else {
       emitter.emit('error', new Error('Cannot find forever process: ' + target));
     }
-  });
-  

Bubble up the error to the appropriate EventEmitter instance.

  runner.on('error', function (err) {
+  });

Bubble up the error to the appropriate EventEmitter instance.

  runner.on('error', function (err) {
     emitter.emit('error', err);
   });
-  
+
   return emitter;
-};

function findByIndex (index, processes)

+};

function findByIndex (index, processes)

@index {string} Index of the process to find.

@@ -240,180 +275,175 @@

@processes {Array} Set of processes to find in.

Finds the process with the specified index.

forever.findByIndex = function (index, processes) {
   var proc = processes && processes[parseInt(index)];
   return proc ? [proc] : null;
-};

function findByScript (script, processes)

+};

function findByScript (script, processes)

@script {string} The name of the script to find.

@processes {Array} Set of processes to find in.

Finds the process with the specified script name.

forever.findByScript = function (script, processes) {
-  return processes 
+  return processes
     ? processes.filter(function (p) { return p.file === script })
-    : null; 
-};

function stopAll (format)

+ : null; +};

function stopAll (format)

@format {boolean} Value indicating if we should format output

Stops all processes managed by forever.

forever.stopAll = function (format) {
-  var emitter = new events.EventEmitter(),
-      processes = getAllProcesses() || [],
-      pids = getAllPids(processes);
+  var emitter = new events.EventEmitter();
 
-  if (format) {
-    processes = forever.list(format, processes);
-  }    
-  
-  if (pids && processes) {
-    var fPids = pids.map(function (pid) { return pid.foreverPid }),
-        cPids = pids.map(function (pid) { return pid.pid });
-    
-    fPids.concat(cPids).forEach(function (pid) {
-      try {
-        if (pid !== process.pid) {
-          process.kill(pid);
+  getAllProcesses(function (processes) {
+    var pids = getAllPids(processes);
+
+    if (format) {
+      processes = forever.format(format, processes);
+    }
+
+    if (pids && processes) {
+      var fPids = pids.map(function (pid) { return pid.foreverPid }),
+          cPids = pids.map(function (pid) { return pid.pid });
+
+      fPids.concat(cPids).forEach(function (pid) {
+        try {
+          if (pid !== process.pid) {
+            process.kill(pid);
+          }
         }
-      }
-      catch (ex) { }
-    });
-    
-    process.nextTick(function () {
-      emitter.emit('stopAll', processes);
-    });
-  }
-  else {
-    process.nextTick(function () {
-      emitter.emit('stopAll', null);
-    });
-  }
-  
+        catch (ex) { }
+      });
+
+      process.nextTick(function () {
+        emitter.emit('stopAll', processes);
+      });
+    }
+    else {
+      process.nextTick(function () {
+        emitter.emit('stopAll', null);
+      });
+    }
+  });
+
   return emitter;
-};

function list (format, procs)

+};

function list (format, procs, callback)

@format {boolean} If set, will return a formatted string of data

-

@procs {Array} Set of processes to list format.

+

@callback {function} Continuation to respond to when complete.

-

Returns the list of all process data managed by forever.

forever.list = function (format, procs) {
-  var formatted;
-  
-  procs = procs || getAllProcesses();
-  if (!procs) {
+

Returns the list of all process data managed by forever.

forever.list = function (format, callback) {
+  getAllProcesses(function (processes) {
+    callback(null, forever.format(format, processes));
+  });
+};

function format (format, procs)

+ +

@format {Boolean} Value indicating if processes should be formatted

+ +

@procs {Array} Processes to format

+ +

Returns a formatted version of the procs supplied based on the column +configuration in forever.config.

forever.format = function (format, procs) {
+  if (!procs || procs.length === 0) {
     return null;
   }
-  
+
   if (format) {
-    var index = 0, rows = [
-      ['   ', 'command ', 'script', 'forever ', 'pid', 'logfile', 'uptime']
-    ];
-    

Iterate over the procs to see which has the + var index = 0, + columns = forever.config.get('columns'), + rows = [[' '].concat(columns)]; + + function mapColumns (prefix, mapFn) { + return [prefix].concat(columns.map(mapFn)); + } +

Iterate over the procs to see which has the longest options string

    procs.forEach(function (proc) {
-      rows.push([
-        '[' + index + ']',
-        (proc.command || 'node').grey,
-        [proc.file].concat(proc.options).join(' ').grey,
-        proc.foreverPid,
-        proc.pid, 
-        proc.logFile ? proc.logFile.magenta : '',
-        timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow
-      ]);
+      rows.push(mapColumns('[' + index + ']', function (column) {
+        return forever.columns[column] 
+          ? forever.columns[column].get(proc) 
+          : 'MISSING';
+      }));
       
       index++;
     });
-    
-    formatted = cliff.stringifyRows(rows, [
-      'white', 
-      'grey',
-      'grey',
-      'white',
-      'white',
-      'magenta',
-      'yellow'
-    ]);
+
+    formatted = cliff.stringifyRows(rows, mapColumns('white', function (column) {
+      return forever.columns[column] 
+        ? forever.columns[column].color 
+        : 'white';
+    }));
   }
-  
+
   return format ? formatted : procs;
-};

function cleanUp ()

+}

function cleanUp ()

-

Utility function for removing excess pid and +

Utility function for removing excess pid and config, and log files used by forever.

forever.cleanUp = function (cleanLogs, allowManager) {
   var emitter = new events.EventEmitter(),
-      processes = getAllProcesses(true),
       pidPath = forever.config.get('pidPath');
-  
-  if (cleanLogs) {
-    forever.cleanLogsSync(processes);
-  }
- 
-  if (processes && processes.length > 0) {
-    function tryUnlink (file, next) {
-      fs.unlink(file, function () { 

Ignore errors (in case the file doesnt exist).

        next();
-      });
+
+  getAllProcesses(function (processes) {
+    if (cleanLogs) {
+      forever.cleanLogsSync(processes);
     }
-    
-    function unlinkProcess (proc, done) {
-      var files = [
-        path.join(pidPath, proc.uid + '.fvr'),
-        path.join(pidPath, proc.uid + '.pid')
-      ];
-      
-      async.forEach(files, tryUnlink, function () {
-        if (cleanLogs && proc.logFile) {
-          return fs.unlink(proc.logFile, function () { 
-            done();
-          });
+
+    if (processes && processes.length > 0) {
+      function unlinkProcess (proc, done) {
+        fs.unlink(path.join(pidPath, proc.uid + '.pid'), function () {

Ignore errors (in case the file doesnt exist).

          if (cleanLogs && proc.logFile) {

If we are cleaning logs then do so if the process +has a logfile.

            return fs.unlink(proc.logFile, function () {
+              done();
+            });
+          }
+
+          done();
+        });
+      }
+
+      function cleanProcess (proc, done) {
+        if (proc.child && proc.manager) {
+          return done();
         }
-        
+        else if (!proc.child && !proc.manager
+          || (!proc.child && proc.manager && allowManager)
+          || proc.dead) {
+          return unlinkProcess(proc, done);
+        }

If we have a manager but no child, wait a moment +in-case the child is currently restarting, but only +if we have not already waited for this process

        if (!proc.waited) {
+          proc.waited = true;
+          return setTimeout(function () {
+            checkProcess(proc, done);
+          }, 500);
+        }
+
         done();
-      });
-    }
-    
-    function cleanProcess (proc, done) {
-      if (proc.child && proc.manager) {
-        return done();
-      }
-      else if (!proc.child && !proc.manager 
-        || (!proc.child && proc.manager && allowManager) 
-        || proc.dead) {
-        return unlinkProcess(proc, done);
       }
-      

If we have a manager but no child, wait a moment -in-case the child is currently restarting, but only -if we have not already waited for this process

      if (!proc.waited) {
-        proc.waited = true;
-        return setTimeout(function () {
-          checkProcess(proc, done);
-        }, 500);
+
+      function checkProcess (proc, next) {
+        forever.checkProcess(proc.pid, function (child) {
+          proc.child = child;
+          forever.checkProcess(proc.foreverPid, function (manager) {
+            proc.manager = manager;
+            cleanProcess(proc, next);
+          });
+        });
       }
-      
-      done();
-    }
-    
-    function checkProcess (proc, next) {
-      forever.checkProcess(proc.pid, function (child) {
-        proc.child = child;
-        forever.checkProcess(proc.foreverPid, function (manager) {
-          proc.manager = manager;
-          cleanProcess(proc, next);
+
+      (function cleanBatch (batch) {
+        async.forEach(batch, checkProcess, function () {
+          return processes.length > 0
+            ? cleanBatch(processes.splice(0, 10))
+            : emitter.emit('cleanUp');
         });
-      });
+      })(processes.splice(0, 10));
     }
-    
-    (function cleanBatch (batch) {
-      async.forEach(batch, checkProcess, function () {
-        return processes.length > 0 
-          ? cleanBatch(processes.splice(0, 10))
-          : emitter.emit('cleanUp');
+    else {
+      process.nextTick(function () {
+        emitter.emit('cleanUp');
       });
-    })(processes.splice(0, 10)); 
-  }
-  else {
-    process.nextTick(function () {
-      emitter.emit('cleanUp');
-    });
-  }
-  
+    }
+  });
+
   return emitter;
-};

function cleanLogsSync (processes)

+};

function cleanLogsSync (processes)

@processes {Array} The set of all forever processes

@@ -423,29 +453,28 @@

@processes {Array} The set of all forever processes

files = fs.readdirSync(root), running = processes && processes.filter(function (p) { return p && p.logFile }), runningLogs = running && running.map(function (p) { return p.logFile.split('/').pop() }); - + files.forEach(function (file) { if (/\.log$/.test(file) && (!runningLogs || runningLogs.indexOf(file) === -1)) { fs.unlinkSync(path.join(root, file)); } }); -};

function randomString (bits)

+};

function randomString (bits)

@bits {Number} Bit-length of the base64 string to return.

-

Returns a pseude-random ASCII string which contains at least -the specified number of bits of entropy the return value is a string of +

Returns a pseude-random ASCII string which contains at least +the specified number of bits of entropy the return value is a string of length ⌈bits/6⌉ of characters from the base64 alphabet.

forever.randomString = function (bits) {
-  var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$', 
-      rand, i, ret = '';
-  

in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53)

  while (bits > 0) {
-    rand = Math.floor(Math.random()*0x100000000) // 32-bit integer

base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters.

    for (i=26; i>0 && bits>0; i-=6, bits-=6) { 
+  var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$',
+      rand, i, ret = '';

in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53)

  while (bits > 0) {
+    rand = Math.floor(Math.random()*0x100000000) // 32-bit integer

base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters.

    for (i=26; i>0 && bits>0; i-=6, bits-=6) {
       ret+=chars[0x3F & rand >>> i];
     }
   }
-  
+
   return ret;
-};

function logFilePath (logFile)

+};

function logFilePath (logFile)

@logFile {string} Log file path

@@ -453,15 +482,15 @@

@logFile {string} Log file path

return logFile && logFile[0] === '/' ? logFile : path.join(forever.config.get('root'), logFile || (uid || 'forever') + '.log'); -};

function pidFilePath (pidFile)

+};

function pidFilePath (pidFile)

@logFile {string} Pid file path

Determines the full pid file path name

forever.pidFilePath = function(pidFile) {
-  return pidFile && pidFile[0] === '/' 
+  return pidFile && pidFile[0] === '/'
     ? pidFile
     : path.join(forever.config.get('pidPath'), pidFile);
-};

function checkProcess (pid, callback)

+};

function checkProcess (pid, callback)

@pid {string} pid of the process to check

@@ -471,82 +500,109 @@

@callback {function} Continuation to pass control backto.

if (!pid) { return callback(false); } - + exec('ps ' + pid + ' | grep -v PID', function (err, stdout, stderr) { if (err) { return callback(false); } - + callback(stdout.indexOf(pid) !== -1); }); -};

function getAllProcess ([findDead])

- -

@findDead {boolean} Optional parameter that indicates to return dead procs

- -

Returns all data for processes managed by forever.

function getAllProcesses (findDead) {
-  var results = [], 
-      processes = {},
-      files = fs.readdirSync(forever.config.get('pidPath'));
-  
-  if (files.length === 0) {
-    return null;
-  }
-  
-  files.forEach(function (file) {
-    try {
-      var fullPath = path.join(forever.config.get('pidPath'), file),
-          ext = path.extname(file),
-          uid = file.replace(ext, ''),
-          data = fs.readFileSync(fullPath).toString();
-
-      switch (ext) {
-        case '.pid':
-          var pid = parseInt(data);
-          processes[uid] = processes[uid] || { 
-            foreverPid: pid,
-            uid: uid
-          };
-          break;
-
-        case '.fvr':
-          var child = JSON.parse(data);
-          processes[uid] = child;
-          break;
-      }
+};

@columns {Object}

+ +

Property descriptors for accessing forever column information +through forever list and forever.list()

forever.columns = {
+  uid: {
+    color: 'white',
+    get: function (proc) {
+      return proc.uid;
     }
-    catch (ex) {

Ignore errors

      processes[uid] = {
-        uid: uid
-      };
+  },
+  command: {
+    color: 'grey',
+    get: function (proc) {
+      return (proc.command || 'node').grey;
     }
-  });
-  
-  Object.keys(processes).forEach(function (key) {
-    if (!processes[key].pid && !findDead) {
-      return;
+  },
+  script: { 
+    color: 'grey',
+    get: function (proc) {
+      return [proc.file].concat(proc.options).join(' ').grey;
     }
-    else if (!processes[key].pid) {
-      processes[key].dead = true;
+  },
+  forever: { 
+    color: 'white',
+    get: function (proc) {
+      return proc.foreverPid;
     }
-    
-    results.push(processes[key]);
-  });
+  },
+  pid: { 
+    color: 'white',
+    get: function (proc) {
+      return proc.pid;
+    }
+  },
+  logfile: { 
+    color: 'magenta',
+    get: function (proc) {
+      return proc.logFile ? proc.logFile.magenta : '';
+    }
+  },
+  uptime: { 
+    color: 'yellow',
+    get: function (proc) {
+      return timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow;
+    }
+  }
+};

function getAllProcess (callback)

+ +

@callback {function} Continuation to respond to when complete.

+ +

Returns all data for processes managed by forever.

function getAllProcesses (callback) {
+  var results = [],
+      sockPath = forever.config.get('sockPath'),
+      sockets = fs.readdirSync(sockPath);
+
+  if (sockets.length === 0) {
+    return callback();
+  }
   
-  return results;
-};

function getAllPids ()

+ function getProcess (name, next) { + var fullPath = path.join(sockPath, name), + socket = new net.Socket({ type: 'unix' }); -

Returns the set of all pids managed by forever. -e.x. [{ pid: 12345, foreverPid: 12346 }, ...]

function getAllPids (processes) {
-  processes = processes || getAllProcesses();
-  if (processes) {
-    return processes.map(function (proc) {
-      return {
-        pid: proc.pid,
-        foreverPid: proc.foreverPid
+    socket.on('error', function (err) {
+      if (err.code === 'ECONNREFUSED') {
+        try { fs.unlinkSync(fullPath) }
+        catch (ex) { }
+        return next();
       }
+
+      next(err);
     });
+
+    socket.on('data', function (data) {
+      var monitors = JSON.parse(data.toString());
+      results.push.apply(results, monitors.monitors);
+      next();
+    });
+
+    socket.connect(fullPath);
   }
-  
-  return null;
+
+  async.forEach(sockets, getProcess, function () {
+    callback(results);
+  });
+};

function getAllPids ()

+ +

Returns the set of all pids managed by forever. +e.x. [{ pid: 12345, foreverPid: 12346 }, ...]

function getAllPids (processes) {
+  return !processes ? null : processes.map(function (proc) {
+    return {
+      pid: proc.pid,
+      foreverPid: proc.foreverPid
+    }
+  });
 };
 
 
\ No newline at end of file diff --git a/docs/forever/cli.html b/docs/forever/cli.html index dbba4835..e95893c0 100644 --- a/docs/forever/cli.html +++ b/docs/forever/cli.html @@ -22,35 +22,39 @@

@file {string} Location of the target forever script or process.

@options {Object} Options to pass to forever for the action.

Executes the action in forever with the specified file and options.

cli.exec = function (action, file, options) {
+  var display = Array.isArray(action) ? action.join(' ') : action;
+  
   if (action) {
-    forever.log.info('Running action: ' + action.yellow);
+    forever.log.info('Running action: ' + display.yellow);
   }
-  
-  forever.log.silly('Tidying ' + forever.config.get('root'));
-  var tidy = forever.cleanUp(action === 'cleanlogs'); 
-  tidy.on('cleanUp', function () {
-    forever.log.silly(forever.config.get('root') + ' tidied.');
 
-    if (file && action === 'start') {
-      forever.log.info('Forever processing file: ' + file.grey);
-    }
-    
-    if (options.command) {
-      forever.log.info('Forever using command: ' + options.command.grey);
-    }
+  if (action === 'cleanlogs') {
+    forever.log.silly('Tidying ' + forever.config.get('root'));
+    var tidy = forever.cleanUp(action === 'cleanlogs');
+    tidy.on('cleanUp', function () {
+      forever.log.silly(forever.config.get('root') + ' tidied.');
+    });
+    return;
+  }
 
-    if (options && action !== 'set') {
-      forever.log.silly('Forever using options', options);
-    }

If there is no action then start in the current -process with the specified file and options.

    if (!action) {
-      return cli.start(file, options);
-    }
-    else if (action === 'cleanlogs') {
-      return;
-    }
+  if (file && action === 'start') {
+    forever.log.info('Forever processing file: ' + file.grey);
+  }
 
-    cli[action](file, options, true);
-  });
+  if (options.command) {
+    forever.log.info('Forever using command: ' + options.command.grey);
+  }
+
+  if (options && action !== 'set') {
+    forever.log.silly('Forever using options', options);
+  }

If there is no action then start in the current +process with the specified file and options.

  if (!action) {
+    return cli.start(file, options);
+  }
+
+  return Array.isArray(action) 
+    ? cli[action[0]](action[1], options)
+    : cli[action](file, options, true);
 };

function start (file, options, daemon)

@file {string} Location of the script to spawn with forever

@@ -59,13 +63,17 @@

@options {Object} Options to spawn the script file with.

@daemon {boolean} Value indicating if we should spawn as a daemon

-

Starts a forever process for the script located at file with the -specified options. If daemon is true, then the script will be +

Starts a forever process for the script located at file with the +specified options. If daemon is true, then the script will be started as a daemon process.

cli.start = function (file, options, daemon) {
-  tryStart(file, options, function () { 
-    return daemon 
+  tryStart(file, options, function () {
+    var monitor = daemon
       ? forever.startDaemon(file, options)
       : forever.start(file, options);
+
+    monitor.on('start', function () {
+      forever.startServer(monitor);
+    });
   });
 };

function stop (file)

@@ -73,12 +81,12 @@

@file {string} Target forever process to stop

Stops the forever process specified by file.

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)
   });
@@ -114,7 +122,7 @@ 

@file {string} Target process to restart

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); @@ -122,22 +130,23 @@

@file {string} Target process to restart

};

function list ()

Lists all currently running forever processes.

cli.list = function () {
-  var processes = forever.list(true);
-  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');
-  }
+  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.

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}')
@@ -147,7 +156,7 @@ 

@file {string} Target process to restart

else { conf = conf.replace(/\n\s{4}/ig, '\n '); } - + conf.split('\n').forEach(function (line) { forever.log.data(line); }); @@ -162,7 +171,7 @@

@value {string} Value to set for key

if (!key || !value) { return forever.log.error('Both <key> and <value> are required.'); } - + updateConfig(function () { forever.log.info('Setting forever config: ' + key.grey); forever.config.set(key, value); @@ -177,12 +186,65 @@

@key {string} Key to remove from ~/.forever/config.json

forever.log.warn('Use `forever set ' + key + '` instead'); return; } - + updateConfig(function () { forever.log.info('Clearing forever config: ' + key.grey); forever.config.clear(key); }); -};

@private function (file, options, callback)

+};

function columns (action, value)

+ +

@action {string} The subaction to execute

+ +

@value {Array} The value to use in the specified action.

+ +

Executes the specified subaction: add, rm, and set which +add, remove, or completely overrides the columns used by forever list +and forever.list().

cli.columns = function (action, value) {
+  if (!~['add', 'rm', 'set'].indexOf(action)) {
+    forever.log.error('Invalid action: ' + ('columns ' + action).yellow);
+    forever.log.info('Use: ' + 'columns <add|rm|set>'.yellow);
+    return;
+  }
+  
+  var columns = forever.config.get('columns'),
+      actions = { add: addColumn, rm: rmColumn, set: setColumns },
+      allColumns = Object.keys(forever.columns);
+  
+  function addColumn () {
+    if (~columns.indexOf(value)) {
+      return forever.log.warn(value.magenta + ' already exists in forever');
+    }
+    
+    forever.log.info('Adding column: ' + value.magenta);
+    columns.push(value);
+  }
+  
+  function rmColumn () {
+    if (!~columns.indexOf(value)) {
+      return forever.log.warn(value.magenta + ' doesn\'t exist in forever');
+    }
+    
+    forever.log.info('Removing column: ' + value.magenta);
+    columns.splice(columns.indexOf(value), 1);
+  }
+  
+  function setColumns () {
+    forever.log.info('Setting columns: ' + value.join(' ').magenta);
+    columns = value;
+  }
+
+  if (action !== 'set') {
+    value = value[0];
+  }
+
+  if (!~allColumns.indexOf(value)) {
+    return forever.log.error('Unknown column: ' + value.magenta);
+  }
+
+  actions[action]();
+  forever.config.set('columns', columns);
+  forever.config.saveSync();
+}

@private function (file, options, callback)

@file {string} Target script to start

@@ -196,28 +258,28 @@

@callback {function} Continuation to respond to when complete.

fullLog = forever.logFilePath(options.logFile, options.uid); fullScript = path.join(options.sourceDir, file); - + forever.stat(fullLog, fullScript, options.appendLog, function (err) { if (err) { forever.log.error('Cannot start forever'); forever.log.error(err.message); process.exit(-1); } - + callback(); }); -}

@private function updateConfig (updater)

+}

@private function updateConfig (updater)

@updater {function} Function which updates the forever config

-

Helper which runs the specified updater and then saves the forever +

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);
diff --git a/docs/forever/monitor.html b/docs/forever/monitor.html
index 6fafc164..b355901d 100644
--- a/docs/forever/monitor.html
+++ b/docs/forever/monitor.html
@@ -19,34 +19,29 @@ 

@script {string} Location of the target script to run.

@options {Object} Configuration for this instance.

Creates a new instance of forever with specified params.

var Monitor = exports.Monitor = function (script, options) {
-  events.EventEmitter.call(this);
-  

Setup basic configuration options

  options          = options || {};
+  var self = this;
+  events.EventEmitter.call(this);

Setup basic configuration options

  options          = options || {};
   this.silent      = options.silent || false;
   this.forever     = options.forever || false;
   this.uid         = options.uid || forever.randomString(24);
   this.pidFile     = options.pidFile || path.join(forever.config.get('pidPath'), this.uid + '.pid');
-  this.fvrFile     = path.join(forever.config.get('pidPath'), this.uid + '.fvr');
   this.max         = options.max;
   this.childExists = false;
-  this.times       = 0;
-  

Setup restart timing. These options control how quickly forever restarts -a child process as well as when to kill a "spinning" process

  this.minUptime     = typeof options.minUptime !== 'number' ? 2000 : options.minUptime;
-  this.spinSleepTime = options.spinSleepTime || null;
-  

Setup the command to spawn and the options to pass + this.times = 0;

Setup restart timing. These options control how quickly forever restarts +a child process as well as when to kill a "spinning" process

  this.minUptime     = typeof options.minUptime !== 'number' ? 0 : options.minUptime;
+  this.spinSleepTime = options.spinSleepTime || null;

Setup the command to spawn and the options to pass to that command.

  this.command   = options.command || 'node';
   this.options   = options.options || [];
   this.spawnWith = options.spawnWith || {};
   this.sourceDir = options.sourceDir;
   this.cwd       = options.cwd || null;
-  this.env       = options.env || {};
-  

Setup log files and logger for this instance.

  this.logFile = options.logFile || path.join(forever.config.get('root'), this.uid + '.log');
+  this.env       = options.env || {};

Setup log files and logger for this instance.

  this.logFile = options.logFile || path.join(forever.config.get('root'), this.uid + '.log');
   this.outFile = options.outFile;
   this.errFile = options.errFile;
   this.logger  = options.logger || new (winston.Logger)({
     transports: [new winston.transports.Console({ silent: this.silent })]
-  });
-  

Extend from the winston logger.

  this.logger.extend(this);
-  
+  });

Extend from the winston logger.

  this.logger.extend(this);
+
   if (Array.isArray(script)) {
     this.command = script[0];
     this.options = script.slice(1);
@@ -54,29 +49,36 @@ 

@options {Object} Configuration for this instance.

else { this.options.unshift(script); } - + if (this.sourceDir) { this.options[0] = path.join(this.sourceDir, this.options[0]); - } -

If we should log stdout, open a file buffer

  if (this.outFile) {
+  }

If we should log stdout, open a file buffer

  if (this.outFile) {
     this.stdout = fs.createWriteStream(this.outFile, { flags: 'a+', encoding: 'utf8', mode: 0666 });
-  }
-  

If we should log stderr, open a file buffer

  if (this.errFile) {
+  }

If we should log stderr, open a file buffer

  if (this.errFile) {
     this.stderr = fs.createWriteStream(this.errFile, { flags: 'a+', encoding: 'utf8', mode: 0666 });
+  }

Last if any hooks have been passed in attach +this instance to them

  if (options.hooks && options.hooks.length > 0) {
+    options.hooks.forEach(function (hook) {
+      if (typeof hook === 'function') {
+        return hook(self);
+      }
+
+      hook.attach(self);
+    });
   }
-};

Inherit from events.EventEmitter

sys.inherits(Monitor, events.EventEmitter);

function start ([restart])

+};

Inherit from events.EventEmitter

sys.inherits(Monitor, events.EventEmitter);

function start ([restart])

@restart {boolean} Value indicating whether this is a restart.

Start the process that this instance is configured for

Monitor.prototype.start = function (restart) {
   var self = this;
-  
+
   if (this.running && !restart) {
     process.nextTick(function () {
       self.emit('error', new Error('Cannot start process that is already running.'));
     });
   }
-  
+
   var child = this.trySpawn();
   if (!child) {
     process.nextTick(function () {
@@ -84,51 +86,44 @@ 

@restart {boolean} Value indicating whether this is a restart.

}); return this; } - + this.ctime = Date.now(); this.child = child; this.running = true; - - this.once('save', function (file, data) { - self.emit(restart ? 'restart' : 'start', self, file, data); - }); - - this.save(); -

Hook all stream data and process it

  function listenTo (stream) {
+
+  process.nextTick(function () {
+    self.emit(restart ? 'restart' : 'start', self, self.data);
+  });

Hook all stream data and process it

  function listenTo (stream) {
     function ldata (data) {
-      if (!self.silent && !self[stream]) {

If we haven't been silenced, and we don't have a file stream + if (!self.silent && !self[stream]) {

If we haven't been silenced, and we don't have a file stream to output to write to the process stdout stream

        process.stdout.write(data);
       }
-      else if (self[stream]) {

If we have been given an output file for the stream, write to it

        self[stream].write(data);
+      else if (self[stream]) {

If we have been given an output file for the stream, write to it

        self[stream].write(data);
       }
-      
+
       self.emit(stream, data);
     }
-    
+
     child[stream].on('data', ldata);
-    
+
     child.on('exit', function () {
       child[stream].removeListener('data', ldata);
     });
-  }
-  

Listen to stdout and stderr

  listenTo('stdout');
+  }

Listen to stdout and stderr

  listenTo('stdout');
   listenTo('stderr');
-    
+
   child.on('exit', function (code) {
     var spinning = Date.now() - self.ctime < self.minUptime;
     self.warn('Forever detected script exited with code: ' + code);
 
     function letChildDie() {
-      self.running = false;

If had to write to an stdout file, close it

      if (self.stdout) {
+      self.running = false;

If had to write to an stdout file, close it

      if (self.stdout) {
         self.stdout.end();
-      }
-      

If had to write to an stderr file, close it

      if (self.stderr) {
+      }

If had to write to an stderr file, close it

      if (self.stderr) {
         self.stderr.end();
       }
-      
-      fs.unlink(self.fvrFile, function () {
-        self.emit('exit', self, spinning);
-      });
+
+      self.emit('exit', self, spinning);
     }
 
     function restartChild() {
@@ -151,9 +146,9 @@ 

@restart {boolean} Value indicating whether this is a restart.

restartChild(); } }); - + return this; -};

function trySpawn()

+};

function trySpawn()

Tries to spawn the target Forever child process. Depending on configuration, it checks the first argument of the options @@ -171,19 +166,18 @@

@restart {boolean} Value indicating whether this is a restart.

this.spawnWith.cwd = this.cwd || this.spawnWith.cwd; this.spawnWith.env = this._getEnv(); - + return spawn(this.command, this.options, this.spawnWith); -};

function save ()

+};

@data {Object}

-

Persists this instance of forever to disk.

Monitor.prototype.save = function () {
+

Responds with the appropriate information about +this Monitor instance and it's associated child process.

Monitor.prototype.__defineGetter__('data', function () {
   var self = this;
-  
-  if (!this.running) {
-    process.nextTick(function () {
-      self.emit('error', new Error('Cannot save Forever instance that is not running'));
-    });
+
+  if (!this.running) {

TODO: Return settings from this forever instance +with a state indicator that it is currently stopped.

    return {};
   }
-  
+
   var childData = {
     ctime: this.ctime,
     command: this.command,
@@ -195,28 +189,24 @@ 

@restart {boolean} Value indicating whether this is a restart.

silent: this.silent, uid: this.uid }; - + ['pidFile', 'outFile', 'errFile', 'env', 'cwd'].forEach(function (key) { if (self[key]) { childData[key] = self[key]; } }); - + if (this.sourceDir) { childData.sourceDir = this.sourceDir; childData.file = childData.file.replace(this.sourceDir + '/', ''); } - + this.childData = childData; - - fs.writeFile(this.fvrFile, JSON.stringify(childData, null, 2), function (err) { - return err ? self.emit('error', err) : self.emit('save', self.fvrFile, childData); - }); -

Setup the forever process to listen to + return this.childData;

Setup the forever process to listen to SIGINT and SIGTERM events so that we can clean up the *.pid file

-

Remark: This should work, but the fd gets screwed up +

Remark: This should work, but the fd gets screwed up with the daemon process.

process.on('SIGINT', function () { @@ -228,59 +218,56 @@

@restart {boolean} Value indicating whether this is a restart.

}); process.on('exit', function () { fs.unlinkSync(childPath); -});

  
-  return this;
-};

function restart ()

+});

});

function restart ()

Restarts the target script associated with this instance.

Monitor.prototype.restart = function () {
   return this.kill(false);
-};

function stop ()

+};

function stop ()

-

Stops the target script associated with this instance. Prevents it from auto-respawning

Monitor.prototype.stop = function () {
+

Stops the target script associated with this instance. Prevents it from auto-respawning

Monitor.prototype.stop = function () {
   return this.kill(true);
-};

function kill (forceStop)

+};

function kill (forceStop)

@forceStop {boolean} Value indicating whether short circuit forever auto-restart.

-

Kills the ChildProcess object associated with this instance.

Monitor.prototype.kill = function (forceStop) {
+

Kills the ChildProcess object associated with this instance.

Monitor.prototype.kill = function (forceStop) {
   var self = this;
-  
+
   if (!this.child || !this.running) {
     process.nextTick(function () {
       self.emit('error', new Error('Cannot stop process that is not running.'));
     });
   }
-  else {

Set an instance variable here to indicate this + else {

Set an instance variable here to indicate this stoppage is forced so that when child.on('exit', ..) fires in Monitor.prototype.start we can short circuit and prevent auto-restart

    if (forceStop) {
       this.forceStop = true;
     }
-    
+
     this.child.kill();
     this.emit('stop', this.childData);
   }
-  
+
   return this;
-};

@private function _getEnv ()

+};

@private function _getEnv ()

Returns the environment variables that should be passed along to the target process spawned by this instance.

Monitor.prototype._getEnv = function () {
   var self   = this,
       extra  = Object.keys(this.env),
       merged = {};
-  
+
   if (extra.length === 0) {
     return process.env;
   }
-  
+
   function addKey (key, source) {
     merged[key] = source[key]
-  }
-  

Mixin the key:value pairs from process.env and the custom + }

Mixin the key:value pairs from process.env and the custom environment variables in this.env.

  Object.keys(process.env).forEach(function (k) { addKey(k, process.env) });
   extra.forEach(function (k) { addKey(k, self.env) });
-  
+
   return merged;
 };