mirror of
https://github.com/thelounge/thelounge
synced 2024-11-23 12:33:07 +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");
|
||||
|
||||
const _ = require("lodash");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const program = require("commander");
|
||||
|
@ -10,18 +11,24 @@ const Helper = require("../helper");
|
|||
const Utils = require("./utils");
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
program.version(Helper.getVersion(), "-v, --version")
|
||||
.option("--home <path>", `${colors.bold("[DEPRECATED]")} Use the ${colors.green("THELOUNGE_HOME")} environment variable instead.`)
|
||||
.on("--help", Utils.extraHelp)
|
||||
.parseOptions(process.argv);
|
||||
.option("--home <path>", `${colors.bold.red("[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);
|
||||
|
||||
// Parse options from `argv` returning `argv` void of these options.
|
||||
const argvWithoutOptions = program.parseOptions(process.argv);
|
||||
|
||||
if (program.home) {
|
||||
log.warn(`${colors.green("--home")} is ${colors.bold("deprecated")} and will be removed in The Lounge v3.`);
|
||||
log.warn(`Use the ${colors.green("THELOUNGE_HOME")} environment variable instead.`);
|
||||
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.`);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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.`);
|
||||
}
|
||||
|
||||
|
@ -49,6 +56,9 @@ if (!home) {
|
|||
|
||||
Helper.setHome(home);
|
||||
|
||||
// Merge config key-values passed as CLI options into the main config
|
||||
_.merge(Helper.config, program.config);
|
||||
|
||||
require("./start");
|
||||
require("./config");
|
||||
if (!Helper.config.public && !Helper.config.ldap.enable) {
|
||||
|
@ -58,12 +68,18 @@ require("./install");
|
|||
|
||||
// TODO: Remove this when releasing The Lounge v3
|
||||
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.`);
|
||||
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) {
|
||||
program.help();
|
||||
|
|
|
@ -10,11 +10,11 @@ const Utils = require("./utils");
|
|||
|
||||
program
|
||||
.command("start")
|
||||
.option("-H, --host <ip>", "set the IP address or hostname for the web server to listen on")
|
||||
.option("-P, --port <port>", "set the port to listen on")
|
||||
.option("-B, --bind <ip>", "set the local IP to bind to for outgoing connections")
|
||||
.option(" --public", "start in public mode")
|
||||
.option(" --private", "start in private mode")
|
||||
.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>", `${colors.bold.red("[DEPRECATED]")} to set the port to listen on, use ${colors.bold("-c port=<port>")} instead`)
|
||||
.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", `${colors.bold.red("[DEPRECATED]")} to start in public mode, use ${colors.bold("-c public=true")} instead`)
|
||||
.option(" --private", `${colors.bold.red("[DEPRECATED]")} to start in private mode, use ${colors.bold("-c public=false")} instead`)
|
||||
.description("Start the server")
|
||||
.on("--help", Utils.extraHelp)
|
||||
.action(function(options) {
|
||||
|
@ -22,6 +22,22 @@ program
|
|||
|
||||
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;
|
||||
if (options.public) {
|
||||
mode = true;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
const _ = require("lodash");
|
||||
const colors = require("colors/safe");
|
||||
const fs = require("fs");
|
||||
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()))}.`,
|
||||
"",
|
||||
].forEach((e) => console.log(e)); // eslint-disable-line no-console
|
||||
].forEach((e) => log.raw(e));
|
||||
}
|
||||
|
||||
static defaultHome() {
|
||||
|
@ -34,7 +35,7 @@ class Utils {
|
|||
".lounge_home"
|
||||
));
|
||||
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.`);
|
||||
|
||||
distConfig = deprecatedDistConfig;
|
||||
|
@ -51,6 +52,55 @@ class Utils {
|
|||
|
||||
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;
|
||||
|
|
|
@ -109,7 +109,7 @@ function setHome(newPath) {
|
|||
// TODO: Remove in future release
|
||||
// Backwards compatibility for old way of specifying themes in settings
|
||||
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 {
|
||||
this.config.theme = `themes/${this.config.theme}.css`;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,11 @@ exports.info = function() {
|
|||
exports.debug = function() {
|
||||
console.log.apply(console, timestamp(colors.green("[DEBUG]"), arguments));
|
||||
};
|
||||
|
||||
exports.raw = function() {
|
||||
console.log.apply(console, arguments);
|
||||
};
|
||||
|
||||
/* eslint-enable no-console */
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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 = {
|
||||
createClient: function() {
|
||||
return new MockClient();
|
||||
|
@ -38,4 +52,5 @@ module.exports = {
|
|||
createWebserver: function() {
|
||||
return express();
|
||||
},
|
||||
mockLogger,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue