Skip to content

Commit 70165e7

Browse files
committed
[FEATURE] Add 'component' type
1 parent 9cec0c0 commit 70165e7

File tree

71 files changed

+2525
-76
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+2525
-76
lines changed

lib/specifications/Specification.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class Specification {
3939
case "application": {
4040
return createAndInitializeSpec("types/Application.js", parameters);
4141
}
42+
case "component": {
43+
return createAndInitializeSpec("types/Component.js", parameters);
44+
}
4245
case "library": {
4346
return createAndInitializeSpec("types/Library.js", parameters);
4447
}

lib/specifications/SpecificationVersion.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ const SUPPORTED_VERSIONS = [
55
"0.1", "1.0", "1.1",
66
"2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6",
77
"3.0", "3.1", "3.2",
8-
"4.0"
8+
"4.0",
9+
"5.0",
910
];
1011

1112
/**

lib/specifications/types/Component.js

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import fsPath from "node:path";
2+
import ComponentProject from "../ComponentProject.js";
3+
import {createReader} from "@ui5/fs/resourceFactory";
4+
5+
/**
6+
* Component
7+
*
8+
* @public
9+
* @class
10+
* @alias @ui5/project/specifications/types/Component
11+
* @extends @ui5/project/specifications/ComponentProject
12+
* @hideconstructor
13+
*/
14+
class Component extends ComponentProject {
15+
constructor(parameters) {
16+
super(parameters);
17+
18+
this._pManifests = Object.create(null);
19+
20+
this._srcPath = "src";
21+
this._testPath = "test";
22+
this._testPathExists = false;
23+
24+
this._propertiesFilesSourceEncoding = "UTF-8";
25+
}
26+
27+
/* === Attributes === */
28+
29+
/**
30+
* Get the cachebuster signature type configuration of the project
31+
*
32+
* @returns {string} <code>time</code> or <code>hash</code>
33+
*/
34+
getCachebusterSignatureType() {
35+
return this._config.builder && this._config.builder.cachebuster &&
36+
this._config.builder.cachebuster.signatureType || "time";
37+
}
38+
39+
/**
40+
* Get the path of the project's source directory. This might not be POSIX-style on some platforms.
41+
*
42+
* @public
43+
* @returns {string} Absolute path to the source directory of the project
44+
*/
45+
getSourcePath() {
46+
return fsPath.join(this.getRootPath(), this._srcPath);
47+
}
48+
49+
/* === Resource Access === */
50+
/**
51+
* Get a resource reader for the sources of the project (excluding any test resources)
52+
*
53+
* @param {string[]} excludes List of glob patterns to exclude
54+
* @returns {@ui5/fs/ReaderCollection} Reader collection
55+
*/
56+
_getSourceReader(excludes) {
57+
return createReader({
58+
fsBasePath: this.getSourcePath(),
59+
virBasePath: `/resources/${this._namespace}/`,
60+
name: `Source reader for component project ${this.getName()}`,
61+
project: this,
62+
excludes
63+
});
64+
}
65+
66+
/**
67+
* Get a resource reader for the test-resources of the project
68+
*
69+
* @param {string[]} excludes List of glob patterns to exclude
70+
* @returns {@ui5/fs/ReaderCollection} Reader collection
71+
*/
72+
_getTestReader(excludes) {
73+
if (!this._testPathExists) {
74+
return null;
75+
}
76+
const testReader = createReader({
77+
fsBasePath: fsPath.join(this.getRootPath(), this._testPath),
78+
virBasePath: `/test-resources/${this._namespace}/`,
79+
name: `Runtime test-resources reader for component project ${this.getName()}`,
80+
project: this,
81+
excludes
82+
});
83+
return testReader;
84+
}
85+
86+
/**
87+
* Get a resource reader for the sources of the project (excluding any test resources)
88+
* without a virtual base path
89+
*
90+
* @returns {@ui5/fs/ReaderCollection} Reader collection
91+
*/
92+
_getRawSourceReader() {
93+
return createReader({
94+
fsBasePath: this.getSourcePath(),
95+
virBasePath: "/",
96+
name: `Raw source reader for component project ${this.getName()}`,
97+
project: this
98+
});
99+
}
100+
101+
/* === Internals === */
102+
/**
103+
* @private
104+
* @param {object} config Configuration object
105+
*/
106+
async _configureAndValidatePaths(config) {
107+
await super._configureAndValidatePaths(config);
108+
109+
if (config.resources && config.resources.configuration && config.resources.configuration.paths) {
110+
if (config.resources.configuration.paths.src) {
111+
this._srcPath = config.resources.configuration.paths.src;
112+
}
113+
if (config.resources.configuration.paths.test) {
114+
this._testPath = config.resources.configuration.paths.test;
115+
}
116+
}
117+
if (!(await this._dirExists("/" + this._srcPath))) {
118+
throw new Error(
119+
`Unable to find source directory '${this._srcPath}' in component project ${this.getName()}`);
120+
}
121+
this._testPathExists = await this._dirExists("/" + this._testPath);
122+
123+
this._log.verbose(`Path mapping for component project ${this.getName()}:`);
124+
this._log.verbose(` Physical root path: ${this.getRootPath()}`);
125+
this._log.verbose(` Mapped to:`);
126+
this._log.verbose(` /resources/ => ${this._srcPath}`);
127+
this._log.verbose(
128+
` /test-resources/ => ${this._testPath}${this._testPathExists ? "" : " [does not exist]"}`);
129+
}
130+
131+
/**
132+
* @private
133+
* @param {object} config Configuration object
134+
* @param {object} buildDescription Cache metadata object
135+
*/
136+
async _parseConfiguration(config, buildDescription) {
137+
await super._parseConfiguration(config, buildDescription);
138+
139+
if (buildDescription) {
140+
this._namespace = buildDescription.namespace;
141+
return;
142+
}
143+
this._namespace = await this._getNamespace();
144+
}
145+
146+
/**
147+
* Determine component namespace either based on a project`s
148+
* manifest.json or manifest.appdescr_variant (fallback if present)
149+
*
150+
* @returns {string} Namespace of the project
151+
* @throws {Error} if namespace can not be determined
152+
*/
153+
async _getNamespace() {
154+
try {
155+
return await this._getNamespaceFromManifestJson();
156+
} catch (manifestJsonError) {
157+
if (manifestJsonError.code !== "ENOENT") {
158+
throw manifestJsonError;
159+
}
160+
// No manifest.json present
161+
// => attempt fallback to manifest.appdescr_variant (typical for App Variants)
162+
try {
163+
return await this._getNamespaceFromManifestAppDescVariant();
164+
} catch (appDescVarError) {
165+
if (appDescVarError.code === "ENOENT") {
166+
// Fallback not possible: No manifest.appdescr_variant present
167+
// => Throw error indicating missing manifest.json
168+
// (do not mention manifest.appdescr_variant since it is only
169+
// relevant for the rather "uncommon" App Variants)
170+
throw new Error(
171+
`Could not find required manifest.json for project ` +
172+
`${this.getName()}: ${manifestJsonError.message}`);
173+
}
174+
throw appDescVarError;
175+
}
176+
}
177+
}
178+
179+
/**
180+
* Determine application namespace by checking manifest.json.
181+
* Any maven placeholders are resolved from the projects pom.xml
182+
*
183+
* @returns {string} Namespace of the project
184+
* @throws {Error} if namespace can not be determined
185+
*/
186+
async _getNamespaceFromManifestJson() {
187+
const manifest = await this._getManifest("/manifest.json");
188+
let appId;
189+
// check for a proper sap.app/id in manifest.json to determine namespace
190+
if (manifest["sap.app"] && manifest["sap.app"].id) {
191+
appId = manifest["sap.app"].id;
192+
} else {
193+
throw new Error(
194+
`No sap.app/id configuration found in manifest.json of project ${this.getName()}`);
195+
}
196+
197+
if (this._hasMavenPlaceholder(appId)) {
198+
try {
199+
appId = await this._resolveMavenPlaceholder(appId);
200+
} catch (err) {
201+
throw new Error(
202+
`Failed to resolve namespace of project ${this.getName()}: ${err.message}`);
203+
}
204+
}
205+
const namespace = appId.replace(/\./g, "/");
206+
this._log.verbose(
207+
`Namespace of project ${this.getName()} is ${namespace} (from manifest.json)`);
208+
return namespace;
209+
}
210+
211+
/**
212+
* Determine application namespace by checking manifest.appdescr_variant.
213+
*
214+
* @returns {string} Namespace of the project
215+
* @throws {Error} if namespace can not be determined
216+
*/
217+
async _getNamespaceFromManifestAppDescVariant() {
218+
const manifest = await this._getManifest("/manifest.appdescr_variant");
219+
let appId;
220+
// check for the id property in manifest.appdescr_variant to determine namespace
221+
if (manifest && manifest.id) {
222+
appId = manifest.id;
223+
} else {
224+
throw new Error(
225+
`No "id" property found in manifest.appdescr_variant of project ${this.getName()}`);
226+
}
227+
228+
const namespace = appId.replace(/\./g, "/");
229+
this._log.verbose(
230+
`Namespace of project ${this.getName()} is ${namespace} (from manifest.appdescr_variant)`);
231+
return namespace;
232+
}
233+
234+
/**
235+
* Reads and parses a JSON file with the provided name from the projects source directory
236+
*
237+
* @param {string} filePath Name of the JSON file to read. Typically "manifest.json" or "manifest.appdescr_variant"
238+
* @returns {Promise<object>} resolves with an object containing the content requested manifest file
239+
*/
240+
async _getManifest(filePath) {
241+
if (this._pManifests[filePath]) {
242+
return this._pManifests[filePath];
243+
}
244+
return this._pManifests[filePath] = this._getRawSourceReader().byPath(filePath)
245+
.then(async (resource) => {
246+
if (!resource) {
247+
throw new Error(
248+
`Could not find resource ${filePath} in project ${this.getName()}`);
249+
}
250+
return JSON.parse(await resource.getString());
251+
}).catch((err) => {
252+
throw new Error(
253+
`Failed to read ${filePath} for project ` +
254+
`${this.getName()}: ${err.message}`);
255+
});
256+
}
257+
}
258+
259+
export default Component;

lib/validation/schema/specVersion/kind/extension.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "object",
66
"required": ["specVersion", "kind", "type", "metadata"],
77
"properties": {
8-
"specVersion": { "enum": ["4.0", "3.2", "3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] },
8+
"specVersion": { "enum": ["5.0", "4.0", "3.2", "3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] },
99
"kind": {
1010
"enum": ["extension"]
1111
},

lib/validation/schema/specVersion/kind/extension/project-shim.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
"required": ["specVersion", "kind", "type", "metadata", "shims"],
77
"if": {
88
"properties": {
9-
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0"] }
9+
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0", "5.0"] }
1010
}
1111
},
1212
"then": {
1313
"additionalProperties": false,
1414
"properties": {
1515
"specVersion": {
16-
"enum": ["3.0", "3.1", "3.2", "4.0"]
16+
"enum": ["3.0", "3.1", "3.2", "4.0", "5.0"]
1717
},
1818
"kind": {
1919
"enum": ["extension"]

lib/validation/schema/specVersion/kind/extension/server-middleware.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
"required": ["specVersion", "kind", "type", "metadata", "middleware"],
88
"if": {
99
"properties": {
10-
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0"] }
10+
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0", "5.0"] }
1111
}
1212
},
1313
"then": {
1414
"additionalProperties": false,
1515
"properties": {
16-
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0"] },
16+
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0", "5.0"] },
1717
"kind": {
1818
"enum": ["extension"]
1919
},

lib/validation/schema/specVersion/kind/extension/task.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
"required": ["specVersion", "kind", "type", "metadata", "task"],
77
"if": {
88
"properties": {
9-
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0"] }
9+
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0", "5.0"] }
1010
}
1111
},
1212
"then": {
1313
"additionalProperties": false,
1414
"properties": {
15-
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0"] },
15+
"specVersion": { "enum": ["3.0", "3.1", "3.2", "4.0", "5.0"] },
1616
"kind": {
1717
"enum": ["extension"]
1818
},

lib/validation/schema/specVersion/kind/project.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
"type": "object",
66
"required": ["specVersion", "type"],
77
"properties": {
8-
"specVersion": { "enum": ["4.0", "3.2", "3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] },
8+
"specVersion": { "enum": ["5.0", "4.0", "3.2", "3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] },
99
"kind": {
1010
"enum": ["project", null],
1111
"$comment": "Using null to allow not defining 'kind' which defaults to project"
1212
},
1313
"type": {
1414
"enum": [
1515
"application",
16+
"component",
1617
"library",
1718
"theme-library",
1819
"module"
@@ -61,6 +62,16 @@
6162
},
6263
"then": {
6364
"$ref": "project/module.json"
65+
},
66+
"else": {
67+
"if": {
68+
"properties": {
69+
"type": {"const": "component"}
70+
}
71+
},
72+
"then": {
73+
"$ref": "project/component.json"
74+
}
6475
}
6576
}
6677
}

lib/validation/schema/specVersion/kind/project/application.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
"required": ["specVersion", "type", "metadata"],
77
"if": {
88
"properties": {
9-
"specVersion": { "enum": ["4.0"] }
9+
"specVersion": { "enum": ["4.0", "5.0"] }
1010
}
1111
},
1212
"then": {
1313
"additionalProperties": false,
1414
"properties": {
15-
"specVersion": { "enum": ["4.0"] },
15+
"specVersion": { "enum": ["4.0", "5.0"] },
1616
"kind": {
1717
"enum": ["project", null]
1818
},

0 commit comments

Comments
 (0)