UNPKG

pandora

Version:
451 lines 16.9 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 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 LoggerBroker_1 = require("../universal/LoggerBroker"); const ProcfileReconcilerAccessor_1 = require("./ProcfileReconcilerAccessor"); const child_process_1 = require("child_process"); 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.configuratorClass = null; this.environmentClass = null; this.processes = []; this.services = []; this.applets = []; 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 uniqApplets() { const nameMap = new Map; const ret = []; for (const applet of this.applets.reverse()) { if (!nameMap.has(applet.appletName)) { nameMap.set(applet.appletName, true); ret.push(applet); } } 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) { try { 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)); } catch (err) { LoggerBroker_1.consoleLogger.error('Fail to load procfile from path ' + target); throw err; } } 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 applet class, service class and configurator 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; } /** * Convert class name to instance name * @param {string} name * @return {string} */ normalizeName(name) { return name; } /** * setDefaultAppletCategory * @param {CategoryReg} name */ setDefaultAppletCategory(name) { this.defaultAppletCategory = name; } /** * getDefaultAppletCategory * @return {CategoryReg} */ getDefaultAppletCategory() { if (!this.defaultAppletCategory) { throw new Error('Should ProcfileReconciler.setDefaultAppletCategory() before ProcfileReconciler.getDefaultAppletCategory().'); } return this.defaultAppletCategory; } /** * 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({}, this.appRepresentation, processRepresentation, { entryFileBaseDir: this.procfileBasePath }); this.processes.push(processRepresentation); return processRepresentation; } /** * Get a process representation by name * @param processName * @return {ProcessRepresentation} */ getProcessByName(processName) { for (const process of this.processes) { if (process.processName === processName) { return process; } } return null; } /** * Inject configurator class * @param {Entry} entry */ injectConfigurator(entry) { this.configuratorClass = this.normalizeEntry(entry); } /** * Get configurator class * @return {EntryClass} */ getConfigurator() { if (!this.configuratorClass) { throw new Error('Should ProcfileReconciler.injectConfigurator() before ProcfileReconciler.getConfigurator().'); } return this.configuratorClass; } /** * 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({}, serviceRepresentation, { serviceName: this.normalizeName(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 entry string or class * @param lookingFor * @return {ServiceRepresentation} */ getServiceByEntry(lookingFor) { for (const service of this.services) { if (matchEntry(lookingFor, service.serviceEntry)) { return service; } } return null; } /** * Get services by category * @param {string} category * @return {ServiceRepresentation[]} */ getServicesByCategory(category) { const serviceFullSet = this.uniqServices; const retSet = []; for (const service of serviceFullSet) { if (service.category === category || category === 'all' || service.category === 'all' || service.category === 'weak-all') { 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; } /** * Inject applet class * @param {AppletRepresentation} appletRepresentation * @return {{appletName: string; category: (CategoryReg | any); appletEntry: EntryClass}} */ injectApplet(appletRepresentation) { const appletEntry = this.normalizeEntry(appletRepresentation.appletEntry); const ret = Object.assign({}, appletRepresentation, { appletName: this.normalizeName(appletRepresentation.appletName || appletEntry.lazyName || appletEntry.appletName || appletEntry.name), category: appletRepresentation.category || this.getDefaultAppletCategory(), appletEntry }); this.applets.push(ret); return ret; } /** * Get a applet representation by entry string or class * @param lookingFor * @return {ApplicationRepresentation} */ getAppletByEntry(lookingFor) { for (const applet of this.applets) { if (matchEntry(lookingFor, applet.appletEntry)) { return applet; } } return null; } /** * Get applets by category * @param {string} category * @return {AppletRepresentation[]} */ getAppletsByCategory(category) { const appletFullSet = this.uniqApplets; const retSet = []; for (const applet of appletFullSet) { if (applet.category === category || category === 'all' || applet.category === 'all' || applet.category === 'weak-all') { retSet.push(applet); } } return retSet; } getAvailableProcessMap() { const availableProcessMap = {}; /** * Allocate applets */ for (const applet of this.getAppletsByCategory('all')) { if (applet.category === 'all') { return foundAll; } if (applet.category === 'weak-all') { continue; } const process = this.getProcessByName(applet.category); if (!process) { throw new Error(`Can't allocate applet ${applet.appletName} at category ${applet.category} to any process.`); } availableProcessMap[applet.category] = true; } /** * Allocate services */ for (const service of this.getServicesByCategory('all')) { 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(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(process); } } return { mount }; } static echoComplex(appRepresentation) { const procfileReconciler = new ProcfileReconciler(appRepresentation); procfileReconciler.discover(); const complex = procfileReconciler.getComplexApplicationStructureRepresentation(); // PLS Keep console.log below, it is useful console.log(JSON.stringify(complex, null, 2)); } static getComplexViaNewProcess(appRepresentation) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { child_process_1.exec(`${process.execPath} ${/\.ts$/.test(__filename) ? '-r ts-node/register' : ''} -e 'require("${__filename}").ProcfileReconciler.echoComplex(${JSON.stringify(appRepresentation)})'`, (error, stdout) => { if (error) { reject(error); return; } try { const complex = JSON.parse(stdout.toString()); resolve(complex); } catch (err) { reject(err); } }); }); }); } } 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); } function matchEntry(userLooking, entry) { return !!(userLooking === entry || userLooking === entry.lazyEntryMadeBy); } //# sourceMappingURL=ProcfileReconciler.js.map