pandora
Version:
A powerful and lightweight application manager for Node.js applications powered by TypeScript.
246 lines • 9.72 kB
JavaScript
;
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