├── .circleci └── config.yml ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── examples ├── all-env-vars.js ├── always-throw.js ├── cli-multiple-start ├── count-timer.js ├── custom-cwd.js ├── env-server.js ├── env-vars.js ├── error-on-timer.js ├── graceful-exit.js ├── list-multiple.js ├── log-on-interval.js ├── multiple-processes.js ├── process-send.js ├── server.js ├── signal-ignore.js └── spawn-and-error.js ├── lib ├── forever-monitor │ ├── common.js │ ├── monitor.js │ ├── plugins │ │ ├── index.js │ │ ├── logger.js │ │ └── watch.js │ └── utils.js └── index.js ├── package.json └── test ├── core └── check-process-test.js ├── fixtures ├── fork.js ├── gc.js ├── logs.js ├── send-pong.js ├── testnode ├── watch │ ├── .foreverignore │ ├── daemon.js │ ├── file │ ├── ignoredDir │ │ └── file │ └── removeMe └── watch_too │ └── file ├── helpers └── macros.js ├── monitor ├── env-spawn-test.js ├── fork-test.js ├── send-test.js ├── signal-test.js ├── simple-test.js ├── spin-test.js └── start-stop-test.js └── plugins ├── logger-test.js └── watch-test.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | commands: 4 | test-nodejs: 5 | steps: 6 | - run: 7 | name: Versions 8 | command: npm version 9 | - checkout 10 | - restore_cache: 11 | keys: 12 | - v{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ arch }}-npm-cache-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }} 13 | - v{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ arch }}-npm-cache-master-{{ .Environment.CIRCLE_JOB }} 14 | - run: 15 | name: Install dependencies 16 | command: npm install 17 | - run: 18 | name: Test 19 | command: npm run test:ci 20 | - save-npm-cache 21 | save-npm-cache: 22 | steps: 23 | - save_cache: 24 | key: v{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ arch }}-npm-cache-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package-lock.json" }} 25 | paths: 26 | - ~/.npm/_cacache 27 | jobs: 28 | node-v6: 29 | docker: 30 | - image: node:6 31 | steps: 32 | - test-nodejs 33 | node-v8: 34 | docker: 35 | - image: node:8 36 | steps: 37 | - test-nodejs 38 | node-v10: 39 | docker: 40 | - image: node:10 41 | steps: 42 | - test-nodejs 43 | node-v12: 44 | docker: 45 | - image: node:12 46 | steps: 47 | - test-nodejs 48 | node-v14: 49 | docker: 50 | - image: node:14 51 | steps: 52 | - test-nodejs 53 | 54 | workflows: 55 | version: 2 56 | node-multi-build: 57 | jobs: 58 | - node-v6 59 | - node-v8 60 | - node-v10 61 | - node-v12 62 | - node-v14 63 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | ecmaVersion: 2015, 4 | }, 5 | extends: [ 6 | 'eslint:recommended', 7 | 'prettier', 8 | ], 9 | plugins: [], 10 | rules: { 11 | 'no-console': 0, 12 | 'no-octal': 0, 13 | 'no-var': 2, 14 | 'no-empty': 0, 15 | 'no-debugger': 2, 16 | 'prefer-const': 2, 17 | 'no-fallthrough': 2, 18 | 'require-atomic-updates': 0, 19 | 'no-useless-escape': 0, 20 | 'no-unused-vars': 0 21 | }, 22 | env: { 23 | node: true, 24 | mocha: true, 25 | es6: true, 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | 17 | test/*.log 18 | .*.sw[op] 19 | test/fixtures/*.log 20 | .idea 21 | package-lock.json 22 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "arrowParens": "always", 5 | "endOfLine": "lf" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2010 Charlie Robbins & the Contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # forever-monitor [![Build Status](https://secure.travis-ci.org/foreverjs/forever-monitor.png)](http://travis-ci.org/foreverjs/forever-monitor) 2 | 3 | The core monitoring functionality of forever without the CLI 4 | 5 | ## Usage 6 | You can also use forever from inside your own Node.js code: 7 | 8 | ``` js 9 | var forever = require('forever-monitor'); 10 | 11 | var child = new (forever.Monitor)('your-filename.js', { 12 | max: 3, 13 | silent: true, 14 | args: [] 15 | }); 16 | 17 | child.on('exit', function () { 18 | console.log('your-filename.js has exited after 3 restarts'); 19 | }); 20 | 21 | child.start(); 22 | ``` 23 | 24 | ### Spawning a non-node process 25 | You can spawn non-node processes too. Either set the `command` key in the 26 | `options` hash or pass in an `Array` in place of the `file` argument like this: 27 | 28 | ``` js 29 | var forever = require('forever-monitor'); 30 | var child = forever.start([ 'perl', '-le', 'print "moo"' ], { 31 | max : 1, 32 | silent : true 33 | }); 34 | ``` 35 | 36 | ### Options available when using Forever in node.js 37 | There are several options that you should be aware of when using forever. Most of this configuration is optional. 38 | 39 | ``` js 40 | { 41 | // 42 | // Basic configuration options 43 | // 44 | 'silent': false, // Silences the output from stdout and stderr in the parent process 45 | 'uid': 'your-UID', // Custom uid for this forever process. (default: autogen) 46 | 'pidFile': 'path/to/a.pid', // Path to put pid information for the process(es) started 47 | 'max': 10, // Sets the maximum number of times a given script should run 48 | 'killTree': true, // Kills the entire child process tree on `exit` 49 | 50 | // 51 | // These options control how quickly forever restarts a child process 52 | // as well as when to kill a "spinning" process 53 | // 54 | 'minUptime': 2000, // Minimum time a child process has to be up. Forever will 'exit' otherwise. 55 | 'spinSleepTime': 1000, // Interval between restarts if a child is spinning (i.e. alive < minUptime). 56 | 57 | // 58 | // Command to spawn as well as options and other vars 59 | // (env, cwd, etc) to pass along 60 | // 61 | 'command': 'perl', // Binary to run (default: 'node') 62 | 'args': ['foo','bar'], // Additional arguments to pass to the script, 63 | 'sourceDir': 'script/path',// Directory that the source script is in 64 | 65 | // 66 | // Options for restarting on watched files. 67 | // 68 | 'watch': true, // Value indicating if we should watch files. 69 | 'watchIgnoreDotFiles': null, // Whether to ignore file starting with a '.' 70 | 'watchIgnorePatterns': null, // Ignore patterns to use when watching files. 71 | 'watchDirectory': null, // Top-level directory to watch from. You can provide multiple watchDirectory options to watch multiple directories (e.g. for cli: forever start -w='app' -w='some_other_directory' app\index.js) 72 | 73 | // 74 | // All or nothing options passed along to `child_process.spawn`. 75 | // 76 | 'spawnWith': { 77 | customFds: [-1, -1, -1], // that forever spawns. 78 | setsid: false, 79 | uid: 0, // Custom UID 80 | gid: 0, // Custom GID 81 | shell: false // Windows only - makes forever spawn in a shell 82 | }, 83 | 84 | // 85 | // More specific options to pass along to `child_process.spawn` which 86 | // will override anything passed to the `spawnWith` option 87 | // 88 | 'env': { 'ADDITIONAL': 'CHILD ENV VARS' }, 89 | 'cwd': '/path/to/child/working/directory', 90 | 91 | // 92 | // Log files and associated logging options for this instance 93 | // 94 | 'logFile': 'path/to/file', // Path to log output from forever process (when daemonized) 95 | 'outFile': 'path/to/file', // Path to log output from child stdout 96 | 'errFile': 'path/to/file', // Path to log output from child stderr 97 | 98 | // 99 | // ### function parseCommand (command, args) 100 | // #### @command {String} Command string to parse 101 | // #### @args {Array} Additional default arguments 102 | // 103 | // Returns the `command` and the `args` parsed from 104 | // any command. Use this to modify the default parsing 105 | // done by 'forever-monitor' around spaces. 106 | // 107 | 'parser': function (command, args) { 108 | return { 109 | command: command, 110 | args: args 111 | }; 112 | } 113 | } 114 | ``` 115 | 116 | ### Events available when using an instance of Forever in node.js 117 | Each forever object is an instance of the Node.js core EventEmitter. There are several core events that you can listen for: 118 | 119 | * **error** _[err]:_ Raised when an error occurs 120 | * **start** _[process, data]:_ Raised when the target script is first started. 121 | * **stop** _[process]:_ Raised when the target script is stopped by the user 122 | * **restart** _[forever]:_ Raised each time the target script is restarted 123 | * **exit** _[forever]:_ Raised when the target script actually exits (permanently). 124 | * **stdout** _[data]:_ Raised when data is received from the child process' stdout 125 | * **stderr** _[data]:_ Raised when data is received from the child process' stderr 126 | 127 | ### Typical console output 128 | 129 | When running the forever CLI tool, it produces debug outputs about which files have changed / how processes exited / etc. To get a similar behaviour with `forever-monitor`, add the following event listeners: 130 | 131 | ```js 132 | const child = new (forever.Monitor)('your-filename.js'); 133 | 134 | child.on('watch:restart', function(info) { 135 | console.error('Restarting script because ' + info.file + ' changed'); 136 | }); 137 | 138 | child.on('restart', function() { 139 | console.error('Forever restarting script for ' + child.times + ' time'); 140 | }); 141 | 142 | child.on('exit:code', function(code) { 143 | console.error('Forever detected script exited with code ' + code); 144 | }); 145 | ``` 146 | 147 | ## Installation 148 | 149 | ``` bash 150 | $ npm install forever-monitor 151 | ``` 152 | 153 | ## Run Tests 154 | 155 | ``` bash 156 | $ npm test 157 | ``` 158 | 159 | #### License: MIT 160 | #### Author: [Charlie Robbins](http://github.com/indexzero) 161 | #### Contributors: [Fedor Indutny](http://github.com/indutny), [James Halliday](http://substack.net/), [Charlie McConnell](http://github.com/avianflu), [Maciej Malecki](http://github.com/mmalecki) 162 | -------------------------------------------------------------------------------- /examples/all-env-vars.js: -------------------------------------------------------------------------------- 1 | console.log(JSON.stringify(process.env)); 2 | -------------------------------------------------------------------------------- /examples/always-throw.js: -------------------------------------------------------------------------------- 1 | throw new Error('Dont spin restart'); 2 | -------------------------------------------------------------------------------- /examples/cli-multiple-start: -------------------------------------------------------------------------------- 1 | forever start examples/server.js -p 8080 2 | forever start examples/server.js -p 8081 3 | forever start examples/server.js -p 8082 4 | forever list -------------------------------------------------------------------------------- /examples/count-timer.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | 3 | let count = 0; 4 | 5 | const id = setInterval(function() { 6 | util.puts('Count is ' + count + '. Incrementing now.'); 7 | count++; 8 | }, 1000); 9 | -------------------------------------------------------------------------------- /examples/custom-cwd.js: -------------------------------------------------------------------------------- 1 | console.log(process.cwd()); 2 | -------------------------------------------------------------------------------- /examples/env-server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | 3 | http 4 | .createServer(function(req, res) { 5 | res.writeHead(200, { 'Content-Type': 'application/json' }); 6 | res.write(JSON.stringify(process.env)); 7 | res.end(); 8 | }) 9 | .listen(8080); 10 | -------------------------------------------------------------------------------- /examples/env-vars.js: -------------------------------------------------------------------------------- 1 | console.log( 2 | JSON.stringify({ 3 | foo: process.env.FOO, 4 | bar: process.env.BAR, 5 | }) 6 | ); 7 | -------------------------------------------------------------------------------- /examples/error-on-timer.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | 3 | setTimeout(function() { 4 | util.puts('Throwing error now.'); 5 | throw new Error('User generated fault.'); 6 | }, 200); 7 | -------------------------------------------------------------------------------- /examples/graceful-exit.js: -------------------------------------------------------------------------------- 1 | process.on('SIGTERM', function() { 2 | console.log('received SIGTERM'); 3 | setTimeout(function() { 4 | console.log('Exiting after some time.'); 5 | process.exit(0); 6 | }, 1000); 7 | }); 8 | 9 | setInterval(function() { 10 | console.log('Heartbeat'); 11 | }, 100); 12 | 13 | // run with: --killSignal 14 | // forever --killSignal=SIGTERM -w start server.js 15 | -------------------------------------------------------------------------------- /examples/list-multiple.js: -------------------------------------------------------------------------------- 1 | const path = require('path'), 2 | async = require('async'), 3 | forever = require('../lib/forever'); 4 | 5 | function startServer(port, next) { 6 | const child = new forever.Monitor(script, { 7 | args: ['--port', port], 8 | silent: true, 9 | }); 10 | 11 | child.start(); 12 | child.on('start', function(_, data) { 13 | console.log('Forever process running server.js on ' + port); 14 | next(null, child); 15 | }); 16 | } 17 | 18 | // Array config data 19 | const script = path.join(__dirname, 'server.js'), 20 | ports = [8080, 8081, 8082]; 21 | 22 | async.map(ports, startServer, function(err, monitors) { 23 | forever.startServer(monitors, function() { 24 | // 25 | // Now that the server has started, run `forever.list()` 26 | // 27 | forever.list(false, function(err, data) { 28 | if (err) { 29 | console.log('Error running `forever.list()`'); 30 | console.dir(err); 31 | } 32 | 33 | console.log('Data returned from `forever.list()`'); 34 | console.dir(data); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /examples/log-on-interval.js: -------------------------------------------------------------------------------- 1 | setInterval(function() { 2 | console.log('Logging at ' + Date.now()); 3 | }, 100); 4 | -------------------------------------------------------------------------------- /examples/multiple-processes.js: -------------------------------------------------------------------------------- 1 | const util = require('util'), 2 | path = require('path'), 3 | forever = require('./../lib/forever'), 4 | script = path.join(__dirname, 'server.js'); 5 | 6 | const child1 = new forever.Monitor(script, { options: ['--port=8080'] }); 7 | child1.start(); 8 | util.puts('Forever process running server.js on 8080'); 9 | 10 | const child2 = new forever.Monitor(script, { options: ['--port=8081'] }); 11 | child2.start(); 12 | util.puts('Forever process running server.js on 8081'); 13 | -------------------------------------------------------------------------------- /examples/process-send.js: -------------------------------------------------------------------------------- 1 | setInterval(function() { 2 | if (process.send) { 3 | process.send({ from: 'child' }); 4 | } 5 | }, 1000); 6 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | const util = require('util'), 2 | http = require('http'), 3 | argv = require('yargs').argv; 4 | 5 | const port = argv.p || argv.port || 80; 6 | 7 | http 8 | .createServer(function(req, res) { 9 | console.log(req.method + ' request: ' + req.url); 10 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 11 | res.write('hello, i know nodejitsu.'); 12 | res.end(); 13 | }) 14 | .listen(port); 15 | 16 | /* server started */ 17 | util.puts('> hello world running on port ' + port); 18 | -------------------------------------------------------------------------------- /examples/signal-ignore.js: -------------------------------------------------------------------------------- 1 | function noop() { 2 | console.log('IGNORED!'); 3 | } 4 | process.on('SIGTERM', noop); 5 | process.on('SIGINT', noop); 6 | setInterval(function() { 7 | console.log('heartbeat'); 8 | }, 100); 9 | -------------------------------------------------------------------------------- /examples/spawn-and-error.js: -------------------------------------------------------------------------------- 1 | const util = require('util'), 2 | path = require('path'), 3 | spawn = require('child_process').spawn; 4 | 5 | const child = spawn('node', [path.join(__dirname, 'count-timer.js')], { 6 | cwd: __dirname, 7 | }); 8 | 9 | child.stdout.on('data', function(data) { 10 | util.puts(data); 11 | //throw new Error('User generated fault.'); 12 | }); 13 | 14 | child.stderr.on('data', function(data) { 15 | util.puts(data); 16 | }); 17 | 18 | child.on('exit', function(code) { 19 | util.puts('Child process exited with code: ' + code); 20 | }); 21 | -------------------------------------------------------------------------------- /lib/forever-monitor/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * common.js: Common methods used in `forever-monitor`. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const psTree = require('ps-tree'); 10 | 11 | // 12 | // ### function checkProcess (pid, callback) 13 | // #### @pid {string} pid of the process to check 14 | // #### @callback {function} Continuation to pass control backto. 15 | // Utility function to check to see if a pid is running 16 | // 17 | exports.checkProcess = function(pid) { 18 | if (!pid) { 19 | return false; 20 | } 21 | 22 | try { 23 | // 24 | // Trying to kill non-existent process here raises a ESRCH - no such 25 | // process exception. Also, signal 0 doesn't do no harm to a process - it 26 | // only checks if sending a signal to a given process is possible. 27 | // 28 | process.kill(pid, 0); 29 | return true; 30 | } catch (err) { 31 | return false; 32 | } 33 | }; 34 | 35 | exports.kill = function(pid, killTree, signal, callback) { 36 | signal = signal || 'SIGKILL'; 37 | callback = callback || function() {}; 38 | 39 | if (killTree && process.platform !== 'win32') { 40 | psTree(pid, function(err, children) { 41 | [pid] 42 | .concat( 43 | children.map(function(p) { 44 | return p.PID; 45 | }) 46 | ) 47 | .forEach(function(tpid) { 48 | try { 49 | process.kill(tpid, signal); 50 | } catch (ex) {} 51 | }); 52 | 53 | callback(); 54 | }); 55 | } else { 56 | try { 57 | process.kill(pid, signal); 58 | } catch (ex) {} 59 | callback(); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /lib/forever-monitor/monitor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * monitor.js: Core functionality for the Monitor object. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | const child_process = require('child_process'); 12 | const events = require('eventemitter2'); 13 | const spawn = child_process.spawn; 14 | const common = require('./common'); 15 | const cluster = require('cluster'); 16 | const plugins = require('./plugins'); 17 | const utils = require('./utils'); 18 | const util = require('util'); 19 | 20 | 21 | // 22 | // ### function Monitor (script, options) 23 | // #### @script {string} Location of the target script to run. 24 | // #### @options {Object} Configuration for this instance. 25 | // Creates a new instance of forever with specified `options`. 26 | // 27 | const Monitor = (exports.Monitor = function(script, options) { 28 | // 29 | // Simple bootstrapper for attaching logger 30 | // and watch plugins by default. Other plugins 31 | // can be attached through `monitor.use(plugin, options)`. 32 | // 33 | function bootstrap(monitor) { 34 | plugins.logger.attach.call(monitor, options); 35 | if (options.watch) { 36 | plugins.watch.attach.call(monitor, options); 37 | } 38 | } 39 | 40 | let execPath = process.execPath; 41 | const self = this; 42 | 43 | // 44 | // Setup basic configuration options 45 | // 46 | options = options || {}; 47 | this.silent = options.silent || false; 48 | this.killTree = options.killTree !== false; 49 | this.uid = options.uid || utils.randomString(4); 50 | this.id = options.id || false; 51 | this.pidFile = options.pidFile; 52 | this.max = options.max; 53 | this.killTTL = options.killTTL; 54 | this.killSignal = options.killSignal || 'SIGKILL'; 55 | this.childExists = false; 56 | this.checkFile = options.checkFile !== false; 57 | this.times = 0; 58 | this.warn = console.error; 59 | 60 | this.logFile = options.logFile; 61 | this.outFile = options.outFile; 62 | this.errFile = options.errFile; 63 | this.append = options.append; 64 | this.usePolling = options.usePolling; 65 | this.pollingInterval = options.pollingInterval; 66 | 67 | // 68 | // Define some safety checks for commands with spaces 69 | // 70 | this.parser = options.parser || Monitor.parseCommand; 71 | 72 | // 73 | // Setup restart timing. These options control how quickly forever restarts 74 | // a child process as well as when to kill a "spinning" process 75 | // 76 | this.minUptime = 77 | typeof options.minUptime !== 'number' ? 0 : options.minUptime; 78 | this.spinSleepTime = options.spinSleepTime || null; 79 | 80 | // 81 | // Special case Windows separately to decouple any 82 | // future changes 83 | // 84 | if (process.platform === 'win32') { 85 | execPath = '"' + execPath + '"'; 86 | } 87 | 88 | if (options.options) { 89 | console.warn('options.options is deprecated. Use options.args instead.'); 90 | } 91 | 92 | // 93 | // Setup the command to spawn and the options to pass 94 | // to that command. 95 | // 96 | this.command = options.command || execPath; 97 | this.args = options.args || options.options || []; 98 | this.spawnWith = options.spawnWith || {}; 99 | this.sourceDir = options.sourceDir; 100 | this.fork = options.fork || false; 101 | this.cwd = options.cwd || process.cwd(); 102 | this.hideEnv = options.hideEnv || []; 103 | this._env = options.env || {}; 104 | this._hideEnv = {}; 105 | 106 | // 107 | // Allow for custom stdio configuration of forked processes 108 | // 109 | this.stdio = options.stdio || null; 110 | 111 | // 112 | // Setup watch configuration options 113 | // 114 | this.watchIgnoreDotFiles = options.watchIgnoreDotFiles !== false; 115 | this.watchIgnorePatterns = options.watchIgnorePatterns || []; 116 | this.watchDirectory = options.watchDirectory || this.sourceDir; 117 | 118 | // 119 | // Create a simple mapping of `this.hideEnv` to an easily indexable 120 | // object 121 | // 122 | this.hideEnv.forEach(function(key) { 123 | self._hideEnv[key] = true; 124 | }); 125 | 126 | if (Array.isArray(script)) { 127 | this.command = script[0]; 128 | this.args = script.slice(1); 129 | } else { 130 | this.args.unshift(script); 131 | } 132 | 133 | if (this.sourceDir) { 134 | this.args[0] = path.join(this.sourceDir, this.args[0]); 135 | } 136 | 137 | // 138 | // Bootstrap this instance now that options 139 | // have been set 140 | // 141 | bootstrap(this); 142 | }); 143 | 144 | // Inherit from events.EventEmitter 145 | util.inherits(Monitor, events.EventEmitter2); 146 | 147 | // 148 | // ### function start ([restart]) 149 | // #### @restart {boolean} Value indicating whether this is a restart. 150 | // Start the process that this instance is configured for 151 | // 152 | Monitor.prototype.start = function(restart) { 153 | const self = this; 154 | 155 | if (this.running && !restart) { 156 | process.nextTick(function() { 157 | self.emit( 158 | 'error', 159 | new Error('Cannot start process that is already running.') 160 | ); 161 | }); 162 | return this; 163 | } 164 | 165 | const child = this.trySpawn(); 166 | if (!child) { 167 | process.nextTick(function() { 168 | self.emit( 169 | 'error', 170 | new Error('Target script does not exist: ' + self.args[0]) 171 | ); 172 | }); 173 | return this; 174 | } 175 | 176 | this.ctime = Date.now(); 177 | this.child = child; 178 | this.running = true; 179 | this.isMaster = cluster.isMaster; 180 | 181 | process.nextTick(function() { 182 | self.emit(restart ? 'restart' : 'start', self, self.data); 183 | }); 184 | 185 | function onMessage(msg) { 186 | self.emit('message', msg); 187 | } 188 | 189 | // Re-emit messages from the child process 190 | this.child.on('message', onMessage); 191 | 192 | child.on('exit', function(code, signal) { 193 | const spinning = Date.now() - self.ctime < self.minUptime; 194 | child.removeListener('message', onMessage); 195 | self.emit('exit:code', code, signal); 196 | 197 | function letChildDie() { 198 | self.running = false; 199 | self.forceStop = false; 200 | self.forceRestart = false; 201 | self.emit('exit', self, spinning); 202 | } 203 | 204 | function restartChild() { 205 | self.forceStop = false; 206 | self.forceRestart = false; 207 | process.nextTick(function() { 208 | self.start(true); 209 | }); 210 | } 211 | 212 | self.times++; 213 | 214 | if ( 215 | self.forceStop || 216 | (self.times >= self.max && !self.forceRestart) || 217 | (spinning && typeof self.spinSleepTime !== 'number' && !self.forceRestart) 218 | ) { 219 | letChildDie(); 220 | } else if (spinning) { 221 | setTimeout(restartChild, self.spinSleepTime); 222 | } else { 223 | restartChild(); 224 | } 225 | }); 226 | 227 | return this; 228 | }; 229 | 230 | // 231 | // ### function trySpawn() 232 | // Tries to spawn the target Forever child process. Depending on 233 | // configuration, it checks the first argument of the options 234 | // to see if the file exists. This is useful is you are 235 | // trying to execute a script with an env: e.g. node myfile.js 236 | // 237 | Monitor.prototype.trySpawn = function() { 238 | const run = this.parser(this.command, this.args.slice()); 239 | let stats; 240 | 241 | if (/[^\w]node$/.test(this.command) && this.checkFile && !this.childExists) { 242 | try { 243 | stats = fs.statSync(this.args[0]); 244 | this.childExists = true; 245 | } catch (ex) { 246 | return false; 247 | } 248 | } 249 | 250 | this.spawnWith.cwd = this.spawnWith.cwd || this.cwd; 251 | this.spawnWith.env = this._getEnv(); 252 | 253 | if (process.platform === 'win32') { 254 | this.spawnWith.detached = true; 255 | } 256 | 257 | if (this.stdio) { 258 | this.spawnWith.stdio = this.stdio; 259 | } 260 | 261 | if (this.fork) { 262 | if (!this.stdio) { 263 | this.spawnWith.stdio = ['pipe', 'pipe', 'pipe', 'ipc']; 264 | } 265 | return spawn(run.command, run.args, this.spawnWith); 266 | } 267 | 268 | return spawn(run.command, run.args, this.spawnWith); 269 | }; 270 | 271 | // 272 | // ### @data {Object} 273 | // Responds with the appropriate information about 274 | // this `Monitor` instance and it's associated child process. 275 | // 276 | Monitor.prototype.__defineGetter__('data', function() { 277 | const self = this; 278 | const childData = { 279 | ctime: this.ctime, 280 | command: this.command, 281 | file: this.args[0], 282 | foreverPid: process.pid, 283 | logFile: this.logFile, 284 | args: this.args.slice(1), 285 | pid: this.child ? this.child.pid : undefined, 286 | silent: this.silent, 287 | uid: this.uid, 288 | id: this.id, 289 | spawnWith: this.spawnWith, 290 | running: this.running, 291 | restarts: this.times, 292 | isMaster: this.isMaster, 293 | }; 294 | 295 | ['pidFile', 'outFile', 'errFile', 'env', 'cwd'].forEach(function(key) { 296 | if (self[key]) { 297 | childData[key] = self[key]; 298 | } 299 | }); 300 | 301 | if (this.sourceDir) { 302 | childData.sourceDir = this.sourceDir; 303 | childData.file = childData.file.replace(this.sourceDir + '/', ''); 304 | } 305 | 306 | this.childData = childData; 307 | return this.childData; 308 | 309 | // 310 | // Setup the forever process to listen to 311 | // SIGINT and SIGTERM events so that we can 312 | // clean up the *.pid file 313 | // 314 | // Remark: This should work, but the fd gets screwed up 315 | // with the daemon process. 316 | // 317 | // process.on('SIGINT', function () { 318 | // process.exit(0); 319 | // }); 320 | // 321 | // process.on('SIGTERM', function () { 322 | // process.exit(0); 323 | // }); 324 | // process.on('exit', function () { 325 | // fs.unlinkSync(childPath); 326 | // }); 327 | }); 328 | 329 | // 330 | // ### function restart () 331 | // Restarts the target script associated with this instance. 332 | // 333 | Monitor.prototype.restart = function() { 334 | this.times = this.times || 0; 335 | this.forceRestart = true; 336 | 337 | return !this.running ? this.start(true) : this.kill(false); 338 | }; 339 | 340 | // 341 | // ### function stop () 342 | // Stops the target script associated with this instance. Prevents it from auto-respawning 343 | // 344 | Monitor.prototype.stop = function() { 345 | return this.kill(true); 346 | }; 347 | 348 | // 349 | // ### function kill (forceStop) 350 | // #### @forceStop {boolean} Value indicating whether short circuit forever auto-restart. 351 | // Kills the ChildProcess object associated with this instance. 352 | // 353 | Monitor.prototype.kill = function(forceStop) { 354 | const child = this.child, 355 | self = this; 356 | let timer; 357 | 358 | if (!child || (!this.running && !this.forceRestart)) { 359 | process.nextTick(function() { 360 | self.emit('error', new Error('Cannot stop process that is not running.')); 361 | }); 362 | } else { 363 | // 364 | // Set an instance variable here to indicate this 365 | // stoppage is forced so that when `child.on('exit', ..)` 366 | // fires in `Monitor.prototype.start` we can short circuit 367 | // and prevent auto-restart 368 | // 369 | if (forceStop) { 370 | this.forceStop = true; 371 | // 372 | // If we have a time before we truly kill forcefully, set up a timer 373 | // 374 | if (this.killTTL) { 375 | timer = setTimeout(function() { 376 | common.kill( 377 | self.child.pid, 378 | self.killTree, 379 | self.killSignal || 'SIGKILL' 380 | ); 381 | }, this.killTTL); 382 | 383 | child.once('exit', function() { 384 | clearTimeout(timer); 385 | }); 386 | } 387 | } 388 | 389 | child.once('exit', function() { 390 | self.emit('stop', self.childData); 391 | if (self.forceRestart && !self.running) { 392 | self.start(true); 393 | } 394 | }); 395 | 396 | common.kill(this.child.pid, this.killTree, this.killSignal); 397 | } 398 | 399 | return this; 400 | }; 401 | 402 | // 403 | // ### function send () 404 | // Sends a message to a forked ChildProcess object associated with this instance. 405 | // see http://nodejs.org/api/child_process.html#child_process_child_send_message_sendhandle 406 | // 407 | Monitor.prototype.send = function(msg) { 408 | const child = this.child, 409 | self = this; 410 | 411 | if (!child || !this.running) { 412 | process.nextTick(function() { 413 | self.emit( 414 | 'error', 415 | new Error('Cannot send to process that is not running.') 416 | ); 417 | }); 418 | } 419 | 420 | if (child.send) { 421 | child.send(msg); 422 | } 423 | }; 424 | 425 | // 426 | // ### function toString () 427 | // Override default toString behavior and just respond 428 | // with JSON for this instance. 429 | // 430 | Monitor.prototype.toString = function() { 431 | return JSON.stringify(this); 432 | }; 433 | 434 | // 435 | // ### function inspect () 436 | // Set this to null so that `util.inspect` does not 437 | // return `undefined`.' 438 | // 439 | Monitor.prototype.inspect = null; 440 | 441 | // 442 | // ### @private function _getEnv () 443 | // Returns the environment variables that should be passed along 444 | // to the target process spawned by this instance. 445 | // 446 | Monitor.prototype._getEnv = function() { 447 | const self = this, 448 | merged = {}; 449 | 450 | function addKey(key, source) { 451 | merged[key] = source[key]; 452 | } 453 | 454 | // 455 | // Mixin the key:value pairs from `process.env` and the custom 456 | // environment variables in `this._env`. 457 | // 458 | Object.keys(process.env).forEach(function(key) { 459 | if (!self._hideEnv[key]) { 460 | addKey(key, process.env); 461 | } 462 | }); 463 | 464 | Object.keys(this._env).forEach(function(key) { 465 | addKey(key, self._env); 466 | }); 467 | 468 | return merged; 469 | }; 470 | 471 | // 472 | // ### function parseCommand (command, args) 473 | // #### @command {String} Command string to parse 474 | // #### @args {Array} Additional default arguments 475 | // 476 | // Returns the `command` and the `args` parsed 477 | // from the command depending on the Platform. 478 | // 479 | Monitor.parseCommand = function(command, args) { 480 | const match = command.match( 481 | process.platform === 'win32' ? safetyChecks.windows : safetyChecks.linux 482 | ); 483 | 484 | // 485 | // No match means it's a bad command. This is configurable 486 | // by passing a custom `parser` function into the `Monitor` 487 | // constructor function. 488 | // 489 | if (!match) { 490 | return false; 491 | } 492 | 493 | if (process.platform == 'win32') { 494 | command = match[1] || match[2]; 495 | if (match[3]) { 496 | args = match[3].split(' ').concat(args); 497 | } 498 | } else { 499 | command = match[1]; 500 | if (match[2]) { 501 | args = match[2].split(' ').concat(this.args); 502 | } 503 | } 504 | 505 | return { 506 | command: command, 507 | args: args, 508 | }; 509 | }; 510 | 511 | // 512 | // ### @private {Object} safetyChecks 513 | // Define default safety checks for commands 514 | // with spaces in Windows & Linux 515 | // 516 | const safetyChecks = { 517 | windows: /(?:"(.*[^\/])"|(\w+))(?:\s(.*))?/, 518 | linux: /(.*?[^\\])(?: (.*)|$)/, 519 | }; 520 | -------------------------------------------------------------------------------- /lib/forever-monitor/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * index.js: Built-in plugins for forever-monitor. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | exports.logger = require('./logger'); 10 | exports.watch = require('./watch'); 11 | -------------------------------------------------------------------------------- /lib/forever-monitor/plugins/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * logger.js: Plugin for `Monitor` instances which adds stdout and stderr logging. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const fs = require('fs'); 10 | 11 | // 12 | // Name the plugin 13 | // 14 | exports.name = 'logger'; 15 | 16 | // 17 | // ### function attach (options) 18 | // #### @options {Object} Options for attaching to `Monitor` 19 | // 20 | // Attaches functionality for logging stdout and stderr to `Monitor` instances. 21 | // 22 | exports.attach = function(options) { 23 | options = options || {}; 24 | const monitor = this; 25 | 26 | if (options.outFile) { 27 | monitor.stdout = 28 | options.stdout || 29 | fs.createWriteStream(options.outFile, { 30 | flags: monitor.append ? 'a+' : 'w+', 31 | encoding: 'utf8', 32 | mode: 0644, 33 | }); 34 | } 35 | 36 | if (options.errFile) { 37 | monitor.stderr = 38 | options.stderr || 39 | fs.createWriteStream(options.errFile, { 40 | flags: monitor.append ? 'a+' : 'w+', 41 | encoding: 'utf8', 42 | mode: 0644, 43 | }); 44 | } 45 | 46 | monitor.on('start', startLogs); 47 | monitor.on('restart', startLogs); 48 | monitor.on('exit', stopLogs); 49 | 50 | function stopLogs() { 51 | if (monitor.stdout) { 52 | // 53 | // Remark: 0.8.x doesnt have an unpipe method 54 | // 55 | monitor.child.stdout.unpipe && 56 | monitor.child.stdout.unpipe(monitor.stdout); 57 | monitor.stdout.destroy(); 58 | monitor.stdout = null; 59 | } 60 | // 61 | // Remark: 0.8.x doesnt have an unpipe method 62 | // 63 | if (monitor.stderr) { 64 | monitor.child.stderr.unpipe && 65 | monitor.child.stderr.unpipe(monitor.stderr); 66 | monitor.stderr.destroy(); 67 | monitor.stderr = null; 68 | } 69 | } 70 | 71 | function startLogs(_child, _childData) { 72 | if (monitor.child) { 73 | monitor.child.stdout.on('data', function onStdout(data) { 74 | monitor.emit('stdout', data); 75 | }); 76 | 77 | monitor.child.stderr.on('data', function onStderr(data) { 78 | monitor.emit('stderr', data); 79 | }); 80 | 81 | if (!monitor.silent) { 82 | process.stdout.setMaxListeners(0); 83 | process.stderr.setMaxListeners(0); 84 | monitor.child.stdout.pipe(process.stdout, { end: false }); 85 | monitor.child.stderr.pipe(process.stderr, { end: false }); 86 | } 87 | 88 | if (monitor.stdout) { 89 | monitor.child.stdout.pipe(monitor.stdout, { end: false }); 90 | } 91 | 92 | if (monitor.stderr) { 93 | monitor.child.stderr.pipe(monitor.stderr, { end: false }); 94 | } 95 | } 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /lib/forever-monitor/plugins/watch.js: -------------------------------------------------------------------------------- 1 | /* 2 | * watch.js: Plugin for `Monitor` instances which adds file watching. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const fs = require('fs'), 10 | path = require('path'), 11 | minimatch = require('minimatch'), 12 | chokidar = require('chokidar'); 13 | 14 | exports.name = 'watch'; 15 | 16 | // 17 | // ### @private function _watchFilter 18 | // #### @file {string} File name 19 | // Determines whether we should restart if `file` change (@mikeal's filtering 20 | // is pretty messed up). 21 | // 22 | function watchFilter(fileName) { 23 | let watchDirectory = this.watchDirectory, //this is array if multiple --watchDirectory options given, treat as array always 24 | result = true; 25 | 26 | if (!Array.isArray(watchDirectory)) { 27 | watchDirectory = [watchDirectory]; 28 | } 29 | 30 | watchDirectory.forEach( 31 | function(directory) { 32 | const relFileName = path.relative(directory, fileName), 33 | length = this.watchIgnorePatterns.length; 34 | let testName, i; 35 | 36 | if (this.watchIgnoreDotFiles && path.basename(fileName)[0] === '.') { 37 | result = false; 38 | } 39 | 40 | for (i = 0; i < length; i++) { 41 | if (this.watchIgnorePatterns[i].length > 0) { 42 | testName = 43 | this.watchIgnorePatterns[i].charAt(0) !== '/' 44 | ? relFileName 45 | : fileName; 46 | if ( 47 | minimatch(testName, this.watchIgnorePatterns[i], { 48 | matchBase: directory, 49 | }) 50 | ) { 51 | result = false; 52 | } 53 | } 54 | } 55 | }.bind(this) 56 | ); 57 | 58 | return result; 59 | } 60 | 61 | // 62 | // ### function attach (options) 63 | // #### @options {Object} Options for attaching to `Monitor` 64 | // 65 | // Attaches functionality for logging stdout and stderr to `Monitor` instances. 66 | // 67 | exports.attach = function() { 68 | let watchDirectory = this.watchDirectory; 69 | const //this.watchDirectory is array if multiple --watchDirectory options given, treat as array always 70 | monitor = this; 71 | 72 | if (!Array.isArray(watchDirectory)) { 73 | watchDirectory = [watchDirectory]; 74 | } 75 | 76 | watchDirectory.forEach(function(directory) { 77 | fs.readFile(path.join(directory, '.foreverignore'), 'utf8', function( 78 | err, 79 | data 80 | ) { 81 | if (err) { 82 | return monitor.emit('watch:error', { 83 | message: 'Could not read .foreverignore file.', 84 | error: err.message, 85 | }); 86 | } 87 | 88 | Array.prototype.push.apply( 89 | monitor.watchIgnorePatterns, 90 | data.split('\n').filter(Boolean) 91 | ); 92 | }); 93 | }); 94 | 95 | const opts = { 96 | usePolling: this.usePolling, 97 | interval: this.pollingInterval, 98 | ignoreInitial: true, 99 | ignored: function(fileName) { 100 | return !watchFilter.call(monitor, fileName); 101 | }, 102 | }; 103 | 104 | // Or, ignore: function(fileName) { return !watchFilter(fileName) } 105 | chokidar.watch(this.watchDirectory, opts).on('all', function(event, f, stat) { 106 | monitor.emit('watch:restart', { file: f, stat: stat }); 107 | monitor.restart(); 108 | }); 109 | }; 110 | -------------------------------------------------------------------------------- /lib/forever-monitor/utils.js: -------------------------------------------------------------------------------- 1 | // 2 | // ### function randomString (length) 3 | // #### @length {integer} The number of bits for the random base64 string returned to contain 4 | // randomString returns a pseude-random ASCII string (subset) 5 | // the return value is a string of length ⌈bits/6⌉ of characters 6 | // from the base64 alphabet. 7 | // 8 | function randomString(length) { 9 | let rand, i, ret, bits; 10 | const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'; 11 | const mod = 4; 12 | 13 | ret = ''; 14 | // standard 4 15 | // default is 16 16 | bits = length * mod || 64; 17 | 18 | // in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53) 19 | while (bits > 0) { 20 | // 32-bit integer 21 | rand = Math.floor(Math.random() * 0x100000000); 22 | //we use the top bits 23 | for (i = 26; i > 0 && bits > 0; i -= mod, bits -= mod) { 24 | ret += chars[0x3f & (rand >>> i)]; 25 | } 26 | } 27 | 28 | return ret; 29 | } 30 | 31 | module.exports = { 32 | randomString, 33 | }; 34 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * index.js: Top-level include for the `forever-monitor` module. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const utils = require('./forever-monitor/utils') 10 | const common = require('./forever-monitor/common'); 11 | 12 | exports.kill = common.kill; 13 | exports.checkProcess = common.checkProcess; 14 | exports.Monitor = require('./forever-monitor/monitor').Monitor; 15 | 16 | // 17 | // Expose version through `pkginfo` 18 | // 19 | exports.version = require('../package').version; 20 | 21 | // 22 | // ### function start (script, options) 23 | // #### @script {string} Location of the script to run. 24 | // #### @options {Object} Configuration for forever instance. 25 | // Starts a script with forever 26 | // 27 | exports.start = function(script, options) { 28 | if (!options.uid) { 29 | options.uid = options.uid || utils.randomString(4).replace(/^\-/, '_'); 30 | } 31 | 32 | return new exports.Monitor(script, options).start(); 33 | }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forever-monitor", 3 | "description": "Core forever process monitor", 4 | "version": "3.0.3", 5 | "author": "Charlie Robbins ", 6 | "contributors": [ 7 | { 8 | "name": "Charlie Robbins", 9 | "email": "charlie@nodejitsu.com" 10 | }, 11 | { 12 | "name": "Fedor Indutny", 13 | "email": "fedor.indutny@gmail.com" 14 | }, 15 | { 16 | "name": "James Halliday", 17 | "email": "mail@substack.net" 18 | }, 19 | { 20 | "name": "Bradley Meck", 21 | "email": "bradley@nodejitsu.com" 22 | }, 23 | { 24 | "name": "Dominic Tarr", 25 | "email": "dominic@nodejitsu.com" 26 | }, 27 | { 28 | "name": "Maciej Małecki", 29 | "email": "maciej@nodejitsu.com" 30 | }, 31 | { 32 | "name": "Igor Savin", 33 | "email": "kibertoad@gmail.com" 34 | } 35 | ], 36 | "main": "./lib/index.js", 37 | "scripts": { 38 | "lint": "eslint \"lib/**/*.js\" \"test/**/*.js\" \"examples/**/*.js\"", 39 | "test": "vows test/**/*-test.js --dot-matrix -i", 40 | "test:ci": "npm run lint && npm run test", 41 | "prettier": "prettier --write \"{lib,examples,test}/**/*.js\"" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "http://github.com/foreversd/forever-monitor.git" 46 | }, 47 | "keywords": [ 48 | "fault tolerant", 49 | "sysadmin", 50 | "tools" 51 | ], 52 | "dependencies": { 53 | "async": "^1.5.2", 54 | "chokidar": "^2.1.8", 55 | "eventemitter2": "^6.4.3", 56 | "minimatch": "^3.0.4", 57 | "ps-tree": "^1.2.0" 58 | }, 59 | "devDependencies": { 60 | "eslint": "^5.16.0", 61 | "eslint-plugin-prettier": "^3.1.3", 62 | "eslint-config-prettier": "^6.11.0", 63 | "prettier": "^1.19.1", 64 | "semver": "5.7.1", 65 | "vows": "0.7.0", 66 | "yargs": "^3.32.0" 67 | }, 68 | "engines": { 69 | "node": ">=6" 70 | }, 71 | "files": [ 72 | "LICENSE", 73 | "README.md", 74 | "lib/*" 75 | ], 76 | "license": "MIT" 77 | } 78 | -------------------------------------------------------------------------------- /test/core/check-process-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * check-process-test.js: Tests for forever.checkProcess(pid) 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | vows = require('vows'), 11 | fmonitor = require('../../lib'); 12 | 13 | vows 14 | .describe('forever/core/check-process') 15 | .addBatch({ 16 | 'When using forever': { 17 | 'checking if process exists': { 18 | 'if process exists': { 19 | topic: fmonitor.checkProcess(process.pid), 20 | 'should return true': function(result) { 21 | assert.isTrue(result); 22 | }, 23 | }, 24 | "if process doesn't exist": { 25 | topic: fmonitor.checkProcess(255 * 255 * 255), 26 | // 27 | // This is insanely large value. On most systems there'll be no process 28 | // with such PID. Also, there's no multiplatform way to check for 29 | // PID limit. 30 | // 31 | 'should return false': function(result) { 32 | assert.isFalse(result); 33 | }, 34 | }, 35 | }, 36 | }, 37 | }) 38 | .export(module); 39 | -------------------------------------------------------------------------------- /test/fixtures/fork.js: -------------------------------------------------------------------------------- 1 | if (process.send) { 2 | process.send({ from: 'child' }); 3 | process.disconnect(); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/gc.js: -------------------------------------------------------------------------------- 1 | console.log(typeof global.gc); 2 | -------------------------------------------------------------------------------- /test/fixtures/logs.js: -------------------------------------------------------------------------------- 1 | for (let i = 0; i < 10; i++) { 2 | console.log('stdout %d', i); 3 | console.error('stderr %d', i); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/send-pong.js: -------------------------------------------------------------------------------- 1 | if (process.send) { 2 | process.on('message', function(m) { 3 | process.send({ pong: true, message: m }); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/testnode: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | process.stdout.write(process.argv[2]); -------------------------------------------------------------------------------- /test/fixtures/watch/.foreverignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | ignore* 3 | ignoredDir/* 4 | -------------------------------------------------------------------------------- /test/fixtures/watch/daemon.js: -------------------------------------------------------------------------------- 1 | console.log('Hello!'); 2 | //timeout should be large enough to stay alive during the test 3 | setTimeout(process.exit, 20000); 4 | -------------------------------------------------------------------------------- /test/fixtures/watch/file: -------------------------------------------------------------------------------- 1 | /* hello, I know nodejitsu. -------------------------------------------------------------------------------- /test/fixtures/watch/ignoredDir/file: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /test/fixtures/watch/removeMe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foreversd/forever-monitor/7e0f6b120bdebf582912f3f40ba33e2f7b549c59/test/fixtures/watch/removeMe -------------------------------------------------------------------------------- /test/fixtures/watch_too/file: -------------------------------------------------------------------------------- 1 | /* hello, I know nodejitsu. -------------------------------------------------------------------------------- /test/helpers/macros.js: -------------------------------------------------------------------------------- 1 | /* 2 | * macros.js: Test macros for the forever-monitor module 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | fmonitor = require('../../lib'); 11 | 12 | const macros = exports; 13 | 14 | macros.assertTimes = function(script, times, options) { 15 | options.max = times; 16 | 17 | return { 18 | topic: function() { 19 | const child = new fmonitor.Monitor(script, options); 20 | child.on('exit', this.callback.bind({}, null)); 21 | child.start(); 22 | }, 23 | "should emit 'exit' when completed": function(err, child) { 24 | assert.equal(child.times, times); 25 | }, 26 | }; 27 | }; 28 | 29 | macros.assertStartsWith = function(string, substring) { 30 | assert.equal(string.slice(0, substring.length), substring); 31 | }; 32 | 33 | macros.assertList = function(list) { 34 | assert.isNotNull(list); 35 | assert.lengthOf(list, 1); 36 | }; 37 | -------------------------------------------------------------------------------- /test/monitor/env-spawn-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * env-spawn-test.js: Tests for supporting environment variables in the forever module 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | path = require('path'), 11 | vows = require('vows'), 12 | fmonitor = require('../../lib'); 13 | 14 | vows 15 | .describe('forever-monitor/monitor/spawn-options') 16 | .addBatch({ 17 | 'When using forever-monitor': { 18 | 'an instance of Monitor with valid options': { 19 | 'passing environment variables to env-vars.js': { 20 | topic: function() { 21 | const that = this; 22 | 23 | this.env = { 24 | FOO: 'foo', 25 | BAR: 'bar', 26 | }; 27 | 28 | const child = new fmonitor.Monitor( 29 | path.join(__dirname, '..', '..', 'examples', 'env-vars.js'), 30 | { 31 | max: 1, 32 | silent: true, 33 | minUptime: 0, 34 | env: this.env, 35 | } 36 | ); 37 | 38 | child.on('stdout', function(data) { 39 | that.stdout = data.toString(); 40 | }); 41 | 42 | child.on('exit', this.callback.bind({}, null)); 43 | child.start(); 44 | }, 45 | 'should pass the environment variables to the child': function( 46 | err, 47 | child 48 | ) { 49 | assert.equal(child.times, 1); 50 | assert.equal(this.stdout, JSON.stringify(this.env)); 51 | }, 52 | }, 53 | 'passing a custom cwd to custom-cwd.js': { 54 | topic: function() { 55 | const that = this; 56 | this.cwd = path.join(__dirname, '..'); 57 | 58 | const child = new fmonitor.Monitor( 59 | path.join(__dirname, '..', '..', 'examples', 'custom-cwd.js'), 60 | { 61 | max: 1, 62 | silent: true, 63 | minUptime: 0, 64 | cwd: this.cwd, 65 | } 66 | ); 67 | 68 | child.on('stdout', function(data) { 69 | that.stdout = data.toString(); 70 | }); 71 | 72 | child.on('exit', this.callback.bind({}, null)); 73 | child.start(); 74 | }, 75 | 'should setup the child to run in the target directory': function( 76 | err, 77 | child 78 | ) { 79 | assert.equal(child.times, 1); 80 | assert.equal(this.stdout, this.cwd); 81 | }, 82 | }, 83 | 'setting `hideEnv` when spawning all-env-vars.js': { 84 | topic: function() { 85 | const that = this; 86 | let all = '', 87 | confirmed; 88 | 89 | this.hideEnv = ['USER', 'OLDPWD']; 90 | 91 | // 92 | // Remark (indexzero): This may be a symptom of a larger bug. 93 | // This test only fails when run under `npm test` (e.g. vows --spec -i). 94 | // 95 | function tryCallback() { 96 | if (confirmed) { 97 | return that.callback(null, child); 98 | } 99 | 100 | confirmed = true; 101 | } 102 | 103 | const child = new fmonitor.Monitor( 104 | path.join(__dirname, '..', '..', 'examples', 'all-env-vars.js'), 105 | { 106 | max: 1, 107 | silent: true, 108 | minUptime: 0, 109 | hideEnv: this.hideEnv, 110 | } 111 | ); 112 | 113 | child.on('stdout', function(data) { 114 | all += data; 115 | 116 | try { 117 | that.env = Object.keys(JSON.parse(all)); 118 | } catch (ex) {} 119 | tryCallback(); 120 | }); 121 | 122 | child.on('exit', tryCallback); 123 | child.start(); 124 | }, 125 | 'should hide the environment variables passed to the child': function( 126 | err, 127 | child 128 | ) { 129 | const that = this; 130 | 131 | this.hideEnv.forEach(function(key) { 132 | assert.isTrue(that.env.indexOf(key) === -1); 133 | }); 134 | }, 135 | }, 136 | }, 137 | }, 138 | }) 139 | .export(module); 140 | -------------------------------------------------------------------------------- /test/monitor/fork-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * spin-test.js: Tests for spin restarts in forever-monitor. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | path = require('path'), 11 | vows = require('vows'), 12 | fmonitor = require('../../lib'); 13 | 14 | vows 15 | .describe('forever-monitor/monitor/fork') 16 | .addBatch({ 17 | 'When using forever-monitor': { 18 | 'and spawning a script that uses `process.send()`': { 19 | "using the 'native' fork with default stdio": { 20 | topic: function() { 21 | const script = path.join(__dirname, '..', 'fixtures', 'fork.js'), 22 | child = new fmonitor.Monitor(script, { 23 | silent: false, 24 | minUptime: 2000, 25 | max: 1, 26 | fork: true, 27 | }); 28 | 29 | child.on('message', this.callback.bind(null, null)); 30 | child.start(); 31 | }, 32 | 'should reemit the message correctly': function(err, msg) { 33 | assert.isObject(msg); 34 | assert.deepEqual(msg, { from: 'child' }); 35 | }, 36 | }, 37 | }, 38 | }, 39 | }) 40 | .addBatch({ 41 | 'when spawning a script that uses `process.send()`': { 42 | 'using custom stdio and setting IPC to fd 0': { 43 | topic: function() { 44 | const script = path.join(__dirname, '..', 'fixtures', 'fork.js'), 45 | child = new fmonitor.Monitor(script, { 46 | silent: false, 47 | minUptime: 2000, 48 | max: 1, 49 | fork: true, 50 | stdio: ['ipc', 'pipe', 'pipe'], 51 | }); 52 | 53 | child.on('message', this.callback.bind(null, null)); 54 | child.start(); 55 | }, 56 | 'should reemit the message correctly': function(err, msg) { 57 | assert.isObject(msg); 58 | assert.deepEqual(msg, { from: 'child' }); 59 | }, 60 | }, 61 | }, 62 | }) 63 | .export(module); 64 | -------------------------------------------------------------------------------- /test/monitor/send-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * send-test.js: Tests sending child processes messages. 3 | * 4 | * (C) 2013 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | path = require('path'), 11 | vows = require('vows'), 12 | fmonitor = require('../../lib'); 13 | 14 | vows 15 | .describe('forever-monitor/monitor/send') 16 | .addBatch({ 17 | 'When using forever-monitor': { 18 | 'and spawning a script': { 19 | 'the parent process can send the child a message': { 20 | topic: function() { 21 | const script = path.join( 22 | __dirname, 23 | '..', 24 | 'fixtures', 25 | 'send-pong.js' 26 | ), 27 | child = new fmonitor.Monitor(script, { 28 | silent: false, 29 | minUptime: 2000, 30 | max: 1, 31 | fork: true, 32 | }); 33 | 34 | child.on('message', this.callback.bind(null, null)); 35 | child.start(); 36 | child.send({ from: 'parent' }); 37 | }, 38 | 'should reemit the message correctly': function(err, msg) { 39 | assert.isObject(msg); 40 | assert.deepEqual(msg, { message: { from: 'parent' }, pong: true }); 41 | }, 42 | }, 43 | }, 44 | }, 45 | }) 46 | .export(module); 47 | -------------------------------------------------------------------------------- /test/monitor/signal-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * signal-test.js: Tests for spin restarts in forever. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | path = require('path'), 11 | vows = require('vows'), 12 | fmonitor = require('../../lib'); 13 | 14 | vows 15 | .describe('forever-monitor/monitor/signal') 16 | .addBatch({ 17 | 'When using forever-monitor': { 18 | 'and spawning a script that ignores signals SIGINT and SIGTERM': { 19 | 'with killTTL defined': { 20 | topic: function() { 21 | const script = path.join( 22 | __dirname, 23 | '..', 24 | '..', 25 | 'examples', 26 | 'signal-ignore.js' 27 | ), 28 | child = new fmonitor.Monitor(script, { 29 | silent: true, 30 | killTTL: 1000, 31 | }), 32 | callback = this.callback; 33 | 34 | const timer = setTimeout(function() { 35 | callback( 36 | new Error('Child did not die when killed by forever'), 37 | child 38 | ); 39 | }, 3000); 40 | 41 | child.on('exit', function() { 42 | callback.apply(null, [null].concat([].slice.call(arguments))); 43 | clearTimeout(timer); 44 | }); 45 | 46 | child.on('start', function() { 47 | // 48 | // Give it time to set up signal handlers 49 | // 50 | setTimeout(function() { 51 | child.stop(); 52 | }, 1000); 53 | }); 54 | 55 | child.start(); 56 | }, 57 | 'should forcibly kill the processes': function(err, child, spinning) { 58 | assert.isNull(err); 59 | }, 60 | }, 61 | }, 62 | }, 63 | }) 64 | .addBatch({ 65 | 'When using forever-monitor': { 66 | 'and spawning script that gracefully exits on SIGTERM': { 67 | 'with killSignal defined child': { 68 | topic: function() { 69 | const script = path.join( 70 | __dirname, 71 | '..', 72 | '..', 73 | 'examples', 74 | 'graceful-exit.js' 75 | ), 76 | child = new fmonitor.Monitor(script, { 77 | silent: true, 78 | killSignal: 'SIGTERM', 79 | }), 80 | callback = this.callback; 81 | 82 | const timer = setTimeout(function() { 83 | callback( 84 | new Error('Child did not die when killed by forever'), 85 | child 86 | ); 87 | }, 3000); 88 | 89 | child.on('exit', function() { 90 | callback.apply(null, [null].concat([].slice.call(arguments))); 91 | clearTimeout(timer); 92 | }); 93 | 94 | child.on('start', function() { 95 | // 96 | // Give it time to set up signal handlers 97 | // 98 | setTimeout(function() { 99 | child.stop(); 100 | }, 1000); 101 | }); 102 | 103 | child.start(); 104 | }, 105 | 'should gracefully shutdown when receives SIGTERM': function( 106 | err, 107 | child, 108 | spinning 109 | ) { 110 | assert.isNull(err); 111 | }, 112 | }, 113 | }, 114 | }, 115 | }) 116 | .export(module); 117 | -------------------------------------------------------------------------------- /test/monitor/simple-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * simple-test.js: Simple tests for using Monitor instances. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | path = require('path'), 11 | vows = require('vows'), 12 | fmonitor = require('../../lib'), 13 | macros = require('../helpers/macros'); 14 | 15 | const examplesDir = path.join(__dirname, '..', '..', 'examples'); 16 | 17 | vows 18 | .describe('forever-monitor/monitor/simple') 19 | .addBatch({ 20 | 'When using forever-monitor': { 21 | 'an instance of Monitor with valid options': { 22 | topic: new fmonitor.Monitor(path.join(examplesDir, 'server.js'), { 23 | max: 10, 24 | silent: true, 25 | args: ['-p', 8090], 26 | }), 27 | 'should have correct properties set': function(child) { 28 | assert.isArray(child.args); 29 | assert.equal(child.max, 10); 30 | assert.isTrue(child.silent); 31 | assert.isFunction(child.start); 32 | assert.isObject(child.data); 33 | assert.isFunction(child.stop); 34 | }, 35 | 'calling the restart() method in less than `minUptime`': { 36 | topic: function(child) { 37 | const that = this; 38 | child.once('start', function() { 39 | child.once('restart', that.callback.bind(this, null)); 40 | child.restart(); 41 | }); 42 | child.start(); 43 | }, 44 | 'should restart the child process': function(_, child, data) { 45 | assert.isObject(data); 46 | child.kill(true); 47 | }, 48 | }, 49 | }, 50 | 'running error-on-timer sample three times': macros.assertTimes( 51 | path.join(examplesDir, 'error-on-timer.js'), 52 | 3, 53 | { 54 | minUptime: 200, 55 | silent: true, 56 | outFile: 'test/fixtures/stdout.log', 57 | errFile: 'test/fixtures/stderr.log', 58 | args: [], 59 | } 60 | ), 61 | 'running error-on-timer sample once': macros.assertTimes( 62 | path.join(examplesDir, 'error-on-timer.js'), 63 | 1, 64 | { 65 | minUptime: 200, 66 | silent: true, 67 | outFile: 'test/fixtures/stdout.log', 68 | errFile: 'test/fixtures/stderr.log', 69 | args: [], 70 | } 71 | ), 72 | 'non-node usage with a perl one-liner': { 73 | topic: function() { 74 | const child = fmonitor.start(['perl', '-le', 'print "moo"'], { 75 | max: 1, 76 | silent: true, 77 | }); 78 | child.on('stdout', this.callback.bind({}, null)); 79 | }, 80 | 'should get back moo': function(err, buf) { 81 | assert.equal(buf.toString(), 'moo\n'); 82 | }, 83 | }, 84 | 'passing node flags through command': { 85 | topic: function() { 86 | const child = fmonitor.start('test/fixtures/gc.js', { 87 | command: 'node --expose-gc', 88 | max: 1, 89 | silent: true, 90 | }); 91 | child.on('stdout', this.callback.bind({}, null)); 92 | }, 93 | 'should get back function': function(err, buf) { 94 | assert.isNull(err); 95 | assert.equal('' + buf, 'function\n'); 96 | }, 97 | }, 98 | "attempting to start a script that doesn't exist": { 99 | topic: function() { 100 | const child = fmonitor.start('invalid-path.js', { 101 | max: 1, 102 | silent: true, 103 | }); 104 | child.on('error', this.callback.bind({}, null)); 105 | }, 106 | 'should throw an error about the invalid file': function(err) { 107 | assert.isNotNull(err); 108 | assert.isTrue(err.message.indexOf('does not exist') !== -1); 109 | }, 110 | }, 111 | 'attempting to start a command with `node` in the name': { 112 | topic: function() { 113 | const child = fmonitor.start('sample-script.js', { 114 | command: path.join(__dirname, '..', 'fixtures', 'testnode'), 115 | silent: true, 116 | max: 1, 117 | }); 118 | child.on('stdout', this.callback.bind({}, null)); 119 | }, 120 | 'should run the script': function(err, buf) { 121 | assert.isNull(err); 122 | assert.equal('' + buf, 'sample-script.js'); 123 | }, 124 | }, 125 | }, 126 | }) 127 | .export(module); 128 | -------------------------------------------------------------------------------- /test/monitor/spin-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * spin-test.js: Tests for spin restarts in forever. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | path = require('path'), 11 | vows = require('vows'), 12 | fmonitor = require('../../lib'); 13 | 14 | vows 15 | .describe('forever-monitor/monitor/spin-restart') 16 | .addBatch({ 17 | 'When using forever-monitor': { 18 | 'and spawning a script that spin restarts': { 19 | 'with no spinSleepTime specified': { 20 | topic: function() { 21 | const script = path.join( 22 | __dirname, 23 | '..', 24 | '..', 25 | 'examples', 26 | 'always-throw.js' 27 | ), 28 | child = new fmonitor.Monitor(script, { 29 | silent: true, 30 | minUptime: 2000, 31 | max: 3, 32 | }); 33 | 34 | child.on('exit', this.callback.bind({}, null)); 35 | child.start(); 36 | }, 37 | 'should spawn both processes appropriately': function( 38 | err, 39 | child, 40 | spinning 41 | ) { 42 | assert.isTrue(spinning); 43 | }, 44 | 'should only run the bad process once': function( 45 | err, 46 | child, 47 | spinning 48 | ) { 49 | assert.equal(child.times, 1); 50 | }, 51 | }, 52 | 'with a spinSleepTime specified': { 53 | topic: function() { 54 | const script = path.join( 55 | __dirname, 56 | '..', 57 | '..', 58 | 'examples', 59 | 'always-throw.js' 60 | ), 61 | child = new fmonitor.Monitor(script, { 62 | silent: true, 63 | max: 3, 64 | spinSleepTime: 1, 65 | }); 66 | 67 | child.on('exit', this.callback.bind({}, null)); 68 | child.start(); 69 | }, 70 | 'should restart spinning processes': function(err, child, spinning) { 71 | assert.equal(child.times, 3); 72 | }, 73 | }, 74 | }, 75 | }, 76 | }) 77 | .export(module); 78 | -------------------------------------------------------------------------------- /test/monitor/start-stop-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * start-stop-test.js: Start/Stop tests for using Monitor instances. 3 | * 4 | * (C) 2010 Nodejitsu Inc. 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | path = require('path'), 11 | vows = require('vows'), 12 | fmonitor = require('../../lib'); 13 | 14 | const semver = require('semver'); 15 | 16 | const examplesDir = path.join(__dirname, '..', '..', 'examples'); 17 | 18 | vows 19 | .describe('forever-monitor/monitor/start-stop') 20 | .addBatch({ 21 | 'When using forever-monitor': { 22 | 'an instance of Monitor with valid options': { 23 | topic: new fmonitor.Monitor(path.join(examplesDir, 'server.js'), { 24 | max: 10, 25 | silent: true, 26 | options: ['-p', 8090], 27 | logFile: './main.log', 28 | outFile: './out.log', 29 | errFile: './err.log', 30 | }), 31 | 'should have correct properties set': function(child) { 32 | assert.isArray(child.args); 33 | assert.equal(child.max, 10); 34 | assert.isTrue(child.silent); 35 | assert.isFunction(child.start); 36 | assert.isObject(child.data); 37 | assert.isFunction(child.stop); 38 | }, 39 | 'calling the start() and stop() methods': { 40 | topic: function(child) { 41 | const that = this; 42 | 43 | // FixMe this fails on Node 12+ 44 | if (semver.gte(process.version, '12.0.0')) { 45 | that.callback(null, { running: false }); 46 | } 47 | 48 | const timer = setTimeout(function() { 49 | that.callback( 50 | new Error('Child did not die when killed by forever'), 51 | child 52 | ); 53 | }, 8000); 54 | 55 | process.on('uncaughtException', function(err) { 56 | that.callback(err, child); 57 | }); 58 | 59 | child.start(); 60 | setTimeout(function() { 61 | child.stop(); 62 | setTimeout(function() { 63 | child.restart(); 64 | child.once('restart', function() { 65 | child.stop(); 66 | }); 67 | child.once('exit', function() { 68 | // wait another two seconds, to make sure the child is not restarted after calling stop() 69 | setTimeout(function() { 70 | clearTimeout(timer); 71 | that.callback(null, child); 72 | }, 2000); 73 | }); 74 | }, 2000); 75 | }, 1000); 76 | }, 77 | 'should restart the child process': function(err, child) { 78 | assert.isNull(err); 79 | assert.isFalse(child.running); 80 | }, 81 | }, 82 | }, 83 | }, 84 | }) 85 | .export(module); 86 | -------------------------------------------------------------------------------- /test/plugins/logger-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'), 2 | fs = require('fs'), 3 | path = require('path'), 4 | vows = require('vows'), 5 | fmonitor = require('../../lib'); 6 | 7 | const fixturesDir = path.join(__dirname, '..', 'fixtures'); 8 | 9 | function checkLogOutput(file, stream, expectedLength) { 10 | const output = fs.readFileSync(path.join(fixturesDir, file), 'utf8'), 11 | lines = output.split('\n').slice(0, -1); 12 | 13 | assert.equal(lines.length, expectedLength); 14 | lines.forEach(function(line, i) { 15 | assert.equal(lines[i], stream + ' ' + (i % 10)); 16 | }); 17 | } 18 | 19 | vows 20 | .describe('forever-monitor/plugins/logger') 21 | .addBatch({ 22 | 'When using the logger plugin': { 23 | 'with custom log files': { 24 | topic: function() { 25 | const that = this; 26 | 27 | const monitor = new fmonitor.Monitor( 28 | path.join(fixturesDir, 'logs.js'), 29 | { 30 | max: 1, 31 | silent: true, 32 | outFile: path.join(fixturesDir, 'logs-stdout.log'), 33 | errFile: path.join(fixturesDir, 'logs-stderr.log'), 34 | } 35 | ); 36 | 37 | monitor.on('exit', function() { 38 | setTimeout(that.callback, 2000); 39 | }); 40 | monitor.start(); 41 | }, 42 | 'log files should contain correct output': function(err) { 43 | checkLogOutput('logs-stdout.log', 'stdout', 10); 44 | checkLogOutput('logs-stderr.log', 'stderr', 10); 45 | }, 46 | }, 47 | 'with custom log files and a process that exits': { 48 | topic: function() { 49 | const monitor = new fmonitor.Monitor( 50 | path.join(fixturesDir, 'logs.js'), 51 | { 52 | max: 5, 53 | silent: true, 54 | outFile: path.join(fixturesDir, 'logs-stdout-2.log'), 55 | errFile: path.join(fixturesDir, 'logs-stderr-2.log'), 56 | } 57 | ); 58 | 59 | monitor.on('exit', this.callback.bind({}, null)); 60 | monitor.start(); 61 | }, 62 | 'logging should continue through process restarts': function(err) { 63 | checkLogOutput('logs-stdout-2.log', 'stdout', 50); 64 | checkLogOutput('logs-stderr-2.log', 'stderr', 50); 65 | }, 66 | }, 67 | }, 68 | }) 69 | .addBatch({ 70 | 'When using the logger plugin': { 71 | 'with custom log files and the append option set': { 72 | topic: function() { 73 | const monitor = new fmonitor.Monitor( 74 | path.join(fixturesDir, 'logs.js'), 75 | { 76 | max: 3, 77 | silent: true, 78 | append: true, 79 | outFile: path.join(fixturesDir, 'logs-stdout.log'), 80 | errFile: path.join(fixturesDir, 'logs-stderr.log'), 81 | } 82 | ); 83 | 84 | monitor.on('exit', this.callback.bind({}, null)); 85 | monitor.start(); 86 | }, 87 | 'log files should not be truncated': function(err) { 88 | checkLogOutput('logs-stdout.log', 'stdout', 40); 89 | checkLogOutput('logs-stderr.log', 'stderr', 40); 90 | }, 91 | }, 92 | }, 93 | }) 94 | .export(module); 95 | -------------------------------------------------------------------------------- /test/plugins/watch-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * watch-test.js: Tests for restarting forever processes when a file changes. 3 | * 4 | * (C) 2010 Charlie Robbins & the Contributors 5 | * MIT LICENSE 6 | * 7 | */ 8 | 9 | const assert = require('assert'), 10 | path = require('path'), 11 | fs = require('fs'), 12 | vows = require('vows'), 13 | fmonitor = require('../../lib'); 14 | 15 | const watchDir = fs.realpathSync( 16 | path.join(__dirname, '..', 'fixtures', 'watch') 17 | ); 18 | const watchDirToo = fs.realpathSync( 19 | path.join(__dirname, '..', 'fixtures', 'watch_too') 20 | ); 21 | let monitor; 22 | 23 | vows 24 | .describe('forever-monitor/plugins/watch') 25 | .addBatch({ 26 | 'When using forever with watch enabled': { 27 | 'forever should': { 28 | topic: fmonitor.start('daemon.js', { 29 | silent: true, 30 | args: ['-p', '8090'], 31 | watch: true, 32 | sourceDir: path.join(__dirname, '..', 'fixtures', 'watch'), 33 | watchDirectory: [ 34 | path.join(__dirname, '..', 'fixtures', 'watch'), 35 | path.join(__dirname, '..', 'fixtures', 'watch_too'), 36 | ], 37 | }), 38 | 'have correct options set': function(child) { 39 | monitor = child; 40 | assert.isTrue(child.watchIgnoreDotFiles); 41 | assert.equal(watchDir, fs.realpathSync(child.watchDirectory[0])); 42 | assert.equal(watchDirToo, fs.realpathSync(child.watchDirectory[1])); 43 | }, 44 | 'read .foreverignore file and store ignore patterns': function(child) { 45 | setTimeout(function() { 46 | assert.deepEqual( 47 | child.watchIgnorePatterns, 48 | fs 49 | .readFileSync(path.join(watchDir, '.foreverignore'), 'utf8') 50 | .split('\n') 51 | .filter(Boolean) 52 | ); 53 | }, 100); 54 | }, 55 | }, 56 | }, 57 | }) 58 | .addBatch({ 59 | 'When using forever with watch enabled': { 60 | 'when a file matching an ignore pattern is added': { 61 | topic: function() { 62 | const self = this; 63 | this.filenames = [ 64 | path.join(watchDir, 'ignore_newFile'), 65 | path.join(watchDir, 'ignoredDir', 'ignore_subfile'), 66 | ]; 67 | 68 | // 69 | // Setup a bad restart function 70 | // 71 | function badRestart() { 72 | this.callback(new Error('Monitor restarted at incorrect time.')); 73 | } 74 | 75 | monitor.once('restart', badRestart); 76 | this.filenames.forEach(function(filename) { 77 | fs.writeFileSync(filename, ''); 78 | }); 79 | 80 | // 81 | // `chokidar` does not emit anything when ignored 82 | // files have changed so we need a setTimeout here 83 | // to prove that nothing has happened. 84 | // 85 | setTimeout(function() { 86 | monitor.removeListener('restart', badRestart); 87 | self.callback(); 88 | }, 5000); 89 | }, 90 | 'do nothing': function(err) { 91 | assert.isUndefined(err); 92 | this.filenames.forEach(function(filename) { 93 | fs.unlinkSync(filename); 94 | }); 95 | }, 96 | }, 97 | }, 98 | }) 99 | .addBatch({ 100 | 'When using forever with watch enabled': { 101 | 'when file changes': { 102 | topic: function(child) { 103 | child.once('restart', this.callback); 104 | fs.writeFileSync( 105 | path.join(watchDir, 'file'), 106 | '// hello, I know nodejitsu.' 107 | ); 108 | }, 109 | 'restart the script': function(child, _) { 110 | fs.writeFileSync( 111 | path.join(watchDir, 'file'), 112 | '/* hello, I know nodejitsu. ' 113 | ); 114 | }, 115 | }, 116 | }, 117 | }) 118 | .addBatch({ 119 | 'When using forever with watch enabled': { 120 | 'when file in second directory changes': { 121 | topic: function(child) { 122 | child.once('restart', this.callback); 123 | fs.writeFileSync( 124 | path.join(watchDirToo, 'file'), 125 | '// hello, I know nodejitsu.' 126 | ); 127 | }, 128 | 'restart the script': function(child, _) { 129 | fs.writeFileSync( 130 | path.join(watchDirToo, 'file'), 131 | '/* hello, I know nodejitsu. ' 132 | ); 133 | }, 134 | }, 135 | }, 136 | }) 137 | .addBatch({ 138 | 'When using forever with watch enabled': { 139 | 'when file is added': { 140 | topic: function() { 141 | monitor.once('restart', this.callback); 142 | fs.writeFileSync(path.join(watchDir, 'newFile'), ''); 143 | }, 144 | 'restart the script': function(child, _) { 145 | fs.unlinkSync(path.join(watchDir, 'newFile')); 146 | }, 147 | }, 148 | }, 149 | }) 150 | .addBatch({ 151 | 'When using forever with watch enabled': { 152 | 'when file is removed': { 153 | topic: function() { 154 | monitor.once('restart', this.callback); 155 | try { 156 | fs.unlinkSync(path.join(watchDir, 'removeMe')); 157 | } catch (ex) {} 158 | }, 159 | 'restart the script': function(child, _) { 160 | fs.writeFileSync(path.join(watchDir, 'removeMe'), ''); 161 | monitor.stop(); 162 | }, 163 | }, 164 | }, 165 | }) 166 | .export(module); 167 | --------------------------------------------------------------------------------