123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- 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));
- }
- }
|