UNPKG

pandora

Version:

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

246 lines 9.72 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const pandora_dollar_1 = require("pandora-dollar"); 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 uuid = require("uuid"); const mzFs = require("mz/fs"); const os = require("os"); const tmpdir = process.env.PANDORA_TMP_DIR || os.tmpdir(); const foundAll = Symbol(); /** * Class ProcfileReconciler */ class ProcfileReconciler { constructor(appRepresentation) { this.appRepresentation = null; this.procfileBasePath = null; this.discovered = false; this.procfileReconcilerAccessor = null; this.processes = []; this.appRepresentation = appRepresentation; this.procfileReconcilerAccessor = new ProcfileReconcilerAccessor_1.ProcfileReconcilerAccessor(this); } 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; } /** * Define process representation * @param processRepresentation * @return {ProcessRepresentation} */ defineProcess(processRepresentation) { const processRepresentation2nd = Object.assign(Object.assign(Object.assign({ env: {}, execArgv: [], args: [], scale: 1 }, this.appRepresentation), processRepresentation), { entryFileBaseDir: this.procfileBasePath }); this.processes.push(processRepresentation2nd); return processRepresentation2nd; } /** * 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`); } /** * Get all available processes * @return {any} */ getAvailableProcessMap() { const availableProcessMap = {}; for (const process of this.processes) { if (process.entryFile && !availableProcessMap.hasOwnProperty(process.processName)) { availableProcessMap[process.processName] = true; } } return availableProcessMap; } /** * Get the Static Structure of the Application * @return {ApplicationStructureRepresentation} */ getApplicationStructure() { const availableProcessMap = this.getAvailableProcessMap(); const processRepresentations = []; let offset = 0; for (const process of this.processes) { if (foundAll === availableProcessMap || availableProcessMap.hasOwnProperty(process.processName)) { const newProcess = this.makeupProcess(process); newProcess.offset = offset; offset += newProcess.scale > 1 ? newProcess.scale + 1 : 1; processRepresentations.push(newProcess); } } const processRepresentationSet2nd = processRepresentations.sort((a, b) => { return a.order - b.order; }); return Object.assign(Object.assign({}, this.appRepresentation), { process: processRepresentationSet2nd }); } /** * Echo the appRepresentation to a file * For static getStructureViaNewProcess() read * @param {ApplicationRepresentation} appRepresentation * @param {string} writeTo */ static echoStructure(appRepresentation, writeTo) { const procfileReconciler = new ProcfileReconciler(appRepresentation); procfileReconciler.discover(); const structure = procfileReconciler.getApplicationStructure(); fs_1.writeFileSync(writeTo, JSON.stringify(structure)); } /** * Get the appRepresentation via a tmp process * Make sure daemon will not got any we don\'t want to be included * @param {ApplicationRepresentation} appRepresentation * @return {Promise<ApplicationStructureRepresentation>} */ static async getStructureViaNewProcess(appRepresentation) { const tmpFile = path_1.join(tmpdir, uuid.v4()); const isTs = /\.ts$/.test(__filename); await new Promise((resolve, reject) => { child_process_1.exec(`${process.execPath} ${isTs ? '-r ts-node/register' : ''} -e 'require("${__filename}").ProcfileReconciler.echoStructure(${JSON.stringify(appRepresentation)}, ${JSON.stringify(tmpFile)}); process.exit()'`, { cwd: appRepresentation.appDir || process.cwd() }, (error) => { if (error) { reject(error); return; } resolve(); }); }); const fileBuffer = await mzFs.readFile(tmpFile); await mzFs.unlink(tmpFile); const fileContent = fileBuffer.toString(); const structure = JSON.parse(fileContent); return structure; } /** * * @param process * @return {ProcessRepresentation} */ makeupProcess(process) { // globalArgv and globalEnv passed from CLI, marge those to the related field const execArgv = process.globalExecArgv ? (process.execArgv || []).concat(process.globalExecArgv) : process.execArgv; const args = process.globalArgs ? (process.args || []).concat(process.globalArgs) : process.args; const env = process.globalEnv ? Object.assign(Object.assign({}, process.env), process.globalEnv) : process.env; // Resolve 'auto' to cpus().length const scale = process.scale === 'auto' ? const_1.defaultWorkerCount : process.scale; return Object.assign(Object.assign({}, process), { execArgv, args, env, scale }); } } 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