Skip to content

Commit

Permalink
[FEATURE] Add 'component' type and specVersion 3.1
Browse files Browse the repository at this point in the history
Basically a copy of the 'application' type but with the same path
mappings as 'library' (directories: 'src' and 'test') and a namespaced
runtime path.

JIRA: CPOUI5FOUNDATION-27
  • Loading branch information
RandomByte committed Jun 14, 2023
1 parent 78d4775 commit 44024d9
Show file tree
Hide file tree
Showing 78 changed files with 2,539 additions and 73 deletions.
3 changes: 3 additions & 0 deletions lib/specifications/Specification.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class Specification {
case "application": {
return createAndInitializeSpec("types/Application.js", parameters);
}
case "component": {
return createAndInitializeSpec("types/Component.js", parameters);
}
case "library": {
return createAndInitializeSpec("types/Library.js", parameters);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/specifications/SpecificationVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const SPEC_VERSION_PATTERN = /^\d+\.\d+$/;
const SUPPORTED_VERSIONS = [
"0.1", "1.0", "1.1",
"2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6",
"3.0"
"3.0", "3.1"
];

/**
Expand Down
8 changes: 7 additions & 1 deletion lib/specifications/types/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,17 @@ class Application extends ComponentProject {
return null; // Applications do not have a dedicated test directory
}

/**
* Get a resource reader for the sources of the project (excluding any test resources)
* without a virtual base path
*
* @returns {@ui5/fs/ReaderCollection} Reader collection
*/
_getRawSourceReader() {
return createReader({
fsBasePath: this.getSourcePath(),
virBasePath: "/",
name: `Source reader for application project ${this.getName()}`,
name: `Raw source reader for application project ${this.getName()}`,
project: this
});
}
Expand Down
259 changes: 259 additions & 0 deletions lib/specifications/types/Component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import fsPath from "node:path";
import ComponentProject from "../ComponentProject.js";
import {createReader} from "@ui5/fs/resourceFactory";

/**
* Component
*
* @public
* @class
* @alias @ui5/project/specifications/types/Component
* @extends @ui5/project/specifications/ComponentProject
* @hideconstructor
*/
class Component extends ComponentProject {
constructor(parameters) {
super(parameters);

this._pManifests = Object.create(null);

this._srcPath = "src";
this._testPath = "test";
this._testPathExists = false;

this._propertiesFilesSourceEncoding = "UTF-8";
}

/* === Attributes === */

/**
* Get the cachebuster signature type configuration of the project
*
* @returns {string} <code>time</code> or <code>hash</code>
*/
getCachebusterSignatureType() {
return this._config.builder && this._config.builder.cachebuster &&
this._config.builder.cachebuster.signatureType || "time";
}

/**
* Get the path of the project's source directory. This might not be POSIX-style on some platforms.
*
* @public
* @returns {string} Absolute path to the source directory of the project
*/
getSourcePath() {
return fsPath.join(this.getRootPath(), this._srcPath);
}

/* === Resource Access === */
/**
* Get a resource reader for the sources of the project (excluding any test resources)
*
* @param {string[]} excludes List of glob patterns to exclude
* @returns {@ui5/fs/ReaderCollection} Reader collection
*/
_getSourceReader(excludes) {
return createReader({
fsBasePath: this.getSourcePath(),
virBasePath: `/resources/${this._namespace}/`,
name: `Source reader for component project ${this.getName()}`,
project: this,
excludes
});
}

/**
* Get a resource reader for the test-resources of the project
*
* @param {string[]} excludes List of glob patterns to exclude
* @returns {@ui5/fs/ReaderCollection} Reader collection
*/
_getTestReader(excludes) {
if (!this._testPathExists) {
return null;
}
const testReader = createReader({
fsBasePath: fsPath.join(this.getRootPath(), this._testPath),
virBasePath: `/test-resources/${this._namespace}/`,
name: `Runtime test-resources reader for component project ${this.getName()}`,
project: this,
excludes
});
return testReader;
}

/**
* Get a resource reader for the sources of the project (excluding any test resources)
* without a virtual base path
*
* @returns {@ui5/fs/ReaderCollection} Reader collection
*/
_getRawSourceReader() {
return createReader({
fsBasePath: this.getSourcePath(),
virBasePath: "/",
name: `Raw source reader for component project ${this.getName()}`,
project: this
});
}

/* === Internals === */
/**
* @private
* @param {object} config Configuration object
*/
async _configureAndValidatePaths(config) {
await super._configureAndValidatePaths(config);

if (config.resources && config.resources.configuration && config.resources.configuration.paths) {
if (config.resources.configuration.paths.src) {
this._srcPath = config.resources.configuration.paths.src;
}
if (config.resources.configuration.paths.test) {
this._testPath = config.resources.configuration.paths.test;
}
}
if (!(await this._dirExists("/" + this._srcPath))) {
throw new Error(
`Unable to find source directory '${this._srcPath}' in component project ${this.getName()}`);
}
this._testPathExists = await this._dirExists("/" + this._testPath);

this._log.verbose(`Path mapping for component project ${this.getName()}:`);
this._log.verbose(` Physical root path: ${this.getRootPath()}`);
this._log.verbose(` Mapped to:`);
this._log.verbose(` /resources/ => ${this._srcPath}`);
this._log.verbose(
` /test-resources/ => ${this._testPath}${this._testPathExists ? "" : " [does not exist]"}`);
}

/**
* @private
* @param {object} config Configuration object
* @param {object} buildDescription Cache metadata object
*/
async _parseConfiguration(config, buildDescription) {
await super._parseConfiguration(config, buildDescription);

if (buildDescription) {
this._namespace = buildDescription.namespace;
return;
}
this._namespace = await this._getNamespace();
}

/**
* Determine component namespace either based on a project`s
* manifest.json or manifest.appdescr_variant (fallback if present)
*
* @returns {string} Namespace of the project
* @throws {Error} if namespace can not be determined
*/
async _getNamespace() {
try {
return await this._getNamespaceFromManifestJson();
} catch (manifestJsonError) {
if (manifestJsonError.code !== "ENOENT") {
throw manifestJsonError;
}
// No manifest.json present
// => attempt fallback to manifest.appdescr_variant (typical for App Variants)
try {
return await this._getNamespaceFromManifestAppDescVariant();
} catch (appDescVarError) {
if (appDescVarError.code === "ENOENT") {
// Fallback not possible: No manifest.appdescr_variant present
// => Throw error indicating missing manifest.json
// (do not mention manifest.appdescr_variant since it is only
// relevant for the rather "uncommon" App Variants)
throw new Error(
`Could not find required manifest.json for project ` +
`${this.getName()}: ${manifestJsonError.message}`);
}
throw appDescVarError;
}
}
}

/**
* Determine application namespace by checking manifest.json.
* Any maven placeholders are resolved from the projects pom.xml
*
* @returns {string} Namespace of the project
* @throws {Error} if namespace can not be determined
*/
async _getNamespaceFromManifestJson() {
const manifest = await this._getManifest("/manifest.json");
let appId;
// check for a proper sap.app/id in manifest.json to determine namespace
if (manifest["sap.app"] && manifest["sap.app"].id) {
appId = manifest["sap.app"].id;
} else {
throw new Error(
`No sap.app/id configuration found in manifest.json of project ${this.getName()}`);
}

if (this._hasMavenPlaceholder(appId)) {
try {
appId = await this._resolveMavenPlaceholder(appId);
} catch (err) {
throw new Error(
`Failed to resolve namespace of project ${this.getName()}: ${err.message}`);
}
}
const namespace = appId.replace(/\./g, "/");
this._log.verbose(
`Namespace of project ${this.getName()} is ${namespace} (from manifest.json)`);
return namespace;
}

/**
* Determine application namespace by checking manifest.appdescr_variant.
*
* @returns {string} Namespace of the project
* @throws {Error} if namespace can not be determined
*/
async _getNamespaceFromManifestAppDescVariant() {
const manifest = await this._getManifest("/manifest.appdescr_variant");
let appId;
// check for the id property in manifest.appdescr_variant to determine namespace
if (manifest && manifest.id) {
appId = manifest.id;
} else {
throw new Error(
`No "id" property found in manifest.appdescr_variant of project ${this.getName()}`);
}

const namespace = appId.replace(/\./g, "/");
this._log.verbose(
`Namespace of project ${this.getName()} is ${namespace} (from manifest.appdescr_variant)`);
return namespace;
}

/**
* Reads and parses a JSON file with the provided name from the projects source directory
*
* @param {string} filePath Name of the JSON file to read. Typically "manifest.json" or "manifest.appdescr_variant"
* @returns {Promise<object>} resolves with an object containing the content requested manifest file
*/
async _getManifest(filePath) {
if (this._pManifests[filePath]) {
return this._pManifests[filePath];
}
return this._pManifests[filePath] = this._getRawSourceReader().byPath(filePath)
.then(async (resource) => {
if (!resource) {
throw new Error(
`Could not find resource ${filePath} in project ${this.getName()}`);
}
return JSON.parse(await resource.getString());
}).catch((err) => {
throw new Error(
`Failed to read ${filePath} for project ` +
`${this.getName()}: ${err.message}`);
});
}
}

export default Component;
5 changes: 3 additions & 2 deletions lib/specifications/types/Library.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,18 @@ class Library extends ComponentProject {
}

/**
*
* Get a resource reader for the sources of the project (excluding any test resources)
* without a virtual base path
* In the future the path structure can be flat or namespaced depending on the project
* setup
*
* @returns {@ui5/fs/ReaderCollection} Reader collection
*/
_getRawSourceReader() {
return resourceFactory.createReader({
fsBasePath: this.getSourcePath(),
virBasePath: "/",
name: `Source reader for library project ${this.getName()}`,
name: `Raw source reader for library project ${this.getName()}`,
project: this
});
}
Expand Down
2 changes: 1 addition & 1 deletion lib/validation/schema/specVersion/kind/extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "object",
"required": ["specVersion", "kind", "type", "metadata"],
"properties": {
"specVersion": { "enum": ["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] },
"specVersion": { "enum": ["3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] },
"kind": {
"enum": ["extension"]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
"required": ["specVersion", "kind", "type", "metadata", "shims"],
"if": {
"properties": {
"specVersion": { "enum": ["3.0"] }
"specVersion": { "enum": ["3.0", "3.1"] }
}
},
"then": {
"additionalProperties": false,
"properties": {
"specVersion": {
"enum": ["3.0"]
"enum": ["3.0", "3.1"]
},
"kind": {
"enum": ["extension"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
"required": ["specVersion", "kind", "type", "metadata", "middleware"],
"if": {
"properties": {
"specVersion": { "enum": ["3.0"] }
"specVersion": { "enum": ["3.0", "3.1"] }
}
},
"then": {
"additionalProperties": false,
"properties": {
"specVersion": { "enum": ["3.0"] },
"specVersion": { "enum": ["3.0", "3.1"] },
"kind": {
"enum": ["extension"]
},
Expand Down
4 changes: 2 additions & 2 deletions lib/validation/schema/specVersion/kind/extension/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
"required": ["specVersion", "kind", "type", "metadata", "task"],
"if": {
"properties": {
"specVersion": { "enum": ["3.0"] }
"specVersion": { "enum": ["3.0", "3.1"] }
}
},
"then": {
"additionalProperties": false,
"properties": {
"specVersion": { "enum": ["3.0"] },
"specVersion": { "enum": ["3.0", "3.1"] },
"kind": {
"enum": ["extension"]
},
Expand Down
Loading

0 comments on commit 44024d9

Please sign in to comment.