diff --git a/internal/documentation/docs/pages/Builder.md b/internal/documentation/docs/pages/Builder.md index 5ac7c647c48..1f3a3e63efc 100644 --- a/internal/documentation/docs/pages/Builder.md +++ b/internal/documentation/docs/pages/Builder.md @@ -58,7 +58,7 @@ All available standard tasks are documented under **API -> @ui5/builder -> tasks 3 Enabled in `self-contained` build, which disables `generateComponentPreload` and `generateLibraryPreload` 4 Enabled for projects defining a [bundle configuration](./Configuration.md#custom-bundling) 5 Can be enabled for framework projects via the `includeTask` option. For other projects, this task is skipped -6 Disabled for the server due to a corresponding middleware producing the same output +6 Disabled for the server (`ui5 serve` and `ui5 build for-server`) due to a corresponding middleware producing the same output 7 Enabled for Specification Version 4.0 and lower, and for framework projects. For other projects using Specification Version 5.0 and higher, this task is skipped ### minify @@ -227,6 +227,8 @@ The cache may grow over time. It can be deleted at any time to reclaim disk spac ::: info By default, build caches created by `ui5 build` and `ui5 serve` are **separate and cannot be mixed**. Each command executes a distinct set of tasks, resulting in separate caches tailored to its specific use case. For more details on server caching, see the [UI5 Server documentation](./Server.md). + +To pre-populate the cache that `ui5 serve` reuses, run `ui5 build for-server`. It executes the same task set as the server (for example, it skips `generateVersionInfo`, which the server generates on the fly) and only warms the cache without writing a build result to the destination directory. A subsequent `ui5 serve` then reuses those cached results. ::: diff --git a/packages/cli/lib/cli/commands/build.js b/packages/cli/lib/cli/commands/build.js index bd9c9d00c80..928325301f4 100644 --- a/packages/cli/lib/cli/commands/build.js +++ b/packages/cli/lib/cli/commands/build.js @@ -32,6 +32,14 @@ build.builder = function(cli) { builder: noop, middlewares: [baseMiddleware] }) + .command("for-server", + "Build the project into the shared build cache for reuse by 'ui5 serve'. " + + "Aligns the task set with the server (e.g. skips 'generateVersionInfo') and does not " + + "write the build result to the destination directory.", { + handler: handleBuild, + builder: noop, + middlewares: [baseMiddleware] + }) .option("include-all-dependencies", { describe: "Include all dependencies in the build result. " + "This is equivalent to '--include-dependency \"*\"'", @@ -215,6 +223,8 @@ async function handleBuild(argv) { }, selfContained: command === "self-contained", jsdoc: command === "jsdoc", + server: command === "for-server", + cacheOnly: command === "for-server", includedTasks: argv["include-task"], excludedTasks: argv["exclude-task"], outputStyle: argv["output-style"], diff --git a/packages/cli/test/lib/cli/commands/build.js b/packages/cli/test/lib/cli/commands/build.js index d439bde5cf7..1dca6308013 100644 --- a/packages/cli/test/lib/cli/commands/build.js +++ b/packages/cli/test/lib/cli/commands/build.js @@ -52,6 +52,8 @@ function getDefaultBuilderArgs() { createBuildManifest: false, selfContained: false, jsdoc: false, + server: false, + cacheOnly: false, includedTasks: undefined, excludedTasks: undefined, outputStyle: "Default" @@ -120,6 +122,19 @@ test.serial("ui5 build jsdoc", async (t) => { t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "JSDoc build called with expected arguments"); }); +test.serial("ui5 build for-server", async (t) => { + const {build, argv, builder, expectedBuilderArgs} = t.context; + + argv._.push("for-server"); + + await build.handler(argv); + + expectedBuilderArgs.server = true; + expectedBuilderArgs.cacheOnly = true; + t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, + "for-server build called with expected arguments"); +}); + test.serial("ui5 build --framework-version", async (t) => { const {build, argv, graphFromPackageDependenciesStub} = t.context; diff --git a/packages/project/lib/build/ProjectBuilder.js b/packages/project/lib/build/ProjectBuilder.js index 4d28d9ad0e6..cdb780c1028 100644 --- a/packages/project/lib/build/ProjectBuilder.js +++ b/packages/project/lib/build/ProjectBuilder.js @@ -23,6 +23,11 @@ class ProjectBuilder { * @typedef {object} @ui5/project/build/ProjectBuilder~BuildConfiguration * @property {boolean} [selfContained=false] Flag to activate self contained build * @property {boolean} [jsdoc=false] Flag to activate JSDoc build + * @property {boolean} [server=false] + * Flag to activate a server-aligned build. Disables tasks whose output the server generates + * on the fly (currently generateVersionInfo) by default, matching the task set + * used by @ui5/server. The disabled tasks can still be re-enabled via + * includedTasks. * @property {boolean} [createBuildManifest=false] * Whether to create a build manifest file for the root project. * This is currently only supported for projects of type 'library' and 'theme-library' @@ -195,6 +200,10 @@ class ProjectBuilder { * @param {boolean} [parameters.includeRootProject=true] Whether to include the root project * @param {Array.} [parameters.includedDependencies=[]] List of dependencies to include * @param {Array.} [parameters.excludedDependencies=[]] List of dependencies to exclude + * @param {@ui5/project/build/ProjectBuilder~DependencyIncludes} [parameters.dependencyIncludes] + * Alternative to the includedDependencies and excludedDependencies parameters. + * Allows for a more sophisticated configuration for defining which dependencies should be built. + * If this is provided, the other mentioned parameters are ignored. * @param {AbortSignal} [parameters.signal] Signal to abort the build * @param {Function} [projectBuiltCallback] Callback invoked after each project is built * @returns {Promise} Promise resolving with array of processed project names @@ -202,10 +211,11 @@ class ProjectBuilder { async build({ includeRootProject = true, includedDependencies = [], excludedDependencies = [], + dependencyIncludes, signal, }, projectBuiltCallback) { const requestedProjects = this._determineRequestedProjects( - includeRootProject, includedDependencies, excludedDependencies); + includeRootProject, includedDependencies, excludedDependencies, dependencyIncludes); return await this.#build(requestedProjects, projectBuiltCallback, signal); } diff --git a/packages/project/lib/build/helpers/BuildContext.js b/packages/project/lib/build/helpers/BuildContext.js index 4d398e98a1c..3d25f555807 100644 --- a/packages/project/lib/build/helpers/BuildContext.js +++ b/packages/project/lib/build/helpers/BuildContext.js @@ -18,6 +18,7 @@ class BuildContext { constructor(graph, taskRepository, { // buildConfig selfContained = false, jsdoc = false, + server = false, createBuildManifest = false, outputStyle = OutputStyleEnum.Default, includedTasks = [], excludedTasks = [], @@ -69,6 +70,7 @@ class BuildContext { this._buildConfig = { selfContained, jsdoc, + server, createBuildManifest, outputStyle, includedTasks, diff --git a/packages/project/lib/build/helpers/composeTaskList.js b/packages/project/lib/build/helpers/composeTaskList.js index dca89a180e3..34631577908 100644 --- a/packages/project/lib/build/helpers/composeTaskList.js +++ b/packages/project/lib/build/helpers/composeTaskList.js @@ -3,6 +3,8 @@ * * Sets specific tasks to be disabled by default, these tasks need to be included explicitly. * Based on the selected build mode (selfContained|preload), different tasks are enabled. + * When the server flag is set, tasks whose output the server generates on the fly + * (currently generateVersionInfo) are disabled by default. * Tasks can be enabled or disabled. The wildcard * is also supported and affects all tasks. * * @private @@ -11,7 +13,7 @@ * Build configuration * @returns {Array} List of tasks to be executed */ -export default function composeTaskList(allTasks, {selfContained, jsdoc, includedTasks, excludedTasks}) { +export default function composeTaskList(allTasks, {selfContained, jsdoc, server, includedTasks, excludedTasks}) { let selectedTasks = allTasks.reduce((list, key) => { list[key] = true; return list; @@ -59,6 +61,13 @@ export default function composeTaskList(allTasks, {selfContained, jsdoc, include selectedTasks.generateFlexChangesBundle = false; } + if (server) { + // The 'versionInfo' server middleware generates the version info on the fly, so a server + // build does not run generateVersionInfo. Keeping it out of the default set here lets + // 'ui5 serve' and 'ui5 build for-server' share one build-cache entry. + selectedTasks.generateVersionInfo = false; + } + // Exclude tasks for (let i = 0; i < excludedTasks.length; i++) { const taskName = excludedTasks[i]; diff --git a/packages/project/lib/graph/ProjectGraph.js b/packages/project/lib/graph/ProjectGraph.js index 3a6cf7f5327..8ddc8e40e55 100644 --- a/packages/project/lib/graph/ProjectGraph.js +++ b/packages/project/lib/graph/ProjectGraph.js @@ -706,6 +706,14 @@ class ProjectGraph { * part of the build result. If this is provided, the other mentioned parameters will be ignored. * @param {boolean} [parameters.selfContained=false] Flag to activate self contained build * @param {boolean} [parameters.jsdoc=false] Flag to activate JSDoc build + * @param {boolean} [parameters.server=false] + * Flag to activate a server-aligned build. Disables tasks whose output the server generates + * on the fly (currently generateVersionInfo) by default, so the build result + * matches what @ui5/server produces. + * @param {boolean} [parameters.cacheOnly=false] + * Only populate the build cache without writing the build result to destPath. + * Intended to warm the shared build cache that a subsequent ui5 serve reuses. + * When set, destPath and cleanDest are ignored. * @param {boolean} [parameters.createBuildManifest=false] * Whether to create a build manifest file for the root project. * This is currently only supported for projects of type 'library' and 'theme-library' @@ -725,7 +733,8 @@ class ProjectGraph { destPath, cleanDest = false, includedDependencies = [], excludedDependencies = [], dependencyIncludes, - selfContained = false, jsdoc = false, createBuildManifest = false, + selfContained = false, jsdoc = false, server = false, createBuildManifest = false, + cacheOnly = false, includedTasks = [], excludedTasks = [], outputStyle = OutputStyleEnum.Default, cache = Cache.Default, @@ -745,13 +754,28 @@ class ProjectGraph { graph: this, taskRepository: await this._getTaskRepository(), buildConfig: { - selfContained, jsdoc, + selfContained, jsdoc, server, createBuildManifest, includedTasks, excludedTasks, outputStyle, cache }, ui5DataDir, }); + if (cacheOnly) { + // Build to populate the cache only, without writing the result to a target directory. + // buildToTarget closes the CacheManager itself; the reader-based build() does not, so + // close it explicitly once the build has finished. + try { + await builder.build({ + includeRootProject: true, + includedDependencies, excludedDependencies, + dependencyIncludes, + }); + } finally { + builder.closeCacheManager(); + } + return; + } return await builder.buildToTarget({ destPath, cleanDest, includedDependencies, excludedDependencies, @@ -803,6 +827,9 @@ class ProjectGraph { taskRepository: await this._getTaskRepository(), buildConfig: { selfContained, jsdoc, + // A serve is always a server-aligned build: disable tasks the server generates + // on the fly (e.g. generateVersionInfo). See composeTaskList. + server: true, createBuildManifest, includedTasks, excludedTasks, outputStyle: OutputStyleEnum.Default, diff --git a/packages/project/test/lib/build/BuildServer.integration.js b/packages/project/test/lib/build/BuildServer.integration.js index 61832f020e8..318afbe9623 100644 --- a/packages/project/test/lib/build/BuildServer.integration.js +++ b/packages/project/test/lib/build/BuildServer.integration.js @@ -218,10 +218,6 @@ test.serial("Serve application.a, request application resource", async (t) => { resource: "/test.js", assertions: { projects: { - "library.d": {}, - "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -251,8 +247,7 @@ test.serial("Serve application.a, request application resource", async (t) => { // Note: replaceCopyright is skipped because no copyright is configured in the project "replaceCopyright", "enhanceManifest", - "generateFlexChangesBundle", - "generateVersionInfo" + "generateFlexChangesBundle" ] } } @@ -319,10 +314,6 @@ test.serial("Serve application.a, create and delete a source file", async (t) => resource: "/created.js", assertions: { projects: { - "library.d": {}, - "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -354,8 +345,7 @@ test.serial("Serve application.a, create and delete a source file", async (t) => "escapeNonAsciiCharacters", "replaceCopyright", "enhanceManifest", - "generateFlexChangesBundle", - "generateVersionInfo" + "generateFlexChangesBundle" ] } } @@ -377,13 +367,19 @@ test.serial("Serve application.a, create and delete a source file", async (t) => } }); - // #5 the second file is no longer served, thus requesting it shouldn't trigger a rebuild - // (all projects are still cached from the previous builds) + // #5 the second file is no longer served. Resolving the not-found lookup searches the + // dependencies, which builds the libraries for the first time (the server build no longer + // pre-builds them via generateVersionInfo). await fixtureTester.requestResource({ resource: "/another.js", notFound: true, assertions: { - projects: {} + projects: { + "library.d": {}, + "library.a": {}, + "library.b": {}, + "library.c": {} + } } }); @@ -403,8 +399,7 @@ test.serial("Serve application.a, create and delete a source file", async (t) => "escapeNonAsciiCharacters", "replaceCopyright", "enhanceManifest", - "generateFlexChangesBundle", - "generateVersionInfo" + "generateFlexChangesBundle" ] } } @@ -583,10 +578,9 @@ test.serial("Serve application.a, request application resource AND library resou resources: ["/test.js", "/resources/library/a/.library"], assertions: { projects: { - "library.d": {}, + // Only the requested projects build. Libraries are no longer pulled in by + // generateVersionInfo, which the server build disables by default. "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -658,10 +652,6 @@ test.serial("Serve application.a with --cache=Default", async (t) => { resource: "/test.js", assertions: { projects: { - "library.d": {}, - "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -690,8 +680,7 @@ test.serial("Serve application.a with --cache=Default", async (t) => { "escapeNonAsciiCharacters", "replaceCopyright", "enhanceManifest", - "generateFlexChangesBundle", - "generateVersionInfo" + "generateFlexChangesBundle" ] } } @@ -714,10 +703,6 @@ test.serial("Serve application.a with --cache=Off", async (t) => { resource: "/test.js", assertions: { projects: { - "library.d": {}, - "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -741,10 +726,6 @@ test.serial("Serve application.a with --cache=Off", async (t) => { resource: "/test.js", assertions: { projects: { - "library.d": {}, - "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -765,10 +746,6 @@ test.serial("Serve application.a with --cache=Off", async (t) => { resource: "/test.js", assertions: { projects: { - "library.d": {}, - "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -791,10 +768,6 @@ test.serial("Serve application.a with --cache=Off", async (t) => { resource: "/test.js", assertions: { projects: { - "library.d": {}, - "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -810,10 +783,6 @@ test.serial("Serve application.a with --cache=ReadOnly", async (t) => { resource: "/test.js", assertions: { projects: { - "library.d": {}, - "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -846,8 +815,7 @@ test.serial("Serve application.a with --cache=ReadOnly", async (t) => { "escapeNonAsciiCharacters", "replaceCopyright", "enhanceManifest", - "generateFlexChangesBundle", - "generateVersionInfo" + "generateFlexChangesBundle" ] } } @@ -876,8 +844,7 @@ test.serial("Serve application.a with --cache=ReadOnly", async (t) => { "escapeNonAsciiCharacters", "replaceCopyright", "enhanceManifest", - "generateFlexChangesBundle", - "generateVersionInfo" + "generateFlexChangesBundle" ] } } @@ -894,10 +861,6 @@ test.serial("Serve application.a with --cache=Force (1)", async (t) => { resource: "/test.js", assertions: { projects: { - "library.d": {}, - "library.a": {}, - "library.b": {}, - "library.c": {}, "application.a": {} } } @@ -1238,17 +1201,15 @@ test.serial("Source change during second build retries cleanly without no_cache }); test.serial("Serve application.a (test exclusion of generateVersionInfo)", async (t) => { - // This test verifies that the "generateVersionInfo" task - // can be excluded from the server build via the "excludedTasks" config option. + // The server build (graph.serve, server:true) disables the "generateVersionInfo" task by + // default, because the "versionInfo" middleware generates the version info on the fly. This + // test verifies the default exclusion and that "includedTasks" can re-enable the task. const fixtureTester = t.context.fixtureTester = await FixtureTester.create(t, "application.a"); - // #1 Exclude "generateVersionInfo": - await fixtureTester.serveProject({ - config: { - excludedTasks: ["generateVersionInfo"], - } - }); + // #1 Default server build: "generateVersionInfo" is excluded by default. It does not pull in + // the libraries, so only application.a builds to serve the request. + await fixtureTester.serveProject(); // Request a resource to trigger the build: await fixtureTester.requestResource({ @@ -1274,10 +1235,11 @@ test.serial("Serve application.a (test exclusion of generateVersionInfo)", async await fixtureTester.teardown(); - // #2 Don't exclude tasks (includes "generateVersionInfo"): + // #2 Re-enable "generateVersionInfo" via includedTasks. It collects the version info of all + // libraries, so those get built as well. await fixtureTester.serveProject({ config: { - excludedTasks: [], + includedTasks: ["generateVersionInfo"], } }); diff --git a/packages/project/test/lib/build/ProjectBuilder.js b/packages/project/test/lib/build/ProjectBuilder.js index 25d3d4fe614..ee9bd50f668 100644 --- a/packages/project/test/lib/build/ProjectBuilder.js +++ b/packages/project/test/lib/build/ProjectBuilder.js @@ -164,6 +164,30 @@ test("build", async (t) => { t.is(executeCleanupTasksStub.callCount, 1, "_executeCleanupTasksStub got called once"); }); +test("build: forwards dependencyIncludes to _determineRequestedProjects", async (t) => { + const {graph, taskRepository, ProjectBuilder, sinon} = t.context; + + const builder = new ProjectBuilder({graph, taskRepository}); + + const determineRequestedProjectsStub = sinon.stub(builder, "_determineRequestedProjects").returns([]); + // Short-circuit the actual build; we only assert the requested-projects resolution + sinon.stub(builder._buildContext, "getRequiredProjectContexts").resolves(new Map()); + sinon.stub(builder, "_registerCleanupSigHooks").returns("cleanup sig hooks"); + sinon.stub(builder, "_deregisterCleanupSigHooks"); + sinon.stub(builder, "_executeCleanupTasks").resolves(); + + await builder.build({ + includedDependencies: ["dep a"], + excludedDependencies: ["dep b"], + dependencyIncludes: "dependencyIncludes" + }); + + t.is(determineRequestedProjectsStub.callCount, 1, "_determineRequestedProjects got called once"); + t.deepEqual(determineRequestedProjectsStub.getCall(0).args, [ + true, ["dep a"], ["dep b"], "dependencyIncludes" + ], "_determineRequestedProjects got called with dependencyIncludes forwarded"); +}); + test("build: Conflicting dependency parameters", async (t) => { const {graph, taskRepository, ProjectBuilder} = t.context; @@ -645,6 +669,7 @@ test.serial("_writeResults: Create build manifest", async (t) => { excludedTasks: [], includedTasks: [], jsdoc: false, + server: false, selfContained: false, cache: "Default", }, "createBuildManifest got called with correct build configuration"); diff --git a/packages/project/test/lib/build/helpers/BuildContext.js b/packages/project/test/lib/build/helpers/BuildContext.js index 1e67e125c87..a2b8f3d9bd2 100644 --- a/packages/project/test/lib/build/helpers/BuildContext.js +++ b/packages/project/test/lib/build/helpers/BuildContext.js @@ -83,6 +83,7 @@ test("getBuildConfig: Default values", (t) => { selfContained: false, outputStyle: OutputStyleEnum.Default, jsdoc: false, + server: false, createBuildManifest: false, includedTasks: [], excludedTasks: [], @@ -103,6 +104,7 @@ test("getBuildConfig: Custom values", (t) => { selfContained: true, outputStyle: OutputStyleEnum.Namespace, jsdoc: true, + server: true, createBuildManifest: false, includedTasks: ["included tasks"], excludedTasks: ["excluded tasks"], @@ -113,6 +115,7 @@ test("getBuildConfig: Custom values", (t) => { selfContained: true, outputStyle: OutputStyleEnum.Namespace, jsdoc: true, + server: true, createBuildManifest: false, includedTasks: ["included tasks"], excludedTasks: ["excluded tasks"], diff --git a/packages/project/test/lib/build/helpers/composeTaskList.js b/packages/project/test/lib/build/helpers/composeTaskList.js index f1fb5f905c4..f82d71555a4 100644 --- a/packages/project/test/lib/build/helpers/composeTaskList.js +++ b/packages/project/test/lib/build/helpers/composeTaskList.js @@ -123,6 +123,51 @@ const allTasks = [ "generateBundle", ] ], + [ + "composeTaskList: server=true excludes generateVersionInfo by default", { + archive: false, + selfContained: false, + jsdoc: false, + server: true, + includedTasks: [], + excludedTasks: [] + }, [ + "replaceCopyright", + "replaceVersion", + "replaceBuildtime", + "escapeNonAsciiCharacters", + "minify", + "buildThemes", + "generateLibraryManifest", + "generateFlexChangesBundle", + "generateComponentPreload", + "generateBundle", + "generateLibraryPreload", + ] + ], + [ + "composeTaskList: server=true with includedTasks re-enables generateVersionInfo", { + archive: false, + selfContained: false, + jsdoc: false, + server: true, + includedTasks: ["generateVersionInfo"], + excludedTasks: [] + }, [ + "replaceCopyright", + "replaceVersion", + "replaceBuildtime", + "escapeNonAsciiCharacters", + "minify", + "buildThemes", + "generateLibraryManifest", + "generateVersionInfo", + "generateFlexChangesBundle", + "generateComponentPreload", + "generateBundle", + "generateLibraryPreload", + ] + ], [ "composeTaskList: includedTasks / excludedTasks", { archive: false, diff --git a/packages/server/lib/serve/stack.js b/packages/server/lib/serve/stack.js index 40c3ac09bd7..20bd2b1dfb3 100644 --- a/packages/server/lib/serve/stack.js +++ b/packages/server/lib/serve/stack.js @@ -34,9 +34,8 @@ const log = getLogger("server"); export async function buildRouter(graph, config, error, getDegradedError) { const { sendSAPTargetCSP = false, simpleIndex = false, liveReload = false, serveCSPReports = false, - cache, ui5DataDir, includedTasks, webSocketToken = null, + cache, ui5DataDir, includedTasks, excludedTasks, webSocketToken = null, } = config; - let {excludedTasks} = config; const rootProject = graph.getRoot(); const readers = []; @@ -72,15 +71,10 @@ export async function buildRouter(graph, config, error, getDegradedError) { initialBuildIncludedDependencies.push("sap.ui.core"); } - // Explicitly exclude task "generateVersionInfo" for Server builds - // because middleware "versionInfo" will generate the version info anyways. - if (!Array.isArray(excludedTasks)) { - excludedTasks = []; - } - if (!excludedTasks.includes("generateVersionInfo")) { - excludedTasks = [...excludedTasks, "generateVersionInfo"]; - } - + // graph.serve() runs a server-aligned build (server:true), which disables the + // "generateVersionInfo" task by default because the "versionInfo" middleware generates the + // version info on the fly. The default task set is owned by @ui5/project (composeTaskList), + // so no task filtering is applied here. const buildServer = await graph.serve({ initialBuildIncludedDependencies, includedTasks, diff --git a/packages/server/test/lib/server/serve/stack.js b/packages/server/test/lib/server/serve/stack.js index 480b030a064..f6e7925b74c 100644 --- a/packages/server/test/lib/server/serve/stack.js +++ b/packages/server/test/lib/server/serve/stack.js @@ -109,23 +109,10 @@ test("buildRouter() getDegradedError is undefined for the embedding path (no sup "no degraded accessor is threaded when none was supplied"); }); -test("buildRouter() adds generateVersionInfo to excludedTasks when undefined", async (t) => { - // The versionInfo middleware generates the version info, so the build must skip - // the generateVersionInfo task. Verify it lands in the excludedTasks passed to - // graph.serve() even when the caller excludes nothing. - const buildServer = createBuildServer(); - const graph = createGraph(buildServer); - const applyMiddleware = sinon.stub().resolves(); - - const {buildRouter} = await importBuildRouter(applyMiddleware); - await buildRouter(graph, {excludedTasks: undefined}); - - t.true(graph.serve.calledOnce); - const callArgs = graph.serve.firstCall.args[0]; - t.deepEqual(callArgs.excludedTasks, ["generateVersionInfo"]); -}); - -test("buildRouter() appends generateVersionInfo to existing excludedTasks", async (t) => { +test("buildRouter() passes the caller's excludedTasks through unchanged", async (t) => { + // The versionInfo middleware generates the version info, so the build must skip the + // generateVersionInfo task. That default is now owned by graph.serve() (server:true) in + // @ui5/project, so stack.js no longer edits excludedTasks: verify it forwards them as-is. const buildServer = createBuildServer(); const graph = createGraph(buildServer); const applyMiddleware = sinon.stub().resolves(); @@ -136,23 +123,22 @@ test("buildRouter() appends generateVersionInfo to existing excludedTasks", asyn t.true(graph.serve.calledOnce); const callArgs = graph.serve.firstCall.args[0]; - t.deepEqual(callArgs.excludedTasks, ["anotherTask", "anotherTask2", "generateVersionInfo"]); + t.is(callArgs.excludedTasks, originalExcludedTasks, + "the caller's excludedTasks are forwarded unchanged, without appending generateVersionInfo"); t.deepEqual(originalExcludedTasks, ["anotherTask", "anotherTask2"], "the caller's excludedTasks array is not mutated"); }); -test("buildRouter() keeps generateVersionInfo when already excluded", async (t) => { +test("buildRouter() forwards an undefined excludedTasks without adding generateVersionInfo", async (t) => { const buildServer = createBuildServer(); const graph = createGraph(buildServer); const applyMiddleware = sinon.stub().resolves(); const {buildRouter} = await importBuildRouter(applyMiddleware); - const originalExcludedTasks = ["anotherTask", "generateVersionInfo", "anotherTask2"]; - await buildRouter(graph, {excludedTasks: originalExcludedTasks}); + await buildRouter(graph, {excludedTasks: undefined}); t.true(graph.serve.calledOnce); const callArgs = graph.serve.firstCall.args[0]; - t.deepEqual(callArgs.excludedTasks, ["anotherTask", "generateVersionInfo", "anotherTask2"]); - t.deepEqual(originalExcludedTasks, ["anotherTask", "generateVersionInfo", "anotherTask2"], - "the caller's excludedTasks array is not mutated"); + t.is(callArgs.excludedTasks, undefined, + "stack.js does not synthesize a generateVersionInfo exclusion"); });