Skip to content

Commit 228b14c

Browse files
matz3RandomByte
andauthored
[FIX] Resolve UI5 data directory relative to project (#642)
The UI5 data directory (previously called UI5 home directory) should always be resolved relative to the project directory, which is where the package.json file is located at. This change also adds a list of options to the Configuration class to be used by the CLI config command to allow all available config options to be set or retrieved. Follow-up of #635 JIRA: CPOUI5FOUNDATION-715 JIRA: CPOUI5FOUNDATION-716 --------- Co-authored-by: Merlin Beutlberger <[email protected]>
1 parent 23fd49d commit 228b14c

File tree

5 files changed

+251
-18
lines changed

5 files changed

+251
-18
lines changed

lib/config/Configuration.js

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,43 @@ import path from "node:path";
22
import os from "node:os";
33

44
/**
5-
* Provides basic configuration settings for @ui5/project/ui5Framework/* resolvers.
5+
* Provides basic configuration for @ui5/project.
66
* Reads/writes configuration from/to ~/.ui5rc
77
*
88
* @public
99
* @class
1010
* @alias @ui5/project/config/Configuration
1111
*/
1212
class Configuration {
13-
#mavenSnapshotEndpointUrl;
14-
#ui5DataDir;
13+
/**
14+
* A list of all configuration options.
15+
*
16+
* @public
17+
* @static
18+
*/
19+
static OPTIONS = [
20+
"mavenSnapshotEndpointUrl",
21+
"ui5DataDir"
22+
];
23+
24+
#options = new Map();
1525

1626
/**
1727
* @param {object} configuration
1828
* @param {string} [configuration.mavenSnapshotEndpointUrl]
1929
* @param {string} [configuration.ui5DataDir]
2030
*/
21-
constructor({mavenSnapshotEndpointUrl, ui5DataDir}) {
22-
this.#mavenSnapshotEndpointUrl = mavenSnapshotEndpointUrl;
23-
this.#ui5DataDir = ui5DataDir;
31+
constructor(configuration) {
32+
// Initialize map with undefined values for every option so that they are
33+
// returned via toJson()
34+
Configuration.OPTIONS.forEach((key) => this.#options.set(key, undefined));
35+
36+
Object.entries(configuration).forEach(([key, value]) => {
37+
if (!Configuration.OPTIONS.includes(key)) {
38+
throw new Error(`Unknown configuration option '${key}'`);
39+
}
40+
this.#options.set(key, value);
41+
});
2442
}
2543

2644
/**
@@ -31,7 +49,7 @@ class Configuration {
3149
* @returns {string}
3250
*/
3351
getMavenSnapshotEndpointUrl() {
34-
return this.#mavenSnapshotEndpointUrl;
52+
return this.#options.get("mavenSnapshotEndpointUrl");
3553
}
3654

3755
/**
@@ -41,18 +59,15 @@ class Configuration {
4159
* @returns {string}
4260
*/
4361
getUi5DataDir() {
44-
return this.#ui5DataDir;
62+
return this.#options.get("ui5DataDir");
4563
}
4664

4765
/**
4866
* @public
4967
* @returns {object} The configuration in a JSON format
5068
*/
5169
toJson() {
52-
return {
53-
mavenSnapshotEndpointUrl: this.#mavenSnapshotEndpointUrl,
54-
ui5DataDir: this.#ui5DataDir,
55-
};
70+
return Object.fromEntries(this.#options);
5671
}
5772

5873
/**

lib/graph/helpers/ui5Framework.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -365,11 +365,15 @@ export default {
365365
});
366366
}
367367

368-
const config = await Configuration.fromFile();
369368
// ENV var should take precedence over the dataDir from the configuration.
370-
const ui5HomeDir = process.env.UI5_DATA_DIR ?
371-
path.resolve(process.env.UI5_DATA_DIR) :
372-
config.getUi5DataDir();
369+
let ui5DataDir = process.env.UI5_DATA_DIR;
370+
if (!ui5DataDir) {
371+
const config = await Configuration.fromFile();
372+
ui5DataDir = config.getUi5DataDir();
373+
}
374+
if (ui5DataDir) {
375+
ui5DataDir = path.resolve(rootProject.getRootPath(), ui5DataDir);
376+
}
373377

374378
// Note: version might be undefined here and the Resolver will throw an error when calling
375379
// #install and it can't be resolved via the provided library metadata
@@ -378,7 +382,7 @@ export default {
378382
version,
379383
providedLibraryMetadata,
380384
cacheMode,
381-
ui5HomeDir
385+
ui5HomeDir: ui5DataDir
382386
});
383387

384388
let startTime;

test/lib/config/Configuration.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ test.afterEach.always((t) => {
2828
esmock.purge(t.context.Configuration);
2929
});
3030

31+
test.serial("Configuration options", (t) => {
32+
const {Configuration} = t.context;
33+
t.deepEqual(Configuration.OPTIONS, [
34+
"mavenSnapshotEndpointUrl",
35+
"ui5DataDir"
36+
]);
37+
});
38+
3139
test.serial("Build configuration with defaults", (t) => {
3240
const {Configuration} = t.context;
3341

@@ -39,7 +47,6 @@ test.serial("Build configuration with defaults", (t) => {
3947
});
4048
});
4149

42-
4350
test.serial("Overwrite defaults defaults", (t) => {
4451
const {Configuration} = t.context;
4552

@@ -53,6 +60,18 @@ test.serial("Overwrite defaults defaults", (t) => {
5360
t.deepEqual(config.toJson(), params);
5461
});
5562

63+
test.serial("Unknown configuration option", (t) => {
64+
const {Configuration} = t.context;
65+
66+
const params = {
67+
unknown: "foo"
68+
};
69+
70+
t.throws(() => new Configuration(params), {
71+
message: `Unknown configuration option 'unknown'`
72+
});
73+
});
74+
5675
test.serial("Check getters", (t) => {
5776
const {Configuration} = t.context;
5877

test/lib/graph/helpers/ui5Framework.integration.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,19 @@ test.beforeEach(async (t) => {
125125
"../../../../lib/specifications/Specification.js": t.context.Specification
126126
});
127127

128+
// Stub os homedir to prevent the actual ~/.ui5rc from being used in tests
129+
t.context.Configuration = await esmock.p("../../../../lib/config/Configuration.js", {
130+
"node:os": {
131+
homedir: sinon.stub().returns(path.join(fakeBaseDir, "homedir"))
132+
}
133+
});
134+
128135
t.context.ui5Framework = await esmock.p("../../../../lib/graph/helpers/ui5Framework.js", {
129136
"@ui5/logger": ui5Logger,
130137
"../../../../lib/graph/Module.js": t.context.Module,
131138
"../../../../lib/ui5Framework/Openui5Resolver.js": t.context.Openui5Resolver,
132139
"../../../../lib/ui5Framework/Sapui5Resolver.js": t.context.Sapui5Resolver,
140+
"../../../../lib/config/Configuration.js": t.context.Configuration
133141
});
134142

135143
t.context.projectGraphBuilder = await esmock.p("../../../../lib/graph/projectGraphBuilder.js", {

test/lib/graph/helpers/ui5Framework.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ const libraryEPath = path.join(__dirname, "..", "..", "..", "fixtures", "library
1616
const libraryFPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.f");
1717

1818
test.beforeEach(async (t) => {
19+
// Tests either rely on not having UI5_DATA_DIR defined, or explicitly define it
20+
t.context.originalUi5DataDirEnv = process.env.UI5_DATA_DIR;
21+
delete process.env.UI5_DATA_DIR;
22+
1923
const sinon = t.context.sinon = sinonGlobal.createSandbox();
2024

2125
t.context.log = {
@@ -51,15 +55,30 @@ test.beforeEach(async (t) => {
5155
t.context.Sapui5MavenSnapshotResolverResolveVersionStub = sinon.stub();
5256
t.context.Sapui5MavenSnapshotResolverStub.resolveVersion = t.context.Sapui5MavenSnapshotResolverResolveVersionStub;
5357

58+
t.context.getUi5DataDirStub = sinon.stub().returns(undefined);
59+
60+
t.context.ConfigurationStub = {
61+
fromFile: sinon.stub().resolves({
62+
getUi5DataDir: t.context.getUi5DataDirStub
63+
})
64+
};
65+
5466
t.context.ui5Framework = await esmock.p("../../../../lib/graph/helpers/ui5Framework.js", {
5567
"@ui5/logger": ui5Logger,
5668
"../../../../lib/ui5Framework/Sapui5Resolver.js": t.context.Sapui5ResolverStub,
5769
"../../../../lib/ui5Framework/Sapui5MavenSnapshotResolver.js": t.context.Sapui5MavenSnapshotResolverStub,
70+
"../../../../lib/config/Configuration.js": t.context.ConfigurationStub,
5871
});
5972
t.context.utils = t.context.ui5Framework._utils;
6073
});
6174

6275
test.afterEach.always((t) => {
76+
// Reset UI5_DATA_DIR env
77+
if (typeof t.context.originalUi5DataDirEnv === "undefined") {
78+
delete process.env.UI5_DATA_DIR;
79+
} else {
80+
process.env.UI5_DATA_DIR = t.context.originalUi5DataDirEnv;
81+
}
6382
t.context.sinon.restore();
6483
esmock.purge(t.context.ui5Framework);
6584
});
@@ -1020,6 +1039,174 @@ test.serial("enrichProjectGraph should allow omitting framework version in case
10201039
t.is(Sapui5ResolverStub.getCall(0).args[0].providedLibraryMetadata, workspaceFrameworkLibraryMetadata);
10211040
});
10221041

1042+
test.serial("enrichProjectGraph should use UI5 data dir from env var", async (t) => {
1043+
const {sinon, ui5Framework, utils, Sapui5ResolverInstallStub} = t.context;
1044+
1045+
const dependencyTree = {
1046+
id: "test1",
1047+
version: "1.0.0",
1048+
path: applicationAPath,
1049+
configuration: {
1050+
specVersion: "2.0",
1051+
type: "application",
1052+
metadata: {
1053+
name: "application.a"
1054+
},
1055+
framework: {
1056+
name: "SAPUI5",
1057+
version: "1.75.0"
1058+
}
1059+
}
1060+
};
1061+
1062+
const referencedLibraries = ["sap.ui.lib1", "sap.ui.lib2", "sap.ui.lib3"];
1063+
const libraryMetadata = {fake: "metadata"};
1064+
1065+
sinon.stub(utils, "getFrameworkLibrariesFromGraph")
1066+
.resolves(referencedLibraries);
1067+
1068+
Sapui5ResolverInstallStub.resolves({libraryMetadata});
1069+
1070+
1071+
const addProjectToGraphStub = sinon.stub();
1072+
sinon.stub(utils, "ProjectProcessor")
1073+
.callsFake(() => {
1074+
return {
1075+
addProjectToGraph: addProjectToGraphStub
1076+
};
1077+
});
1078+
1079+
const provider = new DependencyTreeProvider({dependencyTree});
1080+
const projectGraph = await projectGraphBuilder(provider);
1081+
1082+
process.env.UI5_DATA_DIR = "./ui5-data-dir-from-env-var";
1083+
1084+
const expectedUi5DataDir = path.resolve(dependencyTree.path, "./ui5-data-dir-from-env-var");
1085+
1086+
await ui5Framework.enrichProjectGraph(projectGraph);
1087+
1088+
t.is(t.context.Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once");
1089+
t.deepEqual(t.context.Sapui5ResolverStub.getCall(0).args, [{
1090+
cacheMode: undefined,
1091+
cwd: dependencyTree.path,
1092+
version: dependencyTree.configuration.framework.version,
1093+
ui5HomeDir: expectedUi5DataDir,
1094+
providedLibraryMetadata: undefined
1095+
}], "Sapui5Resolver#constructor should be called with expected args");
1096+
});
1097+
1098+
test.serial("enrichProjectGraph should use UI5 data dir from configuration", async (t) => {
1099+
const {sinon, ui5Framework, utils, Sapui5ResolverInstallStub, getUi5DataDirStub} = t.context;
1100+
1101+
const dependencyTree = {
1102+
id: "test1",
1103+
version: "1.0.0",
1104+
path: applicationAPath,
1105+
configuration: {
1106+
specVersion: "2.0",
1107+
type: "application",
1108+
metadata: {
1109+
name: "application.a"
1110+
},
1111+
framework: {
1112+
name: "SAPUI5",
1113+
version: "1.75.0"
1114+
}
1115+
}
1116+
};
1117+
1118+
const referencedLibraries = ["sap.ui.lib1", "sap.ui.lib2", "sap.ui.lib3"];
1119+
const libraryMetadata = {fake: "metadata"};
1120+
1121+
sinon.stub(utils, "getFrameworkLibrariesFromGraph")
1122+
.resolves(referencedLibraries);
1123+
1124+
Sapui5ResolverInstallStub.resolves({libraryMetadata});
1125+
1126+
1127+
const addProjectToGraphStub = sinon.stub();
1128+
sinon.stub(utils, "ProjectProcessor")
1129+
.callsFake(() => {
1130+
return {
1131+
addProjectToGraph: addProjectToGraphStub
1132+
};
1133+
});
1134+
1135+
const provider = new DependencyTreeProvider({dependencyTree});
1136+
const projectGraph = await projectGraphBuilder(provider);
1137+
1138+
getUi5DataDirStub.returns("./ui5-data-dir-from-config");
1139+
1140+
const expectedUi5DataDir = path.resolve(dependencyTree.path, "./ui5-data-dir-from-config");
1141+
1142+
await ui5Framework.enrichProjectGraph(projectGraph);
1143+
1144+
t.is(t.context.Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once");
1145+
t.deepEqual(t.context.Sapui5ResolverStub.getCall(0).args, [{
1146+
cacheMode: undefined,
1147+
cwd: dependencyTree.path,
1148+
version: dependencyTree.configuration.framework.version,
1149+
ui5HomeDir: expectedUi5DataDir,
1150+
providedLibraryMetadata: undefined
1151+
}], "Sapui5Resolver#constructor should be called with expected args");
1152+
});
1153+
1154+
test.serial("enrichProjectGraph should use absolute UI5 data dir from configuration", async (t) => {
1155+
const {sinon, ui5Framework, utils, Sapui5ResolverInstallStub, getUi5DataDirStub} = t.context;
1156+
1157+
const dependencyTree = {
1158+
id: "test1",
1159+
version: "1.0.0",
1160+
path: applicationAPath,
1161+
configuration: {
1162+
specVersion: "2.0",
1163+
type: "application",
1164+
metadata: {
1165+
name: "application.a"
1166+
},
1167+
framework: {
1168+
name: "SAPUI5",
1169+
version: "1.75.0"
1170+
}
1171+
}
1172+
};
1173+
1174+
const referencedLibraries = ["sap.ui.lib1", "sap.ui.lib2", "sap.ui.lib3"];
1175+
const libraryMetadata = {fake: "metadata"};
1176+
1177+
sinon.stub(utils, "getFrameworkLibrariesFromGraph")
1178+
.resolves(referencedLibraries);
1179+
1180+
Sapui5ResolverInstallStub.resolves({libraryMetadata});
1181+
1182+
1183+
const addProjectToGraphStub = sinon.stub();
1184+
sinon.stub(utils, "ProjectProcessor")
1185+
.callsFake(() => {
1186+
return {
1187+
addProjectToGraph: addProjectToGraphStub
1188+
};
1189+
});
1190+
1191+
const provider = new DependencyTreeProvider({dependencyTree});
1192+
const projectGraph = await projectGraphBuilder(provider);
1193+
1194+
getUi5DataDirStub.returns("/absolute-ui5-data-dir-from-config");
1195+
1196+
const expectedUi5DataDir = path.resolve("/absolute-ui5-data-dir-from-config");
1197+
1198+
await ui5Framework.enrichProjectGraph(projectGraph);
1199+
1200+
t.is(t.context.Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once");
1201+
t.deepEqual(t.context.Sapui5ResolverStub.getCall(0).args, [{
1202+
cacheMode: undefined,
1203+
cwd: dependencyTree.path,
1204+
version: dependencyTree.configuration.framework.version,
1205+
ui5HomeDir: expectedUi5DataDir,
1206+
providedLibraryMetadata: undefined
1207+
}], "Sapui5Resolver#constructor should be called with expected args");
1208+
});
1209+
10231210
test.serial("utils.shouldIncludeDependency", (t) => {
10241211
const {utils} = t.context;
10251212
// root project dependency should always be included

0 commit comments

Comments
 (0)