pandora
Version:
A powerful and lightweight application manager for Node.js applications powered by TypeScript.
177 lines (155 loc) • 5.32 kB
text/typescript
;
require('source-map-support').install();
import program = require('commander');
import {ERROR, FINISH_SHUTDOWN, READY, SHUTDOWN} from '../const';
import {WorkerContext} from './WorkerContext';
import {ProcessRepresentation} from '../domain';
import {ProcfileReconciler} from './ProcfileReconciler';
import assert = require('assert');
import {EnvironmentUtil} from 'pandora-env';
import {consoleLogger, getPandoraLogsDir} from '../universal/LoggerBroker';
import {MonitorManager} from '../monitor/MonitorManager';
/**
* class WorkerProcessBootstrap
* Bootstrap a worker process, handing all phases of an application stating
*/
export class WorkerProcessBootstrap {
public context: WorkerContext;
public entry: string;
public processRepresentation: ProcessRepresentation;
private procfileReconciler: ProcfileReconciler;
/**
* @param {string} entry - where is the entry file of this process of the application. that can be null,
* use the ProcfileReconciler to start if that be null.
* @param {ProcessRepresentation} processRepresentation - ProcessRepresentation object
*/
constructor(entry: string, processRepresentation: ProcessRepresentation) {
this.entry = entry;
this.processRepresentation = processRepresentation;
this.procfileReconciler = new ProcfileReconciler(processRepresentation);
this.context = new WorkerContext(processRepresentation);
}
/**
* Start process
* 1. use the ProcfileReconciler to start if this.entry is not given
* 2. use the this.entry to start if this.entry is given
* @return {Promise<void>}
*/
async start() {
if (this.entry) {
await this.startByEntry();
return;
}
await this.startByProcfile();
}
async stop() {
if (this.context) {
await this.context.stop();
}
}
/**
* Use procfile.js to start
* @returns {Promise<void>}
*/
async startByProcfile() {
const {mode} = this.processRepresentation;
if ('procfile.js' === mode) {
// Beginning discover the process structure by ProcfileReconciler if mode be procfile.js
this.procfileReconciler.discover();
} else {
throw new Error(`Unknown mode ${mode}`);
}
if(!EnvironmentUtil.getInstance().isReady()) {
// Handing the environment object injecting
const Environment = this.procfileReconciler.getEnvironment();
const environment = new Environment({
appDir: this.processRepresentation.appDir,
appName: this.processRepresentation.appName,
processName: this.processRepresentation.processName,
pandoraLogsDir: getPandoraLogsDir()
});
EnvironmentUtil.getInstance().setCurrentEnvironment(environment);
}
// To start worker process monitoring
MonitorManager.injectProcessMonitor();
// Handing the services injecting
const servicesByCurrentCategory = this.procfileReconciler.getServicesByCategory(this.processRepresentation.processName);
this.context.bindService(servicesByCurrentCategory);
// To start process by WorkerContext
await this.context.start();
}
/**
* use this.entry to start
* @returns {Promise<any>}
*/
async startByEntry() {
const entryMod = require(this.entry);
const entryFn = 'function' === typeof entryMod ? entryMod : entryMod.default;
assert('function' === typeof entryFn, 'The entry should export a function, during loading ' + this.entry);
return await new Promise((resolve, reject) => {
entryFn({...this.processRepresentation}, (err) => {
if (err) {
return reject(err);
}
resolve();
});
});
}
/**
* A static method to handing the CLI
*/
static cmd() {
program
.option('--entry [entry]')
.option('--params [params]')
.parse(process.argv);
const entry = program.entry;
let options;
try {
options = JSON.parse(program.params);
assert(options.appName, 'appName required by WorkerProcessBootstrap');
assert(options.appDir, 'appDir required by WorkerProcessBootstrap');
assert(options.processName, 'processName required by WorkerProcessBootstrap');
} catch (err) {
err.message = `invalid options "${program.params}", ${err.message}`;
consoleLogger.error(err);
if (process.send) {
process.send({action: ERROR, error: err});
}
return;
}
const workerBootstrap = new WorkerProcessBootstrap(entry, options);
workerBootstrap.start().then(() => {
process.on('message', (message) => {
if (message.action === SHUTDOWN) {
workerBootstrap.stop().then(() => {
if (process.send) {
process.send({action: FINISH_SHUTDOWN});
}
});
}
});
if (process.send) {
process.send({action: READY});
}
}).catch((err) => {
consoleLogger.error(err);
consoleLogger.error('an error occurred during the start of WorkerProcessBootstrap.');
if (process.send) {
process.send({action: ERROR, error: err});
}
});
}
}
let cmdDid = false;
export function cmd() {
if (cmdDid) {
return;
}
cmdDid = true;
WorkerProcessBootstrap.cmd();
}
// Handing CLI if this module be the main module
if (require.main === module) {
cmd();
}