├── test ├── fixture.js ├── sampleApp │ ├── package.json │ └── server.js └── supervisor.test.js ├── .gitignore ├── lib ├── cli-wrapper.js └── supervisor.js ├── LICENSE ├── package.json └── README.md /test/fixture.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/sampleApp/node_modules/ 2 | node_modules/ 3 | *.iml 4 | .idea 5 | -------------------------------------------------------------------------------- /test/sampleApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cors-demo", 3 | "version": "0.0.1", 4 | "description": "Cors demo", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Petru Isfan", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "express": "^4.12.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/cli-wrapper.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var path = require("path") 3 | , fs = require("fs") 4 | , args = process.argv.slice(1) 5 | 6 | var arg, base; 7 | do arg = args.shift(); 8 | while ( fs.realpathSync(arg) !== __filename 9 | && !(base = path.basename(arg)).match(/^supervisor$|^supervisor.js$|^node-supervisor$/) 10 | ) 11 | 12 | require("./supervisor").run(args) 13 | -------------------------------------------------------------------------------- /test/sampleApp/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | app.get('/', function (req, res) { 7 | // 8 | // CORS 9 | // 10 | res.setHeader("Access-Control-Allow-Origin", "*"); 11 | res.send('Hello World!'); 12 | }); 13 | 14 | var server = app.listen(3001, function () { 15 | var host = server.address().address; 16 | var port = server.address().port; 17 | console.log('Example app listening at http://%s:%s', host, port); 18 | }); 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Isaac Z. Schlueter, Ian Young, and Other Contributors 2 | All rights reserved. 3 | 4 | The BSD License 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS 21 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /test/supervisor.test.js: -------------------------------------------------------------------------------- 1 | var logger = console.log; 2 | var logs = ""; 3 | console.log = function(msg){ 4 | // logger(msg); 5 | logs += msg; 6 | }; 7 | 8 | var testScript = "test/fixture.js" 9 | var testCase = require('nodeunit').testCase; 10 | var supervisor = require('../lib/supervisor.js'); 11 | 12 | var assertValueExists = function(assertion, test){ 13 | test.ok(assertion); 14 | test.done(); 15 | setTimeout(function(){ 16 | logs = ""; 17 | process.kill(); 18 | }, 20) 19 | }; 20 | 21 | var tests = { 22 | tearDown:function testGroupOneTearDown(cb){ 23 | logs = ""; 24 | console.log = logger; 25 | cb(); 26 | }, 27 | "should exist when supervisor is imported": function (test) { 28 | test.ok(!!supervisor); 29 | test.done(); 30 | }, 31 | "should accept a debug port when passed through": function(test){ 32 | var EXPECTED = '--debug=1234'; 33 | supervisor.run([ EXPECTED, "--no-restart-on", "exit", "--", testScript]); 34 | assertValueExists(logs.indexOf(EXPECTED) > -1, test); 35 | }, 36 | "should use debug alone when no port number passed through": function(test){ 37 | var EXPECTED = '--debug'; 38 | supervisor.run([ EXPECTED, "--no-restart-on", "exit", "--", testScript]); 39 | assertValueExists(logs.indexOf(EXPECTED) > -1, test); 40 | }, 41 | "should not overwrite with debug-brk value if debug-brk also passed through": function(test){ 42 | var EXPECTED = '--debug=1236'; 43 | supervisor.run([ EXPECTED, "--debug-brk", "--no-restart-on", "exit", "--", testScript]); 44 | assertValueExists(logs.indexOf(EXPECTED) > -1, test); 45 | }, 46 | "should pass the debug-brk flag through": function(test){ 47 | var EXPECTED = '--debug-brk'; 48 | supervisor.run([ EXPECTED, "--no-restart-on", "exit", "--", testScript]); 49 | assertValueExists(logs.indexOf(EXPECTED) > -1, test); 50 | }, 51 | "should pass the debug-brk port number arg through with debug-brk flag": function(test){ 52 | var EXPECTED = '--debug-brk=5859'; 53 | supervisor.run([ EXPECTED, "--no-restart-on", "exit", "--", testScript]); 54 | assertValueExists(logs.indexOf(EXPECTED) > -1, test); 55 | } 56 | }; 57 | 58 | module.exports.testGroupOne = testCase(tests); 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supervisor", 3 | "version": "0.12.0", 4 | "description": "A supervisor program for running nodejs programs", 5 | "author": "Isaac Z. Schlueter ", 6 | "license": "MIT", 7 | "contributors": [ 8 | "Todd Branchflower ", 9 | "Giannis Dzegoutanis ", 10 | "Brian Ehmann ", 11 | "Corey Jewett ", 12 | "Taka Kojima ", 13 | "Aneil Mallavarapu ", 14 | "Doug McCall ", 15 | "Mathieu M-Gosselin ", 16 | "David Murdoch ", 17 | "mx1700 ", 18 | "Michiel ter Reehorst ", 19 | "Jonathan 'Wolf' Rentzsch ", 20 | "John Roberts ", 21 | "Scott Sanders ", 22 | "Thomas Schaaf ", 23 | "Fernando H. Silva ", 24 | "Kei Son ", 25 | "David Taylor ", 26 | "Antonio Touriño ", 27 | "Oliver Wong ", 28 | "Di Wu ", 29 | "Jesse Yang ", 30 | "Ian Young ", 31 | "jazzzz ", 32 | "philpill ", 33 | "rma4ok ", 34 | "Petru Isfan " 35 | ], 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/petruisfan/node-supervisor.git" 39 | }, 40 | "bugs": "https://github.com/petruisfan/node-supervisor/issues", 41 | "homepage": "https://github.com/petruisfan/node-supervisor/", 42 | "main": "lib/supervisor.js", 43 | "bin": { 44 | "node-supervisor": "lib/cli-wrapper.js", 45 | "supervisor": "lib/cli-wrapper.js" 46 | }, 47 | "engines": { 48 | "node": ">=0.6.0" 49 | }, 50 | "scripts": { 51 | "test": "./node_modules/nodeunit/bin/nodeunit test/*.test.js" 52 | }, 53 | "preferGlobal": true, 54 | "devDependencies": { 55 | "nodeunit": "~0.9.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-supervisor 2 | 3 | A little supervisor script for nodejs. It runs your program, and 4 | watches for code changes, so you can have hot-code reloading-ish 5 | behavior, without worrying about memory leaks and making sure you 6 | clean up all the inter-module references, and without a whole new 7 | `require` system. 8 | 9 | ## node-supervisor -? 10 | 11 | 12 | Node Supervisor is used to restart programs when they crash. 13 | It can also be used to restart programs when a *.js file changes. 14 | 15 | Usage: 16 | supervisor [options] 17 | supervisor [options] -- [args ...] 18 | 19 | Required: 20 | 21 | The program to run. 22 | 23 | Options: 24 | -w|--watch 25 | A comma-delimited list of folders or js files to watch for changes. 26 | When a change to a js file occurs, reload the program 27 | Default is '.' 28 | 29 | -i|--ignore 30 | A comma-delimited list of folders to ignore for changes. 31 | No default 32 | 33 | --ignore-symlinks 34 | Ignore symlinks :) 35 | 36 | -s|--timestamp 37 | Log timestamp after each run. 38 | Make it easy to tell when the task last ran. 39 | 40 | -p|--poll-interval 41 | How often to poll watched files for changes. 42 | Defaults to Node default. 43 | 44 | -e|--extensions 45 | A comma-delimited list of file extensions to watch for changes. 46 | Default is 'node,js' (or when CoffeeScript, 'node,js,coffee,litcoffee'). 47 | 48 | -x|--exec 49 | The executable that runs the specified program. 50 | Default is 'node' 51 | 52 | -pid|--save-pid 53 | Save supervisor's process id to a file at the given path. 54 | 55 | --debug[=port] 56 | Start node with --debug flag. 57 | 58 | --debug-brk[=port] 59 | Start node with --debug-brk flag. 60 | 61 | --harmony 62 | Start node with --harmony flag. 63 | 64 | --inspect[=port] 65 | Start node with --inspect flag. 66 | 67 | -n|--no-restart-on error|exit|success 68 | Don't automatically restart the supervised program if it ends. 69 | Supervisor will wait for a change in the source files. 70 | If "error", an exit code of 0 will still restart. 71 | If "exit", no restart regardless of exit code. 72 | If "success", no restart only if exit code is 0. 73 | 74 | -t|--non-interactive 75 | Dissable interactive capacity 76 | With this option, supervisor won't listen to stdin 77 | 78 | --force-watch 79 | Use fs.watch instead of fs.watchFile. 80 | This may be useful if you see a high cpu load on a windows machine. 81 | 82 | -k|--instant-kill 83 | Instantly kills the server process, instead of gracefully shutting down the server. 84 | This can be useful when the node app has events attached to SIGTERM or SIGINT so as to do a graceful shutdown before the process exits. 85 | 86 | -RV|--restart-verbose 87 | Logs the file(s) that caused supervisor to restart 88 | 89 | -h|--help|-? 90 | Display these usage instructions. 91 | 92 | -q|--quiet 93 | Suppress DEBUG messages 94 | 95 | 96 | Options available after start: 97 | rs - restart process. Useful when you want to restart your program even 98 | if no file has changed. 99 | 100 | 101 | Examples: 102 | supervisor myapp.js 103 | supervisor myapp.coffee 104 | supervisor -w scripts -e myext -x myrunner myapp 105 | supervisor -w lib,server.js,config.js server.js 106 | supervisor -- server.js -h host -p port 107 | 108 | 109 | In order to not watch for file changes, use "-i .". 110 | 111 | ## Simple Install 112 | 113 | Just run: 114 | 115 | npm install supervisor -g 116 | 117 | ## Fancy Install 118 | 119 | Get this code, and then do this: 120 | 121 | npm link 122 | -------------------------------------------------------------------------------- /lib/supervisor.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var spawn = require("child_process").spawn; 3 | var path = require("path"); 4 | var fileExtensionPattern; 5 | var startChildProcess; 6 | var noRestartOn = null; 7 | var debug = true; 8 | var verbose = false; 9 | var restartVerbose = false; 10 | var ignoredPaths = {}; 11 | var ignoreSymLinks = false; 12 | var forceWatchFlag = false; 13 | var instantKillFlag = false; 14 | var timestampFlag = false; 15 | var interactive = true; 16 | var log = console.log; 17 | var crash_queued = false; 18 | var harmony_default_parameters = false; 19 | var harmony_destructuring = false; 20 | 21 | exports.run = run; 22 | 23 | function run (args) { 24 | var arg, next, watch, ignore, pidFilePath, program, extensions, executor, poll_interval, debugFlag, debugBrkFlag, debugBrkFlagArg, harmony, inspect; 25 | while (arg = args.shift()) { 26 | if (arg === "--help" || arg === "-h" || arg === "-?") { 27 | return help(); 28 | } else if (arg === "--quiet" || arg === "-q") { 29 | debug = false; 30 | log = function(){}; 31 | } else if (arg === "--harmony") { 32 | harmony = true; 33 | } else if (arg.indexOf("--inspect") > -1) { 34 | inspect = arg; 35 | } else if (arg === "--harmony_default_parameters") { 36 | harmony_default_parameters = true; 37 | } else if (arg === "--harmony_destructuring") { 38 | harmony_destructuring = true; 39 | } else if (arg === "--verbose" || arg === "-V") { 40 | verbose = true; 41 | } else if (arg === "--restart-verbose" || arg === "-RV") { 42 | restartVerbose = true; 43 | } else if (arg === "--watch" || arg === "-w") { 44 | watch = args.shift(); 45 | } else if (arg == "--non-interactive" || arg === "-t") { 46 | interactive = false; 47 | } else if (arg === "--ignore" || arg === "-i") { 48 | ignore = args.shift(); 49 | } else if (arg === "--save-pid" || arg === "-pid") { 50 | pidFilePath = args.shift(); 51 | } else if (arg === "--ignore-symlinks") { 52 | ignoreSymLinks = true; 53 | } else if (arg === "--poll-interval" || arg === "-p") { 54 | poll_interval = parseInt(args.shift()); 55 | } else if (arg === "--extensions" || arg === "-e") { 56 | extensions = args.shift(); 57 | } else if (arg === "--exec" || arg === "-x") { 58 | executor = args.shift(); 59 | } else if (arg === "--no-restart-on" || arg === "-n") { 60 | noRestartOn = args.shift(); 61 | } else if (arg.indexOf("--debug") > -1 && arg.indexOf('--debug-brk') === -1) { 62 | debugFlag = arg; 63 | } else if (arg.indexOf('--debug-brk')>=0) { 64 | debugBrkFlag = true; 65 | debugBrkFlagArg = arg; 66 | } else if (arg === "--force-watch") { 67 | forceWatchFlag = true; 68 | } else if (arg === "--instant-kill" || arg === "-k") { 69 | instantKillFlag = true; 70 | } else if (arg === "--timestamp" || arg === "-s") { 71 | timestampFlag = true; 72 | } else if (arg === "--") { 73 | program = args; 74 | break; 75 | } else if (arg[0] != "-" && !args.length) { 76 | // Assume last arg is the program 77 | program = [arg]; 78 | } 79 | } 80 | if (!program) { 81 | return help(); 82 | } 83 | if (!watch) { 84 | watch = "."; 85 | } 86 | if (!poll_interval) { 87 | poll_interval = 1000; 88 | } 89 | 90 | var programExt = program.join(" ").match(/.*\.(\S*)/); 91 | programExt = programExt && programExt[1]; 92 | 93 | if (!extensions) { 94 | // If no extensions passed try to guess from the program 95 | extensions = "node,js"; 96 | if (programExt && extensions.indexOf(programExt) == -1) { 97 | // Support coffee and litcoffee extensions 98 | if(programExt === "coffee" || programExt === "litcoffee") { 99 | extensions += ",coffee,litcoffee"; 100 | } else { 101 | extensions += "," + programExt; 102 | } 103 | } 104 | } 105 | fileExtensionPattern = new RegExp("^.*\.(" + extensions.replace(/,/g, "|") + ")$"); 106 | 107 | if (!executor) { 108 | executor = (programExt === "coffee" || programExt === "litcoffee") ? "coffee" : "node"; 109 | } 110 | 111 | if (debugFlag) { 112 | program.unshift(debugFlag); 113 | } 114 | if (debugBrkFlag) { 115 | program.unshift(debugBrkFlagArg); 116 | } 117 | if (harmony) { 118 | program.unshift("--harmony"); 119 | } 120 | if (inspect) { 121 | program.unshift(inspect); 122 | } 123 | if (harmony_default_parameters) { 124 | program.unshift("--harmony_default_parameters"); 125 | } 126 | if (harmony_destructuring) { 127 | program.unshift("--harmony_destructuring"); 128 | } 129 | if (executor === "coffee" && (debugFlag || debugBrkFlag)) { 130 | // coffee does not understand debug or debug-brk, make coffee pass options to node 131 | program.unshift("--nodejs") 132 | } 133 | if (pidFilePath) { 134 | var pid = process.pid; 135 | // 136 | // verify if we have write access 137 | // 138 | canWrite(pidFilePath, function(err) { 139 | if ( err ) { 140 | log("Continuing..."); 141 | } else { 142 | fs.writeFileSync(pidFilePath, pid + '\n'); 143 | } 144 | }); 145 | } 146 | 147 | var deletePidFile = function(){ 148 | fs.exists(pidFilePath, function (exists) { 149 | if ( exists ) { 150 | log("Removing pid file"); 151 | fs.unlinkSync(pidFilePath); 152 | } else { 153 | log("No pid file to remove..."); 154 | } 155 | process.exit(); 156 | }); 157 | }; 158 | 159 | try { 160 | // Pass kill signals through to child 161 | [ "SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT" ].forEach( function(signal) { 162 | process.on(signal, function () { 163 | var child = exports.child; 164 | if (child) { 165 | log("Received "+signal+", killing child process..."); 166 | child.kill(signal); 167 | } 168 | if (pidFilePath){ 169 | deletePidFile(); 170 | } 171 | else { 172 | process.exit(); 173 | } 174 | }); 175 | }); 176 | } catch(e) { 177 | // Windows doesn't support signals yet, so they simply don't get this handling. 178 | // https://github.com/joyent/node/issues/1553 179 | } 180 | 181 | process.on('exit', function () { 182 | var child = exports.child; 183 | if (child) { 184 | log("Parent process exiting, terminating child..."); 185 | child.kill("SIGTERM"); 186 | } 187 | }); 188 | 189 | log(""); 190 | log("Running node-supervisor with"); 191 | log(" program '" + program.join(" ") + "'"); 192 | log(" --watch '" + watch + "'"); 193 | if (!interactive) { 194 | log(" --non-interactive"); 195 | } 196 | if (ignore) { 197 | log(" --ignore '" + ignore + "'"); 198 | } 199 | if (pidFilePath){ 200 | log(" --save-pid '" + pidFilePath + "'"); 201 | } 202 | log(" --extensions '" + extensions + "'"); 203 | log(" --exec '" + executor + "'"); 204 | log(""); 205 | 206 | // store the call to startProgramm in startChildProcess 207 | // in order to call it later 208 | startChildProcess = function() { startProgram(program, executor); }; 209 | 210 | // if we have a program, then run it, and restart when it crashes. 211 | // if we have a watch folder, then watch the folder for changes and restart the prog 212 | startChildProcess(); 213 | 214 | // If interaction has not been disabled, start the CLI 215 | if(interactive) { 216 | 217 | // 218 | // Read input from stdin 219 | // 220 | var stdin = process.stdin; 221 | 222 | stdin.setEncoding( 'utf8' ); 223 | stdin.on('readable', function() { 224 | var chunk = process.stdin.read(); 225 | // 226 | // Restart process when user inputs rs 227 | // 228 | if (chunk !== null && chunk === "rs\n" || chunk === "rs\r\n") { 229 | // process.stdout.write('data: ' + chunk); 230 | crash(); 231 | } 232 | }); 233 | } 234 | 235 | if (ignore) { 236 | var ignoreItems = ignore.split(','); 237 | ignoreItems.forEach(function (ignoreItem) { 238 | ignoreItem = path.resolve(ignoreItem); 239 | ignoredPaths[ignoreItem] = true; 240 | log("Ignoring directory '" + ignoreItem + "'."); 241 | }); 242 | } 243 | 244 | var watchItems = watch.split(','); 245 | watchItems.forEach(function (watchItem) { 246 | watchItem = path.resolve(watchItem); 247 | 248 | if ( ! ignoredPaths[watchItem] ) { 249 | log("Watching directory '" + watchItem + "' for changes."); 250 | if(interactive) { 251 | log("Press rs for restarting the process."); 252 | } 253 | findAllWatchFiles(watchItem, function(f) { 254 | watchGivenFile( f, poll_interval ); 255 | }); 256 | } 257 | }); 258 | } 259 | 260 | // function print (m, n) { console.log(m+(!n?"\n":"")); return print; } 261 | function print (m) { console.log(m); return print; } 262 | 263 | function help () { 264 | print 265 | ("") 266 | ("Node Supervisor is used to restart programs when they crash.") 267 | ("It can also be used to restart programs when a *.js file changes.") 268 | ("") 269 | ("Usage:") 270 | (" supervisor [options] ") 271 | (" supervisor [options] -- [args ...]") 272 | ("") 273 | ("Required:") 274 | (" ") 275 | (" The program to run.") 276 | ("") 277 | ("Options:") 278 | (" -w|--watch ") 279 | (" A comma-delimited list of folders or js files to watch for changes.") 280 | (" When a change to a js file occurs, reload the program") 281 | (" Default is '.'") 282 | ("") 283 | (" -i|--ignore ") 284 | (" A comma-delimited list of folders to ignore for changes.") 285 | (" No default") 286 | ("") 287 | (" --ignore-symlinks") 288 | (" Enable symbolic links ignoring when looking for files to watch.") 289 | ("") 290 | (" -p|--poll-interval ") 291 | (" How often to poll watched files for changes.") 292 | (" Defaults to Node default.") 293 | ("") 294 | (" -e|--extensions ") 295 | (" Specific file extensions to watch in addition to defaults.") 296 | (" Used when --watch option includes folders") 297 | (" Default is 'node,js'") 298 | ("") 299 | (" -x|--exec ") 300 | (" The executable that runs the specified program.") 301 | (" Default is 'node'") 302 | ("") 303 | (" --debug[=port]") 304 | (" Start node with --debug flag. ") 305 | ("") 306 | (" --debug-brk[=port]") 307 | (" Start node with --debug-brk[=port] flag.") 308 | ("") 309 | (" --harmony") 310 | (" Start node with --harmony flag.") 311 | (" --inspect") 312 | (" Start node with --inspect flag.") 313 | ("") 314 | (" --harmony_default_parameters") 315 | (" Start node with --harmony_default_parameters flag.") 316 | ("") 317 | (" -n|--no-restart-on error|exit") 318 | (" Don't automatically restart the supervised program if it ends.") 319 | (" Supervisor will wait for a change in the source files.") 320 | (" If \"error\", an exit code of 0 will still restart.") 321 | (" If \"exit\", no restart regardless of exit code.") 322 | (" If \"success\", no restart only if exit code is 0.") 323 | ("") 324 | (" -t|--non-interactive") 325 | (" Disable interactive capacity.") 326 | (" With this option, supervisor won't listen to stdin.") 327 | ("") 328 | (" -k|--instant-kill") 329 | (" use SIGKILL (-9) to terminate child instead of the more gentle SIGTERM.") 330 | ("") 331 | (" --force-watch") 332 | (" Use fs.watch instead of fs.watchFile.") 333 | (" This may be useful if you see a high cpu load on a windows machine.") 334 | ("") 335 | (" -s|--timestamp") 336 | (" Log timestamp after each run.") 337 | (" Make it easy to tell when the task last ran.") 338 | ("") 339 | (" -h|--help|-?") 340 | (" Display these usage instructions.") 341 | ("") 342 | (" -q|--quiet") 343 | (" Suppress DEBUG messages") 344 | ("") 345 | (" -V|--verbose") 346 | (" Show extra DEBUG messages") 347 | ("") 348 | ("Options available after start:") 349 | ("rs - restart process.") 350 | (" Useful for restarting supervisor eaven if no file has changed.") 351 | ("") 352 | ("Examples:") 353 | (" supervisor myapp.js") 354 | (" supervisor myapp.coffee") 355 | (" supervisor -w scripts -e myext -x myrunner myapp") 356 | (" supervisor -- server.js -h host -p port") 357 | (""); 358 | } 359 | 360 | function startProgram (prog, exec) { 361 | log("Starting child process with '" + exec + " " + prog.join(" ") + "'"); 362 | crash_queued = false; 363 | var child = exports.child = spawn(exec, prog, {stdio: 'inherit'}); 364 | // Support for Windows ".cmd" files 365 | // On Windows 8.1, spawn can't launch apps without the .cmd extention 366 | // If already retried, let the app crash ... :'( 367 | if (process.platform === "win32" && exec.indexOf('.cmd') == -1) { 368 | child.on('error', function (err) { 369 | if (err.code === "ENOENT") 370 | return startProgram(prog, exec + ".cmd"); 371 | }); 372 | } 373 | if (child.stdout) { 374 | // node < 0.8 doesn't understand the 'inherit' option, so pass through manually 375 | child.stdout.addListener("data", function (chunk) { chunk && console.log(chunk); }); 376 | child.stderr.addListener("data", function (chunk) { chunk && console.error(chunk); }); 377 | } 378 | child.addListener("exit", function (code) { 379 | logTimestamp(); 380 | 381 | if (!crash_queued) { 382 | log("Program " + exec + " " + prog.join(" ") + " exited with code " + code + "\n"); 383 | exports.child = null; 384 | if (noRestartOn == "exit" || noRestartOn == "error" && code !== 0 || noRestartOn == "success" && code === 0) return; 385 | } 386 | startProgram(prog, exec); 387 | }); 388 | } 389 | 390 | function logTimestamp() { 391 | if (timestampFlag) { 392 | // use console.log() directly rather than log() so that -q/--quiet 393 | // does not override/silence it 394 | console.log(Date().toString()); 395 | } 396 | } 397 | 398 | function crash () { 399 | 400 | if (crash_queued) 401 | return; 402 | 403 | crash_queued = true; 404 | var child = exports.child; 405 | setTimeout(function() { 406 | if (child) { 407 | if (instantKillFlag) { 408 | log("crashing child with SIGKILL"); 409 | process.kill(child.pid, "SIGKILL"); 410 | } else { 411 | log("crashing child"); 412 | process.kill(child.pid, "SIGTERM"); 413 | } 414 | } else { 415 | log("restarting child"); 416 | startChildProcess(); 417 | } 418 | }, 50); 419 | } 420 | 421 | function crashWin (event, filename) { 422 | var shouldCrash = true; 423 | if( event === 'change' ) { 424 | if (filename) { 425 | filename = path.resolve(filename); 426 | Object.keys(ignoredPaths).forEach(function (ignorePath) { 427 | if ( filename.indexOf(ignorePath + '\\') === 0 || filename === ignorePath) { 428 | shouldCrash = false; 429 | } 430 | }); 431 | } 432 | if (shouldCrash) { 433 | if (verbose || restartVerbose) { 434 | log("Changes detected" + (filename ? ": " + filename : "")); 435 | } 436 | crash(); 437 | } 438 | } 439 | } 440 | /** 441 | * Determine if a file can be written 442 | */ 443 | function canWrite(path, callback) { 444 | fs.open(path, "w", function (err, fd) { 445 | if (err) { 446 | if (err.code === "EISDIR") { 447 | log("Can't open " + path + ". It's a directory."); 448 | } 449 | if (err.code === "EACCESS") { 450 | log("Can't open " + path + ". No access."); 451 | } else { 452 | log("Can't open " + path + "."); 453 | } 454 | return callback(err); 455 | } 456 | fs.close(fd, function (err) { 457 | if (err) return callback(err); 458 | callback(null, true); 459 | }); 460 | }); 461 | } 462 | 463 | 464 | var nodeVersion = process.version.split("."); 465 | 466 | var isWindowsWithoutWatchFile = process.platform === 'win32' && parseInt(nodeVersion[1]) <= 6; 467 | 468 | function watchGivenFile (watch, poll_interval) { 469 | if (isWindowsWithoutWatchFile || forceWatchFlag) { 470 | fs.watch(watch, { persistent: true, interval: poll_interval }, crashWin); 471 | } else { 472 | fs.watchFile(watch, { persistent: true, interval: poll_interval }, function(oldStat, newStat) { 473 | // we only care about modification time, not access time. 474 | if ( newStat.mtime.getTime() !== oldStat.mtime.getTime() ) { 475 | if (verbose) { 476 | log("file changed: " + watch); 477 | } 478 | } 479 | crash(); 480 | }); 481 | } 482 | if (verbose) { 483 | log("watching file '" + watch + "'"); 484 | } 485 | } 486 | 487 | var findAllWatchFiles = function(dir, callback) { 488 | dir = path.resolve(dir); 489 | if (ignoredPaths[dir]) 490 | return; 491 | fs[ignoreSymLinks ? 'lstat' : 'stat'](dir, function(err, stats) { 492 | if (err) { 493 | console.error('Error retrieving stats for file: ' + dir); 494 | } else { 495 | if (ignoreSymLinks && stats.isSymbolicLink()) { 496 | log("Ignoring symbolic link '" + dir + "'."); 497 | return; 498 | } 499 | 500 | if (stats.isDirectory()) { 501 | if (isWindowsWithoutWatchFile || forceWatchFlag) callback(dir); 502 | fs.readdir(dir, function(err, fileNames) { 503 | if(err) { 504 | console.error('Error reading path: ' + dir); 505 | } 506 | else { 507 | fileNames.forEach(function (fileName) { 508 | findAllWatchFiles(path.join(dir, fileName), callback); 509 | }); 510 | } 511 | }); 512 | } else { 513 | if ((!isWindowsWithoutWatchFile || !forceWatchFlag) && dir.match(fileExtensionPattern)) { 514 | callback(dir); 515 | } 516 | } 517 | } 518 | }); 519 | }; 520 | --------------------------------------------------------------------------------