From d035a5ce74acdf9820884f925347af5cb1e4743d Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Sun, 14 Feb 2021 19:09:08 +0100 Subject: [PATCH 01/25] [INTERNAL] Enhance tree and build commands with project graph options Depends on https://github.com/SAP/ui5-project/pull/394 --- lib/cli/commands/build.js | 14 +++++- lib/cli/commands/tree.js | 102 +++++++++++++++++++++++++++++++++++--- 2 files changed, 108 insertions(+), 8 deletions(-) diff --git a/lib/cli/commands/build.js b/lib/cli/commands/build.js index 05b2eb14..a8a60218 100644 --- a/lib/cli/commands/build.js +++ b/lib/cli/commands/build.js @@ -107,6 +107,11 @@ build.builder = function(cli) { default: false, type: "boolean" }) + .option("x-graph-mode", { + describe: "Uses an experimental project graph instead of a dependency tree", + default: false, + type: "boolean" + }) .example("ui5 build", "Preload build for project without dependencies") .example("ui5 build self-contained --all", "Self-contained build for project including dependencies") .example("ui5 build --all --exclude-task=* --include-task=createDebugFiles generateAppPreload", @@ -142,7 +147,13 @@ async function handleBuild(argv) { }; } - const tree = await normalizer.generateProjectTree(normalizerOptions); + let tree; + let graph; + if (argv.xGraphMode) { + graph = await normalizer.generateProjectGraph(normalizerOptions); + } else { + tree = await normalizer.generateProjectTree(normalizerOptions); + } const buildSettings = (tree.builder && tree.builder.settings) || {}; const {includedDependencies, excludedDependencies} = buildHelper.createDependencyLists({ @@ -161,6 +172,7 @@ async function handleBuild(argv) { await builder.build({ tree: tree, + graph: graph, destPath: argv.dest, cleanDest: argv["clean-dest"], buildDependencies: buildAll, diff --git a/lib/cli/commands/tree.js b/lib/cli/commands/tree.js index 2f6df1cc..166e6240 100644 --- a/lib/cli/commands/tree.js +++ b/lib/cli/commands/tree.js @@ -28,7 +28,18 @@ tree.builder = function(cli) { describe: "Overrides the framework version defined by the project. Only supported in combination with --full", type: "string" - }).check((argv) => { + }) + .option("x-graph-mode", { + describe: "Uses an experimental project graph instead of a dependency tree", + default: false, + type: "boolean" + }) + .option("x-perf", { + describe: "Outputs performance measurements", + default: false, + type: "boolean" + }) + .check((argv) => { if (argv.frameworkVersion && !argv.full) { throw new Error(`"framework-version" can only be used in combination with option "--full"`); } else { @@ -42,6 +53,7 @@ tree.builder = function(cli) { tree.handler = async function(argv) { const normalizer = require("@ui5/project").normalizer; const treeify = require("treeify"); + const chalk = require("chalk"); const options = { translatorName: argv.translator, @@ -57,15 +69,91 @@ tree.handler = async function(argv) { }; } - let projectTree; - if (argv.full) { - projectTree = await normalizer.generateProjectTree(options); + let startTime; + let elapsedTime; + if (argv.xPerf) { + startTime = process.hrtime(); + } + if (argv.xGraphMode) { + const graph = await normalizer.generateProjectGraph(options); + + if (argv.xPerf) { + elapsedTime = getElapsedTime(startTime); + } + + const projects = {}; + const indentWidth = 4; + await graph.traverseBreadthFirst(async ({project, getDependencies}) => { + const deps = getDependencies().map((dep) => { + return dep.getName(); + }); + projects[project.getName()] = { + render: function(indentation, connectorIndices, lastChild) { + let baseString = " ".repeat(indentation * indentWidth); + connectorIndices.forEach((idx) => { + baseString = `${baseString.slice(0, idx)}│${baseString.slice(idx + 1)}`; + }); + const connectorString = lastChild ? "╰─" : "├─"; + console.log( + `${baseString}${connectorString} ${chalk.bold(project.getName())} ` + + chalk.dim(`(${project.getVersion()}, ${project.getType()}) `) + + chalk.dim.italic(`${project.getPath()}`) + ); + + const lastIdx = deps.length -1; + const newConnectorIndices = [...connectorIndices]; + if (!lastChild) { + newConnectorIndices.push(indentation * indentWidth); + } + deps.forEach((dep, i) => { + projects[dep].render(indentation + 1, newConnectorIndices, i === lastIdx); + }); + } + }; + }); + + const projectKeys = Object.keys(projects); + console.log(chalk.bold.underline(`Dependencies (${projectKeys.length}):`)); + projects[projectKeys[0]].render(0, [], true); + console.log(""); + + const extensions = Object.entries(graph.getAllExtensions()); + console.log(chalk.bold.underline(`Extensions (${extensions.length}):`)); + if (extensions.length) { + extensions.forEach((extension) => { + console.log( + `${" ".repeat(indentWidth)} ├─ ${extension.getName()}` + + chalk.dim(`(${extension.getVersion()}, ${extension.getType()}) `) + + chalk.dim.italic(`${extension.getPath()}`)); + }); + } else { + console.log(chalk.italic(`None`)); + } } else { - projectTree = await normalizer.generateDependencyTree(options); + let projectTree; + if (argv.full) { + projectTree = await normalizer.generateProjectTree(options); + } else { + projectTree = await normalizer.generateDependencyTree(options); + } + if (argv.xPerf) { + elapsedTime = getElapsedTime(startTime); + } + + + const output = argv.json ? JSON.stringify(projectTree, null, 4) : treeify.asTree(projectTree, true); + console.log(output); } - const output = argv.json ? JSON.stringify(projectTree, null, 4) : treeify.asTree(projectTree, true); - console.log(output); + if (argv.xPerf) { + console.log(""); + console.log(chalk.blue(`Normalizer took ${chalk.bold(elapsedTime)}`)); + } }; +function getElapsedTime(startTime) { + const timeDiff = process.hrtime(startTime); + const prettyHrtime = require("pretty-hrtime"); + return prettyHrtime(timeDiff); +} module.exports = tree; From 2964124a3a66f15c4928cf0d64a0a1a931242332 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Mon, 22 Feb 2021 16:23:34 +0100 Subject: [PATCH 02/25] [INTERNAL] tree: Also show project namespace --- lib/cli/commands/tree.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cli/commands/tree.js b/lib/cli/commands/tree.js index 166e6240..e1831876 100644 --- a/lib/cli/commands/tree.js +++ b/lib/cli/commands/tree.js @@ -96,6 +96,7 @@ tree.handler = async function(argv) { const connectorString = lastChild ? "╰─" : "├─"; console.log( `${baseString}${connectorString} ${chalk.bold(project.getName())} ` + + `${project.getNamespace ? chalk.inverse(project.getNamespace()) + " " : ""}` + chalk.dim(`(${project.getVersion()}, ${project.getType()}) `) + chalk.dim.italic(`${project.getPath()}`) ); From 92cb40b4d5ddf02b5af9673415acd0ee9f7635c8 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Sat, 23 Apr 2022 12:03:11 +0200 Subject: [PATCH 03/25] [INTERNAL] build: Adapt to new ProjectGraph API --- lib/cli/commands/base.js | 17 +++++- lib/cli/commands/build.js | 112 +++++++++++++++++--------------------- lib/cli/commands/tree.js | 57 ++++++++++--------- 3 files changed, 97 insertions(+), 89 deletions(-) diff --git a/lib/cli/commands/base.js b/lib/cli/commands/base.js index 23ee1880..fa3c0f16 100644 --- a/lib/cli/commands/base.js +++ b/lib/cli/commands/base.js @@ -3,7 +3,12 @@ const cli = require("yargs"); cli.usage("Usage: ui5 [options]") .demandCommand(1, "Command required! Please have a look at the help documentation above.") .option("config", { - describe: "Path to configuration file", + describe: "Path to project configuration file in YAML format", + type: "string" + }) + .option("dependency-definition", { + describe: "Path to a YAML file containing the project's dependency tree. " + + "This option will disable resolution of node package dependencies.", type: "string" }) .option("translator", { @@ -22,6 +27,16 @@ cli.usage("Usage: ui5 [options]") default: "info", type: "string" }) + .option("x-graph-mode", { + describe: "Uses an experimental project graph instead of a dependency tree", + default: true, + type: "boolean" + }) + .option("x-perf", { + describe: "Outputs performance measurements", + default: false, + type: "boolean" + }) .showHelpOnFail(true) .strict(true) .alias("help", "h") diff --git a/lib/cli/commands/build.js b/lib/cli/commands/build.js index a8a60218..f19b44f8 100644 --- a/lib/cli/commands/build.js +++ b/lib/cli/commands/build.js @@ -12,7 +12,7 @@ const build = { build.builder = function(cli) { return cli - .command("dev", "Dev build: Skips non-essential and time-intensive tasks during build", { + .command("archive", "Archive build: Creates a reusable build archive of the project", { handler: handleBuild, builder: noop, middlewares: [baseMiddleware] @@ -29,7 +29,7 @@ build.builder = function(cli) { }) .command("self-contained", "Build project and create self-contained bundle. " + - "Recommended to be used in conjunction with --all", { + "Recommended to be used in conjunction with --include-dependencies", { handler: handleBuild, builder: noop, middlewares: [baseMiddleware] @@ -40,35 +40,42 @@ build.builder = function(cli) { default: false, type: "boolean" }) + .deprecateOption("all", "Use --include-dependencies") + .option("include-all-dependencies", { + describe: "Include all dependencies in the build result", + alias: ["d", "deps"], + default: false, + type: "boolean" + }) .option("include-dependency", { - describe: "A list of dependencies to be included into the build process. You can use the asterisk '*' as" + - " an alias for including all dependencies into the build process. The listed dependencies cannot be" + + describe: "A list of dependencies to be included in the build result. You can use the asterisk '*' as" + + " an alias for including all dependencies in the build result. The listed dependencies cannot be" + " overruled by dependencies defined in 'exclude-dependency'.", type: "array" }) .option("include-dependency-regexp", { - describe: "A list of regular expressions defining dependencies to be included into the build process." + + describe: "A list of regular expressions defining dependencies to be included in the build result." + " This list is prioritized like 'include-dependency'.", type: "array" }) .option("include-dependency-tree", { - describe: "A list of dependencies to be included into the build process. Transitive dependencies are" + + describe: "A list of dependencies to be included in the build result. Transitive dependencies are" + " implicitly included and do not need to be part of this list. These dependencies overrule" + " the selection of 'exclude-dependency-tree' but can be overruled by 'exclude-dependency'.", type: "array" }) .option("exclude-dependency", { - describe: "A list of dependencies to be excluded from the build process. The listed dependencies can" + + describe: "A list of dependencies to be excluded from the build result. The listed dependencies can" + " be overruled by dependencies defined in 'include-dependency'.", type: "array" }) .option("exclude-dependency-regexp", { - describe: "A list of regular expressions defining dependencies to be excluded from the build process." + + describe: "A list of regular expressions defining dependencies to be excluded from the build result." + " This list is prioritized like 'exclude-dependency'.", type: "array" }) .option("exclude-dependency-tree", { - describe: "A list of dependencies to be excluded from the build process. Transitive dependencies are" + + describe: "A list of dependencies to be excluded from the build result. Transitive dependencies are" + " implicitly included and do not need to be part of this list.", type: "array" }) @@ -82,18 +89,13 @@ build.builder = function(cli) { default: false, type: "boolean" }) - .option("dev-exclude-project", { - describe: - "A list of specific projects to be excluded from dev mode " + - "(dev mode must be active for this to be effective)", - type: "array" - }) .option("include-task", { - describe: "A list of specific tasks to be included to the default/dev set", + describe: "A list of tasks to be added to the default execution set. " + + "This option takes precedence over any excludes.", type: "array" }) .option("exclude-task", { - describe: "A list of specific tasks to be excluded from default/dev set", + describe: "A list of tasks to be excluded from the default task execution set", type: "array" }) .option("framework-version", { @@ -107,12 +109,9 @@ build.builder = function(cli) { default: false, type: "boolean" }) - .option("x-graph-mode", { - describe: "Uses an experimental project graph instead of a dependency tree", - default: false, - type: "boolean" - }) .example("ui5 build", "Preload build for project without dependencies") + + // TODO 3.0: Update examples .example("ui5 build self-contained --all", "Self-contained build for project including dependencies") .example("ui5 build --all --exclude-task=* --include-task=createDebugFiles generateAppPreload", "Build project and dependencies but only apply the createDebugFiles- and generateAppPreload tasks") @@ -125,60 +124,51 @@ build.builder = function(cli) { .example("ui5 build dev", "Build project and dependencies in dev mode. Only a set of essential tasks is executed.") .example("ui5 build --experimental-css-variables", - "Preload build for project without dependencies but with CSS variable artefacts"); + "Preload build for project without dependencies but with CSS variable artifacts"); }; async function handleBuild(argv) { - const normalizer = require("@ui5/project").normalizer; - const builder = require("@ui5/builder").builder; const logger = require("@ui5/logger"); + const {generateProjectGraph, builder} = require("@ui5/project"); const command = argv._[argv._.length - 1]; logger.setShowProgress(true); - const normalizerOptions = { - translatorName: argv.translator, - configPath: argv.config - }; - - if (argv.frameworkVersion) { - normalizerOptions.frameworkOptions = { - versionOverride: argv.frameworkVersion - }; + if (argv.all) { + logger.warn( + "CLI option --all is deprecated. Dependencies are now built automatically in case they are required"); } - let tree; let graph; - if (argv.xGraphMode) { - graph = await normalizer.generateProjectGraph(normalizerOptions); + if (argv.dependencyDefinition) { + graph = await generateProjectGraph.usingStaticFile({ + filePath: argv.dependencyDefinition, + versionOverride: argv.frameworkVersion + }); } else { - tree = await normalizer.generateProjectTree(normalizerOptions); + graph = await generateProjectGraph.usingNodePackageDependencies({ + rootConfigPath: argv.config, + versionOverride: argv.frameworkVersion + }); } - const buildSettings = (tree.builder && tree.builder.settings) || {}; - - const {includedDependencies, excludedDependencies} = buildHelper.createDependencyLists({ - tree: tree, - includeDependency: argv["include-dependency"], - includeDependencyRegExp: argv["include-dependency-regexp"], - includeDependencyTree: argv["include-dependency-tree"], - excludeDependency: argv["exclude-dependency"], - excludeDependencyRegExp: argv["exclude-dependency-regexp"], - excludeDependencyTree: argv["exclude-dependency-tree"], - defaultIncludeDependency: buildSettings.includeDependency, - defaultIncludeDependencyRegExp: buildSettings.includeDependencyRegExp, - defaultIncludeDependencyTree: buildSettings.includeDependencyTree - }); - const buildAll = buildHelper.alignWithBuilderApi(argv.all, includedDependencies, excludedDependencies); - - await builder.build({ - tree: tree, - graph: graph, + const buildSettings = graph.getRoot().getBuilderSettings() || {}; + await builder({ + graph, destPath: argv.dest, cleanDest: argv["clean-dest"], - buildDependencies: buildAll, - includedDependencies: includedDependencies, - excludedDependencies: excludedDependencies, - dev: command === "dev", + composeProjectList: { + includeAllDependencies: argv["include-all-dependencies"], + includeDependency: argv["include-dependency"], + includeDependencyRegExp: argv["include-dependency-regexp"], + includeDependencyTree: argv["include-dependency-tree"], + excludeDependency: argv["exclude-dependency"], + excludeDependencyRegExp: argv["exclude-dependency-regexp"], + excludeDependencyTree: argv["exclude-dependency-tree"], + defaultIncludeDependency: buildSettings.includeDependency, + defaultIncludeDependencyRegExp: buildSettings.includeDependencyRegExp, + defaultIncludeDependencyTree: buildSettings.includeDependencyTree + }, + archive: command === "archive", selfContained: command === "self-contained", jsdoc: command === "jsdoc", devExcludeProject: argv["dev-exclude-project"], diff --git a/lib/cli/commands/tree.js b/lib/cli/commands/tree.js index e1831876..53e3ef6b 100644 --- a/lib/cli/commands/tree.js +++ b/lib/cli/commands/tree.js @@ -29,16 +29,6 @@ tree.builder = function(cli) { "Overrides the framework version defined by the project. Only supported in combination with --full", type: "string" }) - .option("x-graph-mode", { - describe: "Uses an experimental project graph instead of a dependency tree", - default: false, - type: "boolean" - }) - .option("x-perf", { - describe: "Outputs performance measurements", - default: false, - type: "boolean" - }) .check((argv) => { if (argv.frameworkVersion && !argv.full) { throw new Error(`"framework-version" can only be used in combination with option "--full"`); @@ -55,27 +45,25 @@ tree.handler = async function(argv) { const treeify = require("treeify"); const chalk = require("chalk"); - const options = { - translatorName: argv.translator, - translatorOptions: { - includeDeduped: !argv.dedupe - }, - configPath: argv.config - }; - - if (argv.frameworkVersion ) { - options.frameworkOptions = { - versionOverride: argv.frameworkVersion - }; - } - let startTime; let elapsedTime; if (argv.xPerf) { startTime = process.hrtime(); } if (argv.xGraphMode) { - const graph = await normalizer.generateProjectGraph(options); + const generateProjectGraph = require("@ui5/project").generateProjectGraph; + let graph; + if (argv.dependencyDefinition) { + graph = await generateProjectGraph.usingStaticFile({ + filePath: argv.dependencyDefinition, + versionOverride: argv.frameworkVersion + }); + } else { + graph = await generateProjectGraph.usingNodePackageDependencies({ + rootConfigPath: argv.config, + versionOverride: argv.frameworkVersion + }); + } if (argv.xPerf) { elapsedTime = getElapsedTime(startTime); @@ -118,7 +106,7 @@ tree.handler = async function(argv) { projects[projectKeys[0]].render(0, [], true); console.log(""); - const extensions = Object.entries(graph.getAllExtensions()); + const extensions = graph.getAllExtensions(); console.log(chalk.bold.underline(`Extensions (${extensions.length}):`)); if (extensions.length) { extensions.forEach((extension) => { @@ -131,6 +119,20 @@ tree.handler = async function(argv) { console.log(chalk.italic(`None`)); } } else { + const options = { + translatorName: argv.translator, + translatorOptions: { + includeDeduped: !argv.dedupe + }, + configPath: argv.config + }; + + if (argv.frameworkVersion ) { + options.frameworkOptions = { + versionOverride: argv.frameworkVersion + }; + } + let projectTree; if (argv.full) { projectTree = await normalizer.generateProjectTree(options); @@ -148,7 +150,8 @@ tree.handler = async function(argv) { if (argv.xPerf) { console.log(""); - console.log(chalk.blue(`Normalizer took ${chalk.bold(elapsedTime)}`)); + console.log(chalk.blue( + `Dependency ${argv.xGraphMode ? "graph" : "tree"} generation took ${chalk.bold(elapsedTime)}`)); } }; From ee8aa597d91a59a483882f36160b014080c5c5e3 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 3 May 2022 15:16:46 +0200 Subject: [PATCH 04/25] [INTERNAL] Fix build-all-dependencies CLI options --- lib/cli/commands/build.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/cli/commands/build.js b/lib/cli/commands/build.js index f19b44f8..304c0301 100644 --- a/lib/cli/commands/build.js +++ b/lib/cli/commands/build.js @@ -1,7 +1,6 @@ // Build const baseMiddleware = require("../middlewares/base.js"); -const buildHelper = require("../../utils/buildHelper"); const build = { command: "build", @@ -40,7 +39,7 @@ build.builder = function(cli) { default: false, type: "boolean" }) - .deprecateOption("all", "Use --include-dependencies") + .deprecateOption("all", "Use --include-all-dependencies") .option("include-all-dependencies", { describe: "Include all dependencies in the build result", alias: ["d", "deps"], @@ -135,8 +134,9 @@ async function handleBuild(argv) { logger.setShowProgress(true); if (argv.all) { - logger.warn( - "CLI option --all is deprecated. Dependencies are now built automatically in case they are required"); + console.warn( + "Warning: CLI option --all is deprecated. " + + "Dependencies are now built automatically in case they are required"); } let graph; @@ -156,7 +156,7 @@ async function handleBuild(argv) { graph, destPath: argv.dest, cleanDest: argv["clean-dest"], - composeProjectList: { + complexDependencyIncludes: { includeAllDependencies: argv["include-all-dependencies"], includeDependency: argv["include-dependency"], includeDependencyRegExp: argv["include-dependency-regexp"], From 03ef5b0cad12be8208906906a03a38d4ced94e50 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 18 May 2022 18:33:51 +0200 Subject: [PATCH 05/25] [INTERNAL] build: Remove obsolete parameter 'devExcludeProject' --- lib/cli/commands/build.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/cli/commands/build.js b/lib/cli/commands/build.js index 304c0301..fb29c129 100644 --- a/lib/cli/commands/build.js +++ b/lib/cli/commands/build.js @@ -171,7 +171,6 @@ async function handleBuild(argv) { archive: command === "archive", selfContained: command === "self-contained", jsdoc: command === "jsdoc", - devExcludeProject: argv["dev-exclude-project"], includedTasks: argv["include-task"], excludedTasks: argv["exclude-task"], cssVariables: argv["experimental-css-variables"] From 21dea1d05ba06ec3ced760c8fee53d582fe5085c Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Fri, 20 May 2022 13:05:08 +0200 Subject: [PATCH 06/25] [INTERNAL] build: Alias old --all flag to new --include-all-dependencies The user intend seems the same: To build all dependencies and have them as part of the build result --- lib/cli/commands/build.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/lib/cli/commands/build.js b/lib/cli/commands/build.js index fb29c129..d5c1d842 100644 --- a/lib/cli/commands/build.js +++ b/lib/cli/commands/build.js @@ -33,16 +33,9 @@ build.builder = function(cli) { builder: noop, middlewares: [baseMiddleware] }) - .option("all", { - describe: "Include all project dependencies into build process", - alias: "a", - default: false, - type: "boolean" - }) - .deprecateOption("all", "Use --include-all-dependencies") .option("include-all-dependencies", { describe: "Include all dependencies in the build result", - alias: ["d", "deps"], + alias: ["all", "a"], default: false, type: "boolean" }) @@ -133,12 +126,6 @@ async function handleBuild(argv) { const command = argv._[argv._.length - 1]; logger.setShowProgress(true); - if (argv.all) { - console.warn( - "Warning: CLI option --all is deprecated. " + - "Dependencies are now built automatically in case they are required"); - } - let graph; if (argv.dependencyDefinition) { graph = await generateProjectGraph.usingStaticFile({ From 5ca3f20f4887aefb8995a1f9ee4150881af7e10f Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Fri, 20 May 2022 14:13:34 +0200 Subject: [PATCH 07/25] [INTERNAL] serve: Adapt to new ProjectGraph API --- lib/cli/commands/serve.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/cli/commands/serve.js b/lib/cli/commands/serve.js index 2862b93c..0cd5f597 100644 --- a/lib/cli/commands/serve.js +++ b/lib/cli/commands/serve.js @@ -73,30 +73,32 @@ serve.builder = function(cli) { }; serve.handler = async function(argv) { - const normalizer = require("@ui5/project").normalizer; + const generateProjectGraph = require("@ui5/project").generateProjectGraph; const ui5Server = require("@ui5/server"); const server = ui5Server.server; - const normalizerOptions = { - translatorName: argv.translator, - configPath: argv.config - }; - - if (argv.frameworkVersion) { - normalizerOptions.frameworkOptions = { + let graph; + if (argv.dependencyDefinition) { + graph = await generateProjectGraph.usingStaticFile({ + filePath: argv.dependencyDefinition, + versionOverride: argv.frameworkVersion + }); + } else { + graph = await generateProjectGraph.usingNodePackageDependencies({ + rootConfigPath: argv.config, versionOverride: argv.frameworkVersion - }; + }); } - const tree = await normalizer.generateProjectTree(normalizerOptions); let port = argv.port; let changePortIfInUse = false; - if (!port && tree.server && tree.server.settings) { + if (!port && graph.getRoot().getServerSettings()) { + const serverSettings = graph.getRoot().getServerSettings(); if (argv.h2) { - port = tree.server.settings.httpsPort; + port = serverSettings.httpsPort; } else { - port = tree.server.settings.httpPort; + port = serverSettings.httpPort; } } @@ -127,7 +129,7 @@ serve.handler = async function(argv) { serverConfig.cert = cert; } - const {h2, port: actualPort} = await server.serve(tree, serverConfig); + const {h2, port: actualPort} = await server.serve(graph, serverConfig); const protocol = h2 ? "https" : "http"; let browserUrl = protocol + "://localhost:" + actualPort; From abc2a91eee4675e035c74d063b40a8036e758366 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Mon, 23 May 2022 11:14:04 +0200 Subject: [PATCH 08/25] [INTERNAL] tree: Remove legacy dependency tree implementation --- lib/cli/commands/base.js | 5 -- lib/cli/commands/tree.js | 179 +++++++++++++-------------------------- 2 files changed, 61 insertions(+), 123 deletions(-) diff --git a/lib/cli/commands/base.js b/lib/cli/commands/base.js index fa3c0f16..3a3bee92 100644 --- a/lib/cli/commands/base.js +++ b/lib/cli/commands/base.js @@ -27,11 +27,6 @@ cli.usage("Usage: ui5 [options]") default: "info", type: "string" }) - .option("x-graph-mode", { - describe: "Uses an experimental project graph instead of a dependency tree", - default: true, - type: "boolean" - }) .option("x-perf", { describe: "Outputs performance measurements", default: false, diff --git a/lib/cli/commands/tree.js b/lib/cli/commands/tree.js index 53e3ef6b..c5c7f15d 100644 --- a/lib/cli/commands/tree.js +++ b/lib/cli/commands/tree.js @@ -9,40 +9,14 @@ const tree = { tree.builder = function(cli) { return cli - .option("full", { - describe: "Include more information (currently the project configuration)", - default: false, - type: "boolean" - }) - .option("json", { - describe: "Output tree as formatted JSON string", - default: false, - type: "boolean" - }) - .option("dedupe", { - describe: "Remove duplicate projects from project tree", - default: false, - type: "boolean" - }) .option("framework-version", { describe: - "Overrides the framework version defined by the project. Only supported in combination with --full", + "Overrides the framework version defined by the project", type: "string" - }) - .check((argv) => { - if (argv.frameworkVersion && !argv.full) { - throw new Error(`"framework-version" can only be used in combination with option "--full"`); - } else { - return true; - } - }) - .example("ui5 tree > tree.txt", "Pipes the dependency tree into a new file \"tree.txt\"") - .example("ui5 tree --json > tree.json", "Pipes the dependency tree into a new file \"tree.json\""); + }); }; tree.handler = async function(argv) { - const normalizer = require("@ui5/project").normalizer; - const treeify = require("treeify"); const chalk = require("chalk"); let startTime; @@ -50,104 +24,73 @@ tree.handler = async function(argv) { if (argv.xPerf) { startTime = process.hrtime(); } - if (argv.xGraphMode) { - const generateProjectGraph = require("@ui5/project").generateProjectGraph; - let graph; - if (argv.dependencyDefinition) { - graph = await generateProjectGraph.usingStaticFile({ - filePath: argv.dependencyDefinition, - versionOverride: argv.frameworkVersion - }); - } else { - graph = await generateProjectGraph.usingNodePackageDependencies({ - rootConfigPath: argv.config, - versionOverride: argv.frameworkVersion - }); - } - - if (argv.xPerf) { - elapsedTime = getElapsedTime(startTime); - } - - const projects = {}; - const indentWidth = 4; - await graph.traverseBreadthFirst(async ({project, getDependencies}) => { - const deps = getDependencies().map((dep) => { - return dep.getName(); - }); - projects[project.getName()] = { - render: function(indentation, connectorIndices, lastChild) { - let baseString = " ".repeat(indentation * indentWidth); - connectorIndices.forEach((idx) => { - baseString = `${baseString.slice(0, idx)}│${baseString.slice(idx + 1)}`; - }); - const connectorString = lastChild ? "╰─" : "├─"; - console.log( - `${baseString}${connectorString} ${chalk.bold(project.getName())} ` + - `${project.getNamespace ? chalk.inverse(project.getNamespace()) + " " : ""}` + - chalk.dim(`(${project.getVersion()}, ${project.getType()}) `) + - chalk.dim.italic(`${project.getPath()}`) - ); - - const lastIdx = deps.length -1; - const newConnectorIndices = [...connectorIndices]; - if (!lastChild) { - newConnectorIndices.push(indentation * indentWidth); - } - deps.forEach((dep, i) => { - projects[dep].render(indentation + 1, newConnectorIndices, i === lastIdx); - }); - } - }; + const generateProjectGraph = require("@ui5/project").generateProjectGraph; + let graph; + if (argv.dependencyDefinition) { + graph = await generateProjectGraph.usingStaticFile({ + filePath: argv.dependencyDefinition, + versionOverride: argv.frameworkVersion }); + } else { + graph = await generateProjectGraph.usingNodePackageDependencies({ + rootConfigPath: argv.config, + versionOverride: argv.frameworkVersion + }); + } - const projectKeys = Object.keys(projects); - console.log(chalk.bold.underline(`Dependencies (${projectKeys.length}):`)); - projects[projectKeys[0]].render(0, [], true); - console.log(""); + if (argv.xPerf) { + elapsedTime = getElapsedTime(startTime); + } - const extensions = graph.getAllExtensions(); - console.log(chalk.bold.underline(`Extensions (${extensions.length}):`)); - if (extensions.length) { - extensions.forEach((extension) => { + const projects = {}; + const indentWidth = 4; + await graph.traverseBreadthFirst(async ({project, getDependencies}) => { + const deps = getDependencies().map((dep) => { + return dep.getName(); + }); + projects[project.getName()] = { + render: function(indentation, connectorIndices, lastChild) { + let baseString = " ".repeat(indentation * indentWidth); + connectorIndices.forEach((idx) => { + baseString = `${baseString.slice(0, idx)}│${baseString.slice(idx + 1)}`; + }); + const connectorString = lastChild ? "╰─" : "├─"; console.log( - `${" ".repeat(indentWidth)} ├─ ${extension.getName()}` + - chalk.dim(`(${extension.getVersion()}, ${extension.getType()}) `) + - chalk.dim.italic(`${extension.getPath()}`)); - }); - } else { - console.log(chalk.italic(`None`)); - } - } else { - const options = { - translatorName: argv.translator, - translatorOptions: { - includeDeduped: !argv.dedupe - }, - configPath: argv.config - }; - - if (argv.frameworkVersion ) { - options.frameworkOptions = { - versionOverride: argv.frameworkVersion - }; - } + `${baseString}${connectorString} ${chalk.bold(project.getName())} ` + + `${project.getNamespace ? chalk.inverse(project.getNamespace()) + " " : ""}` + + chalk.dim(`(${project.getVersion()}, ${project.getType()}) `) + + chalk.dim.italic(`${project.getPath()}`) + ); - let projectTree; - if (argv.full) { - projectTree = await normalizer.generateProjectTree(options); - } else { - projectTree = await normalizer.generateDependencyTree(options); - } - if (argv.xPerf) { - elapsedTime = getElapsedTime(startTime); - } + const lastIdx = deps.length -1; + const newConnectorIndices = [...connectorIndices]; + if (!lastChild) { + newConnectorIndices.push(indentation * indentWidth); + } + deps.forEach((dep, i) => { + projects[dep].render(indentation + 1, newConnectorIndices, i === lastIdx); + }); + } + }; + }); + const projectKeys = Object.keys(projects); + console.log(chalk.bold.underline(`Dependencies (${projectKeys.length}):`)); + projects[projectKeys[0]].render(0, [], true); + console.log(""); - const output = argv.json ? JSON.stringify(projectTree, null, 4) : treeify.asTree(projectTree, true); - console.log(output); + const extensions = graph.getAllExtensions(); + console.log(chalk.bold.underline(`Extensions (${extensions.length}):`)); + if (extensions.length) { + extensions.forEach((extension) => { + console.log( + `${" ".repeat(indentWidth)} ├─ ${extension.getName()}` + + chalk.dim(`(${extension.getVersion()}, ${extension.getType()}) `) + + chalk.dim.italic(`${extension.getPath()}`)); + }); + } else { + console.log(chalk.italic(`None`)); } - if (argv.xPerf) { console.log(""); console.log(chalk.blue( From 5ae2a5cd731724e659fc941f9836a5fd1327c093 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 24 May 2022 10:34:21 +0200 Subject: [PATCH 09/25] [INTERNAL] Remove buildHelper: Module moved to ui5-project Now named 'composeProjectList' --- lib/utils/buildHelper.js | 216 ---------------------------- test/lib/utils/buildHelper.js | 263 ---------------------------------- 2 files changed, 479 deletions(-) delete mode 100644 lib/utils/buildHelper.js delete mode 100644 test/lib/utils/buildHelper.js diff --git a/lib/utils/buildHelper.js b/lib/utils/buildHelper.js deleted file mode 100644 index 546e2e01..00000000 --- a/lib/utils/buildHelper.js +++ /dev/null @@ -1,216 +0,0 @@ -const log = require("@ui5/logger").getLogger("cli:utils:buildHelper"); - -/** - * Creates an object containing the flattened project dependency tree. Each dependency is defined as an object key while - * its value is an array of all of its transitive dependencies. - * - * @param {object} tree Project tree as generated by the [@ui5/project.normalizer]{@link module:@ui5/project.normalizer} - * @returns {object} An object with dependency names as key and each with an array of its transitive - * dependencies as value - */ -function getFlattenedDependencyTree(tree) { - const dependencyInfo = {}; - - function _getTransitiveDependencies(project, dependencies) { - project.dependencies.forEach((dep) => { - if (!dependencies.includes(dep.metadata.name)) { - dependencies.push(dep.metadata.name); - _getTransitiveDependencies(dep, dependencies); - } - }); - return dependencies; - } - function _processDependencies(project) { - project.dependencies.forEach((dep) => { - if (!dependencyInfo[dep.metadata.name]) { - dependencyInfo[dep.metadata.name] = _getTransitiveDependencies(dep, []); - _processDependencies(dep); - } - }); - } - - _processDependencies(tree); - return dependencyInfo; -} - -/** - * Creates dependency lists for 'includedDependencies' and 'excludedDependencies'. Regular expressions are directly - * applied to a list of all project dependencies so that they don't need to be evaluated in later processing steps. - * Generally, includes are handled with a higher priority than excludes. Additionally, operations for processing - * transitive dependencies are handled with a lower priority than explicitly mentioned dependencies. The default - * dependencies set in the build settings are appended in the end. - * - * The priority of the various dependency lists is applied in the following order, but note that a later list can't - * overrule earlier ones: - *
    - *
  1. includeDependency, includeDependencyRegExp
  2. - *
  3. excludeDependency, excludeDependencyRegExp
  4. - *
  5. includeDependencyTree
  6. - *
  7. excludeDependencyTree
  8. - *
  9. defaultIncludeDependency, defaultIncludeDependencyRegExp, defaultIncludeDependencyTree
  10. - *
