Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add Configuration #575

Merged
merged 18 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions lib/config/Configuration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import path from "node:path";
import os from "node:os";

/**
* Provides basic configuration settings for @ui5/project/ui5Framework/* resolvers.
* Reads/writes configuration from/to ~/.ui5rc
*
* @public
* @class
* @alias @ui5/project/config/Configuration
*/
class Configuration {
#mavenSnapshotEndpointUrl;

/**
* @param {object} configuration
* @param {string} [configuration.mavenSnapshotEndpointUrl]
*/
constructor({mavenSnapshotEndpointUrl}) {
this.#mavenSnapshotEndpointUrl = mavenSnapshotEndpointUrl;
}

/**
* Maven Repository Snapshot URL.
* Used to download artifacts and packages from Maven's build-snapshots URL.
*
* @public
* @returns {string}
*/
getMavenSnapshotEndpointUrl() {
return this.#mavenSnapshotEndpointUrl;
}

/**
* @public
* @returns {object} The configuration in a JSON format
*/
toJSON() {
return {
mavenSnapshotEndpointUrl: this.#mavenSnapshotEndpointUrl,
};
}

/**
* Creates Configuration from a JSON file
*
* @public
* @static
* @param {string} [filePath="~/.ui5rc"] Path to configuration JSON file
* @returns {Promise<@ui5/project/config/Configuration>} Configuration instance
*/
static async fromFile(filePath) {
filePath = filePath || path.resolve(path.join(os.homedir(), ".ui5rc"));

const {default: fs} = await import("graceful-fs");
const {promisify} = await import("node:util");
const readFile = promisify(fs.readFile);
let config;
try {
const fileContent = await readFile(filePath);
config = JSON.parse(fileContent);
} catch (err) {
if (err.code === "ENOENT") {
// "File or directory does not exist"
config = {};
} else {
throw err;
}
}
return new Configuration(config);
}

/**
* Saves Configuration to a JSON file
*
* @public
* @static
* @param {@ui5/project/config/Configuration} config Configuration to save
* @param {string} [filePath="~/.ui5rc"] Path to configuration JSON file
* @returns {Promise<void>}
*/
static async toFile(config, filePath) {
filePath = filePath || path.resolve(path.join(os.homedir(), ".ui5rc"));

const {default: fs} = await import("graceful-fs");
const {promisify} = await import("node:util");
const writeFile = promisify(fs.writeFile);

return writeFile(filePath, JSON.stringify(config.toJSON()));
}
}

export default Configuration;
2 changes: 1 addition & 1 deletion lib/ui5Framework/Sapui5MavenSnapshotResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ class Sapui5MavenSnapshotResolver extends AbstractResolver {
if (ok) {
log.info(`Using Maven snapshot endpoint URL resolved from Maven configuration file: ${url}`);
log.info(`Consider persisting this choice by executing the following command: ` +
`ui5 config set snapshotEndpointUrl ${url}`);
`ui5 config set mavenSnapshotEndpointUrl ${url}`);
} else {
log.verbose(`User rejected usage of the resolved URL`);
url = null;
Expand Down
2 changes: 1 addition & 1 deletion lib/ui5Framework/maven/Installer.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class Installer extends AbstractInstaller {
reject(new Error(
`Installer: Missing or empty Maven repository URL for snapshot consumption. ` +
`Please configure the correct URL using the following command: ` +
`ui5 config set snapshotEndpointUrl https://registry.corp/vendor/build-snapshots/`));
`ui5 config set mavenSnapshotEndpointUrl https://registry.corp/vendor/build-snapshots/`));
}

