UNPKG

pandora

Version:

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

367 lines 14.3 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 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