diff --git a/bin/ui5.cjs b/bin/ui5.cjs index 1c038497..2b84f795 100755 --- a/bin/ui5.cjs +++ b/bin/ui5.cjs @@ -25,25 +25,37 @@ const ui5 = { pkg.engines && pkg.engines.node && (!semver || !semver.satisfies(nodeVersion, pkg.engines.node, {includePrerelease: true})) ) { - console.log("==================== UNSUPPORTED NODE.JS VERSION ===================="); - console.log("You are using an unsupported version of Node.js"); - console.log("Detected version " + nodeVersion + " but " + pkg.name + " requires " + pkg.engines.node); - console.log(""); - console.log("=> Please upgrade to a supported version of Node.js to use this tool"); - console.log("====================================================================="); + process.stderr.write("==================== UNSUPPORTED NODE.JS VERSION ===================="); + process.stderr.write("\n"); + process.stderr.write("You are using an unsupported version of Node.js"); + process.stderr.write("\n"); + process.stderr.write("Detected version " + nodeVersion + + " but " + pkg.name + " requires " + pkg.engines.node); + process.stderr.write("\n\n"); + process.stderr.write("=> Please upgrade to a supported version of Node.js to use this tool"); + process.stderr.write("\n"); + process.stderr.write("====================================================================="); + process.stderr.write("\n"); return false; } if (semver && semver.prerelease(nodeVersion)) { - console.log("====================== UNSTABLE NODE.JS VERSION ====================="); - console.log("You are using an unstable version of Node.js"); - console.log("Detected Node.js version " + nodeVersion); - console.log(""); - console.log("=> Please note that an unstable version might cause unexpected"); - console.log(" behavior. For productive use please consider using a stable"); - console.log(" version of Node.js! For the release policy of Node.js, see"); - console.log(" https://nodejs.org/en/about/releases"); - console.log("====================================================================="); + process.stderr.write("====================== UNSTABLE NODE.JS VERSION ====================="); + process.stderr.write("\n"); + process.stderr.write("You are using an unstable version of Node.js"); + process.stderr.write("\n"); + process.stderr.write("Detected Node.js version " + nodeVersion); + process.stderr.write("\n\n"); + process.stderr.write("=> Please note that an unstable version might cause unexpected"); + process.stderr.write("\n"); + process.stderr.write(" behavior. For productive use please consider using a stable"); + process.stderr.write("\n"); + process.stderr.write(" version of Node.js! For the release policy of Node.js, see"); + process.stderr.write("\n"); + process.stderr.write(" https://nodejs.org/en/about/releases"); + process.stderr.write("\n"); + process.stderr.write("====================================================================="); + process.stderr.write("\n"); } return true; @@ -65,13 +77,14 @@ const ui5 = { return false; } if (process.argv.includes("--verbose")) { - console.info(`INFO: This project contains an individual ${pkg.name} installation which ` + + process.stderr.write(`INFO: This project contains an individual ${pkg.name} installation which ` + "will be used over the global one."); - console.info("See https://github.com/SAP/ui5-cli#local-vs-global-installation for details."); - console.info(""); + process.stderr.write("\n"); + process.stderr.write("See https://github.com/SAP/ui5-cli#local-vs-global-installation for details."); + process.stderr.write("\n\n"); } else { - console.info(`INFO: Using local ${pkg.name} installation`); - console.info(""); + process.stdout.write(`INFO: Using local ${pkg.name} installation`); + process.stdout.write("\n\n"); } return true; }, @@ -98,8 +111,9 @@ module.exports = ui5; if (process.env.NODE_ENV !== "test" || process.env.UI5_CLI_TEST_BIN_RUN_MAIN !== "false") { ui5.main().catch((err) => { - console.log("Fatal Error: Unable to initialize UI5 CLI"); - console.log(err); + process.stderr.write("Fatal Error: Unable to initialize UI5 CLI"); + process.stderr.write("\n"); + process.stderr.write(err); process.exit(1); }); } diff --git a/lib/cli/base.js b/lib/cli/base.js index 7bc4893e..7a649616 100644 --- a/lib/cli/base.js +++ b/lib/cli/base.js @@ -91,37 +91,44 @@ export default function(cli) { ConsoleWriter.stop(); // Exception if (isLogLevelEnabled("error")) { - console.log(""); - console.log(chalk.bold.red("⚠️ Process Failed With Error")); + process.stderr.write("\n"); + process.stderr.write(chalk.bold.red("⚠️ Process Failed With Error")); - console.log(""); - console.log(chalk.underline("Error Message:")); - console.log(err.message); + process.stderr.write("\n\n"); + process.stderr.write(chalk.underline("Error Message:")); + process.stderr.write("\n"); + process.stderr.write(err.message); + process.stderr.write("\n"); // Unexpected errors should always be logged with stack trace const unexpectedErrors = ["SyntaxError", "ReferenceError", "TypeError"]; if (unexpectedErrors.includes(err.name) || isLogLevelEnabled("verbose")) { - console.log(""); - console.log(chalk.underline("Stack Trace:")); - console.log(err.stack); - console.log(""); - console.log( + process.stderr.write("\n\n"); + process.stderr.write(chalk.underline("Stack Trace:")); + process.stderr.write("\n"); + process.stderr.write(err.stack); + process.stderr.write("\n"); + process.stderr.write( chalk.dim( `If you think this is an issue of the UI5 Tooling, you might report it using the ` + `following URL: `) + chalk.dim.bold.underline(`https://github.com/SAP/ui5-tooling/issues/new/choose`)); + process.stderr.write("\n"); } else { - console.log(""); - console.log(chalk.dim( + process.stderr.write("\n\n"); + process.stderr.write(chalk.dim( `For details, execute the same command again with an additional '--verbose' parameter`)); + process.stderr.write("\n"); } } } else { // Yargs error - console.log(chalk.bold.yellow("Command Failed:")); - console.log(`${msg}`); - console.log(""); - console.log(chalk.dim(`See 'ui5 --help'`)); + process.stderr.write(chalk.bold.yellow("Command Failed:")); + process.stderr.write("\n"); + process.stderr.write(`${msg}`); + process.stderr.write("\n\n"); + process.stderr.write(chalk.dim(`See 'ui5 --help'`)); + process.stderr.write("\n"); } process.exit(1); }); diff --git a/lib/cli/commands/add.js b/lib/cli/commands/add.js index e1aa86b8..d8943a60 100644 --- a/lib/cli/commands/add.js +++ b/lib/cli/commands/add.js @@ -75,7 +75,8 @@ addCommand.handler = async function(argv) { ); } } else { - console.log(`Updated configuration written to ${argv.config || "ui5.yaml"}`); + process.stdout.write(`Updated configuration written to ${argv.config || "ui5.yaml"}`); + process.stdout.write("\n"); let logMessage = `Added framework ${library} ${libraryNames.join(" ")} as`; if (development) { logMessage += " development"; @@ -83,7 +84,8 @@ addCommand.handler = async function(argv) { logMessage += " optional"; } logMessage += libraries.length === 1 ? " dependency": " dependencies"; - console.log(logMessage); + process.stdout.write(logMessage); + process.stdout.write("\n"); } }; diff --git a/lib/cli/commands/init.js b/lib/cli/commands/init.js index 7de0d987..1cda44cd 100644 --- a/lib/cli/commands/init.js +++ b/lib/cli/commands/init.js @@ -23,8 +23,10 @@ initCommand.handler = async function() { const yaml = jsYaml.dump(projectConfig, {quotingType: `"`}); await writeFile(yamlPath, yaml); - console.log(`Wrote ui5.yaml to ${yamlPath}:\n`); - console.log(yaml); + process.stdout.write(`Wrote ui5.yaml to ${yamlPath}:`); + process.stdout.write("\n"); + process.stdout.write(yaml); + process.stdout.write("\n"); }; export default initCommand; diff --git a/lib/cli/commands/remove.js b/lib/cli/commands/remove.js index 261e154b..3665c676 100644 --- a/lib/cli/commands/remove.js +++ b/lib/cli/commands/remove.js @@ -59,10 +59,12 @@ removeCommand.handler = async function(argv) { ); } } else { - console.log(`Updated configuration written to ${argv.config || "ui5.yaml"}`); + process.stdout.write(`Updated configuration written to ${argv.config || "ui5.yaml"}`); + process.stdout.write("\n"); let logMessage = `Removed framework ${library} ${libraryNames.join(" ")} as`; logMessage += libraries.length === 1 ? " dependency": " dependencies"; - console.log(logMessage); + process.stdout.write(logMessage); + process.stdout.write("\n"); } }; diff --git a/lib/cli/commands/serve.js b/lib/cli/commands/serve.js index f37efdc1..b2492e4d 100644 --- a/lib/cli/commands/serve.js +++ b/lib/cli/commands/serve.js @@ -150,20 +150,26 @@ serve.handler = async function(argv) { const protocol = h2 ? "https" : "http"; let browserUrl = protocol + "://localhost:" + actualPort; if (argv.acceptRemoteConnections) { - console.log(""); - console.log(chalk.bold("⚠️ This server is accepting connections from all hosts on your network")); - console.log(chalk.dim.underline("Please Note:")); - console.log(chalk.bold.dim( + process.stderr.write("\n"); + process.stderr.write(chalk.bold("⚠️ This server is accepting connections from all hosts on your network")); + process.stderr.write("\n"); + process.stderr.write(chalk.dim.underline("Please Note:")); + process.stderr.write("\n"); + process.stderr.write(chalk.bold.dim( "* This server is intended for development purposes only. Do not use it in production.")); - console.log(chalk.dim( + process.stderr.write("\n"); + process.stderr.write(chalk.dim( "* Vulnerable (custom-)middleware can pose a threat to your system when exposed to the network")); - console.log(chalk.dim( + process.stderr.write("\n"); + process.stderr.write(chalk.dim( "* The use of proxy-middleware with preconfigured credentials might enable unauthorized access " + "to a target system for third parties on your network")); - console.log(""); + process.stderr.write("\n\n"); } - console.log("Server started"); - console.log("URL: " + browserUrl); + process.stdout.write("Server started"); + process.stdout.write("\n"); + process.stdout.write("URL: " + browserUrl); + process.stdout.write("\n"); if (argv.open !== undefined) { if (typeof argv.open === "string") { diff --git a/lib/cli/commands/tree.js b/lib/cli/commands/tree.js index 94e4c22f..e683a72b 100644 --- a/lib/cli/commands/tree.js +++ b/lib/cli/commands/tree.js @@ -90,12 +90,13 @@ tree.handler = async function(argv) { if (project.isFrameworkProject()) { name = chalk.blue(name); } - console.log( + process.stdout.write( `${baseString}${connectorString} ${name} ` + `${project.getNamespace() ? chalk.inverse(project.getNamespace()) + " " : ""}` + chalk.dim(`(${project.getVersion()}, ${project.getType()}) `) + chalk.dim.italic(`${project.getRootPath()}`) ); + process.stdout.write("\n"); const lastIdx = dependencies.length - 1; const newConnectorIndices = [...connectorIndices]; @@ -109,7 +110,8 @@ tree.handler = async function(argv) { newConnectorIndices.forEach((idx) => { nextBaseString = `${nextBaseString.slice(0, idx)}│${nextBaseString.slice(idx + 1)}`; }); - console.log(`${nextBaseString}╰─ ${msg}`); + process.stdout.write(`${nextBaseString}╰─ ${msg}`); + process.stdout.write("\n"); return; } if (renderDeps) { @@ -121,7 +123,8 @@ tree.handler = async function(argv) { }); }); - console.log(chalk.bold.underline(`Dependencies (${projects.size}):`)); + process.stdout.write(chalk.bold.underline(`Dependencies (${projects.size}):`)); + process.stdout.write("\n"); if (argv.flat) { // Iterate over list of projects, rendering each individually // We need to transform the map into an array in order to know the index @@ -133,28 +136,32 @@ tree.handler = async function(argv) { // Recursively render the tree, starting with the first entry of the map projects.values().next().value.render(0, [], true); } - console.log(""); + process.stdout.write("\n"); const extensionNames = graph.getExtensionNames(); const extensionCount = extensionNames.length; - console.log(chalk.bold.underline(`Extensions (${extensionCount}):`)); + process.stdout.write(chalk.bold.underline(`Extensions (${extensionCount}):`)); + process.stdout.write("\n"); if (extensionCount) { const lastIdx = extensionCount - 1; extensionNames.forEach((extensionName, idx) => { const extension = graph.getExtension(extensionName); const connectorString = idx === lastIdx ? "╰─" : "├─"; - console.log( + process.stdout.write( `${connectorString} ${extensionName} ` + chalk.dim(`(${extension.getVersion()}, ${extension.getType()}) `) + chalk.dim.italic(`${extension.getRootPath()}`)); + process.stdout.write("\n"); }); } else { - console.log(chalk.italic(`None`)); + process.stdout.write(chalk.italic(`None`)); + process.stdout.write("\n"); } if (argv.perf) { - console.log(""); - console.log(chalk.blue( + process.stderr.write("\n"); + process.stderr.write(chalk.blue( `Dependency graph generation took ${chalk.bold(elapsedTime)}`)); + process.stderr.write("\n"); } }; diff --git a/lib/cli/commands/use.js b/lib/cli/commands/use.js index a8904cad..f30f64d7 100644 --- a/lib/cli/commands/use.js +++ b/lib/cli/commands/use.js @@ -81,8 +81,10 @@ useCommand.handler = async function(argv) { throw new Error(`Internal error while updating ui5.yaml to ${usedFramework} version ${usedVersion}`); } } else { - console.log(`Updated configuration written to ${argv.config || "ui5.yaml"}`); - console.log(`This project is now using ${usedFramework} version ${usedVersion}`); + process.stdout.write(`Updated configuration written to ${argv.config || "ui5.yaml"}`); + process.stdout.write("\n"); + process.stdout.write(`This project is now using ${usedFramework} version ${usedVersion}`); + process.stdout.write("\n"); } }; diff --git a/lib/cli/commands/versions.js b/lib/cli/commands/versions.js index 0e671d27..c8b17a0a 100644 --- a/lib/cli/commands/versions.js +++ b/lib/cli/commands/versions.js @@ -34,7 +34,7 @@ versions.handler = async function() { }) )).join("\n"); - console.log(`\n${output}\n`); + process.stdout.write(`\n${output}\n\n`); }; export default versions; diff --git a/test/bin/ui5.js b/test/bin/ui5.js index ee1a85cc..65388898 100644 --- a/test/bin/ui5.js +++ b/test/bin/ui5.js @@ -34,8 +34,8 @@ test.before(() => { test.beforeEach(async (t) => { const sinon = t.context.sinon = sinonGlobal.createSandbox(); - t.context.consoleLogStub = sinon.stub(console, "log"); - t.context.consoleInfoStub = sinon.stub(console, "info"); + t.context.processStdoutWriteStub = sinon.stub(process.stdout, "write"); + t.context.processStderrWriteStub = sinon.stub(process.stderr, "write"); t.context.originalArgv = process.argv; process.env.UI5_CLI_TEST_BIN_RUN_MAIN = "false"; // prevent automatic execution of main function @@ -62,7 +62,7 @@ test.afterEach.always((t) => { }); test.serial("checkRequirements: Using supported Node.js version", (t) => { - const {consoleLogStub} = t.context; + const {processStderrWriteStub} = t.context; const {checkRequirements} = require("../../bin/ui5.cjs"); @@ -77,11 +77,11 @@ test.serial("checkRequirements: Using supported Node.js version", (t) => { }); t.true(returnValue); - t.is(consoleLogStub.callCount, 0, "console.log should not be called"); + t.is(processStderrWriteStub.callCount, 0, "stderr info should not be provided"); }); test.serial("checkRequirements: Using unsupported Node.js version", (t) => { - const {consoleLogStub} = t.context; + const {processStderrWriteStub} = t.context; const {checkRequirements} = require("../../bin/ui5.cjs"); @@ -96,24 +96,23 @@ test.serial("checkRequirements: Using unsupported Node.js version", (t) => { }); t.false(returnValue); - t.is(consoleLogStub.callCount, 6, "console.log should be called 6 times"); + t.is(processStderrWriteStub.callCount, 10, "console.log should be called 6 times"); - t.deepEqual(consoleLogStub.getCall(0).args, + t.deepEqual(processStderrWriteStub.getCall(0).args, ["==================== UNSUPPORTED NODE.JS VERSION ===================="]); - t.deepEqual(consoleLogStub.getCall(1).args, + t.deepEqual(processStderrWriteStub.getCall(2).args, ["You are using an unsupported version of Node.js"]); - t.deepEqual(consoleLogStub.getCall(2).args, + t.deepEqual(processStderrWriteStub.getCall(4).args, [`Detected version v10.0.0 but ui5-cli-engines-test requires ^999`]); - t.deepEqual(consoleLogStub.getCall(3).args, - [""]); - t.deepEqual(consoleLogStub.getCall(4).args, + t.deepEqual(processStderrWriteStub.getCall(5).args, ["\n\n"]); + t.deepEqual(processStderrWriteStub.getCall(6).args, ["=> Please upgrade to a supported version of Node.js to use this tool"]); - t.deepEqual(consoleLogStub.getCall(5).args, + t.deepEqual(processStderrWriteStub.getCall(8).args, ["====================================================================="]); }); test.serial("checkRequirements: logs warning when using pre-release Node.js version", (t) => { - const {consoleLogStub} = t.context; + const {processStderrWriteStub} = t.context; const {checkRequirements} = require("../../bin/ui5.cjs"); @@ -128,30 +127,29 @@ test.serial("checkRequirements: logs warning when using pre-release Node.js vers }); t.true(returnValue); - t.is(consoleLogStub.callCount, 9, "console.log should be called 8 times"); + t.is(processStderrWriteStub.callCount, 16, "console.log should be called 16 times"); - t.is(consoleLogStub.getCall(0).args[0], + t.is(processStderrWriteStub.getCall(0).args[0], "====================== UNSTABLE NODE.JS VERSION ====================="); - t.is(consoleLogStub.getCall(1).args[0], + t.is(processStderrWriteStub.getCall(2).args[0], "You are using an unstable version of Node.js"); - t.is(consoleLogStub.getCall(2).args[0], + t.is(processStderrWriteStub.getCall(4).args[0], "Detected Node.js version v17.0.0-v8-canary202108258414d1aed8"); - t.is(consoleLogStub.getCall(3).args[0], - ""); - t.is(consoleLogStub.getCall(4).args[0], + t.is(processStderrWriteStub.getCall(5).args[0], "\n\n"); + t.is(processStderrWriteStub.getCall(6).args[0], "=> Please note that an unstable version might cause unexpected"); - t.is(consoleLogStub.getCall(5).args[0], + t.is(processStderrWriteStub.getCall(8).args[0], " behavior. For productive use please consider using a stable"); - t.is(consoleLogStub.getCall(6).args[0], + t.is(processStderrWriteStub.getCall(10).args[0], " version of Node.js! For the release policy of Node.js, see"); - t.is(consoleLogStub.getCall(7).args[0], + t.is(processStderrWriteStub.getCall(12).args[0], " https://nodejs.org/en/about/releases"); - t.is(consoleLogStub.getCall(8).args[0], + t.is(processStderrWriteStub.getCall(14).args[0], "====================================================================="); }); test.serial("invokeLocalInstallation: Invokes local installation when found", async (t) => { - const {consoleLogStub, consoleInfoStub} = t.context; + const {processStdoutWriteStub} = t.context; importLocalStub.returns({}); @@ -161,11 +159,10 @@ test.serial("invokeLocalInstallation: Invokes local installation when found", as t.true(returnValue); - t.is(consoleLogStub.callCount, 0, "console.log should not be called"); - t.is(consoleInfoStub.callCount, 2, "console.info should be called 2 times"); + t.is(processStdoutWriteStub.callCount, 2, "Information messages should be provided"); - t.deepEqual(consoleInfoStub.getCall(0).args, ["INFO: Using local ui5-cli-test installation"]); - t.deepEqual(consoleInfoStub.getCall(1).args, [""]); + t.deepEqual(processStdoutWriteStub.getCall(0).args, ["INFO: Using local ui5-cli-test installation"]); + t.deepEqual(processStdoutWriteStub.getCall(1).args, ["\n\n"]); t.is(importLocalStub.callCount, 1, "import-local should be called once"); t.is(importLocalStub.getCall(0).args.length, 1); @@ -175,7 +172,7 @@ test.serial("invokeLocalInstallation: Invokes local installation when found", as }); test.serial("invokeLocalInstallation: Invokes local installation when found (/w --verbose)", async (t) => { - const {consoleLogStub, consoleInfoStub} = t.context; + const {processStderrWriteStub} = t.context; importLocalStub.returns({}); @@ -188,16 +185,15 @@ test.serial("invokeLocalInstallation: Invokes local installation when found (/w t.true(returnValue); - t.is(consoleLogStub.callCount, 0, "console.log should not be called"); - t.is(consoleInfoStub.callCount, 3, "console.info should be called 3 times"); + t.is(processStderrWriteStub.callCount, 4, "console.info should be called 3 times"); - t.deepEqual(consoleInfoStub.getCall(0).args, [ + t.deepEqual(processStderrWriteStub.getCall(0).args, [ "INFO: This project contains an individual ui5-cli-test installation which " + "will be used over the global one."]); - t.deepEqual(consoleInfoStub.getCall(1).args, [ + t.deepEqual(processStderrWriteStub.getCall(2).args, [ "See https://github.com/SAP/ui5-cli#local-vs-global-installation for details." ]); - t.deepEqual(consoleInfoStub.getCall(2).args, [""]); + t.deepEqual(processStderrWriteStub.getCall(3).args, ["\n\n"]); t.is(importLocalStub.callCount, 1, "import-local should be called once"); t.is(importLocalStub.getCall(0).args.length, 1); @@ -207,7 +203,7 @@ test.serial("invokeLocalInstallation: Invokes local installation when found (/w }); test.serial("invokeLocalInstallation: Doesn't invoke local installation when UI5_CLI_NO_LOCAL is set", async (t) => { - const {consoleLogStub, consoleInfoStub} = t.context; + const {processStderrWriteStub} = t.context; process.env.UI5_CLI_NO_LOCAL = "true"; @@ -217,14 +213,13 @@ test.serial("invokeLocalInstallation: Doesn't invoke local installation when UI5 t.false(returnValue); - t.is(consoleLogStub.callCount, 0, "console.log should not be called"); - t.is(consoleInfoStub.callCount, 0, "console.info should not be called"); + t.is(processStderrWriteStub.callCount, 0, "Information messages should be provided"); t.is(importLocalStub.callCount, 0, "import-local should not be called"); }); test.serial("invokeLocalInstallation: Doesn't invoke local installation when it is not found", async (t) => { - const {consoleLogStub, consoleInfoStub} = t.context; + const {processStderrWriteStub} = t.context; importLocalStub.returns(undefined); @@ -234,14 +229,13 @@ test.serial("invokeLocalInstallation: Doesn't invoke local installation when it t.false(returnValue); - t.is(consoleLogStub.callCount, 0, "console.log should not be called"); - t.is(consoleInfoStub.callCount, 0, "console.info should not be called"); + t.is(processStderrWriteStub.callCount, 0, "stderr info should not be provided"); t.is(importLocalStub.callCount, 1, "import-local should be called"); }); test.serial("main (unsupported Node.js version)", async (t) => { - const {sinon, consoleLogStub} = t.context; + const {sinon, processStderrWriteStub} = t.context; const processExit = new Promise((resolve) => { const processExitStub = sinon.stub(process, "exit"); @@ -263,7 +257,7 @@ test.serial("main (unsupported Node.js version)", async (t) => { const errorCode = await processExit; t.is(errorCode, 1); - t.is(consoleLogStub.callCount, 0); + t.is(processStderrWriteStub.callCount, 0); t.is(ui5.checkRequirements.callCount, 1); t.is(ui5.invokeLocalInstallation.callCount, 0); @@ -271,7 +265,7 @@ test.serial("main (unsupported Node.js version)", async (t) => { }); test.serial("main (invocation of local installation)", async (t) => { - const {sinon, consoleLogStub} = t.context; + const {sinon, processStderrWriteStub} = t.context; const processExitStub = sinon.stub(process, "exit"); @@ -284,7 +278,7 @@ test.serial("main (invocation of local installation)", async (t) => { await main(); t.is(processExitStub.callCount, 0); - t.is(consoleLogStub.callCount, 0); + t.is(processStderrWriteStub.callCount, 0); t.is(ui5.invokeLocalInstallation.callCount, 1); t.is(ui5.invokeCLI.callCount, 0); @@ -297,7 +291,7 @@ test.serial("integration: main / invokeCLI", async (t) => { // Therefore this test is an integration test that really invokes the CLI / yargs to // fail with an unknown command error - const {sinon, consoleLogStub} = t.context; + const {sinon, processStderrWriteStub} = t.context; const processExit = new Promise((resolve) => { const processExitStub = sinon.stub(process, "exit"); @@ -316,14 +310,14 @@ test.serial("integration: main / invokeCLI", async (t) => { const errorCode = await processExit; t.is(errorCode, 1); - t.is(consoleLogStub.callCount, 4); + t.is(processStderrWriteStub.callCount, 6); - t.deepEqual(consoleLogStub.getCall(0).args, [chalk.bold.yellow("Command Failed:")], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(1).args, ["Unknown argument: foo"], "Correct error log"); + t.deepEqual(processStderrWriteStub.getCall(0).args, [chalk.bold.yellow("Command Failed:")], "Correct error log"); + t.deepEqual(processStderrWriteStub.getCall(2).args, ["Unknown argument: foo"], "Correct error log"); }); test.serial("integration: Executing main when required as main module", async (t) => { - const {sinon, consoleLogStub} = t.context; + const {sinon, processStderrWriteStub} = t.context; const processExit = new Promise((resolve) => { const processExitStub = sinon.stub(process, "exit"); @@ -342,14 +336,14 @@ test.serial("integration: Executing main when required as main module", async (t const errorCode = await processExit; t.is(errorCode, 1); - t.is(consoleLogStub.callCount, 4); + t.is(processStderrWriteStub.callCount, 6); - t.deepEqual(consoleLogStub.getCall(0).args, [chalk.bold.yellow("Command Failed:")], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(1).args, ["Unknown argument: foo"], "Correct error log"); + t.deepEqual(processStderrWriteStub.getCall(0).args, [chalk.bold.yellow("Command Failed:")], "Correct error log"); + t.deepEqual(processStderrWriteStub.getCall(2).args, ["Unknown argument: foo"], "Correct error log"); }); test.serial("integration: Executing main when required as main module (catch initialize error)", async (t) => { - const {sinon, consoleLogStub} = t.context; + const {sinon, processStderrWriteStub} = t.context; const processExit = new Promise((resolve) => { const processExitStub = sinon.stub(process, "exit"); @@ -372,10 +366,10 @@ test.serial("integration: Executing main when required as main module (catch ini const errorCode = await processExit; t.is(errorCode, 1); - t.is(consoleLogStub.callCount, 2); + t.is(processStderrWriteStub.callCount, 3); - t.deepEqual(consoleLogStub.getCall(0).args, ["Fatal Error: Unable to initialize UI5 CLI"]); - t.is(consoleLogStub.getCall(1).args.length, 1); - t.true(consoleLogStub.getCall(1).args[0] instanceof Error); - t.is(consoleLogStub.getCall(1).args[0].message, "TEST: Unable to invoke CLI"); + t.deepEqual(processStderrWriteStub.getCall(0).args, ["Fatal Error: Unable to initialize UI5 CLI"]); + t.is(processStderrWriteStub.getCall(2).args.length, 1); + t.true(processStderrWriteStub.getCall(2).args[0] instanceof Error); + t.is(processStderrWriteStub.getCall(2).args[0].message, "TEST: Unable to invoke CLI"); }); diff --git a/test/lib/cli/base.js b/test/lib/cli/base.js index f9ce0f3d..2ada72fe 100644 --- a/test/lib/cli/base.js +++ b/test/lib/cli/base.js @@ -15,7 +15,7 @@ const ui5Cli = path.join(__dirname, "..", "..", "..", "bin", "ui5.cjs"); const ui5 = (args, options = {}) => execa(ui5Cli, args, options); test.beforeEach(async (t) => { - t.context.consoleLogStub = sinon.stub(console, "log"); + t.context.consoleLogStub = sinon.stub(process.stderr, "write"); t.context.isLogLevelEnabledStub = sinon.stub(); t.context.isLogLevelEnabledStub.withArgs("error").returns(true); @@ -48,7 +48,7 @@ test.serial("ui5 -v", async (t) => { test.serial("Handle multiple options", async (t) => { const {stderr} = await ui5(["versions", "--log-level", "silent", "--log-level", "silly"]); - t.regex(stripAnsi(stderr), /^verb/, "Verbose logging got enabled"); + t.regex(stripAnsi(stderr), /verb/g, "Verbose logging got enabled"); }); test.serial("Yargs error handling", async (t) => { @@ -80,11 +80,11 @@ test.serial("Yargs error handling", async (t) => { t.is(errorCode, 1, "Should exit with error code 1"); t.is(consoleWriterStopStub.callCount, 0, "ConsoleWriter.stop did not get called"); - t.is(consoleLogStub.callCount, 4); + t.is(consoleLogStub.callCount, 6); t.deepEqual(consoleLogStub.getCall(0).args, [chalk.bold.yellow("Command Failed:")], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(1).args, ["Unknown argument: invalid"], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(2).args, [""], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(3).args, [ + t.deepEqual(consoleLogStub.getCall(2).args, ["Unknown argument: invalid"], "Correct error log"); + t.deepEqual(consoleLogStub.getCall(3).args, ["\n\n"], "Correct error log"); + t.deepEqual(consoleLogStub.getCall(4).args, [ chalk.dim(`See 'ui5 --help'`) ], "Correct error log"); }); @@ -123,12 +123,12 @@ test.serial("Exception error handling", async (t) => { t.is(errorCode, 1, "Should exit with error code 1"); t.is(consoleWriterStopStub.callCount, 1, "ConsoleWriter.stop got called once"); - t.is(consoleLogStub.callCount, 7); + t.is(consoleLogStub.callCount, 10); t.deepEqual(consoleLogStub.getCall(1).args, [chalk.bold.red("⚠️ Process Failed With Error")], "Correct error log"); t.deepEqual(consoleLogStub.getCall(3).args, [chalk.underline("Error Message:")], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(4).args, + t.deepEqual(consoleLogStub.getCall(5).args, ["Some error from foo command"], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(6).args, [chalk.dim( + t.deepEqual(consoleLogStub.getCall(8).args, [chalk.dim( `For details, execute the same command again with an additional '--verbose' parameter`)], "Correct error log"); }); @@ -204,17 +204,17 @@ test.serial("Exception error handling with verbose logging", async (t) => { const errorCode = await processExit; t.is(errorCode, 1, "Should exit with error code 1"); - t.is(consoleLogStub.callCount, 10); + t.is(consoleLogStub.callCount, 14); t.deepEqual(consoleLogStub.getCall(1).args, [chalk.bold.red("⚠️ Process Failed With Error")], "Correct error log"); t.deepEqual(consoleLogStub.getCall(3).args, [chalk.underline("Error Message:")], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(4).args, + t.deepEqual(consoleLogStub.getCall(5).args, ["Some error from foo command"], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(6).args, [chalk.underline("Stack Trace:")], "Correct error log"); - t.is(consoleLogStub.getCall(7).args.length, 1); - t.true(consoleLogStub.getCall(7).args[0] + t.deepEqual(consoleLogStub.getCall(8).args, [chalk.underline("Stack Trace:")], "Correct error log"); + t.is(consoleLogStub.getCall(10).args.length, 1); + t.true(consoleLogStub.getCall(10).args[0] .startsWith("Error: Some error from foo command"), "Correct error log"); - t.deepEqual(consoleLogStub.getCall(consoleLogStub.callCount - 1).args, + t.deepEqual(consoleLogStub.getCall(consoleLogStub.callCount - 2).args, [chalk.dim( `If you think this is an issue of the UI5 Tooling, you might report it using the ` + `following URL: `) + @@ -257,17 +257,17 @@ test.serial("Unexpected error handling", async (t) => { t.is(errorCode, 1, "Should exit with error code 1"); t.is(consoleWriterStopStub.callCount, 0, "ConsoleWriter.stop did not get called"); - t.is(consoleLogStub.callCount, 10); + t.is(consoleLogStub.callCount, 14); t.deepEqual(consoleLogStub.getCall(1).args, [chalk.bold.red("⚠️ Process Failed With Error")], "Correct error log"); t.deepEqual(consoleLogStub.getCall(3).args, [chalk.underline("Error Message:")], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(4).args, + t.deepEqual(consoleLogStub.getCall(5).args, ["Cannot do this"], "Correct error log"); - t.deepEqual(consoleLogStub.getCall(6).args, [chalk.underline("Stack Trace:")], "Correct error log"); - t.is(consoleLogStub.getCall(7).args.length, 1); - t.true(consoleLogStub.getCall(7).args[0] + t.deepEqual(consoleLogStub.getCall(8).args, [chalk.underline("Stack Trace:")], "Correct error log"); + t.is(consoleLogStub.getCall(10).args.length, 1); + t.true(consoleLogStub.getCall(10).args[0] .startsWith("TypeError: Cannot do this"), "Correct error log"); - t.deepEqual(consoleLogStub.getCall(consoleLogStub.callCount - 1).args, + t.deepEqual(consoleLogStub.getCall(consoleLogStub.callCount - 2).args, [chalk.dim( `If you think this is an issue of the UI5 Tooling, you might report it using the ` + `following URL: `) + diff --git a/test/lib/cli/commands/add.js b/test/lib/cli/commands/add.js index b5ccf09b..2f22163e 100644 --- a/test/lib/cli/commands/add.js +++ b/test/lib/cli/commands/add.js @@ -24,10 +24,10 @@ async function assertAddHandler(t, {argv, expectedLibraries, expectedConsoleLog} }], "Add function should be called with expected args"); - t.is(t.context.consoleLogStub.callCount, expectedConsoleLog.length, + t.is(t.context.processStdoutStub.callCount, expectedConsoleLog.length, "console.log should be called " + expectedConsoleLog.length + " times"); expectedConsoleLog.forEach((expectedLog, i) => { - t.deepEqual(t.context.consoleLogStub.getCall(i).args, [expectedLog], + t.deepEqual(t.context.processStdoutStub.getCall(i).args, [expectedLog], "console.log should be called with expected string on call index " + i); }); } @@ -49,7 +49,7 @@ async function assertFailingYamlUpdateAddHandler(t, {argv, expectedMessage}) { } test.beforeEach((t) => { - t.context.consoleLogStub = sinon.stub(console, "log"); + t.context.processStdoutStub = sinon.stub(process.stdout, "write"); }); test.afterEach.always((t) => { @@ -63,7 +63,9 @@ test.serial("Accepts single library", async (t) => { expectedLibraries: [{name: "sap.ui.lib1"}], expectedConsoleLog: [ "Updated configuration written to ui5.yaml", - "Added framework library sap.ui.lib1 as dependency" + "\n", + "Added framework library sap.ui.lib1 as dependency", + "\n", ] }); }); @@ -74,7 +76,9 @@ test.serial("Accepts multiple libraries", async (t) => { expectedLibraries: [{name: "sap.ui.lib1"}, {name: "sap.ui.lib2"}], expectedConsoleLog: [ "Updated configuration written to ui5.yaml", - "Added framework libraries sap.ui.lib1 sap.ui.lib2 as dependencies" + "\n", + "Added framework libraries sap.ui.lib1 sap.ui.lib2 as dependencies", + "\n", ] }); }); @@ -100,7 +104,9 @@ test.serial("Accepts multiple libraries (--development)", async (t) => { ], expectedConsoleLog: [ "Updated configuration written to ui5.yaml", - "Added framework libraries sap.ui.lib1 sap.ui.lib2 as development dependencies" + "\n", + "Added framework libraries sap.ui.lib1 sap.ui.lib2 as development dependencies", + "\n" ] }); }); @@ -126,7 +132,9 @@ test.serial("Accepts multiple libraries (--optional)", async (t) => { ], expectedConsoleLog: [ "Updated configuration written to ui5.yaml", - "Added framework libraries sap.ui.lib1 sap.ui.lib2 as optional dependencies" + "\n", + "Added framework libraries sap.ui.lib1 sap.ui.lib2 as optional dependencies", + "\n" ] }); }); diff --git a/test/lib/cli/commands/config.js b/test/lib/cli/commands/config.js index 05cf77f8..1b57e0b2 100644 --- a/test/lib/cli/commands/config.js +++ b/test/lib/cli/commands/config.js @@ -227,6 +227,7 @@ test.serial("ui5 config set false should update the configuration", async (t) => test.serial("ui5 config invalid option", async (t) => { await t.throwsAsync(ui5(["config", "set", "_invalid_key_", "https://_snapshot_endpoint_"]), { message: ($) => { + // stderr might include debug information return $.includes("Command failed with exit code 1") && $.includes("Invalid values") && $.includes(`Given: "_invalid_key_", Choices: "`); @@ -236,22 +237,27 @@ test.serial("ui5 config invalid option", async (t) => { test.serial("ui5 config empty option", async (t) => { await t.throwsAsync(ui5(["config", "set"]), { - message: ($) => stripAnsi($) === - `Command failed with exit code 1: ${ui5Cli} config set -Command Failed: + message: ($) => { + const message = stripAnsi($); + // stderr might include debug information + return message.includes(`Command failed with exit code 1: ${ui5Cli} config set`) && + message.includes(`Command Failed: Not enough non-option arguments: got 0, need at least 1 -See 'ui5 --help'` +See 'ui5 --help'`); + } }); }); test.serial("ui5 config unknown command", async (t) => { await t.throwsAsync(ui5(["config", "foo"]), { - message: ($) => stripAnsi($) === - `Command failed with exit code 1: ${ui5Cli} config foo -Command Failed: + message: ($) => { + const message = stripAnsi($); + return message.includes(`Command failed with exit code 1: ${ui5Cli} config foo`) && + message.includes(`Command Failed: Unknown argument: foo -See 'ui5 --help'` +See 'ui5 --help'`); + } }); }); diff --git a/test/lib/cli/commands/remove.js b/test/lib/cli/commands/remove.js index 360534e1..a2b0afbb 100644 --- a/test/lib/cli/commands/remove.js +++ b/test/lib/cli/commands/remove.js @@ -24,10 +24,10 @@ async function assertRemoveHandler(t, {argv, expectedLibraries, expectedConsoleL }], "Remove function should be called with expected args"); - t.is(t.context.consoleLogStub.callCount, expectedConsoleLog.length, + t.is(t.context.processStdoutStub.callCount, expectedConsoleLog.length, "console.log should be called " + expectedConsoleLog.length + " times"); expectedConsoleLog.forEach((expectedLog, i) => { - t.deepEqual(t.context.consoleLogStub.getCall(i).args, [expectedLog], + t.deepEqual(t.context.processStdoutStub.getCall(i).args, [expectedLog], "console.log should be called with expected string on call index " + i); }); } @@ -50,7 +50,7 @@ async function assertFailingYamlUpdateRemoveHandler(t, {argv, expectedMessage}) } test.beforeEach((t) => { - t.context.consoleLogStub = sinon.stub(console, "log"); + t.context.processStdoutStub = sinon.stub(process.stdout, "write"); }); test.afterEach.always((t) => { @@ -64,7 +64,9 @@ test.serial("Accepts single library", async (t) => { expectedLibraries: [{name: "sap.ui.lib1"}], expectedConsoleLog: [ "Updated configuration written to ui5.yaml", - "Removed framework library sap.ui.lib1 as dependency" + "\n", + "Removed framework library sap.ui.lib1 as dependency", + "\n" ] }); }); @@ -75,7 +77,9 @@ test.serial("Accepts multiple libraries", async (t) => { expectedLibraries: [{name: "sap.ui.lib1"}, {name: "sap.ui.lib2"}], expectedConsoleLog: [ "Updated configuration written to ui5.yaml", - "Removed framework libraries sap.ui.lib1 sap.ui.lib2 as dependencies" + "\n", + "Removed framework libraries sap.ui.lib1 sap.ui.lib2 as dependencies", + "\n" ] }); }); @@ -86,7 +90,9 @@ test.serial("Accepts multiple libraries duplicates", async (t) => { expectedLibraries: [{name: "sap.ui.lib1"}, {name: "sap.ui.lib2"}], expectedConsoleLog: [ "Updated configuration written to ui5.yaml", - "Removed framework libraries sap.ui.lib1 sap.ui.lib2 as dependencies" + "\n", + "Removed framework libraries sap.ui.lib1 sap.ui.lib2 as dependencies", + "\n" ] }); }); diff --git a/test/lib/cli/commands/serve.js b/test/lib/cli/commands/serve.js index 4b0fc883..7284c394 100644 --- a/test/lib/cli/commands/serve.js +++ b/test/lib/cli/commands/serve.js @@ -59,9 +59,13 @@ test.beforeEach(async (t) => { t.context.consoleOutput = ""; - t.context.consoleLog = sinon.stub(console, "log").callsFake((message) => { + t.context.processStderrWrite = sinon.stub(process.stderr, "write").callsFake((message) => { // NOTE: This fake impl only supports one string arg passed to console.log - t.context.consoleOutput += message + "\n"; + t.context.consoleOutput += message; + }); + t.context.processStdoutWrite = sinon.stub(process.stdout, "write").callsFake((message) => { + // NOTE: This fake impl only supports one string arg passed to console.log + t.context.consoleOutput += message; }); t.context.open = sinon.stub(); diff --git a/test/lib/cli/commands/tree.js b/test/lib/cli/commands/tree.js index a7ad189f..f8e8fcad 100644 --- a/test/lib/cli/commands/tree.js +++ b/test/lib/cli/commands/tree.js @@ -38,9 +38,11 @@ test.beforeEach(async (t) => { }; 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.processStderrWrite = sinon.stub(process.stderr, "write").callsFake((message) => { + t.context.consoleOutput += message; + }); + t.context.processStdoutWrite = sinon.stub(process.stdout, "write").callsFake((message) => { + t.context.consoleOutput += message; }); t.context.tree = await esmock.p("../../../../lib/cli/commands/tree.js", { diff --git a/test/lib/cli/commands/use.js b/test/lib/cli/commands/use.js index b6b20cb0..061ec786 100644 --- a/test/lib/cli/commands/use.js +++ b/test/lib/cli/commands/use.js @@ -55,7 +55,7 @@ async function assertFailingYamlUpdateUseHandler(t, {argv, expectedMessage}) { } test.beforeEach(async (t) => { - t.context.consoleLogStub = sinon.stub(console, "log"); + t.context.processStdoutStub = sinon.stub(process.stdout, "write"); t.context.frameworkUseStub = sinon.stub(); @@ -271,13 +271,15 @@ test.serial("Logs framework name, version and default config path when updating const expectedConsoleLog = [ "Updated configuration written to ui5.yaml", - "This project is now using SAPUI5 version 1.76.0" + "\n", + "This project is now using SAPUI5 version 1.76.0", + "\n" ]; - t.is(t.context.consoleLogStub.callCount, expectedConsoleLog.length, + t.is(t.context.processStdoutStub.callCount, expectedConsoleLog.length, "console.log should be called " + expectedConsoleLog.length + " times"); expectedConsoleLog.forEach((expectedLog, i) => { - t.deepEqual(t.context.consoleLogStub.getCall(i).args, [expectedLog], + t.deepEqual(t.context.processStdoutStub.getCall(i).args, [expectedLog], "console.log should be called with expected string on call index " + i); }); }); @@ -295,13 +297,15 @@ test.serial("Logs framework name, version and custom config path when updating c const expectedConsoleLog = [ "Updated configuration written to /path/to/ui5.yaml", - "This project is now using SAPUI5 version 1.76.0" + "\n", + "This project is now using SAPUI5 version 1.76.0", + "\n" ]; - t.is(t.context.consoleLogStub.callCount, expectedConsoleLog.length, + t.is(t.context.processStdoutStub.callCount, expectedConsoleLog.length, "console.log should be called " + expectedConsoleLog.length + " times"); expectedConsoleLog.forEach((expectedLog, i) => { - t.deepEqual(t.context.consoleLogStub.getCall(i).args, [expectedLog], + t.deepEqual(t.context.processStdoutStub.getCall(i).args, [expectedLog], "console.log should be called with expected string on call index " + i); }); }); diff --git a/test/lib/cli/middlewares/logger.js b/test/lib/cli/middlewares/logger.js index ed688a6d..0727aa8d 100644 --- a/test/lib/cli/middlewares/logger.js +++ b/test/lib/cli/middlewares/logger.js @@ -102,7 +102,8 @@ const ui5 = (args, options = {}) => execa(ui5Cli, args, options); test("ui5 --verbose", async (t) => { // Using "versions" as a command, as the --verbose flag can't be used standalone const {stderr} = await ui5(["versions", "--verbose"]); - t.is(stripAnsi(stderr), + // Debugger info is also included into the message. We need to exclude it + t.true(stripAnsi(stderr).includes( `verb cli:middlewares:base using @ui5/cli version ${pkg.version} (from ${ui5Cli})\n`+ - `verb cli:middlewares:base using node version ${process.version}`); + `verb cli:middlewares:base using node version ${process.version}`)); });