json-config-transform
Version:
Transform JSON config files by environment.
243 lines (193 loc) • 9.12 kB
JavaScript
;
let PluginError = require('plugin-error'),
path = require('path'),
PLUGIN_NAME = 'json-config-transform',
logEnabled = false;
function NewFile(filePath, contents) {
let fs = require('fs');
if (filePath.startsWith(".")) {
filePath = "." + filePath;
}
fs.writeFile(filePath, contents, function (err) {
if (err) {
throw err;
}
Log("File saved at " + filePath);
});
}
function TransformProperties(base, target, output) {
// start with all properties in base that match in target
// these will be direct matches and result in target value replacing base value
for (var prop in base) {
if (!target.hasOwnProperty(prop)) {
continue;
}
// recursively merge nested properties if property is an object excluding arrays
if (typeof target[prop] !== null
&& typeof base[prop] === "object"
&& typeof target[prop] === "object"
&& toString.call(base[prop]) !== "[object Array]")
{
TransformProperties(base[prop], target[prop], output[prop]);
} else {
// set the output property to the new target property
Log(prop + " value REPLACED with " + target[prop] + ".");
output[prop] = target[prop];
}
}
// see if any new properties exist on the target and add to the output if not present
// this is also where special transform actions take place
for (var prop in target) {
if (prop.includes("[transform:remove]")) {
let originalProp = prop.replace("[transform:remove]", "");
if (!output.hasOwnProperty(originalProp)) {
throw new PluginError(PLUGIN_NAME, "Action [transform:remove] failed. Cannot remove property " + originalProp + ". Property does not exist.");
}
delete output[originalProp];
Log(originalProp + " REMOVED.");
continue;
}
// append items found in array to array output
if (prop.includes("[transform:append]")) {
let originalProp = prop.replace("[transform:append]", "");
if (!output.hasOwnProperty(originalProp)) {
throw new PluginError(PLUGIN_NAME, "Action [transform:append] failed. Cannot append array items to property " + originalProp + ". Property does not exist.");
}
if (toString.call(target[prop]) !== "[object Array]" || toString.call(output[originalProp]) !== "[object Array]") {
throw new PluginError(PLUGIN_NAME, "Action [transform:append] invalid. Action only applicable on array properties.");
}
for (let i = 0; i < target[prop].length; i++) {
output[originalProp].push(target[prop][i]);
}
let newItemsCount = target[prop].length;
Log(originalProp + " APPENDED " + newItemsCount + " new item" + (newItemsCount === 1 ? "." : "s."));
continue;
}
if (prop.includes("[transform:match:")) {
let cmd = prop.slice(prop.indexOf("[transform"), prop.length),
originalProp = prop.replace(cmd, ""),
matchProp = cmd.slice(cmd.lastIndexOf(":") + 1, cmd.length).replace("]", "");
if (!output.hasOwnProperty(originalProp) || toString.call(output[originalProp]) !== "[object Array]") {
throw new PluginError(PLUGIN_NAME, "Action " + cmd + " failed. Cannot match array items to property " + originalProp + ". Property does not exist.");
}
if (toString.call(target[prop]) !== "[object Array]") {
throw new PluginError(PLUGIN_NAME, "Action " + cmd + " invalid. Action only applicable on array properties.");
}
Log(originalProp + " array being MATCHED by property " + matchProp + ".");
for (let i = 0; i < target[prop].length; i++) {
let targetItem = target[prop][i];
if (typeof targetItem !== "object" || !targetItem.hasOwnProperty(matchProp)) {
throw new PluginError(PLUGIN_NAME, "Action " + cmd + " invalid. Array items must be an object amd have property " + matchProp + ".");
}
for (let o = 0; o < output[originalProp].length; o++) {
let outputItem = output[originalProp][o];
if (typeof outputItem !== "object" || !outputItem.hasOwnProperty(matchProp)) {
throw new PluginError(PLUGIN_NAME, "Action " + cmd + " invalid. Array items must be an object amd have property " + matchProp + ".");
}
if (outputItem[matchProp].toLowerCase() === targetItem[matchProp].toLowerCase()) {
for (var itemProp in targetItem) {
outputItem[itemProp] = targetItem[itemProp];
}
}
}
}
continue;
}
if (!output.hasOwnProperty(prop)) {
Log(prop + " ADDED with value " + target[prop] + ".");
output[prop] = target[prop];
}
}
}
function Log(message) {
if (logEnabled) {
console.log(message);
}
}
function ToBool(value) {
if (value === undefined) {
return false;
} else if (typeof value === 'boolean') {
return value;
} else if (typeof value === 'number' ) {
value = value.toString();
} else if (typeof value !== 'string') {
return false;
}
switch (value.toLowerCase()) {
case "true":
case "yes":
case "1":
case "y":
return true;
default:
return false;
}
}
function Settings(options) {
if (!options) {
throw new PluginError(PLUGIN_NAME, "Options object required.");
}
let executingDirectory = process.cwd();
if (typeof options === "string") {
options = {
Environment: options
};
}
options = Object.assign({}, {
environment: "",
configSource: "./appsettings.json",
outputPath: "./appsettings.json",
logEnabled: false,
indent: false,
pathsAreAbsolute: false
}, options);
this.Environment = options.environment;
this.ConfigSource = options.pathsAreAbsolute ? options.configSource : path.join(executingDirectory, options.configSource);
this.OutputPath = options.pathsAreAbsolute ? options.outputPath : path.join(executingDirectory, options.outputPath);
this.LogEnabled = ToBool(options.logEnabled);
this.Indent = ToBool(options.indent);
this.ConfigFileName = _getFileNameFromPath(this.ConfigSource);
this.ConfigDirectoryPath = _getDirectoryFromFullPath(this.ConfigSource);
this.EnvironmentConfigSource = path.join(this.ConfigDirectoryPath, _getFileNameWithoutExtension(this.ConfigFileName) + "." + this.Environment + ".json");
function _getFileNameFromPath(filePath) {
if (!filePath) {
return filePath;
}
return filePath.split('\\').pop().split('/').pop();
}
function _getFileNameWithoutExtension(filename) {
if (!filename) {
return filename;
}
return filename.replace(/\.[^/.]+$/, "");
}
function _getDirectoryFromFullPath(filePath) {
if (!filePath) {
return filePath;
}
let forwardSlashLastIndex = filePath.lastIndexOf("/"),
backSlashLastIndex = filePath.lastIndexOf("\\"),
indexToSubStr = forwardSlashLastIndex,
endingSlash = "/";
if (backSlashLastIndex > forwardSlashLastIndex) {
indexToSubStr = backSlashLastIndex;
endingSlash = "\\";
}
return filePath.substr(0, indexToSubStr) + endingSlash;
}
}
module.exports = function(options) {
let transformSettings = new Settings(options);
if (!transformSettings.Environment) {
throw new PluginError(PLUGIN_NAME, "Transform operation aborted. No environment specified.");
}
logEnabled = transformSettings.LogEnabled;
Log("** Transforming JSON file '" + transformSettings.ConfigFileName + "' for '" + transformSettings.Environment + "' environment. **");
let baselineConfigSettings = require(transformSettings.ConfigSource),
newSettings = baselineConfigSettings,
environmentSettings = require(transformSettings.EnvironmentConfigSource);
TransformProperties(baselineConfigSettings, environmentSettings, newSettings);
// convert new object to a JSON string and write it a file in output directory
return NewFile(transformSettings.OutputPath, JSON.stringify(newSettings, undefined, transformSettings.Indent ? '\t' : 0));
}