diff --git a/src/cli/init-config/index.mjs b/src/cli/init-config/index.mjs index 12d8d91ae..1ffe88d60 100644 --- a/src/cli/init-config/index.mjs +++ b/src/cli/init-config/index.mjs @@ -75,8 +75,14 @@ function manifestIsUpdatable(pNormalizedInitConfig) { /** * @param {boolean|import("./types").OneShotConfigIDType} pInit * @param {string=} pConfigFileName + * @param {{stdout: NodeJS.WritableStream, stderr: NodeJS.WritableStream}=} pStreams */ -export default function initConfig(pInit, pConfigFileName) { +export default function initConfig(pInit, pConfigFileName, pStreams) { + const lStreams = { + stdout: process.stdout, + stderr: process.stderr, + ...pStreams, + }; /* c8 ignore start */ if (pInit === true) { getUserInput() @@ -84,7 +90,7 @@ export default function initConfig(pInit, pConfigFileName) { .then(buildConfig) .then(writeConfig) .catch((pError) => { - process.stderr.write(`\n ERROR: ${pError.message}\n`); + lStreams.stderr.write(`\n ERROR: ${pError.message}\n`); }); /* c8 ignore stop */ } else if (pInit !== false) { @@ -92,11 +98,17 @@ export default function initConfig(pInit, pConfigFileName) { const lConfigFileName = pConfigFileName || getDefaultConfigFileName(); if (!fileExists(lConfigFileName)) { - writeConfig(buildConfig(lNormalizedInitConfig), lConfigFileName); + writeConfig( + buildConfig(lNormalizedInitConfig), + lConfigFileName, + lStreams.stdout, + ); } if (manifestIsUpdatable(lNormalizedInitConfig)) { - writeRunScriptsToManifest(lNormalizedInitConfig); + writeRunScriptsToManifest(lNormalizedInitConfig, { + outStream: lStreams.stdout, + }); } } } diff --git a/src/cli/init-config/write-config.mjs b/src/cli/init-config/write-config.mjs index 7aaf51a3e..0f30bfdc4 100644 --- a/src/cli/init-config/write-config.mjs +++ b/src/cli/init-config/write-config.mjs @@ -12,6 +12,7 @@ import { * @returns {void} Nothing * @param {string} pConfig - dependency-cruiser configuration * @param {import("fs").PathOrFileDescriptor} pFileName - name of the file to write to + * @param {NodeJS.WritableStream} pOutStream - the stream to write user feedback to * @throws {Error} An error object with the root cause of the problem * as a description: * - file already exists @@ -20,22 +21,23 @@ import { */ export default function writeConfig( pConfig, - pFileName = getDefaultConfigFileName() + pFileName = getDefaultConfigFileName(), + pOutStream = process.stdout, ) { if (fileExists(pFileName)) { throw new Error(`A '${pFileName}' already exists here - leaving it be.\n`); } else { try { writeFileSync(pFileName, pConfig); - process.stdout.write( + pOutStream.write( `\n ${chalk.green( - figures.tick - )} Successfully created '${pFileName}'\n\n` + figures.tick, + )} Successfully created '${pFileName}'\n\n`, ); /* c8 ignore start */ } catch (pError) { throw new Error( - `ERROR: Writing to '${pFileName}' didn't work. ${pError}\n` + `ERROR: Writing to '${pFileName}' didn't work. ${pError}\n`, ); } /* c8 ignore stop */ diff --git a/src/cli/init-config/write-run-scripts-to-manifest.mjs b/src/cli/init-config/write-run-scripts-to-manifest.mjs index ca0a1a5bb..236aa5e4c 100644 --- a/src/cli/init-config/write-run-scripts-to-manifest.mjs +++ b/src/cli/init-config/write-run-scripts-to-manifest.mjs @@ -131,21 +131,30 @@ function getSuccessMessage(pDestinationManifestFileName) { ); } -export function writeRunScriptsToManifest( - pNormalizedInitOptions, - pManifest = readManifest(), - pDestinationManifestFileName = PACKAGE_MANIFEST, -) { +/** + * + * @param {any} pNormalizedInitOptions + * @param {{manifest?: string, destinationManifestFileName?: string, outStream?: NodeJS.WritableStream}} pOptions + */ +export function writeRunScriptsToManifest(pNormalizedInitOptions, pOptions) { + const lOptions = { + manifest: readManifest(), + destinationManifestFileName: PACKAGE_MANIFEST, + outStream: process.stdout, + ...pOptions, + }; const lUpdatedManifest = addRunScriptsToManifest( - pManifest, + lOptions.manifest, compileRunScripts(pNormalizedInitOptions), ); writeFileSync( - pDestinationManifestFileName, + lOptions.destinationManifestFileName, JSON.stringify(lUpdatedManifest, null, " "), "utf8", ); - process.stdout.write(getSuccessMessage(pDestinationManifestFileName)); + lOptions.outStream.write( + getSuccessMessage(lOptions.destinationManifestFileName), + ); } diff --git a/test/cli/init-config/index.spec.mjs b/test/cli/init-config/index.spec.mjs index ec3535244..d701267fa 100644 --- a/test/cli/init-config/index.spec.mjs +++ b/test/cli/init-config/index.spec.mjs @@ -3,6 +3,10 @@ import { join } from "node:path"; import { deepEqual, equal } from "node:assert/strict"; import Ajv from "ajv"; import deleteDammit from "../delete-dammit.utl.cjs"; +import { + UnCalledWritableTestStream, + WritableTestStream, +} from "./writable-test-stream.utl.mjs"; import configurationSchema from "#configuration-schema"; import initConfig from "#cli/init-config/index.mjs"; @@ -12,12 +16,16 @@ const RULES_FILE_JS = ".dependency-cruiser.js"; describe("[I] cli/init-config/index", () => { const WORKINGDIR = process.cwd(); + const lErrorStream = new UnCalledWritableTestStream(); afterEach("tear down", () => { process.chdir(WORKINGDIR); }); it("init creates a self-contained js rules file", async () => { + const lOutStream = new WritableTestStream( + /Successfully created '[.]dependency-cruiser[.]js'/, + ); process.chdir("test/cli/__fixtures__/init-config/no-config-files-exist"); const lConfigResultFileName = `./${join( "../__fixtures__/init-config/no-config-files-exist", @@ -25,7 +33,10 @@ describe("[I] cli/init-config/index", () => { )}`; try { - initConfig("notyes"); + initConfig("notyes", null, { + stdout: lOutStream, + stderr: lErrorStream, + }); const lResult = await import(lConfigResultFileName); ajv.validate(configurationSchema, lResult.default); @@ -36,6 +47,9 @@ describe("[I] cli/init-config/index", () => { }); it("init yes creates a self-contained js rules file", async () => { + const lOutStream = new WritableTestStream( + /Successfully created '[.]dependency-cruiser-self-contained[.]js'/, + ); process.chdir("test/cli/__fixtures__/init-config/no-config-files-exist"); const lConfig = ".dependency-cruiser-self-contained.js"; const lConfigResultFileName = `./${join( @@ -44,7 +58,10 @@ describe("[I] cli/init-config/index", () => { )}`; try { - initConfig("yes", lConfig); + initConfig("yes", lConfig, { + stdout: lOutStream, + stderr: lErrorStream, + }); const lResult = await import(lConfigResultFileName); ajv.validate(configurationSchema, lResult.default); @@ -56,6 +73,9 @@ describe("[I] cli/init-config/index", () => { }); it("init yes in a ts project creates a self-contained js rules file with typescript things flipped to yes", async () => { + const lOutStream = new WritableTestStream( + /Successfully created '[.]dependency-cruiser[.]js'/, + ); process.chdir("test/cli/__fixtures__/init-config/ts-config-exists"); const lConfigResultFileName = `./${join( "../__fixtures__/init-config/ts-config-exists", @@ -63,7 +83,10 @@ describe("[I] cli/init-config/index", () => { )}`; try { - initConfig("yes"); + initConfig("yes", null, { + stdout: lOutStream, + stderr: lErrorStream, + }); const lResult = await import(lConfigResultFileName); ajv.validate(configurationSchema, lResult.default); @@ -78,6 +101,10 @@ describe("[I] cli/init-config/index", () => { }); it("init yes in a webpack project creates a self-contained js rules file with webpack things flipped to yes", async () => { + const lOutStream = new WritableTestStream( + /Successfully created '[.]dependency-cruiser[.]js'/, + ); + process.chdir("test/cli/__fixtures__/init-config/webpack-config-exists"); const lConfigResultFileName = `./${join( "../__fixtures__/init-config/webpack-config-exists", @@ -85,7 +112,10 @@ describe("[I] cli/init-config/index", () => { )}`; try { - initConfig("yes"); + initConfig("yes", null, { + stdout: lOutStream, + stderr: lErrorStream, + }); const lResult = await import(lConfigResultFileName); ajv.validate(configurationSchema, lResult.default); @@ -100,6 +130,9 @@ describe("[I] cli/init-config/index", () => { }); it("init experimental-scripts creates a .dependency-cruiser config + updates package.json with scripts", async () => { + const lOutStream = new WritableTestStream( + /(Successfully created '.dependency-cruiser.js'|Run scripts added to '[.]\/package.json':)/, + ); process.chdir("test/cli/init-config/__fixtures__/update-manifest"); const lConfigResultFileName = `./${join( @@ -111,7 +144,10 @@ describe("[I] cli/init-config/index", () => { writeFileSync(lManifestFilename, "{}"); try { - initConfig("experimental-scripts"); + initConfig("experimental-scripts", null, { + stdout: lOutStream, + stderr: lErrorStream, + }); const lResult = await import(lConfigResultFileName); ajv.validate(configurationSchema, lResult.default); @@ -125,6 +161,9 @@ describe("[I] cli/init-config/index", () => { }); it("init experimental-scripts updates package.json with scripts, and leaves an existing dc config alone", async () => { + const lOutStream = new WritableTestStream( + /Run scripts added to '[.]\/package.json':/, + ); process.chdir( "test/cli/init-config/__fixtures__/update-manifest-dc-config-exists", ); @@ -137,7 +176,10 @@ describe("[I] cli/init-config/index", () => { writeFileSync(lManifestFilename, "{}"); try { - initConfig("experimental-scripts"); + initConfig("experimental-scripts", null, { + stdout: lOutStream, + stderr: lErrorStream, + }); const lResult = await import(lConfigResultFileName); ajv.validate(configurationSchema, lResult.default); diff --git a/test/cli/init-config/writable-test-stream.utl.mjs b/test/cli/init-config/writable-test-stream.utl.mjs new file mode 100644 index 000000000..52dbed399 --- /dev/null +++ b/test/cli/init-config/writable-test-stream.utl.mjs @@ -0,0 +1,27 @@ +/* eslint-disable max-classes-per-file */ +import { match } from "node:assert"; +import { Writable } from "node:stream"; + +export class WritableTestStream extends Writable { + expected = /^$/; + + /** + * @param {RegExp=} pExpected + */ + constructor(pExpected) { + super(); + if (pExpected) { + this.expected = pExpected; + } + } + write(pChunk) { + match(pChunk, this.expected); + return true; + } +} + +export class UnCalledWritableTestStream extends Writable { + write(pChunk) { + throw new Error(`Unexpected write to stream: ${pChunk}`); + } +} diff --git a/test/cli/init-config/write-config.spec.mjs b/test/cli/init-config/write-config.spec.mjs index 657b6cbb5..d4e8f151e 100644 --- a/test/cli/init-config/write-config.spec.mjs +++ b/test/cli/init-config/write-config.spec.mjs @@ -2,6 +2,7 @@ import { ok, equal } from "node:assert/strict"; import { writeFileSync, readFileSync } from "node:fs"; import { join } from "node:path"; import deleteDammit from "../delete-dammit.utl.cjs"; +import { WritableTestStream } from "./writable-test-stream.utl.mjs"; import writeConfig from "#cli/init-config/write-config.mjs"; const RULES_FILE_JS = ".dependency-cruiser.js"; @@ -14,6 +15,9 @@ describe("[U] cli/init-config/write-config", () => { }); it("writes if there's no file there yet", async () => { + const lOutStream = new WritableTestStream( + /Successfully created 'depcruise[.]config[.]js'/, + ); const lEmptyDirectory = "test/cli/__fixtures__/init-config/no-config-files-exist"; const lCustomConfigFileName = "depcruise.config.js"; @@ -30,6 +34,7 @@ describe("[U] cli/init-config/write-config", () => { wim: { zus: "jet heide", does: "hok schapen" }, }`, lCustomConfigFileName, + lOutStream, ); const lResult = await import(lConfigResultFileName);