UNPKG

devebot

Version:

Nodejs Microservice Framework

546 lines (539 loc) 18.7 kB
"use strict"; const path = require("path"); const util = require("util"); const lodash = require("lodash"); const minimist = require("minimist"); const appinfoLoader = require("./backbone/appinfo-loader"); const ConfigLoader = require("./backbone/config-loader"); const ContextManager = require("./backbone/context-manager"); const LoggingWrapper = require("./backbone/logging-wrapper"); const IssueInspector = require("./backbone/issue-inspector"); const StateInspector = require("./backbone/state-inspector"); const PackageStocker = require("./backbone/package-stocker"); const ManifestHandler = require("./backbone/manifest-handler"); const NameResolver = require("./backbone/name-resolver"); const chores = require("./utils/chores"); const constx = require("./utils/constx"); const envbox = require("./utils/envbox"); const errors = require("./utils/errors"); const nodash = require("./utils/nodash"); const Runner = require("./runner"); const Server = require("./server"); const blockRef = chores.getBlockRef(__filename); const _isUpgradeSupported = chores.isUpgradeSupported.bind(chores); const issueInspector = IssueInspector.instance; const stateInspector = StateInspector.instance; const packageStocker = PackageStocker.instance; const FRAMEWORK_NAMESPACE = constx.FRAMEWORK.NAMESPACE; const FRAMEWORK_PACKAGE_NAME = constx.FRAMEWORK.PACKAGE_NAME; const FRAMEWORK_CAPNAME = lodash.capitalize(FRAMEWORK_NAMESPACE); const FRAMEWORK_BRIDGE_LABEL = "bridge"; const FRAMEWORK_PLUGIN_LABEL = "plugin"; const REGISTER_LAYERWARE_OPTION_NAMES = ["name", "type", "path", "formers", "presets"]; function appLoader(params = {}) { const { logger: L, tracer: T } = params; L.has("silly") && L.log("silly", T.add({ context: lodash.cloneDeep(params) }).toMessage({ tags: [blockRef, "constructor-begin", "appLoader"], text: " + application loading start ..." })); const appRootPath = params.appRootPath; const libRootPaths = lodash.map(params.pluginRefs, function (pluginRef) { return pluginRef.path; }); const topRootPath = path.join(__dirname, "/.."); const appInfo = appinfoLoader(appRootPath, libRootPaths, topRootPath); const appName = params.appName || appInfo.name || FRAMEWORK_NAMESPACE + "-application"; const options = { privateProfile: params.privateProfile || params.privateProfiles, privateSandbox: params.privateSandbox || params.privateSandboxes, privateTexture: params.privateTexture || params.privateTextures }; L.has("dunce") && L.log("dunce", T.add({ appName }).toMessage({ text: " - application name (appName): ${appName}" })); const appRef = { type: "application", name: appName }; if (lodash.isString(appRootPath)) { appRef.path = appRootPath; } if (lodash.isObject(params.presets)) { appRef.presets = lodash.cloneDeep(params.presets); } const frameworkRef = { type: "framework", name: FRAMEWORK_PACKAGE_NAME, path: topRootPath }; // declare user-defined environment variables const currentEnvNames = envbox.getEnvNames(); const evDescriptors = lodash.get(params, ["environmentVarDescriptors"], []); const duplicated = lodash.filter(evDescriptors, function (ev) { return currentEnvNames.indexOf(ev.name) >= 0; }); if (duplicated.length > 0) { issueInspector.collect({ hasError: true, stage: "bootstrap", type: "application", name: appName, stack: duplicated.map(function (ev) { const evName = chores.stringLabelCase(appName) + "_" + ev.name; return util.format("- Environment Variable '%s' has already been defined", evName); }).join("\n") }); } else { envbox.define(evDescriptors); } // freeze occupied environment variables envbox.setNamespace(chores.stringLabelCase(appName), { occupyValues: params.environmentVarOccupied, ownershipLabel: util.format("<owned-by-%s>", appName) }); const bridgeList = lodash.values(params.bridgeRefs); const pluginList = lodash.values(params.pluginRefs); const bundleList = [].concat(appRef, pluginList, frameworkRef); const nameResolver = new NameResolver({ issueInspector, bridgeList, pluginList }); const manifestHandler = new ManifestHandler({ nameResolver, issueInspector, bridgeList, bundleList }); stateInspector.register({ nameResolver, bridgeList, pluginList }); const configLoader = new ConfigLoader({ options, appRef, frameworkRef, pluginRefs: params.pluginRefs, bridgeRefs: params.bridgeRefs, nameResolver, issueInspector, stateInspector, manifestHandler }); const contextManager = new ContextManager({ issueInspector }); contextManager.addDefaultFeatures(params.defaultFeatures); const _app_ = {}; const _ref_ = {}; Object.defineProperty(_app_, "config", { get: function () { if (_ref_.config === undefined || _ref_.config === null) { _ref_.config = configLoader.load(); _ref_.config.appName = appName; _ref_.config.appInfo = appInfo; _ref_.config.bridgeList = bridgeList; _ref_.config.bundleList = bundleList; if (!_isUpgradeSupported("config-extended-fields")) { _ref_.config.bridgeRefs = bridgeList; // @Deprecated _ref_.config.pluginRefs = bundleList; // @Deprecated } } return _ref_.config; }, set: function (value) {} }); Object.defineProperty(_app_, "runner", { get: function () { _ref_.runner = _ref_.runner || new Runner({ appName, appInfo, bridgeList, bundleList, configObject: this.config, contextManager, issueInspector, stateInspector, nameResolver, manifestHandler }); return _ref_.runner; }, set: function (value) {} }); Object.defineProperty(_app_, "server", { get: function () { _ref_.server = _ref_.server || new Server({ appName, appInfo, bridgeList, bundleList, configObject: this.config, contextManager, issueInspector, stateInspector, nameResolver, manifestHandler }); return _ref_.server; }, set: function (value) {} }); L.has("silly") && L.log("silly", T.toMessage({ tags: [blockRef, "constructor-end", "appLoader"], text: " - Application loading has done" })); return _app_; } const ATTRS = ["libRootPaths", "pluginRefs", "bridgeRefs"]; function registerLayerware(context, pluginNames, bridgeNames) { if (arguments.length < 3 && lodash.isArray(context)) { bridgeNames = pluginNames; pluginNames = context; context = null; } context = lodash.isString(context) ? { layerRootPath: context } : context; if (!lodash.isEmpty(context)) { const result = chores.validate(context, constx.BOOTSTRAP.registerLayerware.context.schema); if (!result.ok) { issueInspector.collect({ stage: "bootstrap", type: "application", name: "registerLayerware", hasError: true, stack: JSON.stringify(result.errors, null, 4) }); } } context = context || {}; const loggingWrapper = new LoggingWrapper(blockRef); context.logger = loggingWrapper.getLogger(); context.tracer = loggingWrapper.getTracer(); if (!lodash.isEmpty(pluginNames)) { const result = chores.validate(pluginNames, constx.BOOTSTRAP.registerLayerware.plugins.schema); if (!result.ok) { issueInspector.collect({ stage: "bootstrap", type: "application", name: "registerLayerware", hasError: true, stack: JSON.stringify(result.errors, null, 4) }); } } if (!lodash.isEmpty(bridgeNames)) { const result = chores.validate(bridgeNames, constx.BOOTSTRAP.registerLayerware.bridges.schema); if (!result.ok) { issueInspector.collect({ stage: "bootstrap", type: "application", name: "registerLayerware", hasError: true, stack: JSON.stringify(result.errors, null, 4) }); } } function initialize(context, pluginNames, bridgeNames, accumulator) { context = context || {}; accumulator = accumulator || {}; const { logger: L, tracer: T } = context; lodash.defaults(accumulator, lodash.pick(context, ["logger", "tracer"])); if (context.layerRootPath && context.layerRootPath != accumulator.libRootPath) { L.has("warn") && L.log("warn", T.add({ layerRootPath: context.layerRootPath, libRootPath: accumulator.libRootPath }).toMessage({ text: " - layerRootPath is different with libRootPath" })); } if (typeof context.layerRootPath === "string" && context.layerRootPath.length > 0) { accumulator.libRootPaths = accumulator.libRootPaths || []; accumulator.libRootPaths.push(context.layerRootPath); } if (!_isUpgradeSupported("presets")) { return expandExtensions(accumulator, pluginNames, bridgeNames); } if (accumulator.libRootPath) { const newPresets = context.presets || {}; const oldPresets = lodash.get(accumulator, ["pluginRefs", accumulator.libRootPath, "presets"], null); if (oldPresets) { lodash.defaultsDeep(oldPresets, newPresets); } else { lodash.set(accumulator, ["pluginRefs", accumulator.libRootPath, "presets"], newPresets); } } return expandExtensions(accumulator, pluginNames, bridgeNames); } return initialize.bind(undefined, context, pluginNames, bridgeNames); } function launchApplication(context, pluginNames, bridgeNames) { context = lodash.isString(context) ? { appRootPath: context } : context; if (!lodash.isEmpty(context)) { const result = chores.validate(context, constx.BOOTSTRAP.launchApplication.context.schema); if (!result.ok) { issueInspector.collect({ stage: "bootstrap", type: "application", name: "launchApplication", hasError: true, stack: JSON.stringify(result.errors, null, 4) }); } } context = context || {}; if (lodash.isString(context.appRootPath)) { context.libRootPath = context.appRootPath; } const loggingWrapper = new LoggingWrapper(blockRef); context.logger = loggingWrapper.getLogger(); context.tracer = loggingWrapper.getTracer(); if (!lodash.isEmpty(pluginNames)) { const result = chores.validate(pluginNames, constx.BOOTSTRAP.launchApplication.plugins.schema); if (!result.ok) { issueInspector.collect({ stage: "bootstrap", type: "application", name: "launchApplication", hasError: true, stack: JSON.stringify(result.errors, null, 4) }); } } if (!lodash.isEmpty(bridgeNames)) { const result = chores.validate(bridgeNames, constx.BOOTSTRAP.launchApplication.bridges.schema); if (!result.ok) { issueInspector.collect({ stage: "bootstrap", type: "application", name: "launchApplication", hasError: true, stack: JSON.stringify(result.errors, null, 4) }); } } return appLoader(lodash.assign(context, expandExtensions(lodash.omit(context, ATTRS), pluginNames, bridgeNames))); } function expandExtensions(accumulator, pluginNames, bridgeNames) { accumulator = accumulator || {}; accumulator.bridgeRefs = accumulator.bridgeRefs || {}; accumulator.pluginRefs = accumulator.pluginRefs || {}; const context = lodash.pick(accumulator, ATTRS.concat(["logger", "tracer"])); const { logger: L, tracer: T } = context; context.libRootPaths = context.libRootPaths || []; context.bridgeRefs = context.bridgeRefs || {}; context.pluginRefs = context.pluginRefs || {}; bridgeNames = nodash.arrayify(bridgeNames || []); pluginNames = nodash.arrayify(pluginNames || []); const CTX = { issueInspector }; const bridgeInfos = lodash.map(bridgeNames, function (bridgeName) { const item = lodash.isString(bridgeName) ? { name: bridgeName, path: bridgeName } : bridgeName; item.type = FRAMEWORK_BRIDGE_LABEL; if (!_isUpgradeSupported("presets")) { return item; } item.path = locatePackage(CTX, item, FRAMEWORK_BRIDGE_LABEL); return item; }); const pluginInfos = lodash.map(pluginNames, function (pluginName) { const item = lodash.isString(pluginName) ? { name: pluginName, path: pluginName } : pluginName; item.type = FRAMEWORK_PLUGIN_LABEL; if (!_isUpgradeSupported("presets")) { return item; } item.path = locatePackage(CTX, item, FRAMEWORK_PLUGIN_LABEL); return item; }); // create the bridge & plugin dependencies if (lodash.isString(accumulator.libRootPath)) { const crateRef = accumulator.pluginRefs[accumulator.libRootPath]; if (lodash.isObject(crateRef)) { crateRef.bridgeDepends = lodash.map(bridgeInfos, function (item) { return item.name; }); crateRef.pluginDepends = lodash.map(pluginInfos, function (item) { return item.name; }); L.has("debug") && L.log("debug", T.add({ libRootPath: accumulator.libRootPath, crateObject: crateRef }).toMessage({ text: " - crate '${libRootPath}' object: ${crateObject}" })); } else { L.has("warn") && L.log("warn", T.add({ libRootPath: accumulator.libRootPath }).toMessage({ text: " - crate '${libRootPath}' hasnot defined" })); } } const bridgeDiffs = lodash.differenceWith(bridgeInfos, lodash.keys(context.bridgeRefs), function (bridgeInfo, bridgeKey) { if (!_isUpgradeSupported("presets")) { return bridgeInfo.name == bridgeKey; } return bridgeInfo.path == bridgeKey; }); const pluginDiffs = lodash.differenceWith(pluginInfos, lodash.keys(context.pluginRefs), function (pluginInfo, pluginKey) { if (!_isUpgradeSupported("presets")) { return pluginInfo.name == pluginKey; } return pluginInfo.path == pluginKey; }); bridgeDiffs.forEach(function (bridgeInfo) { if (!_isUpgradeSupported("presets")) { context.bridgeRefs[bridgeInfo.name] = { name: bridgeInfo.name, type: bridgeInfo.type, path: locatePackage(CTX, bridgeInfo, FRAMEWORK_BRIDGE_LABEL) }; return; } const inc = lodash.pick(bridgeInfo, REGISTER_LAYERWARE_OPTION_NAMES); context.bridgeRefs[bridgeInfo.path] = lodash.assign(context.bridgeRefs[bridgeInfo.path], inc); }); pluginDiffs.forEach(function (pluginInfo) { if (!_isUpgradeSupported("presets")) { context.pluginRefs[pluginInfo.name] = { name: pluginInfo.name, type: pluginInfo.type, path: locatePackage(CTX, pluginInfo, FRAMEWORK_PLUGIN_LABEL) }; return; } const inc = lodash.pick(pluginInfo, REGISTER_LAYERWARE_OPTION_NAMES); context.pluginRefs[pluginInfo.path] = lodash.assign(context.pluginRefs[pluginInfo.path], inc); }); issueInspector.barrier({ invoker: blockRef, footmark: "package-touching" }); const pluginInitializers = lodash.map(pluginDiffs, function (pluginInfo) { if (!_isUpgradeSupported("presets")) { return require(pluginInfo.path); } return { path: pluginInfo.path, initializer: require(pluginInfo.path) }; }); return pluginInitializers.reduce(function (params, pluginInitializer) { if (!_isUpgradeSupported("presets")) { return pluginInitializer(params); } params.libRootPath = pluginInitializer.path; return pluginInitializer.initializer(params); }, context); } const bootstrap = {}; bootstrap.registerLayerware = registerLayerware; bootstrap.launchApplication = launchApplication; // @Deprecated bootstrap.parseArguments = function (active) { return this.initialize("actions", { enabled: active, forced: true }); }; bootstrap.initialize = function (action, options = {}) { if (["actions", "tasks"].indexOf(action) >= 0) { if (options.enabled !== false) { const argv = minimist(process.argv.slice(2)); const tasks = argv.tasks || argv.actions; if (lodash.isEmpty(tasks)) { if (options.forced && !lodash.isEmpty(argv._)) { _consoleInfo("Incorrect task(s). Should be: (--tasks=print-config,check-config)"); _processExit(0); } } else { const jobs = stateInspector.init({ tasks }); if (lodash.isEmpty(jobs)) { _consoleInfo("Unknown task(s): (%s)!", tasks); _processExit(0); } } } } return this; }; bootstrap.declare = packageStocker.declare.bind(packageStocker); // @Deprecated bootstrap.require = packageStocker.require.bind(packageStocker); bootstrap.modules = packageStocker.modules; function locatePackage({ issueInspector } = {}, pkgInfo, pkgType) { chores.assertOk(issueInspector, pkgInfo, pkgInfo.name, pkgInfo.type || pkgType, pkgInfo.path); try { const entrypoint = require.resolve(pkgInfo.path); const buf = {}; buf.packagePath = path.dirname(entrypoint); buf.packageJson = chores.loadPackageInfo(buf.packagePath); while (buf.packageJson === null) { const parentPath = path.dirname(buf.packagePath); if (parentPath === buf.packagePath) break; buf.packagePath = parentPath; buf.packageJson = chores.loadPackageInfo(buf.packagePath); } if (nodash.isObject(buf.packageJson)) { if (nodash.isString(buf.packageJson.main)) { const verifiedPath = require.resolve(path.join(buf.packagePath, buf.packageJson.main)); if (verifiedPath !== entrypoint) { const MismatchedMainError = errors.assertConstructor("PackageError"); throw new MismatchedMainError("package.json file's [main] attribute is mismatched"); } } if (nodash.isString(pkgInfo.name)) { if (pkgInfo.name !== buf.packageJson.name) { const MismatchedNameError = errors.assertConstructor("PackageError"); throw new MismatchedNameError("package name is different with provided name"); } } } else { const InvalidPackageError = errors.assertConstructor("PackageError"); throw new InvalidPackageError("package.json file is not found or has invalid format"); } return buf.packagePath; } catch (err) { issueInspector.collect({ hasError: true, stage: "bootstrap", type: pkgInfo.type, name: pkgInfo.name, stack: err.stack }); return null; } } function _processExit() { return process.exit.apply(process, arguments); } const _consoleInfo = console.info.bind(console); module.exports = global[FRAMEWORK_NAMESPACE] = global[FRAMEWORK_CAPNAME] = bootstrap;