actionhero
Version:
The reusable, scalable, and quick node.js API server for stateless and stateful applications
194 lines (193 loc) • 8.79 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.rebuildConfig = exports.config = void 0;
exports.buildConfig = buildConfig;
const fs = require("fs");
const path = require("path");
const utils_1 = require("./utils");
const ensureNoTsHeaderOrSpecFiles_1 = require("./utils/ensureNoTsHeaderOrSpecFiles");
const env_1 = require("./../classes/process/env");
const id_1 = require("./../classes/process/id");
const actionheroVersion_1 = require("./../classes/process/actionheroVersion");
const typescript_1 = require("./../classes/process/typescript");
const projectRoot_1 = require("./../classes/process/projectRoot");
const safeGlob_1 = require("./utils/safeGlob");
function buildConfig() {
var _a;
const configPaths = [];
let config = {
process: {
env: env_1.env,
id: id_1.id,
typescript: typescript_1.typescript,
projectRoot: projectRoot_1.projectRoot,
actionheroVersion: actionheroVersion_1.actionheroVersion,
},
};
// We support multiple configuration paths as follows:
//
// 1. Use the project 'config' folder, if it exists.
// 2. "actionhero --config=PATH1 --config=PATH2 --config=PATH3,PATH4"
// 3. "ACTIONHERO_CONFIG=PATH1,PATH2 npm start"
// 4. "ACTIONHERO_CONFIG_OVERRIDES" (stringified JSON) can partially override any of the config objects loaded from the above
//
// Note that if --config or ACTIONHERO_CONFIG are used, they _overwrite_ the use of the default "config" folder. If
// you wish to use both, you need to re-specify "config", e.g. "--config=config,local-config". Also, note that
// specifying multiple --config options on the command line does exactly the same thing as using one parameter with
// comma separators, however the environment variable method only supports the comma-delimited syntax.
function addConfigPath(pathToCheck, alreadySplit) {
if (typeof pathToCheck === "string") {
if (!alreadySplit) {
addConfigPath(pathToCheck.split(","), true);
}
else {
if (pathToCheck.charAt(0) !== "/") {
pathToCheck = path.resolve(projectRoot_1.projectRoot, pathToCheck);
}
if (fs.existsSync(pathToCheck)) {
configPaths.push(pathToCheck);
}
}
}
else if (Array.isArray(pathToCheck)) {
pathToCheck.map((entry) => {
addConfigPath(entry, alreadySplit);
});
}
}
[(_a = utils_1.utils.argv.config) === null || _a === void 0 ? void 0 : _a.toString(), process.env.ACTIONHERO_CONFIG].map((entry) => {
addConfigPath(entry, false);
});
if (configPaths.length < 1 && typescript_1.typescript) {
addConfigPath("src/config", false);
}
if (configPaths.length < 1) {
addConfigPath("dist/config", false);
}
if (configPaths.length < 1) {
throw new Error(configPaths +
"No config directory found in this project. Did you compile your typescript project?");
}
const loadConfigFile = (f) => {
const localConfig = require(f);
if (f.includes("routes.js") || f.includes("routes.ts")) {
// We don't want to merge in routes from Actionhero core unless we are running core directly
// Routes can be loaded by plugins via `registerRoute`
if (f.includes(`${path.sep}node_modules${path.sep}actionhero${path.sep}`)) {
return;
}
let localRoutes = { routes: {} };
if (localConfig.DEFAULT) {
// @ts-ignore
localRoutes = utils_1.utils.hashMerge(localRoutes, localConfig.DEFAULT, config);
}
if (localConfig[env_1.env]) {
// @ts-ignore
localRoutes = utils_1.utils.hashMerge(localRoutes, localConfig[env_1.env], config);
}
Object.keys(localRoutes.routes).forEach((v) => {
if (config.routes && config.routes[v]) {
config.routes[v].push(...localRoutes.routes[v]);
}
else {
if (!config.routes)
config.routes = {};
config.routes[v] = localRoutes.routes[v];
}
});
}
else {
if (localConfig.DEFAULT) {
config = utils_1.utils.hashMerge(config, localConfig.DEFAULT, config);
}
if (localConfig[env_1.env]) {
config = utils_1.utils.hashMerge(config, localConfig[env_1.env], config);
}
}
};
const loadConfigDirectory = (configPath, watch) => {
const configFiles = (0, ensureNoTsHeaderOrSpecFiles_1.ensureNoTsHeaderOrSpecFiles)((0, safeGlob_1.safeGlobSync)(path.join(configPath, "**", "**/*(*.js|*.ts)")));
let loadRetries = 0;
let loadErrors = {};
for (let i = 0, limit = configFiles.length; i < limit; i++) {
const f = configFiles[i];
try {
// attempt configuration file load
loadConfigFile(f);
// configuration file load success: clear retries and
// errors since progress has been made
loadRetries = 0;
loadErrors = {};
}
catch (error) {
// error loading configuration, abort if all remaining
// configuration files have been tried and failed
// indicating inability to progress
loadErrors[f] = { error: error, msg: error.toString() };
if (++loadRetries === limit - i) {
Object.keys(loadErrors).forEach((e) => {
console.log(loadErrors[e].error.stack);
console.log("");
delete loadErrors[e].error;
});
throw new Error("Unable to load configurations, errors: " +
JSON.stringify(loadErrors));
}
// adjust configuration files list: remove and push
// failed configuration to the end of the list and
// continue with next file at same index
configFiles.push(configFiles.splice(i--, 1)[0]);
continue;
}
}
// We load the config twice. Utilize configuration files load order that succeeded on the first pass.
// This is to allow 'literal' values to be loaded whenever possible, and then for references to be resolved
configFiles.forEach(loadConfigFile);
// Remove duplicate routes since we might be loading from multiple config directories, also we load every
// config directory twice.
if (config.routes) {
Object.keys(config.routes).forEach((v) => {
config.routes[v] = config.routes[v].filter((route, index, self) => index ===
self.findIndex((r) => r.path === route.path &&
r.action === route.action &&
r.apiVersion === route.apiVersion &&
r.matchTrailingPathParts === route.matchTrailingPathParts &&
r.dir === route.dir));
});
}
};
// load the default config of actionhero
loadConfigDirectory(path.join(__dirname, "/../config"), false);
// load the project specific config
configPaths.map((p) => loadConfigDirectory(p, false));
if (process.env.ACTIONHERO_CONFIG_OVERRIDES) {
try {
config = utils_1.utils.hashMerge(config, JSON.parse(process.env.ACTIONHERO_CONFIG_OVERRIDES));
}
catch (error) {
throw new Error(`could not parse ACTIONHERO_CONFIG_OVERRIDES: ${error}`);
}
}
if (utils_1.utils.argv.ACTIONHERO_CONFIG_OVERRIDES) {
try {
config = utils_1.utils.hashMerge(config, JSON.parse(utils_1.utils.argv.ACTIONHERO_CONFIG_OVERRIDES.toString()));
}
catch (error) {
throw new Error(`could not parse ACTIONHERO_CONFIG_OVERRIDES: ${error}`);
}
}
return config;
}
exports.config = buildConfig();
/**
* Rebuild Actionhero's `config` object. Useful when Environment variables effecting the config may have changed.
*/
const rebuildConfig = () => {
(0, env_1.recalculateEnv)();
(0, actionheroVersion_1.recalculateActionheroVersion)();
(0, id_1.recalcuateId)();
(0, projectRoot_1.recalculateProjectRoot)();
(0, typescript_1.recalculateIsTypescript)();
exports.config = buildConfig();
};
exports.rebuildConfig = rebuildConfig;