UNPKG

@grouparoo/core

Version:
285 lines (284 loc) 12.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.deleteLockedObjects = exports.processConfigObjects = exports.shouldExternallyValidate = exports.loadConfigObjects = exports.loadConfigDirectory = exports.getSeenIds = void 0; const actionhero_1 = require("actionhero"); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const glob_1 = __importDefault(require("glob")); const json5_1 = __importDefault(require("json5")); const codeConfig_1 = require("../../classes/codeConfig"); const errors_1 = require("../../config/errors"); const model_1 = require("./model"); const app_1 = require("./app"); const source_1 = require("./source"); const property_1 = require("./property"); const apiKey_1 = require("./apiKey"); const team_1 = require("./team"); const teamMember_1 = require("./teamMember"); const group_1 = require("./group"); const schedule_1 = require("./schedule"); const setting_1 = require("./setting"); const destination_1 = require("./destination"); const configWriter_1 = require("../configWriter"); const record_1 = require("./record"); const sequelize_1 = __importDefault(require("sequelize")); const deprecation_1 = require("../deprecation"); const pluralize_1 = __importDefault(require("pluralize")); const freshIdsByClass = () => ({ model: [], app: [], source: [], property: [], group: [], schedule: [], destination: [], apikey: [], team: [], teammember: [], record: [], }); function getSeenIds(configObjects) { return configObjects.reduce((agg, co) => { var _a; const klass = (0, codeConfig_1.cleanClass)(co); if (co.id && klass) { //@ts-ignore (_a = agg[klass]) === null || _a === void 0 ? void 0 : _a.push(co.id); } return agg; }, freshIdsByClass()); } exports.getSeenIds = getSeenIds; async function loadConfigDirectory(configDir, externallyValidate = true) { let seenIds = {}; let deletedIds = {}; let errors = []; if (configDir) { const { configObjects, errors: loadErrors } = await loadConfigObjects(configDir); if (loadErrors.length > 0) { return { errors: loadErrors, seenIds, deletedIds }; } ({ errors, seenIds, deletedIds } = await processConfigObjects(configObjects, externallyValidate)); } return { seenIds, errors, deletedIds }; } exports.loadConfigDirectory = loadConfigDirectory; async function loadConfigObjects(configDir) { if (!configDir) return { configObjects: [], errors: [] }; const globSearch = path_1.default.join(configDir, "**", "+(*.json|*.js)"); const configFiles = glob_1.default.sync(globSearch); let configObjects = []; let errors = []; for (const file of configFiles) { const objects = await loadConfigFile(file); const nullishObjects = objects.filter((o) => o === null || o === undefined); if (nullishObjects.length > 0) { errors.push(`The file \`${file}\` has ${nullishObjects.length} null or undefined top-level config ${(0, pluralize_1.default)("object")}`); } configObjects = configObjects .concat(objects) .filter((o) => o && Object.keys(o).length > 0); // skip empty objects } return { configObjects, errors }; } exports.loadConfigObjects = loadConfigObjects; async function loadConfigFile(file) { let payload = {}; if (file.match(/\.json$/)) { const contents = fs_1.default.readFileSync(file).toString(); if (contents.trim().includes("{")) payload = json5_1.default.parse(contents); } else { payload = require(file); } const payloadKeys = Object.keys(payload); if (payloadKeys.length === 1 && payloadKeys[0] === "default") { payload = payload.default; } if (typeof payload === "function") { payload = await payload(actionhero_1.config); } const objects = Array.isArray(payload) ? payload : [payload]; configWriter_1.ConfigWriter.cacheConfigFile({ absFilePath: file, objects }); return objects; } async function shouldExternallyValidate(canExternallyValidate, configObject, locallyValidateIds) { if (!canExternallyValidate) return false; if (!locallyValidateIds) return true; const objectId = configObject.id; const parentId = await (0, codeConfig_1.getDirectParentId)(configObject); if (parentId && locallyValidateIds.has(parentId)) { locallyValidateIds.add(objectId); } if (locallyValidateIds.has(objectId)) { return false; } return true; } exports.shouldExternallyValidate = shouldExternallyValidate; async function processConfigObjects(configObjects, canExternallyValidate, locallyValidateIds, validate = false) { var _a; const seenIds = freshIdsByClass(); const errors = []; const { errors: validationErrors } = (0, codeConfig_1.validateConfigObjects)(configObjects); validationErrors.map((err) => (0, actionhero_1.log)(`[ config ] ${err}`, actionhero_1.env === "test" ? "info" : "error")); errors.push(...validationErrors); if (errors.length > 0) { return { seenIds, errors, deletedIds: {} }; } try { configObjects = await (0, codeConfig_1.sortConfigurationObjects)(configObjects); } catch (error) { // If something we wrong while sorting, log the messages and return. We // aren't going to process the config objects if we can't be confident we're // doing it in the right order. error.message.split("\n").map((msg) => { if (msg.startsWith("unknownNodeId")) { msg = `Could not find object with ID: ${msg.slice(14).split(":")[1]}`; } const err = new Error(msg); errors.push(err.message); (0, actionhero_1.log)(msg, "error"); }); return { seenIds: {}, errors, deletedIds: {} }; } if (locallyValidateIds) { const configObjectIds = configObjects.map((o) => o.id); locallyValidateIds.forEach((id) => !configObjectIds.includes(id) && (0, actionhero_1.log)(`[ config ] tried to locally validate \`${id}\`, but an object with that ID does not exist`, "warning")); } // Delete unseen config objects ahead of time const deletedIds = await deleteLockedObjects(getSeenIds(configObjects)); for (const configObject of configObjects) { if (Object.keys(configObject).length === 0) continue; let klass = (_a = configObject === null || configObject === void 0 ? void 0 : configObject.class) === null || _a === void 0 ? void 0 : _a.toLowerCase(); let ids; const externallyValidate = await shouldExternallyValidate(canExternallyValidate, configObject, locallyValidateIds); if (!externallyValidate) { (0, actionhero_1.log)(`[ config ] skipping external validation for ${configObject.class} \`${configObject.id}\``, "notice"); } try { switch (klass) { case "setting": ids = await (0, setting_1.loadSetting)(configObject, externallyValidate, validate); break; case "model": ids = await (0, model_1.loadModel)(configObject, externallyValidate, validate); break; case "app": ids = await (0, app_1.loadApp)(configObject, externallyValidate, validate); break; case "source": ids = await (0, source_1.loadSource)(configObject, configObjects, externallyValidate, validate); break; case "property": ids = await (0, property_1.loadProperty)(configObject, externallyValidate, validate); break; case "group": ids = await (0, group_1.loadGroup)(configObject, externallyValidate, validate); break; case "schedule": ids = await (0, schedule_1.loadSchedule)(configObject, externallyValidate, validate); break; case "destination": ids = await (0, destination_1.loadDestination)(configObject, externallyValidate, validate); break; case "apikey": ids = await (0, apiKey_1.loadApiKey)(configObject, externallyValidate, validate); break; case "team": ids = await (0, team_1.loadTeam)(configObject, externallyValidate, validate); break; case "teammember": ids = await (0, teamMember_1.loadTeamMember)(configObject, externallyValidate, validate); break; case "record": ids = await (0, record_1.loadRecord)(configObject, externallyValidate, validate); break; case "profile": deprecation_1.Deprecation.warnChanged("config", "Profile", "Record"); ids = await (0, record_1.loadRecord)(configObject, externallyValidate, validate); break; default: throw new Error(`unknown config object class: ${configObject.class}`); } } catch (error) { // Normally, we can can keep going after an error and keep checking the other config objects // but, there's some types of errors (like unique key duplicates) which pollute or abort the transaction and we need to stop if (error instanceof sequelize_1.default.DatabaseError) { (0, actionhero_1.log)(`TRANSACTION ABORTED at SequelizeDatabaseError at query: ${error.sql}`, "debug"); throw new Error(`Sequelize Database Error with Config object for ${configObject === null || configObject === void 0 ? void 0 : configObject.class //@ts-ignore } \`${configObject["key"] || configObject["name"]}\`(${configObject.id}). Cannot validate additional objects.`); } const { message, fields } = (0, errors_1.GrouparooErrorSerializer)(error); const errorMessage = `[ config ] error with ${configObject === null || configObject === void 0 ? void 0 : configObject.class} \`${ //@ts-ignore configObject["key"] || configObject["name"]}\` (${configObject.id}): ${message}`; errors.push(errorMessage); (0, actionhero_1.log)((error === null || error === void 0 ? void 0 : error.stack) || error, "debug"); if (fields.length === 0) { (0, actionhero_1.log)(errorMessage, "warning"); continue; } else { (0, actionhero_1.log)(errorMessage, "error"); throw new Error(`Cannot validate additional objects.`); } } // should set ids in all cases for (const className in ids) { //@ts-ignore const newIds = ids[className]; //@ts-ignore seenIds[className].push(...newIds); } } return { seenIds, errors, deletedIds }; } exports.processConfigObjects = processConfigObjects; async function deleteLockedObjects(seenIds) { const deletedIds = freshIdsByClass(); if (seenIds.teammember) { deletedIds["teammember"] = await (0, teamMember_1.deleteTeamMembers)(seenIds.teammember); } if (seenIds.team) { deletedIds["team"] = await (0, team_1.deleteTeams)(seenIds.team); } if (seenIds.apikey) { deletedIds["apikey"] = await (0, apiKey_1.deleteApiKeys)(seenIds.apikey); } if (seenIds.destination) { deletedIds["destination"] = await (0, destination_1.deleteDestinations)(seenIds.destination); } if (seenIds.schedule) { deletedIds["schedule"] = await (0, schedule_1.deleteSchedules)(seenIds.schedule); } if (seenIds.group) { deletedIds["group"] = await (0, group_1.deleteGroups)(seenIds.group); } if (seenIds.property) { deletedIds["property"] = await (0, property_1.deleteProperties)(seenIds.property); } if (seenIds.source) { deletedIds["source"] = await (0, source_1.deleteSources)(seenIds.source); } if (seenIds.app) { deletedIds["app"] = await (0, app_1.deleteApps)(seenIds.app); } if (seenIds.model) { deletedIds["model"] = await (0, model_1.deleteModels)(seenIds.model); } return deletedIds; } exports.deleteLockedObjects = deleteLockedObjects;