Skip to content

Commit

Permalink
[FEATURE] Apply specVersion defaults from ui5.yaml.json schema (#733)
Browse files Browse the repository at this point in the history
JIRA: CPOUI5FOUNDATION-835

---------

Co-authored-by: Günter Klatt <57760635+KlattG@users.noreply.github.com>
  • Loading branch information
d3xter666 and KlattG authored Jul 2, 2024
1 parent e06c62a commit e3e8f85
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 41 deletions.
6 changes: 5 additions & 1 deletion lib/build/definitions/application.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {enhancePatternWithExcludes} from "./_utils.js";
import {enhanceBundlesWithDefaults} from "../../validation/validator.js";

/**
* Get tasks and their configuration for a given application project
Expand Down Expand Up @@ -84,7 +85,10 @@ export default function({project, taskUtil, getTask}) {
requiresDependencies: true,
taskFunction: async ({workspace, dependencies, taskUtil, options}) => {
const generateBundleTask = await getTask("generateBundle");
return bundles.reduce(function(sequence, bundle) {
// Async resolve default values for bundle definitions and options
const bundlesDefaults = await enhanceBundlesWithDefaults(bundles, taskUtil.getProject());

return bundlesDefaults.reduce(async function(sequence, bundle) {
return sequence.then(function() {
return generateBundleTask.task({
workspace,
Expand Down
6 changes: 5 additions & 1 deletion lib/build/definitions/library.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {enhancePatternWithExcludes} from "./_utils.js";
import {enhanceBundlesWithDefaults} from "../../validation/validator.js";

/**
* Get tasks and their configuration for a given application project
Expand Down Expand Up @@ -119,7 +120,10 @@ export default function({project, taskUtil, getTask}) {
requiresDependencies: true,
taskFunction: async ({workspace, dependencies, taskUtil, options}) => {
const generateBundleTask = await getTask("generateBundle");
return bundles.reduce(function(sequence, bundle) {
// Async resolve default values for bundle definitions and options
const bundlesDefaults = await enhanceBundlesWithDefaults(bundles, taskUtil.getProject());

return bundlesDefaults.reduce(function(sequence, bundle) {
return sequence.then(function() {
return generateBundleTask.task({
workspace,
Expand Down
75 changes: 72 additions & 3 deletions lib/validation/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const SCHEMA_VARIANTS = {
};

class Validator {
constructor({Ajv, ajvErrors, schemaName}) {
constructor({Ajv, ajvErrors, schemaName, ajvConfig}) {
if (!schemaName || !SCHEMA_VARIANTS[schemaName]) {
throw new Error(
`"schemaName" is missing or incorrect. The available schemaName variants are ${Object.keys(
Expand All @@ -29,11 +29,12 @@ class Validator {

this._schemaName = SCHEMA_VARIANTS[schemaName];

this.ajv = new Ajv({
ajvConfig = Object.assign({
allErrors: true,
jsonPointers: true,
loadSchema: Validator.loadSchema
});
}, ajvConfig);
this.ajv = new Ajv(ajvConfig);
ajvErrors(this.ajv);
}

Expand Down Expand Up @@ -77,6 +78,7 @@ class Validator {
}

const validator = Object.create(null);
const defaultsValidator = Object.create(null);

async function _validate(schemaName, options) {
if (!validator[schemaName]) {
Expand All @@ -91,6 +93,27 @@ async function _validate(schemaName, options) {
await schemaValidator.validate(options);
}

async function _validateAndSetDefaults(schemaName, options) {
if (!defaultsValidator[schemaName]) {
defaultsValidator[schemaName] = (async () => {
const {default: Ajv} = await import("ajv");
const {default: ajvErrors} = await import("ajv-errors");
return new Validator({Ajv, ajvErrors, ajvConfig: {useDefaults: true}, schemaName});
})();
}

// When AJV is configured with useDefaults: true, it may add properties to the
// provided configuration that were not initially present. This behavior can
// lead to unexpected side effects and potential issues. To avoid these
// problems, we create a copy of the configuration. If we need the altered
// configuration later, we return this copied version.
const optionsCopy = structuredClone(options);
const schemaValidator = await defaultsValidator[schemaName];
await schemaValidator.validate(optionsCopy);

return optionsCopy;
}

/**
* Validates the given ui5 configuration.
*
Expand All @@ -114,6 +137,52 @@ export async function validate(options) {
await _validate("ui5", options);
}

/**
* Validates the given ui5 configuration and returns default values if none are provided.
*
* @public
* @function
* @static
* @param {object} options
* @param {object} options.config The UI5 Configuration to validate
* @param {object} options.project Project information
* @param {string} options.project.id ID of the project
* @param {object} [options.yaml] YAML information
* @param {string} options.yaml.path Path of the YAML file
* @param {string} options.yaml.source Content of the YAML file
* @param {number} [options.yaml.documentIndex=0] Document index in case the YAML file contains multiple documents
* @throws {module:@ui5/project/validation/ValidationError}
* Rejects with a {@link @ui5/project/validation/ValidationError ValidationError}
* when the validation fails.
* @returns {Promise<options>} Returns a Promise that resolves when the validation succeeds
*/
export async function getDefaults(options) {
return await _validateAndSetDefaults("ui5", options);
}

/**
* Enhances bundleDefinition by adding missing properties with their respective default values.
*
* @param {object[]} bundles Bundles to be enhanced
* @param {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleDefinition} bundles[].bundleDefinition
* Module bundle definition
* @param {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleOptions} [bundles[].bundleOptions]
* Module bundle options
* @param {module:@ui5/project/specifications/Project} project The project to get metadata from
* @returns {Promise<object>} The enhanced BundleDefinition & BundleOptions
*/
export async function enhanceBundlesWithDefaults(bundles, project) {
const config = {
specVersion: `${project.getSpecVersion()}`,
type: `${project.getType()}`,
metadata: {name: project.getName()},
builder: {bundles}
};
const result = await getDefaults({config, project: {id: project.getName()}});

return result.config.builder.bundles;
}

/**
* Validates the given ui5-workspace configuration.
*
Expand Down
57 changes: 39 additions & 18 deletions test/lib/build/definitions/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ function getMockProject() {
}

test.beforeEach((t) => {
t.context.project = getMockProject();
t.context.taskUtil = {
getProject: sinon.stub().returns(t.context.project),
isRootProject: sinon.stub().returns(true),
getBuildOption: sinon.stub(),
getInterface: sinon.stub()
};

t.context.project = getMockProject();
t.context.getTask = sinon.stub();
});

Expand Down Expand Up @@ -198,12 +199,14 @@ test("Custom bundles", async (t) => {
"project/b/sectionsA/",
"!project/b/sectionsA/section2**",
]
}],
sort: true
}]
},
bundleOptions: {
optimize: true,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}, {
bundleDefinition: {
Expand All @@ -215,12 +218,14 @@ test("Custom bundles", async (t) => {
"project/b/sectionsB/",
"!project/b/sectionsB/section2**",
]
}],
sort: true
}]
},
bundleOptions: {
optimize: false,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}];

Expand Down Expand Up @@ -322,13 +327,20 @@ test("Custom bundles", async (t) => {
filters: [
"project/b/sectionsA/",
"!project/b/sectionsA/section2**",
]
}],
sort: true
],
declareRawModules: false,
renderer: false,
resolve: false,
resolveConditional: false,
sort: true,
}]
},
bundleOptions: {
optimize: true,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}
}, "generateBundle task got called with correct arguments");
Expand All @@ -346,13 +358,20 @@ test("Custom bundles", async (t) => {
filters: [
"project/b/sectionsB/",
"!project/b/sectionsB/section2**",
]
}],
sort: true
],
declareRawModules: false,
renderer: false,
resolve: false,
resolveConditional: false,
sort: true,
}]
},
bundleOptions: {
optimize: false,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}
}, "generateBundle task got called with correct arguments");
Expand Down Expand Up @@ -415,12 +434,14 @@ test("generateComponentPreload with custom paths, excludes and custom bundle", (
"project/b/sectionsA/",
"!project/b/sectionsA/section2**",
]
}],
sort: true
}]
},
bundleOptions: {
optimize: true,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}];

Expand Down
57 changes: 39 additions & 18 deletions test/lib/build/definitions/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ function getMockProject() {
}

test.beforeEach((t) => {
t.context.project = getMockProject();
t.context.taskUtil = {
getProject: sinon.stub().returns(t.context.project),
isRootProject: sinon.stub().returns(true),
getBuildOption: sinon.stub(),
getInterface: sinon.stub()
};

t.context.project = getMockProject();
t.context.getTask = sinon.stub();
});

Expand Down Expand Up @@ -277,12 +278,14 @@ test("Custom bundles", async (t) => {
"project/b/sectionsA/",
"!project/b/sectionsA/section2**",
]
}],
sort: true
}]
},
bundleOptions: {
optimize: true,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}, {
bundleDefinition: {
Expand All @@ -294,12 +297,14 @@ test("Custom bundles", async (t) => {
"project/b/sectionsB/",
"!project/b/sectionsB/section2**",
]
}],
sort: true
}]
},
bundleOptions: {
optimize: false,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}];

Expand Down Expand Up @@ -415,13 +420,20 @@ test("Custom bundles", async (t) => {
filters: [
"project/b/sectionsA/",
"!project/b/sectionsA/section2**",
]
}],
sort: true
],
declareRawModules: false,
renderer: false,
resolve: false,
resolveConditional: false,
sort: true,
}]
},
bundleOptions: {
optimize: true,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}
}, "generateBundle task got called with correct arguments");
Expand All @@ -439,13 +451,20 @@ test("Custom bundles", async (t) => {
filters: [
"project/b/sectionsB/",
"!project/b/sectionsB/section2**",
]
}],
sort: true
],
declareRawModules: false,
renderer: false,
resolve: false,
resolveConditional: false,
sort: true,
}]
},
bundleOptions: {
optimize: false,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}
}, "generateBundle task got called with correct arguments");
Expand Down Expand Up @@ -508,12 +527,14 @@ test("generateComponentPreload with custom paths, excludes and custom bundle", (
"project/b/sectionsA/",
"!project/b/sectionsA/section2**",
]
}],
sort: true
}]
},
bundleOptions: {
optimize: true,
usePredefinedCalls: true
usePredefineCalls: true,
addTryCatchRestartWrapper: false,
decorateBootstrapModule: true,
numberOfParts: 1,
}
}];

Expand Down

0 comments on commit e3e8f85

Please sign in to comment.