- * - * @param {object} parameters Parameters - * @param {object} parameters.tree Project tree as generated by the - * [@ui5/project.normalizer]{@link module:@ui5/project.normalizer} - * @param {string[]} parameters.includeDependency The dependencies to be considered in 'includedDependencies'; the - * "*" character can be used as wildcard for all dependencies and is an alias for the CLI option "--all" - * @param {string[]} parameters.includeDependencyRegExp Strings which are interpreted as regular expressions - * to describe the selection of dependencies to be considered in 'includedDependencies' - * @param {string[]} parameters.includeDependencyTree The dependencies to be considered in 'includedDependencies'; - * transitive dependencies are also appended - * @param {string[]} parameters.excludeDependency The dependencies to be considered in 'excludedDependencies' - * @param {string[]} parameters.excludeDependencyRegExp Strings which are interpreted as regular expressions - * to describe the selection of dependencies to be considered in 'excludedDependencies' - * @param {string[]} parameters.excludeDependencyTree The dependencies to be considered in 'excludedDependencies'; - * transitive dependencies are also appended - * @param {string[]} parameters.defaultIncludeDependency Same as 'includeDependency' parameter; used for build - * settings - * @param {string[]} parameters.defaultIncludeDependencyRegExp Same as 'includeDependencyRegExp' parameter; used - * for build settings - * @param {string[]} parameters.defaultIncludeDependencyTree Same as 'includeDependencyTree' parameter; used for - * build settings - * @returns {{includedDependencies:string[],excludedDependencies:string[]}} An object containing the - * 'includedDependencies' and 'excludedDependencies' - */ -function createDependencyLists({ - tree, - includeDependency = [], includeDependencyRegExp = [], includeDependencyTree = [], - excludeDependency = [], excludeDependencyRegExp = [], excludeDependencyTree = [], - defaultIncludeDependency = [], defaultIncludeDependencyRegExp = [], defaultIncludeDependencyTree = [] -}) { - if ( - !includeDependency.length && !includeDependencyRegExp.length && !includeDependencyTree.length && - !excludeDependency.length && !excludeDependencyRegExp.length && !excludeDependencyTree.length && - !defaultIncludeDependency.length && !defaultIncludeDependencyRegExp.length && - !defaultIncludeDependencyTree.length - ) { - return {includedDependencies: [], excludedDependencies: []}; - } - - const flattenedDependencyTree = getFlattenedDependencyTree(tree); - - function isExcluded(excludeList, depName) { - return excludeList && excludeList.has(depName); - } - function processDependencies({targetList, dependencies, dependenciesRegExp = [], excludeList, handleSubtree}) { - if (handleSubtree && dependenciesRegExp.length) { - throw new Error("dependenciesRegExp can't be combined with handleSubtree:true option"); - } - dependencies.forEach((depName) => { - if (depName === "*") { - targetList.add(depName); - } else if (flattenedDependencyTree[depName]) { - if (!isExcluded(excludeList, depName)) { - targetList.add(depName); - } - if (handleSubtree) { - flattenedDependencyTree[depName].forEach((dep) => { - if (!isExcluded(excludeList, dep)) { - targetList.add(dep); - } - }); - } - } else { - log.warn( - `Could not find dependency "${depName}" for project ${tree.metadata.name}. Dependency filter is ` + - `ignored`); - } - }); - dependenciesRegExp.map((exp) => new RegExp(exp)).forEach((regExp) => { - for (const depName in flattenedDependencyTree) { - if (regExp.test(depName) && !isExcluded(excludeList, depName)) { - targetList.add(depName); - } - } - }); - } - - const includedDependencies = new Set(); - const excludedDependencies = new Set(); - - // add dependencies defined in includeDependency and includeDependencyRegExp to the list of includedDependencies - processDependencies({ - targetList: includedDependencies, - dependencies: includeDependency, - dependenciesRegExp: includeDependencyRegExp - }); - // add dependencies defined in excludeDependency and excludeDependencyRegExp to the list of excludedDependencies - processDependencies({ - targetList: excludedDependencies, - dependencies: excludeDependency, - dependenciesRegExp: excludeDependencyRegExp - }); - // add dependencies defined in includeDependencyTree with their transitive dependencies to the list of - // includedDependencies; due to prioritization only those dependencies are added which are not excluded - // by excludedDependencies - processDependencies({ - targetList: includedDependencies, - dependencies: includeDependencyTree, - excludeList: excludedDependencies, - handleSubtree: true - }); - // add dependencies defined in excludeDependencyTree with their transitive dependencies to the list of - // excludedDependencies; due to prioritization only those dependencies are added which are not excluded - // by includedDependencies - processDependencies({ - targetList: excludedDependencies, - dependencies: excludeDependencyTree, - excludeList: includedDependencies, - handleSubtree: true - }); - // due to the lowest priority only add the dependencies defined in build settings if they are not excluded - // by any other dependency defined in excludedDependencies - processDependencies({ - targetList: includedDependencies, - dependencies: defaultIncludeDependency, - dependenciesRegExp: defaultIncludeDependencyRegExp, - excludeList: excludedDependencies - }); - processDependencies({ - targetList: includedDependencies, - dependencies: defaultIncludeDependencyTree, - excludeList: excludedDependencies, - handleSubtree: true - }); - - return { - includedDependencies: Array.from(includedDependencies), - excludedDependencies: Array.from(excludedDependencies) - }; -} - -/** - * Returns whether project dependencies have to be built influenced by includedDependencies and - * excludedDependencies. - * If only selected dependencies (via includedDependencies) have to be built, the "*" character - * is added to the excludedDependencies to make sure that all other dependencies are - * excluded. - * In case a "*" character is included in includedDependencies, it is removed and the - * buildAll flag is set to true as it behaves as an alias. - * - * @param {boolean} buildAll The value of the all command line parameter to decide if project - * dependencies have to be built - * @param {string[]} includedDependencies The list of included dependencies - * @param {string[]} excludedDependencies The list of excluded dependencies - * @returns {boolean} Whether it is required to build project dependencies - */ -function alignWithBuilderApi(buildAll, includedDependencies, excludedDependencies) { - if ((!buildAll && !includedDependencies.includes("*")) && includedDependencies.length) { - excludedDependencies.push("*"); - } - if (includedDependencies.includes("*")) { - buildAll = true; - includedDependencies.splice(includedDependencies.indexOf("*"), 1); - } - if (!buildAll && includedDependencies.length) { - buildAll = true; - } - return buildAll; -} - -module.exports = { - getFlattenedDependencyTree, - createDependencyLists, - alignWithBuilderApi -}; diff --git a/test/lib/utils/buildHelper.js b/test/lib/utils/buildHelper.js deleted file mode 100644 index b7308925..00000000 --- a/test/lib/utils/buildHelper.js +++ /dev/null @@ -1,263 +0,0 @@ -const test = require("ava"); -const sinon = require("sinon"); -const mock = require("mock-require"); -const logger = require("@ui5/logger"); - -test.beforeEach((t) => { - t.context.log = { - warn: sinon.stub() - }; - sinon.stub(logger, "getLogger").withArgs("cli:utils:buildHelper").returns(t.context.log); - t.context.buildHelper = mock.reRequire("../../../lib/utils/buildHelper"); -}); - -test.afterEach.always((t) => { - sinon.restore(); - mock.stopAll(); -}); - -test.serial("getFlattenedDependencyTree", (t) => { - const {getFlattenedDependencyTree} = t.context.buildHelper; - const tree = { - metadata: { - name: "Sample" - }, - dependencies: [{ - metadata: { - name: "a0" - }, - dependencies: [{ - metadata: { - name: "b" - }, - dependencies: [{ - metadata: { - name: "c" - }, - dependencies: [{ - metadata: { - name: "d0" - }, - dependencies: [] - }, { - metadata: { - name: "d1" - }, - dependencies: [] - }] - }] - }] - }, { - metadata: { - name: "a1" - }, - dependencies: [{ - metadata: { - name: "c" - }, - dependencies: [{ - metadata: { - name: "d0" - }, - dependencies: [] - }, { - metadata: { - name: "d1" - }, - dependencies: [] - }] - }] - }] - }; - - t.deepEqual(getFlattenedDependencyTree(tree), { - a0: ["b", "c", "d0", "d1"], - a1: ["c", "d0", "d1"], - b: ["c", "d0", "d1"], - c: ["d0", "d1"], - d0: [], - d1: [] - }); -}); - -function assertCreateDependencyLists(t, { - includeDependency, includeDependencyRegExp, includeDependencyTree, - excludeDependency, excludeDependencyRegExp, excludeDependencyTree, - defaultIncludeDependency, defaultIncludeDependencyRegExp, defaultIncludeDependencyTree, - expectedIncludedDependencies, expectedExcludedDependencies -}) { - const {createDependencyLists} = t.context.buildHelper; - const tree = { - metadata: { - name: "Sample" - }, - dependencies: [{ - metadata: { - name: "a0" - }, - dependencies: [{ - metadata: { - name: "b0" - }, - dependencies: [] - }, { - metadata: { - name: "b1" - }, - dependencies: [{ - metadata: { - name: "c" - }, - dependencies: [] - }] - }] - }, { - metadata: { - name: "a1" - }, - dependencies: [{ - metadata: { - name: "b0" - }, - dependencies: [] - }, { - metadata: { - name: "b1" - }, - dependencies: [{ - metadata: { - name: "c" - }, - dependencies: [] - }] - }, { - metadata: { - name: "b2" - }, - dependencies: [] - }] - }, { - metadata: { - name: "a2" - }, - dependencies: [{ - metadata: { - name: "b3" - }, - dependencies: [] - }] - }] - }; - - const {includedDependencies, excludedDependencies} = createDependencyLists({ - tree: tree, - includeDependency: includeDependency, - includeDependencyRegExp: includeDependencyRegExp, - includeDependencyTree: includeDependencyTree, - excludeDependency: excludeDependency, - excludeDependencyRegExp: excludeDependencyRegExp, - excludeDependencyTree: excludeDependencyTree, - defaultIncludeDependency: defaultIncludeDependency, - defaultIncludeDependencyRegExp: defaultIncludeDependencyRegExp, - defaultIncludeDependencyTree: defaultIncludeDependencyTree - }); - t.deepEqual(includedDependencies, expectedIncludedDependencies); - t.deepEqual(excludedDependencies, expectedExcludedDependencies); -} - -test.serial("createDependencyLists: only includes", (t) => { - assertCreateDependencyLists(t, { - includeDependency: ["a1", "b2"], - includeDependencyRegExp: ["^b0$"], - includeDependencyTree: ["a2"], - expectedIncludedDependencies: ["a1", "b2", "b0", "a2", "b3"], - expectedExcludedDependencies: [] - }); -}); - -test.serial("createDependencyLists: only excludes", (t) => { - assertCreateDependencyLists(t, { - excludeDependency: ["a1", "b2"], - excludeDependencyRegExp: ["^b0$"], - excludeDependencyTree: ["a2"], - expectedIncludedDependencies: [], - expectedExcludedDependencies: ["a1", "b2", "b0", "a2", "b3"] - }); -}); - -test.serial("createDependencyLists: includeDependencyTree has lower priority than excludes", (t) => { - assertCreateDependencyLists(t, { - includeDependencyTree: ["a1"], - excludeDependency: ["a1"], - excludeDependencyRegExp: ["^b[0-2]$"], - expectedIncludedDependencies: ["c"], - expectedExcludedDependencies: ["a1", "b0", "b1", "b2"] - }); -}); - -test.serial("createDependencyLists: excludeDependencyTree has lower priority than includes", (t) => { - assertCreateDependencyLists(t, { - includeDependency: ["a1"], - includeDependencyRegExp: ["^b[0-2]$"], - excludeDependencyTree: ["a1"], - expectedIncludedDependencies: ["a1", "b0", "b1", "b2"], - expectedExcludedDependencies: ["c"] - }); -}); - -test.serial("createDependencyLists: includeDependencyTree has higher priority than excludeDependencyTree", (t) => { - assertCreateDependencyLists(t, { - includeDependencyTree: ["a1"], - excludeDependencyTree: ["a1"], - expectedIncludedDependencies: ["a1", "b0", "b1", "c", "b2"], - expectedExcludedDependencies: [] - }); -}); - -test.serial("createDependencyLists: defaultIncludeDependency/RegExp has lower priority than excludes", (t) => { - assertCreateDependencyLists(t, { - defaultIncludeDependency: ["a1", "b2", "c"], - defaultIncludeDependencyRegExp: ["^b0$"], - excludeDependency: ["a1"], - excludeDependencyRegExp: ["^b.$"], - expectedIncludedDependencies: ["c"], - expectedExcludedDependencies: ["a1", "b0", "b1", "b2", "b3"] - }); -}); - -test.serial("createDependencyLists: defaultIncludeDependencyTree has lower priority than excludes", (t) => { - assertCreateDependencyLists(t, { - defaultIncludeDependencyTree: ["a1"], - excludeDependencyTree: ["b1"], - expectedIncludedDependencies: ["a1", "b0", "b2"], - expectedExcludedDependencies: ["b1", "c"] - }); -}); - -test.serial("alignWithBuilderApi: * is added to excludedDependencies", (t) => { - const {alignWithBuilderApi} = t.context.buildHelper; - const args = { - includedDependencies: ["a"], - excludedDependencies: [] - }; - t.is(alignWithBuilderApi(false, args.includedDependencies, args.excludedDependencies), true, - "Should return true if includedDependencies are given"); - t.deepEqual(args, { - includedDependencies: ["a"], - excludedDependencies: ["*"] - }); -}); - -test.serial("alignWithBuilderApi: includedDependencies=* is is an alias for buildAll", (t) => { - const {alignWithBuilderApi} = t.context.buildHelper; - const args = { - includedDependencies: ["*"], - excludedDependencies: [] - }; - t.is(alignWithBuilderApi(false, args.includedDependencies, args.excludedDependencies), true, - "Should return true if includedDependencies are given"); - t.deepEqual(args, { - includedDependencies: [], - excludedDependencies: [] - }); -}); From 8edd1374f986bec7c2c3ac0cf89e58971d10ca2e Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 24 May 2022 10:36:15 +0200 Subject: [PATCH 10/25] [INTERNAL] Remove obsolete 'translator' option --- lib/cli/commands/base.js | 6 ------ lib/cli/commands/serve.js | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/cli/commands/base.js b/lib/cli/commands/base.js index 3a3bee92..3264799e 100644 --- a/lib/cli/commands/base.js +++ b/lib/cli/commands/base.js @@ -11,12 +11,6 @@ cli.usage("Usage: ui5 [options]") "This option will disable resolution of node package dependencies.", type: "string" }) - .option("translator", { - describe: "Translator to use. Including optional colon separated translator parameters.", - alias: "t8r", - default: "npm", - type: "string" - }) .option("verbose", { describe: "Enable verbose logging.", type: "boolean" diff --git a/lib/cli/commands/serve.js b/lib/cli/commands/serve.js index 0cd5f597..5878632c 100644 --- a/lib/cli/commands/serve.js +++ b/lib/cli/commands/serve.js @@ -73,7 +73,7 @@ serve.builder = function(cli) { }; serve.handler = async function(argv) { - const generateProjectGraph = require("@ui5/project").generateProjectGraph; + const {generateProjectGraph} = require("@ui5/project"); const ui5Server = require("@ui5/server"); const server = ui5Server.server; From 2d1b34fedece106c31618ee252baf81c21b5d34b Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 24 May 2022 10:36:40 +0200 Subject: [PATCH 11/25] [INTERNAL] add/remove/use: Adapt to new ProjectGraph API --- lib/cli/commands/add.js | 8 +- lib/cli/commands/remove.js | 8 +- lib/cli/commands/use.js | 8 +- lib/framework/add.js | 46 ++- lib/framework/remove.js | 26 +- lib/framework/updateYaml.js | 6 +- lib/framework/use.js | 21 +- lib/framework/utils.js | 22 +- test/lib/cli/commands/add.js | 6 +- test/lib/cli/commands/remove.js | 6 +- test/lib/framework/use.js | 708 ++++++++++++-------------------- 11 files changed, 340 insertions(+), 525 deletions(-) diff --git a/lib/cli/commands/add.js b/lib/cli/commands/add.js index 5c590ace..d96c9c69 100644 --- a/lib/cli/commands/add.js +++ b/lib/cli/commands/add.js @@ -41,9 +41,9 @@ addCommand.handler = async function(argv) { throw new Error("Options 'development' and 'optional' cannot be combined"); } - const normalizerOptions = { - translatorName: argv.translator, - configPath: argv.config + const projectGraphOptions = { + dependencyDefinition: argv.dependencyDefinition, + config: argv.config }; const libraries = libraryNames.map((name) => { @@ -57,7 +57,7 @@ addCommand.handler = async function(argv) { }); const {yamlUpdated} = await require("../../framework/add")({ - normalizerOptions, + projectGraphOptions, libraries }); diff --git a/lib/cli/commands/remove.js b/lib/cli/commands/remove.js index 57cb4495..f5464b42 100644 --- a/lib/cli/commands/remove.js +++ b/lib/cli/commands/remove.js @@ -27,9 +27,9 @@ removeCommand.handler = async function(argv) { return libraryNames.indexOf(libraryName) === index; }); - const normalizerOptions = { - translatorName: argv.translator, - configPath: argv.config + const projectGraphOptions = { + dependencyDefinition: argv.dependencyDefinition, + config: argv.config }; const libraries = libraryNames.map((name) => { @@ -38,7 +38,7 @@ removeCommand.handler = async function(argv) { }); const {yamlUpdated} = await require("../../framework/remove")({ - normalizerOptions, + projectGraphOptions, libraries }); diff --git a/lib/cli/commands/use.js b/lib/cli/commands/use.js index 68d242e8..0152a34a 100644 --- a/lib/cli/commands/use.js +++ b/lib/cli/commands/use.js @@ -55,13 +55,13 @@ function parseFrameworkInfo(frameworkInfo) { useCommand.handler = async function(argv) { const frameworkOptions = parseFrameworkInfo(argv["framework-info"]); - const normalizerOptions = { - translatorName: argv.translator, - configPath: argv.config + const projectGraphOptions = { + dependencyDefinition: argv.dependencyDefinition, + config: argv.config }; const {usedFramework, usedVersion, yamlUpdated} = await require("../../framework/use")({ - normalizerOptions, + projectGraphOptions, frameworkOptions }); diff --git a/lib/framework/add.js b/lib/framework/add.js index e53448f8..512ed451 100644 --- a/lib/framework/add.js +++ b/lib/framework/add.js @@ -1,33 +1,40 @@ const {getRootProjectConfiguration, getFrameworkResolver, isValidSpecVersion} = require("./utils"); -module.exports = async function({normalizerOptions, libraries}) { - const project = await getRootProjectConfiguration({normalizerOptions}); +/** + * Adds the given set of libraries to the framework libraries section in the ui5.yaml + * + * @param {object} parameters Parameters + * @param {object} parameters.projectGraphOptions + * @param {object} parameters.libraries + */ +module.exports = async function({projectGraphOptions, libraries}) { + const project = await getRootProjectConfiguration(projectGraphOptions); - if (!isValidSpecVersion(project.specVersion)) { + if (!isValidSpecVersion(project.getSpecVersion())) { throw new Error( `ui5 add command requires specVersion "2.0" or higher. ` + - `Project ${project.metadata.name} uses specVersion "${project.specVersion}"` + `Project ${project.getName()} uses specVersion "${project.getSpecVersion()}"` ); } - if (!project.framework) { + if (!project.getFrameworkName()) { throw new Error( - `Project ${project.metadata.name} is missing a framework configuration. ` + + `Project ${project.getName()} is missing a framework configuration. ` + `Please use "ui5 use" to configure a framework and version.` ); } - if (!project.framework.version) { + if (!project.getFrameworkVersion()) { throw new Error( - `Project ${project.metadata.name} does not define a framework version configuration. ` + + `Project ${project.getName()} does not define a framework version configuration. ` + `Please use "ui5 use" to configure a version.` ); } - const Resolver = getFrameworkResolver(project.framework.name); + const Resolver = getFrameworkResolver(project.getFrameworkName()); const resolver = new Resolver({ - cwd: project.path, - version: project.framework.version + cwd: project.getPath(), + version: project.getFrameworkVersion() }); // Get metadata of all libraries to verify that they can be installed @@ -35,21 +42,20 @@ module.exports = async function({normalizerOptions, libraries}) { try { await resolver.getLibraryMetadata(name); } catch (err) { - throw new Error(`Failed to find ${project.framework.name} framework library ${name}: ` + err.message); + throw new Error(`Failed to find ${project.getFrameworkName()} framework library ${name}: ` + err.message); } })); // Shallow copy of given libraries to not modify the input parameter when pushing other libraries const allLibraries = [...libraries]; - if (project.framework.libraries) { - project.framework.libraries.forEach((library) => { - // Don't add libraries twice! - if (allLibraries.findIndex(($) => $.name === library.name) === -1) { - allLibraries.push(library); - } - }); - } + project.getFrameworkDependencies().forEach((library) => { + // Don't add libraries twice! + if (allLibraries.findIndex(($) => $.name === library.name) === -1) { + allLibraries.push(library); + } + }); + allLibraries.sort((a, b) => { return a.name.localeCompare(b.name); }); diff --git a/lib/framework/remove.js b/lib/framework/remove.js index 34e80830..a9bcc587 100644 --- a/lib/framework/remove.js +++ b/lib/framework/remove.js @@ -6,39 +6,39 @@ const log = require("@ui5/logger").getLogger("cli:framework:remove"); * Removes the given set of libraries from the framework libraries section in the ui5.yaml * * @param {object} parameters Parameters - * @param {object} parameters.normalizerOptions + * @param {object} parameters.projectGraphOptions * @param {object} parameters.libraries */ -module.exports = async function({normalizerOptions, libraries}) { - const project = await getRootProjectConfiguration({normalizerOptions}); +module.exports = async function({projectGraphOptions, libraries}) { + const project = await getRootProjectConfiguration({projectGraphOptions}); - if (!isValidSpecVersion(project.specVersion)) { + if (!isValidSpecVersion(project.getSpecVersion())) { throw new Error( `ui5 remove command requires specVersion "2.0" or higher. ` + - `Project ${project.metadata.name} uses specVersion "${project.specVersion}"` + `Project ${project.getName()} uses specVersion "${project.getSpecVersion()}"` ); } - if (!project.framework) { + if (!project.getFrameworkName()) { throw new Error( - `Project ${project.metadata.name} is missing a framework configuration. ` + + `Project ${project.getName()} is missing a framework configuration. ` + `Please use "ui5 use" to configure a framework and version.` ); } - if (!project.framework.version) { + if (!project.getFrameworkVersion()) { throw new Error( - `Project ${project.metadata.name} does not define a framework version configuration. ` + + `Project ${project.getName()} does not define a framework version configuration. ` + `Please use "ui5 use" to configure a version.` ); } - if (!project.framework.libraries) { + if (!project.getFrameworkDependencies().length) { throw new Error( - `Project ${project.metadata.name} does not define framework libraries.` + `Project ${project.getName()} does not define framework libraries.` ); } - const allLibraries = [...project.framework.libraries]; + const allLibraries = [...project.getFrameworkDependencies()]; libraries.forEach((library) => { const iIndexToRemove = allLibraries.findIndex(($) => $.name === library.name); @@ -46,7 +46,7 @@ module.exports = async function({normalizerOptions, libraries}) { // do not fail here just log, because the framework library is not present afterwards log.warn( `Failed to remove framework library ${library.name} from project ` + - `${project.metadata.name} because it is not present.` + `${project.getName()} because it is not present.` ); } else { allLibraries.splice(iIndexToRemove, 1); diff --git a/lib/framework/updateYaml.js b/lib/framework/updateYaml.js index be272b16..fabcf13c 100644 --- a/lib/framework/updateYaml.js +++ b/lib/framework/updateYaml.js @@ -9,11 +9,11 @@ function getProjectYamlDocument({project, configFile, configPath}) { }); const projectDocumentIndex = configs.findIndex((config) => { - return config.metadata && config.metadata.name === project.metadata.name; + return config.metadata && config.metadata.name === project.getName(); }); if (projectDocumentIndex === -1) { throw new Error( - `Could not find project with name ${project.metadata.name} in YAML: ${configPath}` + `Could not find project with name ${project.getName()} in YAML: ${configPath}` ); } @@ -143,7 +143,7 @@ module.exports = async function({project, data}) { const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); - const configPath = project.configPath || path.join(project.path, "ui5.yaml"); + const configPath = path.join(project.getPath(), "ui5.yaml"); const configFile = await readFile(configPath, {encoding: "utf8"}); let { diff --git a/lib/framework/use.js b/lib/framework/use.js index 4730400c..8f90af2d 100644 --- a/lib/framework/use.js +++ b/lib/framework/use.js @@ -5,13 +5,10 @@ async function resolveVersion({frameworkName, frameworkVersion}, resolverOptions } function getEffectiveFrameworkName({project, frameworkOptions}) { - if (!project.framework && !frameworkOptions.name) { + if (!project.getFrameworkName() && !frameworkOptions.name) { throw new Error("No framework configuration defined. Make sure to also provide the framework name."); } - if (project.framework && !project.framework.name) { - // This should not happen as the configuration should have been validated against the schema - throw new Error(`Project ${project.metadata.name} does not define a framework name configuration`); - } + if (frameworkOptions.name) { if (frameworkOptions.name.toLowerCase() === "openui5") { return "OpenUI5"; @@ -21,17 +18,17 @@ function getEffectiveFrameworkName({project, frameworkOptions}) { throw new Error("Invalid framework name: " + frameworkOptions.name); } } else { - return project.framework.name; + return project.getFrameworkName(); } } -module.exports = async function({normalizerOptions, frameworkOptions}) { - const project = await getRootProjectConfiguration({normalizerOptions}); +module.exports = async function({projectGraphOptions, frameworkOptions}) { + const project = await getRootProjectConfiguration(projectGraphOptions); - if (!isValidSpecVersion(project.specVersion)) { + if (!isValidSpecVersion(project.getSpecVersion())) { throw new Error( `ui5 use command requires specVersion "2.0" or higher. ` + - `Project ${project.metadata.name} uses specVersion "${project.specVersion}"` + `Project ${project.getName()} uses specVersion "${project.getSpecVersion()}"` ); } @@ -39,13 +36,13 @@ module.exports = async function({normalizerOptions, frameworkOptions}) { name: getEffectiveFrameworkName({project, frameworkOptions}) }; - const frameworkVersion = frameworkOptions.version || (project.framework && project.framework.version); + const frameworkVersion = frameworkOptions.version || project.getFrameworkVersion(); if (frameworkVersion) { framework.version = await resolveVersion({ frameworkName: framework.name, frameworkVersion }, { - cwd: project.path + cwd: project.getPath() }); } diff --git a/lib/framework/utils.js b/lib/framework/utils.js index 90c8b542..67d6e14b 100644 --- a/lib/framework/utils.js +++ b/lib/framework/utils.js @@ -1,17 +1,19 @@ module.exports = { - getRootProjectConfiguration: async function({normalizerOptions}) { - const {normalizer, projectPreprocessor} = require("@ui5/project"); + getRootProjectConfiguration: async function(projectGraphOptions) { + const {generateProjectGraph} = require("@ui5/project"); - const tree = await normalizer.generateDependencyTree(normalizerOptions); - - if (normalizerOptions.configPath) { - tree.configPath = normalizerOptions.configPath; + let graph; + if (projectGraphOptions.dependencyDefinition) { + graph = await generateProjectGraph.usingStaticFile({ + filePath: projectGraphOptions.dependencyDefinition + }); + } else { + graph = await generateProjectGraph.usingNodePackageDependencies({ + rootConfigPath: projectGraphOptions.config + }); } - // Prevent dependencies from being processed - tree.dependencies = []; - - return projectPreprocessor.processTree(tree); + return graph.getRoot(); }, getFrameworkResolver: function(frameworkName) { if (frameworkName === "SAPUI5") { diff --git a/test/lib/cli/commands/add.js b/test/lib/cli/commands/add.js index d77eaacb..210e33fa 100644 --- a/test/lib/cli/commands/add.js +++ b/test/lib/cli/commands/add.js @@ -16,9 +16,9 @@ async function assertAddHandler(t, {argv, expectedLibraries, expectedConsoleLog} t.deepEqual(frameworkAddStub.getCall(0).args, [ { libraries: expectedLibraries, - normalizerOptions: { - configPath: undefined, - translatorName: undefined + projectGraphOptions: { + dependencyDefinition: undefined, + config: undefined } }], "Add function should be called with expected args"); diff --git a/test/lib/cli/commands/remove.js b/test/lib/cli/commands/remove.js index 0c317317..8fc3420a 100644 --- a/test/lib/cli/commands/remove.js +++ b/test/lib/cli/commands/remove.js @@ -16,9 +16,9 @@ async function assertRemoveHandler(t, {argv, expectedLibraries, expectedConsoleL t.deepEqual(frameworkRemoveStub.getCall(0).args, [ { libraries: expectedLibraries, - normalizerOptions: { - configPath: undefined, - translatorName: undefined + projectGraphOptions: { + dependencyDefinition: undefined, + config: undefined } }], "Remove function should be called with expected args"); diff --git a/test/lib/framework/use.js b/test/lib/framework/use.js index 5cfdcd7f..983d3bdc 100644 --- a/test/lib/framework/use.js +++ b/test/lib/framework/use.js @@ -2,16 +2,26 @@ const test = require("ava"); const sinon = require("sinon"); const mock = require("mock-require"); -const ui5Project = require("@ui5/project"); +const utils = require("../../../lib/framework/utils"); let useFramework; -test.beforeEach((t) => { - t.context.generateDependencyTreeStub = sinon.stub(ui5Project.normalizer, "generateDependencyTree"); - t.context.processTreeStub = sinon.stub(ui5Project.projectPreprocessor, "processTree"); - t.context.Openui5ResolveVersionStub = sinon.stub(ui5Project.ui5Framework.Openui5Resolver, "resolveVersion"); - t.context.Sapui5ResolveVersionStub = sinon.stub(ui5Project.ui5Framework.Sapui5Resolver, "resolveVersion"); +function createMockProject(attr) { + return { + getName: () => attr.name, + getSpecVersion: () => attr.specVersion, + getPath: () => attr.path, + getFrameworkName: () => attr.frameworkName, + getFrameworkVersion: () => attr.frameworkVersion, + }; +} +test.beforeEach((t) => { + t.context.getRootProjectConfigurationStub = sinon.stub(utils, "getRootProjectConfiguration"); + t.context.resolveVersionStub = sinon.stub(); + t.context.getFrameworkResolverStub = sinon.stub(utils, "getFrameworkResolver").returns({ + resolveVersion: t.context.resolveVersionStub + }); t.context.updateYamlStub = sinon.stub(); mock("../../../lib/framework/updateYaml", t.context.updateYamlStub); @@ -24,29 +34,26 @@ test.afterEach.always(() => { }); test.serial("Use with name and version (OpenUI5)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, Openui5ResolveVersionStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, resolveVersionStub, + getFrameworkResolverStub, updateYamlStub + } = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project" - }; + name: "my-project", + path: "my-project-path" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Openui5ResolveVersionStub.resolves("1.76.0"); + getRootProjectConfigurationStub.resolves(project); + resolveVersionStub.resolves("1.76.0"); const result = await useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: "openui5", version: "latest" @@ -59,19 +66,16 @@ test.serial("Use with name and version (OpenUI5)", async (t) => { yamlUpdated: true }, "useFramework should return expected result object"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); - - t.is(Openui5ResolveVersionStub.callCount, 1, "Openui5Resolver.resolveVersion should be called once"); - t.deepEqual(Openui5ResolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project"}], - "Openui5Resolver.resolveVersion should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "OpenUI5", + "getFrameworkResolver called with expected framework"); + t.is(resolveVersionStub.callCount, 1, "Resolver#resolveVersion should be called once"); + t.deepEqual(resolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project-path"}], + "Resolver#resolveVersion should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ @@ -86,29 +90,26 @@ test.serial("Use with name and version (OpenUI5)", async (t) => { }); test.serial("Use with name and version (SAPUI5)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, resolveVersionStub, + getFrameworkResolverStub, updateYamlStub + } = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project" - }; + name: "my-project", + path: "my-project-path" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Sapui5ResolveVersionStub.resolves("1.76.0"); + getRootProjectConfigurationStub.resolves(project); + resolveVersionStub.resolves("1.76.0"); const result = await useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: "sapui5", version: "latest" @@ -121,19 +122,16 @@ test.serial("Use with name and version (SAPUI5)", async (t) => { yamlUpdated: true }, "useFramework should return expected result object"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(Sapui5ResolveVersionStub.callCount, 1, "Sapui5Resolver.resolveVersion should be called once"); - t.deepEqual(Sapui5ResolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project"}], - "Sapui5Resolver.resolveVersion should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "SAPUI5", + "getFrameworkResolver called with expected framework"); + t.is(resolveVersionStub.callCount, 1, "Resolver#resolveVersion should be called once"); + t.deepEqual(resolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project-path"}], + "Resolver#resolveVersion should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ @@ -148,32 +146,27 @@ test.serial("Use with name and version (SAPUI5)", async (t) => { }); test.serial("Use with version only (OpenUI5)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, Openui5ResolveVersionStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, resolveVersionStub, + getFrameworkResolverStub, updateYamlStub + } = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project", - framework: { - name: "OpenUI5" - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "OpenUI5" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Openui5ResolveVersionStub.resolves("1.76.0"); + getRootProjectConfigurationStub.resolves(project); + resolveVersionStub.resolves("1.76.0"); const result = await useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: null, version: "latest" @@ -186,19 +179,16 @@ test.serial("Use with version only (OpenUI5)", async (t) => { yamlUpdated: true }, "useFramework should return expected result object"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(Openui5ResolveVersionStub.callCount, 1, "Openui5Resolver.resolveVersion should be called once"); - t.deepEqual(Openui5ResolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project"}], - "Openui5Resolver.resolveVersion should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "OpenUI5", + "getFrameworkResolver called with expected framework"); + t.is(resolveVersionStub.callCount, 1, "Resolver#resolveVersion should be called once"); + t.deepEqual(resolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project-path"}], + "Resolver#resolveVersion should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ @@ -213,32 +203,27 @@ test.serial("Use with version only (OpenUI5)", async (t) => { }); test.serial("Use with version only (SAPUI5)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, resolveVersionStub, + getFrameworkResolverStub, updateYamlStub + } = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project", - framework: { - name: "SAPUI5" - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "SAPUI5" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Sapui5ResolveVersionStub.resolves("1.76.0"); + getRootProjectConfigurationStub.resolves(project); + resolveVersionStub.resolves("1.76.0"); const result = await useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: null, version: "latest" @@ -251,19 +236,16 @@ test.serial("Use with version only (SAPUI5)", async (t) => { yamlUpdated: true }, "useFramework should return expected result object"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(Sapui5ResolveVersionStub.callCount, 1, "Sapui5Resolver.resolveVersion should be called once"); - t.deepEqual(Sapui5ResolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project"}], - "Sapui5Resolver.resolveVersion should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "SAPUI5", + "getFrameworkResolver called with expected framework"); + t.is(resolveVersionStub.callCount, 1, "Resolver#resolveVersion should be called once"); + t.deepEqual(resolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project-path"}], + "Resolver#resolveVersion should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ @@ -278,28 +260,25 @@ test.serial("Use with version only (SAPUI5)", async (t) => { }); test.serial("Use with name only (no existing framework configuration)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, resolveVersionStub, + getFrameworkResolverStub, updateYamlStub + } = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project" - }; + name: "my-project", + path: "my-project-path" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const result = await useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: "SAPUI5", version: null @@ -312,17 +291,12 @@ test.serial("Use with name only (no existing framework configuration)", async (t yamlUpdated: true }, "useFramework should return expected result object"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(Sapui5ResolveVersionStub.callCount, 0, "Sapui5Resolver.resolveVersion should not be called"); + t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); + t.is(resolveVersionStub.callCount, 0, "Resolver#resolveVersion should not be called"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ @@ -336,33 +310,28 @@ test.serial("Use with name only (no existing framework configuration)", async (t }); test.serial("Use with name only (existing framework configuration)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, resolveVersionStub, + getFrameworkResolverStub, updateYamlStub + } = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project", - framework: { - name: "OpenUI5", - version: "1.76.0" - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Sapui5ResolveVersionStub.resolves("1.76.0"); + getRootProjectConfigurationStub.resolves(project); + resolveVersionStub.resolves("1.76.0"); const result = await useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: "SAPUI5", version: null @@ -375,19 +344,16 @@ test.serial("Use with name only (existing framework configuration)", async (t) = yamlUpdated: true }, "useFramework should return expected result object"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); - - t.is(Sapui5ResolveVersionStub.callCount, 1, "Sapui5Resolver.resolveVersion should be called once"); - t.deepEqual(Sapui5ResolveVersionStub.getCall(0).args, ["1.76.0", {cwd: "my-project"}], - "Sapui5Resolver.resolveVersion should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "SAPUI5", + "getFrameworkResolver called with expected framework"); + t.is(resolveVersionStub.callCount, 1, "Resolver#resolveVersion should be called once"); + t.deepEqual(resolveVersionStub.getCall(0).args, ["1.76.0", {cwd: "my-project-path"}], + "Resolver#resolveVersion should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ @@ -401,30 +367,27 @@ test.serial("Use with name only (existing framework configuration)", async (t) = }], "updateYaml should be called with expected args"); }); -test.serial("Use with normalizerOptions.configPath", async (t) => { - const {generateDependencyTreeStub, processTreeStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; +test.serial("Use with projectGraphOptions.config", async (t) => { + const { + getRootProjectConfigurationStub, resolveVersionStub, + getFrameworkResolverStub, updateYamlStub + } = t.context; - const normalizerOptions = { - configPath: "/path/to/ui5.yaml" + const projectGraphOptions = { + config: "/path/to/ui5.yaml" }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project" - }; + name: "my-project", + path: "my-project-path" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Sapui5ResolveVersionStub.resolves("1.76.0"); + getRootProjectConfigurationStub.resolves(project); + resolveVersionStub.resolves("1.76.0"); const result = await useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: "SAPUI5", version: "latest" @@ -437,20 +400,16 @@ test.serial("Use with normalizerOptions.configPath", async (t) => { yamlUpdated: true }, "useFramework should return expected result object"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{configPath: "/path/to/ui5.yaml"}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - configPath: "/path/to/ui5.yaml", - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{config: "/path/to/ui5.yaml"}], + "generateProjectGraph should be called with expected args"); - t.is(Sapui5ResolveVersionStub.callCount, 1, "Sapui5Resolver.resolveVersion should be called once"); - t.deepEqual(Sapui5ResolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project"}], - "Sapui5Resolver.resolveVersion should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "SAPUI5", + "getFrameworkResolver called with expected framework"); + t.is(resolveVersionStub.callCount, 1, "Resolver#resolveVersion should be called once"); + t.deepEqual(resolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project-path"}], + "Resolver#resolveVersion should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ @@ -465,29 +424,23 @@ test.serial("Use with normalizerOptions.configPath", async (t) => { }); test.serial("Use with version only (no framework name)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5ResolveVersionStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, + resolveVersionStub, getFrameworkResolverStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project" - }; + name: "my-project", + path: "my-project-path", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: null, version: "latest" @@ -496,46 +449,34 @@ test.serial("Use with version only (no framework name)", async (t) => { t.is(error.message, "No framework configuration defined. Make sure to also provide the framework name."); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(Openui5ResolveVersionStub.callCount, 0, "Openui5Resolver.resolveVersion should not be called"); - t.is(Sapui5ResolveVersionStub.callCount, 0, "Sapui5Resolver.resolveVersion should not be called"); + t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); + t.is(resolveVersionStub.callCount, 0, "Resolver#resolveVersion should not be called"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Use with invalid name", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5ResolveVersionStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, + resolveVersionStub, getFrameworkResolverStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project" - }; + name: "my-project", + path: "my-project-path", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: "Foo", version: "latest" @@ -544,46 +485,34 @@ test.serial("Use with invalid name", async (t) => { t.is(error.message, "Invalid framework name: Foo"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); - - t.is(Openui5ResolveVersionStub.callCount, 0, "Openui5Resolver.resolveVersion should not be called"); - t.is(Sapui5ResolveVersionStub.callCount, 0, "Sapui5Resolver.resolveVersion should not be called"); + t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); + t.is(resolveVersionStub.callCount, 0, "Resolver#resolveVersion should not be called"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Use with specVersion 1.0", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5ResolveVersionStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, + resolveVersionStub, getFrameworkResolverStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "1.0", - metadata: { - name: "my-project" - }, - path: "my-project" - }; + name: "my-project", + path: "my-project-path", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: "Foo", version: "latest" @@ -593,151 +522,41 @@ test.serial("Use with specVersion 1.0", async (t) => { t.is(error.message, `ui5 use command requires specVersion "2.0" or higher. Project my-project uses specVersion "1.0"`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(Openui5ResolveVersionStub.callCount, 0, "Openui5Resolver.resolveVersion should not be called"); - t.is(Sapui5ResolveVersionStub.callCount, 0, "Sapui5Resolver.resolveVersion should not be called"); - - t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); -}); - -test.serial("Use without framework.name (should actually be validated via ui5-project)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5ResolveVersionStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; - - const normalizerOptions = { - "fakeNormalizerOption": true - }; - - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { - specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project", - framework: {} - }; - - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - - const error = await t.throwsAsync(useFramework({ - normalizerOptions, - frameworkOptions: { - name: "SAPUI5", - version: "latest" - } - })); - - t.is(error.message, - `Project my-project does not define a framework name configuration`); - - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); - - t.is(Openui5ResolveVersionStub.callCount, 0, "Openui5Resolver.resolveVersion should not be called"); - t.is(Sapui5ResolveVersionStub.callCount, 0, "Sapui5Resolver.resolveVersion should not be called"); - - t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); -}); - -test.serial("Use with invalid framework name in config (should actually be validated via ui5-project)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5ResolveVersionStub, Sapui5ResolveVersionStub, updateYamlStub} = t.context; - - const normalizerOptions = { - "fakeNormalizerOption": true - }; - - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { - specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project", - framework: { - name: "Foo" - } - }; - - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - - const error = await t.throwsAsync(useFramework({ - normalizerOptions, - frameworkOptions: { - name: null, - version: "latest" - } - })); - - t.is(error.message, "Invalid framework.name: Foo"); - - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); - - t.is(Openui5ResolveVersionStub.callCount, 0, "Openui5Resolver.resolveVersion should not be called"); - t.is(Sapui5ResolveVersionStub.callCount, 0, "Sapui5Resolver.resolveVersion should not be called"); + t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); + t.is(resolveVersionStub.callCount, 0, "Resolver#resolveVersion should not be called"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Use with name and version (YAML update fails)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, Openui5ResolveVersionStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, resolveVersionStub, + getFrameworkResolverStub, updateYamlStub + } = t.context; const yamlUpdateError = new Error("Failed to update YAML file"); yamlUpdateError.name = "FrameworkUpdateYamlFailed"; updateYamlStub.rejects(yamlUpdateError); - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project" - }; + name: "my-project", + path: "my-project-path", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Openui5ResolveVersionStub.resolves("1.76.0"); + getRootProjectConfigurationStub.resolves(project); + resolveVersionStub.resolves("1.76.0"); const result = await useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: "openui5", version: "latest" @@ -750,19 +569,16 @@ test.serial("Use with name and version (YAML update fails)", async (t) => { yamlUpdated: false }, "useFramework should return expected result object"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(Openui5ResolveVersionStub.callCount, 1, "Openui5Resolver.resolveVersion should be called once"); - t.deepEqual(Openui5ResolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project"}], - "Openui5Resolver.resolveVersion should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "OpenUI5", + "getFrameworkResolver called with expected framework"); + t.is(resolveVersionStub.callCount, 1, "Resolver#resolveVersion should be called once"); + t.deepEqual(resolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project-path"}], + "Resolver#resolveVersion should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ @@ -777,31 +593,28 @@ test.serial("Use with name and version (YAML update fails)", async (t) => { }); test.serial("Use with name and version (YAML update fails with unexpected error)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, Openui5ResolveVersionStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, resolveVersionStub, + getFrameworkResolverStub, updateYamlStub + } = t.context; updateYamlStub.rejects(new Error("Some unexpected error")); - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + "fakeProjectGraphOptions": true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - path: "my-project" - }; + name: "my-project", + path: "my-project-path", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Openui5ResolveVersionStub.resolves("1.76.0"); + getRootProjectConfigurationStub.resolves(project); + resolveVersionStub.resolves("1.76.0"); const error = await t.throwsAsync(useFramework({ - normalizerOptions, + projectGraphOptions, frameworkOptions: { name: "openui5", version: "latest" @@ -810,19 +623,16 @@ test.serial("Use with name and version (YAML update fails with unexpected error) t.is(error.message, "Some unexpected error"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + "generateProjectGraph should be called with expected args"); - t.is(Openui5ResolveVersionStub.callCount, 1, "Openui5Resolver.resolveVersion should be called once"); - t.deepEqual(Openui5ResolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project"}], - "Openui5Resolver.resolveVersion should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "OpenUI5", + "getFrameworkResolver called with expected framework"); + t.is(resolveVersionStub.callCount, 1, "Resolver#resolveVersion should be called once"); + t.deepEqual(resolveVersionStub.getCall(0).args, ["latest", {cwd: "my-project-path"}], + "Resolver#resolveVersion should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ From b70fd3c0eee1272fd2ff02c4710cb4f9af860830 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 24 May 2022 13:12:47 +0200 Subject: [PATCH 12/25] [INTERNAL] utils: Do not add framework deps to graph --- lib/framework/utils.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/framework/utils.js b/lib/framework/utils.js index 67d6e14b..ae53fb1c 100644 --- a/lib/framework/utils.js +++ b/lib/framework/utils.js @@ -5,11 +5,13 @@ module.exports = { let graph; if (projectGraphOptions.dependencyDefinition) { graph = await generateProjectGraph.usingStaticFile({ - filePath: projectGraphOptions.dependencyDefinition + filePath: projectGraphOptions.dependencyDefinition, + resolveFrameworkDependencies: false }); } else { graph = await generateProjectGraph.usingNodePackageDependencies({ - rootConfigPath: projectGraphOptions.config + rootConfigPath: projectGraphOptions.config, + resolveFrameworkDependencies: false }); } From 8fc8c526f3786b818792b347dd6ade1dcae619cf Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 24 May 2022 13:12:53 +0200 Subject: [PATCH 13/25] [INTERNAL] updateYaml: Add config path override --- lib/framework/add.js | 1 + lib/framework/remove.js | 1 + lib/framework/updateYaml.js | 14 ++- lib/framework/use.js | 1 + test/lib/framework/updateYaml.js | 186 ++++++++++++++++++++++--------- 5 files changed, 148 insertions(+), 55 deletions(-) diff --git a/lib/framework/add.js b/lib/framework/add.js index 512ed451..4e21c088 100644 --- a/lib/framework/add.js +++ b/lib/framework/add.js @@ -65,6 +65,7 @@ module.exports = async function({projectGraphOptions, libraries}) { try { await require("./updateYaml")({ project, + configPathOverride: projectGraphOptions.config, data: { framework: { libraries: allLibraries diff --git a/lib/framework/remove.js b/lib/framework/remove.js index a9bcc587..c2d14768 100644 --- a/lib/framework/remove.js +++ b/lib/framework/remove.js @@ -63,6 +63,7 @@ module.exports = async function({projectGraphOptions, libraries}) { try { await require("./updateYaml")({ project, + configPathOverride: projectGraphOptions.config, data: { framework: { libraries: allLibraries diff --git a/lib/framework/updateYaml.js b/lib/framework/updateYaml.js index fabcf13c..afc72313 100644 --- a/lib/framework/updateYaml.js +++ b/lib/framework/updateYaml.js @@ -137,13 +137,23 @@ function formatValue(value, indent) { } } -module.exports = async function({project, data}) { +module.exports = async function({project, configPathOverride, data}) { const {promisify} = require("util"); const fs = require("fs"); const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); - const configPath = path.join(project.getPath(), "ui5.yaml"); + let configPath; + if (configPathOverride) { + if (path.isAbsolute(configPathOverride)) { + configPath = configPathOverride; + } else { + configPath = path.join(project.getPath(), configPathOverride); + } + } else { + configPath = path.join(project.getPath(), "ui5.yaml"); + } + const configFile = await readFile(configPath, {encoding: "utf8"}); let { diff --git a/lib/framework/use.js b/lib/framework/use.js index 8f90af2d..2b634496 100644 --- a/lib/framework/use.js +++ b/lib/framework/use.js @@ -51,6 +51,7 @@ module.exports = async function({projectGraphOptions, frameworkOptions}) { try { await require("./updateYaml")({ project, + configPathOverride: projectGraphOptions.config, data: { framework: framework } diff --git a/test/lib/framework/updateYaml.js b/test/lib/framework/updateYaml.js index 54f67d01..4cacb313 100644 --- a/test/lib/framework/updateYaml.js +++ b/test/lib/framework/updateYaml.js @@ -31,8 +31,8 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -75,8 +75,8 @@ shims: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -131,8 +131,8 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -173,8 +173,8 @@ metadata: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -201,8 +201,8 @@ metadata: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -234,8 +234,8 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -267,8 +267,8 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -301,14 +301,14 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { libraries: [ {name: "sap.ui.core"}, - {name: "sap.m"} + {name: "sap.m"}, ] } } @@ -342,14 +342,14 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { libraries: [ {name: "sap.ui.core"}, - {name: "sap.m"} + {name: "sap.m"}, ] } } @@ -386,8 +386,8 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -424,8 +424,8 @@ resources: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -462,14 +462,14 @@ resources: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { libraries: [ {name: "sap.ui.core"}, - {name: "sap.m"} + {name: "sap.m"}, ] } } @@ -507,14 +507,14 @@ resources: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { libraries: [ {name: "sap.ui.core"}, - {name: "sap.m"} + {name: "sap.m"}, ] } } @@ -552,14 +552,14 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { libraries: [ {name: "sap.ui.core"}, - {name: "sap.m"} + {name: "sap.m"}, ] } } @@ -593,15 +593,15 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { libraries: [ {name: "sap.ui.core"}, {name: "sap.m"}, - {name: "sap.ui.layout"} + {name: "sap.ui.layout"}, ] } } @@ -639,15 +639,15 @@ resources: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { libraries: [ {name: "sap.ui.core"}, {name: "sap.m"}, - {name: "sap.ui.layout"} + {name: "sap.ui.layout"}, ] } } @@ -690,15 +690,15 @@ resources: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { libraries: [ {name: "sap.ui.core"}, {name: "sap.m"}, - {name: "sap.ui.layout"} + {name: "sap.ui.layout"}, ] } } @@ -737,15 +737,15 @@ framework: await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { libraries: [ {name: "sap.ui.core", optional: true}, {name: "sap.m", optional: true}, - {name: "sap.ui.layout", optional: true} + {name: "sap.ui.layout", optional: true}, ] } } @@ -779,8 +779,8 @@ framework: { name: "SAPUI5" } const error = await t.throwsAsync(updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -813,14 +813,14 @@ metadata: const error = await t.throwsAsync(updateYaml({ project: { - path: "my-project", - configPath: "ui5.yaml", - metadata: {"name": "my-project-3"} + getPath: () => "my-project", + getName: () => "my-project-3" }, + configPathOverride: "ui5.yaml", data: {} })); - t.is(error.message, "Could not find project with name my-project-3 in YAML: ui5.yaml"); + t.is(error.message, "Could not find project with name my-project-3 in YAML: my-project/ui5.yaml"); t.is(t.context.fsWriteFileStub.callCount, 0, "fs.writeFile should not be called"); }); @@ -837,8 +837,8 @@ something: else await updateYaml({ project: { - path: "my-project", - metadata: {"name": "my-project"} + getPath: () => "my-project", + getName: () => "my-project" }, data: { framework: { @@ -861,3 +861,83 @@ framework: something: else `, "writeFile should be called with expected content"); }); + +test.serial("Relative configPathOverride", async (t) => { + t.context.fsReadFileStub.yieldsAsync(null, ` +--- +metadata: + name: my-project +framework: + name: SAPUI5 + version: 1.0.0 +`); + + await updateYaml({ + project: { + getPath: () => "my-project", + getName: () => "my-project" + }, + configPathOverride: "dir/other-file.yaml", + data: { + framework: { + name: "OpenUI5", + version: "1.76.0" + } + } + }); + + t.is(t.context.fsReadFileStub.callCount, 1, "fs.readFile should be called once"); + t.deepEqual(t.context.fsWriteFileStub.getCall(0).args[0], path.join("my-project", "dir", "other-file.yaml"), + "readFile should be called with expected path"); + t.is(t.context.fsWriteFileStub.callCount, 1, "fs.writeFile should be called once"); + t.deepEqual(t.context.fsWriteFileStub.getCall(0).args[0], path.join("my-project", "dir", "other-file.yaml"), + "writeFile should be called with expected path"); + t.deepEqual(t.context.fsWriteFileStub.getCall(0).args[1], ` +--- +metadata: + name: my-project +framework: + name: OpenUI5 + version: "1.76.0" +`, "writeFile should be called with expected content"); +}); + +test.serial("Absolute configPathOverride", async (t) => { + t.context.fsReadFileStub.yieldsAsync(null, ` +--- +metadata: + name: my-project +framework: + name: SAPUI5 + version: 1.0.0 +`); + + await updateYaml({ + project: { + getPath: () => "my-project", + getName: () => "my-project" + }, + configPathOverride: "/dir/other-file.yaml", + data: { + framework: { + name: "OpenUI5", + version: "1.76.0" + } + } + }); + + t.is(t.context.fsReadFileStub.callCount, 1, "fs.readFile should be called once"); + t.deepEqual(t.context.fsWriteFileStub.getCall(0).args[0], path.join("/", "dir", "other-file.yaml"), + "readFile should be called with expected path"); + t.is(t.context.fsWriteFileStub.callCount, 1, "fs.writeFile should be called once"); + t.deepEqual(t.context.fsWriteFileStub.getCall(0).args[0], path.join("/", "dir", "other-file.yaml"), + "writeFile should be called with expected path"); + t.deepEqual(t.context.fsWriteFileStub.getCall(0).args[1], ` +--- +metadata: + name: my-project +framework: + name: OpenUI5 + version: "1.76.0" +`, "writeFile should be called with expected content"); +}); From e8150f9354e7f29f87f0a8cd0c1419ad989804f5 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 25 May 2022 13:05:48 +0200 Subject: [PATCH 14/25] [INTERNAL] Update framework/* tests --- lib/framework/remove.js | 2 +- test/lib/cli/commands/use.js | 6 +- test/lib/framework/add.js | 601 +++++++++++++++++------------------ test/lib/framework/remove.js | 429 +++++++++++-------------- test/lib/framework/use.js | 53 +-- 5 files changed, 513 insertions(+), 578 deletions(-) diff --git a/lib/framework/remove.js b/lib/framework/remove.js index c2d14768..827df75b 100644 --- a/lib/framework/remove.js +++ b/lib/framework/remove.js @@ -10,7 +10,7 @@ const log = require("@ui5/logger").getLogger("cli:framework:remove"); * @param {object} parameters.libraries */ module.exports = async function({projectGraphOptions, libraries}) { - const project = await getRootProjectConfiguration({projectGraphOptions}); + const project = await getRootProjectConfiguration(projectGraphOptions); if (!isValidSpecVersion(project.getSpecVersion())) { throw new Error( diff --git a/test/lib/cli/commands/use.js b/test/lib/cli/commands/use.js index 8a8b1c65..acf394ca 100644 --- a/test/lib/cli/commands/use.js +++ b/test/lib/cli/commands/use.js @@ -18,9 +18,9 @@ async function assertUseHandler(t, {argv, expectedFrameworkOptions}) { t.deepEqual(frameworkUseStub.getCall(0).args, [ { frameworkOptions: expectedFrameworkOptions, - normalizerOptions: { - configPath: undefined, - translatorName: undefined + projectGraphOptions: { + dependencyDefinition: undefined, + config: undefined } }], "Use function should be called with expected args"); diff --git a/test/lib/framework/add.js b/test/lib/framework/add.js index 7e892ebd..cbf384bb 100644 --- a/test/lib/framework/add.js +++ b/test/lib/framework/add.js @@ -2,17 +2,29 @@ const test = require("ava"); const sinon = require("sinon"); const mock = require("mock-require"); -const ui5Project = require("@ui5/project"); +const utils = require("../../../lib/framework/utils"); let addFramework; +function createMockProject(attr) { + return { + getName: () => attr.name, + getSpecVersion: () => attr.specVersion, + getPath: () => attr.path, + getFrameworkName: () => attr.frameworkName, + getFrameworkVersion: () => attr.frameworkVersion, + getFrameworkDependencies: () => attr.frameworkLibraries || [], + }; +} + test.beforeEach((t) => { - t.context.generateDependencyTreeStub = sinon.stub(ui5Project.normalizer, "generateDependencyTree"); - t.context.processTreeStub = sinon.stub(ui5Project.projectPreprocessor, "processTree"); - t.context.Openui5GetLibraryMetadataStub = sinon.stub( - ui5Project.ui5Framework.Openui5Resolver.prototype, "getLibraryMetadata"); - t.context.Sapui5GetLibraryMetadataStub = sinon.stub( - ui5Project.ui5Framework.Sapui5Resolver.prototype, "getLibraryMetadata"); + t.context.getRootProjectConfigurationStub = sinon.stub(utils, "getRootProjectConfiguration"); + t.context.getLibraryMetadataStub = sinon.stub(); + t.context.getFrameworkResolverStub = sinon.stub(utils, "getFrameworkResolver").returns(function Resolver() { + return { + getLibraryMetadata: t.context.getLibraryMetadataStub + }; + }); t.context.updateYamlStub = sinon.stub(); mock("../../../lib/framework/updateYaml", t.context.updateYamlStub); @@ -26,54 +38,46 @@ test.afterEach.always(() => { }); test.serial("Add without existing libraries in config", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5GetLibraryMetadataStub, updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, getFrameworkResolverStub, + getLibraryMetadataStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5", - version: "1.76.0" - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Openui5GetLibraryMetadataStub.resolves(); + getRootProjectConfigurationStub.resolves(project); + getLibraryMetadataStub.resolves(); const result = await addFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{name: "sap.ui.lib1"}] }); t.deepEqual(result, {yamlUpdated: true}, "yamlUpdated should be true"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "OpenUI5", + "getFrameworkResolver called with expected framework"); - t.is(Openui5GetLibraryMetadataStub.callCount, 1, "Openui5Resolver.getLibraryMetadata should be called once"); - t.deepEqual(Openui5GetLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], - "Openui5Resolver.getLibraryMetadata should be called with expected args"); + t.is(getLibraryMetadataStub.callCount, 1, "Resolver.getLibraryMetadata should be called once"); + t.deepEqual(getLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], + "Resolver.getLibraryMetadata should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ + configPathOverride: undefined, project, data: { framework: {libraries: [{name: "sap.ui.lib1"}]} @@ -82,61 +86,53 @@ test.serial("Add without existing libraries in config", async (t) => { }); test.serial("Add with existing libraries in config", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5GetLibraryMetadataStub, updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, getFrameworkResolverStub, + getLibraryMetadataStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5", - version: "1.76.0", - libraries: [{ - name: "sap.ui.lib2" - }, { - name: "sap.ui.lib1" - }] - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0", + frameworkLibraries: [{ + name: "sap.ui.lib2" + }, { + name: "sap.ui.lib1" + }] + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Openui5GetLibraryMetadataStub.resolves(); + getRootProjectConfigurationStub.resolves(project); + getLibraryMetadataStub.resolves(); const result = await addFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{name: "sap.ui.lib1"}, {name: "sap.ui.lib3"}] }); t.deepEqual(result, {yamlUpdated: true}, "yamlUpdated should be true"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "OpenUI5", + "getFrameworkResolver called with expected framework"); - t.is(Openui5GetLibraryMetadataStub.callCount, 2, "Openui5Resolver.getLibraryMetadata should be called twice"); - t.deepEqual(Openui5GetLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], - "Openui5Resolver.getLibraryMetadata should be called with expected args on first call"); - t.deepEqual(Openui5GetLibraryMetadataStub.getCall(1).args, ["sap.ui.lib3"], - "Openui5Resolver.getLibraryMetadata should be called with expected args on second call"); + t.is(getLibraryMetadataStub.callCount, 2, "Resolver.getLibraryMetadata should be called twice"); + t.deepEqual(getLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], + "Resolver.getLibraryMetadata should be called with expected args on first call"); + t.deepEqual(getLibraryMetadataStub.getCall(1).args, ["sap.ui.lib3"], + "Resolver.getLibraryMetadata should be called with expected args on second call"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ + configPathOverride: undefined, project, data: { framework: { @@ -151,63 +147,55 @@ test.serial("Add with existing libraries in config", async (t) => { }); test.serial("Add optional with existing libraries in config", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5GetLibraryMetadataStub, updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, getFrameworkResolverStub, + getLibraryMetadataStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5", - version: "1.76.0", - libraries: [{ - name: "sap.ui.lib2", - development: true - }, { - name: "sap.ui.lib1", - development: true - }] - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0", + frameworkLibraries: [{ + name: "sap.ui.lib2", + development: true + }, { + name: "sap.ui.lib1", + development: true + }] + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); - Openui5GetLibraryMetadataStub.resolves(); + getRootProjectConfigurationStub.resolves(project); + getLibraryMetadataStub.resolves(); const result = await addFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{name: "sap.ui.lib1", optional: true}, {name: "sap.ui.lib3", optional: true}] }); t.deepEqual(result, {yamlUpdated: true}, "yamlUpdated should be true"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "OpenUI5", + "getFrameworkResolver called with expected framework"); - t.is(Openui5GetLibraryMetadataStub.callCount, 2, "Openui5Resolver.getLibraryMetadata should be called twice"); - t.deepEqual(Openui5GetLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], - "Openui5Resolver.getLibraryMetadata should be called with expected args on first call"); - t.deepEqual(Openui5GetLibraryMetadataStub.getCall(1).args, ["sap.ui.lib3"], - "Openui5Resolver.getLibraryMetadata should be called with expected args on second call"); + t.is(getLibraryMetadataStub.callCount, 2, "Resolver.getLibraryMetadata should be called twice"); + t.deepEqual(getLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], + "Resolver.getLibraryMetadata should be called with expected args on first call"); + t.deepEqual(getLibraryMetadataStub.getCall(1).args, ["sap.ui.lib3"], + "Resolver.getLibraryMetadata should be called with expected args on second call"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ + configPathOverride: undefined, project, data: { framework: { @@ -222,243 +210,201 @@ test.serial("Add optional with existing libraries in config", async (t) => { }); test.serial("Add with specVersion 1.0", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5GetLibraryMetadataStub, Sapui5GetLibraryMetadataStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, getFrameworkResolverStub, getLibraryMetadataStub, + updateYamlStub + } = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "1.0", - metadata: { - name: "my-project" - } - }; + name: "my-project", + path: "my-project-path", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(addFramework({ - normalizerOptions + projectGraphOptions })); t.is(error.message, `ui5 add command requires specVersion "2.0" or higher. Project my-project uses specVersion "1.0"`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); - t.is(Openui5GetLibraryMetadataStub.callCount, 0, "Openui5Resolver.getLibraryMetadata should not be called"); - t.is(Sapui5GetLibraryMetadataStub.callCount, 0, "Sapui5Resolver.getLibraryMetadata should not be called"); + t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); + t.is(getLibraryMetadataStub.callCount, 0, "Resolver.getLibraryMetadata should not be called"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Add without framework configuration", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5GetLibraryMetadataStub, Sapui5GetLibraryMetadataStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, getFrameworkResolverStub, getLibraryMetadataStub, + updateYamlStub + } = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - } - }; + name: "my-project", + path: "my-project-path", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(addFramework({ - normalizerOptions + projectGraphOptions })); t.is(error.message, `Project my-project is missing a framework configuration. ` + `Please use "ui5 use" to configure a framework and version.`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); - t.is(Openui5GetLibraryMetadataStub.callCount, 0, "Openui5Resolver.getLibraryMetadata should not be called"); - t.is(Sapui5GetLibraryMetadataStub.callCount, 0, "Sapui5Resolver.getLibraryMetadata should not be called"); + t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); + t.is(getLibraryMetadataStub.callCount, 0, "Resolver.getLibraryMetadata should not be called"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Add without framework version configuration", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Openui5GetLibraryMetadataStub, Sapui5GetLibraryMetadataStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, getFrameworkResolverStub, getLibraryMetadataStub, + updateYamlStub + } = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5" - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "OpenUI5" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(addFramework({ - normalizerOptions + projectGraphOptions })); t.is(error.message, `Project my-project does not define a framework version configuration. ` + `Please use "ui5 use" to configure a version.`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); - t.is(Openui5GetLibraryMetadataStub.callCount, 0, "Openui5Resolver.getLibraryMetadata should not be called"); - t.is(Sapui5GetLibraryMetadataStub.callCount, 0, "Sapui5Resolver.getLibraryMetadata should not be called"); + t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); + t.is(getLibraryMetadataStub.callCount, 0, "Resolver.getLibraryMetadata should not be called"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Add with failing library metadata call", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Sapui5GetLibraryMetadataStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, getFrameworkResolverStub, getLibraryMetadataStub, + updateYamlStub + } = t.context; - Sapui5GetLibraryMetadataStub.rejects(new Error("Failed to load library sap.ui.lib1")); + getLibraryMetadataStub.rejects(new Error("Failed to load library sap.ui.lib1")); - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "SAPUI5", - version: "1.76.0" - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "SAPUI5", + frameworkVersion: "1.76.0" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(addFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{name: "sap.ui.lib1"}] })); t.is(error.message, `Failed to find SAPUI5 framework library sap.ui.lib1: Failed to load library sap.ui.lib1`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "SAPUI5", + "getFrameworkResolver called with expected framework"); - t.is(Sapui5GetLibraryMetadataStub.callCount, 1, "Sapui5Resolver.getLibraryMetadata should be called once"); - t.deepEqual(Sapui5GetLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], - "Sapui5Resolver.getLibraryMetadata should be called with expected args on first call"); + t.is(getLibraryMetadataStub.callCount, 1, "Resolver.getLibraryMetadata should be called once"); + t.deepEqual(getLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], + "Resolver.getLibraryMetadata should be called with expected args on first call"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Add with failing YAML update", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Sapui5GetLibraryMetadataStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, getFrameworkResolverStub, getLibraryMetadataStub, + updateYamlStub + } = t.context; const yamlUpdateError = new Error("Failed to update YAML file"); yamlUpdateError.name = "FrameworkUpdateYamlFailed"; updateYamlStub.rejects(yamlUpdateError); - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "SAPUI5", - version: "1.76.0" - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "SAPUI5", + frameworkVersion: "1.76.0" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const result = await addFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{name: "sap.ui.lib1"}] }); t.deepEqual(result, {yamlUpdated: false}, "yamlUpdated should be false"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "SAPUI5", + "getFrameworkResolver called with expected framework"); - t.is(Sapui5GetLibraryMetadataStub.callCount, 1, "Sapui5Resolver.getLibraryMetadata should be called once"); - t.deepEqual(Sapui5GetLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], - "Sapui5Resolver.getLibraryMetadata should be called with expected args on first call"); + t.is(getLibraryMetadataStub.callCount, 1, "Resolver.getLibraryMetadata should be called once"); + t.deepEqual(getLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], + "Resolver.getLibraryMetadata should be called with expected args on first call"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ + configPathOverride: undefined, project, data: { framework: { @@ -469,55 +415,49 @@ test.serial("Add with failing YAML update", async (t) => { }); test.serial("Add with failing YAML update (unexpected error)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - Sapui5GetLibraryMetadataStub, updateYamlStub} = t.context; + const { + getRootProjectConfigurationStub, getFrameworkResolverStub, getLibraryMetadataStub, + updateYamlStub + } = t.context; updateYamlStub.rejects(new Error("Some unexpected error")); - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "SAPUI5", - version: "1.76.0" - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "SAPUI5", + frameworkVersion: "1.76.0" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(addFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{name: "sap.ui.lib1"}] })); t.is(error.message, `Some unexpected error`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "SAPUI5", + "getFrameworkResolver called with expected framework"); - t.is(Sapui5GetLibraryMetadataStub.callCount, 1, "Sapui5Resolver.getLibraryMetadata should be called once"); - t.deepEqual(Sapui5GetLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], - "Sapui5Resolver.getLibraryMetadata should be called with expected args on first call"); + t.is(getLibraryMetadataStub.callCount, 1, "Resolver.getLibraryMetadata should be called once"); + t.deepEqual(getLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], + "Resolver.getLibraryMetadata should be called with expected args on first call"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ + configPathOverride: undefined, project, data: { framework: { @@ -527,38 +467,83 @@ test.serial("Add with failing YAML update (unexpected error)", async (t) => { }], "updateYaml should be called with expected args"); }); - test.serial("Add should not modify input parameters", async (t) => { - const {generateDependencyTreeStub, processTreeStub} = t.context; + const {getRootProjectConfigurationStub, getFrameworkResolverStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "SAPUI5", - version: "1.76.0", - libraries: [{"name": "sap.ui.lib1"}] - } - }; + name: "my-project", + path: "my-project-path", + frameworkName: "SAPUI5", + frameworkVersion: "1.76.0", + frameworkLibraries: [{"name": "sap.ui.lib1"}] + }); const libraries = [{name: "sap.ui.lib2"}]; - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); await addFramework({ - normalizerOptions, + projectGraphOptions, libraries }); + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "SAPUI5", + "getFrameworkResolver called with expected framework"); + t.deepEqual(libraries, [{name: "sap.ui.lib2"}], "libraries array should not be changed"); }); + +test.serial("Add with projectGraphOptions.config", async (t) => { + const { + getRootProjectConfigurationStub, getFrameworkResolverStub, getLibraryMetadataStub, + updateYamlStub + } = t.context; + + const projectGraphOptions = { + config: "/path/to/ui5.yaml" + }; + + const project = createMockProject({ + specVersion: "2.0", + name: "my-project", + path: "my-project-path", + frameworkName: "SAPUI5", + frameworkVersion: "1.76.0" + }); + + getRootProjectConfigurationStub.resolves(project); + + const libraries = [{name: "sap.ui.lib1"}]; + await addFramework({ + projectGraphOptions, + libraries + }); + + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{config: "/path/to/ui5.yaml"}], + "generateProjectGraph should be called with expected args"); + + t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); + t.deepEqual(getFrameworkResolverStub.getCall(0).args[0], "SAPUI5", + "getFrameworkResolver called with expected framework"); + t.is(getLibraryMetadataStub.callCount, 1, "Resolver.getLibraryMetadata should be called once"); + t.deepEqual(getLibraryMetadataStub.getCall(0).args, ["sap.ui.lib1"], + "Resolver.getLibraryMetadata should be called with expected args on first call"); + t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); + + t.deepEqual(updateYamlStub.getCall(0).args, [{ + project, + configPathOverride: "/path/to/ui5.yaml", + data: { + framework: { + libraries: [{name: "sap.ui.lib1"}] + } + } + }], "updateYaml should be called with expected args"); +}); diff --git a/test/lib/framework/remove.js b/test/lib/framework/remove.js index 0af49357..7b6e2ef5 100644 --- a/test/lib/framework/remove.js +++ b/test/lib/framework/remove.js @@ -3,13 +3,23 @@ const sinon = require("sinon"); const mock = require("mock-require"); const log = require("@ui5/logger"); -const ui5Project = require("@ui5/project"); +const utils = require("../../../lib/framework/utils"); let removeFramework; +function createMockProject(attr) { + return { + getName: () => attr.name, + getSpecVersion: () => attr.specVersion, + getPath: () => attr.path, + getFrameworkName: () => attr.frameworkName, + getFrameworkVersion: () => attr.frameworkVersion, + getFrameworkDependencies: () => attr.frameworkLibraries || [], + }; +} + test.beforeEach((t) => { - t.context.generateDependencyTreeStub = sinon.stub(ui5Project.normalizer, "generateDependencyTree"); - t.context.processTreeStub = sinon.stub(ui5Project.projectPreprocessor, "processTree"); + t.context.getRootProjectConfigurationStub = sinon.stub(utils, "getRootProjectConfiguration"); t.context.updateYamlStub = sinon.stub(); @@ -30,95 +40,71 @@ test.afterEach.always(() => { }); test.serial("Remove with existing libraries in config", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5", - version: "1.76.0", - libraries: [{ - name: "sap.ui.lib2" - }, { - name: "sap.ui.lib1" - }] - } - }; + name: "my-project", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0", + frameworkLibraries: [{ + name: "sap.ui.lib2" + }, { + name: "sap.ui.lib1" + }] + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const result = await removeFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{name: "sap.ui.lib1"}] }); t.deepEqual(result, {yamlUpdated: true}, "yamlUpdated should be true"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: {libraries: [{name: "sap.ui.lib2"}]} } }], "updateYaml should be called with expected args"); }); - test.serial("Remove with 2 existing libraries in config", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5", - version: "1.76.0", - libraries: [{ - name: "sap.ui.lib2" - }, { - name: "sap.ui.lib1" - }] - } - }; + name: "my-project", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0", + frameworkLibraries: [{ + name: "sap.ui.lib2" + }, { + name: "sap.ui.lib1" + }] + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const result = await removeFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{ name: "sap.ui.lib1" }, { @@ -128,19 +114,14 @@ test.serial("Remove with 2 existing libraries in config", async (t) => { t.deepEqual(result, {yamlUpdated: true}, "yamlUpdated should be true"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: {libraries: []} } @@ -149,37 +130,28 @@ test.serial("Remove with 2 existing libraries in config", async (t) => { test.serial("Remove with non-existing library in config", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - updateYamlStub, logWarnStub} = t.context; + const {getRootProjectConfigurationStub, updateYamlStub, logWarnStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5", - version: "1.76.0", - libraries: [{ - name: "sap.ui.lib1" - }, { - name: "sap.ui.lib2" - }] - } - }; + name: "my-project", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0", + frameworkLibraries: [{ + name: "sap.ui.lib1" + }, { + name: "sap.ui.lib2" + }] + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); await removeFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{ name: "sap.ui.nonexisting" }] @@ -190,19 +162,14 @@ test.serial("Remove with non-existing library in config", async (t) => { t.deepEqual(logWarnStub.getCall(0).args, [`Failed to remove framework library sap.ui.nonexisting from project my-project because it is not present.`]); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { libraries: [{ @@ -216,186 +183,131 @@ test.serial("Remove with non-existing library in config", async (t) => { }); test.serial("Remove with specVersion 1.0", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "1.0", - metadata: { - name: "my-project" - } - }; + name: "my-project", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(removeFramework({ - normalizerOptions + projectGraphOptions })); t.is(error.message, `ui5 remove command requires specVersion "2.0" or higher. Project my-project uses specVersion "1.0"`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Remove without framework configuration", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - } - }; + name: "my-project", + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(removeFramework({ - normalizerOptions + projectGraphOptions })); t.is(error.message, `Project my-project is missing a framework configuration. ` + `Please use "ui5 use" to configure a framework and version.`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Remove without framework version configuration", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, updateYamlStub} = t.context; - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5" - } - }; + name: "my-project", + frameworkName: "OpenUI5" + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(removeFramework({ - normalizerOptions + projectGraphOptions })); t.is(error.message, `Project my-project does not define a framework version configuration. ` + `Please use "ui5 use" to configure a version.`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); t.is(updateYamlStub.callCount, 0, "updateYaml should not be called"); }); test.serial("Remove with failing YAML update", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, updateYamlStub} = t.context; const yamlUpdateError = new Error("Failed to update YAML file"); yamlUpdateError.name = "FrameworkUpdateYamlFailed"; updateYamlStub.rejects(yamlUpdateError); - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5", - version: "1.76.0", - libraries: [{ - name: "sap.ui.lib2" - }, { - name: "sap.ui.lib1" - }] - } - }; + name: "my-project", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0", + frameworkLibraries: [{ + name: "sap.ui.lib2" + }, { + name: "sap.ui.lib1" + }] + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const result = await removeFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{name: "sap.ui.lib1"}] }); t.deepEqual(result, {yamlUpdated: false}, "yamlUpdated should be false"); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { libraries: [{name: "sap.ui.lib2"}] @@ -405,57 +317,43 @@ test.serial("Remove with failing YAML update", async (t) => { }); test.serial("Remove with failing YAML update (unexpected error)", async (t) => { - const {generateDependencyTreeStub, processTreeStub, - updateYamlStub} = t.context; + const {getRootProjectConfigurationStub, updateYamlStub} = t.context; updateYamlStub.rejects(new Error("Some unexpected error")); - const normalizerOptions = { - "fakeNormalizerOption": true + const projectGraphOptions = { + fakeProjectGraphOptions: true }; - const tree = { - dependencies: [{id: "fake-dependency"}] - }; - const project = { + const project = createMockProject({ specVersion: "2.0", - metadata: { - name: "my-project" - }, - framework: { - name: "OpenUI5", - version: "1.76.0", - libraries: [{ - name: "sap.ui.lib2" - }, { - name: "sap.ui.lib1" - }] - } - }; + name: "my-project", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0", + frameworkLibraries: [{ + name: "sap.ui.lib2" + }, { + name: "sap.ui.lib1" + }] + }); - generateDependencyTreeStub.resolves(tree); - processTreeStub.resolves(project); + getRootProjectConfigurationStub.resolves(project); const error = await t.throwsAsync(removeFramework({ - normalizerOptions, + projectGraphOptions, libraries: [{name: "sap.ui.lib1"}] })); t.is(error.message, `Some unexpected error`); - t.is(generateDependencyTreeStub.callCount, 1, "normalizer.generateDependencyTree should be called once"); - t.deepEqual(generateDependencyTreeStub.getCall(0).args, [{"fakeNormalizerOption": true}], - "normalizer.generateDependencyTree should be called with expected args"); - - t.is(processTreeStub.callCount, 1, "projectPreprocessor.processTree should be called once"); - t.deepEqual(processTreeStub.getCall(0).args, [{ - dependencies: [] - }], - "projectPreprocessor.processTree should be called with expected args"); + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], + "generateProjectGraph should be called with expected args"); t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { libraries: [{name: "sap.ui.lib2"}] @@ -463,3 +361,46 @@ test.serial("Remove with failing YAML update (unexpected error)", async (t) => { } }], "updateYaml should be called with expected args"); }); + +test.serial("Remove with projectGraphOptions.config", async (t) => { + const {getRootProjectConfigurationStub, updateYamlStub} = t.context; + + const projectGraphOptions = { + config: "/path/to/ui5.yaml" + }; + + const project = createMockProject({ + specVersion: "2.0", + name: "my-project", + frameworkName: "OpenUI5", + frameworkVersion: "1.76.0", + frameworkLibraries: [{ + name: "sap.ui.lib2" + }, { + name: "sap.ui.lib1" + }] + }); + + getRootProjectConfigurationStub.resolves(project); + + + const result = await removeFramework({ + projectGraphOptions, + libraries: [{name: "sap.ui.lib1"}] + }); + + t.deepEqual(result, {yamlUpdated: true}, "yamlUpdated should be true"); + + t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{config: "/path/to/ui5.yaml"}], + "generateProjectGraph should be called with expected args"); + + t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); + t.deepEqual(updateYamlStub.getCall(0).args, [{ + project, + configPathOverride: "/path/to/ui5.yaml", + data: { + framework: {libraries: [{name: "sap.ui.lib2"}]} + } + }], "updateYaml should be called with expected args"); +}); diff --git a/test/lib/framework/use.js b/test/lib/framework/use.js index 983d3bdc..b7c982c6 100644 --- a/test/lib/framework/use.js +++ b/test/lib/framework/use.js @@ -40,7 +40,7 @@ test.serial("Use with name and version (OpenUI5)", async (t) => { } = t.context; const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -67,7 +67,7 @@ test.serial("Use with name and version (OpenUI5)", async (t) => { }, "useFramework should return expected result object"); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); @@ -80,6 +80,7 @@ test.serial("Use with name and version (OpenUI5)", async (t) => { t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { name: "OpenUI5", @@ -96,7 +97,7 @@ test.serial("Use with name and version (SAPUI5)", async (t) => { } = t.context; const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -123,7 +124,7 @@ test.serial("Use with name and version (SAPUI5)", async (t) => { }, "useFramework should return expected result object"); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); @@ -136,6 +137,7 @@ test.serial("Use with name and version (SAPUI5)", async (t) => { t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { name: "SAPUI5", @@ -152,7 +154,7 @@ test.serial("Use with version only (OpenUI5)", async (t) => { } = t.context; const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -180,7 +182,7 @@ test.serial("Use with version only (OpenUI5)", async (t) => { }, "useFramework should return expected result object"); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); @@ -193,6 +195,7 @@ test.serial("Use with version only (OpenUI5)", async (t) => { t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { name: "OpenUI5", @@ -209,7 +212,7 @@ test.serial("Use with version only (SAPUI5)", async (t) => { } = t.context; const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -237,7 +240,7 @@ test.serial("Use with version only (SAPUI5)", async (t) => { }, "useFramework should return expected result object"); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); @@ -250,6 +253,7 @@ test.serial("Use with version only (SAPUI5)", async (t) => { t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { name: "SAPUI5", @@ -266,7 +270,7 @@ test.serial("Use with name only (no existing framework configuration)", async (t } = t.context; const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -292,7 +296,7 @@ test.serial("Use with name only (no existing framework configuration)", async (t }, "useFramework should return expected result object"); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); @@ -301,6 +305,7 @@ test.serial("Use with name only (no existing framework configuration)", async (t t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { name: "SAPUI5" @@ -316,7 +321,7 @@ test.serial("Use with name only (existing framework configuration)", async (t) = } = t.context; const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -345,7 +350,7 @@ test.serial("Use with name only (existing framework configuration)", async (t) = }, "useFramework should return expected result object"); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); @@ -358,6 +363,7 @@ test.serial("Use with name only (existing framework configuration)", async (t) = t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { name: "SAPUI5", @@ -414,6 +420,7 @@ test.serial("Use with projectGraphOptions.config", async (t) => { t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: "/path/to/ui5.yaml", data: { framework: { name: "SAPUI5", @@ -428,7 +435,7 @@ test.serial("Use with version only (no framework name)", async (t) => { resolveVersionStub, getFrameworkResolverStub, updateYamlStub} = t.context; const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -450,7 +457,7 @@ test.serial("Use with version only (no framework name)", async (t) => { t.is(error.message, "No framework configuration defined. Make sure to also provide the framework name."); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); @@ -464,7 +471,7 @@ test.serial("Use with invalid name", async (t) => { resolveVersionStub, getFrameworkResolverStub, updateYamlStub} = t.context; const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -486,7 +493,7 @@ test.serial("Use with invalid name", async (t) => { t.is(error.message, "Invalid framework name: Foo"); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); @@ -500,7 +507,7 @@ test.serial("Use with specVersion 1.0", async (t) => { resolveVersionStub, getFrameworkResolverStub, updateYamlStub} = t.context; const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -523,7 +530,7 @@ test.serial("Use with specVersion 1.0", async (t) => { `ui5 use command requires specVersion "2.0" or higher. Project my-project uses specVersion "1.0"`); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 0, "getFrameworkResolverStub should not be called"); @@ -543,7 +550,7 @@ test.serial("Use with name and version (YAML update fails)", async (t) => { updateYamlStub.rejects(yamlUpdateError); const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -570,7 +577,7 @@ test.serial("Use with name and version (YAML update fails)", async (t) => { }, "useFramework should return expected result object"); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); @@ -583,6 +590,7 @@ test.serial("Use with name and version (YAML update fails)", async (t) => { t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { name: "OpenUI5", @@ -601,7 +609,7 @@ test.serial("Use with name and version (YAML update fails with unexpected error) updateYamlStub.rejects(new Error("Some unexpected error")); const projectGraphOptions = { - "fakeProjectGraphOptions": true + fakeProjectGraphOptions: true }; const project = createMockProject({ @@ -624,7 +632,7 @@ test.serial("Use with name and version (YAML update fails with unexpected error) t.is(error.message, "Some unexpected error"); t.is(getRootProjectConfigurationStub.callCount, 1, "generateProjectGraph should be called once"); - t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{"fakeProjectGraphOptions": true}], + t.deepEqual(getRootProjectConfigurationStub.getCall(0).args, [{fakeProjectGraphOptions: true}], "generateProjectGraph should be called with expected args"); t.is(getFrameworkResolverStub.callCount, 1, "getFrameworkResolverStub should be called once"); @@ -637,6 +645,7 @@ test.serial("Use with name and version (YAML update fails with unexpected error) t.is(updateYamlStub.callCount, 1, "updateYaml should be called once"); t.deepEqual(updateYamlStub.getCall(0).args, [{ project, + configPathOverride: undefined, data: { framework: { name: "OpenUI5", From 6cf7592460551fdb117694425bb7184ecc7fa7e9 Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Thu, 2 Jun 2022 08:15:20 +0200 Subject: [PATCH 15/25] Start updating unit tests for commands/build --- test/lib/cli/commands/build.js | 201 +++++++++++++++++---------------- 1 file changed, 103 insertions(+), 98 deletions(-) diff --git a/test/lib/cli/commands/build.js b/test/lib/cli/commands/build.js index db097333..3f9ba1a0 100644 --- a/test/lib/cli/commands/build.js +++ b/test/lib/cli/commands/build.js @@ -1,11 +1,6 @@ const test = require("ava"); const sinon = require("sinon"); const mock = require("mock-require"); -const normalizer = require("@ui5/project").normalizer; -const builder = require("@ui5/builder").builder; -const logger = require("@ui5/logger"); -let normalizerStub = null; -let builderStub = null; const args = { _: [], @@ -14,37 +9,84 @@ const args = { t8r: "npm", translator: "npm" }; -const defaultBuilderArgs = { - tree: { - metadata: { - name: "Sample" + +function getDefaultArgv() { + return { + "_": ["build"], + "loglevel": "info", + "log-level": "info", + "logLevel": "info", + "x-perf": false, + "xPerf": false, + "include-all-dependencies": false, + "all": false, + "a": false, + "includeAllDependencies": false, + "dest": "./dist", + "clean-dest": false, + "cleanDest": false, + "experimental-css-variables": false, + "experimentalCssVariables": false, + "$0": "ui5" + }; +} + +function getDefaultBuilderArgs() { + return { + destPath: "./dist", + cleanDest: false, + complexDependencyIncludes: { + includeAllDependencies: false, + includeDependency: undefined, + includeDependencyRegExp: undefined, + includeDependencyTree: undefined, + excludeDependency: undefined, + excludeDependencyRegExp: undefined, + excludeDependencyTree: undefined, + defaultIncludeDependency: undefined, + defaultIncludeDependencyRegExp: undefined, + defaultIncludeDependencyTree: undefined }, - dependencies: [] - }, - destPath: "./dist", - buildDependencies: undefined, - includedDependencies: [], - excludedDependencies: [], - cleanDest: undefined, - dev: false, - selfContained: false, - jsdoc: false, - devExcludeProject: undefined, - includedTasks: undefined, - excludedTasks: undefined, - cssVariables: undefined -}; + archive: false, + selfContained: false, + jsdoc: false, + includedTasks: undefined, + excludedTasks: undefined, + cssVariables: false + }; +} test.beforeEach((t) => { - normalizerStub = sinon.stub(normalizer, "generateProjectTree"); - builderStub = sinon.stub(builder, "build").returns(Promise.resolve()); - sinon.stub(logger, "setShowProgress"); - t.context.log = { - warn: sinon.stub() + const project = require("@ui5/project"); + + t.context.argv = getDefaultArgv(); + t.context.expectedBuilderArgs = getDefaultBuilderArgs(); + + sinon.stub(project.generateProjectGraph, "usingStaticFile").resolves(); + sinon.stub(project.generateProjectGraph, "usingNodePackageDependencies").resolves(); + t.context.generateProjectGraph = project.generateProjectGraph; + + t.context.builder = sinon.stub().resolves(); + + // NOTE: project.builder needs to be re-defined via defineProperty. + // Sinon is unable to create a stub for the property because of the lazy-loading + // mechanism in @ui5/project index.js + Object.defineProperty(project, "builder", { + get() { + return t.context.builder; + } + }); + + // Create basic mocking objects + const fakeGraph = { + getRoot: sinon.stub().returns({ + getBuilderSettings: sinon.stub().returns(undefined) + }) }; - sinon.stub(logger, "getLogger").withArgs("cli:utils:buildHelper").returns(t.context.log); - mock.reRequire("../../../../lib/utils/buildHelper"); // rerequire buildHelper to ensure usage of stubbed logger - t.context.build = mock.reRequire("../../../../lib/cli/commands/build"); + t.context.generateProjectGraph.usingNodePackageDependencies.resolves(fakeGraph); + t.context.expectedBuilderArgs.graph = fakeGraph; + + t.context.build = require("../../../../lib/cli/commands/build"); }); test.afterEach.always((t) => { @@ -53,85 +95,48 @@ test.afterEach.always((t) => { }); test.serial("ui5 build (default) ", async (t) => { - normalizerStub.resolves({ - metadata: { - name: "Sample" - }, - dependencies: [] - }); - args._ = ["build"]; - await t.context.build.handler(args); - t.deepEqual(builderStub.getCall(0).args[0], defaultBuilderArgs, "default build triggered with expected arguments"); -}); + const {build, argv, builder, expectedBuilderArgs} = t.context; -test.serial("ui5 build dev", async (t) => { - normalizerStub.resolves({ - metadata: { - name: "Sample" - }, - dependencies: [] - }); - args._ = ["build", "dev"]; - await t.context.build.handler(args); - t.deepEqual( - builderStub.getCall(0).args[0], - Object.assign({}, defaultBuilderArgs, {dev: true}), - "Dev build called with expected arguments" - ); + await build.handler(argv); + + t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "default build triggered with expected arguments"); }); test.serial("ui5 build self-contained", async (t) => { - normalizerStub.resolves({ - metadata: { - name: "Sample" - }, - dependencies: [] - }); - args._ = ["build", "self-contained"]; - await t.context.build.handler(args); - t.deepEqual( - builderStub.getCall(0).args[0], - Object.assign({}, defaultBuilderArgs, {selfContained: true}), - "Self-contained build called with expected arguments" - ); + const {build, argv, builder, expectedBuilderArgs} = t.context; + + argv._.push("self-contained"); + + await build.handler(argv); + + expectedBuilderArgs.selfContained = true; + t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "Self-contained build called with expected arguments"); }); test.serial("ui5 build jsdoc", async (t) => { - normalizerStub.resolves({ - metadata: { - name: "Sample" - }, - dependencies: [] - }); - args._ = ["build", "jsdoc"]; - await t.context.build.handler(args); - t.deepEqual( - builderStub.getCall(0).args[0], - Object.assign({}, defaultBuilderArgs, {jsdoc: true}), - "JSDoc build called with expected arguments" - ); + const {build, argv, builder, expectedBuilderArgs} = t.context; + + argv._.push("jsdoc"); + + await build.handler(argv); + + expectedBuilderArgs.jsdoc = true; + t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "JSDoc build called with expected arguments"); }); test.serial("ui5 build --framework-version 1.99", async (t) => { - normalizerStub.resolves({ - metadata: { - name: "Sample" - }, - dependencies: [] - }); + const {build, argv, generateProjectGraph} = t.context; + + argv.frameworkVersion = "1.99.0"; + + await build.handler(argv); - args._ = ["build"]; - args.frameworkVersion = "1.99.0"; - await t.context.build.handler(args); t.deepEqual( - normalizerStub.getCall(0).args[0], + generateProjectGraph.usingNodePackageDependencies.getCall(0).args[0], { - configPath: undefined, - translatorName: "npm", - frameworkOptions: { - versionOverride: "1.99.0" - } - }, "generateProjectTree got called with expected arguments" + rootConfigPath: undefined, + versionOverride: "1.99.0" + }, "generateProjectGraph.usingNodePackageDependencies got called with expected arguments" ); }); From 860a21714ff9183e64c71c25fcbdf8e7f6d7d567 Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Thu, 2 Jun 2022 11:23:59 +0200 Subject: [PATCH 16/25] Update all commands/build tests --- test/lib/cli/commands/build.js | 428 +++++---------------------------- 1 file changed, 64 insertions(+), 364 deletions(-) diff --git a/test/lib/cli/commands/build.js b/test/lib/cli/commands/build.js index 3f9ba1a0..8325c079 100644 --- a/test/lib/cli/commands/build.js +++ b/test/lib/cli/commands/build.js @@ -2,15 +2,8 @@ const test = require("ava"); const sinon = require("sinon"); const mock = require("mock-require"); -const args = { - _: [], - dest: "./dist", - loglevel: "info", - t8r: "npm", - translator: "npm" -}; - function getDefaultArgv() { + // This has been taken from the actual argv object yargs provides return { "_": ["build"], "loglevel": "info", @@ -78,9 +71,10 @@ test.beforeEach((t) => { }); // Create basic mocking objects + t.context.getBuilderSettings = sinon.stub().returns(undefined); const fakeGraph = { getRoot: sinon.stub().returns({ - getBuilderSettings: sinon.stub().returns(undefined) + getBuilderSettings: t.context.getBuilderSettings }) }; t.context.generateProjectGraph.usingNodePackageDependencies.resolves(fakeGraph); @@ -124,6 +118,17 @@ 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 archive", async (t) => { + const {build, argv, builder, expectedBuilderArgs} = t.context; + + argv._.push("archive"); + + await build.handler(argv); + + expectedBuilderArgs.archive = true; + t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "archive build called with expected arguments"); +}); + test.serial("ui5 build --framework-version 1.99", async (t) => { const {build, argv, generateProjectGraph} = t.context; @@ -140,374 +145,69 @@ test.serial("ui5 build --framework-version 1.99", async (t) => { ); }); -async function assertIncludeDependency(t, { - treeDeps, includeDeps, includeDepsRegExp, includeDepsTree, excludeDeps, excludeDepsRegExp, excludeDepsTree, - expectedBuilderArgs -}) { - const tree = Object.assign({metadata: {name: "Sample"}}, treeDeps); - const _args = Object.assign({}, args); // copy default args to ensure it is not modified - normalizerStub.resolves(tree); - _args._ = ["build"]; - _args["include-dependency"] = includeDeps; - _args["include-dependency-regexp"] = includeDepsRegExp; - _args["include-dependency-tree"] = includeDepsTree; - _args["exclude-dependency"] = excludeDeps; - _args["exclude-dependency-regexp"] = excludeDepsRegExp; - _args["exclude-dependency-tree"] = excludeDepsTree; - await t.context.build.handler(_args); - t.deepEqual(builderStub.getCall(0).args[0], - Object.assign({}, defaultBuilderArgs, {tree: tree}, expectedBuilderArgs), - "default build triggered with expected arguments"); -} +test.serial("ui5 build (Include/Exclude dependency options)", async (t) => { + const {build, argv, builder, expectedBuilderArgs} = t.context; -test.serial("ui5 build --include-dependency", async (t) => { - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [{ - metadata: { - name: "sap.ui.core" - }, - dependencies: [] - }] - }, - includeDeps: ["sap.ui.core"], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: ["sap.ui.core"], - excludedDependencies: ["*"] - } - }); -}); + argv["include-dependency"] = ["sap.ui.core"]; + argv["include-dependency-regexp"] = ["^sap.[mf]$"]; + argv["include-dependency-tree"] = ["a"]; + argv["exclude-dependency"] = ["sap.ui.layout"]; + argv["exclude-dependency-regexp"] = ["^b0$"]; + argv["exclude-dependency-tree"] = ["c1"]; -[{ - title: "no excludes", - excludeDepsRegExp: [], - excludeDepsTree: [], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: ["a", "b0", "b1", "c"], - excludedDependencies: ["*"] - } -}, { - title: "overridden by excludes", - excludeDepsRegExp: ["^b0$"], - excludeDepsTree: ["b1"], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: ["a"], - excludedDependencies: ["b0", "b1", "c", "*"] - } -}].forEach((fixture) => { - test.serial(`ui5 build (dependency included via ui5.yaml); ${fixture.title}`, async (t) => { - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [{ - metadata: { - name: "a" - }, - dependencies: [{ - metadata: { - name: "b0" - }, - dependencies: [] - }, { - metadata: { - name: "b1" - }, - dependencies: [{ - metadata: { - name: "c" - }, - dependencies: [] - }] - }] - }], - builder: { - settings: { - includeDependency: ["a"], - includeDependencyRegExp: ["^b0$"], - includeDependencyTree: ["b1"], - } - } - }, - excludeDepsRegExp: fixture.excludeDepsRegExp, - excludeDepsTree: fixture.excludeDepsTree, - expectedBuilderArgs: fixture.expectedBuilderArgs - }); - }); -}); + await build.handler(argv); -test.serial("ui5 build --include-dependency-regexp", async (t) => { - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [{ - metadata: { - name: "sap.ui.core" - }, - dependencies: [] - }, { - metadata: { - name: "sap.m" - }, - dependencies: [] - }, { - metadata: { - name: "sap.f" - }, - dependencies: [] - }] - }, - includeDepsRegExp: ["^sap.[mf]$"], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: ["sap.m", "sap.f"], - excludedDependencies: ["*"] - } - }); -}); + expectedBuilderArgs.complexDependencyIncludes = { + includeAllDependencies: false, + includeDependency: ["sap.ui.core"], + includeDependencyRegExp: ["^sap.[mf]$"], + includeDependencyTree: ["a"], + excludeDependency: ["sap.ui.layout"], + excludeDependencyRegExp: ["^b0$"], + excludeDependencyTree: ["c1"], + defaultIncludeDependency: undefined, + defaultIncludeDependencyRegExp: undefined, + defaultIncludeDependencyTree: undefined, + }; -test.serial("ui5 build --include-dependency-tree", async (t) => { - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [{ - metadata: { - name: "a" - }, - dependencies: [{ - metadata: { - name: "b0" - }, - dependencies: [] - }, { - metadata: { - name: "b1" - }, - dependencies: [{ - metadata: { - name: "c" - }, - dependencies: [] - }] - }] - }] - }, - includeDepsTree: ["a"], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: ["a", "b0", "b1", "c"], - excludedDependencies: ["*"] - } - }); + t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "default build triggered with expected arguments"); }); -test.serial("ui5 build include/exclude tree (two subtrees, sharing a transitive dependency)", async (t) => { - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [{ - metadata: { - name: "a0" - }, - dependencies: [{ - metadata: { - name: "b0" - }, - dependencies: [] - }, { - metadata: { - name: "b1" - }, - dependencies: [{ - metadata: { - name: "c" - }, - dependencies: [] - }] - }] - }, { - metadata: { - name: "a1" - }, - dependencies: [{ - metadata: { - name: "b0" - }, - dependencies: [] - }, { - metadata: { - name: "b1" - }, - dependencies: [{ - metadata: { - name: "c" - }, - dependencies: [] - }] - }] - }] - }, - includeDepsTree: ["a0"], - excludeDepsTree: ["a1"], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: ["a0", "b0", "b1", "c"], - excludedDependencies: ["a1", "*"] - } - }); -}); +test.serial("ui5 build (Include dependency via configuration)", async (t) => { + const {build, argv, builder, expectedBuilderArgs, getBuilderSettings} = t.context; -test.serial("ui5 build --include-dependency --include-dependency-tree (select a transitive dependency)", async (t) => { - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [{ - metadata: { - name: "a" - }, - dependencies: [{ - metadata: { - name: "b0" - }, - dependencies: [{ - metadata: { - name: "b0.c" - }, - dependencies: [] - }] - }, { - metadata: { - name: "b1" - }, - dependencies: [{ - metadata: { - name: "b1.c" - }, - dependencies: [] - }] - }] - }] - }, - includeDeps: ["b0"], - includeDepsTree: ["b1"], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: ["b0", "b1", "b1.c"], - excludedDependencies: ["*"] - } + getBuilderSettings.returns({ + includeDependency: ["a"], + includeDependencyRegExp: ["^b0$"], + includeDependencyTree: ["b1"], }); -}); -test.serial("ui5 build --include-dependency=* --exclude-dependency=sap.ui.core", async (t) => { - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [{ - metadata: { - name: "sap.ui.core" - }, - dependencies: [] - }] - }, - includeDeps: ["*"], - excludeDeps: ["sap.ui.core"], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: [], - excludedDependencies: ["sap.ui.core"] - } - }); -}); + await build.handler(argv); -test.serial("ui5 build --include-dependency-tree=a --exclude-dependency=a", async (t) => { - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [{ - metadata: { - name: "a" - }, - dependencies: [{ - metadata: { - name: "b0" - }, - dependencies: [] - }, { - metadata: { - name: "b1" - }, - dependencies: [] - }] - }] - }, - includeDepsTree: ["a"], - excludeDeps: ["a"], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: ["b0", "b1"], - excludedDependencies: ["a", "*"] - } - }); -}); + expectedBuilderArgs.complexDependencyIncludes = { + includeAllDependencies: false, + includeDependency: undefined, + includeDependencyRegExp: undefined, + includeDependencyTree: undefined, + excludeDependency: undefined, + excludeDependencyRegExp: undefined, + excludeDependencyTree: undefined, + defaultIncludeDependency: ["a"], + defaultIncludeDependencyRegExp: ["^b0$"], + defaultIncludeDependencyTree: ["b1"] + }; -test.serial("ui5 build --include-dependency-tree include/exclude tree has a lower priority", async (t) => { - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [{ - metadata: { - name: "a" - }, - dependencies: [{ - metadata: { - name: "b0" - }, - dependencies: [] - }, { - metadata: { - name: "b1" - }, - dependencies: [{ - metadata: { - name: "c" - }, - dependencies: [] - }] - }] - }] - }, - includeDepsTree: ["a"], - excludeDepsRegExp: ["^b.$"], - expectedBuilderArgs: { - buildDependencies: true, - includedDependencies: ["a", "c"], - excludedDependencies: ["b0", "b1", "*"] - } - }); + t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "default build triggered with expected arguments"); }); -test.serial("ui5 build --include-dependency (dependency not found)", async (t) => { - const {log} = t.context; - await assertIncludeDependency(t, { - treeDeps: { - dependencies: [] - }, - includeDeps: ["sap.ui.core"] - }); - t.is(log.warn.callCount, 1, "log.warn should be called once"); - t.deepEqual(log.warn.getCall(0).args, - ["Could not find dependency \"sap.ui.core\" for project Sample. Dependency filter is ignored"], - "logger.warn should be called with expected string"); -}); +test.serial("ui5 build --experimental-css-variables", async (t) => { + const {build, argv, builder, expectedBuilderArgs} = t.context; + argv["experimental-css-variables"] = true; -test.serial("ui5 build --experimental-css-variables", async (t) => { - normalizerStub.resolves({ - metadata: { - name: "Sample" - }, - dependencies: [] - }); - args._ = ["build"]; - args["experimental-css-variables"] = true; - await t.context.build.handler(args); - t.deepEqual( - builderStub.getCall(0).args[0], - Object.assign({}, defaultBuilderArgs, {cssVariables: true}), - "Build with activated CSS Variables is called with expected arguments" - ); + await build.handler(argv); + + expectedBuilderArgs.cssVariables = true; + t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, + "Build with activated CSS Variables is called with expected arguments"); }); From ab949665dba8f6067a4caabf376ed0e688842055 Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Thu, 2 Jun 2022 14:12:04 +0200 Subject: [PATCH 17/25] Start updating unit tests for commands/tree --- lib/cli/commands/tree.js | 2 +- test/lib/cli/commands/build.js | 2 -- test/lib/cli/commands/tree.js | 56 +++++++++++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/lib/cli/commands/tree.js b/lib/cli/commands/tree.js index c5c7f15d..f6be7f71 100644 --- a/lib/cli/commands/tree.js +++ b/lib/cli/commands/tree.js @@ -24,7 +24,7 @@ tree.handler = async function(argv) { if (argv.xPerf) { startTime = process.hrtime(); } - const generateProjectGraph = require("@ui5/project").generateProjectGraph; + const {generateProjectGraph} = require("@ui5/project"); let graph; if (argv.dependencyDefinition) { graph = await generateProjectGraph.usingStaticFile({ diff --git a/test/lib/cli/commands/build.js b/test/lib/cli/commands/build.js index 8325c079..8bc0d173 100644 --- a/test/lib/cli/commands/build.js +++ b/test/lib/cli/commands/build.js @@ -1,6 +1,5 @@ const test = require("ava"); const sinon = require("sinon"); -const mock = require("mock-require"); function getDefaultArgv() { // This has been taken from the actual argv object yargs provides @@ -85,7 +84,6 @@ test.beforeEach((t) => { test.afterEach.always((t) => { sinon.restore(); - mock.stopAll(); }); test.serial("ui5 build (default) ", async (t) => { diff --git a/test/lib/cli/commands/tree.js b/test/lib/cli/commands/tree.js index 8ea7b216..cbc7ec13 100644 --- a/test/lib/cli/commands/tree.js +++ b/test/lib/cli/commands/tree.js @@ -4,16 +4,64 @@ const normalizer = require("@ui5/project").normalizer; const tree = require("../../../../lib/cli/commands/tree"); const treeify = require("treeify"); +function getDefaultArgv() { + // This has been taken from the actual argv object yargs provides + return { + "_": ["tree"], + "loglevel": "info", + "log-level": "info", + "logLevel": "info", + "x-perf": false, + "xPerf": false, + "$0": "ui5" + }; +} + test.beforeEach((t) => { - sinon.stub(normalizer, "generateProjectTree"); - sinon.stub(normalizer, "generateDependencyTree"); -}); + const project = require("@ui5/project"); + + t.context.argv = getDefaultArgv(); + sinon.stub(project.generateProjectGraph, "usingStaticFile").resolves(); + sinon.stub(project.generateProjectGraph, "usingNodePackageDependencies").resolves(); + t.context.generateProjectGraph = project.generateProjectGraph; + + // Create basic mocking objects + t.context.traverseBreadthFirst = sinon.stub(); + t.context.getAllExtensions = sinon.stub().returns([]); + const fakeGraph = { + traverseBreadthFirst: t.context.traverseBreadthFirst, + getAllExtensions: t.context.getAllExtensions + }; + t.context.generateProjectGraph.usingNodePackageDependencies.resolves(fakeGraph); + + t.context.tree = require("../../../../lib/cli/commands/tree"); +}); test.afterEach.always((t) => { sinon.restore(); }); -test.serial("ui5 tree (generates dependency tree before output)", async (t) => { +test.serial("ui5 tree (Without dependencies)", async (t) => { + const {argv, tree, traverseBreadthFirst} = t.context; + + traverseBreadthFirst.callsFake(async (fn) => { + await fn({ + project: { + getName: sinon.stub().returns("project1"), + getVersion: sinon.stub().returns("1.0.0"), + getType: sinon.stub().returns("application"), + getPath: sinon.stub().returns("/home/project1") + }, + getDependencies: sinon.stub().returns([]) + }); + }); + + await tree.handler(argv); + + t.pass(); +}); + +test.serial("ui5 tree (creates project graph dependency tree before output)", async (t) => { normalizer.generateDependencyTree.resolves({}); await tree.handler({}); t.is(normalizer.generateDependencyTree.called, true, "dependency tree output"); From 4c48ee537d7b828e2e77ab34338d9df926782778 Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Tue, 7 Jun 2022 11:37:05 +0200 Subject: [PATCH 18/25] commands/tree unit tests --- test/lib/cli/commands/tree.js | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/test/lib/cli/commands/tree.js b/test/lib/cli/commands/tree.js index cbc7ec13..a401b848 100644 --- a/test/lib/cli/commands/tree.js +++ b/test/lib/cli/commands/tree.js @@ -35,6 +35,12 @@ test.beforeEach((t) => { }; t.context.generateProjectGraph.usingNodePackageDependencies.resolves(fakeGraph); + t.context.consoleOutput = ""; + t.context.consoleLog = sinon.stub(console, "log").callsFake((message) => { + // NOTE: This fake impl only supports one string arg passed to console.log + t.context.consoleOutput += message + "\n"; + }); + t.context.tree = require("../../../../lib/cli/commands/tree"); }); test.afterEach.always((t) => { @@ -48,6 +54,7 @@ test.serial("ui5 tree (Without dependencies)", async (t) => { await fn({ project: { getName: sinon.stub().returns("project1"), + getNamespace: sinon.stub().returns("test/project1"), getVersion: sinon.stub().returns("1.0.0"), getType: sinon.stub().returns("application"), getPath: sinon.stub().returns("/home/project1") @@ -58,9 +65,69 @@ test.serial("ui5 tree (Without dependencies)", async (t) => { await tree.handler(argv); + t.is(t.context.consoleOutput, + `Dependencies (1): +╰─ project1 test/project1 (1.0.0, application) /home/project1 + +Extensions (0): +None +`); + t.pass(); }); +test.serial("ui5 tree", async (t) => { + const {argv, tree, traverseBreadthFirst} = t.context; + + traverseBreadthFirst.callsFake(async (fn) => { + await fn({ + project: { + getName: sinon.stub().returns("project1"), + getNamespace: sinon.stub().returns("test/project1"), + getVersion: sinon.stub().returns("1.0.0"), + getType: sinon.stub().returns("application"), + getPath: sinon.stub().returns("/home/project1") + }, + getDependencies: sinon.stub().returns([{ + getName: sinon.stub().returns("dependency2") + }]) + }); + await fn({ + project: { + getName: sinon.stub().returns("dependency2"), + getNamespace: sinon.stub().returns("test/dependency2"), + getVersion: sinon.stub().returns("2.0.0"), + getType: sinon.stub().returns("library"), + getPath: sinon.stub().returns("/home/dependency2") + }, + getDependencies: sinon.stub().returns([{ + getName: sinon.stub().returns("dependency1") + }]) + }); + await fn({ + project: { + getName: sinon.stub().returns("dependency1"), + getVersion: sinon.stub().returns("1.1.1"), + getType: sinon.stub().returns("library"), + getPath: sinon.stub().returns("/home/dependency1") + }, + getDependencies: sinon.stub().returns([]) + }); + }); + + await tree.handler(argv); + + t.is(t.context.consoleOutput, + `Dependencies (3): +╰─ project1 test/project1 (1.0.0, application) /home/project1 + ╰─ dependency2 test/dependency2 (2.0.0, library) /home/dependency2 + ╰─ dependency1 (1.1.1, library) /home/dependency1 + +Extensions (0): +None +`); +}); + test.serial("ui5 tree (creates project graph dependency tree before output)", async (t) => { normalizer.generateDependencyTree.resolves({}); await tree.handler({}); From 267b36f671c68a8b1ab886e382a2d6f1a5c1e2a3 Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Tue, 7 Jun 2022 16:23:52 +0200 Subject: [PATCH 19/25] Finish tree tests --- lib/cli/commands/tree.js | 4 +- test/lib/cli/commands/tree.js | 284 ++++++++++++++++++++++++---------- 2 files changed, 207 insertions(+), 81 deletions(-) diff --git a/lib/cli/commands/tree.js b/lib/cli/commands/tree.js index f6be7f71..2d2f473b 100644 --- a/lib/cli/commands/tree.js +++ b/lib/cli/commands/tree.js @@ -84,7 +84,7 @@ tree.handler = async function(argv) { if (extensions.length) { extensions.forEach((extension) => { console.log( - `${" ".repeat(indentWidth)} ├─ ${extension.getName()}` + + `├─ ${extension.getName()} ` + chalk.dim(`(${extension.getVersion()}, ${extension.getType()}) `) + chalk.dim.italic(`${extension.getPath()}`)); }); @@ -94,7 +94,7 @@ tree.handler = async function(argv) { if (argv.xPerf) { console.log(""); console.log(chalk.blue( - `Dependency ${argv.xGraphMode ? "graph" : "tree"} generation took ${chalk.bold(elapsedTime)}`)); + `Dependency graph generation took ${chalk.bold(elapsedTime)}`)); } }; diff --git a/test/lib/cli/commands/tree.js b/test/lib/cli/commands/tree.js index a401b848..75f44082 100644 --- a/test/lib/cli/commands/tree.js +++ b/test/lib/cli/commands/tree.js @@ -1,8 +1,5 @@ const test = require("ava"); const sinon = require("sinon"); -const normalizer = require("@ui5/project").normalizer; -const tree = require("../../../../lib/cli/commands/tree"); -const treeify = require("treeify"); function getDefaultArgv() { // This has been taken from the actual argv object yargs provides @@ -33,6 +30,7 @@ test.beforeEach((t) => { traverseBreadthFirst: t.context.traverseBreadthFirst, getAllExtensions: t.context.getAllExtensions }; + t.context.generateProjectGraph.usingStaticFile.resolves(fakeGraph); t.context.generateProjectGraph.usingNodePackageDependencies.resolves(fakeGraph); t.context.consoleOutput = ""; @@ -48,7 +46,7 @@ test.afterEach.always((t) => { }); test.serial("ui5 tree (Without dependencies)", async (t) => { - const {argv, tree, traverseBreadthFirst} = t.context; + const {argv, tree, traverseBreadthFirst, generateProjectGraph} = t.context; traverseBreadthFirst.callsFake(async (fn) => { await fn({ @@ -65,6 +63,12 @@ test.serial("ui5 tree (Without dependencies)", async (t) => { await tree.handler(argv); + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + t.is(t.context.consoleOutput, `Dependencies (1): ╰─ project1 test/project1 (1.0.0, application) /home/project1 @@ -72,12 +76,10 @@ test.serial("ui5 tree (Without dependencies)", async (t) => { Extensions (0): None `); - - t.pass(); }); test.serial("ui5 tree", async (t) => { - const {argv, tree, traverseBreadthFirst} = t.context; + const {argv, tree, traverseBreadthFirst, generateProjectGraph} = t.context; traverseBreadthFirst.callsFake(async (fn) => { await fn({ @@ -89,6 +91,8 @@ test.serial("ui5 tree", async (t) => { getPath: sinon.stub().returns("/home/project1") }, getDependencies: sinon.stub().returns([{ + getName: sinon.stub().returns("dependency1") + }, { getName: sinon.stub().returns("dependency2") }]) }); @@ -111,103 +115,225 @@ test.serial("ui5 tree", async (t) => { getType: sinon.stub().returns("library"), getPath: sinon.stub().returns("/home/dependency1") }, + getDependencies: sinon.stub().returns([{ + getName: sinon.stub().returns("dependency3") + }]) + }); + await fn({ + project: { + getName: sinon.stub().returns("dependency3"), + getVersion: sinon.stub().returns("3.3.3"), + getType: sinon.stub().returns("library"), + getPath: sinon.stub().returns("/home/dependency3") + }, getDependencies: sinon.stub().returns([]) }); }); await tree.handler(argv); + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + t.is(t.context.consoleOutput, - `Dependencies (3): + `Dependencies (4): ╰─ project1 test/project1 (1.0.0, application) /home/project1 + ├─ dependency1 (1.1.1, library) /home/dependency1 + │ ╰─ dependency3 (3.3.3, library) /home/dependency3 ╰─ dependency2 test/dependency2 (2.0.0, library) /home/dependency2 ╰─ dependency1 (1.1.1, library) /home/dependency1 + ╰─ dependency3 (3.3.3, library) /home/dependency3 Extensions (0): None `); }); -test.serial("ui5 tree (creates project graph dependency tree before output)", async (t) => { - normalizer.generateDependencyTree.resolves({}); - await tree.handler({}); - t.is(normalizer.generateDependencyTree.called, true, "dependency tree output"); -}); +test.serial("ui5 tree (With extensions)", async (t) => { + const {argv, tree, traverseBreadthFirst, generateProjectGraph, getAllExtensions} = t.context; -test.serial("ui5 tree --full (generates project tree before output)", async (t) => { - normalizer.generateProjectTree.resolves({}); - await tree.handler({full: true}); - t.is(normalizer.generateProjectTree.called, true, "project tree output"); -}); + traverseBreadthFirst.callsFake(async (fn) => { + await fn({ + project: { + getName: sinon.stub().returns("project1"), + getNamespace: sinon.stub().returns("test/project1"), + getVersion: sinon.stub().returns("1.0.0"), + getType: sinon.stub().returns("application"), + getPath: sinon.stub().returns("/home/project1") + }, + getDependencies: sinon.stub().returns([]) + }); + }); -test.serial("ui5 tree --json (output tree in json)", async (t) => { - const jsonStringifySpy = sinon.spy(JSON, "stringify"); - const consoleStub = sinon.stub(console, "log"); - - normalizer.generateDependencyTree.resolves({name: "sample"}); - await tree.handler({json: true}); - - // Note: Some versions of Node.js seem to call stringify internally during this test - t.deepEqual(jsonStringifySpy.called, true, "Stringify got called at least once"); - t.deepEqual(jsonStringifySpy.getCall(jsonStringifySpy.callCount - 1).args[0], {name: "sample"}, - "JSON.stringify called with correct argument"); - t.deepEqual(consoleStub.callCount, 1, "console.log was called once"); - t.deepEqual(consoleStub.getCall(0).args[0], `{ - "name": "sample" -}`, "console.log was called with correct argument"); -}); + getAllExtensions.returns([{ + getName: sinon.stub().returns("extension1"), + getVersion: sinon.stub().returns("3.0.0"), + getType: sinon.stub().returns("task"), + getPath: sinon.stub().returns("/home/extension1") + }]); -test.serial("ui5 tree (output tree)", async (t) => { - const treeifySpy = sinon.stub(treeify, "asTree").returns("🌲"); - const consoleStub = sinon.stub(console, "log"); - normalizer.generateDependencyTree.resolves({name: "sample"}); - await tree.handler({}); + await tree.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, + `Dependencies (1): +╰─ project1 test/project1 (1.0.0, application) /home/project1 - t.deepEqual(treeifySpy.callCount, 1, "Treeify called once"); - t.deepEqual(treeifySpy.getCall(0).args[0], {name: "sample"}, "Treeify called with correct argument"); - t.deepEqual(consoleStub.callCount, 1, "console.log was called once"); - t.deepEqual(consoleStub.getCall(0).args[0], "🌲", "console.log was called with correct argument"); +Extensions (1): +├─ extension1 (3.0.0, task) /home/extension1 +`); }); -test.serial("ui5 tree --dedupe=false (default)", async (t) => { - normalizer.generateDependencyTree.resolves({}); - await tree.handler({dedupe: false}); - t.is(normalizer.generateDependencyTree.calledWithMatch({ - translatorOptions: { - includeDeduped: true - } - }), true, "includeDeduped passed as expected"); +test.serial("ui5 tree --x-perf", async (t) => { + const {argv, tree, traverseBreadthFirst, generateProjectGraph} = t.context; + + traverseBreadthFirst.callsFake(async (fn) => { + await fn({ + project: { + getName: sinon.stub().returns("project1"), + getNamespace: sinon.stub().returns("test/project1"), + getVersion: sinon.stub().returns("1.0.0"), + getType: sinon.stub().returns("application"), + getPath: sinon.stub().returns("/home/project1") + }, + getDependencies: sinon.stub().returns([]) + }); + }); + + argv.xPerf = true; + + sinon.stub(process, "hrtime") + .withArgs().returns([0, 0]) + .withArgs([0, 0]).returns([0, 1000000]); + + await tree.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, + `Dependencies (1): +╰─ project1 test/project1 (1.0.0, application) /home/project1 + +Extensions (0): +None + +Dependency graph generation took 1 ms +`); }); -test.serial("ui5 tree --dedupe=true", async (t) => { - normalizer.generateDependencyTree.resolves({}); - await tree.handler({dedupe: true}); - t.is(normalizer.generateDependencyTree.calledWithMatch({ - translatorOptions: { - includeDeduped: false - } - }), true, "includeDeduped passed as expected"); +test.serial("ui5 tree --framework-version", async (t) => { + const {argv, tree, traverseBreadthFirst, generateProjectGraph} = t.context; + + argv.frameworkVersion = "1.234.5"; + + traverseBreadthFirst.callsFake(async (fn) => { + await fn({ + project: { + getName: sinon.stub().returns("project1"), + getNamespace: sinon.stub().returns("test/project1"), + getVersion: sinon.stub().returns("1.0.0"), + getType: sinon.stub().returns("application"), + getPath: sinon.stub().returns("/home/project1") + }, + getDependencies: sinon.stub().returns([]) + }); + }); + + await tree.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: "1.234.5"} + ]); + + t.is(t.context.consoleOutput, + `Dependencies (1): +╰─ project1 test/project1 (1.0.0, application) /home/project1 + +Extensions (0): +None +`); }); -test.serial("ui5 tree --full --framework-version", async (t) => { - normalizer.generateProjectTree.resolves({}); - await tree.handler({full: true, frameworkVersion: "1.2.3"}); - t.is(normalizer.generateProjectTree.called, true, "project tree output"); - t.deepEqual(normalizer.generateProjectTree.getCall(0).args, [{ - configPath: undefined, - translatorName: undefined, - translatorOptions: { - includeDeduped: true - }, - frameworkOptions: { - versionOverride: "1.2.3" - } - }]); +test.serial("ui5 tree --config", async (t) => { + const {argv, tree, traverseBreadthFirst, generateProjectGraph} = t.context; + + argv.config = "/home/project1/config.yaml"; + + traverseBreadthFirst.callsFake(async (fn) => { + await fn({ + project: { + getName: sinon.stub().returns("project1"), + getNamespace: sinon.stub().returns("test/project1"), + getVersion: sinon.stub().returns("1.0.0"), + getType: sinon.stub().returns("application"), + getPath: sinon.stub().returns("/home/project1") + }, + getDependencies: sinon.stub().returns([]) + }); + }); + + await tree.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: "/home/project1/config.yaml", versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, + `Dependencies (1): +╰─ project1 test/project1 (1.0.0, application) /home/project1 + +Extensions (0): +None +`); }); -// test.serial("Error: throws on error during processing", async (t) => { -// normalizer.generateDependencyTree.rejects(new Error("Some error happened ...")); -// const processExitStub = sinon.stub(process, "exit"); -// t.is(processExitStub.getCall(0).args[0], 1, "killed process on error using process.exit(1)"); -// processExitStub.restore(); -// }); +test.serial("ui5 tree --dependency-definition", async (t) => { + const {argv, tree, traverseBreadthFirst, generateProjectGraph} = t.context; + + argv.dependencyDefinition = "/home/project1/dependencies.yaml"; + + traverseBreadthFirst.callsFake(async (fn) => { + await fn({ + project: { + getName: sinon.stub().returns("project1"), + getNamespace: sinon.stub().returns("test/project1"), + getVersion: sinon.stub().returns("1.0.0"), + getType: sinon.stub().returns("application"), + getPath: sinon.stub().returns("/home/project1") + }, + getDependencies: sinon.stub().returns([]) + }); + }); + + await tree.handler(argv); + + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 0); + t.is(generateProjectGraph.usingStaticFile.callCount, 1); + t.deepEqual(generateProjectGraph.usingStaticFile.getCall(0).args, [ + {filePath: "/home/project1/dependencies.yaml", versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, + `Dependencies (1): +╰─ project1 test/project1 (1.0.0, application) /home/project1 + +Extensions (0): +None +`); +}); From 079677699d288e9dc6acb02b8b2e174bf3c79ef5 Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Wed, 8 Jun 2022 11:51:57 +0200 Subject: [PATCH 20/25] Update serve command tests --- test/lib/cli/commands/serve.js | 884 +++++++++++++++++++++------------ 1 file changed, 575 insertions(+), 309 deletions(-) diff --git a/test/lib/cli/commands/serve.js b/test/lib/cli/commands/serve.js index c3a11944..c65808fb 100644 --- a/test/lib/cli/commands/serve.js +++ b/test/lib/cli/commands/serve.js @@ -1,371 +1,637 @@ const test = require("ava"); const sinon = require("sinon"); -const path = require("path"); -const os = require("os"); -const normalizer = require("@ui5/project").normalizer; -const serve = require("../../../../lib/cli/commands/serve"); -const ui5Server = require("@ui5/server"); -const server = ui5Server.server; -const mockRequire = require("mock-require"); -const defaultInitialHandlerArgs = Object.freeze({ - accessRemoteConnections: false, - cert: path.join(os.homedir(), ".ui5", "server", "server.crt"), - h2: false, - key: path.join(os.homedir(), ".ui5", "server", "server.key"), - loglevel: "info", - t8r: "npm", - translator: "npm", - simpleIndex: false -}); +const mock = require("mock-require"); + +function getDefaultArgv() { + // This has been taken from the actual argv object yargs provides + return { + "_": ["serve"], + "loglevel": "info", + "log-level": "info", + "logLevel": "info", + "x-perf": false, + "xPerf": false, + "h2": false, + "simple-index": false, + "simpleIndex": false, + "accept-remote-connections": false, + "acceptRemoteConnections": false, + "key": "/home/.ui5/server/server.key", + "cert": "/home/.ui5/server/server.crt", + "sap-csp-policies": false, + "sapCspPolicies": false, + "serve-csp-reports": false, + "serveCspReports": false, + "$0": "ui5" + }; +} -const projectTree = { - metadata: { - name: "Sample" - } -}; +test.beforeEach((t) => { + const server = require("@ui5/server"); + const project = require("@ui5/project"); -let normalizerStub = null; -let serverStub = null; -let sslUtilStub = null; + t.context.argv = getDefaultArgv(); -test.beforeEach((t) => { - normalizerStub = sinon.stub(normalizer, "generateProjectTree"); - serverStub = sinon.stub(server, "serve"); - sslUtilStub = sinon.stub(ui5Server.sslUtil, "getSslCertificate"); + t.context.server = server.server; + sinon.stub(t.context.server, "serve").returns({ + h2: false, + port: 8080 + }); + + t.context.sslUtil = server.sslUtil; + sinon.stub(t.context.sslUtil, "getSslCertificate"); + + sinon.stub(project.generateProjectGraph, "usingStaticFile").resolves(); + sinon.stub(project.generateProjectGraph, "usingNodePackageDependencies").resolves(); + t.context.generateProjectGraph = project.generateProjectGraph; + + // Create basic mocking objects + t.context.getServerSettings = sinon.stub().returns({}); + t.context.fakeGraph = { + getRoot: () => { + return { + getServerSettings: t.context.getServerSettings + }; + } + }; + t.context.generateProjectGraph.usingStaticFile.resolves(t.context.fakeGraph); + t.context.generateProjectGraph.usingNodePackageDependencies.resolves(t.context.fakeGraph); + + t.context.consoleOutput = ""; + t.context.consoleLog = sinon.stub(console, "log").callsFake((message) => { + // NOTE: This fake impl only supports one string arg passed to console.log + t.context.consoleOutput += message + "\n"; + }); + + t.context.open = sinon.stub(); + mock("open", t.context.open); + + t.context.serve = mock.reRequire("../../../../lib/cli/commands/serve"); }); test.afterEach.always((t) => { sinon.restore(); + mock.stopAll(); }); test.serial("ui5 serve: default", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({h2: false, port: 8080}); - - // loads project tree - const pPrepareServerConfig = await serve.handler(defaultInitialHandlerArgs); - // preprocess project config, skipping cert load - const pServeServer = await pPrepareServerConfig; - // serve server using config - await pServeServer; - - const injectedProjectTree = serverStub.getCall(0).args[0]; - const injectedServerConfig = serverStub.getCall(0).args[1]; - - t.deepEqual(injectedProjectTree, projectTree, "Starting server with given project tree"); - t.deepEqual(injectedServerConfig, { - changePortIfInUse: true, - acceptRemoteConnections: false, - h2: false, - simpleIndex: false, - port: 8080, - cert: undefined, - key: undefined, - sendSAPTargetCSP: false, - serveCSPReports: false - }, "Starting server with specific server config"); + const {argv, serve, generateProjectGraph, server, fakeGraph} = t.context; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, + } + ]); }); test.serial("ui5 serve --h2", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({h2: true, port: 8443}); - sslUtilStub.resolves({ + const {argv, serve, generateProjectGraph, server, fakeGraph, sslUtil} = t.context; + + sslUtil.getSslCertificate.resolves({ key: "randombyte-likes-ponies-key", cert: "randombyte-likes-ponies-cert" }); - // loads project tree using http 2 - const pPrepareServerConfig = await serve.handler(Object.assign({}, defaultInitialHandlerArgs, {h2: true})); - // preprocess project config - const pFetchSSLCert = await pPrepareServerConfig; - // Fetching ssl certificate - const pServeServer = await pFetchSSLCert; - // serve server using config - await pServeServer; - - const injectedProjectTree = serverStub.getCall(0).args[0]; - const injectedServerConfig = serverStub.getCall(0).args[1]; - - t.is(sslUtilStub.getCall(0).args[0], path.join(os.homedir(), ".ui5", "server", "server.key"), - "Load ssl key from default path"); - t.is(sslUtilStub.getCall(0).args[1], path.join(os.homedir(), ".ui5", "server", "server.crt"), - "Load ssl cert from default path"); - t.deepEqual(injectedProjectTree, projectTree, "Starting server with given project tree"); - t.is(injectedServerConfig.port === 8443, true, "http2 default port was auto set"); - - t.deepEqual(injectedServerConfig, { - changePortIfInUse: true, - acceptRemoteConnections: false, + server.serve.returns({ h2: true, - simpleIndex: false, - port: 8443, - key: "randombyte-likes-ponies-key", - cert: "randombyte-likes-ponies-cert", - sendSAPTargetCSP: false, - serveCSPReports: false - }, "Starting server with specific server config"); + port: 8443 + }); + + argv.h2 = true; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: https://localhost:8443 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + changePortIfInUse: true, + h2: true, + key: "randombyte-likes-ponies-key", + cert: "randombyte-likes-ponies-cert", + port: 8443, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, + } + ]); + + t.is(sslUtil.getSslCertificate.callCount, 1); + t.deepEqual(sslUtil.getSslCertificate.getCall(0).args, [ + "/home/.ui5/server/server.key", + "/home/.ui5/server/server.crt" + ]); }); test.serial("ui5 serve --accept-remote-connections", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({port: 8080}); - const pPrepareServerConfig = await serve.handler( - Object.assign({}, defaultInitialHandlerArgs, {acceptRemoteConnections: true}) - ); - const pServeServer = await pPrepareServerConfig; - await pServeServer; - const injectedServerConfig = serverStub.getCall(0).args[1]; - t.is(injectedServerConfig.acceptRemoteConnections, true, "Remove connections are accepted"); + const {argv, serve, generateProjectGraph, server, fakeGraph} = t.context; + + argv.acceptRemoteConnections = true; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, ` +⚠️ This server is accepting connections from all hosts on your network +Please Note: +* This server is intended for development purposes only. Do not use it in production. +* Vulnerable (custom-)middleware can pose a threat to your system when exposed to the network +* The use of proxy-middleware with preconfigured credentials might enable unauthorized access to a target \ +system for third parties on your network + +Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: true, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, + } + ]); }); test.serial("ui5 serve --open", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({port: 8080}); - mockRequire("open", function(openedUrl) { - t.is(openedUrl, "http://localhost:8080/webapp/index.html", `Opens url: ${openedUrl}`); - mockRequire.stop("open"); - }); - await serve.handler( - Object.assign({}, defaultInitialHandlerArgs, {open: "webapp/index.html"}) - ); + const {argv, serve, generateProjectGraph, server, fakeGraph, open} = t.context; + + argv.open = "index.html"; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, + } + ]); + + t.is(open.callCount, 1); + t.deepEqual(open.getCall(0).args, [ + "http://localhost:8080/index.html", + { + url: true + } + ]); }); test.serial("ui5 serve --open (opens default url)", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({port: 8080}); - mockRequire("open", function(openedUrl) { - t.is(openedUrl, "http://localhost:8080", `Opens url: ${openedUrl}`); - mockRequire.stop("open"); - }); - await serve.handler( - Object.assign({}, defaultInitialHandlerArgs, {open: true}) - ); -}); + const {argv, serve, generateProjectGraph, server, fakeGraph, open} = t.context; + + argv.open = true; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, + } + ]); -test.serial("ui5 serve --key --cert", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({h2: true, port: 8443}); - sslUtilStub.resolves({ - key: "ponies-loaded-from-custompath-key", - cert: "ponies-loaded-from-custompath-crt" - }); + t.is(open.callCount, 1); + t.deepEqual(open.getCall(0).args, [ + "http://localhost:8080", + { + url: true + } + ]); +}); - // loads project tree using http 2 - const pPrepareServerConfig = await serve.handler(Object.assign({}, defaultInitialHandlerArgs, { - h2: true, - key: "server/randombyte-likes-ponies.key", - cert: "server/randombyte-likes-ponies.crt" - })); - // preprocess project config - const pFetchSSLCert = await pPrepareServerConfig; - // Fetching ssl certificate - const pServeServer = await pFetchSSLCert; - // serve server using config - await pServeServer; - - const injectedServerConfig = serverStub.getCall(0).args[1]; - t.is(sslUtilStub.getCall(0).args[0], "server/randombyte-likes-ponies.key", "Loading key from specified path"); - t.is(sslUtilStub.getCall(0).args[1], "server/randombyte-likes-ponies.crt", "Loading cert from specified path"); - t.deepEqual(injectedServerConfig, { - changePortIfInUse: true, - acceptRemoteConnections: false, - h2: true, - simpleIndex: false, - port: 8443, - key: "ponies-loaded-from-custompath-key", - cert: "ponies-loaded-from-custompath-crt", - sendSAPTargetCSP: false, - serveCSPReports: false - }, "Starting server with specific server config"); +test.serial("ui5 serve --config", async (t) => { + const {argv, serve, generateProjectGraph, server, fakeGraph} = t.context; + + argv.config = "/path/to/ui5.yaml"; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: "/path/to/ui5.yaml", versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, + } + ]); }); +test.serial("ui5 serve --dependency-definition", async (t) => { + const {argv, serve, generateProjectGraph, server, fakeGraph} = t.context; + + argv.dependencyDefinition = "/path/to/dependencies.yaml"; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 0); + t.is(generateProjectGraph.usingStaticFile.callCount, 1); + t.deepEqual(generateProjectGraph.usingStaticFile.getCall(0).args, [ + {filePath: "/path/to/dependencies.yaml", versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, + } + ]); +}); -test.serial("ui5 serve --translator --config", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({h2: false, port: 8080}); +test.serial("ui5 serve --framework-version", async (t) => { + const {argv, serve, generateProjectGraph, server, fakeGraph} = t.context; + + argv.frameworkVersion = "1.234.5"; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: "1.234.5"} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, + } + ]); +}); - const pPrepareServerConfig = await serve.handler(Object.assign({}, defaultInitialHandlerArgs, { - translator: "static", - config: "path/to/my/config.json" - })); - const pServeServer = await pPrepareServerConfig; - await pServeServer; +test.serial("ui5 serve --sap-csp-policies", async (t) => { + const {argv, serve, generateProjectGraph, server, fakeGraph} = t.context; + + argv.sapCspPolicies = true; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: true, + serveCSPReports: false, + simpleIndex: false, + } + ]); +}); - t.deepEqual(normalizerStub.getCall(0).args[0], { - translatorName: "static", - configPath: "path/to/my/config.json" - }, "CLI was called with static translator"); +test.serial("ui5 serve --serve-csp-reports", async (t) => { + const {argv, serve, generateProjectGraph, server, fakeGraph} = t.context; + + argv.serveCspReports = true; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: false, + serveCSPReports: true, + simpleIndex: false, + } + ]); }); -test.serial("ui5 serve --sap-csp-policies", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({}); - - // loads project tree using http 2 - const pPrepareServerConfig = await serve.handler( - Object.assign({}, defaultInitialHandlerArgs, {sapCspPolicies: true})); - // preprocess project config - const pServeServer = await pPrepareServerConfig; - // serve server using config - await pServeServer; - - const injectedProjectTree = serverStub.getCall(0).args[0]; - const injectedServerConfig = serverStub.getCall(0).args[1]; - - t.deepEqual(injectedProjectTree, projectTree, "Starting server with given project tree"); - t.deepEqual(injectedServerConfig, { - changePortIfInUse: true, - acceptRemoteConnections: false, - h2: false, - simpleIndex: false, - port: 8080, - cert: undefined, - key: undefined, - sendSAPTargetCSP: true, - serveCSPReports: false - }, "Starting server with specific server config"); +test.serial("ui5 serve --simple-index", async (t) => { + const {argv, serve, generateProjectGraph, server, fakeGraph} = t.context; + + argv.simpleIndex = true; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:8080 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: true, + h2: false, + key: undefined, + port: 8080, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: true, + } + ]); }); test.serial("ui5 serve with ui5.yaml port setting", async (t) => { - const projectTree = { - metadata: { - name: "Sample" - }, - server: { - settings: { - httpPort: 1337 - } + const {argv, serve, generateProjectGraph, server, fakeGraph, getServerSettings} = t.context; + + getServerSettings.returns({ + httpPort: 3333 + }); + + server.serve.returns({ + h2: false, + port: 3333 + }); + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: http://localhost:3333 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + cert: undefined, + changePortIfInUse: false, + h2: false, + key: undefined, + port: 3333, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, } - }; - normalizerStub.resolves(projectTree); - serverStub.resolves({h2: false, port: 1}); - // loads project tree using http 2 - const pPrepareServerConfig = await serve.handler(defaultInitialHandlerArgs); - // preprocess project config, skipping cert load - const pServeServer = await pPrepareServerConfig; - // serve server using config - await pServeServer; - - const injectedProjectTree = serverStub.getCall(0).args[0]; - const injectedServerConfig = serverStub.getCall(0).args[1]; - - t.deepEqual(injectedProjectTree, projectTree, "Starting server with given project tree"); - t.deepEqual(injectedServerConfig.port, 1337, "http port setting from project tree was used"); - t.deepEqual(injectedServerConfig.changePortIfInUse, false, "changePortIfInUse is set to false"); + ]); }); test.serial("ui5 serve --h2 with ui5.yaml port setting", async (t) => { - const projectTree = { - metadata: { - name: "Sample" - }, - server: { - settings: { - httpsPort: 1443 - } - } - }; - normalizerStub.resolves(projectTree); - serverStub.resolves({h2: true, port: 1}); - sslUtilStub.resolves({ + const {argv, serve, generateProjectGraph, server, fakeGraph, sslUtil, getServerSettings} = t.context; + + sslUtil.getSslCertificate.resolves({ key: "randombyte-likes-ponies-key", cert: "randombyte-likes-ponies-cert" }); - // loads project tree using http 2 - const pPrepareServerConfig = await serve.handler(Object.assign({}, defaultInitialHandlerArgs, {h2: true})); - // preprocess project config - const pFetchSSLCert = await pPrepareServerConfig; - // Fetching ssl certificate - const pServeServer = await pFetchSSLCert; - // serve server using config - await pServeServer; - - const injectedProjectTree = serverStub.getCall(0).args[0]; - const injectedServerConfig = serverStub.getCall(0).args[1]; - - t.deepEqual(injectedProjectTree, projectTree, "Starting server with given project tree"); - t.deepEqual(injectedServerConfig.port, 1443, "https port setting from project tree was used"); - t.deepEqual(injectedServerConfig.changePortIfInUse, false, "changePortIfInUse is set to false"); + getServerSettings.returns({ + httpsPort: 4444 + }); + + server.serve.returns({ + h2: true, + port: 4444 + }); + + argv.h2 = true; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: https://localhost:4444 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + changePortIfInUse: false, + h2: true, + key: "randombyte-likes-ponies-key", + cert: "randombyte-likes-ponies-cert", + port: 4444, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, + } + ]); + + t.is(sslUtil.getSslCertificate.callCount, 1); + t.deepEqual(sslUtil.getSslCertificate.getCall(0).args, [ + "/home/.ui5/server/server.key", + "/home/.ui5/server/server.crt" + ]); }); test.serial("ui5 serve --h2 with ui5.yaml port setting and port CLI argument", async (t) => { - const projectTree = { - metadata: { - name: "Sample" - }, - server: { - settings: { - httpPort: 1337, - httpsPort: 1443 - } - } - }; - normalizerStub.resolves(projectTree); - serverStub.resolves({h2: true, port: 1}); - sslUtilStub.resolves({ + const {argv, serve, generateProjectGraph, server, fakeGraph, sslUtil, getServerSettings} = t.context; + + sslUtil.getSslCertificate.resolves({ key: "randombyte-likes-ponies-key", cert: "randombyte-likes-ponies-cert" }); - // loads project tree using http 2 - const pPrepareServerConfig = await serve.handler(Object.assign({}, defaultInitialHandlerArgs, { + getServerSettings.returns({ + httpsPort: 4444 + }); + + server.serve.returns({ h2: true, port: 5555 - })); - // preprocess project config - const pFetchSSLCert = await pPrepareServerConfig; - // Fetching ssl certificate - const pServeServer = await pFetchSSLCert; - // serve server using config - await pServeServer; - - const injectedProjectTree = serverStub.getCall(0).args[0]; - const injectedServerConfig = serverStub.getCall(0).args[1]; - - t.deepEqual(injectedProjectTree, projectTree, "Starting server with given project tree"); - t.deepEqual(injectedServerConfig.port, 5555, "https port setting from CLI argument was used"); - t.deepEqual(injectedServerConfig.changePortIfInUse, false, "changePortIfInUse is set to false"); -}); + }); -test.serial("ui5 serve: --framework-version", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({h2: false, port: 8080}); - - // loads project tree - const pPrepareServerConfig = await serve.handler( - Object.assign({}, defaultInitialHandlerArgs, {frameworkVersion: "1.2.3"}) - ); - // preprocess project config, skipping cert load - const pServeServer = await pPrepareServerConfig; - // serve server using config - await pServeServer; - - t.is(normalizerStub.called, true); - t.deepEqual(normalizerStub.getCall(0).args, [{ - configPath: undefined, - translatorName: "npm", - frameworkOptions: { - versionOverride: "1.2.3" + argv.h2 = true; + argv.port = 5555; + + await serve.handler(argv); + + t.is(generateProjectGraph.usingStaticFile.callCount, 0); + t.is(generateProjectGraph.usingNodePackageDependencies.callCount, 1); + t.deepEqual(generateProjectGraph.usingNodePackageDependencies.getCall(0).args, [ + {rootConfigPath: undefined, versionOverride: undefined} + ]); + + t.is(t.context.consoleOutput, `Server started +URL: https://localhost:5555 +`); + + t.is(server.serve.callCount, 1); + t.deepEqual(server.serve.getCall(0).args, [ + fakeGraph, + { + acceptRemoteConnections: false, + changePortIfInUse: false, + h2: true, + key: "randombyte-likes-ponies-key", + cert: "randombyte-likes-ponies-cert", + port: 5555, + sendSAPTargetCSP: false, + serveCSPReports: false, + simpleIndex: false, } - }]); -}); + ]); -test.serial("ui5 serve --serve-csp-reports", async (t) => { - normalizerStub.resolves(projectTree); - serverStub.resolves({h2: false, port: 8080}); - - // loads project tree - const pPrepareServerConfig = await serve.handler( - Object.assign({}, defaultInitialHandlerArgs, {serveCspReports: true}) - ); - // preprocess project config, skipping cert load - const pServeServer = await pPrepareServerConfig; - // serve server using config - await pServeServer; - - const injectedServerConfig = serverStub.getCall(0).args[1]; - - t.is(normalizerStub.called, true); - t.deepEqual(injectedServerConfig.serveCSPReports, true, "serveCSPReports value set"); + t.is(sslUtil.getSslCertificate.callCount, 1); + t.deepEqual(sslUtil.getSslCertificate.getCall(0).args, [ + "/home/.ui5/server/server.key", + "/home/.ui5/server/server.crt" + ]); }); From d4f907ea8560addfeda931021ac250645e839a40 Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Wed, 8 Jun 2022 12:07:38 +0200 Subject: [PATCH 21/25] Remove unused dependency treeify --- npm-shrinkwrap.json | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 28939e5d..abab8f4f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -20,7 +20,6 @@ "js-yaml": "^4.1.0", "open": "^7.4.2", "semver": "^7.3.5", - "treeify": "^1.0.1", "update-notifier": "^5.1.0", "yargs": "^16.2.0" }, diff --git a/package.json b/package.json index 2dd98386..47354a79 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,6 @@ "js-yaml": "^4.1.0", "open": "^7.4.2", "semver": "^7.3.5", - "treeify": "^1.0.1", "update-notifier": "^5.1.0", "yargs": "^16.2.0" }, From 2c56c5b24582804cf9e8b14d40c015d11f5db1f0 Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Wed, 8 Jun 2022 12:08:36 +0200 Subject: [PATCH 22/25] Add missing dependency pretty-hrtime --- npm-shrinkwrap.json | 5 +++-- package.json | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index abab8f4f..115061ec 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -19,6 +19,7 @@ "import-local": "^3.1.0", "js-yaml": "^4.1.0", "open": "^7.4.2", + "pretty-hrtime": "^1.0.3", "semver": "^7.3.5", "update-notifier": "^5.1.0", "yargs": "^16.2.0" @@ -7298,7 +7299,7 @@ "node_modules/pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", "engines": { "node": ">= 0.8" } @@ -15359,7 +15360,7 @@ "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==" }, "pretty-ms": { "version": "7.0.1", diff --git a/package.json b/package.json index 47354a79..397c0c50 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "version": "git-chglog --sort semver --next-tag v$npm_package_version -o CHANGELOG.md && git add CHANGELOG.md", "prepublishOnly": "git push --follow-tags", "release-note": "git-chglog --sort semver -c .chglog/release-config.yml v$npm_package_version | node .chglog/consolidate-changelogs.js", - "depcheck": "depcheck --ignores docdash,@ui5/fs" + "depcheck": "depcheck --ignores docdash,@ui5/fs,@ui5/builder" }, "files": [ "CHANGELOG.md", @@ -118,6 +118,7 @@ "import-local": "^3.1.0", "js-yaml": "^4.1.0", "open": "^7.4.2", + "pretty-hrtime": "^1.0.3", "semver": "^7.3.5", "update-notifier": "^5.1.0", "yargs": "^16.2.0" From 9157b168303524e2e66ee13a332a373c6226df7b Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 8 Jun 2022 16:05:28 +0200 Subject: [PATCH 23/25] [INTERNAL] Make build command 'archive' an option 'create-build-description' --- lib/cli/commands/build.js | 13 +++++++------ test/lib/cli/commands/build.js | 26 ++++++++++++++------------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/cli/commands/build.js b/lib/cli/commands/build.js index d5c1d842..d5f20c26 100644 --- a/lib/cli/commands/build.js +++ b/lib/cli/commands/build.js @@ -11,11 +11,6 @@ const build = { build.builder = function(cli) { return cli - .command("archive", "Archive build: Creates a reusable build archive of the project", { - handler: handleBuild, - builder: noop, - middlewares: [baseMiddleware] - }) .command("jsdoc", "Build JSDoc resources", { handler: handleBuild, builder: noop, @@ -81,6 +76,12 @@ build.builder = function(cli) { default: false, type: "boolean" }) + .option("create-build-description", { + describe: "Store build metadata in a '.ui5' directory in the build destination, " + + "allowing reuse of the build result in other builds", + default: false, + type: "boolean" + }) .option("include-task", { describe: "A list of tasks to be added to the default execution set. " + "This option takes precedence over any excludes.", @@ -143,6 +144,7 @@ async function handleBuild(argv) { graph, destPath: argv.dest, cleanDest: argv["clean-dest"], + createBuildDescription: argv["create-build-description"], complexDependencyIncludes: { includeAllDependencies: argv["include-all-dependencies"], includeDependency: argv["include-dependency"], @@ -155,7 +157,6 @@ async function handleBuild(argv) { defaultIncludeDependencyRegExp: buildSettings.includeDependencyRegExp, defaultIncludeDependencyTree: buildSettings.includeDependencyTree }, - archive: command === "archive", selfContained: command === "self-contained", jsdoc: command === "jsdoc", includedTasks: argv["include-task"], diff --git a/test/lib/cli/commands/build.js b/test/lib/cli/commands/build.js index 8bc0d173..cb166275 100644 --- a/test/lib/cli/commands/build.js +++ b/test/lib/cli/commands/build.js @@ -14,6 +14,8 @@ function getDefaultArgv() { "all": false, "a": false, "includeAllDependencies": false, + "create-build-description": false, + "createBuildDescription": false, "dest": "./dist", "clean-dest": false, "cleanDest": false, @@ -39,7 +41,7 @@ function getDefaultBuilderArgs() { defaultIncludeDependencyRegExp: undefined, defaultIncludeDependencyTree: undefined }, - archive: false, + createBuildDescription: false, selfContained: false, jsdoc: false, includedTasks: undefined, @@ -116,17 +118,6 @@ 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 archive", async (t) => { - const {build, argv, builder, expectedBuilderArgs} = t.context; - - argv._.push("archive"); - - await build.handler(argv); - - expectedBuilderArgs.archive = true; - t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "archive build called with expected arguments"); -}); - test.serial("ui5 build --framework-version 1.99", async (t) => { const {build, argv, generateProjectGraph} = t.context; @@ -143,6 +134,17 @@ test.serial("ui5 build --framework-version 1.99", async (t) => { ); }); +test.serial("ui5 build --createBuildDescription", async (t) => { + const {build, argv, builder, expectedBuilderArgs} = t.context; + + argv["create-build-description"] = true; + + await build.handler(argv); + + expectedBuilderArgs.createBuildDescription = true; + t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "default build triggered with expected arguments"); +}); + test.serial("ui5 build (Include/Exclude dependency options)", async (t) => { const {build, argv, builder, expectedBuilderArgs} = t.context; From 372631a3d3c0c511fb0cae2611aac94e6e662617 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Fri, 10 Jun 2022 10:41:49 +0200 Subject: [PATCH 24/25] Rename build description to build manifest --- lib/cli/commands/build.js | 4 ++-- test/lib/cli/commands/build.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cli/commands/build.js b/lib/cli/commands/build.js index d5f20c26..bc3edb0a 100644 --- a/lib/cli/commands/build.js +++ b/lib/cli/commands/build.js @@ -76,7 +76,7 @@ build.builder = function(cli) { default: false, type: "boolean" }) - .option("create-build-description", { + .option("create-build-manifest", { describe: "Store build metadata in a '.ui5' directory in the build destination, " + "allowing reuse of the build result in other builds", default: false, @@ -144,7 +144,7 @@ async function handleBuild(argv) { graph, destPath: argv.dest, cleanDest: argv["clean-dest"], - createBuildDescription: argv["create-build-description"], + createBuildManifest: argv["create-build-manifest"], complexDependencyIncludes: { includeAllDependencies: argv["include-all-dependencies"], includeDependency: argv["include-dependency"], diff --git a/test/lib/cli/commands/build.js b/test/lib/cli/commands/build.js index cb166275..fa931377 100644 --- a/test/lib/cli/commands/build.js +++ b/test/lib/cli/commands/build.js @@ -14,8 +14,8 @@ function getDefaultArgv() { "all": false, "a": false, "includeAllDependencies": false, - "create-build-description": false, - "createBuildDescription": false, + "create-build-manifest": false, + "createBuildManifest": false, "dest": "./dist", "clean-dest": false, "cleanDest": false, @@ -41,7 +41,7 @@ function getDefaultBuilderArgs() { defaultIncludeDependencyRegExp: undefined, defaultIncludeDependencyTree: undefined }, - createBuildDescription: false, + createBuildManifest: false, selfContained: false, jsdoc: false, includedTasks: undefined, @@ -134,14 +134,14 @@ test.serial("ui5 build --framework-version 1.99", async (t) => { ); }); -test.serial("ui5 build --createBuildDescription", async (t) => { +test.serial("ui5 build --createBuildManifest", async (t) => { const {build, argv, builder, expectedBuilderArgs} = t.context; - argv["create-build-description"] = true; + argv["create-build-manifest"] = true; await build.handler(argv); - expectedBuilderArgs.createBuildDescription = true; + expectedBuilderArgs.createBuildManifest = true; t.deepEqual(builder.getCall(0).args[0], expectedBuilderArgs, "default build triggered with expected arguments"); }); From a9ae0dbee207924f6a79468301325e4337f3dc2b Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Fri, 10 Jun 2022 12:30:21 +0200 Subject: [PATCH 25/25] build: pass --config arg to generateProjectGraph.usingStaticFile --- lib/cli/commands/build.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cli/commands/build.js b/lib/cli/commands/build.js index bc3edb0a..48c0022a 100644 --- a/lib/cli/commands/build.js +++ b/lib/cli/commands/build.js @@ -131,6 +131,7 @@ async function handleBuild(argv) { if (argv.dependencyDefinition) { graph = await generateProjectGraph.usingStaticFile({ filePath: argv.dependencyDefinition, + rootConfigPath: argv.config, versionOverride: argv.frameworkVersion }); } else {