Skip to content

Commit b55100e

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

File tree

72 files changed

+2562
-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.

72 files changed

+2562
-76
lines changed

lib/build/TaskRunner.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class TaskRunner {
5050
case "application":
5151
buildDefinition = "./definitions/application.js";
5252
break;
53+
case "component":
54+
buildDefinition = "./definitions/application.js";
55+
break;
5356
case "library":
5457
buildDefinition = "./definitions/library.js";
5558
break;

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

0 commit comments

Comments
 (0)