UNPKG

pandora

Version:

A powerful and lightweight application manager for Node.js applications powered by TypeScript.

178 lines 7.71 kB
'use strict'; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); const Base = require("sdk-base"); const const_1 = require("../const"); const fs_1 = require("fs"); const assert = require("assert"); const LoggerBroker_1 = require("../universal/LoggerBroker"); const pathProcessMaster = require.resolve('./ProcessMaster'); const pathProcessBootstrap = require.resolve('./ProcessBootstrap'); const daemonLogger = LoggerBroker_1.getDaemonLogger(); /** * Class ApplicationHandler */ class ApplicationHandler extends Base { constructor(applicationRepresentation) { super(); this.startCount = 0; this.state = const_1.State.pending; this.appRepresentation = applicationRepresentation; assert(fs_1.existsSync(this.appDir), `AppDir ${this.appDir} does not exist`); this.nodejsStdout = LoggerBroker_1.createAppLogger(applicationRepresentation.appName, 'nodejs_stdout'); } get name() { return this.appRepresentation.appName; } get appDir() { return this.appRepresentation.appDir; } get mode() { return this.appRepresentation.mode; } get pid() { return this.proc && this.proc.pid; } /** * Start application through fork * @return {Promise<void>} */ start() { return __awaiter(this, void 0, void 0, function* () { const { mode, entryFile } = this.appRepresentation; const args = []; if ('procfile.js' === mode || 'cluster' === mode) { args.push('--entry', pathProcessMaster); args.push('--params', JSON.stringify(Object.assign({ name: const_1.MASTER }, this.appRepresentation))); } else if ('fork' === mode) { args.push('--entry', entryFile); args.push('--params', JSON.stringify(Object.assign({ name: const_1.MASTER }, this.appRepresentation))); } else { throw new Error(`Unknown start mode ${mode} when start an application`); } this.startCount++; yield this.doFork(args); }); } doFork(args) { const nodejsStdout = this.nodejsStdout; const execArgv = process.execArgv.slice(0); // Handing typeScript file,just for testing if (/\.ts$/.test(module.filename) && execArgv.indexOf('ts-node/register') === -1) { execArgv.push('-r', 'ts-node/register', '-r', 'nyc-ts-patch'); } const userArgv = this.appRepresentation.argv; if (userArgv && userArgv.length) { execArgv.push.apply(execArgv, userArgv); } const env = Object.assign({}, process.env, this.appRepresentation.env, { [const_1.PANDORA_CWD]: process.cwd(), // require.main === module maybe be 'false' after patched spawn wrap RUN_PROCESS_BOOTSTRAP_BY_FORCE: true }); return new Promise((resolve, reject) => { // Fork it const proc = child_process_1.fork(pathProcessBootstrap, args, { cwd: this.appRepresentation.appDir, execArgv, stdio: ['ipc', 'pipe', 'pipe'], env }); proc.once('message', (message) => { if (message.action === const_1.APP_START_SUCCESS) { const msg = `Application [appName = ${this.appRepresentation.appName}, processName = ${this.appRepresentation.processName || 'null'}, dir = ${this.appDir}, pid = ${proc.pid}] started successfully!`; daemonLogger.info(msg); nodejsStdout.info(msg); this.state = const_1.State.complete; resolve(); } else if (message.action === const_1.APP_START_ERROR) { this.stop().catch((err) => { daemonLogger.error(err); nodejsStdout.error(err); }).then(() => { reject(new Error(`Application [name = ${this.appRepresentation.appName}, dir = ${this.appDir}, pid = ${proc.pid}] start error!`)); }); } }); // TODO: enhance performance proc.stdout.on('data', (data) => { nodejsStdout.write(LoggerBroker_1.removeEOL(data.toString())); }); proc.stderr.on('data', (err) => { nodejsStdout.write(LoggerBroker_1.removeEOL(err.toString())); }); // Here just to distinguish normal exits and exceptional exits, exceptional exits needs to restart proc.once('exit', (code, signal) => { const msg = `Application [name = ${this.appRepresentation.appName}, dir = ${this.appDir}, pid = ${proc.pid}] exit with code ${code} and signal ${signal}`; daemonLogger.info(msg); nodejsStdout.info(msg); switch (this.state) { case const_1.State.complete: // Restart it automatically when it exceptional exits after it start successful this.start().catch(err => { daemonLogger.error('Restart application error'); nodejsStdout.error('Restart application error'); daemonLogger.error(err); nodejsStdout.error(err); }); break; case const_1.State.pending: default: const err = new Error('Start failed, log file: ' + LoggerBroker_1.getAppLogPath(this.name, 'nodejs_stdout')); reject(err); break; } }); this.proc = proc; }); } /** * Stop application through kill * @return {Promise<void>} */ stop() { if (this.state === const_1.State.stopped) { return Promise.resolve(); } this.state = const_1.State.stopped; return new Promise((resolve) => { this.proc.once('exit', () => { this.proc = null; resolve(); }); this.proc.kill('SIGTERM'); }); } /** * Reload application through process message * @param processName * @return {Promise<void>} */ reload(processName) { return new Promise((resolve, reject) => { this.proc.once('message', (message) => { if (message.action === const_1.RELOAD_SUCCESS) { resolve(); } if (message.action === const_1.RELOAD_ERROR) { reject(); } }); this.proc.send({ action: const_1.RELOAD, name: processName, }); }); } } exports.ApplicationHandler = ApplicationHandler; //# sourceMappingURL=ApplicationHandler.js.map