pandora
Version:
A powerful and lightweight application manager for Node.js applications powered by TypeScript.
367 lines • 14.3 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 pandora_dollar_1 = require("pandora-dollar");
const GlobalConfigProcessor_1 = require("../universal/GlobalConfigProcessor");
const assert = require("assert");
const path_1 = require("path");
const fs_1 = require("fs");
const const_1 = require("../const");
const ProcfileReconcilerAccessor_1 = require("./ProcfileReconcilerAccessor");
const child_process_1 = require("child_process");
const os_1 = require("os");
const uuid = require("uuid");
const mzFs = require("mz/fs");
const foundAll = Symbol();
/**
* Class ProcfileReconciler
* TODO: Add more description
*/
class ProcfileReconciler {
constructor(appRepresentation) {
this.appRepresentation = null;
this.procfileBasePath = null;
this.discovered = false;
this.procfileReconcilerAccessor = null;
this.environmentClass = null;
this.processes = [];
this.services = [];
this.appRepresentation = appRepresentation;
this.procfileReconcilerAccessor = new ProcfileReconcilerAccessor_1.ProcfileReconcilerAccessor(this);
// Attach default procfile
const { procfile: defaultProcfile } = GlobalConfigProcessor_1.GlobalConfigProcessor.getInstance().getAllProperties();
this.callProcfile(defaultProcfile);
}
get uniqServices() {
const nameMap = new Map;
const ret = [];
for (const service of this.services.reverse()) {
if (!nameMap.has(service.serviceName)) {
nameMap.set(service.serviceName, true);
ret.push(service);
}
}
return ret.reverse();
}
get appDir() {
assert(this.appRepresentation && this.appRepresentation.appDir, 'Can not get appDir from ProcfileReconciler.appRepresentation, it should passed from time of constructing ProcfileReconciler');
return this.appRepresentation.appDir;
}
/**
* Find out all possibly profile.js paths
* @return {Array}
*/
resovle() {
const retSet = [];
const appDir = this.appDir;
const findBasePath = [
path_1.join(appDir, 'node_modules/.bin'),
appDir
];
for (const basePath of findBasePath) {
for (const alias of const_1.PROCFILE_NAMES) {
const targetPath = path_1.join(basePath, alias);
if (fs_1.existsSync(targetPath)) {
retSet.push(targetPath);
}
}
}
return retSet;
}
/**
* Discover procfile.js in appDir, and apply them.
*/
discover() {
if (this.discovered) {
return;
}
const procfileTargets = this.resovle();
for (const target of procfileTargets) {
const targetMod = require(target);
const entryFn = 'function' === typeof targetMod ? targetMod : targetMod.default;
assert('function' === typeof entryFn, 'The procfile should export a function, during loading ' + target);
this.callProcfile(entryFn, getProcfileBasePath(target));
}
this.discovered = true;
}
/**
* callProcfile required a argument as typed function, then call that function, pass ProcfileReconcilerAccessor as the first argument of that function.
* @param entryFn
* @param path
*/
callProcfile(entryFn, path) {
try {
this.procfileBasePath = path || null;
/**
* inject a pandora object
* example: exports = (pandora) => {}
*/
entryFn(this.procfileReconcilerAccessor);
}
finally {
this.procfileBasePath = null;
}
}
/**
* Normalize entry class, entry class such as service class
* Those classes have a lazy way to represent, it can get a relative path
* this method will wrap that relative path to a real class
* @param entry
* @return {EntryClass}
*/
normalizeEntry(entry) {
if ('string' === typeof entry && this.procfileBasePath) {
const procfileBasePath = this.procfileBasePath;
function getLazyClass() {
const targetMod = pandora_dollar_1.makeRequire(procfileBasePath)(entry);
const TargetClass = 'function' === typeof targetMod ? targetMod : targetMod.default;
return TargetClass;
}
function LazyEntry(option) {
const LazyClass = getLazyClass();
return new LazyClass(option);
}
LazyEntry.lazyEntryMadeBy = entry;
LazyEntry.lazyName = path_1.basename(entry, path_1.extname(entry));
LazyEntry.getLazyClass = getLazyClass;
return LazyEntry;
}
return entry;
}
/**
* setDefaultServiceCategory
* @param {CategoryReg} name
*/
setDefaultServiceCategory(name) {
this.defaultServiceCategory = name;
}
/**
* getDefaultServiceCategory
* @return {CategoryReg}
*/
getDefaultServiceCategory() {
if (!this.defaultServiceCategory) {
throw new Error('Should ProcfileReconciler.setDefaultServiceCategory() before ProcfileReconciler.getDefaultServiceCategory().');
}
return this.defaultServiceCategory;
}
/**
* Define process representation
* @param processRepresentation
* @return {ProcessRepresentation}
*/
defineProcess(processRepresentation) {
processRepresentation = Object.assign({ env: {}, argv: [] }, this.appRepresentation, processRepresentation, { entryFileBaseDir: this.procfileBasePath });
this.processes.push(processRepresentation);
return processRepresentation;
}
/**
* Get a process representation by name
* @param lookingFor
* @return {ProcessRepresentation}
*/
getProcessByName(lookingFor) {
for (const process of this.processes) {
if (process.processName === lookingFor) {
return process;
}
}
return null;
}
/**
* Drop a process representation by name
*/
dropProcessByName(lookingFor) {
for (let idx = 0, len = this.processes.length; idx < len; idx++) {
const process = this.processes[idx];
if (lookingFor === process.processName) {
this.processes.splice(idx, 1);
return;
}
}
throw new Error(`Can\'t drop a process named ${lookingFor} it not exist`);
}
/**
* Inject environment class
* @param {Entry} entry
*/
injectEnvironment(entry) {
this.environmentClass = this.normalizeEntry(entry);
}
/**
* Get environment class
* @return {EntryClass}
*/
getEnvironment() {
if (!this.environmentClass) {
throw new Error('Should ProcfileReconciler.injectEnvironment() before ProcfileReconciler.getEnvironment().');
}
return this.environmentClass;
}
/**
* Inject service class
* @param serviceRepresentation
* @return {ServiceRepresentation}
*/
injectService(serviceRepresentation) {
const serviceEntry = this.normalizeEntry(serviceRepresentation.serviceEntry);
const ret = Object.assign({ config: {} }, serviceRepresentation, { serviceName: serviceRepresentation.serviceName || serviceEntry.lazyName || serviceEntry.serviceName || serviceEntry.name, category: serviceRepresentation.category || this.getDefaultServiceCategory(), serviceEntry: serviceEntry });
this.services.push(ret);
return ret;
}
/**
* Get a service representation by name
* @param lookingFor
* @return {ServiceRepresentation}
*/
getServiceByName(lookingFor) {
for (const service of this.services) {
if (lookingFor === service.serviceName) {
return service;
}
}
return null;
}
/**
* Drop a service representation by name
*/
dropServiceByName(lookingFor) {
for (let idx = 0, len = this.services.length; idx < len; idx++) {
const service = this.services[idx];
if (lookingFor === service.serviceName) {
this.services.splice(idx, 1);
return;
}
}
throw new Error(`Can\'t drop a service named ${lookingFor} it not exist`);
}
/**
* Get services by category
* @param {string} category
* @return {ServiceRepresentation[]}
*/
getServicesByCategory(category, simple) {
const serviceFullSet = this.uniqServices;
const retSet = [];
for (const service of serviceFullSet) {
if (service.category === category || category === 'all'
|| service.category === 'all' || service.category === 'weak-all') {
if (simple) {
retSet.push(service);
continue;
}
const serviceEntry = service.serviceEntry.getLazyClass
? service.serviceEntry.getLazyClass() : service.serviceEntry;
// Sucks code below, just unique the dependencies array...
service.dependencies = [...new Set((serviceEntry.dependencies || []).concat(service.dependencies || []))];
retSet.push(service);
}
}
return retSet;
}
getAvailableProcessMap() {
const availableProcessMap = {};
/**
* Allocate services
*/
for (const service of this.getServicesByCategory('all', true)) {
if (service.category === 'all') {
return foundAll;
}
if (service.category === 'weak-all') {
continue;
}
const process = this.getProcessByName(service.category);
if (!process) {
throw new Error(`Can't allocate service ${service.serviceName} at category ${service.category} to any process.`);
}
availableProcessMap[service.category] = true;
}
return availableProcessMap;
}
/**
* Get the application's structure
* @returns {ApplicationStructureRepresentation}
*/
getApplicationStructure() {
const availableProcessMap = this.getAvailableProcessMap();
const processRepresentations = [];
for (const process of this.processes) {
if (process.mode === 'profile.js' &&
foundAll === availableProcessMap || availableProcessMap.hasOwnProperty(process.processName)) {
processRepresentations.push(this.processGlobalForProcess(process));
}
}
const processRepresentationSet2nd = processRepresentations.sort((a, b) => {
return a.order - b.order;
});
return Object.assign({}, this.appRepresentation, { mode: 'procfile.js', process: processRepresentationSet2nd });
}
/**
* Get the complex application's structure
* @returns {ApplicationStructureRepresentation}
*/
getComplexApplicationStructureRepresentation() {
const processes = this.processes;
const mount = [];
const applicationStructure = this.getApplicationStructure();
if (applicationStructure.process.length) {
mount.push(applicationStructure);
}
for (const process of processes) {
if (process.mode === 'fork') {
mount.push(this.processGlobalForProcess(process));
}
}
return { mount };
}
static echoComplex(appRepresentation, writeTo) {
const procfileReconciler = new ProcfileReconciler(appRepresentation);
procfileReconciler.discover();
const complex = procfileReconciler.getComplexApplicationStructureRepresentation();
fs_1.writeFileSync(writeTo, JSON.stringify(complex));
}
static getComplexViaNewProcess(appRepresentation) {
return __awaiter(this, void 0, void 0, function* () {
const tmpFile = path_1.join(os_1.tmpdir(), uuid.v4());
const isTs = /\.ts$/.test(__filename);
yield new Promise((resolve, reject) => {
child_process_1.exec(`${process.execPath} ${isTs ? '-r ts-node/register' : ''} -e 'require("${__filename}").ProcfileReconciler.echoComplex(${JSON.stringify(appRepresentation)}, ${JSON.stringify(tmpFile)})'`, (error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
const fileBuffer = yield mzFs.readFile(tmpFile);
yield mzFs.unlink(tmpFile);
const fileContent = fileBuffer.toString();
const complex = JSON.parse(fileContent);
return complex;
});
}
processGlobalForProcess(process) {
const argv = process.globalArgv ? (process.argv || []).concat(process.globalArgv) : process.argv;
const env = process.globalEnv ? Object.assign({}, process.env, process.globalEnv) : process.env;
return Object.assign({}, process, { argv, env });
}
}
exports.ProcfileReconciler = ProcfileReconciler;
/**
* Get procfile's dirname through resolved symlink
* @param tagetPath
* @return {any}
*/
function getProcfileBasePath(tagetPath) {
const resolvedTarget = pandora_dollar_1.resolveSymlink(tagetPath);
return path_1.dirname(resolvedTarget);
}
//# sourceMappingURL=ProcfileReconciler.js.map