Add a --config
/ -c
option to the start
CLI command to arbitrarily override any configuration key
The biggest caveat is that JS code (such as functions) will not be interpreted as such, on purpose, for security precautions. If such thing is needed, then a configuration file must be used.
This commit is contained in:
parent
b90c224a99
commit
df2787d3e9
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
global.log = require("../log.js");
|
global.log = require("../log.js");
|
||||||
|
|
||||||
|
const _ = require("lodash");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const program = require("commander");
|
const program = require("commander");
|
||||||
@ -16,6 +17,11 @@ if (require("semver").lt(process.version, "6.0.0")) {
|
|||||||
|
|
||||||
program.version(Helper.getVersion(), "-v, --version")
|
program.version(Helper.getVersion(), "-v, --version")
|
||||||
.option("--home <path>", `${colors.bold("[DEPRECATED]")} Use the ${colors.green("THELOUNGE_HOME")} environment variable instead.`)
|
.option("--home <path>", `${colors.bold("[DEPRECATED]")} Use the ${colors.green("THELOUNGE_HOME")} environment variable instead.`)
|
||||||
|
.option(
|
||||||
|
"-c, --config <key=value>",
|
||||||
|
"override entries of the configuration file, must be specified for each entry that needs to be overriden",
|
||||||
|
Utils.parseConfigOptions
|
||||||
|
)
|
||||||
.on("--help", Utils.extraHelp)
|
.on("--help", Utils.extraHelp)
|
||||||
.parseOptions(process.argv);
|
.parseOptions(process.argv);
|
||||||
|
|
||||||
@ -49,6 +55,9 @@ if (!home) {
|
|||||||
|
|
||||||
Helper.setHome(home);
|
Helper.setHome(home);
|
||||||
|
|
||||||
|
// Merge config key-values passed as CLI options into the main config
|
||||||
|
_.merge(Helper.config, program.config);
|
||||||
|
|
||||||
require("./start");
|
require("./start");
|
||||||
require("./config");
|
require("./config");
|
||||||
if (!Helper.config.public && !Helper.config.ldap.enable) {
|
if (!Helper.config.public && !Helper.config.ldap.enable) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
const _ = require("lodash");
|
||||||
const colors = require("colors/safe");
|
const colors = require("colors/safe");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const Helper = require("../helper");
|
const Helper = require("../helper");
|
||||||
@ -51,6 +52,55 @@ class Utils {
|
|||||||
|
|
||||||
return home;
|
return home;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parses CLI options such as `-c public=true`, `-c debug.raw=true`, etc.
|
||||||
|
static parseConfigOptions(val, memo) {
|
||||||
|
// Invalid option that is not of format `key=value`, do nothing
|
||||||
|
if (!val.includes("=")) {
|
||||||
|
return memo;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseValue = (value) => {
|
||||||
|
if (value === "true") {
|
||||||
|
return true;
|
||||||
|
} else if (value === "false") {
|
||||||
|
return false;
|
||||||
|
} else if (value === "undefined") {
|
||||||
|
return undefined;
|
||||||
|
} else if (value === "null") {
|
||||||
|
return null;
|
||||||
|
} else if (/^\[.*\]$/.test(value)) { // Arrays
|
||||||
|
// Supporting arrays `[a,b]` and `[a, b]`
|
||||||
|
const array = value.slice(1, -1).split(/,\s*/);
|
||||||
|
// If [] is given, it will be parsed as `[ "" ]`, so treat this as empty
|
||||||
|
if (array.length === 1 && array[0] === "") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return array.map(parseValue); // Re-parses all values of the array
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// First time the option is parsed, memo is not set
|
||||||
|
if (memo === undefined) {
|
||||||
|
memo = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: If passed `-c foo="bar=42"` (with single or double quotes), `val`
|
||||||
|
// will always be passed as `foo=bar=42`, never with quotes.
|
||||||
|
const position = val.indexOf("="); // Only split on the first = found
|
||||||
|
const key = val.slice(0, position);
|
||||||
|
const value = val.slice(position + 1);
|
||||||
|
const parsedValue = parseValue(value);
|
||||||
|
|
||||||
|
if (_.has(memo, key)) {
|
||||||
|
log.warn(`Configuration key ${colors.bold(key)} was already specified, ignoring...`);
|
||||||
|
} else {
|
||||||
|
memo = _.set(memo, key, parsedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return memo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Utils;
|
module.exports = Utils;
|
||||||
|
@ -43,4 +43,108 @@ describe("Utils", function() {
|
|||||||
expect(stdout).to.include("THELOUNGE_HOME");
|
expect(stdout).to.include("THELOUNGE_HOME");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(".parseConfigOptions", function() {
|
||||||
|
describe("when it's the first option given", function() {
|
||||||
|
it("should return nothing when passed an invalid config", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo")).to.be.undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly parse boolean values", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo=true")).to.deep.equal({foo: true});
|
||||||
|
expect(Utils.parseConfigOptions("foo=false")).to.deep.equal({foo: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly parse empty strings", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo=")).to.deep.equal({foo: ""});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly parse null values", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo=null")).to.deep.equal({foo: null});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly parse undefined values", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo=undefined"))
|
||||||
|
.to.deep.equal({foo: undefined});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly parse array values", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo=[bar,true]"))
|
||||||
|
.to.deep.equal({foo: ["bar", true]});
|
||||||
|
|
||||||
|
expect(Utils.parseConfigOptions("foo=[bar, true]"))
|
||||||
|
.to.deep.equal({foo: ["bar", true]});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly parse empty array values", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo=[]"))
|
||||||
|
.to.deep.equal({foo: []});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly parse values that contain `=` sign", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo=bar=42"))
|
||||||
|
.to.deep.equal({foo: "bar=42"});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly parse keys using dot-notation", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo.bar=value"))
|
||||||
|
.to.deep.equal({foo: {bar: "value"}});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly parse keys using array-notation", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo[0]=value"))
|
||||||
|
.to.deep.equal({foo: ["value"]});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when some options have already been parsed", function() {
|
||||||
|
it("should not modify existing options when passed an invalid config", function() {
|
||||||
|
const memo = {foo: "bar"};
|
||||||
|
expect(Utils.parseConfigOptions("foo", memo)).to.equal(memo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should combine a new option with previously parsed ones", function() {
|
||||||
|
expect(Utils.parseConfigOptions("bar=false", {foo: true}))
|
||||||
|
.to.deep.equal({foo: true, bar: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should maintain existing properties of a nested object", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo.bar=true", {foo: {baz: false}}))
|
||||||
|
.to.deep.equal({foo: {bar: true, baz: false}});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should maintain existing entries of an array", function() {
|
||||||
|
expect(Utils.parseConfigOptions("foo[1]=baz", {foo: ["bar"]}))
|
||||||
|
.to.deep.equal({foo: ["bar", "baz"]});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when given the same key multiple times", function() {
|
||||||
|
let originalWarn;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
originalWarn = log.warn;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
log.warn = originalWarn;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not override options", function() {
|
||||||
|
log.warn = () => {};
|
||||||
|
|
||||||
|
expect(Utils.parseConfigOptions("foo=baz", {foo: "bar"}))
|
||||||
|
.to.deep.equal({foo: "bar"});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display a warning", function() {
|
||||||
|
let warning = "";
|
||||||
|
log.warn = TestUtil.mockLogger((str) => warning += str);
|
||||||
|
|
||||||
|
Utils.parseConfigOptions("foo=bar", {foo: "baz"});
|
||||||
|
|
||||||
|
expect(warning).to.include("foo was already specified");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user