pandora
Version:
A powerful and lightweight application manager for Node.js applications powered by TypeScript.
178 lines • 7.71 kB
JavaScript
;
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