resolve(new Registry({endpointUrl: snapshotEndpointUrl}));
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
],
"type": "module",
"exports": {
"./config/Configuration": "./lib/config/Configuration.js",
"./specifications/Specification": "./lib/specifications/Specification.js",
"./specifications/SpecificationVersion": "./lib/specifications/SpecificationVersion.js",
"./ui5Framework/Sapui5MavenSnapshotResolver": "./lib/ui5Framework/Sapui5MavenSnapshotResolver.js",
Expand Down
122 changes: 122 additions & 0 deletions test/lib/config/Configuration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import test from "ava";
import sinonGlobal from "sinon";
import esmock from "esmock";

test.beforeEach(async (t) => {
const sinon = t.context.sinon = sinonGlobal.createSandbox();

t.context.homedirStub = sinon.stub().returns("~");
t.context.promisifyStub = sinon.stub();
t.context.resolveStub = sinon.stub().callsFake((path) => path);
t.context.joinStub = sinon.stub().callsFake((...args) => args.join("/"));
t.context.Configuration = await esmock.p("../../../lib/config/Configuration.js", {
"node:path": {
resolve: t.context.resolveStub,
join: t.context.joinStub
},
"node:util": {
"promisify": t.context.promisifyStub
},
"node:os": {
"homedir": t.context.homedirStub
}
});
});

test.afterEach.always((t) => {
t.context.sinon.restore();
esmock.purge(t.context.Configuration);
});

test.serial("Build configuration with defaults", (t) => {
const {Configuration} = t.context;

const config = new Configuration({});

t.deepEqual(config.toJSON(), {
mavenSnapshotEndpointUrl: undefined
});
});


test.serial("Overwrite defaults defaults", (t) => {
const {Configuration} = t.context;

const params = {
mavenSnapshotEndpointUrl: "https://snapshot.url"
};

const config = new Configuration(params);

t.deepEqual(config.toJSON(), params);
});

test.serial("Check getters", (t) => {
const {Configuration} = t.context;

const params = {
mavenSnapshotEndpointUrl: "https://snapshot.url"
};

const config = new Configuration(params);

t.is(config.getMavenSnapshotEndpointUrl(), params.mavenSnapshotEndpointUrl);
});


test.serial("fromFile", async (t) => {
const fromFile = t.context.Configuration.fromFile;
const {promisifyStub, sinon} = t.context;

const ui5rcContents = {
mavenSnapshotEndpointUrl: "https://snapshot.url"
};
const responseStub = sinon.stub().resolves(JSON.stringify(ui5rcContents));
promisifyStub.callsFake(() => responseStub);

const config = await fromFile("/custom/path/.ui5rc");

t.deepEqual(config.toJSON(), ui5rcContents);
});

test.serial("fromFile: configuration file not found- fallback to default config", async (t) => {
const {promisifyStub, sinon, Configuration} = t.context;
const fromFile = Configuration.fromFile;

const responseStub = sinon.stub().throws({code: "ENOENT"});
promisifyStub.callsFake(() => responseStub);

const config = await fromFile("/non-existing/path/.ui5rc");

t.is(config instanceof Configuration, true, "Created a default configuration");
t.is(config.getMavenSnapshotEndpointUrl(), undefined, "Dafault settings");
});

test.serial("fromFile: throws", async (t) => {
const fromFile = t.context.Configuration.fromFile;
const {promisifyStub, sinon} = t.context;

const responseStub = sinon.stub().throws(new Error("Error"));
promisifyStub.callsFake(() => responseStub);

await t.throwsAsync(fromFile("/non-existing/path/.ui5rc"), {
message: "Error"
});
});

test.serial("toFile", async (t) => {
const {promisifyStub, sinon, Configuration} = t.context;
const toFile = Configuration.toFile;

const writeStub = sinon.stub().resolves();
promisifyStub.callsFake(() => writeStub);

const config = new Configuration({mavenSnapshotEndpointUrl: "https://registry.corp/vendor/build-snapshots/"});
await toFile(config, "/path/to/save/.ui5rc");

t.deepEqual(
writeStub.getCall(0).args,
["/path/to/save/.ui5rc", JSON.stringify(config.toJSON())],
"Write config to path"
);
});
3 changes: 2 additions & 1 deletion test/lib/package-exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ test("export of package.json", (t) => {
// Check number of definied exports
test("check number of exports", (t) => {
const packageJson = require("@ui5/project/package.json");
t.is(Object.keys(packageJson.exports).length, 11);
t.is(Object.keys(packageJson.exports).length, 12);
});

// Public API contract (exported modules)
[
"config/Configuration",
"specifications/Specification",
"specifications/SpecificationVersion",
"ui5Framework/Openui5Resolver",
Expand Down
2 changes: 1 addition & 1 deletion test/lib/ui5framework/Sapui5MavenSnapshotResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ test.serial("_resolveSnapshotEndpointUrlFromMaven", async (t) => {
"Using Maven snapshot endpoint URL resolved from Maven configuration file: /build-snapshots/");
t.is(loggerInfo.getCall(1).args[0],
"Consider persisting this choice by executing the following command: " +
"ui5 config set snapshotEndpointUrl /build-snapshots/");
"ui5 config set mavenSnapshotEndpointUrl /build-snapshots/");
});

test.serial("_resolveSnapshotEndpointUrlFromMaven fails", async (t) => {
Expand Down