mirror of
https://github.com/thelounge/thelounge
synced 2024-11-27 14:30:57 +00:00
Merge pull request #1820 from thelounge/astorije/config-options
Deprecate existing options of `thelounge start` and add a generic `--config` override
This commit is contained in:
commit
d0f5d5025e
7 changed files with 269 additions and 17 deletions
|
@ -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");
|
||||||
|
@ -10,18 +11,24 @@ const Helper = require("../helper");
|
||||||
const Utils = require("./utils");
|
const Utils = require("./utils");
|
||||||
|
|
||||||
if (require("semver").lt(process.version, "6.0.0")) {
|
if (require("semver").lt(process.version, "6.0.0")) {
|
||||||
log.warn(`Support of Node.js v4 is ${colors.bold("deprecated")} and will be removed in The Lounge v3.`);
|
log.warn(`Support of Node.js v4 is ${colors.bold.red("deprecated")} and will be removed in The Lounge v3.`);
|
||||||
log.warn("Please upgrade to Node.js v6 or more recent.");
|
log.warn("Please upgrade to Node.js v6 or more recent.");
|
||||||
}
|
}
|
||||||
|
|
||||||
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.red("[DEPRECATED]")} Use the ${colors.green("THELOUNGE_HOME")} environment variable instead.`)
|
||||||
.on("--help", Utils.extraHelp)
|
.option(
|
||||||
.parseOptions(process.argv);
|
"-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);
|
||||||
|
|
||||||
|
// Parse options from `argv` returning `argv` void of these options.
|
||||||
|
const argvWithoutOptions = program.parseOptions(process.argv);
|
||||||
|
|
||||||
if (program.home) {
|
if (program.home) {
|
||||||
log.warn(`${colors.green("--home")} is ${colors.bold("deprecated")} and will be removed in The Lounge v3.`);
|
log.warn(`${colors.bold("--home")} is ${colors.bold.red("deprecated")} and will be removed in The Lounge v3. Use the ${colors.bold("THELOUNGE_HOME")} environment variable instead.`);
|
||||||
log.warn(`Use the ${colors.green("THELOUNGE_HOME")} environment variable instead.`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the app was built before calling setHome as it wants to load manifest.json from the public folder
|
// Check if the app was built before calling setHome as it wants to load manifest.json from the public folder
|
||||||
|
@ -37,7 +44,7 @@ if (!fs.existsSync(path.join(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.LOUNGE_HOME) {
|
if (process.env.LOUNGE_HOME) {
|
||||||
log.warn(`${colors.green("LOUNGE_HOME")} is ${colors.bold("deprecated")} and will be removed in The Lounge v3.`);
|
log.warn(`${colors.green("LOUNGE_HOME")} is ${colors.bold.red("deprecated")} and will be removed in The Lounge v3.`);
|
||||||
log.warn(`Use ${colors.green("THELOUNGE_HOME")} instead.`);
|
log.warn(`Use ${colors.green("THELOUNGE_HOME")} instead.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +56,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) {
|
||||||
|
@ -58,12 +68,18 @@ require("./install");
|
||||||
|
|
||||||
// TODO: Remove this when releasing The Lounge v3
|
// TODO: Remove this when releasing The Lounge v3
|
||||||
if (process.argv[1].endsWith(`${require("path").sep}lounge`)) {
|
if (process.argv[1].endsWith(`${require("path").sep}lounge`)) {
|
||||||
log.warn(`The ${colors.red("lounge")} CLI is ${colors.bold("deprecated")} and will be removed in v3.`);
|
log.warn(`The ${colors.red("lounge")} CLI is ${colors.bold.red("deprecated")} and will be removed in v3.`);
|
||||||
log.warn(`Use ${colors.green("thelounge")} instead.`);
|
log.warn(`Use ${colors.green("thelounge")} instead.`);
|
||||||
process.argv[1] = "thelounge";
|
process.argv[1] = "thelounge";
|
||||||
}
|
}
|
||||||
|
|
||||||
program.parse(process.argv);
|
// `parse` expects to be passed `process.argv`, but we need to remove to give it
|
||||||
|
// a version of `argv` that does not contain options already parsed by
|
||||||
|
// `parseOptions` above.
|
||||||
|
// This is done by giving it the updated `argv` that `parseOptions` returned,
|
||||||
|
// except it returns an object with `args`/`unknown`, so we need to concat them.
|
||||||
|
// See https://github.com/tj/commander.js/blob/fefda77f463292/index.js#L686-L763
|
||||||
|
program.parse(argvWithoutOptions.args.concat(argvWithoutOptions.unknown));
|
||||||
|
|
||||||
if (!program.args.length) {
|
if (!program.args.length) {
|
||||||
program.help();
|
program.help();
|
||||||
|
|
|
@ -10,11 +10,11 @@ const Utils = require("./utils");
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("start")
|
.command("start")
|
||||||
.option("-H, --host <ip>", "set the IP address or hostname for the web server to listen on")
|
.option("-H, --host <ip>", `${colors.bold.red("[DEPRECATED]")} to set the IP address or hostname for the web server to listen on, use ${colors.bold("-c host=<ip>")} instead`)
|
||||||
.option("-P, --port <port>", "set the port to listen on")
|
.option("-P, --port <port>", `${colors.bold.red("[DEPRECATED]")} to set the port to listen on, use ${colors.bold("-c port=<port>")} instead`)
|
||||||
.option("-B, --bind <ip>", "set the local IP to bind to for outgoing connections")
|
.option("-B, --bind <ip>", `${colors.bold.red("[DEPRECATED]")} to set the local IP to bind to for outgoing connections, use ${colors.bold("-c bind=<ip>")} instead`)
|
||||||
.option(" --public", "start in public mode")
|
.option(" --public", `${colors.bold.red("[DEPRECATED]")} to start in public mode, use ${colors.bold("-c public=true")} instead`)
|
||||||
.option(" --private", "start in private mode")
|
.option(" --private", `${colors.bold.red("[DEPRECATED]")} to start in private mode, use ${colors.bold("-c public=false")} instead`)
|
||||||
.description("Start the server")
|
.description("Start the server")
|
||||||
.on("--help", Utils.extraHelp)
|
.on("--help", Utils.extraHelp)
|
||||||
.action(function(options) {
|
.action(function(options) {
|
||||||
|
@ -22,6 +22,22 @@ program
|
||||||
|
|
||||||
const server = require("../server");
|
const server = require("../server");
|
||||||
|
|
||||||
|
if (options.host) {
|
||||||
|
log.warn(`${colors.bold("-H, --host <ip>")} is ${colors.bold.red("deprecated")} and will be removed in The Lounge v3. Use ${colors.bold("-c host=<ip>")} instead.`);
|
||||||
|
}
|
||||||
|
if (options.port) {
|
||||||
|
log.warn(`${colors.bold("-P, --port <port>")} is ${colors.bold.red("deprecated")} and will be removed in The Lounge v3. Use ${colors.bold("-c port=<port>")} instead.`);
|
||||||
|
}
|
||||||
|
if (options.bind) {
|
||||||
|
log.warn(`${colors.bold("-B, --bind <ip>")} is ${colors.bold.red("deprecated")} and will be removed in The Lounge v3. Use ${colors.bold("-c bind=<ip>")} instead.`);
|
||||||
|
}
|
||||||
|
if (options.public) {
|
||||||
|
log.warn(`${colors.bold("--public")} is ${colors.bold.red("deprecated")} and will be removed in The Lounge v3. Use ${colors.bold("-c public=true")} instead.`);
|
||||||
|
}
|
||||||
|
if (options.private) {
|
||||||
|
log.warn(`${colors.bold("--private")} is ${colors.bold.red("deprecated")} and will be removed in The Lounge v3. Use ${colors.bold("-c public=false")} instead.`);
|
||||||
|
}
|
||||||
|
|
||||||
var mode = Helper.config.public;
|
var mode = Helper.config.public;
|
||||||
if (options.public) {
|
if (options.public) {
|
||||||
mode = true;
|
mode = true;
|
||||||
|
|
|
@ -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");
|
||||||
|
@ -16,7 +17,7 @@ class Utils {
|
||||||
"",
|
"",
|
||||||
` THELOUNGE_HOME Path for all configuration files and folders. Defaults to ${colors.green(Helper.expandHome(Utils.defaultHome()))}.`,
|
` THELOUNGE_HOME Path for all configuration files and folders. Defaults to ${colors.green(Helper.expandHome(Utils.defaultHome()))}.`,
|
||||||
"",
|
"",
|
||||||
].forEach((e) => console.log(e)); // eslint-disable-line no-console
|
].forEach((e) => log.raw(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultHome() {
|
static defaultHome() {
|
||||||
|
@ -34,7 +35,7 @@ class Utils {
|
||||||
".lounge_home"
|
".lounge_home"
|
||||||
));
|
));
|
||||||
if (fs.existsSync(deprecatedDistConfig)) {
|
if (fs.existsSync(deprecatedDistConfig)) {
|
||||||
log.warn(`${colors.green(".lounge_home")} is ${colors.bold("deprecated")} and will be ignored as of The Lounge v3.`);
|
log.warn(`${colors.green(".lounge_home")} is ${colors.bold.red("deprecated")} and will be ignored as of The Lounge v3.`);
|
||||||
log.warn(`Use ${colors.green(".thelounge_home")} instead.`);
|
log.warn(`Use ${colors.green(".thelounge_home")} instead.`);
|
||||||
|
|
||||||
distConfig = deprecatedDistConfig;
|
distConfig = deprecatedDistConfig;
|
||||||
|
@ -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;
|
||||||
|
|
|
@ -109,7 +109,7 @@ function setHome(newPath) {
|
||||||
// TODO: Remove in future release
|
// TODO: Remove in future release
|
||||||
// Backwards compatibility for old way of specifying themes in settings
|
// Backwards compatibility for old way of specifying themes in settings
|
||||||
if (this.config.theme.includes(".css")) {
|
if (this.config.theme.includes(".css")) {
|
||||||
log.warn(`Referring to CSS files in the ${colors.green("theme")} setting of ${colors.green(configPath)} is ${colors.bold("deprecated")} and will be removed in a future version.`);
|
log.warn(`Referring to CSS files in the ${colors.green("theme")} setting of ${colors.green(configPath)} is ${colors.bold.red("deprecated")} and will be removed in a future version.`);
|
||||||
} else {
|
} else {
|
||||||
this.config.theme = `themes/${this.config.theme}.css`;
|
this.config.theme = `themes/${this.config.theme}.css`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,11 @@ exports.info = function() {
|
||||||
exports.debug = function() {
|
exports.debug = function() {
|
||||||
console.log.apply(console, timestamp(colors.green("[DEBUG]"), arguments));
|
console.log.apply(console, timestamp(colors.green("[DEBUG]"), arguments));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.raw = function() {
|
||||||
|
console.log.apply(console, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
|
|
||||||
exports.prompt = (options, callback) => {
|
exports.prompt = (options, callback) => {
|
||||||
|
|
150
test/src/command-line/utilsTest.js
Normal file
150
test/src/command-line/utilsTest.js
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const expect = require("chai").expect;
|
||||||
|
const TestUtil = require("../../util");
|
||||||
|
const Utils = require("../../../src/command-line/utils");
|
||||||
|
|
||||||
|
describe("Utils", function() {
|
||||||
|
describe(".extraHelp", function() {
|
||||||
|
let originalRaw;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
originalRaw = log.raw;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
log.raw = originalRaw;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should start and end with empty lines to display correctly with --help", function() {
|
||||||
|
// Mock `log.raw` to extract its effect into an array
|
||||||
|
const stdout = [];
|
||||||
|
log.raw = TestUtil.mockLogger((str) => stdout.push(str));
|
||||||
|
|
||||||
|
Utils.extraHelp();
|
||||||
|
|
||||||
|
// Starts with 2 empty lines
|
||||||
|
expect(stdout[0]).to.equal("\n");
|
||||||
|
expect(stdout[1]).to.equal("\n");
|
||||||
|
expect(stdout[2]).to.not.equal("\n");
|
||||||
|
|
||||||
|
// Ends with 1 empty line
|
||||||
|
expect(stdout[stdout.length - 2]).to.not.equal("\n");
|
||||||
|
expect(stdout[stdout.length - 1]).to.equal("\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should contain information about THELOUNGE_HOME env var", function() {
|
||||||
|
// Mock `log.raw` to extract its effect into a concatenated string
|
||||||
|
let stdout = "";
|
||||||
|
log.raw = TestUtil.mockLogger((str) => stdout += str);
|
||||||
|
|
||||||
|
Utils.extraHelp();
|
||||||
|
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
15
test/util.js
15
test/util.js
|
@ -23,6 +23,20 @@ MockClient.prototype.createMessage = function(opts) {
|
||||||
return message;
|
return message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function mockLogger(callback) {
|
||||||
|
return function() {
|
||||||
|
// TODO: Use ...args with The Lounge v3: add `...args` as function argument
|
||||||
|
// and replaced the next line with `args.join(", ")`
|
||||||
|
const stdout = Array.prototype.slice.call(arguments).join(", ")
|
||||||
|
.replace( // Removes ANSI colors. See https://stackoverflow.com/a/29497680
|
||||||
|
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
callback(stdout + "\n");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createClient: function() {
|
createClient: function() {
|
||||||
return new MockClient();
|
return new MockClient();
|
||||||
|
@ -38,4 +52,5 @@ module.exports = {
|
||||||
createWebserver: function() {
|
createWebserver: function() {
|
||||||
return express();
|
return express();
|
||||||
},
|
},
|
||||||
|
mockLogger,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue