1 | const path = require('path');
|
2 | const fs = require('fs');
|
3 | const os = require('os');
|
4 | const spawn = require('child_process').spawn;
|
5 | const chalk = require('chalk');
|
6 |
|
7 | const readline = require('readline')
|
8 | const which = require('../../tools/which.js')
|
9 | const sexec = require('../../tools/sexec.js')
|
10 | const copydirSync = require('../../tools/copydirSync.js')
|
11 | const deleteFolderRecursive = require('../../tools/deleteFolderRecursive.js')
|
12 |
|
13 | var Configuration = require('../../Configuration.js');
|
14 | var cst = require('../../../constants.js');
|
15 | var Common = require('../../Common');
|
16 | var Utility = require('../../Utility.js');
|
17 |
|
18 | module.exports = {
|
19 | install,
|
20 | uninstall,
|
21 | start,
|
22 | publish,
|
23 | generateSample,
|
24 | localStart,
|
25 | getModuleConf
|
26 | }
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | function localStart(PM2, opts, cb) {
|
41 | var proc_path = '',
|
42 | cmd = '',
|
43 | conf = {};
|
44 |
|
45 | Common.printOut(cst.PREFIX_MSG_MOD + 'Installing local module in DEVELOPMENT MODE with WATCH auto restart');
|
46 | proc_path = process.cwd();
|
47 |
|
48 | cmd = path.join(proc_path, cst.DEFAULT_MODULE_JSON);
|
49 |
|
50 | Common.extend(opts, {
|
51 | cmd : cmd,
|
52 | development_mode : true,
|
53 | proc_path : proc_path
|
54 | });
|
55 |
|
56 | return StartModule(PM2, opts, function(err, dt) {
|
57 | if (err) return cb(err);
|
58 | Common.printOut(cst.PREFIX_MSG_MOD + 'Module successfully installed and launched');
|
59 | return cb(null, dt);
|
60 | });
|
61 | }
|
62 |
|
63 | function generateSample(app_name, cb) {
|
64 | var rl = readline.createInterface({
|
65 | input: process.stdin,
|
66 | output: process.stdout
|
67 | });
|
68 |
|
69 | function samplize(module_name) {
|
70 | var cmd1 = 'git clone https://github.com/pm2-hive/sample-module.git ' + module_name + '; cd ' + module_name + '; rm -rf .git';
|
71 | var cmd2 = 'cd ' + module_name + ' ; sed -i "s:sample-module:'+ module_name +':g" package.json';
|
72 | var cmd3 = 'cd ' + module_name + ' ; npm install';
|
73 |
|
74 | Common.printOut(cst.PREFIX_MSG_MOD + 'Getting sample app');
|
75 |
|
76 | sexec(cmd1, function(err) {
|
77 | if (err) Common.printError(cst.PREFIX_MSG_MOD_ERR + err.message);
|
78 | sexec(cmd2, function(err) {
|
79 | console.log('');
|
80 | sexec(cmd3, function(err) {
|
81 | console.log('');
|
82 | Common.printOut(cst.PREFIX_MSG_MOD + 'Module sample created in folder: ', path.join(process.cwd(), module_name));
|
83 | console.log('');
|
84 | Common.printOut('Start module in development mode:');
|
85 | Common.printOut('$ cd ' + module_name + '/');
|
86 | Common.printOut('$ pm2 install . ');
|
87 | console.log('');
|
88 |
|
89 | Common.printOut('Module Log: ');
|
90 | Common.printOut('$ pm2 logs ' + module_name);
|
91 | console.log('');
|
92 | Common.printOut('Uninstall module: ');
|
93 | Common.printOut('$ pm2 uninstall ' + module_name);
|
94 | console.log('');
|
95 | Common.printOut('Force restart: ');
|
96 | Common.printOut('$ pm2 restart ' + module_name);
|
97 | return cb ? cb() : false;
|
98 | });
|
99 | });
|
100 | });
|
101 | }
|
102 |
|
103 | if (app_name) return samplize(app_name);
|
104 |
|
105 | rl.question(cst.PREFIX_MSG_MOD + "Module name: ", function(module_name) {
|
106 | samplize(module_name);
|
107 | });
|
108 | }
|
109 |
|
110 | function publish(opts, cb) {
|
111 | var rl = readline.createInterface({
|
112 | input: process.stdin,
|
113 | output: process.stdout
|
114 | });
|
115 |
|
116 | var semver = require('semver');
|
117 |
|
118 | var package_file = path.join(process.cwd(), 'package.json');
|
119 |
|
120 | var package_json = require(package_file);
|
121 |
|
122 | package_json.version = semver.inc(package_json.version, 'minor');
|
123 | Common.printOut(cst.PREFIX_MSG_MOD + 'Incrementing module to: %s@%s',
|
124 | package_json.name,
|
125 | package_json.version);
|
126 |
|
127 |
|
128 | rl.question("Write & Publish? [Y/N]", function(answer) {
|
129 | if (answer != "Y")
|
130 | return cb();
|
131 |
|
132 |
|
133 | fs.writeFile(package_file, JSON.stringify(package_json, null, 2), function(err, data) {
|
134 | if (err) return cb(err);
|
135 |
|
136 | Common.printOut(cst.PREFIX_MSG_MOD + 'Publishing module - %s@%s',
|
137 | package_json.name,
|
138 | package_json.version);
|
139 |
|
140 | sexec('npm publish', function(code) {
|
141 | Common.printOut(cst.PREFIX_MSG_MOD + 'Module - %s@%s successfully published',
|
142 | package_json.name,
|
143 | package_json.version);
|
144 |
|
145 | Common.printOut(cst.PREFIX_MSG_MOD + 'Pushing module on Git');
|
146 | sexec('git add . ; git commit -m "' + package_json.version + '"; git push origin master', function(code) {
|
147 |
|
148 | Common.printOut(cst.PREFIX_MSG_MOD + 'Installable with pm2 install %s', package_json.name);
|
149 | return cb(null, package_json);
|
150 | });
|
151 | });
|
152 | });
|
153 |
|
154 | });
|
155 | }
|
156 |
|
157 | function moduleExistInLocalDB(CLI, module_name, cb) {
|
158 | var modules = Configuration.getSync(cst.MODULE_CONF_PREFIX);
|
159 | if (!modules) return cb(false);
|
160 | var module_name_only = Utility.getCanonicModuleName(module_name)
|
161 | modules = Object.keys(modules);
|
162 | return cb(modules.indexOf(module_name_only) > -1 ? true : false);
|
163 | };
|
164 |
|
165 | function install(CLI, module_name, opts, cb) {
|
166 | moduleExistInLocalDB(CLI, module_name, function (exists) {
|
167 | if (exists) {
|
168 | Common.logMod('Module already installed. Updating.');
|
169 |
|
170 | Rollback.backup(module_name);
|
171 |
|
172 | return uninstall(CLI, module_name, function () {
|
173 | return continueInstall(CLI, module_name, opts, cb);
|
174 | });
|
175 | }
|
176 | return continueInstall(CLI, module_name, opts, cb);
|
177 | })
|
178 | }
|
179 |
|
180 |
|
181 | function getNPMCommandLine(module_name, install_path) {
|
182 | if (which('npm')) {
|
183 | return spawn.bind(this, cst.IS_WINDOWS ? 'npm.cmd' : 'npm', ['install', module_name, '--loglevel=error', '--prefix', `"${install_path}"` ], {
|
184 | stdio : 'inherit',
|
185 | env: process.env,
|
186 | shell : true
|
187 | })
|
188 | }
|
189 | else {
|
190 | return spawn.bind(this, cst.BUILTIN_NODE_PATH, [cst.BUILTIN_NPM_PATH, 'install', module_name, '--loglevel=error', '--prefix', `"${install_path}"`], {
|
191 | stdio : 'inherit',
|
192 | env: process.env,
|
193 | shell : true
|
194 | })
|
195 | }
|
196 | }
|
197 |
|
198 | function continueInstall(CLI, module_name, opts, cb) {
|
199 | Common.printOut(cst.PREFIX_MSG_MOD + 'Calling ' + chalk.bold.red('[NPM]') + ' to install ' + module_name + ' ...');
|
200 |
|
201 | var canonic_module_name = Utility.getCanonicModuleName(module_name);
|
202 | var install_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name);
|
203 |
|
204 | require('mkdirp')(install_path)
|
205 | .then(function() {
|
206 | process.chdir(os.homedir());
|
207 |
|
208 | var install_instance = getNPMCommandLine(module_name, install_path)();
|
209 |
|
210 | install_instance.on('close', finalizeInstall);
|
211 |
|
212 | install_instance.on('error', function (err) {
|
213 | console.error(err.stack || err);
|
214 | });
|
215 | });
|
216 |
|
217 | function finalizeInstall(code) {
|
218 | if (code != 0) {
|
219 |
|
220 | return Rollback.revert(CLI, module_name, function() {
|
221 | return cb(new Error('Installation failed via NPM, module has been restored to prev version'));
|
222 | });
|
223 | }
|
224 |
|
225 | Common.printOut(cst.PREFIX_MSG_MOD + 'Module downloaded');
|
226 |
|
227 | var proc_path = path.join(install_path, 'node_modules', canonic_module_name);
|
228 | var package_json_path = path.join(proc_path, 'package.json');
|
229 |
|
230 |
|
231 | try {
|
232 | var conf = JSON.parse(fs.readFileSync(package_json_path).toString()).config;
|
233 |
|
234 | if (conf) {
|
235 | Object.keys(conf).forEach(function(key) {
|
236 | Configuration.setSyncIfNotExist(canonic_module_name + ':' + key, conf[key]);
|
237 | });
|
238 | }
|
239 | } catch(e) {
|
240 | Common.printError(e);
|
241 | }
|
242 |
|
243 | opts = Common.extend(opts, {
|
244 | cmd : package_json_path,
|
245 | development_mode : false,
|
246 | proc_path : proc_path
|
247 | });
|
248 |
|
249 | Configuration.set(cst.MODULE_CONF_PREFIX + ':' + canonic_module_name, {
|
250 | uid : opts.uid,
|
251 | gid : opts.gid
|
252 | }, function(err, data) {
|
253 | if (err) return cb(err);
|
254 |
|
255 | StartModule(CLI, opts, function(err, dt) {
|
256 | if (err) return cb(err);
|
257 |
|
258 | if (process.env.PM2_PROGRAMMATIC === 'true')
|
259 | return cb(null, dt);
|
260 |
|
261 | CLI.conf(canonic_module_name, function() {
|
262 | Common.printOut(cst.PREFIX_MSG_MOD + 'Module successfully installed and launched');
|
263 | Common.printOut(cst.PREFIX_MSG_MOD + 'Checkout module options: `$ pm2 conf`');
|
264 | return cb(null, dt);
|
265 | });
|
266 | });
|
267 | });
|
268 | }
|
269 | }
|
270 |
|
271 | function start(PM2, modules, module_name, cb) {
|
272 | Common.printOut(cst.PREFIX_MSG_MOD + 'Starting NPM module ' + module_name);
|
273 |
|
274 | var install_path = path.join(cst.DEFAULT_MODULE_PATH, module_name);
|
275 | var proc_path = path.join(install_path, 'node_modules', module_name);
|
276 | var package_json_path = path.join(proc_path, 'package.json');
|
277 |
|
278 | var opts = {};
|
279 |
|
280 |
|
281 | Common.extend(opts, modules[module_name]);
|
282 |
|
283 |
|
284 | Common.extend(opts, {
|
285 |
|
286 | cmd : package_json_path,
|
287 |
|
288 | development_mode : false,
|
289 |
|
290 | proc_path : proc_path
|
291 | });
|
292 |
|
293 | StartModule(PM2, opts, function(err, dt) {
|
294 | if (err) console.error(err);
|
295 | return cb();
|
296 | })
|
297 | }
|
298 |
|
299 | function uninstall(CLI, module_name, cb) {
|
300 | var module_name_only = Utility.getCanonicModuleName(module_name)
|
301 | var proc_path = path.join(cst.DEFAULT_MODULE_PATH, module_name_only);
|
302 | Configuration.unsetSync(cst.MODULE_CONF_PREFIX + ':' + module_name_only);
|
303 |
|
304 | CLI.deleteModule(module_name_only, function(err, data) {
|
305 | console.log('Deleting', proc_path)
|
306 | if (module_name != '.' && proc_path.includes('modules') === true) {
|
307 | deleteFolderRecursive(proc_path)
|
308 | }
|
309 |
|
310 | if (err) {
|
311 | Common.printError(err);
|
312 | return cb(err);
|
313 | }
|
314 |
|
315 | return cb(null, data);
|
316 | });
|
317 | }
|
318 |
|
319 | function getModuleConf(app_name) {
|
320 | if (!app_name) throw new Error('No app_name defined');
|
321 |
|
322 | var module_conf = Configuration.getAllSync();
|
323 |
|
324 | var additional_env = {};
|
325 |
|
326 | if (!module_conf[app_name]) {
|
327 | additional_env = {};
|
328 | additional_env[app_name] = {};
|
329 | }
|
330 | else {
|
331 | additional_env = Common.clone(module_conf[app_name]);
|
332 | additional_env[app_name] = JSON.stringify(module_conf[app_name]);
|
333 | }
|
334 | return additional_env;
|
335 | }
|
336 |
|
337 | function StartModule(CLI, opts, cb) {
|
338 | if (!opts.cmd && !opts.package) throw new Error('module package.json not defined');
|
339 | if (!opts.development_mode) opts.development_mode = false;
|
340 |
|
341 | var package_json = require(opts.cmd || opts.package);
|
342 |
|
343 | |
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 | if (!package_json.apps && !package_json.pm2) {
|
350 | package_json.apps = {};
|
351 |
|
352 | if (package_json.bin) {
|
353 | var bin = Object.keys(package_json.bin)[0];
|
354 | package_json.apps.script = package_json.bin[bin];
|
355 | }
|
356 | else if (package_json.main) {
|
357 | package_json.apps.script = package_json.main;
|
358 | }
|
359 | }
|
360 |
|
361 | Common.extend(opts, {
|
362 | cwd : opts.proc_path,
|
363 | watch : opts.development_mode,
|
364 | force_name : package_json.name,
|
365 | started_as_module : true
|
366 | });
|
367 |
|
368 |
|
369 | CLI.start(package_json, opts, function(err, data) {
|
370 | if (err) return cb(err);
|
371 |
|
372 | if (opts.safe) {
|
373 | Common.printOut(cst.PREFIX_MSG_MOD + 'Monitoring module behavior for potential issue (5secs...)');
|
374 |
|
375 | var time = typeof(opts.safe) == 'boolean' ? 3000 : parseInt(opts.safe);
|
376 | return setTimeout(function() {
|
377 | CLI.describe(package_json.name, function(err, apps) {
|
378 | if (err || apps[0].pm2_env.restart_time > 2) {
|
379 | return Rollback.revert(CLI, package_json.name, function() {
|
380 | return cb(new Error('New Module is instable, restored to previous version'));
|
381 | });
|
382 | }
|
383 | return cb(null, data);
|
384 | });
|
385 | }, time);
|
386 | }
|
387 |
|
388 | return cb(null, data);
|
389 | });
|
390 | };
|
391 |
|
392 |
|
393 |
|
394 | var Rollback = {
|
395 | revert : function(CLI, module_name, cb) {
|
396 | var canonic_module_name = Utility.getCanonicModuleName(module_name);
|
397 | var backup_path = path.join(require('os').tmpdir(), canonic_module_name);
|
398 | var module_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name);
|
399 |
|
400 | try {
|
401 | fs.statSync(backup_path)
|
402 | } catch(e) {
|
403 | return cb(new Error('no backup found'));
|
404 | }
|
405 |
|
406 | Common.printOut(cst.PREFIX_MSG_MOD + chalk.bold.red('[[[[[ Module installation failure! ]]]]]'));
|
407 | Common.printOut(cst.PREFIX_MSG_MOD + chalk.bold.red('[RESTORING TO PREVIOUS VERSION]'));
|
408 |
|
409 | CLI.deleteModule(canonic_module_name, function() {
|
410 |
|
411 |
|
412 | if (module_name.includes('modules') === true)
|
413 | deleteFolderRecursive(module_path)
|
414 |
|
415 | copydirSync(backup_path, path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name));
|
416 |
|
417 | var proc_path = path.join(module_path, 'node_modules', canonic_module_name);
|
418 | var package_json_path = path.join(proc_path, 'package.json');
|
419 |
|
420 |
|
421 | StartModule(CLI, {
|
422 | cmd : package_json_path,
|
423 | development_mode : false,
|
424 | proc_path : proc_path
|
425 | }, cb);
|
426 | });
|
427 | },
|
428 | backup : function(module_name) {
|
429 |
|
430 | var tmpdir = require('os').tmpdir();
|
431 | var canonic_module_name = Utility.getCanonicModuleName(module_name);
|
432 | var module_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name);
|
433 | copydirSync(module_path, path.join(tmpdir, canonic_module_name));
|
434 | }
|
435 | }
|