// Ensure internal require aliases (e.g., "logger") resolve when this file is run as a standalone script require("./alias-resolver"); const path = require("node:path"); const fs = require("node:fs"); const { styleText } = require("node:util"); const Ajv = require("ajv"); const globals = require("globals"); const { Linter } = require("eslint"); const Log = require("logger"); const rootPath = path.resolve(`${__dirname}/../`); const Utils = require(`${rootPath}/js/utils.js`); const linter = new Linter({ configType: "flat" }); const ajv = new Ajv(); /** * Returns a string with path of configuration file. * Check if set by environment variable MM_CONFIG_FILE * @returns {string} path and filename of the config file */ function getConfigFile () { // FIXME: This function should be in core. Do you want refactor me ;) ?, be good! return path.resolve(process.env.MM_CONFIG_FILE || `${rootPath}/config/config.js`); } /** * Checks the config file using eslint. */ function checkConfigFile () { const configFileName = getConfigFile(); // Check if file exists and is accessible try { fs.accessSync(configFileName, fs.constants.R_OK); } catch (error) { if (error.code === "ENOENT") { Log.error(`File not found: ${configFileName}`); } else if (error.code === "EACCES") { Log.error(`No permission to read config file: ${configFileName}`); } else { Log.error(`Cannot access config file: ${configFileName}\n${error.message}`); } process.exit(1); } // Validate syntax of the configuration file. Log.info(`Checking config file ${configFileName} ...`); // I'm not sure if all ever is utf-8 const configFile = fs.readFileSync(configFileName, "utf-8"); const errors = linter.verify( configFile, { languageOptions: { ecmaVersion: "latest", globals: { ...globals.node } }, rules: { "no-sparse-arrays": "error", "no-undef": "error" } }, configFileName ); if (errors.length === 0) { Log.info(styleText("green", "Your configuration file doesn't contain syntax errors :)")); validateModulePositions(configFileName); } else { let errorMessage = "Your configuration file contains syntax errors :("; for (const error of errors) { errorMessage += `\nLine ${error.line} column ${error.column}: ${error.message}`; } Log.error(errorMessage); process.exit(1); } } /** * * @param {string} configFileName - The path and filename of the configuration file to validate. */ function validateModulePositions (configFileName) { Log.info("Checking modules structure configuration ..."); const positionList = Utils.getModulePositions(); // Make Ajv schema configuration of modules config // Only scan "module" and "position" const schema = { type: "object", properties: { modules: { type: "array", items: { type: "object", properties: { module: { type: "string" }, position: { type: "string" } }, required: ["module"] } } } }; // Scan all modules const validate = ajv.compile(schema); const data = require(configFileName); const valid = validate(data); if (valid) { Log.info(styleText("green", "Your modules structure configuration doesn't contain errors :)")); // Check for unknown positions (warning only, not an error) if (data.modules) { for (const [index, module] of data.modules.entries()) { if (module.position && !positionList.includes(module.position)) { Log.warn(`Module ${index} ("${module.module}") uses unknown position: "${module.position}"`); Log.warn(`Known positions are: ${positionList.join(", ")}`); } } } } else { const module = validate.errors[0].instancePath.split("/")[2]; const position = validate.errors[0].instancePath.split("/")[3]; let errorMessage = "This module configuration contains errors:"; errorMessage += `\n${JSON.stringify(data.modules[module], null, 2)}`; if (position) { errorMessage += `\n${position}: ${validate.errors[0].message}`; errorMessage += `\n${JSON.stringify(validate.errors[0].params.allowedValues, null, 2).slice(1, -1)}`; } else { errorMessage += validate.errors[0].message; } Log.error(errorMessage); process.exit(1); } } try { checkConfigFile(); } catch (error) { const message = error && error.message ? error.message : error; Log.error(`Unexpected error: ${message}`); process.exit(1); }