-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This feature depends on SAP/ui5-project#575 and makes the configuration of the `mavenSnapshotEndpointUrl` option possible, which is used by SAP/ui5-project#570 Co-authored-by: Merlin Beutlberger <m.beutlberger@sap.com> Co-authored-by: Günter Klatt <57760635+KlattG@users.noreply.github.com>
- Loading branch information
1 parent
f994468
commit 9910e30
Showing
4 changed files
with
351 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import chalk from "chalk"; | ||
import process from "node:process"; | ||
import baseMiddleware from "../middlewares/base.js"; | ||
|
||
const configCommand = { | ||
command: "config", | ||
describe: "Get and set UI5 Tooling configuration options", | ||
middlewares: [baseMiddleware], | ||
handler: handleConfig | ||
}; | ||
|
||
configCommand.builder = function(cli) { | ||
return cli | ||
.demandCommand(1, "Command required. Available commands are 'set', 'get', and 'list'") | ||
.command("set <key> [value]", "Set the value for a given configuration key. " + | ||
"Clear an existing configuration by omitting the value", { | ||
handler: handleConfig, | ||
builder: noop, | ||
middlewares: [baseMiddleware], | ||
}) | ||
.command("get <key>", "Get the value for a given configuration key", { | ||
handler: handleConfig, | ||
builder: noop, | ||
middlewares: [baseMiddleware], | ||
}) | ||
.command("list", "Display the current configuration", { | ||
handler: handleConfig, | ||
builder: noop, | ||
middlewares: [baseMiddleware], | ||
}) | ||
.example("$0 set mavenSnapshotEndpointUrl http://example.com/snapshots/", | ||
"Set a value for the mavenSnapshotEndpointUrl configuration") | ||
.example("$0 set mavenSnapshotEndpointUrl", | ||
"Unset the current value of the mavenSnapshotEndpointUrl configuration"); | ||
}; | ||
|
||
function noop() {} | ||
|
||
async function handleConfig(argv) { | ||
const {_: commandArgs, key, value} = argv; | ||
const command = commandArgs[commandArgs.length - 1]; | ||
|
||
const {default: Configuration} = await import( "@ui5/project/config/Configuration"); | ||
const allowedKeys = ["mavenSnapshotEndpointUrl"]; | ||
|
||
if (["set", "get"].includes(command) && !allowedKeys.includes(key)) { | ||
throw new Error( | ||
`The provided key is not a valid configuration option. Valid options are: ${allowedKeys.join(", ")}`); | ||
} | ||
|
||
const config = await Configuration.fromFile(); | ||
if (command === "list") { | ||
// Print all configuration values to stdout | ||
process.stdout.write(formatJsonForOutput(config.toJson())); | ||
} else if (command === "get") { | ||
// Get a single configuration value and print to stdout | ||
let configValue = config.toJson()[key]; | ||
if (configValue === undefined) { | ||
configValue = ""; | ||
} | ||
process.stdout.write(`${configValue}\n`); | ||
} else if (command === "set") { | ||
const jsonConfig = config.toJson(); | ||
if (value === undefined || value === "") { | ||
delete jsonConfig[key]; | ||
process.stderr.write(`Configuration option ${chalk.bold(key)} has been unset\n`); | ||
} else { | ||
jsonConfig[key] = value; | ||
process.stderr.write(`Configuration option ${chalk.bold(key)} has been updated: | ||
${formatJsonForOutput(jsonConfig, key)}`); | ||
} | ||
|
||
await Configuration.toFile(new Configuration(jsonConfig)); | ||
} else { | ||
throw new Error(`Unknown 'ui5 config' command '${command}'`); | ||
} | ||
} | ||
|
||
function formatJsonForOutput(config, filterKey) { | ||
return Object.keys(config) | ||
.filter((key) => !filterKey || filterKey === key) | ||
.filter((key) => config[key] !== undefined) // Don't print undefined config values | ||
.map((key) => { | ||
return ` ${key} = ${config[key]}\n`; | ||
}).join(""); | ||
} | ||
|
||
export default configCommand; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
import test from "ava"; | ||
import sinon from "sinon"; | ||
import esmock from "esmock"; | ||
import chalk from "chalk"; | ||
|
||
function getDefaultArgv() { | ||
// This has been taken from the actual argv object yargs provides | ||
return { | ||
"_": ["build"], | ||
"loglevel": "info", | ||
"log-level": "info", | ||
"logLevel": "info", | ||
"perf": false, | ||
"silent": false, | ||
"include-all-dependencies": false, | ||
"all": false, | ||
"a": false, | ||
"includeAllDependencies": false, | ||
"create-build-manifest": false, | ||
"createBuildManifest": false, | ||
"dest": "./dist", | ||
"clean-dest": false, | ||
"cleanDest": false, | ||
"experimental-css-variables": false, | ||
"experimentalCssVariables": false, | ||
"$0": "ui5" | ||
}; | ||
} | ||
|
||
test.beforeEach(async (t) => { | ||
t.context.argv = getDefaultArgv(); | ||
|
||
t.context.builder = sinon.stub().resolves(); | ||
t.context.stdoutWriteStub = sinon.stub(process.stdout, "write"); | ||
t.context.stderrWriteStub = sinon.stub(process.stderr, "write"); | ||
t.context.configToJson = sinon.stub().returns({}); | ||
|
||
const {default: Configuration} = await import( "@ui5/project/config/Configuration"); | ||
t.context.Configuration = Configuration; | ||
sinon.stub(Configuration, "fromFile").resolves(new Configuration({})); | ||
sinon.stub(Configuration, "toFile").resolves(); | ||
|
||
t.context.config = await esmock.p("../../../../lib/cli/commands/config.js", { | ||
"@ui5/project/config/Configuration": t.context.Configuration | ||
}); | ||
}); | ||
|
||
test.afterEach.always((t) => { | ||
sinon.restore(); | ||
esmock.purge(t.context.config); | ||
}); | ||
|
||
test.serial("ui5 config list", async (t) => { | ||
const {config, argv, stdoutWriteStub, stderrWriteStub, Configuration} = t.context; | ||
|
||
const configurationStub = new Configuration({}); | ||
sinon.stub(configurationStub, "toJson").returns({ | ||
mavenSnapshotEndpointUrl: "my/url", | ||
otherConfig: false, | ||
pony: 130, | ||
horses: null, | ||
cows: undefined // Won't be rendered | ||
}); | ||
Configuration.fromFile.resolves(configurationStub); | ||
|
||
argv["_"] = ["list"]; | ||
await config.handler(argv); | ||
|
||
t.is(stdoutWriteStub.firstCall.firstArg, ` mavenSnapshotEndpointUrl = my/url | ||
otherConfig = false | ||
pony = 130 | ||
horses = null | ||
`, "Logged expected text to stdout"); | ||
t.is(stderrWriteStub.callCount, 0, "Nothing written to stderr"); | ||
}); | ||
|
||
test.serial("ui5 config list: No config", async (t) => { | ||
const {config, argv, stdoutWriteStub, stderrWriteStub} = t.context; | ||
|
||
argv["_"] = ["list"]; | ||
await config.handler(argv); | ||
|
||
t.is(stdoutWriteStub.firstCall.firstArg, "", | ||
"Logged no text to stdout"); | ||
t.is(stderrWriteStub.callCount, 0, "Nothing written to stderr"); | ||
}); | ||
|
||
test.serial("ui5 config get", async (t) => { | ||
const {config, argv, stdoutWriteStub, stderrWriteStub, Configuration} = t.context; | ||
|
||
Configuration.fromFile.resolves(new Configuration({ | ||
mavenSnapshotEndpointUrl: "my/url" | ||
})); | ||
|
||
argv["_"] = ["get"]; | ||
argv["key"] = "mavenSnapshotEndpointUrl"; | ||
await config.handler(argv); | ||
|
||
t.is(stdoutWriteStub.firstCall.firstArg, "my/url\n", | ||
"Logged configuration value to stdout"); | ||
t.is(stderrWriteStub.callCount, 0, "Nothing written to stderr"); | ||
}); | ||
|
||
test.serial("ui5 config get: Empty value", async (t) => { | ||
const {config, argv, stdoutWriteStub} = t.context; | ||
|
||
argv["_"] = ["get"]; | ||
argv["key"] = "mavenSnapshotEndpointUrl"; | ||
await config.handler(argv); | ||
|
||
t.is(stdoutWriteStub.firstCall.firstArg, "\n", "Logged no value to console"); | ||
}); | ||
|
||
test.serial("ui5 config set", async (t) => { | ||
const {config, argv, stderrWriteStub, stdoutWriteStub, Configuration} = t.context; | ||
|
||
const configurationStub = new Configuration({}); | ||
sinon.stub(configurationStub, "toJson").returns({ | ||
mavenSnapshotEndpointUrl: "my/url" | ||
}); | ||
|
||
Configuration.fromFile.resolves(configurationStub); | ||
|
||
argv["_"] = ["set"]; | ||
argv["key"] = "mavenSnapshotEndpointUrl"; | ||
argv["value"] = "https://_snapshot_endpoint_"; | ||
await config.handler(argv); | ||
|
||
t.is(stderrWriteStub.firstCall.firstArg, | ||
`Configuration option ${chalk.bold("mavenSnapshotEndpointUrl")} has been updated: | ||
mavenSnapshotEndpointUrl = https://_snapshot_endpoint_\n`, | ||
"Logged expected message to stderr"); | ||
t.is(stdoutWriteStub.callCount, 0, "Nothing written to stdout"); | ||
|
||
t.is(Configuration.toFile.callCount, 1, "Configuration#toFile got called once"); | ||
t.deepEqual(Configuration.toFile.firstCall.firstArg.toJson(), { | ||
mavenSnapshotEndpointUrl: "https://_snapshot_endpoint_" | ||
}, "Configuration#toFile got called with expected argument"); | ||
}); | ||
|
||
test.serial("ui5 config set without a value should delete the configuration", async (t) => { | ||
const {config, argv, stderrWriteStub, stdoutWriteStub, Configuration} = t.context; | ||
|
||
argv["_"] = ["set"]; | ||
argv["key"] = "mavenSnapshotEndpointUrl"; | ||
argv["value"] = undefined; // Simulating no value parameter provided to Yargs | ||
await config.handler(argv); | ||
|
||
t.is(stderrWriteStub.firstCall.firstArg, | ||
`Configuration option ${chalk.bold("mavenSnapshotEndpointUrl")} has been unset\n`, | ||
"Logged expected message to stderr"); | ||
t.is(stdoutWriteStub.callCount, 0, "Nothing written to stdout"); | ||
|
||
t.is(Configuration.toFile.callCount, 1, "Configuration#toFile got called once"); | ||
t.deepEqual(Configuration.toFile.firstCall.firstArg.toJson(), { | ||
mavenSnapshotEndpointUrl: undefined | ||
}, "Configuration#toFile got called with expected argument"); | ||
}); | ||
|
||
test.serial("ui5 config set with an empty value should delete the configuration", async (t) => { | ||
const {config, argv, stderrWriteStub, stdoutWriteStub, Configuration} = t.context; | ||
|
||
argv["_"] = ["set"]; | ||
argv["key"] = "mavenSnapshotEndpointUrl"; | ||
argv["value"] = ""; // Simulating empty value provided to Yargs using quotes "" | ||
await config.handler(argv); | ||
|
||
t.is(stderrWriteStub.firstCall.firstArg, | ||
`Configuration option ${chalk.bold("mavenSnapshotEndpointUrl")} has been unset\n`, | ||
"Logged expected message to stderr"); | ||
t.is(stdoutWriteStub.callCount, 0, "Nothing written to stdout"); | ||
|
||
t.is(Configuration.toFile.callCount, 1, "Configuration#toFile got called once"); | ||
t.deepEqual(Configuration.toFile.firstCall.firstArg.toJson(), { | ||
mavenSnapshotEndpointUrl: undefined | ||
}, "Configuration#toFile got called with expected argument"); | ||
}); | ||
|
||
test.serial("ui5 config set null should update the configuration", async (t) => { | ||
const {config, argv, stderrWriteStub, stdoutWriteStub, Configuration} = t.context; | ||
|
||
argv["_"] = ["set"]; | ||
argv["key"] = "mavenSnapshotEndpointUrl"; | ||
|
||
// Yargs would never provide us with other types than string. Still, our code should | ||
// check for empty strings and nothing else (like falsy) | ||
argv["value"] = 0; | ||
await config.handler(argv); | ||
|
||
t.is(stderrWriteStub.firstCall.firstArg, | ||
`Configuration option ${chalk.bold("mavenSnapshotEndpointUrl")} has been updated: | ||
mavenSnapshotEndpointUrl = 0\n`, | ||
"Logged expected message to stderr"); | ||
t.is(stdoutWriteStub.callCount, 0, "Nothing written to stdout"); | ||
|
||
t.is(Configuration.toFile.callCount, 1, "Configuration#toFile got called once"); | ||
t.deepEqual(Configuration.toFile.firstCall.firstArg.toJson(), { | ||
mavenSnapshotEndpointUrl: 0 | ||
}, "Configuration#toFile got called with expected argument"); | ||
}); | ||
|
||
test.serial("ui5 config set false should update the configuration", async (t) => { | ||
const {config, argv, stderrWriteStub, stdoutWriteStub, Configuration} = t.context; | ||
|
||
argv["_"] = ["set"]; | ||
argv["key"] = "mavenSnapshotEndpointUrl"; | ||
|
||
// Yargs would never provide us with other types than string. Still, our code should | ||
// check for empty strings and nothing else (like falsyness) | ||
argv["value"] = false; | ||
await config.handler(argv); | ||
|
||
t.is(stderrWriteStub.firstCall.firstArg, | ||
`Configuration option ${chalk.bold("mavenSnapshotEndpointUrl")} has been updated: | ||
mavenSnapshotEndpointUrl = false\n`, | ||
"Logged expected message to stderr"); | ||
t.is(stdoutWriteStub.callCount, 0, "Nothing written to stdout"); | ||
|
||
t.is(Configuration.toFile.callCount, 1, "Configuration#toFile got called once"); | ||
t.deepEqual(Configuration.toFile.firstCall.firstArg.toJson(), { | ||
mavenSnapshotEndpointUrl: false | ||
}, "Configuration#toFile got called with expected argument"); | ||
}); | ||
|
||
test.serial("ui5 config invalid key", async (t) => { | ||
const {config, argv} = t.context; | ||
|
||
argv["_"] = ["get"]; | ||
argv["key"] = "_invalid_key_"; | ||
argv["value"] = "https://_snapshot_endpoint_"; | ||
|
||
await t.throwsAsync(config.handler(argv), { | ||
message: | ||
"The provided key is not a valid configuration option. Valid options are: mavenSnapshotEndpointUrl", | ||
}); | ||
}); | ||
|
||
test.serial("ui5 config empty key", async (t) => { | ||
const {config, argv} = t.context; | ||
|
||
argv["_"] = ["set"]; | ||
argv["key"] = ""; | ||
argv["value"] = undefined; | ||
|
||
await t.throwsAsync(config.handler(argv), { | ||
message: | ||
"The provided key is not a valid configuration option. Valid options are: mavenSnapshotEndpointUrl", | ||
}); | ||
}); | ||
|
||
test.serial("ui5 config unknown command", async (t) => { | ||
const {config, argv} = t.context; | ||
|
||
argv["_"] = ["foo"]; | ||
|
||
await t.throwsAsync(config.handler(argv), { | ||
message: | ||
"Unknown 'ui5 config' command 'foo'", | ||
}); | ||
}); |