Skip to content

Instantly share code, notes, and snippets.

@TroublesNCuddles
Created January 22, 2021 03:59
Show Gist options
  • Save TroublesNCuddles/b20a60ca0a318df2e7273c886211269d to your computer and use it in GitHub Desktop.
Save TroublesNCuddles/b20a60ca0a318df2e7273c886211269d to your computer and use it in GitHub Desktop.
/*
*
* DEPENDENCIES
*
*/
const fs = require('fs');
const path = require('path');
const JustAnotterPlatform = require('../src/index.js');
const {Logger, LEVELS: LogLevels} = require('../src/utilities/logger.js');
const {mergeDeep} = require('../src/utilities/index.js');
const NPMPackage = require('../package.json');
/*
*
* CONSTANTS
*
*/
const ENVIRONMENT_PREFIX = 'OTTERPLATFORM';
const ENVIRONMENT_SEPARATOR = '__';
const CONFIGURATIONS = ['modules.json', 'permissions.json'];
const CONFIGURATION_PATH = path.join('lib', 'configs');
/*
*
* FUNCTIONS
*
*/
/**
*
* This function takes your object of variables (Usually provided by process.env) and filters out any entries
* that doesn't start with the defined prefix and separator joined.
*
* @param environment_variables your object of variables to filter, usually from process.env
* @returns {{}} the input filtered
*/
const filterEnvironmentVariables = (environment_variables) => {
const prefix = [ENVIRONMENT_PREFIX, ENVIRONMENT_SEPARATOR].join('');
return Object.entries(environment_variables)
.filter(([key]) => key.toUpperCase().startsWith(prefix))
.reduce((previous, [key, value]) => {
previous[key] = value;
return previous;
}, {});
};
/**
*
* This cleans up the variables (that should've been filtered) by removing the prefix
*
* @param environment_variables
* @returns {{}}
*/
const removedPrefixFromEnvironmentVariables = (environment_variables) => {
const prefix = [ENVIRONMENT_PREFIX, ENVIRONMENT_SEPARATOR].join('').toUpperCase();
return Object.entries(environment_variables)
.reduce((previous, [key, value]) => {
previous[key.toUpperCase().startsWith(prefix) ? key.slice(prefix.length) : key] = value;
return previous;
}, {});
};
/**
*
* This splits the path using ENVIRONMENT_SEPARATOR
*
* @param path
* @returns {*|string[]}
*/
const splitEnvironmentPath = (path) => {
return path.split(ENVIRONMENT_SEPARATOR);
};
/**
*
* This will build an options object from the provided set of variables.
* The variables should ideally be filtered and cleaned prior to running this.
*
* @param filtered_environment_variables
* @returns {{}}
*/
const buildOptionsObject = (filtered_environment_variables) => {
return Object.entries(filtered_environment_variables).reduce((previous, [key, value]) => {
const key_parts = splitEnvironmentPath(key);
const first_key = key_parts[0];
if (key_parts.length === 1) {
previous[first_key] = value;
return previous;
}
previous[first_key] = fillObject(key_parts.slice(1), value, previous[first_key] || {});
return previous;
}, {});
};
/**
*
* This fills the object with the value using the keys as the path to aid in building the options object
*
* @param keys
* @param value
* @param obj
* @returns {*}
*/
const fillObject = (keys, value, obj) => {
const first_key = keys[0];
if (keys.length === 1) {
obj[first_key] = value;
return obj;
}
obj[first_key] = fillObject(keys.slice(1), value, obj[first_key] || {});
return obj;
};
/**
*
* This is used to get the working directory from either fivem's GetResourcePath or process.cwd
*
* @returns {string} the working directory.
*/
const getWorkingDirectory = () => {
return typeof GetResourcePath === 'function' ? GetResourcePath(GetCurrentResourceName()) : process.cwd();
};
/**
*
* This is just a wrapper for fs.readFile, with the encoding set to utf8 and to return a profile.
*
* @param file
* @returns {Promise}
*/
const readFile = file => {
return new Promise((resolve, reject) => {
fs.readFile(file, {encoding: 'utf8'}, (error, file) => {
if (error) {
return reject(error);
}
return resolve(file);
});
});
};
/**
*
* This will read a configuration file and parse the JSON.
*
* @param configuration_file
* @returns {Promise<any>}
*/
const loadConfiguration = async (configuration_file) => {
const content = await readFile(configuration_file);
return {[path.parse(configuration_file).name]: JSON.parse(content)};
};
/**
*
* This will get an ordered list of applicable configurations to load into memory
*
* @returns {(string)[]}
*/
const getApplicableConfigFiles = (entry_configurations) => {
return [...entry_configurations, ...CONFIGURATIONS].filter(element => !!element);
};
/**
*
* @param file_name
* @returns {string}
*/
const getFullPathForConfiguration = (file_name) => {
return path.join(getWorkingDirectory(), CONFIGURATION_PATH, file_name);
};
/*
*
* BOOTSTRAP
*
*/
//Initialize the loggers
const logger_app = new Logger({
level: LogLevels.INFO,
components: typeof GetCurrentResourceName === 'function' ? [GetCurrentResourceName()] : []
});
const logger_bootstrap = logger_app.createNewLogger('Boostrap');
(async () => {
logger_bootstrap.info({
message: 'Boostrapping %s v%s with working directory: "%s"',
message_data: [
NPMPackage.name,
NPMPackage.version,
getWorkingDirectory()
]
});
//Get filtered environment variables first
const filtered = filterEnvironmentVariables(process.env);
logger_bootstrap.info({
message: 'Found %d environmental %s',
message_data: [Object.keys(filtered).length, Object.keys(filtered).length === 1 ? 'option' : 'options']
});
//Removing the prefix that was defined CONSTANTS/ENVIRONMENT_PREFIX
const no_prefix = removedPrefixFromEnvironmentVariables(filtered);
//Build the environmental options
const environmental_options = buildOptionsObject(no_prefix);
//Get a list of all of the applicable configurations that we should load
const config_files = getApplicableConfigFiles(((environmental_options || {}).config_files || '').split(','));
logger_bootstrap.info({
message: 'Loading the following configurations: %s',
message_data: [config_files.join(', ')]
});
//Convert the configuration file names into full paths, followed up by reading their respective files.
const configs = await Promise.all(
config_files
.map(file_name => getFullPathForConfiguration(file_name))
.map(config_file => loadConfiguration(config_file, config_file))
);
//Merge all of the configurations together into one object.
const configuration = mergeDeep(...configs, environmental_options);
//Inform the Server Administrator of our configuration accomplishments
logger_bootstrap.info({
message: 'Loaded configurations with %d PRIMARY key%s',
message_data: [Object.keys(configuration).length, Object.keys(configuration).length > 1 ? 's' : '']
});
//Check if there's a log level present
if (configuration.log_level) {
//Convert it to the number that our logger will understand
const log_level = logger_app.getLevelFromString(configuration.log_level);
//Verify that the level is valid before proceeding
if (log_level !== undefined) {
//Set the levels for the loggers
logger_app.setLevel(log_level);
logger_bootstrap.setLevel(log_level);
}
}
//Initialize our App and return the run promise to be handled by the bootstrap's promise handler.
return (new JustAnotterPlatform(configuration, logger_app)).run();
})()
.then(() => {
logger_bootstrap.info('Completed');
})
.catch(e => {
logger_bootstrap.fatal(e);
return process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment