diff --git a/.gitignore b/.gitignore index 9f426d89e..e106a5d6c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ currentTagChangelog.md joblog-X test/fixtures/path-check*.txt yarn.lock +*.tar.gz diff --git a/.travis.yml b/.travis.yml index 0c217f312..50bd28a26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ os: - linux before_install: - sudo apt-get -qq update - - sudo apt-get install parallel - sudo apt-get install python3 - sudo apt-get install php5-cli services: diff --git a/CHANGELOG.md b/CHANGELOG.md index c4673f170..fe2d62ed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ + +## 3.2.0 (3/10/18) + +### Features + +- package.json version field retrieval and display in pm2 ls, pm2 show, pm2 monit +- pm2 internal configuration system via `pm2 set pm2:key value`, attached to pm2.user_conf +- add the .user field (CLI + Config) to set the user to start the application with +- add the .time field (CLI + Config) to enable default logs date prefix +- max_memory_restart now triggers a reload +- pm2 env command to display the environment the application is running with +- exponential backoff restart delay via `--exp-backoff-restart-delay ` with reset mechanism +- new timing library on PM2 daemon (increase log througput, reduce CPU usage and memory usage) +- better user management system with username resolution to uid +- websocket default switch for pm2 plus +- new module management system (`pm2 package `, `pm2 publish `, `pm2 install `) + +### Fix + +- @pm2/io 2.4 (restart > 10.0) +- restart behavior tested +- fix module version parsing +- module system refactoring (TAR + NPM) +- fix watch_delay in config file + ## 3.1.3 (20/09/18) ### Features diff --git a/bin/pm2 b/bin/pm2 index 0fa916d2f..945f10871 100755 --- a/bin/pm2 +++ b/bin/pm2 @@ -46,6 +46,7 @@ commander.version(pkg.version) .option('-l --log [path]', 'specify log file which gathers both stdout and stderr') .option('--log-type ', 'specify log output style (raw by default, json optional)') .option('--log-date-format ', 'add custom prefix timestamp to logs') + .option('--time', 'enable time logging') .option('--disable-logs', 'disable all logs storage') .option('--env ', 'specify which set of environment variables from ecosystem file must be injected') .option('-a --update-env', 'force an update of the environment with restart/reload (-a <=> apply)') @@ -57,6 +58,7 @@ commander.version(pkg.version) .option('--listen-timeout ', 'listen timeout on application reload') .option('--max-memory-restart ', 'Restart the app if an amount of memory is exceeded (in bytes)') .option('--restart-delay ', 'specify a delay between restarts (in milliseconds)') + .option('--exp-backoff-restart-delay ', 'specify a delay between restarts (in milliseconds)') .option('-x --execute-command', 'execute a program using fork system') .option('--max-restarts [count]', 'only restart the script COUNT times') .option('-u --user ', 'define user when generating startup script') @@ -482,14 +484,14 @@ commander.command('update') */ commander.command('install ') .alias('module:install') + .option('--tarball', 'is local tarball') + .option('--http', 'is remote tarball') + .option('--docker', 'is docker container') .option('--v1', 'install module in v1 manner (do not use it)') .option('--safe [time]', 'keep module backup, if new module fail = restore with previous') .description('install or update a module and run it forever') .action(function(plugin_name, opts) { - if (opts.v1) - commander.v1 = true; - if (opts.safe) - commander.safe = opts.safe; + require('util')._extend(commander, opts) pm2.install(plugin_name, commander); }); @@ -513,12 +515,18 @@ commander.command('uninstall ') pm2.uninstall(plugin_name); }); +commander.command('package [target]') + .description('Check & Package TAR type module') + .action(function(target) { + pm2.package(target); + }); -commander.command('publish') +commander.command('publish [folder]') + .option('--npm', 'publish on npm') .alias('module:publish') .description('Publish the module you are currently on') - .action(function() { - pm2.publish(); + .action(function(folder, opts) { + pm2.publish(folder, opts); }); commander.command('set [key] [value]') @@ -788,6 +796,12 @@ commander.command('info ') pm2.describe(proc_id); }); +commander.command('env ') + .description('(alias) describe all parameters of a process id') + .action(function(proc_id) { + pm2.env(proc_id); + }); + commander.command('show ') .description('(alias) describe all parameters of a process id') .action(function(proc_id) { diff --git a/constants.js b/constants.js index 68a5d0298..642ed4063 100644 --- a/constants.js +++ b/constants.js @@ -48,6 +48,7 @@ var csts = { ONLINE_STATUS : 'online', STOPPED_STATUS : 'stopped', STOPPING_STATUS : 'stopping', + WAITING_RESTART : 'waiting restart', LAUNCHING_STATUS : 'launching', ERRORED_STATUS : 'errored', ONE_LAUNCH_STATUS : 'one-launch-status', @@ -67,6 +68,11 @@ var csts = { PM2_UPDATE : '../lib/API/pm2-plus/pres/motd.update', DEFAULT_MODULE_JSON : 'package.json', + MODULE_BASEFOLDER: 'module', + MODULE_CONF_PREFIX: 'module-db-v2', + MODULE_CONF_PREFIX_TAR: 'tar-modules', + + EXP_BACKOFF_RESET_TIMER : parseInt(process.env.EXP_BACKOFF_RESET_TIMER) || 30000, REMOTE_PORT_TCP : isNaN(parseInt(process.env.KEYMETRICS_PUSH_PORT)) ? 80 : parseInt(process.env.KEYMETRICS_PUSH_PORT), REMOTE_PORT : 41624, REMOTE_HOST : 's1.keymetrics.io', @@ -95,7 +101,7 @@ var csts = { WORKER_INTERVAL : process.env.PM2_WORKER_INTERVAL || 30000, KILL_TIMEOUT : process.env.PM2_KILL_TIMEOUT || 1600, PM2_PROGRAMMATIC : typeof(process.env.pm_id) !== 'undefined' || process.env.PM2_PROGRAMMATIC, - PM2_LOG_DATE_FORMAT : process.env.PM2_LOG_DATE_FORMAT !== undefined ? process.env.PM2_LOG_DATE_FORMAT : 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' + PM2_LOG_DATE_FORMAT : process.env.PM2_LOG_DATE_FORMAT !== undefined ? process.env.PM2_LOG_DATE_FORMAT : 'YYYY-MM-DDTHH:mm:ss' }; diff --git a/examples/log-test/log.js b/examples/echo/log.js similarity index 100% rename from examples/log-test/log.js rename to examples/echo/log.js diff --git a/examples/echo/stdout.js b/examples/echo/stdout.js new file mode 100644 index 000000000..717b2c11f --- /dev/null +++ b/examples/echo/stdout.js @@ -0,0 +1,4 @@ + +setInterval(function() { + process.stdout.write('ooo') +}, 100) diff --git a/examples/run-php-python-ruby-bash/echo.py b/examples/run-php-python-ruby-bash/echo.py index 1fe864939..5d45e0d62 100644 --- a/examples/run-php-python-ruby-bash/echo.py +++ b/examples/run-php-python-ruby-bash/echo.py @@ -3,4 +3,5 @@ while 1: print("Start : %s" % time.ctime()) + print("second line") time.sleep(1) diff --git a/lib/API.js b/lib/API.js index ebd2fc2df..74b058aea 100644 --- a/lib/API.js +++ b/lib/API.js @@ -26,7 +26,8 @@ var path_structure = require('../paths.js'); var UX = require('./API/CliUx'); var pkg = require('../package.json'); var flagWatch = require("./API/Modules/flagWatch.js"); -var hf = require('./API/Modules/flagExt.js'); +var hf = require('./API/Modules/flagExt.js'); +var Configuration = require('./Configuration.js'); var IMMUTABLE_MSG = chalk.bold.blue('Use --update-env to update environment variables'); @@ -103,6 +104,8 @@ class API { process.stdout._handle.setBlocking(true); } + this.user_conf = Configuration.getSync('pm2') + this.Client = new Client({ pm2_home: that.pm2_home, conf: this._conf, @@ -179,7 +182,7 @@ class API { // If new pm2 instance has been popped // Lauch all modules - Modularizer.launchAll(that, function(err_mod) { + that.launchAll(that, function(err_mod) { return cb(err, meta); }); }); @@ -245,7 +248,7 @@ class API { * @param {Function} cb callback once pm2 has launched modules */ launchModules (cb) { - Modularizer.launchAll(this, cb); + this.launchAll(this, cb); } /** @@ -320,7 +323,6 @@ class API { opts = {}; var that = this; - if (util.isArray(opts.watch) && opts.watch.length === 0) opts.watch = (opts.rawArgs ? !!~opts.rawArgs.indexOf('--watch') : !!~process.argv.indexOf('--watch')) || false; @@ -406,7 +408,7 @@ class API { that.Client.launchRPC(function() { that.resurrect(function() { Common.printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated')); - Modularizer.launchAll(that, function() { + that.launchAll(that, function() { KMDaemon.launchAndInteract(that._conf, null, function(err, data, interactor_proc) { return cb ? cb(null, {success:true}) : that.speedList(); }); @@ -520,8 +522,9 @@ class API { return that.actionFromJson('deleteProcessId', process_name, commander, 'pipe', cb); if (Common.isConfigFile(process_name)) return that.actionFromJson('deleteProcessId', process_name, commander, 'file', cb); - else + else { that._operate('deleteProcessId', process_name, cb); + } } /** @@ -595,34 +598,31 @@ class API { * @param {Function} cb Callback */ killDaemon (cb) { + process.env.PM2_STATUS = 'stopping' + var that = this; that.Client.executeRemote('notifyKillPM2', {}, function() {}); - Common.printOut(conf.PREFIX_MSG + '[-] Stopping Modules'); - that.killAllModules(function() { - Common.printOut(conf.PREFIX_MSG + '[v] Modules Stopped'); + Common.printOut(conf.PREFIX_MSG + '[v] Modules Stopped'); - Common.printOut(conf.PREFIX_MSG + '[-] Stopping all Applications'); - that._operate('deleteProcessId', 'all', function(err, list) { - Common.printOut(conf.PREFIX_MSG + '[v] All Applications Stopped'); - process.env.PM2_SILENT = 'false'; + that._operate('deleteProcessId', 'all', function(err, list) { + Common.printOut(conf.PREFIX_MSG + '[v] All Applications Stopped'); + process.env.PM2_SILENT = 'false'; - Common.printOut(conf.PREFIX_MSG + '[-] Stopping Agent'); - that.killAgent(function(err, data) { - //if (err) console.error(err) + that.killAgent(function(err, data) { + if (!err) { Common.printOut(conf.PREFIX_MSG + '[v] Agent Stopped'); + } - Common.printOut(conf.PREFIX_MSG + '[-] Stopping PM2 Daemon'); - that.Client.killDaemon(function(err, res) { - if (err) Common.printError(err); - Common.printOut(conf.PREFIX_MSG + '[-] PM2 Daemon Stopped'); - - return cb ? cb(err, res) : that.exitCli(conf.SUCCESS_EXIT); - }); + that.Client.killDaemon(function(err, res) { + if (err) Common.printError(err); + Common.printOut(conf.PREFIX_MSG + '[v] PM2 Daemon Stopped'); + return cb ? cb(err, res) : that.exitCli(conf.SUCCESS_EXIT); }); + }); - }); + }) } kill (cb) { @@ -646,23 +646,20 @@ class API { } var that = this; - var app_conf = Config.transCMDToConf(opts); + /** + * Commander.js tricks + */ + var app_conf = Config.filterOptions(opts); var appConf = {}; var ignoreFileArray = []; - if (!!opts.executeCommand) - app_conf.exec_mode = 'fork'; - else if (opts.instances !== undefined) - app_conf.exec_mode = 'cluster'; - else - app_conf.exec_mode = 'fork'; - if (typeof app_conf.name == 'function') delete app_conf.name; delete app_conf.args; + // Retrieve arguments via -- var argsIndex; if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0) @@ -677,7 +674,6 @@ class API { app_conf = appConf[0]; - if (opts.ignoreWatch) { flagWatch.handleFolders(opts.ignoreWatch, ignoreFileArray); if (app_conf.ignore_watch) { @@ -698,7 +694,6 @@ class API { hf.make_available_extension(opts, mas); // for -e flag mas.length > 0 ? app_conf.ignore_watch = mas : 0; - /** * If -w option, write configuration to configuration.json file */ @@ -713,6 +708,24 @@ class API { } } + series([ + restartExistingProcessName, + restartExistingProcessId, + restartExistingProcessPathOrStartNew + ], function(err, data) { + if (err instanceof Error) + return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT); + + var ret = {}; + + data.forEach(function(_dt) { + if (_dt !== undefined) + ret = _dt; + }); + + return cb ? cb(null, ret) : that.speedList(); + }); + /** * If start start/restart application */ @@ -758,7 +771,7 @@ class API { * Restart a process with the same full path * Or start it */ - function restartExistingProcessPath(cb) { + function restartExistingProcessPathOrStartNew(cb) { that.Client.executeRemote('getMonitorData', {}, function(err, procs) { if (err) return cb ? cb(new Error(err)) : that.exitCli(conf.ERROR_EXIT); @@ -767,7 +780,7 @@ class API { procs.forEach(function(proc) { if (proc.pm2_env.pm_exec_path == full_path && - proc.pm2_env.name == app_conf.name) + proc.pm2_env.name == app_conf.name) managed_script = proc; }); @@ -828,24 +841,6 @@ class API { return false; }); } - - series([ - restartExistingProcessName, - restartExistingProcessId, - restartExistingProcessPath - ], function(err, data) { - - if (err instanceof Error) - return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT); - - var ret = {}; - data.forEach(function(_dt) { - if (_dt !== undefined) - ret = _dt; - }); - - return cb ? cb(null, ret) : that.speedList(); - }); } /** @@ -862,10 +857,12 @@ class API { var apps_info = []; var that = this; + /** + * Get File configuration + */ if (typeof(cb) === 'undefined' && typeof(pipe) === 'function') { cb = pipe; } - if (typeof(file) === 'object') { config = file; } else if (pipe === 'pipe') { @@ -894,18 +891,19 @@ class API { } } + /** + * Alias some optional fields + */ if (config.deploy) deployConf = config.deploy; - if (config.apps) appConf = config.apps; else if (config.pm2) appConf = config.pm2; else appConf = config; - if (!Array.isArray(appConf)) - appConf = [appConf]; //convert to array + appConf = [appConf]; if ((appConf = Common.verifyConfs(appConf)) instanceof Error) return cb ? cb(appConf) : that.exitCli(conf.ERROR_EXIT); @@ -932,6 +930,8 @@ class API { // --ignore-watch if (!app.ignore_watch && opts.ignore_watch) app.ignore_watch = opts.ignore_watch; + if (opts.install_url) + app.install_url = opts.install_url // --instances if (opts.instances && typeof(opts.instances) === 'number') app.instances = opts.instances; @@ -944,6 +944,9 @@ class API { // Specific if (app.append_env_to_name && opts.env) app.name += ('-' + opts.env); + if (opts.name_prefix && app.name.indexOf(opts.name_prefix) == -1) + app.name = `${opts.name_prefix}:${app.name}` + app.username = Common.getCurrentUsername(); apps_name.push(app.name); }); @@ -967,7 +970,6 @@ class API { */ eachLimit(Object.keys(proc_list), conf.CONCURRENT_ACTIONS, function(proc_name, next) { - // Skip app name (--only option) if (apps_name.indexOf(proc_name) == -1) return next(); @@ -1346,7 +1348,19 @@ class API { } if (process_name == 'all') { - that.Client.getAllProcessId(function(err, ids) { + // When using shortcuts like 'all', do not delete modules + var fn + + if (process.env.PM2_STATUS == 'stopping') + that.Client.getAllProcessId(function(err, ids) { + reoperate(err, ids) + }); + else + that.Client.getAllProcessIdWithoutModules(function(err, ids) { + reoperate(err, ids) + }); + + function reoperate(err, ids) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); @@ -1355,9 +1369,8 @@ class API { Common.printError(conf.PREFIX_MSG_WARNING + 'No process found'); return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT); } - return processIds(ids, cb); - }); + } } // operate using regex else if (isNaN(process_name) && process_name[0] === '/' && process_name[process_name.length - 1] === '/') { @@ -1426,7 +1439,7 @@ class API { * (nodeArgs -> node_args) */ _handleAttributeUpdate (opts) { - var conf = Config.transCMDToConf(opts); + var conf = Config.filterOptions(opts); var that = this; if (typeof(conf.name) != 'string') @@ -1707,7 +1720,7 @@ class API { require('./API/Extra.js')(API); require('./API/Deploy.js')(API); -require('./API/Modules/Modules.js')(API); +require('./API/Modules/index.js')(API); require('./API/pm2-plus/link.js')(API); require('./API/pm2-plus/process-selector.js')(API); diff --git a/lib/API/CliUx.js b/lib/API/CliUx.js index 256cf3284..91c7d5c85 100644 --- a/lib/API/CliUx.js +++ b/lib/API/CliUx.js @@ -1,3 +1,4 @@ +'use strict' /** * Copyright 2013 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that @@ -9,6 +10,7 @@ var chalk = require('chalk'); var Common = require('../Common'); var Spinner = require('./Spinner.js'); var UX = module.exports = {}; +var Passwd = require('../tools/passwd.js') /** * Description @@ -24,13 +26,14 @@ UX.miniDisplay = function(list) { var key = l.pm2_env.name || p.basename(l.pm2_env.pm_exec_path.script); console.log('+--- %s', key); + console.log('version : %s', l.pm2_env.version); console.log('pid : %s', l.pid); console.log('pm2 id : %s', l.pm2_env.pm_id); console.log('status : %s', status); console.log('mode : %s', mode); console.log('restarted : %d', l.pm2_env.restart_time ? l.pm2_env.restart_time : 0); console.log('uptime : %s', (l.pm2_env.pm_uptime && status == 'online') ? timeSince(l.pm2_env.pm_uptime) : 0); - console.log('memory usage : %s', l.monit ? UX.bytesToSize(l.monit.memory, 3) : ''); + console.log('memory usage : %s', l.monit ? UX.bytesToSize(l.monit.memory, 1) : ''); console.log('error log : %s', l.pm2_env.pm_err_log_path); console.log('watching : %s', l.pm2_env.watch ? 'yes' : 'no'); console.log('PID file : %s\n', l.pm2_env.pm_pid_path); @@ -86,6 +89,7 @@ UX.describeTable = function(process) { safe_push(table, { 'status' : colorStatus(pm2_env.status) }, { 'name': pm2_env.name }, + { 'version': pm2_env.version }, { 'restarts' : pm2_env.restart_time }, { 'uptime' : (pm2_env.pm_uptime && pm2_env.status == 'online') ? timeSince(pm2_env.pm_uptime) : 0 }, { 'script path' : pm2_env.pm_exec_path }, @@ -117,7 +121,9 @@ UX.describeTable = function(process) { /** * Module conf display */ - if (pm2_env.axm_options && pm2_env.axm_options.module_conf && Object.keys(pm2_env.axm_options.module_conf).length > 0) { + if (pm2_env.axm_options && + pm2_env.axm_options.module_conf && + Object.keys(pm2_env.axm_options.module_conf).length > 0) { var table_conf = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }); @@ -186,6 +192,7 @@ UX.describeTable = function(process) { Common.printOut(chalk.white.italic(' Add your own code metrics: http://bit.ly/code-metrics')); Common.printOut(chalk.white.italic(' Use `pm2 logs %s [--lines 1000]` to display logs'), pm2_env.name); + Common.printOut(chalk.white.italic(' Use `pm2 env %s` to display environement variables'), pm2_env.pm_id); Common.printOut(chalk.white.italic(' Use `pm2 monit` to monitor CPU and Memory usage'), pm2_env.name); }; @@ -199,9 +206,9 @@ UX.describeTable = function(process) { UX.dispAsTable = function(list, commander) { var stacked = (process.stdout.columns || 90) < 90; var app_head = stacked ? ['Name', 'id', 'mode', 'status', '↺', 'cpu', 'memory'] : - ['App name', 'id', 'mode', 'pid', 'status', 'restart', 'uptime', 'cpu', 'mem', 'user', 'watching']; - var mod_head = stacked ? ['Module', 'status', 'cpu', 'mem'] : - ['Module', 'version', 'target PID', 'status', 'restart', 'cpu', 'memory', 'user']; + ['App name', 'id', 'version', 'mode', 'pid', 'status', 'restart', 'uptime', 'cpu', 'mem', 'user', 'watching']; + var mod_head = stacked ? ['Module', 'id', 'status', 'cpu', 'mem'] : + ['Module', 'id', 'version', 'pid', 'status', 'restart', 'cpu', 'memory', 'user']; var app_table = new Table({ head : app_head, @@ -221,6 +228,7 @@ UX.dispAsTable = function(list, commander) { var sortField = 'name', sortOrder = 'asc', sort, fields = { name: 'pm2_env.name', + id: 'id', pid: 'pid', id: 'pm_id', cpu: 'monit.cpu', @@ -279,9 +287,13 @@ UX.dispAsTable = function(list, commander) { // pm2 ls for Modules obj[key] = []; + obj[key].push(l.pm_id) + // Module version + PID - if (!stacked) - obj[key].push(chalk.bold(l.pm2_env.axm_options.module_version || 'N/A'), typeof(l.pm2_env.axm_options.pid) === 'number' ? l.pm2_env.axm_options.pid : 'N/A' ); + if (!stacked) { + var pid = l.pm2_env.axm_options.pid ? l.pm2_env.axm_options.pid : l.pid + obj[key].push(chalk.bold(l.pm2_env.version || 'N/A'), pid); + } // Status obj[key].push(colorStatus(status)); @@ -291,11 +303,20 @@ UX.dispAsTable = function(list, commander) { obj[key].push(l.pm2_env.restart_time ? l.pm2_env.restart_time : 0); // CPU + Memory - obj[key].push(l.monit ? (l.monit.cpu + '%') : 'N/A', l.monit ? UX.bytesToSize(l.monit.memory, 3) : 'N/A' ); + obj[key].push(l.monit ? (l.monit.cpu + '%') : 'N/A', l.monit ? UX.bytesToSize(l.monit.memory, 1) : 'N/A' ); // User - if (!stacked) + if (!stacked) { + if (l.pm2_env.uid && typeof(l.pm2_env.uid) == 'number') { + // Resolve user id to username + let users = Passwd.getUsers() + l.pm2_env.uid = Object.keys(users).reduce((acc, username) => { + if (users[username].userId == l.pm2_env.uid) + acc = users[username].name + }) + } obj[key].push(chalk.bold(l.pm2_env.uid || l.pm2_env.username)); + } safe_push(module_table, obj); } @@ -306,6 +327,9 @@ UX.dispAsTable = function(list, commander) { // PM2 ID obj[key].push(l.pm2_env.pm_id); + // Version + obj[key].push(l.pm2_env.version); + // Exec mode obj[key].push(mode == 'fork_mode' ? chalk.inverse.bold('fork') : chalk.blue.bold('cluster')); @@ -330,8 +354,17 @@ UX.dispAsTable = function(list, commander) { obj[key].push(l.monit ? UX.bytesToSize(l.monit.memory, 1) : 'N/A'); // User - if (!stacked) + if (!stacked) { + if (l.pm2_env.uid && typeof(l.pm2_env.uid) == 'number') { + // Resolve user id to username + let users = Passwd.getUsers() + l.pm2_env.uid = Object.keys(users).reduce((acc, username) => { + if (users[username].userId == l.pm2_env.uid) + acc = users[username].name + }) + } obj[key].push(chalk.bold(l.pm2_env.uid || l.pm2_env.username)); + } // Watch status if (!stacked) @@ -343,8 +376,13 @@ UX.dispAsTable = function(list, commander) { }); console.log(app_table.toString()); - if (module_table.length > 0) { - console.log(chalk.bold(' Module activated')); + + if (module_table.length > 0 && module_table.length == 1) { + console.log(chalk.bold('Module')); + console.log(module_table.toString()); + } + if (module_table.length > 0 && module_table.length > 1) { + console.log(chalk.bold('Modules')); console.log(module_table.toString()); } }; @@ -523,7 +561,7 @@ function getNestedProperty(propertyName, obj) { length = parts.length, property = obj || {}; - for ( i = 0; i < length; i++ ) { + for ( var i = 0; i < length; i++ ) { property = property[parts[i]]; } diff --git a/lib/API/Dashboard.js b/lib/API/Dashboard.js index 4b78b31ef..5e396de8f 100644 --- a/lib/API/Dashboard.js +++ b/lib/API/Dashboard.js @@ -290,22 +290,23 @@ Dashboard.refresh = function(processes) { var proc = processes[this.list.selected]; this.metadataBox.setLine(0, 'App Name ' + '{bold}' + proc.pm2_env.name + '{/}'); - this.metadataBox.setLine(1, 'Restarts ' + proc.pm2_env.restart_time); - this.metadataBox.setLine(2, 'Uptime ' + ((proc.pm2_env.pm_uptime && proc.pm2_env.status == 'online') ? timeSince(proc.pm2_env.pm_uptime) : 0)); - this.metadataBox.setLine(3, 'Script path ' + proc.pm2_env.pm_exec_path); - this.metadataBox.setLine(4, 'Script args ' + (proc.pm2_env.args ? (typeof proc.pm2_env.args == 'string' ? JSON.parse(proc.pm2_env.args.replace(/'/g, '"')):proc.pm2_env.args).join(' ') : 'N/A')); - this.metadataBox.setLine(5, 'Interpreter ' + proc.pm2_env.exec_interpreter); - this.metadataBox.setLine(6, 'Interpreter args ' + (proc.pm2_env.node_args.length != 0 ? proc.pm2_env.node_args : 'N/A')); - this.metadataBox.setLine(7, 'Exec mode ' + (proc.pm2_env.exec_mode == 'fork_mode' ? '{bold}fork{/}' : '{blue-fg}{bold}cluster{/}')); - this.metadataBox.setLine(8, 'Node.js version ' + proc.pm2_env.node_version); - this.metadataBox.setLine(9, 'watch & reload ' + (proc.pm2_env.watch ? '{green-fg}{bold}✔{/}' : '{red-fg}{bold}✘{/}')); - this.metadataBox.setLine(10, 'Unstable restarts ' + proc.pm2_env.unstable_restarts); - - this.metadataBox.setLine(11, 'Comment ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.comment : 'N/A')); - this.metadataBox.setLine(12, 'Revision ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.revision : 'N/A')); - this.metadataBox.setLine(13, 'Branch ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.branch : 'N/A')); - this.metadataBox.setLine(14, 'Remote url ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.url : 'N/A')); - this.metadataBox.setLine(15, 'Last update ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.update_time : 'N/A')); + this.metadataBox.setLine(1, 'Version ' + '{bold}' + proc.pm2_env.version + '{/}'); + this.metadataBox.setLine(2, 'Restarts ' + proc.pm2_env.restart_time); + this.metadataBox.setLine(3, 'Uptime ' + ((proc.pm2_env.pm_uptime && proc.pm2_env.status == 'online') ? timeSince(proc.pm2_env.pm_uptime) : 0)); + this.metadataBox.setLine(4, 'Script path ' + proc.pm2_env.pm_exec_path); + this.metadataBox.setLine(5, 'Script args ' + (proc.pm2_env.args ? (typeof proc.pm2_env.args == 'string' ? JSON.parse(proc.pm2_env.args.replace(/'/g, '"')):proc.pm2_env.args).join(' ') : 'N/A')); + this.metadataBox.setLine(6, 'Interpreter ' + proc.pm2_env.exec_interpreter); + this.metadataBox.setLine(7, 'Interpreter args ' + (proc.pm2_env.node_args.length != 0 ? proc.pm2_env.node_args : 'N/A')); + this.metadataBox.setLine(8, 'Exec mode ' + (proc.pm2_env.exec_mode == 'fork_mode' ? '{bold}fork{/}' : '{blue-fg}{bold}cluster{/}')); + this.metadataBox.setLine(9, 'Node.js version ' + proc.pm2_env.node_version); + this.metadataBox.setLine(10, 'watch & reload ' + (proc.pm2_env.watch ? '{green-fg}{bold}✔{/}' : '{red-fg}{bold}✘{/}')); + this.metadataBox.setLine(11, 'Unstable restarts ' + proc.pm2_env.unstable_restarts); + + this.metadataBox.setLine(12, 'Comment ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.comment : 'N/A')); + this.metadataBox.setLine(13, 'Revision ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.revision : 'N/A')); + this.metadataBox.setLine(14, 'Branch ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.branch : 'N/A')); + this.metadataBox.setLine(15, 'Remote url ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.url : 'N/A')); + this.metadataBox.setLine(16, 'Last update ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.update_time : 'N/A')); if (Object.keys(proc.pm2_env.axm_monitor).length != this.metricsBox.items.length) { this.metricsBox.clearItems(); diff --git a/lib/API/Extra.js b/lib/API/Extra.js index 147bc521d..576510153 100644 --- a/lib/API/Extra.js +++ b/lib/API/Extra.js @@ -17,7 +17,6 @@ var pkg = require('../../package.json'); const semver = require('semver'); module.exports = function(CLI) { - /** * Get version of the daemonized PM2 * @method getVersion @@ -31,6 +30,33 @@ module.exports = function(CLI) { }); }; + /** + * Show application environment + * @method env + * @callback cb + */ + CLI.prototype.env = function(app_id, cb) { + var procs = [] + var printed = 0 + + this.Client.executeRemote('getMonitorData', {}, (err, list) => { + list.forEach(l => { + if (app_id == l.pm_id) { + printed++ + Object.keys(l.pm2_env.env).forEach(key => { + console.log(`${key}: ${chalk.green(l.pm2_env.env[key])}`) + }) + } + }) + + if (printed == 0) { + Common.err(`Modules with id ${app_id} not found`) + return cb ? cb.apply(null, arguments) : this.exitCli(cst.ERROR_EXIT); + } + return cb ? cb.apply(null, arguments) : this.exitCli(cst.SUCCESS_EXIT); + }) + }; + /** * Get version of the daemonized PM2 * @method getVersion diff --git a/lib/API/Modules/HTTP.js b/lib/API/Modules/HTTP.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/lib/API/Modules/HTTP.js @@ -0,0 +1 @@ + diff --git a/lib/API/Modules/LOCAL.js b/lib/API/Modules/LOCAL.js new file mode 100644 index 000000000..368b5de7b --- /dev/null +++ b/lib/API/Modules/LOCAL.js @@ -0,0 +1,121 @@ + +var path = require('path'); +var fs = require('fs'); +var os = require('os'); +var spawn = require('child_process').spawn; +var chalk = require('chalk'); +var parallel = require('async/parallel'); + +var Configuration = require('../../Configuration.js'); +var cst = require('../../../constants.js'); +var Common = require('../../Common'); +var Utility = require('../../Utility.js'); +var readline = require('readline') + +var INTERNAL_MODULES = { + 'deep-monitoring': { + dependencies: [{name: 'v8-profiler-node8'}, {name: 'gc-stats'}, {name: 'event-loop-inspector'}] + }, + 'gc-stats': {name: 'gc-stats'}, + 'event-loop-inspector': {name: 'event-loop-inspector'}, + 'v8-profiler': {name: 'v8-profiler-node8'}, + 'profiler': {name: 'v8-profiler-node8'}, + 'typescript': {dependencies: [{name: 'typescript'}, {name: 'ts-node@latest'}]}, + 'livescript': {name: 'livescript'}, + 'coffee-script': {name: 'coffee-script', message: 'Coffeescript v1 support'}, + 'coffeescript': {name: 'coffeescript', message: 'Coffeescript v2 support'} +}; + +module.exports = { + install, + INTERNAL_MODULES, + installMultipleModules +} + + +function install(module, cb, verbose) { + if (!module || !module.name || module.name.length === 0) { + return cb(new Error('No module name !')); + } + + if (typeof verbose === 'undefined') { + verbose = true; + } + + installLangModule(module.name, function (err) { + var display = module.message || module.name; + if (err) { + if (verbose) { Common.printError(cst.PREFIX_MSG_MOD_ERR + chalk.bold.green(display + ' installation has FAILED (checkout previous logs)')); } + return cb(err); + } + + if (verbose) { Common.printOut(cst.PREFIX_MSG + chalk.bold.green(display + ' ENABLED')); } + return cb(); + }); +} + +function installMultipleModules(modules, cb, post_install) { + var functionList = []; + for (var i = 0; i < modules.length; i++) { + functionList.push((function (index) { + return function (callback) { + var module = modules[index]; + if (typeof modules[index] === 'string') { + module = {name: modules[index]}; + } + install(module, function ($post_install, err, $index, $modules) { + try { + var install_instance = spawn(post_install[modules[index]], { + stdio : 'inherit', + env: process.env, + shell : true, + cwd : process.cwd() + }); + Common.printOut(cst.PREFIX_MSG_MOD + 'Running configuraton script.'); + } + catch(e) + { + Common.printOut(cst.PREFIX_MSG_MOD + 'No configuraton script found.'); + } + callback(null, { module: module, err: err }); + }, false); + }; + })(i)); + } + + parallel(functionList, function (err, results) { + for (var i = 0; i < results.length; i++) { + var display = results[i].module.message || results[i].module.name; + if (results[i].err) { + err = results[i].err; + Common.printError(cst.PREFIX_MSG_MOD_ERR + chalk.bold.green(display + ' installation has FAILED (checkout previous logs)')); + } else { + Common.printOut(cst.PREFIX_MSG + chalk.bold.green(display + ' ENABLED')); + } + } + + if(cb) cb(err); + }); +}; + +function installLangModule(module_name, cb) { + var node_module_path = path.resolve(path.join(__dirname, '../../../')); + Common.printOut(cst.PREFIX_MSG_MOD + 'Calling ' + chalk.bold.red('[NPM]') + ' to install ' + module_name + ' ...'); + + var install_instance = spawn(cst.IS_WINDOWS ? 'npm.cmd' : 'npm', ['install', module_name, '--loglevel=error'], { + stdio : 'inherit', + env: process.env, + shell : true, + cwd : node_module_path + }); + + install_instance.on('close', function(code) { + if (code > 0) + return cb(new Error('Module install failed')); + return cb(null); + }); + + install_instance.on('error', function (err) { + console.error(err.stack || err); + }); +}; diff --git a/lib/API/Modules/Modularizer.js b/lib/API/Modules/Modularizer.js index f6797a148..c9df6fbe9 100644 --- a/lib/API/Modules/Modularizer.js +++ b/lib/API/Modules/Modularizer.js @@ -4,659 +4,144 @@ * can be found in the LICENSE file. */ var path = require('path'); -var fs = require('fs'); -var os = require('os'); -var parallel = require('async/parallel'); var eachLimit = require('async/eachLimit'); var forEachLimit = require('async/forEachLimit'); -var p = path; -var readline = require('readline'); -var spawn = require('child_process').spawn; -var chalk = require('chalk'); -var Configuration = require('../../Configuration.js'); -var cst = require('../../../constants.js'); -var Common = require('../../Common'); -var Utility = require('../../Utility.js'); -var ModularizerV1 = require('./Modularizerv1.js'); +var Configuration = require('../../Configuration.js'); +var cst = require('../../../constants.js'); +var Common = require('../../Common'); +var NPM = require('./NPM.js') +var TAR = require('./TAR.js') +var LOCAL = require('./LOCAL.js') var Modularizer = module.exports = {}; -var MODULE_CONF_PREFIX = 'module-db-v2'; - -var INTERNAL_MODULES = { - 'deep-monitoring': { - dependencies: [{name: 'v8-profiler-node8'}, {name: 'gc-stats'}, {name: 'event-loop-inspector'}] - }, - 'gc-stats': {name: 'gc-stats'}, - 'event-loop-inspector': {name: 'event-loop-inspector'}, - 'v8-profiler': {name: 'v8-profiler-node8'}, - 'profiler': {name: 'v8-profiler-node8'}, - 'typescript': {dependencies: [{name: 'typescript'}, {name: 'ts-node@latest'}]}, - 'livescript': {name: 'livescript'}, - 'coffee-script': {name: 'coffee-script', message: 'Coffeescript v1 support'}, - 'coffeescript': {name: 'coffeescript', message: 'Coffeescript v2 support'} -}; - /** * PM2 Module System. - * Features: - * - Installed modules are listed separately from user applications - * - Always ON, a module is always up along PM2, to stop it, you need to uninstall it - * - Install a runnable module from NPM/Github/HTTP (require a package.json only) - * - Some modules add internal PM2 depencencies (like typescript, profiling...) - * - Internally it uses NPM install (https://docs.npmjs.com/cli/install) - * - Auto discover script to launch (first it checks the apps field, then bin and finally main attr) - * - Generate sample module via pm2 module:generate */ -Modularizer.install = function (CLI, moduleName, opts, cb) { - // if user want to install module from ecosystem.config.js file - // it can also be a custom json file's name - if (!moduleName || moduleName.length === 0 || moduleName.indexOf('.json') > 0) { - var file = moduleName || cst.APP_CONF_DEFAULT_FILE; - var isAbsolute = require('../../tools/IsAbsolute.js')(file); - var filePath = isAbsolute ? file : path.join(CLI.cwd, file); - - try { - var data = fs.readFileSync(filePath); - } catch (e) { - Common.printError(cst.PREFIX_MSG_ERR + 'File ' + file + ' not found'); - return cb(Common.retErr(e)); - } - - try { - var config = Common.parseConfig(data, file); - } catch (e) { - Common.printError(cst.PREFIX_MSG_ERR + 'File ' + file + ' malformated'); - console.error(e); - return cb(Common.retErr(e)); - } - Modularizer.installMultipleModules(config.dependencies, cb, config.post_install); - return; +Modularizer.install = function (CLI, module_name, opts, cb) { + if (typeof(opts) == 'function') { + cb = opts; + opts = {}; } - Common.printOut(cst.PREFIX_MSG_MOD + 'Installing module ' + moduleName); - - var canonicModuleName = Utility.getCanonicModuleName(moduleName); - - if (INTERNAL_MODULES.hasOwnProperty(moduleName)) { - var currentModule = INTERNAL_MODULES[moduleName]; + if (LOCAL.INTERNAL_MODULES.hasOwnProperty(module_name)) { + Common.logMod(`Adding dependency ${module_name} to PM2 Runtime`); + var currentModule = LOCAL.INTERNAL_MODULES[module_name]; if (currentModule && currentModule.hasOwnProperty('dependencies')) { - Modularizer.installMultipleModules(currentModule.dependencies, cb); + LOCAL.installMultipleModules(currentModule.dependencies, cb); } else { - installModuleByName(currentModule, cb); + LOCAL.install(currentModule, cb); } - return false; } - - moduleExist(CLI, canonicModuleName, function (exists) { - if (exists) { - // Update - Common.printOut(cst.PREFIX_MSG_MOD + 'Module already installed. Updating.'); - - // Create a backup - Rollback.backup(moduleName); - - return uninstallModule(CLI, { - module_name: canonicModuleName, - deep_uninstall: false - }, function () { - return Modularizer.installModule(CLI, moduleName, opts, cb); - }); - } - - // Install - Modularizer.installModule(CLI, moduleName, opts, cb); - }); -}; - -Modularizer.installMultipleModules = function (modules, cb, post_install) { - var functionList = []; - for (var i = 0; i < modules.length; i++) { - functionList.push((function (index) { - return function (callback) { - var module = modules[index]; - if (typeof modules[index] === 'string') { - module = {name: modules[index]}; - } - installModuleByName(module, function ($post_install, err, $index, $modules) { - try - { - var install_instance = spawn(post_install[modules[index]], { - stdio : 'inherit', - env: process.env, - shell : true, - cwd : process.cwd() - }); - Common.printOut(cst.PREFIX_MSG_MOD + 'Running configuraton script.'); - } - catch(e) - { - Common.printOut(cst.PREFIX_MSG_MOD + 'No configuraton script found.'); - } - callback(null, { module: module, err: err }); - }, false); - }; - })(i)); + else if (module_name == '.') { + Common.logMod(`Installing local NPM module`); + return NPM.localStart(CLI, opts, cb) } - - parallel(functionList, function (err, results) { - for (var i = 0; i < results.length; i++) { - var display = results[i].module.message || results[i].module.name; - if (results[i].err) { - err = results[i].err; - Common.printError(cst.PREFIX_MSG_MOD_ERR + chalk.bold.green(display + ' installation has FAILED (checkout previous logs)')); - } else { - Common.printOut(cst.PREFIX_MSG + chalk.bold.green(display + ' ENABLED')); - } - } - - if(cb) cb(err); - }); -}; - -Modularizer.installModule = function(CLI, module_name, opts, cb) { - var proc_path = '', - cmd = '', - conf = {}, - development_mode = false; - - if (typeof(opts) == 'function') { - cb = opts; - opts = {}; - } - - if (module_name == '.') { - /******************* - * Development mode - *******************/ - Common.printOut(cst.PREFIX_MSG_MOD + 'Installing local module in DEVELOPMENT MODE with WATCH auto restart'); - development_mode = true; - proc_path = process.cwd(); - - cmd = p.join(proc_path, cst.DEFAULT_MODULE_JSON); - - Common.extend(opts, { - cmd : cmd, - development_mode : development_mode, - proc_path : proc_path - }); - - return startModule(CLI, opts, function(err, dt) { - if (err) return cb(err); - Common.printOut(cst.PREFIX_MSG_MOD + 'Module successfully installed and launched'); - return cb(null, dt); - }); + else if (opts.tarball || module_name.indexOf('.tar.gz') > -1) { + Common.logMod(`Installing TAR module`); + TAR.install(CLI, module_name, opts, cb) } - - /****************** - * Production mode - ******************/ - Common.printOut(cst.PREFIX_MSG_MOD + 'Calling ' + chalk.bold.red('[NPM]') + ' to install ' + module_name + ' ...'); - - var canonic_module_name = Utility.getCanonicModuleName(module_name); - var install_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name); - - require('mkdirp')(install_path, function() { - process.chdir(os.homedir()); - - var install_instance = spawn(cst.IS_WINDOWS ? 'npm.cmd' : 'npm', ['install', module_name, '--loglevel=error', '--prefix', install_path ], { - stdio : 'inherit', - env: process.env, - shell : true - }); - - install_instance.on('close', finalize); - - install_instance.on('error', function (err) { - console.error(err.stack || err); - }); - - }); - - - function finalize(code) { - if (code != 0) { - // If install has failed, revert to previous module version - return Rollback.revert(CLI, module_name, function() { - return cb(new Error('Installation failed via NPM, module has been restored to prev version')); - }); - } - - Common.printOut(cst.PREFIX_MSG_MOD + 'Module downloaded'); - - var proc_path = path.join(install_path, 'node_modules', canonic_module_name); - var package_json_path = path.join(proc_path, 'package.json'); - - // Append default configuration to module configuration - try { - var conf = JSON.parse(fs.readFileSync(package_json_path).toString()).config; - - if (conf) { - Object.keys(conf).forEach(function(key) { - Configuration.setSyncIfNotExist(canonic_module_name + ':' + key, conf[key]); - }); - } - } catch(e) { - Common.printError(e); - } - - opts = Common.extend(opts, { - cmd : package_json_path, - development_mode : development_mode, - proc_path : proc_path - }); - - Configuration.set(MODULE_CONF_PREFIX + ':' + canonic_module_name, { - uid : opts.uid, - gid : opts.gid - }, function(err, data) { - if (err) return cb(err); - - startModule(CLI, opts, function(err, dt) { - if (err) return cb(err); - - if (process.env.PM2_PROGRAMMATIC === 'true') - return cb(null, dt); - - CLI.conf(canonic_module_name, function() { - Common.printOut(cst.PREFIX_MSG_MOD + 'Module successfully installed and launched'); - Common.printOut(cst.PREFIX_MSG_MOD + 'Edit configuration via: `pm2 conf`'); - return cb(null, dt); - }); - }); - }); + else { + Common.logMod(`Installing NPM ${module_name} module`); + NPM.install(CLI, module_name, opts, cb) } -} - -// Start V1 and V2 modules -Modularizer.launchAll = function(CLI, cb) { - ModularizerV1.launchModules(CLI, function() { - Modularizer.launchModules(CLI, cb); - }); }; +/** + * Launch All Modules + * Used PM2 at startup + */ Modularizer.launchModules = function(CLI, cb) { var modules = Modularizer.listModules(); if (!modules) return cb(); - eachLimit(Object.keys(modules), 1, function(module_name, next) { - Common.printOut(cst.PREFIX_MSG_MOD + 'Starting module ' + module_name); - - var install_path = path.join(cst.DEFAULT_MODULE_PATH, module_name); - var proc_path = path.join(install_path, 'node_modules', module_name); - var package_json_path = path.join(proc_path, 'package.json'); - - var opts = {}; - - // Merge with embedded configuration inside module_conf (uid, gid) - Common.extend(opts, modules[module_name]); - - // Merge meta data to start module properly - Common.extend(opts, { - // package.json path - cmd : package_json_path, - // starting mode - development_mode : false, - // process cwd - proc_path : proc_path - }); + // 1# + function launchNPMModules(cb) { + if (!modules.npm_modules) return launchTARModules(cb) - startModule(CLI, opts, function(err, dt) { - if (err) console.error(err); - return next(); + eachLimit(Object.keys(modules.npm_modules), 1, function(module_name, next) { + NPM.start(CLI, modules, module_name, next) + }, function() { + launchTARModules(cb) }); - - }, function() { - return cb ? cb(null) : false; - }); -} - - -function startModule(CLI, opts, cb) { - if (!opts.cmd) throw new Error('module package.json not defined'); - if (!opts.development_mode) opts.development_mode = false; - - var package_json = require(opts.cmd); - - /** - * Script file detection - * 1- *apps* field (default pm2 json configuration) - * 2- *bin* field - * 3- *main* field - */ - if (!package_json.apps) { - package_json.apps = {}; - - if (package_json.bin) { - var bin = Object.keys(package_json.bin)[0]; - package_json.apps.script = package_json.bin[bin]; - } - else if (package_json.main) { - package_json.apps.script = package_json.main; - } } - Common.extend(opts, { - cwd : opts.proc_path, - watch : opts.development_mode, - force_name : package_json.name, - started_as_module : true - }); - - // Start the module - CLI.start(package_json, opts, function(err, data) { - if (err) return cb(err); + // 2# + function launchTARModules(cb) { + if (!modules.tar_modules) return cb() - if (opts.safe) { - Common.printOut(cst.PREFIX_MSG_MOD + 'Monitoring module behavior for potential issue (5secs...)'); + eachLimit(Object.keys(modules.tar_modules), 1, function(module_name, next) { + TAR.start(CLI, module_name, next) + }, function() { + return cb ? cb(null) : false; + }); + } - var time = typeof(opts.safe) == 'boolean' ? 3000 : parseInt(opts.safe); - return setTimeout(function() { - CLI.describe(package_json.name, function(err, apps) { - if (err || apps[0].pm2_env.restart_time > 2) { - return Rollback.revert(CLI, package_json.name, function() { - return cb(new Error('New Module is instable, restored to previous version')); - }); - } - return cb(null, data); - }); - }, time); - } + launchNPMModules(cb) +} - return cb(null, data); - }); -}; +Modularizer.package = function(CLI, module_path, cb) { + var fullpath = process.cwd() + if (module_path) + fullpath = require('path').resolve(module_path) + TAR.package(fullpath, process.cwd(), cb) +} /** * Uninstall module */ Modularizer.uninstall = function(CLI, module_name, cb) { Common.printOut(cst.PREFIX_MSG_MOD + 'Uninstalling module ' + module_name); + var modules_list = Modularizer.listModules(); if (module_name == 'all') { - var modules = Modularizer.listModules(); - - if (!modules) return cb(); - - return forEachLimit(Object.keys(modules), 1, function(module_name, next) { - uninstallModule(CLI, { - module_name : module_name, - deep_uninstall : true - }, next); - }, cb); + if (!modules_list) return cb(); + + return forEachLimit(Object.keys(modules_list.npm_modules), 1, function(module_name, next) { + NPM.uninstall(CLI, module_name, next) + }, () => { + forEachLimit(Object.keys(modules_list.tar_modules), 1, function(module_name, next) { + TAR.uninstall(CLI, module_name, next) + }, cb) + }); } - var canonic_module_name = Utility.getCanonicModuleName(module_name); - - uninstallModule(CLI, { - module_name : canonic_module_name, - deep_uninstall : true - }, cb); -}; - -function uninstallModule(CLI, opts, cb) { - var module_name = opts.module_name; - var proc_path = p.join(cst.PM2_ROOT_PATH, 'node_modules', module_name); - - try { - // v1 uninstallation - fs.statSync(proc_path) - if (opts.deep_uninstall == true) - Configuration.unsetSync('module-db:' + module_name); - } catch(e) { - proc_path = p.join(cst.DEFAULT_MODULE_PATH, module_name); - if (opts.deep_uninstall == true) - Configuration.unsetSync(MODULE_CONF_PREFIX + ':' + module_name); + if (modules_list.npm_modules[module_name]) { + NPM.uninstall(CLI, module_name, cb) + } else if (modules_list.tar_modules[module_name]) { + TAR.uninstall(CLI, module_name, cb) } - - CLI.deleteModule(module_name, function(err, data) { - if (err) { - Common.printError(err); - - if (module_name != '.') { - console.log(proc_path); - require('shelljs').rm('-r', proc_path); - } - - return cb(err); - } - - if (module_name != '.') { - require('shelljs').rm('-r', proc_path); - } - - return cb(null, data); - }); -} - -var Rollback = { - revert : function(CLI, module_name, cb) { - var canonic_module_name = Utility.getCanonicModuleName(module_name); - var backup_path = path.join(require('os').tmpdir(), canonic_module_name); - var module_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name); - - try { - fs.statSync(backup_path) - } catch(e) { - return cb(new Error('no backup found')); - } - - Common.printOut(cst.PREFIX_MSG_MOD + chalk.bold.red('[[[[[ Module installation failure! ]]]]]')); - Common.printOut(cst.PREFIX_MSG_MOD + chalk.bold.red('[RESTORING TO PREVIOUS VERSION]')); - - CLI.deleteModule(canonic_module_name, function() { - // Delete failing module - require('shelljs').rm('-r', module_path); - // Restore working version - require('shelljs').cp('-r', backup_path, cst.DEFAULT_MODULE_PATH); - - var proc_path = path.join(module_path, 'node_modules', canonic_module_name); - var package_json_path = path.join(proc_path, 'package.json'); - - // Start module - startModule(CLI, { - cmd : package_json_path, - development_mode : false, - proc_path : proc_path - }, cb); - }); - }, - backup : function(module_name) { - // Backup current module - var tmpdir = require('os').tmpdir(); - var canonic_module_name = Utility.getCanonicModuleName(module_name); - var module_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name); - require('shelljs').cp('-r', module_path, tmpdir); + else { + Common.errMod('Unknown module') + CLI.exitCli(1) } -} +}; /** * List modules based on modules present in ~/.pm2/modules/ folder */ Modularizer.listModules = function() { - return Configuration.getSync(MODULE_CONF_PREFIX); + return { + npm_modules: Configuration.getSync(cst.MODULE_CONF_PREFIX) || {}, + tar_modules: Configuration.getSync(cst.MODULE_CONF_PREFIX_TAR) || {} + } }; -// Expose old module installation method for testing purpose -Modularizer.installModuleV1 = ModularizerV1.installModule; - Modularizer.getAdditionalConf = function(app_name) { - if (!app_name) throw new Error('No app_name defined'); - - var module_conf = Configuration.getAllSync(); - - var additional_env = {}; + return NPM.getModuleConf(app_name) +}; - if (!module_conf[app_name]) { - additional_env = {}; - additional_env[app_name] = {}; +Modularizer.publish = function(PM2, folder, opts, cb) { + if (opts.npm == true) { + NPM.publish(opts, cb) } else { - additional_env = Common.clone(module_conf[app_name]); - additional_env[app_name] = JSON.stringify(module_conf[app_name]); + TAR.publish(PM2, folder, cb) } - return additional_env; -}; - -/** - * Publish a module - */ -Modularizer.publish = function(cb) { - var rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - var semver = require('semver'); - - var package_file = p.join(process.cwd(), 'package.json'); - - var package_json = require(package_file); - - package_json.version = semver.inc(package_json.version, 'minor'); - Common.printOut(cst.PREFIX_MSG_MOD + 'Incrementing module to: %s@%s', - package_json.name, - package_json.version); - - - rl.question("Write & Publish? [Y/N]", function(answer) { - if (answer != "Y") - return cb(); - - - fs.writeFile(package_file, JSON.stringify(package_json, null, 2), function(err, data) { - if (err) return cb(err); - - Common.printOut(cst.PREFIX_MSG_MOD + 'Publishing module - %s@%s', - package_json.name, - package_json.version); - - require('shelljs').exec('npm publish', function(code) { - Common.printOut(cst.PREFIX_MSG_MOD + 'Module - %s@%s successfully published', - package_json.name, - package_json.version); - - Common.printOut(cst.PREFIX_MSG_MOD + 'Pushing module on Git'); - require('shelljs').exec('git add . ; git commit -m "' + package_json.version + '"; git push origin master', function(code) { - - Common.printOut(cst.PREFIX_MSG_MOD + 'Installable with pm2 install %s', package_json.name); - return cb(null, package_json); - }); - }); - }); - - }); }; Modularizer.generateSample = function(app_name, cb) { - var rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - function samplize(module_name) { - var cmd1 = 'git clone https://github.com/pm2-hive/sample-module.git ' + module_name + '; cd ' + module_name + '; rm -rf .git'; - var cmd2 = 'cd ' + module_name + ' ; sed -i "s:sample-module:'+ module_name +':g" package.json'; - var cmd3 = 'cd ' + module_name + ' ; npm install'; - - Common.printOut(cst.PREFIX_MSG_MOD + 'Getting sample app'); - require('shelljs').exec(cmd1, function(err) { - if (err) Common.printError(cst.PREFIX_MSG_MOD_ERR + err.message); - require('shelljs').exec(cmd2, function(err) { - console.log(''); - require('shelljs').exec(cmd3, function(err) { - console.log(''); - Common.printOut(cst.PREFIX_MSG_MOD + 'Module sample created in folder: ', path.join(process.cwd(), module_name)); - console.log(''); - Common.printOut('Start module in development mode:'); - Common.printOut('$ cd ' + module_name + '/'); - Common.printOut('$ pm2 install . '); - console.log(''); - - Common.printOut('Module Log: '); - Common.printOut('$ pm2 logs ' + module_name); - console.log(''); - Common.printOut('Uninstall module: '); - Common.printOut('$ pm2 uninstall ' + module_name); - console.log(''); - Common.printOut('Force restart: '); - Common.printOut('$ pm2 restart ' + module_name); - return cb ? cb() : false; - }); - }); - }); - } - - if (app_name) return samplize(app_name); - - rl.question(cst.PREFIX_MSG_MOD + "Module name: ", function(module_name) { - samplize(module_name); - }); -}; - -function installModuleByName (module, cb, verbose) { - if (!module || !module.name || module.name.length === 0) { - return cb(new Error('No module name !')); - } - - if (typeof verbose === 'undefined') { - verbose = true; - } - - installLangModule(module.name, function (err) { - var display = module.message || module.name; - if (err) { - if (verbose) { Common.printError(cst.PREFIX_MSG_MOD_ERR + chalk.bold.green(display + ' installation has FAILED (checkout previous logs)')); } - return cb(err); - } - - if (verbose) { Common.printOut(cst.PREFIX_MSG + chalk.bold.green(display + ' ENABLED')); } - return cb(); - }); -} - -function installLangModule(module_name, cb) { - var node_module_path = path.resolve(path.join(__dirname, '../../../')); - Common.printOut(cst.PREFIX_MSG_MOD + 'Calling ' + chalk.bold.red('[NPM]') + ' to install ' + module_name + ' ...'); - - var install_instance = spawn(cst.IS_WINDOWS ? 'npm.cmd' : 'npm', ['install', module_name, '--loglevel=error'], { - stdio : 'inherit', - env: process.env, - shell : true, - cwd : node_module_path - }); - - install_instance.on('close', function(code) { - if (code > 0) - return cb(new Error('Module install failed')); - return cb(null); - }); - - install_instance.on('error', function (err) { - console.error(err.stack || err); - }); -}; - -function moduleExist(CLI, module_name, cb) { - // If old module, force his deletion - var modules_v1 = Configuration.getSync('module-db'); - - if (modules_v1) { - modules_v1 = Object.keys(modules_v1); - if (modules_v1.indexOf(module_name) > -1) { - return uninstallModule(CLI, { - module_name : module_name, - deep_uninstall : true - }, function() { - cb(false); - }); - } - } - - var modules = Configuration.getSync(MODULE_CONF_PREFIX); - if (!modules) return cb(false); - modules = Object.keys(modules); - return cb(modules.indexOf(module_name) > -1 ? true : false); + NPM.generateSample(app_name, cb) }; diff --git a/lib/API/Modules/Modularizerv1.js b/lib/API/Modules/Modularizerv1.js deleted file mode 100644 index f95f69f80..000000000 --- a/lib/API/Modules/Modularizerv1.js +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ -var path = require('path'); -var fs = require('fs'); -var eachLimit = require('async/eachLimit'); -var p = path; -var spawn = require('child_process').spawn; -var chalk = require('chalk'); -var Configuration = require('../../Configuration.js'); -var cst = require('../../../constants.js'); -var Common = require('../../Common'); -var Utility = require('../../Utility.js'); - -var MODULE_CONF_PREFIX = 'module-db'; - -var Modularizer = module.exports = {}; - -function startModule(CLI, opts, cb) { - /** SCRIPT - * Open file and make the script detection smart - */ - - if (!opts.cmd) throw new Error('module package.json not defined'); - if (!opts.development_mode) opts.development_mode = false; - - try { - var package_json = require(opts.cmd); - } catch(e) { - Common.printError(e); - return cb(); - } - - /** - * Script file detection - * 1- *apps* field (default pm2 json configuration) - * 2- *bin* field - * 3- *main* field - */ - if (!package_json.apps) { - package_json.apps = {}; - - if (package_json.bin) { - var bin = Object.keys(package_json.bin)[0]; - - package_json.apps.script = package_json.bin[bin]; - } - else if (package_json.main) { - package_json.apps.script = package_json.main; - } - } - - Common.extend(opts, { - cwd : opts.proc_path, - watch : opts.development_mode, - force_name : package_json.name, - started_as_module : true - }); - - // Start the module - CLI.start(package_json, opts, function(err, data) { - if (err) return cb(err); - return cb(null, data); - }); -}; - -Modularizer.launchModules = function(CLI, cb) { - var module_folder = p.join(cst.PM2_ROOT_PATH, 'node_modules'); - var modules = Configuration.getSync(MODULE_CONF_PREFIX); - - if (!modules) return cb(); - - eachLimit(Object.keys(modules), 1, function(module, next) { - var pmod = p.join(module_folder, module, cst.DEFAULT_MODULE_JSON); - - Common.printOut(cst.PREFIX_MSG_MOD + 'Starting module ' + module); - - var opts = {}; - - if (modules[module] != true) { - Common.extend(opts, modules[module]); - } - - Common.extend(opts, { - cmd : pmod, - development_mode : false, - proc_path : p.join(module_folder, module) - }); - - startModule(CLI, opts, function(err, dt) { - if (err) console.error(err); - return next(); - }); - - }, function() { - return cb ? cb(null) : false; - }); -} - -Modularizer.installModule = function(CLI, module_name, opts, cb) { - var proc_path = '', - cmd = '', - conf = {}, - development_mode = false; - - var cli = { - bin : 'npm', - cmd : 'install' - } - - Common.printOut(cst.PREFIX_MSG_MOD + 'Calling ' + chalk.bold.red('[' + cli.bin.toUpperCase() + ']') + ' to install ' + module_name + ' ...'); - - var install_instance = spawn(cst.IS_WINDOWS ? cli.bin + '.cmd' : cli.bin, [cli.cmd, module_name, '--loglevel=error'], { - stdio : 'inherit', - env: process.env, - shell : true, - cwd : cst.PM2_ROOT_PATH - }); - - install_instance.on('close', finalize); - - install_instance.on('error', function (err) { - console.error(err.stack || err); - }); - - function finalize(code) { - if (code != 0) { - return cb(new Error("Installation failed")); - } - - Common.printOut(cst.PREFIX_MSG_MOD + 'Module downloaded'); - - var canonic_module_name = Utility.getCanonicModuleName(module_name); - - proc_path = p.join(cst.PM2_ROOT_PATH, 'node_modules', canonic_module_name); - - cmd = p.join(proc_path, cst.DEFAULT_MODULE_JSON); - - /** - * Append default configuration to module configuration - */ - try { - var conf = JSON.parse(fs.readFileSync(path.join(proc_path, 'package.json')).toString()).config; - if (conf) { - Object.keys(conf).forEach(function(key) { - Configuration.setSyncIfNotExist(canonic_module_name + ':' + key, conf[key]); - }); - } - } catch(e) { - Common.printError(e); - } - - opts = Common.extend(opts, { - cmd : cmd, - development_mode : development_mode, - proc_path : proc_path - }); - - Configuration.set(MODULE_CONF_PREFIX + ':' + canonic_module_name, { - uid : opts.uid, - gid : opts.gid, - version : 0 - }, function(err, data) { - - startModule(CLI, opts, function(err, dt) { - if (err) return cb(err); - - if (process.env.PM2_PROGRAMMATIC === 'true') - return cb(null, dt); - - CLI.conf(canonic_module_name, function() { - Common.printOut(cst.PREFIX_MSG_MOD + 'Module successfully installed and launched'); - Common.printOut(cst.PREFIX_MSG_MOD + 'Edit configuration via: `pm2 conf`'); - return cb(null, dt); - }); - }); - }); - } -} diff --git a/lib/API/Modules/NPM.js b/lib/API/Modules/NPM.js new file mode 100644 index 000000000..c703e8922 --- /dev/null +++ b/lib/API/Modules/NPM.js @@ -0,0 +1,417 @@ +var path = require('path'); +var fs = require('fs'); +var os = require('os'); +var spawn = require('child_process').spawn; +var chalk = require('chalk'); + +var Configuration = require('../../Configuration.js'); +var cst = require('../../../constants.js'); +var Common = require('../../Common'); +var Utility = require('../../Utility.js'); +var readline = require('readline') + +module.exports = { + install, + uninstall, + start, + publish, + generateSample, + localStart, + getModuleConf +} + +/** + * PM2 Module System. + * Features: + * - Installed modules are listed separately from user applications + * - Always ON, a module is always up along PM2, to stop it, you need to uninstall it + * - Install a runnable module from NPM/Github/HTTP (require a package.json only) + * - Some modules add internal PM2 depencencies (like typescript, profiling...) + * - Internally it uses NPM install (https://docs.npmjs.com/cli/install) + * - Auto discover script to launch (first it checks the apps field, then bin and finally main attr) + * - Generate sample module via pm2 module:generate + */ + +function localStart(PM2, opts, cb) { + var proc_path = '', + cmd = '', + conf = {}; + + Common.printOut(cst.PREFIX_MSG_MOD + 'Installing local module in DEVELOPMENT MODE with WATCH auto restart'); + proc_path = process.cwd(); + + cmd = path.join(proc_path, cst.DEFAULT_MODULE_JSON); + + Common.extend(opts, { + cmd : cmd, + development_mode : true, + proc_path : proc_path + }); + + return StartModule(PM2, opts, function(err, dt) { + if (err) return cb(err); + Common.printOut(cst.PREFIX_MSG_MOD + 'Module successfully installed and launched'); + return cb(null, dt); + }); +} + +function generateSample(app_name, cb) { + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + function samplize(module_name) { + var cmd1 = 'git clone https://github.com/pm2-hive/sample-module.git ' + module_name + '; cd ' + module_name + '; rm -rf .git'; + var cmd2 = 'cd ' + module_name + ' ; sed -i "s:sample-module:'+ module_name +':g" package.json'; + var cmd3 = 'cd ' + module_name + ' ; npm install'; + + Common.printOut(cst.PREFIX_MSG_MOD + 'Getting sample app'); + require('shelljs').exec(cmd1, function(err) { + if (err) Common.printError(cst.PREFIX_MSG_MOD_ERR + err.message); + require('shelljs').exec(cmd2, function(err) { + console.log(''); + require('shelljs').exec(cmd3, function(err) { + console.log(''); + Common.printOut(cst.PREFIX_MSG_MOD + 'Module sample created in folder: ', path.join(process.cwd(), module_name)); + console.log(''); + Common.printOut('Start module in development mode:'); + Common.printOut('$ cd ' + module_name + '/'); + Common.printOut('$ pm2 install . '); + console.log(''); + + Common.printOut('Module Log: '); + Common.printOut('$ pm2 logs ' + module_name); + console.log(''); + Common.printOut('Uninstall module: '); + Common.printOut('$ pm2 uninstall ' + module_name); + console.log(''); + Common.printOut('Force restart: '); + Common.printOut('$ pm2 restart ' + module_name); + return cb ? cb() : false; + }); + }); + }); + } + + if (app_name) return samplize(app_name); + + rl.question(cst.PREFIX_MSG_MOD + "Module name: ", function(module_name) { + samplize(module_name); + }); +} + +function publish(opts, cb) { + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + var semver = require('semver'); + + var package_file = path.join(process.cwd(), 'package.json'); + + var package_json = require(package_file); + + package_json.version = semver.inc(package_json.version, 'minor'); + Common.printOut(cst.PREFIX_MSG_MOD + 'Incrementing module to: %s@%s', + package_json.name, + package_json.version); + + + rl.question("Write & Publish? [Y/N]", function(answer) { + if (answer != "Y") + return cb(); + + + fs.writeFile(package_file, JSON.stringify(package_json, null, 2), function(err, data) { + if (err) return cb(err); + + Common.printOut(cst.PREFIX_MSG_MOD + 'Publishing module - %s@%s', + package_json.name, + package_json.version); + + require('shelljs').exec('npm publish', function(code) { + Common.printOut(cst.PREFIX_MSG_MOD + 'Module - %s@%s successfully published', + package_json.name, + package_json.version); + + Common.printOut(cst.PREFIX_MSG_MOD + 'Pushing module on Git'); + require('shelljs').exec('git add . ; git commit -m "' + package_json.version + '"; git push origin master', function(code) { + + Common.printOut(cst.PREFIX_MSG_MOD + 'Installable with pm2 install %s', package_json.name); + return cb(null, package_json); + }); + }); + }); + + }); +} + +function moduleExistInLocalDB(CLI, module_name, cb) { + var modules = Configuration.getSync(cst.MODULE_CONF_PREFIX); + if (!modules) return cb(false); + var module_name_only = Utility.getCanonicModuleName(module_name) + modules = Object.keys(modules); + return cb(modules.indexOf(module_name_only) > -1 ? true : false); +}; + +function install(CLI, module_name, opts, cb) { + moduleExistInLocalDB(CLI, module_name, function (exists) { + if (exists) { + Common.logMod('Module already installed. Updating.'); + + Rollback.backup(module_name); + + return uninstall(CLI, module_name, function () { + return continueInstall(CLI, module_name, opts, cb); + }); + } + return continueInstall(CLI, module_name, opts, cb); + }) +} + +function continueInstall(CLI, module_name, opts, cb) { + Common.printOut(cst.PREFIX_MSG_MOD + 'Calling ' + chalk.bold.red('[NPM]') + ' to install ' + module_name + ' ...'); + + var canonic_module_name = Utility.getCanonicModuleName(module_name); + var install_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name); + + require('mkdirp')(install_path, function() { + process.chdir(os.homedir()); + + var install_instance = spawn(cst.IS_WINDOWS ? 'npm.cmd' : 'npm', ['install', module_name, '--loglevel=error', '--prefix', install_path ], { + stdio : 'inherit', + env: process.env, + shell : true + }); + + install_instance.on('close', finalizeInstall); + + install_instance.on('error', function (err) { + console.error(err.stack || err); + }); + }); + + function finalizeInstall(code) { + if (code != 0) { + // If install has failed, revert to previous module version + return Rollback.revert(CLI, module_name, function() { + return cb(new Error('Installation failed via NPM, module has been restored to prev version')); + }); + } + + Common.printOut(cst.PREFIX_MSG_MOD + 'Module downloaded'); + + var proc_path = path.join(install_path, 'node_modules', canonic_module_name); + var package_json_path = path.join(proc_path, 'package.json'); + + // Append default configuration to module configuration + try { + var conf = JSON.parse(fs.readFileSync(package_json_path).toString()).config; + + if (conf) { + Object.keys(conf).forEach(function(key) { + Configuration.setSyncIfNotExist(canonic_module_name + ':' + key, conf[key]); + }); + } + } catch(e) { + Common.printError(e); + } + + opts = Common.extend(opts, { + cmd : package_json_path, + development_mode : false, + proc_path : proc_path + }); + + Configuration.set(cst.MODULE_CONF_PREFIX + ':' + canonic_module_name, { + uid : opts.uid, + gid : opts.gid + }, function(err, data) { + if (err) return cb(err); + + StartModule(CLI, opts, function(err, dt) { + if (err) return cb(err); + + if (process.env.PM2_PROGRAMMATIC === 'true') + return cb(null, dt); + + CLI.conf(canonic_module_name, function() { + Common.printOut(cst.PREFIX_MSG_MOD + 'Module successfully installed and launched'); + Common.printOut(cst.PREFIX_MSG_MOD + 'Edit configuration via: `pm2 conf`'); + return cb(null, dt); + }); + }); + }); + } +} + +function start(PM2, modules, module_name, cb) { + Common.printOut(cst.PREFIX_MSG_MOD + 'Starting NPM module ' + module_name); + + var install_path = path.join(cst.DEFAULT_MODULE_PATH, module_name); + var proc_path = path.join(install_path, 'node_modules', module_name); + var package_json_path = path.join(proc_path, 'package.json'); + + var opts = {}; + + // Merge with embedded configuration inside module_conf (uid, gid) + Common.extend(opts, modules[module_name]); + + // Merge meta data to start module properly + Common.extend(opts, { + // package.json path + cmd : package_json_path, + // starting mode + development_mode : false, + // process cwd + proc_path : proc_path + }); + + StartModule(PM2, opts, function(err, dt) { + if (err) console.error(err); + return cb(); + }) +} + +function uninstall(CLI, module_name, cb) { + var module_name_only = Utility.getCanonicModuleName(module_name) + var proc_path = path.join(cst.DEFAULT_MODULE_PATH, module_name_only); + Configuration.unsetSync(cst.MODULE_CONF_PREFIX + ':' + module_name_only); + + CLI.deleteModule(module_name_only, function(err, data) { + if (err) { + Common.printError(err); + + if (module_name != '.') { + console.log(proc_path); + require('shelljs').rm('-r', proc_path); + } + + return cb(err); + } + + if (module_name != '.') { + require('shelljs').rm('-r', proc_path); + } + + return cb(null, data); + }); +} + +function getModuleConf(app_name) { + if (!app_name) throw new Error('No app_name defined'); + + var module_conf = Configuration.getAllSync(); + + var additional_env = {}; + + if (!module_conf[app_name]) { + additional_env = {}; + additional_env[app_name] = {}; + } + else { + additional_env = Common.clone(module_conf[app_name]); + additional_env[app_name] = JSON.stringify(module_conf[app_name]); + } + return additional_env; +} + +function StartModule(CLI, opts, cb) { + if (!opts.cmd && !opts.package) throw new Error('module package.json not defined'); + if (!opts.development_mode) opts.development_mode = false; + + var package_json = require(opts.cmd || opts.package); + + /** + * Script file detection + * 1- *apps* field (default pm2 json configuration) + * 2- *bin* field + * 3- *main* field + */ + if (!package_json.apps && !package_json.pm2) { + package_json.apps = {}; + + if (package_json.bin) { + var bin = Object.keys(package_json.bin)[0]; + package_json.apps.script = package_json.bin[bin]; + } + else if (package_json.main) { + package_json.apps.script = package_json.main; + } + } + + Common.extend(opts, { + cwd : opts.proc_path, + watch : opts.development_mode, + force_name : package_json.name, + started_as_module : true + }); + + // Start the module + CLI.start(package_json, opts, function(err, data) { + if (err) return cb(err); + + if (opts.safe) { + Common.printOut(cst.PREFIX_MSG_MOD + 'Monitoring module behavior for potential issue (5secs...)'); + + var time = typeof(opts.safe) == 'boolean' ? 3000 : parseInt(opts.safe); + return setTimeout(function() { + CLI.describe(package_json.name, function(err, apps) { + if (err || apps[0].pm2_env.restart_time > 2) { + return Rollback.revert(CLI, package_json.name, function() { + return cb(new Error('New Module is instable, restored to previous version')); + }); + } + return cb(null, data); + }); + }, time); + } + + return cb(null, data); + }); +}; + + + +var Rollback = { + revert : function(CLI, module_name, cb) { + var canonic_module_name = Utility.getCanonicModuleName(module_name); + var backup_path = path.join(require('os').tmpdir(), canonic_module_name); + var module_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name); + + try { + fs.statSync(backup_path) + } catch(e) { + return cb(new Error('no backup found')); + } + + Common.printOut(cst.PREFIX_MSG_MOD + chalk.bold.red('[[[[[ Module installation failure! ]]]]]')); + Common.printOut(cst.PREFIX_MSG_MOD + chalk.bold.red('[RESTORING TO PREVIOUS VERSION]')); + + CLI.deleteModule(canonic_module_name, function() { + // Delete failing module + require('shelljs').rm('-r', module_path); + // Restore working version + require('shelljs').cp('-r', backup_path, cst.DEFAULT_MODULE_PATH); + + var proc_path = path.join(module_path, 'node_modules', canonic_module_name); + var package_json_path = path.join(proc_path, 'package.json'); + + // Start module + StartModule(CLI, { + cmd : package_json_path, + development_mode : false, + proc_path : proc_path + }, cb); + }); + }, + backup : function(module_name) { + // Backup current module + var tmpdir = require('os').tmpdir(); + var canonic_module_name = Utility.getCanonicModuleName(module_name); + var module_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name); + require('shelljs').cp('-r', module_path, tmpdir); + } +} diff --git a/lib/API/Modules/TAR.js b/lib/API/Modules/TAR.js new file mode 100644 index 000000000..a644c4820 --- /dev/null +++ b/lib/API/Modules/TAR.js @@ -0,0 +1,343 @@ + +var Configuration = require('../../Configuration.js'); +var cst = require('../../../constants.js'); +var Common = require('../../Common'); +var forEachLimit = require('async/forEachLimit'); + +var path = require('path'); +var fs = require('fs'); +var os = require('os'); +var spawn = require('child_process').spawn; +var exec = require('child_process').exec; + +module.exports = { + install, + uninstall, + start, + publish, + package +} + +/** + * Module management to manage tarball packages + * + * pm2 install http.tar.gz + * pm2 uninstall http + * + * - the first and only folder in the tarball must be called module (tar zcvf http module/) + * - a package.json must be present with attribute "name", "version" and "pm2" to declare apps to run + */ + +function install(PM2, module_filepath, opts, cb) { + // Remote file retrieval + if (module_filepath.includes('http') === true) { + var target_file = module_filepath.split('/').pop() + var target_filepath = path.join(os.tmpdir(), target_file) + + opts.install_url = module_filepath + + return retrieveRemote(module_filepath, target_filepath, (err) => { + if (err) { + Common.errMod(err) + process.exit(1) + } + installLocal(PM2, target_filepath, opts, cb) + }) + } + + // Local install + installLocal(PM2, module_filepath, opts, cb) +} + +function retrieveRemote(url, dest, cb) { + Common.logMod(`Retrieving remote package ${url}...`) + + var wget = spawn('wget', [url, '-O', dest, '-q'], { + stdio : 'inherit', + env: process.env, + shell : true + }) + + wget.on('error', (err) => { + console.error(err.stack || err) + }) + + wget.on('close', (code) => { + if (code !== 0) + return cb(new Error('Could not download')) + return cb(null) + }) +} + +function installLocal(PM2, module_filepath, opts, cb) { + Common.logMod(`Installing package ${module_filepath}`) + + // Get module name by unpacking the module/package.json only and read the name attribute + getModuleName(module_filepath, function(err, module_name) { + if (err) return cb(err) + + Common.logMod(`Module name is ${module_name}`) + + Common.logMod(`Depackaging module...`) + + var install_path = path.join(cst.DEFAULT_MODULE_PATH, module_name); + + if (fs.existsSync(install_path)) { + deleteModulePath(module_name) + } + + require('mkdirp').sync(install_path) + + var install_instance = spawn('tar', ['zxf', module_filepath, '-C', install_path, '--strip-components 1'], { + stdio : 'inherit', + env: process.env, + shell : true + }) + + install_instance.on('close', function(code) { + Common.logMod(`Module depackaged in ${install_path}`) + if (code == 0) + return runInstall(PM2, install_path, module_name, opts, cb) + return PM2.exitCli(1) + }); + + install_instance.on('error', function (err) { + console.error(err.stack || err); + }); + }) +} + +function deleteModulePath(module_name) { + var sanitized = module_name.replace(/\./g, '') + require('shelljs').exec(`rm -r ${path.join(cst.DEFAULT_MODULE_PATH, module_name)}`, { silent: true }) +} + +function runInstall(PM2, target_path, module_name, opts, cb) { + Common.logMod(`Starting ${target_path}`) + + var config_file = path.join(target_path, 'package.json') + var conf + + try { + conf = require(config_file) + module_name = conf.name + } catch(e) { + Common.errMod(new Error('Cannot find package.json file with name attribute at least')); + } + + // Force with the name in the package.json + opts.started_as_module = true + opts.cwd = target_path + + if (needPrefix(conf)) + opts.name_prefix = module_name + + // Start apps under "apps" or "pm2" attribute + PM2.start(conf, opts, function(err, data) { + if (err) return cb(err) + + Configuration.setSync(`${cst.MODULE_CONF_PREFIX_TAR}:${module_name}`, { + source: 'tarball', + install_url: opts.install_url, + installed_at: Date.now() + }) + + Common.logMod(`Module INSTALLED and STARTED`) + return cb(null, 'Module installed & Started') + }) +} + +function start(PM2, module_name, cb) { + var module_path = path.join(cst.DEFAULT_MODULE_PATH, module_name); + Common.printOut(cst.PREFIX_MSG_MOD + 'Starting TAR module ' + module_name); + var package_json_path = path.join(module_path, 'package.json'); + var module_conf = Configuration.getSync(`${cst.MODULE_CONF_PREFIX_TAR}:${module_name}`) + + try { + var conf = require(package_json_path) + } catch(e) { + Common.printError(`Could not find package.json as ${package_json_path}`) + return cb() + } + + var opts = {}; + + opts.started_as_module = true + opts.cwd = module_path + + if (module_conf.install_url) + opts.install_url = module_conf.install_url + + if (needPrefix(conf)) + opts.name_prefix = module_name + + PM2.start(conf, opts, function(err, data) { + if (err) { + Common.printError(`Could not start ${module_name} ${module_path}`) + return cb() + } + + Common.printOut(`${cst.PREFIX_MSG_MOD} Module ${module_name} STARTED`) + return cb(); + }) +} + +/** + * Retrieve from module package.json the name of each application + * delete process and delete folder + */ +function uninstall(PM2, module_name, cb) { + var module_path = path.join(cst.DEFAULT_MODULE_PATH, module_name); + + Common.logMod(`Removing ${module_name} from auto startup`) + + try { + var pkg = require(path.join(module_path, 'package.json')) + } catch(e) { + Common.errMod('Could not retrieve module package.json'); + return cb(e) + } + + var apps = pkg.apps || pkg.pm2 + apps = [].concat(apps); + + /** + * Some time a module can have multiple processes + */ + forEachLimit(apps, 1, (app, next) => { + var app_name + + if (!app.name) { + Common.renderApplicationName(app) + } + + if (apps.length > 1) + app_name = `${module_name}:${app.name}` + else if (apps.length == 1 && pkg.name != apps[0].name) + app_name = `${module_name}:${app.name}` + else + app_name = app.name + + PM2._operate('deleteProcessId', app_name, () => { + deleteModulePath(module_name) + next() + }) + }, () => { + Configuration.unsetSync(`${cst.MODULE_CONF_PREFIX_TAR}:${module_name}`) + cb(null) + }) +} + + +/** + * Uncompress only module/package.json and retrieve the "name" attribute in the package.json + */ +function getModuleName(module_filepath, cb) { + var tmp_folder = path.join(os.tmpdir(), cst.MODULE_BASEFOLDER) + + var install_instance = spawn('tar', ['zxf', module_filepath, '-C', os.tmpdir(), `${cst.MODULE_BASEFOLDER}/package.json`], { + stdio : 'inherit', + env: process.env, + shell : true + }) + + install_instance.on('close', function(code) { + try { + var pkg = JSON.parse(fs.readFileSync(path.join(tmp_folder, `package.json`))) + return cb(null, pkg.name) + } catch(e) { + return cb(e) + } + }); +} + +function package(module_path, target_path, cb) { + var base_folder = path.dirname(module_path) + var module_folder_name = path.basename(module_path) + var pkg = require(path.join(module_path, 'package.json')) + var pkg_name = `${module_folder_name}-v${pkg.version.replace(/\./g, '-')}.tar.gz` + var target_fullpath = path.join(target_path, pkg_name) + + var cmd = `tar zcf ${target_fullpath} -C ${base_folder} --transform 's,${module_folder_name},module,' ${module_folder_name}` + + Common.logMod(`Gziping ${module_path} to ${target_fullpath}`) + + var tar = exec(cmd, (err, sto, ste) => { + if (err) { + console.log(sto.toString().trim()) + console.log(ste.toString().trim()) + } + }) + + tar.on('close', function (code) { + cb(code == 0 ? null : code, { + package_name: pkg_name, + path: target_fullpath + }) + }) +} + +function publish(PM2, folder, cb) { + var target_folder = folder ? path.resolve(folder) : process.cwd() + + try { + var pkg = JSON.parse(fs.readFileSync(path.join(target_folder, 'package.json')).toString()) + } catch(e) { + Common.errMod(`${process.cwd()} module does not contain any package.json`) + process.exit(1) + } + + if (!pkg.name) throw new Error('Attribute name should be present') + if (!pkg.version) throw new Error('Attribute version should be present') + if (!pkg.pm2 && !pkg.apps) throw new Error('Attribute apps should be present') + + var current_path = target_folder + var module_name = path.basename(current_path) + var target_path = os.tmpdir() + + Common.logMod(`Starting publishing procedure for ${module_name}@${pkg.version}`) + + package(current_path, target_path, (err, res) => { + if (err) { + Common.errMod('Can\'t package, exiting') + process.exit(1) + } + + Common.logMod(`Package [${pkg.name}] created in path ${res.path}`) + + var data = { + module_data: { + file: res.path, + content_type: 'content/gzip' + }, + id: pkg.name, + name: pkg.name, + version: pkg.version + }; + + var uri = `${PM2.user_conf.registry}/api/v1/modules` + Common.logMod(`Sending Package to remote ${pkg.name} ${uri}`) + + require('needle') + .post(uri, data, { multipart: true }, function(err, res, body) { + if (err) { + Common.errMod(err) + process.exit(1) + } + if (res.statusCode !== 200) { + Common.errMod(`${pkg.name}-${pkg.version}: ${res.body.msg}`) + process.exit(1) + } + Common.logMod(`Module ${module_name} published under version ${pkg.version}`) + process.exit(0) + }) + }) +} + +function needPrefix(conf) { + if ((conf.apps && conf.apps.length > 1) || + (conf.pm2 && conf.pm2.length > 1) || + (conf.apps.length == 1 && conf.name != conf.apps[0].name)) + return true + return false +} diff --git a/lib/API/Modules/Modules.js b/lib/API/Modules/index.js similarity index 73% rename from lib/API/Modules/Modules.js rename to lib/API/Modules/index.js index 7b97c9ced..14e7db409 100644 --- a/lib/API/Modules/Modules.js +++ b/lib/API/Modules/index.js @@ -12,51 +12,6 @@ var chalk = require('chalk'); var forEachLimit = require('async/forEachLimit'); var Modularizer = require('./Modularizer.js'); -var ModularizerV1 = require('./Modularizerv1.js'); - -// Special module with post display -function postDisplay(app, cb) { - var that = this; - var retry = 0; - - UX.processing.start('Initializing module'); - - (function detectModuleInit() { - retry++; - if (retry > 12) { - // Module init has timeouted - return displayOrNot(null); - } - that.describe(app.pm_id, function(err, data) { - - if (data && data[0] && data[0].pm2_env && - data[0].pm2_env.axm_options && - data[0].pm2_env.axm_options.human_info) { - return displayOrNot(data[0]); - } - setTimeout(function() { - detectModuleInit(); - }, 300); - }); - })(); - - function displayOrNot(app) { - UX.processing.stop(); - - if (app) { - var module_name = app.name; - var human_info = app.pm2_env.axm_options.human_info; - - UX.postModuleInfos(module_name, human_info); - Common.printOut(chalk.white.italic(' Use `pm2 show %s` to display this helper'), module_name); - Common.printOut(chalk.white.italic(' Use `pm2 logs %s [--lines 1000]` to display logs'), module_name); - Common.printOut(chalk.white.italic(' Use `pm2 monit` to monitor CPU and Memory usage'), module_name); - return cb ? cb(null, app) : that.exitCli(cst.SUCCESS_EXIT); - } - - return cb ? cb(null, { msg : 'Module started' }) : that.speedList(cst.SUCCESS_EXIT); - } -} module.exports = function(CLI) { /** @@ -70,35 +25,11 @@ module.exports = function(CLI) { opts = {}; } - // Because for test, allow to install module in V1 way - if (opts.v1) { - Common.printOut('Installing the V1 way...'); - console.log(opts.uid, opts.gid, opts.v1); - ModularizerV1.installModule(this, module_name, opts, function(err, data) { - if (err) { - Common.printError(cst.PREFIX_MSG_ERR + (err.message || err)); - return cb ? cb(Common.retErr(err)) : that.speedList(cst.ERROR_EXIT); - } - - // Check if special module with post_install display - if (data && data[0] && data[0].pm2_env && data[0].pm2_env.PM2_EXTRA_DISPLAY) { - return postDisplay.call(that, data[0].pm2_env, cb); - } - return cb ? cb(null, data) : that.speedList(cst.SUCCESS_EXIT); - }); - return false; - } - Modularizer.install(this, module_name, opts, function(err, data) { if (err) { Common.printError(cst.PREFIX_MSG_ERR + (err.message || err)); return cb ? cb(Common.retErr(err)) : that.speedList(cst.ERROR_EXIT); } - - // Check if special module with post_install display - if (data && data[0] && data[0].pm2_env && data[0].pm2_env.PM2_EXTRA_DISPLAY) { - return postDisplay.call(that, data[0].pm2_env, cb); - } return cb ? cb(null, data) : that.speedList(cst.SUCCESS_EXIT); }); }; @@ -116,13 +47,28 @@ module.exports = function(CLI) { }); }; + CLI.prototype.launchAll = function(CLI, cb) { + Modularizer.launchModules(CLI, cb); + }; + + CLI.prototype.package = function(module_path, cb) { + Modularizer.package(this, module_path, (err, res) => { + if (err) { + Common.errMod(err) + return cb ? cb(err) : this.exitCli(1) + } + Common.logMod(`Module packaged in ${res.path}`) + return cb ? cb(err) : this.exitCli(0) + }) + }; + /** * Publish module on NPM + Git push */ - CLI.prototype.publish = function(module_name, cb) { + CLI.prototype.publish = function(folder, opts, cb) { var that = this; - Modularizer.publish(function(err, data) { + Modularizer.publish(this, folder, opts, function(err, data) { if (err) return cb ? cb(Common.retErr(err)) : that.speedList(cst.ERROR_EXIT); return cb ? cb(null, data) : that.speedList(cst.SUCCESS_EXIT); @@ -142,18 +88,9 @@ module.exports = function(CLI) { }); }; - CLI.prototype.killAllModules = function(cb) { - var that = this; - - this.Client.getAllModulesId(function(err, modules_id) { - forEachLimit(modules_id, 1, function(id, next) { - that._operate('deleteProcessId', id, next); - }, function() { - return cb ? cb() : false; - }); - }); - }; - + /** + * Special delete method + */ CLI.prototype.deleteModule = function(module_name, cb) { var that = this; @@ -182,3 +119,47 @@ module.exports = function(CLI) { }); }; }; + +// Special module with post display +function postDisplay(app, cb) { + var that = this; + var retry = 0; + + UX.processing.start('Initializing module'); + + (function detectModuleInit() { + retry++; + if (retry > 12) { + // Module init has timeouted + return displayOrNot(null); + } + that.describe(app.pm_id, function(err, data) { + + if (data && data[0] && data[0].pm2_env && + data[0].pm2_env.axm_options && + data[0].pm2_env.axm_options.human_info) { + return displayOrNot(data[0]); + } + setTimeout(function() { + detectModuleInit(); + }, 300); + }); + })(); + + function displayOrNot(app) { + UX.processing.stop(); + + if (app) { + var module_name = app.name; + var human_info = app.pm2_env.axm_options.human_info; + + UX.postModuleInfos(module_name, human_info); + Common.printOut(chalk.white.italic(' Use `pm2 show %s` to display this helper'), module_name); + Common.printOut(chalk.white.italic(' Use `pm2 logs %s [--lines 1000]` to display logs'), module_name); + Common.printOut(chalk.white.italic(' Use `pm2 monit` to monitor CPU and Memory usage'), module_name); + return cb ? cb(null, app) : that.exitCli(cst.SUCCESS_EXIT); + } + + return cb ? cb(null, { msg : 'Module started' }) : that.speedList(cst.SUCCESS_EXIT); + } +} diff --git a/lib/API/Startup.js b/lib/API/Startup.js index b92c65d33..23103753a 100644 --- a/lib/API/Startup.js +++ b/lib/API/Startup.js @@ -391,7 +391,7 @@ module.exports = function(CLI) { // try to fix issues with empty dump file // like #3485 - if (env_arr.length === 0) { + if (env_arr.length === 0 && !process.env.FORCE) { // fix : if no dump file, no process, only module and after pm2 update if (!fs.existsSync(cst.DUMP_FILE_PATH)) { @@ -448,6 +448,7 @@ module.exports = function(CLI) { if (!apps[0]) return fin(null); delete apps[0].pm2_env.instances; delete apps[0].pm2_env.pm_id; + delete apps[0].pm2_env.prev_restart_delay; if (!apps[0].pm2_env.pmx_module) env_arr.push(apps[0].pm2_env); apps.shift(); diff --git a/lib/API/pm2-plus/helpers.js b/lib/API/pm2-plus/helpers.js index dfaf90219..e23ad3936 100644 --- a/lib/API/pm2-plus/helpers.js +++ b/lib/API/pm2-plus/helpers.js @@ -6,7 +6,7 @@ const chalk = require('chalk'); const forEach = require('async/forEach'); const open = require('../../tools/open.js'); const semver = require('semver'); -const Modularizer = require('../Modules/Modularizer.js'); +const Modules = require('../Modules'); function processesAreAlreadyMonitored(CLI, cb) { CLI.Client.executeRemote('getMonitorData', {}, function(err, list) { @@ -42,7 +42,7 @@ module.exports = function(CLI) { } forEach(modules, (_module, next) => { - Modularizer.uninstall(this, _module, () => { + Modules.uninstall(this, _module, () => { next() }); }, (err) => { @@ -73,7 +73,7 @@ module.exports = function(CLI) { } forEach(modules, (_module, next) => { - Modularizer.install(self, _module, {}, () => { + Modules.install(self, _module, {}, () => { next() }); }, (err) => { diff --git a/lib/API/schema.json b/lib/API/schema.json index 94ef70e03..addcb0564 100644 --- a/lib/API/schema.json +++ b/lib/API/schema.json @@ -10,6 +10,12 @@ "docDefault": "Script filename without the extension (app for app.js)", "docDescription": "Process name in the process list" }, + "name_prefix": { + "type": "string" + }, + "install_url": { + "type": "string" + }, "cwd": { "type": "string", "docDefault": "CWD of the current environment (from your shell)", @@ -70,6 +76,9 @@ "type": "string", "docDescription": "Format for log timestamps in moment.js format (eg YYYY-MM-DD HH:mm Z)" }, + "time": { + "type": "boolean" + }, "env": { "type": [ "object", @@ -105,6 +114,11 @@ "docDefault": 0, "docDescription": "Time in ms to wait before restarting a crashing app" }, + "exp_backoff_restart_delay": { + "type": "number", + "docDefault": 0, + "docDescription": "Restart Time in ms to wait before restarting a crashing app" + }, "source_map_support": { "type": "boolean", "docDefault": true, @@ -157,6 +171,11 @@ "docDefault": "True", "docDescription": "Enable or disable auto restart after process failure" }, + "watch_delay": { + "type": "number", + "docDefault": "True", + "docDescription": "Restart delay on file change detected" + }, "watch": { "type": [ "boolean", @@ -195,10 +214,12 @@ "docDefault": 16, "docDescription": "Number of times a script is restarted when it exits in less than min_uptime" }, + "execute_command": { + "type": "boolean" + }, "exec_mode": { "type": "string", "regex": "^(cluster|fork)(_mode)?$", - "alias": "executeCommand", "desc": "it should be \"cluster\"(\"cluster_mode\") or \"fork\"(\"fork_mode\") only", "docDefault": "fork", "docDescription": "Set the execution mode, possible values: fork|cluster" @@ -283,12 +304,13 @@ "docDescription": "Current user that started the process" }, "uid": { - "type" : "string", + "type" : "number", + "alias": "user", "docDefault": "Current user uid", "docDescription": "Set user id" }, "gid": { - "type" : "string", + "type" : "number", "docDefault": "Current user gid", "docDescription": "Set group id" }, diff --git a/lib/Client.js b/lib/Client.js index e7c598ffc..3f17c7984 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -625,56 +625,46 @@ Client.prototype.stopWatch = function stopWatch(method, env, fn) { }); }; -Client.prototype.getAllModulesId = function(cb) { +Client.prototype.getAllProcess = function(cb) { var found_proc = []; - this.executeRemote('getMonitorData', {}, function(err, list) { + this.executeRemote('getMonitorData', {}, function(err, procs) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } - list.forEach(function(proc) { - if (proc.pm2_env.pmx_module) - found_proc.push(proc.pm_id); - }); - - return cb(null, found_proc); + return cb(null, procs); }); }; -Client.prototype.getAllProcess = function(cb) { +Client.prototype.getAllProcessId = function(cb) { var found_proc = []; - this.executeRemote('getMonitorData', {}, function(err, list) { + this.executeRemote('getMonitorData', {}, function(err, procs) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } - list.forEach(function(proc) { - found_proc.push(proc); - }); - - return cb(null, found_proc); + return cb(null, procs.map(proc => proc.pm_id)); }); }; -Client.prototype.getAllProcessId = function(cb) { +Client.prototype.getAllProcessIdWithoutModules = function(cb) { var found_proc = []; - this.executeRemote('getMonitorData', {}, function(err, list) { + this.executeRemote('getMonitorData', {}, function(err, procs) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } - list.forEach(function(proc) { - if (!proc.pm2_env.pmx_module) - found_proc.push(proc.pm_id); - }); + var proc_ids = procs + .filter(proc => !proc.pm2_env.pmx_module) + .map(proc => proc.pm_id) - return cb(null, found_proc); + return cb(null, proc_ids); }); }; @@ -697,8 +687,7 @@ Client.prototype.getProcessIdByName = function(name, force_all, cb) { } list.forEach(function(proc) { - if ((proc.pm2_env.name == name || proc.pm2_env.pm_exec_path == path.resolve(name)) && - !(proc.pm2_env.pmx_module && !force_all)) { + if (proc.pm2_env.name == name || proc.pm2_env.pm_exec_path == path.resolve(name)) { found_proc.push(proc.pm_id); full_details[proc.pm_id] = proc; } diff --git a/lib/Common.js b/lib/Common.js index e3ddbf222..cfb4122df 100644 --- a/lib/Common.js +++ b/lib/Common.js @@ -50,21 +50,6 @@ function resolveHome(filepath) { return filepath; } -function resolveCWD(custom_path, default_path) { - var target_cwd; - - if (custom_path) { - if (custom_path[0] == '/') - target_cwd = custom_path; - else - target_cwd = path.join(default_path, custom_path); - } - else - target_cwd = default_path; - - return target_cwd; -} - Common.lockReload = function() { try { var t1 = fs.readFileSync(cst.PM2_RELOAD_LOCKFILE).toString(); @@ -110,24 +95,6 @@ Common.prepareAppConf = function(opts, app) { if (!app.script) return new Error('No script path - aborting'); - if (app.automation == false) - app.pmx = false; - - if (!app.node_args) - app.node_args = []; - - if (app.port && app.env) - app.env.PORT = app.port; - - // CRON - var ret; - if ((ret = Common.sink.determineCron(app)) instanceof Error) - return ret; - - // Resolve paths - // app.pm_exec_path = path.join(resolveCWD(app.cwd, opts.cwd), app.script); - // app.pm_cwd = path.dirname(app.pm_exec_path); - // console.log(' hads', app.pm_cwd); var cwd = null; if (app.cwd) { @@ -142,7 +109,6 @@ Common.prepareAppConf = function(opts, app) { // Full path script resolution app.pm_exec_path = path.resolve(cwd, app.script); - // If script does not exist after resolution if (!fs.existsSync(app.pm_exec_path)) { var ckd; @@ -267,20 +233,18 @@ Common.parseConfig = function(confObj, filename) { var yamljs = require('yamljs'); var vm = require('vm'); - if (!filename || filename == 'pipe' || filename == 'none' || + if (!filename || + filename == 'pipe' || + filename == 'none' || filename.indexOf('.json') > -1) { var code = '(' + confObj + ')'; var sandbox = {}; - if (semver.satisfies(process.version, '>= 0.12.0')) { - return vm.runInThisContext(code, sandbox, { - filename: path.resolve(filename), - displayErrors: false, - timeout: 1000 - }); - } else { - // Use the Node 0.10 API - return vm.runInNewContext(code, sandbox, filename); - } + + return vm.runInThisContext(code, sandbox, { + filename: path.resolve(filename), + displayErrors: false, + timeout: 1000 + }); } else if (filename.indexOf('.yml') > -1 || filename.indexOf('.yaml') > -1) { @@ -340,7 +304,7 @@ Common.sink.determineExecMode = function(app) { }; var resolveNodeInterpreter = function(app) { - if (app.exec_mode.indexOf('cluster') > -1) { + if (app.exec_mode && app.exec_mode.indexOf('cluster') > -1) { Common.printError(cst.PREFIX_MSG_WARNING + chalk.bold.yellow('Choosing the Node.js version in cluster mode is not supported')); return false; } @@ -362,7 +326,6 @@ var resolveNodeInterpreter = function(app) { ? '/versions/node/v' + node_version + '/bin/node' : '/v' + node_version + '/bin/node'; var nvm_node_path = path.join(nvm_path, path_to_node); - try { fs.accessSync(nvm_node_path); } catch(e) { @@ -408,9 +371,8 @@ Common.sink.resolveInterpreter = function(app) { else if (app.exec_interpreter.indexOf('node@') > -1) resolveNodeInterpreter(app); - if (app.exec_interpreter.indexOf('python') > -1) { + if (app.exec_interpreter.indexOf('python') > -1) app.env.PYTHONUNBUFFERED = '1' - } /** * Specific installed JS transpilers @@ -438,12 +400,20 @@ Common.deepCopy = Common.serialize = Common.clone = function(obj) { return fclone(obj); }; -/** - * Description - * @method printError - * @param {} msg - * @return CallExpression - */ +Common.errMod = function(msg) { + if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; + if (msg instanceof Error) + return console.error(msg.message); + return console.error(`${cst.PREFIX_MSG_MOD_ERR}${msg}`); +} + +Common.err = function(msg) { + if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; + if (msg instanceof Error) + return console.error(msg.message); + return console.error(`${cst.PREFIX_MSG_ERR}${msg}`); +} + Common.printError = function(msg) { if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; if (msg instanceof Error) @@ -451,11 +421,16 @@ Common.printError = function(msg) { return console.error.apply(console, arguments); }; -/** - * Description - * @method printOut - * @return - */ +Common.log = function(msg) { + if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; + return console.log(`${cst.PREFIX_MSG}${msg}`); +} + +Common.logMod = function(msg) { + if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; + return console.log(`${cst.PREFIX_MSG_MOD}${msg}`); +} + Common.printOut = function() { if (process.env.PM2_SILENT === 'true' || process.env.PM2_PROGRAMMATIC === 'true') return false; return console.log.apply(console, arguments); @@ -583,15 +558,14 @@ Common.resolveAppAttributes = function(opts, legacy_app) { return app; } - /** * Verify configurations * Called on EVERY Operation (start/restart/reload/stop...) * @param {Array} appConfs * @returns {Array} */ -Common.verifyConfs = function(appConfs){ - if (!appConfs || appConfs.length == 0){ +Common.verifyConfs = function(appConfs) { + if (!appConfs || appConfs.length == 0) { return []; } @@ -614,11 +588,24 @@ Common.verifyConfs = function(appConfs){ delete app.command } - app.username = Common.getCurrentUsername(); + if (!app.env) { + app.env = {} + } - // If command is like pm2 start "python xx.py --ok" - // Then automatically start the script with bash -c and set a name eq to command + // Render an app name if not existing. + Common.renderApplicationName(app); + if (app.execute_command == true) { + app.exec_mode = 'fork' + delete app.execute_command + } + + app.username = Common.getCurrentUsername(); + + /** + * If command is like pm2 start "python xx.py --ok" + * Then automatically start the script with bash -c and set a name eq to command + */ if (app.script && app.script.indexOf(' ') > -1 && app.script === path.basename(app.script)) { var _script = app.script; if (require('shelljs').which('bash')) @@ -634,48 +621,105 @@ Common.verifyConfs = function(appConfs){ } } - if ((app.uid || app.gid) && app.force !== true) { - if (process.getuid && process.getuid() !== 0) { + /** + * Add log_date_format by default + */ + if (app.time) { + app.log_date_format = 'YYYY-MM-DDTHH:mm:ss' + } + + /** + * Checks + Resolve UID/GID + * comes from pm2 --uid <> --gid <> or --user + */ + if ((app.uid || app.gid || app.user) && app.force !== true) { + // 1/ Check if windows + if (cst.IS_WINDOWS === true) { + Common.printError(cst.PREFIX_MSG_ERR + '--uid and --git does not works on windows'); + return new Error('--uid and --git does not works on windows'); + } + + // 2/ Verify that user is root (todo: verify if other has right) + if (process.env.NODE_ENV != 'test' && process.getuid && process.getuid() !== 0) { Common.printError(cst.PREFIX_MSG_ERR + 'To use --uid and --gid please run pm2 as root'); return new Error('To use UID and GID please run PM2 as root'); } + + // 3/ Resolve user info via /etc/password + var passwd = require('./tools/passwd.js') + var users + try { + users = passwd.getUsers() + } catch(e) { + Common.printError(e); + return new Error(e); + } + + var user_info = users[app.uid || app.user] + if (!user_info) { + Common.printError(`${cst.PREFIX_MSG_ERR} User ${app.uid || app.user} cannot be found`); + return new Error(`${cst.PREFIX_MSG_ERR} User ${app.uid || app.user} cannot be found`); + } + + app.env.HOME = user_info.homedir + app.uid = parseInt(user_info.userId) + app.gid = parseInt(user_info.groupId) } - // Specific options of PM2.io - if (process.env.PM2_DEEP_MONITORING) + /** + * Specific options of PM2.io + */ + if (process.env.PM2_DEEP_MONITORING) { app.deep_monitoring = true; + } + + if (app.automation == false) { + app.pmx = false; + } if (app.disable_trace) { app.trace = false delete app.disable_trace; } - if (app.instances == 'max') + /** + * Instances params + */ + if (app.instances == 'max') { app.instances = 0; - // Sanity check, default to number of cores if value can't be parsed - if (typeof(app.instances) === 'string') - app.instances = parseInt(app.instances) || 0; + } - // Check Exec mode - checkExecMode(app); + if (typeof(app.instances) === 'string') { + app.instances = parseInt(app.instances) || 0; + } if (app.exec_mode != 'cluster_mode' && - !app.instances && typeof(app.merge_logs) == 'undefined') + !app.instances && + typeof(app.merge_logs) == 'undefined') { app.merge_logs = true; + } - // Render an app name if not existing. - prepareAppName(app); + if (!app.node_args) { + app.node_args = []; + } - var ret = Config.validateJSON(app); + if (app.port && app.env) { + app.env.PORT = app.port; + } - // Show errors if existing. + var ret; + if ((ret = Common.sink.determineCron(app)) instanceof Error) + return ret; + + /** + * Now validation configuration + */ + var ret = Config.validateJSON(app); if (ret.errors && ret.errors.length > 0){ - ret.errors.forEach(function(err){ - warn(err); - }); - // Return null == error + ret.errors.forEach(function(err) { warn(err) }); return new Error(ret.errors); } + verifiedConf.push(ret.config); } @@ -707,27 +751,11 @@ Common.getCurrentUsername = function(){ return current_user; } -/** - * Check if right Node.js version for cluster mode - * @param {Object} conf - */ -function checkExecMode(app) { - if (app.exec_mode === 'cluster' || - app.exec_mode === 'cluster_mode' || - app.instances && app.exec_mode === undefined) - app.exec_mode = 'cluster_mode'; - else - app.exec_mode = 'fork_mode'; - - if (app.instances && app.exec_mode === undefined) - app.exec_mode = 'cluster_mode'; -} - /** * Render an app name if not existing. * @param {Object} conf */ -function prepareAppName(conf){ +Common.renderApplicationName = function(conf){ if (!conf.name && conf.script){ conf.name = conf.script !== undefined ? path.basename(conf.script) : 'undefined'; var lastDot = conf.name.lastIndexOf('.'); @@ -737,7 +765,6 @@ function prepareAppName(conf){ } } - /** * Show warnings * @param {String} warning diff --git a/lib/God.js b/lib/God.js index 60b53fbc7..4d1633e2f 100644 --- a/lib/God.js +++ b/lib/God.js @@ -147,7 +147,7 @@ God.executeApp = function executeApp(env, cb) { God.notify('online', proc); proc.pm2_env.status = cst.ONLINE_STATUS; - console.log('App name:%s id:%s online', proc.pm2_env.name, proc.pm2_env.pm_id); + console.log(`App [${proc.pm2_env.name}:${proc.pm2_env.pm_id}] online`); if (cb) cb(null, proc); } @@ -281,7 +281,7 @@ God.executeApp = function executeApp(env, cb) { * @return */ God.handleExit = function handleExit(clu, exit_code, kill_signal) { - console.log('App [%s] with id [%s] and pid [%s], exited with code [%s] via signal [%s]', clu.pm2_env.name, clu.pm2_env.pm_id, clu.process.pid, exit_code, kill_signal || 'SIGINT'); + console.log(`App [${clu.pm2_env.name}:${clu.pm2_env.pm_id}] exited with code [${exit_code}] via signal [${kill_signal || 'SIGINT'}]`) var proc = this.clusters_db[clu.pm2_env.pm_id]; @@ -361,10 +361,27 @@ God.handleExit = function handleExit(clu, exit_code, kill_signal) { } var restart_delay = 0; - if (proc.pm2_env.restart_delay !== undefined && !isNaN(parseInt(proc.pm2_env.restart_delay))) { + + if (proc.pm2_env.restart_delay !== undefined && + !isNaN(parseInt(proc.pm2_env.restart_delay))) { + proc.pm2_env.status = cst.WAITING_RESTART; restart_delay = parseInt(proc.pm2_env.restart_delay); } + if (proc.pm2_env.exp_backoff_restart_delay !== undefined && + !isNaN(parseInt(proc.pm2_env.exp_backoff_restart_delay))) { + proc.pm2_env.status = cst.WAITING_RESTART; + if (!proc.pm2_env.prev_restart_delay) { + proc.pm2_env.prev_restart_delay = proc.pm2_env.exp_backoff_restart_delay + restart_delay = proc.pm2_env.exp_backoff_restart_delay + } + else { + proc.pm2_env.prev_restart_delay = Math.floor(Math.min(15000, proc.pm2_env.prev_restart_delay * 1.5)) + restart_delay = proc.pm2_env.prev_restart_delay + } + console.log(`App [${clu.pm2_env.name}:${clu.pm2_env.pm_id}] will restart in ${restart_delay}ms`) + } + if (!stopping && !overlimit) { //make this property unenumerable Object.defineProperty(proc.pm2_env, 'restart_task', {configurable: true, writable: true}); @@ -436,12 +453,14 @@ God.finalizeProcedure = function finalizeProcedure(proc) { var current_path = proc.pm2_env.cwd || path.dirname(proc.pm2_env.pm_exec_path); var proc_id = proc.pm2_env.pm_id; + proc.pm2_env.version = Utility.findPackageVersion(proc.pm2_env.pm_exec_path || proc.pm2_env.cwd); + if (proc.pm2_env.vizion_running === true) { debug('Vizion is already running for proc id: %d, skipping this round', proc_id); return God.notify('online', proc); } - proc.pm2_env.vizion_running = true; + vizion.analyze({folder : current_path}, function recur_path(err, meta){ var proc = God.clusters_db[proc_id]; diff --git a/lib/God/ClusterMode.js b/lib/God/ClusterMode.js index 677f08708..e301daf2e 100644 --- a/lib/God/ClusterMode.js +++ b/lib/God/ClusterMode.js @@ -33,10 +33,7 @@ module.exports = function ClusterMode(God) { God.nodeApp = function nodeApp(env_copy, cb){ var clu = null; - console.log('Starting execution sequence in -cluster mode- for app name:%s id:%s', - env_copy.name, - env_copy.pm_id); - + console.log(`App [${env_copy.name}:${env_copy.pm_id}] starting in -cluster mode-`) if (env_copy.node_args && Array.isArray(env_copy.node_args)) { cluster.settings.execArgv = env_copy.node_args; } diff --git a/lib/God/ForkMode.js b/lib/God/ForkMode.js index 1bddafb12..fb5a1f179 100644 --- a/lib/God/ForkMode.js +++ b/lib/God/ForkMode.js @@ -12,9 +12,9 @@ */ var log = require('debug')('pm2:fork_mode'); var fs = require('fs'); -var moment = require('moment'); var Utility = require('../Utility.js'); var path = require('path'); +var dateFns = require('date-fns') /** * Description @@ -35,9 +35,7 @@ module.exports = function ForkMode(God) { var command = ''; var args = []; - console.log('Starting execution sequence in -fork mode- for app name:%s id:%s', - pm2_env.name, - pm2_env.pm_id); + console.log(`App [${pm2_env.name}:${pm2_env.pm_id}] starting in -fork mode-`) var spawn = require('child_process').spawn; var interpreter = pm2_env.exec_interpreter || 'node'; @@ -89,20 +87,29 @@ module.exports = function ForkMode(God) { return cb(err); }; - var windowsHide; - if (typeof(pm2_env.windowsHide) === "boolean") { - windowsHide = pm2_env.windowsHide; - } else { - windowsHide = true; - } try { - var cspr = spawn(command, args, { + var options = { env : pm2_env, detached : true, - windowsHide: windowsHide, cwd : pm2_env.pm_cwd || process.cwd(), stdio : ['pipe', 'pipe', 'pipe', 'ipc'] //Same as fork() in node core - }); + } + + if (typeof(pm2_env.windowsHide) === "boolean") { + options.windowsHide = pm2_env.windowsHide; + } else { + options.windowsHide = true; + } + + if (pm2_env.uid) { + options.uid = pm2_env.uid + } + + if (pm2_env.gid) { + options.gid = pm2_env.gid + } + + var cspr = spawn(command, args, options); } catch(e) { God.logAndGenerateError(e); return cb(e); @@ -112,23 +119,36 @@ module.exports = function ForkMode(God) { cspr.process.pid = cspr.pid; cspr.pm2_env = pm2_env; + function transformLogToJson(pm2_env, type, data) { + return JSON.stringify({ + message : data.toString(), + timestamp : pm2_env.log_date_format ? dateFns.format(Date.now(), pm2_env.log_date_format) : new Date().toISOString(), + type : type, + process_id : cspr.pm2_env.pm_id, + app_name : cspr.pm2_env.name + }) + '\n' + } + + function prefixLogWithDate(pm2_env, data) { + var log_data = [] + log_data = data.toString().split('\n') + if (log_data.length > 1) + log_data.pop() + log_data = log_data.map(line => `${dateFns.format(Date.now(), pm2_env.log_date_format)}: ${line}\n`) + log_data = log_data.join('') + return log_data + } + cspr.stderr.on('data', function forkErrData(data) { var log_data = null; - if (pm2_env.disable_logs === true) - return false; + // via --out /dev/null --err /dev/null + if (pm2_env.disable_logs === true) return false; - if (pm2_env.log_type && pm2_env.log_type === 'json') { - log_data = JSON.stringify({ - message : data.toString(), - timestamp : pm2_env.log_date_format ? moment().format(pm2_env.log_date_format) : new Date().toISOString(), - type : 'err', - process_id : cspr.pm2_env.pm_id, - app_name : cspr.pm2_env.name - }) + '\n'; - } + if (pm2_env.log_type && pm2_env.log_type === 'json') + log_data = transformLogToJson(pm2_env, 'err', data) else if (pm2_env.log_date_format) - log_data = moment().format(pm2_env.log_date_format) + ': ' + data.toString(); + log_data = prefixLogWithDate(pm2_env, data) else log_data = data.toString(); @@ -157,19 +177,12 @@ module.exports = function ForkMode(God) { if (pm2_env.disable_logs === true) return false; - if (pm2_env.log_type && pm2_env.log_type === 'json') { - log_data = JSON.stringify({ - message : data.toString(), - timestamp : pm2_env.log_date_format ? moment().format(pm2_env.log_date_format) : new Date().toISOString(), - type : 'out', - process_id : cspr.pm2_env.pm_id, - app_name : cspr.pm2_env.name - }) + '\n'; - } + if (pm2_env.log_type && pm2_env.log_type === 'json') + log_data = transformLogToJson(pm2_env, 'out', data) else if (pm2_env.log_date_format) - log_data = moment().format(pm2_env.log_date_format) + ': ' + data.toString(); + log_data = prefixLogWithDate(pm2_env, data) else - log_data = data.toString(); + log_data = data.toString() God.bus.emit('log:out', { process : { diff --git a/lib/God/Methods.js b/lib/God/Methods.js index 73e0c589c..04534ba27 100644 --- a/lib/God/Methods.js +++ b/lib/God/Methods.js @@ -243,6 +243,7 @@ module.exports = function(God) { God.resetState = function(pm2_env) { pm2_env.created_at = Date.now(); pm2_env.unstable_restarts = 0; + pm2_env.prev_restart_delay = 0; }; }; diff --git a/lib/ProcessContainer.js b/lib/ProcessContainer.js index 0d739bac9..318959a25 100644 --- a/lib/ProcessContainer.js +++ b/lib/ProcessContainer.js @@ -177,10 +177,10 @@ function exec(script, stds) { } }); - var moment = null; + var dateFns = null; if (pm2_env.log_date_format) - moment = require('moment'); + dateFns = require('date-fns'); Utility.startLogging(stds, function (err) { if (err) { @@ -207,15 +207,15 @@ function exec(script, stds) { if (pm2_env.log_type && pm2_env.log_type === 'json') { log_data = JSON.stringify({ message : string.toString(), - timestamp : pm2_env.log_date_format && moment ? - moment().format(pm2_env.log_date_format) : new Date().toISOString(), + timestamp : pm2_env.log_date_format && dateFns ? + dateFns.format(Date.now(), pm2_env.log_date_format) : new Date().toISOString(), type : 'err', process_id : pm2_env.pm_id, app_name : pm2_env.name }) + '\n'; } - else if (pm2_env.log_date_format && moment) - log_data = moment().format(pm2_env.log_date_format) + ': ' + string.toString(); + else if (pm2_env.log_date_format && dateFns) + log_data = `${dateFns.format(Date.now(), pm2_env.log_date_format)}: ${string.toString()}`; else log_data = string.toString(); @@ -245,15 +245,15 @@ function exec(script, stds) { if (pm2_env.log_type && pm2_env.log_type === 'json') { log_data = JSON.stringify({ message : string.toString(), - timestamp : pm2_env.log_date_format && moment ? - moment().format(pm2_env.log_date_format) : new Date().toISOString(), + timestamp : pm2_env.log_date_format && dateFns ? + dateFns.format(Date.now(), pm2_env.log_date_format) : new Date().toISOString(), type : 'out', process_id : pm2_env.pm_id, app_name : pm2_env.name }) + '\n'; } - else if (pm2_env.log_date_format && moment) - log_data = moment().format(pm2_env.log_date_format) + ': ' + string.toString(); + else if (pm2_env.log_date_format && dateFns) + log_data = `${dateFns.format(Date.now(), pm2_env.log_date_format)}: ${string.toString()}`; else log_data = string.toString(); diff --git a/lib/ProcessContainerFork.js b/lib/ProcessContainerFork.js index 59c45d3fb..e17704003 100644 --- a/lib/ProcessContainerFork.js +++ b/lib/ProcessContainerFork.js @@ -43,33 +43,6 @@ if (process.connected && 'node_version': process.versions.node }); -// uid/gid management -if (process.env.uid || process.env.gid) { - - if (typeof(process.env.uid) === 'string') { - process.env.HOME = '/home/' + process.env.uid; - process.env.USER = process.env.uid; - } - - try { - if (process.env.gid) - process.setgid(process.env.gid); - if (process.env.uid){ - // If no gid specified - set gid to uid - var new_gid = process.env.gid == null ? process.env.uid : process.env.gid; - process.initgroups(process.env.uid, new_gid); - process.setgid(new_gid); - process.setuid(process.env.uid); - } - } catch(e) { - setTimeout(function() { - console.error('%s on call %s', e.message, e.syscall); - console.error('%s is not accessible', process.env.uid); - return process.exit(1); - }, 100); - } -} - // Require the real application if (process.env.pm_exec_path) require('module')._load(process.env.pm_exec_path, null, true); diff --git a/lib/Utility.js b/lib/Utility.js index b3be4e196..87deff78a 100644 --- a/lib/Utility.js +++ b/lib/Utility.js @@ -14,8 +14,20 @@ var cst = require('../constants.js'); var waterfall = require('async/waterfall'); var util = require('util'); var url = require('url'); +var dateFns = require('date-fns') +var findPackageJson = require('./tools/find-package-json') var Utility = module.exports = { + findPackageVersion : function(fullpath) { + var version + + try { + version = findPackageJson(fullpath).next().value.version + } catch(e) { + version = 'N/A' + } + return version + }, getDate : function() { return Date.now(); }, @@ -78,12 +90,10 @@ var Utility = module.exports = { return fclone(obj); }, overrideConsole : function(bus) { - if (cst.PM2_LOG_DATE_FORMAT && typeof cst.PM2_LOG_DATE_FORMAT == 'string'){ - var moment = require('moment'); - + if (cst.PM2_LOG_DATE_FORMAT && typeof cst.PM2_LOG_DATE_FORMAT == 'string') { // Generate timestamp prefix function timestamp(){ - return '[' + moment().format(cst.PM2_LOG_DATE_FORMAT) + ']'; + return `${dateFns.format(Date.now(), cst.PM2_LOG_DATE_FORMAT)}:`; } var hacks = ['info', 'log', 'error', 'warn'], consoled = {}; diff --git a/lib/Worker.js b/lib/Worker.js index 60f9597ca..64b093cbc 100644 --- a/lib/Worker.js +++ b/lib/Worker.js @@ -47,6 +47,7 @@ module.exports = function(God) { } }; + // Deprecated var versioningRefresh = function(proc_key, cb) { var proc = _getProcessById(proc_key.pm2_env.pm_id); if (!(proc && @@ -104,18 +105,26 @@ module.exports = function(God) { return console.error(err); } - eachLimit(data, 1, function(proc_key, next) { - if (!proc_key || - !proc_key.pm2_env || - proc_key.pm2_env.pm_id === undefined) + eachLimit(data, 1, function(proc, next) { + if (!proc || !proc.pm2_env || proc.pm2_env.pm_id === undefined) return next(); - debug('[PM2][WORKER] Processing proc id:', proc_key.pm2_env.pm_id); - - versioningRefresh(proc_key, function() { - maxMemoryRestart(proc_key, function() { - return next(); - }); + debug('[PM2][WORKER] Processing proc id:', proc.pm2_env.pm_id); + + // Reset restart delay if application has an uptime of more > 30secs + if (proc.pm2_env.exp_backoff_restart_delay !== undefined && + proc.pm2_env.prev_restart_delay && proc.pm2_env.prev_restart_delay > 0) { + var app_uptime = Date.now() - proc.pm2_env.pm_uptime + if (app_uptime > cst.EXP_BACKOFF_RESET_TIMER) { + var ref_proc = _getProcessById(proc.pm2_env.pm_id); + ref_proc.pm2_env.prev_restart_delay = 0 + console.log(`[PM2][WORKER] Reset the restart delay, as app ${proc.name} is up for more than ${cst.EXP_BACKOFF_RESET_TIMER}`) + } + } + + // Check if application has reached memory threshold + maxMemoryRestart(proc, function() { + return next(); }); }, function(err) { God.Worker.is_running = false; diff --git a/lib/tools/Config.js b/lib/tools/Config.js index 4059e1015..08869b854 100644 --- a/lib/tools/Config.js +++ b/lib/tools/Config.js @@ -56,21 +56,21 @@ var Config = module.exports = { }; /** - * Transform commander options to app config. - * @param {Commander} cmd - * @returns {{}} + * Filter / Alias options */ -Config.transCMDToConf = function(cmd){ - var conf = {}, defines = this.schema; - // Wrap. - for(var k in defines){ - var aliases = defines[k].alias; +Config.filterOptions = function(cmd) { + var conf = {}; + var schema = this.schema; + + for (var key in schema) { + var aliases = schema[key].alias; aliases && aliases.forEach(function(alias){ - //if (cmd[alias]) { - conf[k] || (conf[k] = cmd[alias]); - //} + if (typeof(cmd[alias]) !== 'undefined') { + conf[key] || (conf[key] = cmd[alias]); + } }); } + return conf; }; diff --git a/lib/tools/find-package-json.js b/lib/tools/find-package-json.js new file mode 100644 index 000000000..04f9f6281 --- /dev/null +++ b/lib/tools/find-package-json.js @@ -0,0 +1,74 @@ +'use strict'; + +var path = require('path') + , fs = require('fs'); + +/** + * Attempt to somewhat safely parse the JSON. + * + * @param {String} data JSON blob that needs to be parsed. + * @returns {Object|false} Parsed JSON or false. + * @api private + */ +function parse(data) { + data = data.toString('utf-8'); + + // + // Remove a possible UTF-8 BOM (byte order marker) as this can lead to parse + // values when passed in to the JSON.parse. + // + if (data.charCodeAt(0) === 0xFEFF) data = data.slice(1); + + try { return JSON.parse(data); } + catch (e) { return false; } +} + +/** + * Find package.json files. + * + * @param {String|Object} root The root directory we should start searching in. + * @returns {Object} Iterator interface. + * @api public + */ +module.exports = function find(root) { + root = root || process.cwd(); + if (typeof root !== "string") { + if (typeof root === "object" && typeof root.filename === 'string') { + root = root.filename; + } else { + throw new Error("Must pass a filename string or a module object to finder"); + } + } + return { + /** + * Return the parsed package.json that we find in a parent folder. + * + * @returns {Object} Value, filename and indication if the iteration is done. + * @api public + */ + next: function next() { + if (root.match(/^(\w:\\|\/)$/)) return { + value: undefined, + filename: undefined, + done: true + }; + + var file = path.join(root, 'package.json') + , data; + + root = path.resolve(root, '..'); + + if (fs.existsSync(file) && (data = parse(fs.readFileSync(file)))) { + data.__path = file; + + return { + value: data, + filename: file, + done: false + }; + } + + return next(); + } + }; +}; diff --git a/lib/tools/passwd.js b/lib/tools/passwd.js new file mode 100644 index 000000000..7c16e337c --- /dev/null +++ b/lib/tools/passwd.js @@ -0,0 +1,57 @@ + +var fs = require('fs') + +var getUsers = function() { + return fs.readFileSync('/etc/passwd') + .toString() + .split('\n') + .filter(function (user) { + return user.length && user[0] != '#'; + }) + .reduce(function(map, user) { + var fields = user.split(':'); + + map[fields[0]] = { + username : fields[0], + password : fields[1], + userId : fields[2], + groupId : fields[3], + name : fields[4], + homedir : fields[5], + shell : fields[6] + }; + + return map + }, {}) +} + +var getGroups = function(cb) { + var groups + + try { + groups = fs.readFileSync('/etc/group') + } catch(e) { + return e + } + + return groups + .toString() + .split('\n') + .filter(function (group) { + return group.length && group[0] != '#'; + }) + .map(function (group) { + var fields = group.split(':'); + return { + name : fields[0], + password : fields[1], + id : fields[2], + members : fields[3].split(',') + }; + }) +} + +module.exports = { + getUsers, + getGroups +} diff --git a/package.json b/package.json index eb2af292d..88743d6ff 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pm2", "preferGlobal": true, - "version": "3.1.3", + "version": "3.2.0", "engines": { "node": ">=4.0.0" }, @@ -91,7 +91,7 @@ "main": "index.js", "types": "types/index.d.ts", "scripts": { - "test": "bash test/e2e.sh && bash test/unit.sh" + "test": "bash test/unit.sh && bash test/e2e.sh" }, "keywords": [ "cli", @@ -159,7 +159,7 @@ }, "dependencies": { "@pm2/agent": "^0.5.11", - "@pm2/io": "~2.3.11", + "@pm2/io": "~2.4.2", "@pm2/js-api": "^0.5.15", "async": "^2.6.1", "blessed": "^0.1.81", @@ -168,6 +168,7 @@ "cli-table-redemption": "^1.0.0", "commander": "2.15.1", "cron": "^1.3", + "date-fns": "^1.29.0", "debug": "^3.1", "eventemitter2": "5.0.1", "fclone": "1.0.11", diff --git a/test/e2e.sh b/test/e2e.sh index 1b73924a3..bc79bd895 100644 --- a/test/e2e.sh +++ b/test/e2e.sh @@ -7,14 +7,6 @@ source "${SRC}/e2e/include.sh" set -e set -o verbose -# MODULES -bash ./test/e2e/modules/get-set.sh -spec "Configuration system working" -bash ./test/e2e/modules/module.sh -spec "module system" -bash ./test/e2e/modules/module-safeguard.sh -spec "module safeguard system (--safe)" - # CLI bash ./test/e2e/cli/reload.sh spec "Reload" @@ -36,6 +28,8 @@ bash ./test/e2e/cli/env-refresh.sh spec "Environment refresh on restart" bash ./test/e2e/cli/extra-lang.sh spec "Various programming languages checks (Python, PHP)" +bash ./test/e2e/cli/python-support.sh +spec "Python support checks" bash ./test/e2e/cli/multiparam.sh spec "Multiparam process management" bash ./test/e2e/cli/smart-start.sh @@ -62,8 +56,6 @@ bash ./test/e2e/cli/watch.sh spec "watch system tests" bash ./test/e2e/cli/right-exit-code.sh spec "Verification exit code" -bash ./test/e2e/cli/harmony.sh -spec "Harmony test" bash ./test/e2e/cli/fork.sh spec "Fork system working" bash ./test/e2e/cli/piped-config.sh @@ -141,4 +133,15 @@ spec "Logging path set to null" bash ./test/e2e/logs/log-json.sh spec "Logging directly to file in json" +# MODULES +bash ./test/e2e/modules/get-set.sh +spec "Configuration system working" +bash ./test/e2e/modules/module.sh +spec "module system" +bash ./test/e2e/modules/module-safeguard.sh +spec "module safeguard system (--safe)" + $pm2 kill + +# cat ~/.pm2/pm2.log | grep "PM2 global error caught" +# spec "PM2 Daemon should not have thrown any global error" diff --git a/test/e2e/cli/cli-actions-2.sh b/test/e2e/cli/cli-actions-2.sh index 46ef2c494..efc330c68 100644 --- a/test/e2e/cli/cli-actions-2.sh +++ b/test/e2e/cli/cli-actions-2.sh @@ -114,11 +114,12 @@ should 'should app be stopped' 'stopped' 1 $pm2 delete 455 should 'should has been deleted process by id' "name: '455'" 0 +$pm2 kill ########### OPTIONS OUTPUT FILES $pm2 delete all -$pm2 start echo.js -o outech.log -e errech.log --name gmail -i 1 -sleep 1 +$pm2 start echo.js -o outech.log -e errech.log --name gmail -i 2 +sleep 2 cat outech-0.log > /dev/null spec "file outech-0.log exist" cat errech-0.log > /dev/null diff --git a/test/e2e/cli/harmony.sh b/test/e2e/cli/harmony.sh deleted file mode 100644 index bba81985d..000000000 --- a/test/e2e/cli/harmony.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -SRC=$(cd $(dirname "$0"); pwd) -source "${SRC}/../include.sh" - -cd $file_path - -echo "################ HARMONY ES6" - -$pm2 start harmony.js --node-args="--harmony" -sleep 2 -$pm2 list -should 'should not fail when passing node-args=harmony opts in CLUSTERMODE' 'restart_time: 0' 1 -$pm2 delete all - -echo "################ HARMONY / NODEARGS ES6 FORK MODE" - -$pm2 start harmony.js --node-args="--harmony" -x -sleep 2 -$pm2 list -should 'should not fail when passing node-args=harmony opts in FORKMODE' 'restart_time: 0' 1 -$pm2 delete all - -echo "################## NODE ARGS VIA JSON" - -$pm2 start harmony.json -sleep 2 -$pm2 list -should 'should not fail when passing harmony option to V8 via node_args in JSON files' 'restart_time: 0' 1 - -$pm2 delete all diff --git a/test/e2e/cli/python-support.sh b/test/e2e/cli/python-support.sh new file mode 100644 index 000000000..edaaaa602 --- /dev/null +++ b/test/e2e/cli/python-support.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +SRC=$(cd $(dirname "$0"); pwd) +source "${SRC}/../include.sh" + +cd $file_path/extra-lang + +which python +spec "should have python installed" + +# +# Config file +# + +$pm2 start app-python.config.js --only 'echo-python-1' +should 'should mode be fork' 'fork_mode' 1 +should 'should have started 1 apps' 'online' 1 + +$pm2 delete all + +# Check with multi instances +$pm2 start app-python.config.js --only 'echo-python-max' +should 'should mode be fork' 'fork_mode' 4 +should 'should have started 4 apps' 'online' 4 + +# Should keep same params on restart +$pm2 restart all +should 'should have restarted processes' 'restart_time: 1' 4 +should 'should mode be fork' 'fork_mode' 4 + +$pm2 delete all + +# +# CLI +# + +$pm2 start echo.py +should 'should mode be fork' 'fork_mode' 1 +should 'should have started 1 apps' 'online' 1 + +$pm2 delete all + +$pm2 start echo.py -i 4 +should 'should mode be fork' 'fork_mode' 4 +should 'should have started 4 apps' 'online' 4 + +$pm2 restart all +should 'should have restarted processes' 'restart_time: 1' 4 +should 'should mode be fork' 'fork_mode' 4 diff --git a/test/e2e/logs/log-reload.sh b/test/e2e/logs/log-reload.sh index ce64f5c6a..b52229d69 100644 --- a/test/e2e/logs/log-reload.sh +++ b/test/e2e/logs/log-reload.sh @@ -7,19 +7,18 @@ cd $file_path echo -e "\033[1mRunning tests:\033[0m" ->out-rel.log +# >out-rel.log -$pm2 start echo.js -o out-rel.log --merge-logs -i 1 +# $pm2 start echo.js -o out-rel.log --merge-logs -i 1 -$pm2 reloadLogs - -sleep 1 +# $pm2 reloadLogs -grep "Reloading log..." out-rel.log +# sleep 2 -spec "Should have started the reloading action" +# grep "Reloading log..." ~/.pm2/pm2.log +# spec "Should have started the reloading action" -rm out-rel.log +# rm out-rel.log ## FORK MODE diff --git a/test/e2e/modules/module.sh b/test/e2e/modules/module.sh index 715abb2d9..fa05a6559 100644 --- a/test/e2e/modules/module.sh +++ b/test/e2e/modules/module.sh @@ -63,20 +63,14 @@ spec "Should resurrect pm2" should 'and module still online' 'online' 1 -$pm2 stop pm2-probe -should 'should module status not be modified' 'online' 1 - $pm2 delete all should 'should module status not be modified' 'online' 1 -$pm2 delete pm2-probe -should 'should module status not be modified' 'online' 1 - $pm2 stop all should 'should module status not be modified' 'online' 1 $pm2 stop pm2-probe -should 'should module status not be modified' 'online' 1 +should 'should module be possible to stop' 'stopped' 1 $pm2 uninstall pm2-probe spec "Should uninstall a module" diff --git a/test/fixtures/ecosystem.config.js b/test/fixtures/ecosystem.config.js index a5455ea4d..0107dac72 100644 --- a/test/fixtures/ecosystem.config.js +++ b/test/fixtures/ecosystem.config.js @@ -1,33 +1,22 @@ module.exports = { - /** - * Application configuration section - * http://pm2.keymetrics.io/docs/usage/application-declaration/ - */ - apps : [ + apps : [{ + name: 'API', + script: 'app.js', - // First application - { - name : 'API', - script : 'app.js', - env: { - COMMON_VARIABLE: 'true' - }, - env_production : { - NODE_ENV: 'production' - } + // Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/ + args: 'one two', + instances: 1, + autorestart: true, + watch: false, + max_memory_restart: '1G', + env: { + NODE_ENV: 'development' }, - - // Second application - { - name : 'WEB', - script : 'web.js' + env_production: { + NODE_ENV: 'production' } - ], + }], - /** - * Deployment section - * http://pm2.keymetrics.io/docs/usage/deployment/ - */ deploy : { production : { user : 'node', @@ -36,17 +25,6 @@ module.exports = { repo : 'git@github.com:repo.git', path : '/var/www/production', 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production' - }, - dev : { - user : 'node', - host : '212.83.163.1', - ref : 'origin/master', - repo : 'git@github.com:repo.git', - path : '/var/www/development', - 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env dev', - env : { - NODE_ENV: 'dev' - } } } }; diff --git a/test/fixtures/extra-lang/app-python.config.js b/test/fixtures/extra-lang/app-python.config.js new file mode 100644 index 000000000..de29e1310 --- /dev/null +++ b/test/fixtures/extra-lang/app-python.config.js @@ -0,0 +1,23 @@ +module.exports = { + apps : [{ + name: 'echo-python-1', + cmd: 'echo.py', + max_memory_restart: '1G', + env: { + NODE_ENV: 'development' + }, + env_production : { + NODE_ENV: 'production' + } + },{ + name: 'echo-python-max', + cmd: 'echo.py', + instances: 4, + env: { + NODE_ENV: 'development' + }, + env_production : { + NODE_ENV: 'production' + } + }] +}; diff --git a/test/fixtures/extra-lang/apps.json b/test/fixtures/extra-lang/apps.json index 750efe738..74e35a48b 100644 --- a/test/fixtures/extra-lang/apps.json +++ b/test/fixtures/extra-lang/apps.json @@ -3,7 +3,6 @@ "interpreter" : "/usr/bin/python3", "name" : "echo-python", "script" : "echo.py", - "interpreter_args" : "-u", "log" : "python-app.log", "combine_logs" : true, "env" : { diff --git a/test/fixtures/git/index b/test/fixtures/git/index index a79f428b8..3b30bd515 100644 Binary files a/test/fixtures/git/index and b/test/fixtures/git/index differ diff --git a/test/fixtures/harmony.js b/test/fixtures/harmony.js deleted file mode 100644 index 8b9c80a9d..000000000 --- a/test/fixtures/harmony.js +++ /dev/null @@ -1,11 +0,0 @@ -var assert = require('assert') -, s = new Set() -; - -s.add('a'); - -assert.ok(s.has('a')); - -setInterval(function() { - console.log(s.has('a')); -}, 1000); diff --git a/test/fixtures/harmony.json b/test/fixtures/harmony.json deleted file mode 100644 index 99ed3067a..000000000 --- a/test/fixtures/harmony.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name" : "ES6", - "script" : "harmony.js", - "node_args" : "--harmony" -} diff --git a/test/fixtures/stdout-stderr.js b/test/fixtures/stdout-stderr.js index 8668111ac..391562d5f 100644 --- a/test/fixtures/stdout-stderr.js +++ b/test/fixtures/stdout-stderr.js @@ -6,3 +6,13 @@ process.stdout.write('outwrite', 'utf8', function() { process.stderr.write('errwrite', 'utf8', function() { console.log('errcb'); }); + +setInterval(function() { + process.stdout.write('outwrite', 'utf8', function() { + console.log('outcb'); + }); + + process.stderr.write('errwrite', 'utf8', function() { + console.log('errcb'); + }); +}, 1000) diff --git a/test/fixtures/watcher/server-watch.js b/test/fixtures/watcher/server-watch.js deleted file mode 100644 index 7d1c9d7f6..000000000 --- a/test/fixtures/watcher/server-watch.js +++ /dev/null @@ -1,7 +0,0 @@ -var http = require('http'); - -http.createServer(function(req, res) { - res.writeHead(200); - res.end('hey'); -}).listen(8010); -console.log("edit") \ No newline at end of file diff --git a/test/mocha.opts b/test/mocha.opts index 1e35b83e0..2b75af558 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1 +1,3 @@ ---timeout 25000 +--timeout 10000 +--exit +--bail diff --git a/test/programmatic/api.mocha.js b/test/programmatic/api.mocha.js index 47c137d8e..bdbf99fb9 100644 --- a/test/programmatic/api.mocha.js +++ b/test/programmatic/api.mocha.js @@ -250,12 +250,11 @@ describe('API checks', function() { var pm2; after(function(done) { - pm2.destroy(done); + pm2.kill(done); }); it('should create new custom PM2 instance', function() { pm2 = new PM2.custom({ - independent : true, daemon_mode : true }); should.exists(pm2.pm2_home); @@ -291,12 +290,11 @@ describe('API checks', function() { var pm2; after(function(done) { - pm2.destroy(done); + pm2.kill(done); }); it('should create new custom PM2 instance', function() { pm2 = new PM2.custom({ - independent : true, daemon_mode : false }); diff --git a/test/programmatic/auto_restart.mocha.js b/test/programmatic/auto_restart.mocha.js new file mode 100644 index 000000000..84c0dc13e --- /dev/null +++ b/test/programmatic/auto_restart.mocha.js @@ -0,0 +1,49 @@ + +const pm2 = require('../..'); +const should = require('should'); +const path = require('path') + +describe('PM2 auto restart on uncaughtexception', function() { + var test_path = path.join(__dirname, 'fixtures', 'auto-restart') + + after((done) => { + pm2.delete('all', () => { done() }) + }) + + before((done) => { + pm2.uninstall('all', () => { + pm2.delete('all', () => { done() }) + }) + }) + + it('should start a failing app in fork mode', function(done) { + pm2.start({ + script: path.join(test_path, 'throw.js'), + }, (err, apps) => { + setTimeout(function() { + pm2.list((err, list) => { + should(list[0].pm2_env.restart_time).aboveOrEqual(0) + pm2.delete('throw', () => { + done() + }) + }) + }, 200) + }) + }) + + it('should start a failing app in cluster mode', function(done) { + pm2.start({ + script: path.join(test_path, 'throw.js'), + instances: 4 + }, (err, apps) => { + setTimeout(function() { + pm2.list((err, list) => { + should(list[0].pm2_env.restart_time).aboveOrEqual(0) + pm2.delete('throw', () => { + done() + }) + }) + }, 200) + }) + }) +}) diff --git a/test/programmatic/cluster.mocha.js b/test/programmatic/cluster.mocha.js index dbe6af7d9..3eefaac9c 100644 --- a/test/programmatic/cluster.mocha.js +++ b/test/programmatic/cluster.mocha.js @@ -10,12 +10,11 @@ process.chdir(__dirname); describe('Cluster programmatic tests', function() { var pm2 = new PM2.custom({ - cwd : '../fixtures', - independent : true + cwd : '../fixtures' }); after(function(done) { - pm2.destroy(done) + pm2.kill(done) }); describe('Start with different instances number parameter', function() { @@ -150,6 +149,7 @@ describe('Cluster programmatic tests', function() { pm2.start({ script : './echo.js', listen_timeout : 1000, + exec_mode: 'cluster', instances : 1, name : 'echo' }, done); @@ -163,7 +163,7 @@ describe('Cluster programmatic tests', function() { }); }); - it('should take listen timeout into account', function(done) { + it('should take listen_timeout into account', function(done) { var called = false; var plan = new Plan(3, done); diff --git a/test/programmatic/conf_update.mocha.js b/test/programmatic/conf_update.mocha.js index 8ec781af9..b1bf8465a 100644 --- a/test/programmatic/conf_update.mocha.js +++ b/test/programmatic/conf_update.mocha.js @@ -8,12 +8,11 @@ describe('Modules programmatic testing', function() { var pm2; after(function(done) { - pm2.destroy(done); + pm2.kill(done); }); it('should instanciate PM2', function() { pm2 = new PM2.custom({ - independent : true, cwd : '../fixtures' }); }); diff --git a/test/programmatic/env_switching.js b/test/programmatic/env_switching.js index 0f0c43094..560d33a13 100644 --- a/test/programmatic/env_switching.js +++ b/test/programmatic/env_switching.js @@ -49,10 +49,10 @@ describe('PM2 programmatic calls', function() { var procs = []; var bus = null; - var pm2 = new PM2.custom({ independent : true }); + var pm2 = new PM2.custom({ }); after(function(done) { - pm2.destroy(done); + pm2.kill(done); }); before(function(done) { diff --git a/test/programmatic/exp_backoff_restart_delay.mocha.js b/test/programmatic/exp_backoff_restart_delay.mocha.js new file mode 100644 index 000000000..e7b651a74 --- /dev/null +++ b/test/programmatic/exp_backoff_restart_delay.mocha.js @@ -0,0 +1,65 @@ + +process.env.EXP_BACKOFF_RESET_TIMER = 500 +process.env.PM2_WORKER_INTERVAL = 100 + +const PM2 = require('../..'); +const should = require('should'); +const exec = require('child_process').exec +const path = require('path') + +describe('Exponential backoff feature', function() { + var pm2 + var test_path = path.join(__dirname, 'fixtures', 'exp-backoff') + + after(function(done) { + pm2.delete('all', function() { + pm2.kill(done); + }) + }); + + before(function(done) { + pm2 = new PM2.custom({ + cwd : test_path + }); + + pm2.delete('all', () => done()) + }) + + it('should set exponential backoff restart', (done) => { + pm2.start({ + script: path.join(test_path, 'throw-stable.js'), + exp_backoff_restart_delay: 100 + }, (err, apps) => { + should(err).be.null() + should(apps[0].pm2_env.exp_backoff_restart_delay).eql(100) + done() + }) + }) + + it('should have set the prev_restart delay', (done) => { + setTimeout(() => { + pm2.list((err, procs) => { + should(procs[0].pm2_env.prev_restart_delay).be.aboveOrEqual(100) + done() + }) + }, 200) + }) + + it('should have incremented the prev_restart delay', (done) => { + setTimeout(() => { + pm2.list((err, procs) => { + should(procs[0].pm2_env.prev_restart_delay).be.above(100) + done() + }) + }, 500) + }) + + it('should reset prev_restart_delay if application has reach stable uptime', (done) => { + setTimeout(() => { + pm2.list((err, procs) => { + should(procs[0].pm2_env.prev_restart_delay).be.eql(0) + done() + }) + }, 3000) + }) +}) diff --git a/test/programmatic/fixtures/auto-restart/throw.js b/test/programmatic/fixtures/auto-restart/throw.js new file mode 100644 index 000000000..4e6d81e32 --- /dev/null +++ b/test/programmatic/fixtures/auto-restart/throw.js @@ -0,0 +1 @@ +throw new Error('err') diff --git a/test/programmatic/fixtures/exp-backoff/throw-stable.js b/test/programmatic/fixtures/exp-backoff/throw-stable.js new file mode 100644 index 000000000..09de389df --- /dev/null +++ b/test/programmatic/fixtures/exp-backoff/throw-stable.js @@ -0,0 +1,8 @@ + +if (parseInt(process.env.restart_time) === 5) { + return setInterval(function() { + console.log('Im stable mamen') + }, 1000) +} + +throw new Error('Ugly error') diff --git a/test/programmatic/fixtures/exp-backoff/throw.js b/test/programmatic/fixtures/exp-backoff/throw.js new file mode 100644 index 000000000..89c51b6ea --- /dev/null +++ b/test/programmatic/fixtures/exp-backoff/throw.js @@ -0,0 +1 @@ +throw new Error('Ugly error') diff --git a/test/programmatic/fixtures/tar-module/mono-app-module/README.md b/test/programmatic/fixtures/tar-module/mono-app-module/README.md new file mode 100644 index 000000000..b1da5a010 --- /dev/null +++ b/test/programmatic/fixtures/tar-module/mono-app-module/README.md @@ -0,0 +1,8 @@ + +To start http application in cluster mode: + +```bash +$ pm2 start ecosystem.config.js +# OR +$ pm2 start http.js -i max +``` diff --git a/test/programmatic/fixtures/tar-module/mono-app-module/ecosystem.config.js b/test/programmatic/fixtures/tar-module/mono-app-module/ecosystem.config.js new file mode 100644 index 000000000..57a73d505 --- /dev/null +++ b/test/programmatic/fixtures/tar-module/mono-app-module/ecosystem.config.js @@ -0,0 +1,18 @@ +module.exports = { + name: 'envision', + apps : [{ + name : 'clustered_http', + script : './http.js', + instances : 'max', + exec_mode : 'cluster', + env : { + PORT : 8002 + } + }, { + name : 'forked_app', + script : './http.js', + env : { + PORT : 8001 + } + }] +} diff --git a/test/programmatic/fixtures/tar-module/mono-app-module/http.js b/test/programmatic/fixtures/tar-module/mono-app-module/http.js new file mode 100644 index 000000000..912569de6 --- /dev/null +++ b/test/programmatic/fixtures/tar-module/mono-app-module/http.js @@ -0,0 +1,9 @@ + +var http = require('http'); + +var server = http.createServer(function(req, res) { + res.writeHead(200); + res.end('hey'); +}).listen(process.env.PORT || 8000, function() { + console.log('App listening on port %d', server.address().port); +}); diff --git a/test/programmatic/fixtures/tar-module/mono-app-module/package.json b/test/programmatic/fixtures/tar-module/mono-app-module/package.json new file mode 100644 index 000000000..e0ab1423d --- /dev/null +++ b/test/programmatic/fixtures/tar-module/mono-app-module/package.json @@ -0,0 +1,11 @@ +{ + "name": "mono-app-module", + "version": "0.23.0", + "pm2": [{ + "name" : "mono_app", + "script": "http.js", + "env" : { + "PORT" : "8008" + } + }] +} diff --git a/test/programmatic/fixtures/tar-module/multi-app-module/README.md b/test/programmatic/fixtures/tar-module/multi-app-module/README.md new file mode 100644 index 000000000..b1da5a010 --- /dev/null +++ b/test/programmatic/fixtures/tar-module/multi-app-module/README.md @@ -0,0 +1,8 @@ + +To start http application in cluster mode: + +```bash +$ pm2 start ecosystem.config.js +# OR +$ pm2 start http.js -i max +``` diff --git a/test/programmatic/fixtures/tar-module/multi-app-module/ecosystem.config.js b/test/programmatic/fixtures/tar-module/multi-app-module/ecosystem.config.js new file mode 100644 index 000000000..57a73d505 --- /dev/null +++ b/test/programmatic/fixtures/tar-module/multi-app-module/ecosystem.config.js @@ -0,0 +1,18 @@ +module.exports = { + name: 'envision', + apps : [{ + name : 'clustered_http', + script : './http.js', + instances : 'max', + exec_mode : 'cluster', + env : { + PORT : 8002 + } + }, { + name : 'forked_app', + script : './http.js', + env : { + PORT : 8001 + } + }] +} diff --git a/test/programmatic/fixtures/tar-module/multi-app-module/http.js b/test/programmatic/fixtures/tar-module/multi-app-module/http.js new file mode 100644 index 000000000..912569de6 --- /dev/null +++ b/test/programmatic/fixtures/tar-module/multi-app-module/http.js @@ -0,0 +1,9 @@ + +var http = require('http'); + +var server = http.createServer(function(req, res) { + res.writeHead(200); + res.end('hey'); +}).listen(process.env.PORT || 8000, function() { + console.log('App listening on port %d', server.address().port); +}); diff --git a/test/programmatic/fixtures/tar-module/multi-app-module/package.json b/test/programmatic/fixtures/tar-module/multi-app-module/package.json new file mode 100644 index 000000000..4e9ad6cba --- /dev/null +++ b/test/programmatic/fixtures/tar-module/multi-app-module/package.json @@ -0,0 +1,17 @@ +{ + "name": "multi-app-module", + "version": "0.1", + "pm2": [{ + "name" : "first_app", + "script": "http.js", + "env" : { + "PORT" : "8005" + } + }, { + "name" : "second_app", + "script" : "./http.js", + "env" : { + "PORT" : "8002" + } + }] +} diff --git a/test/programmatic/fixtures/version-test/index.js b/test/programmatic/fixtures/version-test/index.js new file mode 100644 index 000000000..1be9148ed --- /dev/null +++ b/test/programmatic/fixtures/version-test/index.js @@ -0,0 +1,4 @@ + +setInterval(function() { + console.log('HEY') +}, 1000) diff --git a/test/programmatic/fixtures/version-test/package.json b/test/programmatic/fixtures/version-test/package.json new file mode 100644 index 000000000..f4c25ad2b --- /dev/null +++ b/test/programmatic/fixtures/version-test/package.json @@ -0,0 +1 @@ +{"name":"version-module","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":"","license":"ISC"} \ No newline at end of file diff --git a/test/programmatic/graceful.mocha.js b/test/programmatic/graceful.mocha.js index 01d8c0d1f..8d7d669ab 100644 --- a/test/programmatic/graceful.mocha.js +++ b/test/programmatic/graceful.mocha.js @@ -14,12 +14,11 @@ describe('Wait ready / Graceful start / restart', function() { process.exit(0); var pm2 = new PM2.custom({ - cwd : '../fixtures/listen-timeout/', - independent : true + cwd : '../fixtures/listen-timeout/' }); after(function(done) { - pm2.destroy(done) + pm2.kill(done) }); describe('(FORK) Listen timeout feature', function() { diff --git a/test/programmatic/inside.mocha.js b/test/programmatic/inside.mocha.js index f25addffb..ff8094706 100644 --- a/test/programmatic/inside.mocha.js +++ b/test/programmatic/inside.mocha.js @@ -4,12 +4,11 @@ var should = require('should'); describe('Call PM2 inside PM2', function() { var pm2 = new PM2.custom({ - independent : true, cwd : __dirname + '/../fixtures/inside' }); after(function(done) { - pm2.destroy(function(){ + pm2.kill(function(){ done(); }); }); diff --git a/test/programmatic/internal_config.mocha.js b/test/programmatic/internal_config.mocha.js new file mode 100644 index 000000000..db2d2896b --- /dev/null +++ b/test/programmatic/internal_config.mocha.js @@ -0,0 +1,23 @@ + +const PM2 = require('../..'); +const should = require('should'); + +describe('Internal PM2 configuration', function() { + var pm2 + + before(function() { + pm2 = new PM2.custom(); + }) + + it('should set pm2:registry', function(done) { + pm2.set('pm2:registry', 'http://thing.com', done) + }) + + it('should new instance have the configuration', function() { + var pm3 = new PM2.custom(); + + pm3.connect(() => { + should(pm2.user_conf.registry).eql('http://thing.com') + }) + }) +}) diff --git a/test/programmatic/max_memory_limit.js b/test/programmatic/max_memory_limit.js index a5d8d0e67..d8e2117b2 100644 --- a/test/programmatic/max_memory_limit.js +++ b/test/programmatic/max_memory_limit.js @@ -12,12 +12,11 @@ describe('Max memory restart programmatic', function() { var proc1 = null; var procs = []; var pm2 = new PM2.custom({ - cwd : __dirname + '/../fixtures/json-reload/', - independent : true + cwd : __dirname + '/../fixtures/json-reload/' }); after(function(done) { - pm2.destroy(done) + pm2.kill(done) }); afterEach(function(done) { diff --git a/test/programmatic/misc_commands.js b/test/programmatic/misc_commands.js index ac80e5b85..43065a14c 100644 --- a/test/programmatic/misc_commands.js +++ b/test/programmatic/misc_commands.js @@ -9,12 +9,11 @@ var cst = require('../../constants.js'); describe('Misc commands', function() { var pm2 = new PM2.custom({ - independent : true, cwd : __dirname + '/../fixtures' }); after(function(done) { - pm2.destroy(done); + pm2.kill(done); }); before(function(done) { diff --git a/test/programmatic/module_retrocompat.mocha.js b/test/programmatic/module_retrocompat.mocha.js deleted file mode 100644 index 3c8ac5f78..000000000 --- a/test/programmatic/module_retrocompat.mocha.js +++ /dev/null @@ -1,104 +0,0 @@ - - -var PM2 = require('../..'); -var should = require('should'); -var shelljs = require('shelljs'); -var path = require('path'); -var fs = require('fs'); - -describe('Modules programmatic testing', function() { - var pm2; - - // after(function(done) { - // pm2.kill(done); - // }); - - var MODULE_CONF_PATH; - var MODULE_PATH; - - it('should instanciate PM2', function() { - pm2 = new PM2.custom({ - //independent : true, - //daemon_mode : true - }); - - MODULE_CONF_PATH = pm2._conf.PM2_MODULE_CONF_FILE; - MODULE_PATH = pm2._conf.DEFAULT_MODULE_PATH; - }); - - it('should cleanup paths', function() { - fs.writeFileSync(MODULE_CONF_PATH, '{}'); - shelljs.rm('-r', MODULE_PATH); - shelljs.rm('-r', path.join(pm2._conf.PM2_HOME, 'node_modules')); - }); - - describe('Be able to manage old module system', function() { - it('should install a module the old school way', function(done) { - pm2.install('pm2-server-monit', { v1 : true}, function(err, apps) { - var data = JSON.parse(fs.readFileSync(MODULE_CONF_PATH)); - should.exists(data['module-db']['pm2-server-monit']); - fs.statSync(path.join(pm2._conf.PM2_HOME, 'node_modules', 'pm2-server-monit')); - done(); - }); - }); - - it('should be able to uninstall module', function(done) { - pm2.uninstall('pm2-server-monit', function(err, apps) { - var data = JSON.parse(fs.readFileSync(MODULE_CONF_PATH)); - should.not.exists(data['module-db']['pm2-server-monit']); - try { - fs.statSync(path.join(pm2._conf.PM2_HOME, 'node_modules', 'pm2-server-monit')); - } catch(e) { - if (!e) done(new Error('module must have been deleted...')); - } - done(); - }); - }); - }); - - describe('Upgrade module to V2 management', function() { - it('should install a module the old school way', function(done) { - pm2.install('pm2-server-monit', { v1 : true}, function(err, apps) { - var data = JSON.parse(fs.readFileSync(MODULE_CONF_PATH)); - should.exists(data['module-db']['pm2-server-monit']); - fs.statSync(path.join(pm2._conf.PM2_HOME, 'node_modules', 'pm2-server-monit')); - done(); - }); - }); - - it('should update and still have module started', function(done) { - pm2.update(function() { - pm2.list(function(err, procs) { - should(procs.length).eql(1); - done(); - }); - }); - }); - - it('should reinstall module in new school way', function(done) { - pm2.install('pm2-server-monit', function(err, apps) { - var data = JSON.parse(fs.readFileSync(MODULE_CONF_PATH)); - should.exists(data['module-db-v2']['pm2-server-monit']); - should.not.exists(data['module-db']['pm2-server-monit']); - try { - fs.statSync(path.join(pm2._conf.PM2_HOME, 'node_modules', 'pm2-server-monit')); - } catch(e) { - if (!e) - done(new Error('The old module has not been deleted...')); - } - - fs.statSync(path.join(MODULE_PATH, 'pm2-server-monit', 'node_modules', 'pm2-server-monit')); - done(); - }); - }); - - it('should update and still have module started', function(done) { - pm2.update(function() { - pm2.list(function(err, procs) { - should(procs.length).eql(1); - done(); - }); - }); - }); - }); -}); diff --git a/test/programmatic/module_tar.mocha.js b/test/programmatic/module_tar.mocha.js new file mode 100644 index 000000000..8c5233e06 --- /dev/null +++ b/test/programmatic/module_tar.mocha.js @@ -0,0 +1,225 @@ + +const PM2 = require('../..'); +const should = require('should'); +const exec = require('child_process').exec +const path = require('path') +const fs = require('fs') + +describe('Modules programmatic testing', function() { + var pm2; + + var MODULE_FOLDER_MONO = path.join(__dirname, './fixtures/tar-module/mono-app-module') + var MODULE_FOLDER_MULTI = path.join(__dirname, './fixtures/tar-module/multi-app-module') + + var PACKAGE_MONO = path.join(process.cwd(), 'mono-app-module-v0-23-0.tar.gz') + var PACKAGE_MULTI = path.join(process.cwd(), 'multi-app-module-v0-1.tar.gz') + + after(function(done) { + pm2.kill(done); + }); + + before(function(done) { + pm2 = new PM2.custom({ + cwd : './fixtures' + }); + + pm2.uninstall('all', () => done()) + }) + + describe('Package', function() { + before((done) => { + fs.unlink(PACKAGE_MONO, () => { + fs.unlink(PACKAGE_MULTI, () => { + done() + }) + }) + }) + + it('should package tarball for mono app', function(done) { + pm2.package(MODULE_FOLDER_MONO, (err) => { + should(err).be.null() + should(fs.existsSync(PACKAGE_MONO)).eql(true) + done() + }) + }) + + it('should package tarball for multi app', function(done) { + pm2.package(MODULE_FOLDER_MULTI, (err) => { + should(err).be.null() + should(fs.existsSync(PACKAGE_MULTI)).eql(true) + done() + }) + }) + }) + + describe('MULTI Install', function() { + it('should install module', function(done) { + pm2.install(PACKAGE_MULTI, { + tarball: true + }, function(err, apps) { + should(err).eql(null); + done(); + }); + }); + + it('should have file decompressed in the right folder', function() { + var target_path = path.join(PM2._conf.DEFAULT_MODULE_PATH, 'multi-app-module') + fs.readFileSync(path.join(target_path, 'package.json')) + }) + + it('should have boot key present', function(done) { + var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) + should.exist(conf['tar-modules']['multi-app-module']); + done() + }) + + it('should have started 2 apps', function(done) { + pm2.list(function(err, list) { + should(err).be.null(); + should(list.length).eql(2) + should(list[0].pm2_env.version).eql('0.1') + should(list[0].name).eql('multi-app-module:first_app') + should(list[1].name).eql('multi-app-module:second_app') + should(list[1].pm2_env.version).eql('0.1') + should(list[0].pm2_env.status).eql('online') + should(list[1].pm2_env.status).eql('online') + done() + }) + }) + }) + + describe('Reinstall', () => { + it('should install module', function(done) { + pm2.install(PACKAGE_MULTI, { + tarball: true + }, function(err, apps) { + should(err).eql(null); + done(); + }); + }); + + it('should have only 2 apps', function(done) { + pm2.list(function(err, list) { + should(err).be.null(); + should(list.length).eql(2) + should(list[0].pm2_env.status).eql('online') + should(list[1].pm2_env.status).eql('online') + done() + }) + }) + }) + + describe('Re spawn PM2', () => { + it('should kill/resurect pm2', (done) => { + pm2.update(function(err) { + should(err).be.null(); + done() + }) + }) + + it('should have boot key present', function(done) { + var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) + should.exist(conf['tar-modules']['multi-app-module']); + done() + }) + + it('should have started 2 apps', function(done) { + pm2.list(function(err, list) { + should(err).be.null(); + should(list.length).eql(2) + should(list[0].pm2_env.status).eql('online') + should(list[0].pm2_env.version).eql('0.1') + should(list[1].pm2_env.version).eql('0.1') + should(list[1].pm2_env.status).eql('online') + done() + }) + }) + }) + + describe('CLI UX', () => { + it('should not delete modules when calling pm2 delete all', (done) => { + pm2.delete('all', (err, apps) => { + should(apps.length).eql(2) + done() + }) + }) + }) + + describe('Uninstall', () => { + it('should uninstall multi app module', (done) => { + pm2.uninstall('multi-app-module', (err, data) => { + should(err).be.null(); + done() + }) + }) + + it('should have boot key deleted', function(done) { + var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) + should.not.exist(conf['tar-modules']['multi-app-module']); + done() + }) + + it('should have no running apps', function(done) { + pm2.list(function(err, list) { + should(err).be.null(); + should(list.length).eql(0) + done() + }) + }) + }) + + describe('MONO APP', () => { + it('should install module', function(done) { + pm2.install(PACKAGE_MONO, { + tarball: true + }, function(err, apps) { + should(err).eql(null); + done(); + }); + }); + + it('should have file decompressed in the right folder', function() { + var target_path = path.join(PM2._conf.DEFAULT_MODULE_PATH, 'mono-app-module') + var pkg_path = path.join(target_path, 'package.json') + fs.readFileSync(pkg_path) + }) + + it('should have boot key present', function(done) { + var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) + should.exist(conf['tar-modules']['mono-app-module']); + done() + }) + + it('should have started 1 app', function(done) { + pm2.list(function(err, list) { + should(err).be.null(); + should(list.length).eql(1) + should(list[0].name).eql('mono_app') + should(list[0].pm2_env.version).eql('0.23.0') + should(list[0].pm2_env.status).eql('online') + done() + }) + }) + + it('should uninstall multi app module', (done) => { + pm2.uninstall('mono-app-module', (err, data) => { + should(err).be.null(); + done() + }) + }) + + it('should have boot key deleted', function(done) { + var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) + should.not.exist(conf['tar-modules']['mono-app-module']); + done() + }) + + it('should have no running apps', function(done) { + pm2.list(function(err, list) { + should(err).be.null(); + should(list.length).eql(0) + done() + }) + }) + }) +}) diff --git a/test/programmatic/modules.mocha.js b/test/programmatic/modules.mocha.js index 32363d7fb..7ac68c43f 100644 --- a/test/programmatic/modules.mocha.js +++ b/test/programmatic/modules.mocha.js @@ -6,12 +6,11 @@ describe('Modules programmatic testing', function() { var pm2; after(function(done) { - pm2.destroy(done); + pm2.kill(done); }); it('should instanciate PM2', function() { pm2 = new PM2.custom({ - independent : true, daemon_mode : true }); }); @@ -26,8 +25,7 @@ describe('Modules programmatic testing', function() { }); }); - it('should run post install command', function(done) - { + it('should run post install command', function(done) { var fs = require('fs'); var ec = {}; ec.dependencies = new Array(); @@ -51,30 +49,6 @@ describe('Modules programmatic testing', function() { }); }); - it('should install (update) a module with uid option', function(done) { - pm2.install('pm2-server-monit', { - uid : process.env.USER - }, function(err, apps) { - should(err).eql(null); - should(apps.length).eql(1); - var pm2_env = apps[0].pm2_env; - should.exist(pm2_env); - should(pm2_env.uid).eql(process.env.USER); - done(); - }); - }); - - it('should have uid option via pm2 list', function(done) { - pm2.list(function(err, apps) { - should(err).eql(null); - should(apps.length).eql(1); - var pm2_env = apps[0].pm2_env; - should.exist(pm2_env); - should(pm2_env.uid).eql(process.env.USER); - done(); - }); - }); - it('should uninstall all modules', function(done) { pm2.uninstall('all', function(err, apps) { done(); diff --git a/test/programmatic/programmatic.js b/test/programmatic/programmatic.js index 478e13618..2d4cead31 100644 --- a/test/programmatic/programmatic.js +++ b/test/programmatic/programmatic.js @@ -101,7 +101,6 @@ describe('PM2 programmatic calls', function() { name : 'tota', instances : 3 }, function(err, data) { - console.log(err) should.exists(err); done(); }); diff --git a/test/programmatic/signals.js b/test/programmatic/signals.js index fad65206e..c4f7e07d5 100644 --- a/test/programmatic/signals.js +++ b/test/programmatic/signals.js @@ -7,13 +7,12 @@ describe('Signal kill (+delayed)', function() { var proc1 = null; var pm2 = new PM2.custom({ - independent : true, cwd : __dirname + '/../fixtures' }); after(function(done) { pm2.delete('all', function(err, ret) { - pm2.destroy(done); + pm2.kill(done); }); }); diff --git a/test/programmatic/user_management.mocha.js b/test/programmatic/user_management.mocha.js new file mode 100644 index 000000000..db5fbaa74 --- /dev/null +++ b/test/programmatic/user_management.mocha.js @@ -0,0 +1,60 @@ + +process.env.NODE_ENV = 'test' +process.chdir(__dirname); + +var PM2 = require('../..'); +var should = require('should'); + +describe('User management', function() { + before(function(done) { + PM2.delete('all', function() { done() }); + }); + + after(function(done) { + PM2.kill(done); + }); + + it('should fail with unknown user', function(done) { + PM2.start('./../fixtures/child.js', { + user: 'toto' + },function(err) { + should(err.message).match(/cannot be found/) + + PM2.list(function(err, list) { + should(err).be.null(); + should(list.length).eql(0); + done(); + }); + }); + }) + + it('should succeed with known user', function(done) { + PM2.start('./../fixtures/child.js', { + user: process.env.USER + },function(err) { + should(err).be.null(); + PM2.list(function(err, list) { + should(err).be.null(); + should(list.length).eql(1); + should.exist(list[0].pm2_env.uid) + should.exist(list[0].pm2_env.gid) + PM2.delete('all', done) + }); + }); + }) + + it('should succeed with known user via uid field', function(done) { + PM2.start('./../fixtures/child.js', { + uid: process.env.USER + },function(err) { + should(err).be.null(); + PM2.list(function(err, list) { + should(err).be.null(); + should.exist(list[0].pm2_env.uid) + should.exist(list[0].pm2_env.gid) + should(list.length).eql(1); + PM2.delete('all', done) + }); + }); + }) +}) diff --git a/test/programmatic/version.mocha.js b/test/programmatic/version.mocha.js new file mode 100644 index 000000000..a71b1167a --- /dev/null +++ b/test/programmatic/version.mocha.js @@ -0,0 +1,69 @@ + +const PM2 = require('../..'); +const should = require('should'); +const exec = require('child_process').exec +const path = require('path') +const fs = require('fs') + +describe('Modules programmatic testing', function() { + var pm2 + var pkg_path = path.join(__dirname, 'fixtures/version-test/package.json') + + after(function(done) { + pm2.delete('all', function() { + pm2.kill(done); + }) + }); + + before(function(done) { + pm2 = new PM2.custom({ + cwd : path.join(__dirname, 'fixtures') + }); + + var pkg = JSON.parse(fs.readFileSync(pkg_path)) + pkg.version = '1.0.0' + fs.writeFileSync(pkg_path, JSON.stringify(pkg)) + + pm2.delete('all', () => done()) + }) + + it('should start app and find version', function(done) { + pm2.start('./version-test/index.js', (err) => { + pm2.list(function(err, apps) { + should(err).be.null() + var real_version = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures/version-test/package.json'))).version + should(apps[0].pm2_env.version).equal(real_version) + done() + }) + }) + }) + + var origin_version + it('should update version', function(done) { + var old = JSON.parse(fs.readFileSync(pkg_path)) + origin_version = old.version + old.version = '2.0.0' + fs.writeFileSync(pkg_path, JSON.stringify(old)) + pm2.restart('all', function() { + setTimeout(() => { + pm2.list((err, list) => { + should(list[0].pm2_env.version).equal('2.0.0') + done() + }) + }, 400) + }) + }) + + it('should restore version', function(done) { + var old = JSON.parse(fs.readFileSync(pkg_path)) + old.version = origin_version + fs.writeFileSync(pkg_path, JSON.stringify(old)) + + pm2.restart('all', function() { + pm2.list((err, list) => { + should(list[0].pm2_env.version).equal(origin_version) + done() + }) + }) + }) +}) diff --git a/test/programmatic/watcher.js b/test/programmatic/watcher.js index 41d96e634..67df7fe72 100644 --- a/test/programmatic/watcher.js +++ b/test/programmatic/watcher.js @@ -47,7 +47,6 @@ function errShouldBeNull(err) { describe('Watcher', function() { var pm2 = new PM2.custom({ - independent : true, cwd : __dirname + '/../fixtures/watcher' }); diff --git a/test/unit.sh b/test/unit.sh index 1fb7143dc..2677a83cd 100644 --- a/test/unit.sh +++ b/test/unit.sh @@ -33,9 +33,6 @@ $pm2 uninstall all # fi cd test/programmatic -mocha --exit --opts ./mocha.opts ./god.mocha.js -spec "God test" - mocha --exit --opts ./mocha.opts ./programmatic.js spec "Programmatic test" @@ -51,6 +48,16 @@ spec "API tests" mocha --exit --opts ./mocha.opts ./reload-locker.mocha.js spec "Reload locker tests" +mocha --exit --opts ./mocha.opts ./auto_restart.mocha.js +spec "Auto restart feature when uncaughtException" +mocha --exit --opts ./mocha.opts ./version.mocha.js +spec "Package json version retriever" +$pm2 kill +mocha --exit --opts ./mocha.opts ./exp_backoff_restart_delay.mocha.js +spec "Exponential backoff restart delay tests" +mocha --exit --opts ./mocha.opts ./internal_config.mocha.js +spec "PM2 local configuration working" + mocha --exit --opts ./mocha.opts ./api.backward.compatibility.mocha.js spec "API Backward compatibility tests" mocha --exit --opts ./mocha.opts ./custom_action.mocha.js @@ -78,9 +85,6 @@ mocha --exit --opts ./mocha.opts ./send_data_process.mocha.js spec "Send data to a process" mocha --exit --opts ./mocha.opts ./modules.mocha.js spec "Module API testing" -# mocha --exit --opts ./mocha.opts ./module_retrocompat.mocha.js -# spec "Module retrocompatibility system" - mocha --exit --opts ./mocha.opts ./json_validation.mocha.js spec "JSON validation test" mocha --exit --opts ./mocha.opts ./env_switching.js @@ -90,6 +94,10 @@ spec "Configuration system working" mocha --exit --opts ./mocha.opts ./id.mocha.js spec "Uniqueness id for each process" +mocha --exit --opts ./mocha.opts ./god.mocha.js +spec "God test" + + # # Interface testing #