devebot
Version:
Nodejs Microservice Framework
724 lines (723 loc) • 22 kB
JavaScript
"use strict";
const assert = require("assert");
const lodash = require("lodash");
const semver = require("semver");
const crypto = require("crypto");
const fs = require("fs");
const os = require("os");
const path = require("path");
const util = require("util");
const LogConfig = require("logolite").LogConfig;
const uuidv4 = require("logolite/uuidv4");
const format = require("logolite/lib/format");
const Validator = require("schemato").Validator;
const constx = require("./constx");
const loader = require("./loader");
const envbox = require("./envbox");
const nodash = require("./nodash");
const getenv = require("./getenv");
const FRAMEWORK_NAMESPACE = constx.FRAMEWORK.NAMESPACE;
const FRAMEWORK_NAMESPACE_UCASE = lodash.toUpper(FRAMEWORK_NAMESPACE);
const FRAMEWORK_PACKAGE_NAME = constx.FRAMEWORK.PACKAGE_NAME;
const codetags = require("codetags").getInstance(FRAMEWORK_PACKAGE_NAME, {
namespace: FRAMEWORK_NAMESPACE,
INCLUDED_TAGS: "UPGRADE_ENABLED",
EXCLUDED_TAGS: "UPGRADE_DISABLED",
version: constx.FRAMEWORK.VERSION
}).register(constx.UPGRADE_TAGS);
const store = {
defaultScope: getenv(FRAMEWORK_NAMESPACE_UCASE + "_DEFAULT_SCOPE", FRAMEWORK_PACKAGE_NAME),
injektorOptions: {
namePatternTemplate: "^[@a-zA-Z]{1}[a-zA-Z0-9&#\\-_%s]*$",
separator: "/"
},
injektorContext: {
scope: FRAMEWORK_PACKAGE_NAME
},
validatorOptions: {
schemaVersion: 4
}
};
const chores = {};
chores.assertOk = function () {
for (const k in arguments) {
assert.ok(arguments[k], util.format("The argument #%s evaluated to a falsy value", k));
}
};
chores.getUUID = function () {
return uuidv4();
};
chores.formatTemplate = function (tmpl, data) {
return format(tmpl, data);
};
chores.loadPackageInfo = function (pkgRootPath, selectedFieldNames, defaultInfo) {
try {
const pkgJson = JSON.parse(fs.readFileSync(path.join(pkgRootPath, "/package.json"), "utf8"));
if (lodash.isArray(selectedFieldNames)) {
return lodash.pick(pkgJson, selectedFieldNames);
}
return pkgJson;
} catch (err) {
return defaultInfo || null;
}
};
chores.getFirstDefinedValue = function () {
for (const i in arguments) {
const val = arguments[i];
if (val !== undefined && val !== null) return val;
}
return undefined;
};
chores.kickOutOf = function (map, excludedNames) {
if (lodash.isObject(map) && lodash.isArray(excludedNames)) {
lodash.forEach(excludedNames, function (name) {
if (name in map) delete map[name];
});
}
};
chores.isOwnOrInheritedProperty = function (object, property) {
for (const propName in object) {
if (propName === property) return true;
}
return object.hasOwnProperty(property);
};
chores.pickProperty = function (propName, containers, propDefault) {
if (!lodash.isString(propName) || !lodash.isArray(containers)) return null;
for (const i in containers) {
if (lodash.isObject(containers[i]) && containers[i][propName]) {
return containers[i][propName];
}
}
return propDefault;
};
chores.deepFreeze = function (o) {
const self = this;
Object.freeze(o);
Object.getOwnPropertyNames(o).forEach(function (prop) {
if (o.hasOwnProperty(prop) && (nodash.isObject(o[prop]) || nodash.isFunction(o[prop])) && !Object.isFrozen(o[prop])) {
self.deepFreeze(o[prop]);
}
});
return o;
};
chores.fileExists = function (filepath) {
return fs.existsSync(filepath);
};
chores.filterFiles = function (dir, filter, filenames) {
filenames = filenames || [];
const regex = assertFilterRegExp(filter);
try {
const files = readDir(dir);
for (const i in files) {
if (regex ? regex.test(files[i]) : true) {
const name = path.join(dir, files[i]);
if (isFile(name)) {
filenames.push(files[i]);
}
}
}
} catch (err) {
return filenames;
}
return filenames;
};
function assertFilterRegExp(filter) {
if (lodash.isRegExp(filter)) {
return filter;
}
if (lodash.isString(filter)) {
return new RegExp(filter);
}
return null;
}
function readDir(dir) {
return fs.readdirSync(dir);
}
function isFile(filepath) {
const fileStat = fs.statSync(filepath, {
throwIfNoEntry: false
});
return fileStat && fileStat.isFile() || false;
}
chores.loadServiceByNames = function (serviceMap, serviceFolder, serviceNames) {
serviceNames = nodash.arrayify(serviceNames);
serviceNames.forEach(function (serviceName) {
const filepath = path.join(serviceFolder, serviceName + ".js");
const serviceConstructor = loader(filepath);
if (lodash.isFunction(serviceConstructor)) {
const serviceEntry = {};
serviceEntry[chores.stringCamelCase(serviceName)] = serviceConstructor;
lodash.defaults(serviceMap, serviceEntry);
}
});
return serviceMap;
};
chores.stringKebabCase = function kebabCase(str) {
if (!nodash.isString(str)) return str;
return str.toLowerCase().replace(/\s{1,}/g, "-");
};
chores.stringLabelCase = function labelCase(str) {
if (!nodash.isString(str)) return str;
return str.toUpperCase().replace(/\W{1,}/g, "_");
};
chores.stringCamelCase = function camelCase(str) {
if (!nodash.isString(str)) return str;
return str.replace(/-([a-z])/g, function (m, w) {
return w.toUpperCase();
}).replace(/-([0-9])/g, function (m, w) {
return "_" + w;
});
};
chores.assertDir = function (appName) {
const configDir = path.join(this.homedir(), "." + appName);
try {
fs.readdirSync(configDir);
} catch (err) {
if (err.code === "ENOENT") {
try {
fs.mkdirSync(configDir);
} catch (err) {
return null;
}
} else {
return null;
}
}
return configDir;
};
chores.homedir = typeof os.homedir === "function" ? os.homedir : function () {
const env = process.env;
const home = env.HOME;
const user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME;
if (process.platform === "win32") {
return env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null;
}
if (process.platform === "darwin") {
return home || (user ? "/Users/" + user : null);
}
if (process.platform === "linux") {
return home || (process.getuid() === 0 ? "/root" : user ? "/home/" + user : null);
}
return home || null;
};
const SPECIAL_BUNDLES = ["application", "framework", FRAMEWORK_PACKAGE_NAME];
chores.isSpecialBundle = function (bundle) {
return SPECIAL_BUNDLES.indexOf(_getBundleType(bundle)) >= 0;
};
chores.isAppboxBundle = function (bundle) {
return _getBundleType(bundle) === "application";
};
chores.isFrameworkBundle = function (bundle) {
const type = _getBundleType(bundle);
return type === "framework" || type === FRAMEWORK_NAMESPACE;
};
function _getBundleType(bundle) {
return lodash.isString(bundle) && bundle || lodash.isObject(bundle) && bundle.type;
}
chores.extractCodeByPattern = function (patterns, name) {
assert.ok(patterns instanceof Array);
for (const k in patterns) {
assert.ok(patterns[k] instanceof RegExp);
}
const info = {};
for (info.i = 0; info.i < patterns.length; info.i++) {
if (name.match(patterns[info.i])) break;
}
if (info.i >= patterns.length) {
return {
i: -1,
code: name
};
}
info.code = name.replace(patterns[info.i], "$1");
return info;
};
chores.getComponentDir = function (pluginRef, componentType) {
const compDir = lodash.get(pluginRef, ["presets", "componentDir"], {});
if (componentType) {
return compDir[componentType] || constx[componentType].SCRIPT_DIR;
}
return compDir;
};
chores.getBlockRef = function (filename, blockScope) {
if (filename == null) return null;
const blockName = chores.stringCamelCase(path.basename(filename, ".js"));
blockScope = blockScope || store.defaultScope;
if (!nodash.isArray(blockScope)) blockScope = [blockScope];
return blockScope.concat(blockName).join(chores.getSeparator());
};
chores.getSeparator = function () {
return store.injektorOptions.separator;
};
const _getFrameworkProfileConfig = function (profileConfig, section, subpath) {
let location = [section];
if (subpath) {
location = location.concat(subpath);
}
return lodash.get(profileConfig, location, null);
};
chores.getFrameworkProfileConfig = function (profileConfig, subpath) {
const o1 = _getFrameworkProfileConfig(profileConfig, "framework", subpath);
if (o1 != null) {
return o1;
}
//
if (!this.isUpgradeSupported("profile-config-field-framework")) {
const o2 = _getFrameworkProfileConfig(profileConfig, constx.LEGACY.PROFILE_CONFIG_FRAMEWORK_FIELD, subpath);
if (o2 != null) {
return o2;
}
}
//
return {};
};
chores.getFullname = function (parts, separator) {
return lodash.filter(parts, lodash.negate(lodash.isEmpty)).join(separator || chores.getSeparator());
};
chores.toFullname = function () {
return lodash.filter(arguments, lodash.negate(lodash.isEmpty)).join(store.injektorOptions.separator);
};
chores.transformBeanName = function (name, opts) {
opts = opts || {};
if (typeof name !== "string") return name;
const pattern = opts.namePattern instanceof RegExp ? opts.namePattern : /^(.+)\/([^:^/]+)$/g;
return name.replace(pattern, "$1:$2");
};
chores.parseScriptTree = function (scriptType, scriptFile, scriptInstance, isHierarchical) {
let entryPath = scriptFile.replace(".js", "").toLowerCase().split("_");
if (entryPath.length > 0 && entryPath[0] !== constx[scriptType].ROOT_KEY) {
entryPath.unshift(constx[scriptType].ROOT_KEY);
}
entryPath = entryPath.reverse();
entryPath.unshift(scriptInstance);
const entry = lodash.reduce(entryPath, function (result, item) {
const nestEntry = {};
nestEntry[item] = result;
return nestEntry;
});
return entry;
};
chores.printError = function (err) {
if (getenv([FRAMEWORK_NAMESPACE_UCASE + "_NODE_ENV", "NODE_ENV"]) !== "test") {
["", "========== FATAL ERROR ==========", err, "---------------------------------", ""].forEach(function (item) {
console.error(item);
});
}
};
chores.logConsole = function () {
return console.info.apply(console, arguments);
};
chores.injektorOptions = store.injektorOptions;
chores.injektorContext = store.injektorContext;
chores.isDevelopmentMode = function () {
return ["test", "dev", "development"].indexOf(envbox.getEnv("ENV")) >= 0;
};
chores.isProductionMode = function () {
return envbox.getEnv("ENV") === "production";
};
chores.fatalErrorReaction = function () {
return envbox.getEnv("FATAL_ERROR_REACTION");
};
chores.skipProcessExit = function () {
return envbox.getEnv("SKIP_PROCESS_EXIT") === "true";
};
chores.isSilentForced = function (moduleIds, cfg) {
const fsm = envbox.getEnv("FORCING_SILENT");
//
moduleIds = nodash.stringToArray(moduleIds);
for (let moduleId of moduleIds) {
if (fsm.indexOf(moduleId) >= 0) {
return true;
}
}
//
return cfg && cfg.verbose === false || false;
};
chores.isVerboseForced = function (moduleIds, cfg) {
const fvm = envbox.getEnv("FORCING_VERBOSE");
//
moduleIds = nodash.stringToArray(moduleIds);
for (let moduleId of moduleIds) {
if (fvm.indexOf(moduleId) >= 0) {
return true;
}
}
//
return !this.isSilentForced(moduleIds, cfg);
};
chores.setEnvironments = function (envMap) {
envbox.setup(envMap);
return this.clearCache();
};
chores.clearCache = function () {
envbox.clearCache();
codetags.clearCache();
LogConfig.reset();
return this;
};
chores.isUpgradeSupported = codetags.isActive.bind(codetags);
chores.dateToString = function (d) {
return d.toISOString().replace(/[:-]/g, "").replace(/[T.]/g, "-");
};
chores.getValidator = function () {
return store.validator = store.validator || new Validator(store.validatorOptions);
};
chores.validate = function (object, schema) {
const result = this.getValidator().validate(object, schema);
if (typeof result.ok === "boolean") {
result.valid = result.ok;
}
return result;
};
chores.argumentsToArray = function (args, l, r) {
if (args && (!Array.isArray(args) && args.length >= 0 || Array.isArray(args) && (l >= 0 || r >= 0))) {
args = Array.prototype.slice.call(args, l || 0, args.length - (r || 0));
}
return args;
};
chores.extractObjectInfo = function (data, opts) {
function detect(data, level = 2) {
if (typeof level !== "number" || level < 0) level = 0;
const type = typeof data;
switch (type) {
case "boolean":
case "number":
case "string":
case "symbol":
case "function":
case "undefined":
{
return type;
}
case "object":
{
if (data === null) {
return "null";
}
if (Array.isArray(data)) {
const info = [];
if (level > 0) {
for (const i in data) {
info.push(detect(data[i], level - 1));
}
}
return info;
} else {
const info = {};
if (level > 0) {
for (const field in data) {
info[field] = detect(data[field], level - 1);
}
}
return info;
}
break;
}
}
return undefined;
}
return detect(data, opts && opts.level);
};
chores.isVersionLT = function (version1, version2) {
if (!semver.valid(version1) || !semver.valid(version2)) return null;
return semver.lt(version1, version2);
};
chores.isVersionLTE = function (version1, version2) {
if (!semver.valid(version1) || !semver.valid(version2)) return null;
return semver.lte(version1, version2);
};
chores.isVersionGT = function (version1, version2) {
if (!semver.valid(version1) || !semver.valid(version2)) return null;
return semver.gt(version1, version2);
};
chores.isVersionGTE = function (version1, version2) {
if (!semver.valid(version1) || !semver.valid(version2)) return null;
return semver.gte(version1, version2);
};
chores.isVersionLessThan = chores.isVersionLT;
chores.isVersionLessThanOrEqualTo = chores.isVersionLTE;
chores.isVersionSatisfied = function (version, versionMask) {
if (semver.valid(version)) {
if (lodash.isString(versionMask)) {
if (version === versionMask) return true;
if (semver.satisfies(version, versionMask)) return true;
}
if (lodash.isArray(versionMask)) {
if (versionMask.indexOf(version) >= 0) return true;
for (const i in versionMask) {
if (semver.satisfies(version, versionMask[i])) return true;
}
}
}
return false;
};
chores.getVersionOf = function (packageName) {
if (packageName === FRAMEWORK_PACKAGE_NAME) {
const pkg = require(path.join(__dirname, "../../package.json"));
return pkg && pkg.version;
} else {
let parentPath, modulePath;
try {
modulePath = parentPath = path.dirname(require.resolve(packageName));
} catch (err) {
return null;
}
do {
try {
const pkg = require(path.join(modulePath, "package.json"));
return pkg.version;
} catch (err) {
parentPath = modulePath;
modulePath = path.dirname(modulePath);
}
} while (modulePath !== parentPath);
return null;
}
};
chores.getNodeVersion = function () {
if ("node" in process.versions) {
return process.versions["node"];
}
const vstr = process.version;
if (typeof vstr === "string") {
return vstr.replace("v", "");
}
return null;
};
chores.renameJsonFields = function (data, nameMappings) {
if (!lodash.isPlainObject(data)) {
return data;
}
if (!lodash.isPlainObject(nameMappings)) {
return data;
}
//
for (const oldName in nameMappings) {
if (!lodash.has(data, oldName)) {
continue;
}
const val = lodash.get(data, oldName);
if (!lodash.isUndefined(val)) {
const newName = nameMappings[oldName];
lodash.unset(data, oldName);
lodash.set(data, newName, val);
}
}
//
return data;
};
function ServiceSelector(kwargs = {}) {
const {
serviceResolver,
sandboxRegistry,
binding
} = kwargs;
assert.ok(this.constructor === ServiceSelector);
assert.ok(serviceResolver && lodash.isString(serviceResolver));
assert.ok(sandboxRegistry && lodash.isObject(sandboxRegistry));
let serviceResolverAvailable = true;
this.lookup = function (serviceName, methodName) {
const hasMethod = typeof methodName === "string" && methodName.length > 0;
let ref = {};
if (serviceResolverAvailable) {
let resolver = sandboxRegistry.lookupService(serviceResolver);
if (resolver) {
ref.proxied = true;
ref.isRemote = true; // @Deprecated
ref.service = resolver.lookupService(serviceName);
if (hasMethod && ref.service) {
ref.method = ref.service[methodName];
}
} else {
serviceResolverAvailable = false;
}
}
if (!ref.service) {
ref.proxied = false;
ref.isRemote = false; // @Deprecated
ref.service = sandboxRegistry.lookupService(serviceName);
}
if (!ref.method && hasMethod && ref.service) {
ref.method = ref.service[methodName];
}
// bind the method to the service
if (binding !== false && ref.method) {
ref.method = ref.method.bind(ref.service);
}
return ref;
};
function extractMethod(resolver, methodIndicator, ref = {}) {
const {
serviceName,
methodName,
portletName
} = methodIndicator || {};
ref.service = resolver.lookupService(serviceName);
//
if (portletName && ref.service) {
if (lodash.isFunction(ref.service.hasPortlet) && lodash.isFunction(ref.service.getPortlet)) {
if (ref.service.hasPortlet(portletName)) {
ref.portlet = ref.service.getPortlet(portletName);
}
}
}
//
if (methodName) {
if (ref.portlet) {
ref.method = ref.portlet[methodName];
if (binding !== false && ref.method) {
ref.method = ref.method.bind(ref.portlet);
}
} else if (ref.service) {
ref.method = ref.service[methodName];
if (binding !== false && ref.method) {
ref.method = ref.method.bind(ref.service);
}
}
}
//
return ref;
}
this.lookupMethod = function (serviceName, methodName, portletName) {
const methodIndicator = {
serviceName,
methodName,
portletName
};
let ref = {};
//
if (serviceResolverAvailable) {
let resolver = sandboxRegistry.lookupService(serviceResolver);
if (resolver) {
ref = extractMethod(resolver, methodIndicator, {
proxied: true
});
ref.isRemote = true; // @Deprecated
} else {
serviceResolverAvailable = false;
}
}
//
if (!ref.service) {
ref = extractMethod(sandboxRegistry, methodIndicator, {
proxied: false
});
ref.isRemote = false; // @Deprecated
}
//
return ref;
};
if (chores.isUpgradeSupported("legacy-service-selector")) {
// @Deprecated
this.lookupMethod = this.lookup;
} else {
this.lookup = this.lookupMethod;
}
}
chores.newServiceSelector = function (kwargs) {
return new ServiceSelector(kwargs);
};
chores.lookupMethodRef = function (methodName, serviceName, proxyName, sandboxRegistry) {
const ref = {};
const commander = sandboxRegistry.lookupService(proxyName);
if (commander && lodash.isFunction(commander.lookupService)) {
ref.isDirected = false;
ref.isRemote = true;
ref.service = commander.lookupService(serviceName);
if (ref.service) {
ref.method = ref.service[methodName];
}
}
if (!ref.method) {
ref.isDirected = true;
ref.isRemote = false;
ref.service = sandboxRegistry.lookupService(serviceName);
if (ref.service) {
ref.method = ref.service[methodName];
}
}
return ref;
};
const HD_ALGORITHMS = ["md5", "sha1", "sha256", "sha512", "ripemd160"];
const HD_ENCODINGS = ["hex", "base64"];
chores.getHashDigest = function (message, {
algorithm,
encoding
} = {}) {
if (HD_ALGORITHMS.indexOf(algorithm) < 0) {
algorithm = "sha1";
}
if (HD_ENCODINGS.indexOf(encoding) < 0) {
encoding = "hex";
}
const hash = crypto.createHash(algorithm);
const data = hash.update(message, "utf-8");
return data.digest(encoding);
};
function newError(errorName, message, opts = {}) {
const err = new Error(message);
err.name = errorName;
for (const fieldName in opts) {
if (opts[fieldName] !== undefined) {
err[fieldName] = opts[fieldName];
}
}
return err;
}
function ErrorBuilder({
packageName,
errorCodes,
defaultLanguage
}) {
const packageRef = chores.getHashDigest(packageName, {
encoding: "base64"
});
this.newError = function (errorName, {
payload,
language
} = {}) {
language = language || defaultLanguage;
const errInfo = errorCodes[errorName];
if (errInfo == null) {
return newError(errorName, "Error[" + errorName + "] unsupported", {
packageRef,
returnCode: -1,
statusCode: 500
});
}
let msg = errInfo.message || errorName;
if (errInfo.messageIn && typeof language === "string") {
msg = errInfo.messageIn[language] || msg;
}
if (payload && typeof payload === "object") {
msg = chores.formatTemplate(msg, payload);
} else {
payload = null;
}
return newError(errorName, msg, {
packageRef,
returnCode: errInfo.returnCode,
statusCode: errInfo.statusCode,
payload: payload
});
};
this.getDescriptor = function () {
return {
packageRef,
errorCodes,
defaultLanguage
};
};
}
chores.newErrorBuilder = function (kwargs) {
return new ErrorBuilder(kwargs);
};
chores.stringifyFunction = function (jsObj) {
jsObj = lodash.mergeWith({}, jsObj, function (objVal, srcVal) {
if (lodash.isFunction(srcVal)) {
return "[Function]";
}
});
return jsObj;
};
module.exports = chores;