@grouparoo/core
Version:
The Grouparoo Core
205 lines (204 loc) • 9.11 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigWriter = void 0;
const actionhero_1 = require("actionhero");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const prettier_1 = __importDefault(require("prettier"));
const App_1 = require("../models/App");
const GrouparooModel_1 = require("../models/GrouparooModel");
const Destination_1 = require("../models/Destination");
const Group_1 = require("../models/Group");
const Property_1 = require("../models/Property");
const Setting_1 = require("../models/Setting");
const Source_1 = require("../models/Source");
const codeConfig_1 = require("../classes/codeConfig");
const pluginDetails_1 = require("../modules/pluginDetails");
const GrouparooRecord_1 = require("../models/GrouparooRecord");
const runMode_1 = require("./runMode");
/**
* Loading
* ----------------------------------------
* 1. Loader tells Writer to cache all files it read.
* 2. Writer caches files that are writable (i.e. not locked). It ignores locked
* files.
* 3. Individual loaders ask the writer for a lock key. Writer responds with the
* key unless it has a cached object.
*
*
* Writing
* ----------------------------------------
* 1. Delete all files in the cache.
* 2. Query the database for all writable objects. These are UNLOCKED Apps,
* Sources (and their Schedules), Properties, Groups, and Destinations.
* 3. Each config object is retrieved from the model, written to file, and then
* cached.
*
*/
let CONFIG_FILE_CACHE = [];
var ConfigWriter;
(function (ConfigWriter) {
// ---------------------------------------- | Helpers
function generateId(name, separator = "_") {
if (!name || name.length === 0)
return;
const id = name
.toLowerCase()
// replace bad characters with a space
.replace(/[^a-zA-Z0-9\-_ ]/g, " ")
// remove spaces from beginning and end
.trim()
// replace spaces with underscore
.replace(/[ ]/g, separator)
// replace multiple word separators with an underscore
.replace(/[\-_ ][\-_ ]+/g, separator);
if (id.length === 0)
throw new Error("Could not generate ID from name.");
return id;
}
ConfigWriter.generateId = generateId;
function generateFilePath(object, prefix) {
var _a;
const id = Array.isArray(object) ? (_a = object[0]) === null || _a === void 0 ? void 0 : _a.id : object === null || object === void 0 ? void 0 : object.id;
if (!id)
return;
let filePath = `${id}.json`;
if (prefix)
filePath = `${prefix}/${filePath}`;
return filePath;
}
ConfigWriter.generateFilePath = generateFilePath;
// ---------------------------------------- | Controllers
async function run() {
// If we're not in config mode, do nothing.
if ((0, runMode_1.getGrouparooRunMode)() !== "cli:config")
return;
// Any models we see before starting would be from existing code config
// files.
if (!actionhero_1.api.process.started)
return;
// Get the config objects before deleting any of the current objects. Then,
// if we run into an error, we leave what we had before and don't rewrite
// the config files until the objects are fixed through the UI.
const configObjects = await getConfigObjects();
await deleteFiles();
await writeFiles(configObjects);
return configObjects;
}
ConfigWriter.run = run;
async function getConfigObjects() {
let objects = [];
const queryParams = { where: { locked: null } };
const queries = {
models: await GrouparooModel_1.GrouparooModel.findAll(queryParams),
apps: await App_1.App.findAll(queryParams),
sources: await Source_1.Source.findAll(queryParams),
properties: await Property_1.Property.findAll(queryParams),
groups: await Group_1.Group.findAll(queryParams),
destinations: await Destination_1.Destination.findAll(queryParams),
records: await GrouparooRecord_1.GrouparooRecord.findAll({ include: [GrouparooModel_1.GrouparooModel] }),
};
const clusterNameSetting = await Setting_1.Setting.findOne({
where: { pluginName: "core", key: "cluster-name" },
});
if (clusterNameSetting.value !== clusterNameSetting.defaultValue) {
queries["settings"] = [clusterNameSetting];
}
for (let [type, instances] of Object.entries(queries)) {
if (!instances)
continue;
for (let instance of instances) {
const object = await instance.getConfigObject();
// Don't process arrays that have objects missing id values.
if (Array.isArray(object) && object.filter((o) => !o.id).length > 0) {
continue;
}
// Don't process objects that have missing id values.
if (!Array.isArray(object) && !(object === null || object === void 0 ? void 0 : object.id))
continue;
const filePath = instance instanceof GrouparooRecord_1.GrouparooRecord
? `development/${(await instance.$get("model")).getConfigId()}/records.json`
: generateFilePath(object, type);
const index = objects.findIndex((obj) => obj.filePath === filePath);
if (index >= 0) {
objects[index].object = [objects[index].object, object].flat();
}
else {
objects.push({ filePath, object });
}
}
}
return objects;
}
ConfigWriter.getConfigObjects = getConfigObjects;
// ---------------------------------------- | Config File Cache
function getConfigFileCache() {
return CONFIG_FILE_CACHE;
}
ConfigWriter.getConfigFileCache = getConfigFileCache;
function resetConfigFileCache() {
CONFIG_FILE_CACHE = [];
}
ConfigWriter.resetConfigFileCache = resetConfigFileCache;
function getLockKey(configObject) {
if ((0, runMode_1.getGrouparooRunMode)() !== "cli:config") {
return (0, codeConfig_1.getCodeConfigLockKey)();
}
if (isLockable(configObject)) {
return "config:writer";
}
// If we are in config mode and the file is not lockable (it is JSON file),
// we return null. A null value is equivalent to the object being unlocked.
return null;
}
ConfigWriter.getLockKey = getLockKey;
function fileIsLockable(absFilePath) {
// Otherwise, it is lockable if it is a JS file.
const ext = path_1.default.extname(absFilePath);
return ext !== ".json";
}
function isLockable(object) {
const isMatch = (o) => o.id === object.id && o.class === object.class;
const cachedFileObj = CONFIG_FILE_CACHE.find((cache) => cache.objects.filter(isMatch).length > 0);
// If there is no cached file, we assume the file is locked. This is because
// the Writer does not cache locked files.
if (!cachedFileObj)
return true;
// Otherwise, check the file itself.
return fileIsLockable(cachedFileObj.absFilePath);
}
async function cacheConfigFile(cacheObj) {
if (fileIsLockable(cacheObj.absFilePath))
return null;
CONFIG_FILE_CACHE.push(cacheObj);
}
ConfigWriter.cacheConfigFile = cacheConfigFile;
// ---------------------------------------- | File Writers
async function deleteFiles() {
for (let { absFilePath } of CONFIG_FILE_CACHE) {
if (fs_1.default.existsSync(absFilePath))
fs_1.default.unlinkSync(absFilePath);
}
resetConfigFileCache();
}
async function writeFile({ filePath, object }) {
const configDir = await (0, pluginDetails_1.getConfigDir)(true);
const configFilePath = path_1.default.join(configDir, filePath);
const dir = path_1.default.dirname(configFilePath);
if (!fs_1.default.existsSync(dir))
fs_1.default.mkdirSync(dir, { recursive: true });
const stringifyFilter = (_, v) => v === null ? undefined : v;
const content = JSON.stringify(object, stringifyFilter, 2);
fs_1.default.writeFileSync(configFilePath, prettier_1.default.format(content, { parser: "json" }));
cacheConfigFile({ absFilePath: configFilePath, objects: [object] });
return true;
}
async function writeFiles(configObjects) {
for (let configObject of configObjects) {
await writeFile(configObject);
}
}
})(ConfigWriter = exports.ConfigWriter || (exports.ConfigWriter = {}));