From 4a8f2f0bdb3d3642ef781e10fd774e4b1dd0f510 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 14 Jun 2023 17:37:59 +0200 Subject: [PATCH] [FEATURE] ProjectBuilder: Add option to request flat build output New option 'flatOutput'. If set to true, libraries and theme-libraries will omit the "/resources/" directory structure in the build output. --- lib/build/ProjectBuilder.js | 36 +++++++++++++--- lib/build/helpers/BuildContext.js | 12 ++++++ test/lib/build/ProjectBuilder.js | 37 ++++++++++++---- test/lib/build/helpers/BuildContext.js | 59 +++++++++++++++++++++++++- 4 files changed, 126 insertions(+), 18 deletions(-) diff --git a/lib/build/ProjectBuilder.js b/lib/build/ProjectBuilder.js index 63f10aa1c..b148a391f 100644 --- a/lib/build/ProjectBuilder.js +++ b/lib/build/ProjectBuilder.js @@ -24,6 +24,11 @@ class ProjectBuilder { * Whether to create a build manifest file for the root project. * This is currently only supported for projects of type 'library' and 'theme-library' * No other dependencies can be included in the build result. + * @property {boolean} [flatOutput=false] + * Whether to write the build results into a flat directory structure, omitting the project + * namespace and the "resources" directory. + * This is currently only supported for projects of type 'library' and 'theme-library'. + * No other dependencies can be included in the build result. * @property {Array.} [includedTasks=[]] List of tasks to be included * @property {Array.} [excludedTasks=[]] List of tasks to be excluded. * If the wildcard '*' is provided, only the included tasks will be executed. @@ -164,10 +169,18 @@ class ProjectBuilder { return filterProject(projectName); }); - if (this._buildContext.getBuildConfig().createBuildManifest && requestedProjects.length > 1) { - throw new Error( - `It is currently not supported to request the creation of a build manifest ` + - `while including any dependencies into the build result`); + if (requestedProjects.length > 1) { + const {createBuildManifest, flatOutput} = this._buildContext.getBuildConfig(); + if (createBuildManifest) { + throw new Error( + `It is currently not supported to request the creation of a build manifest ` + + `while including any dependencies into the build result`); + } + if (flatOutput) { + throw new Error( + `It is currently not supported to request flat build output ` + + `while including any dependencies into the build result`); + } } const projectBuildContexts = await this._createRequiredBuildContexts(requestedProjects); @@ -359,13 +372,22 @@ class ProjectBuilder { const project = projectBuildContext.getProject(); const taskUtil = projectBuildContext.getTaskUtil(); const buildConfig = this._buildContext.getBuildConfig(); + const {createBuildManifest, flatOutput} = buildConfig; + + let readerStyle = "dist"; + if (createBuildManifest) { + // Ensure buildtime (=namespaced) style when writing with a build manifest + readerStyle = "buildtime"; + } else if (flatOutput) { + readerStyle = "flat"; + } + const reader = project.getReader({ - // Force buildtime (=namespaced) style when writing with a build manifest - style: taskUtil.isRootProject() && buildConfig.createBuildManifest ? "buildtime" : "dist" + style: readerStyle }); const resources = await reader.byGlob("/**/*"); - if (taskUtil.isRootProject() && buildConfig.createBuildManifest) { + if (createBuildManifest) { // Create and write a build manifest metadata file const { default: createBuildManifest diff --git a/lib/build/helpers/BuildContext.js b/lib/build/helpers/BuildContext.js index 48286c562..142ed2f2b 100644 --- a/lib/build/helpers/BuildContext.js +++ b/lib/build/helpers/BuildContext.js @@ -12,6 +12,7 @@ class BuildContext { cssVariables = false, jsdoc = false, createBuildManifest = false, + flatOutput = false, includedTasks = [], excludedTasks = [], } = {}) { if (!graph) { @@ -33,12 +34,23 @@ class BuildContext { `self-contained builds`); } + if (flatOutput && !["library", "theme-library"].includes(graph.getRoot().getType())) { + throw new Error( + `Flat build output is currently not supported for projects of type ` + + graph.getRoot().getType()); + } + if (createBuildManifest && flatOutput) { + throw new Error( + `Build manifest creation is not supported in conjunction with flat build output`); + } + this._graph = graph; this._buildConfig = { selfContained, cssVariables, jsdoc, createBuildManifest, + flatOutput, includedTasks, excludedTasks, }; diff --git a/test/lib/build/ProjectBuilder.js b/test/lib/build/ProjectBuilder.js index 2d1021e1f..6ac2e5a86 100644 --- a/test/lib/build/ProjectBuilder.js +++ b/test/lib/build/ProjectBuilder.js @@ -636,6 +636,7 @@ test.serial("_writeResults: Create build manifest", async (t) => { "createBuildManifest got called with correct project"); t.deepEqual(createBuildManifestStub.getCall(0).args[1], { createBuildManifest: true, + flatOutput: false, cssVariables: false, excludedTasks: [], includedTasks: [], @@ -667,21 +668,28 @@ test.serial("_writeResults: Create build manifest", async (t) => { esmock.purge(ProjectBuilder); }); -test("_writeResults: Do not create build manifest for non-root project", async (t) => { - const {ProjectBuilder, sinon} = t.context; +test.serial("_writeResults: Flat build output", async (t) => { + const {sinon, ProjectBuilder} = t.context; t.context.getRootTypeStub = sinon.stub().returns("library"); const {graph, taskRepository} = t.context; const builder = new ProjectBuilder({ graph, taskRepository, buildConfig: { - createBuildManifest: true + flatOutput: true, + otherBuildConfig: "yes" } }); const dummyResources = [{ _resourceName: "resource.a", getPath: () => "resource.a" + }, { + _resourceName: "resource.b", + getPath: () => "resource.b" + }, { + _resourceName: "resource.c", + getPath: () => "resource.c" }]; const byGlobStub = sinon.stub().resolves(dummyResources); const getReaderStub = sinon.stub().returns({ @@ -690,12 +698,12 @@ test("_writeResults: Do not create build manifest for non-root project", async ( const mockProject = getMockProject("library", "c"); mockProject.getReader = getReaderStub; - const getTagStub = sinon.stub().returns(false); + const getTagStub = sinon.stub().returns(false).onFirstCall().returns(true); const projectBuildContextMock = { getProject: () => mockProject, getTaskUtil: () => { return { - isRootProject: () => false, + isRootProject: () => true, getTag: getTagStub, STANDARD_TAGS: { OmitFromBuildResult: "OmitFromBuildResultTag" @@ -711,15 +719,26 @@ test("_writeResults: Do not create build manifest for non-root project", async ( t.is(getReaderStub.callCount, 1, "One reader requested"); t.deepEqual(getReaderStub.getCall(0).args[0], { - style: "dist" + style: "flat" }, "Reader requested expected style"); - t.is(getTagStub.callCount, 1, "TaskUtil#getTag got called once"); + t.is(byGlobStub.callCount, 1, "One byGlob call"); + t.is(byGlobStub.getCall(0).args[0], "/**/*", "byGlob called with expected pattern"); - t.is(writerMock.write.callCount, 1, "Write got called once"); - t.is(writerMock.write.getCall(0).args[0], dummyResources[0], "Write got called with only resource"); + t.is(getTagStub.callCount, 3, "TaskUtil#getTag got called three times"); + t.is(getTagStub.getCall(0).args[0], dummyResources[0], "TaskUtil#getTag called with first resource"); + t.is(getTagStub.getCall(0).args[1], "OmitFromBuildResultTag", "TaskUtil#getTag called with correct tag value"); + t.is(getTagStub.getCall(1).args[0], dummyResources[1], "TaskUtil#getTag called with second resource"); + t.is(getTagStub.getCall(1).args[1], "OmitFromBuildResultTag", "TaskUtil#getTag called with correct tag value"); + t.is(getTagStub.getCall(2).args[0], dummyResources[2], "TaskUtil#getTag called with third resource"); + t.is(getTagStub.getCall(2).args[1], "OmitFromBuildResultTag", "TaskUtil#getTag called with correct tag value"); + + t.is(writerMock.write.callCount, 2, "Write got called twice"); + t.is(writerMock.write.getCall(0).args[0], dummyResources[1], "Write got called with second resource"); + t.is(writerMock.write.getCall(1).args[0], dummyResources[2], "Write got called with third resource"); }); + test("_executeCleanupTasks", async (t) => { const {graph, taskRepository, ProjectBuilder, sinon} = t.context; const builder = new ProjectBuilder({graph, taskRepository}); diff --git a/test/lib/build/helpers/BuildContext.js b/test/lib/build/helpers/BuildContext.js index 1a072668a..fe3844bc6 100644 --- a/test/lib/build/helpers/BuildContext.js +++ b/test/lib/build/helpers/BuildContext.js @@ -46,6 +46,7 @@ test("getBuildConfig: Default values", (t) => { t.deepEqual(buildContext.getBuildConfig(), { selfContained: false, + flatOutput: false, cssVariables: false, jsdoc: false, createBuildManifest: false, @@ -63,6 +64,7 @@ test("getBuildConfig: Custom values", (t) => { } }, "taskRepository", { selfContained: true, + flatOutput: false, cssVariables: true, jsdoc: true, createBuildManifest: false, @@ -72,6 +74,7 @@ test("getBuildConfig: Custom values", (t) => { t.deepEqual(buildContext.getBuildConfig(), { selfContained: true, + flatOutput: false, cssVariables: true, jsdoc: true, createBuildManifest: false, @@ -162,7 +165,59 @@ test("createBuildManifest supported for jsdoc build", (t) => { }); }); -test("getBuildOption", (t) => { +test("flatOutput not supported for type application", (t) => { + const err = t.throws(() => { + new BuildContext({ + getRoot: () => { + return { + getType: () => "application" + }; + } + }, "taskRepository", { + flatOutput: true + }); + }); + t.is(err.message, + "Flat build output is currently not supported for projects of type application", + "Threw with expected error message"); +}); + +test("flatOutput not supported for type module", (t) => { + const err = t.throws(() => { + new BuildContext({ + getRoot: () => { + return { + getType: () => "module" + }; + } + }, "taskRepository", { + flatOutput: true + }); + }); + t.is(err.message, + "Flat build output is currently not supported for projects of type module", + "Threw with expected error message"); +}); + +test("flatOutput not supported for createBuildManifest build", (t) => { + const err = t.throws(() => { + new BuildContext({ + getRoot: () => { + return { + getType: () => "library" + }; + } + }, "taskRepository", { + createBuildManifest: true, + flatOutput: true + }); + }); + t.is(err.message, + "Build manifest creation is not supported in conjunction with flat build output", + "Threw with expected error message"); +}); + +test("getOption", (t) => { const buildContext = new BuildContext("graph", "taskRepository", { cssVariables: "value", }); @@ -171,7 +226,7 @@ test("getBuildOption", (t) => { "Returned correct value for build configuration 'cssVariables'"); t.is(buildContext.getOption("selfContained"), undefined, "Returned undefined for build configuration 'selfContained' " + - "(not exposed as buold option)"); + "(not exposed as build option)"); }); test("createProjectContext", async (t) => {