%PDF- %PDF-
Direktori : /lib/node_modules/pm2/lib/API/Modules/ |
Current File : //lib/node_modules/pm2/lib/API/Modules/NPM.js |
const path = require('path'); const fs = require('fs'); const os = require('os'); const spawn = require('child_process').spawn; const chalk = require('chalk'); const readline = require('readline') const which = require('../../tools/which.js') const sexec = require('../../tools/sexec.js') const copydirSync = require('../../tools/copydirSync.js') const deleteFolderRecursive = require('../../tools/deleteFolderRecursive.js') var Configuration = require('../../Configuration.js'); var cst = require('../../../constants.js'); var Common = require('../../Common'); var Utility = require('../../Utility.js'); 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 <module_name> */ 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'); sexec(cmd1, function(err) { if (err) Common.printError(cst.PREFIX_MSG_MOD_ERR + err.message); sexec(cmd2, function(err) { console.log(''); sexec(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); sexec('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'); sexec('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); }) } // Builtin Node Switch function getNPMCommandLine(module_name, install_path) { if (which('npm')) { return spawn.bind(this, cst.IS_WINDOWS ? 'npm.cmd' : 'npm', ['install', module_name, '--loglevel=error', '--prefix', `"${install_path}"` ], { stdio : 'inherit', env: process.env, windowsHide: true, shell : true }) } else { return spawn.bind(this, cst.BUILTIN_NODE_PATH, [cst.BUILTIN_NPM_PATH, 'install', module_name, '--loglevel=error', '--prefix', `"${install_path}"`], { stdio : 'inherit', env: process.env, windowsHide: true, shell : true }) } } 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) .then(function() { process.chdir(os.homedir()); var install_instance = getNPMCommandLine(module_name, install_path)(); 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 + 'Checkout module options: `$ 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) { console.log('Deleting', proc_path) if (module_name != '.' && proc_path.includes('modules') === true) { deleteFolderRecursive(proc_path) } if (err) { Common.printError(err); return cb(err); } 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 if (module_name.includes('modules') === true) deleteFolderRecursive(module_path) // Restore working version copydirSync(backup_path, path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name)); 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); copydirSync(module_path, path.join(tmpdir, canonic_module_name)); } }