Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Apply specVersion defaults from ui5.yaml.json schema #733

Merged
merged 16 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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