Skip to content

Commit

Permalink
refactor(init-config): makes stderr, stdout configurable - so we can …
Browse files Browse the repository at this point in the history
…test their uses more easily (#897)

## Description

- adds out & err stream parameters to the init config
- updates tests so they test for what is written to them

## Motivation and Context

- More reliable tests
- Less stdout/ stderr poluting the standard test report

## How Has This Been Tested?

- [x] green ci
- [x] adapted automated non-regression tests

## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Documentation only change
- [x] Refactor (non-breaking change which fixes an issue without
changing functionality)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
  • Loading branch information
sverweij committed Dec 28, 2023
1 parent 0d70164 commit 3d31cc3
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 23 deletions.
20 changes: 16 additions & 4 deletions src/cli/init-config/index.mjs
Expand Up @@ -75,28 +75,40 @@ 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()
.then(normalizeInitOptions)
.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) {
const lNormalizedInitConfig = normalizeInitOptions(getOneShotConfig(pInit));
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,
});
}
}
}
12 changes: 7 additions & 5 deletions src/cli/init-config/write-config.mjs
Expand Up @@ -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
Expand All @@ -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 */
Expand Down
25 changes: 17 additions & 8 deletions src/cli/init-config/write-run-scripts-to-manifest.mjs
Expand Up @@ -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),
);
}
54 changes: 48 additions & 6 deletions test/cli/init-config/index.spec.mjs
Expand Up @@ -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";

Expand All @@ -12,20 +16,27 @@ 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",
RULES_FILE_JS,
)}`;

try {
initConfig("notyes");
initConfig("notyes", null, {
stdout: lOutStream,
stderr: lErrorStream,
});
const lResult = await import(lConfigResultFileName);

ajv.validate(configurationSchema, lResult.default);
Expand All @@ -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(
Expand All @@ -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);
Expand All @@ -56,14 +73,20 @@ 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",
RULES_FILE_JS,
)}`;

try {
initConfig("yes");
initConfig("yes", null, {
stdout: lOutStream,
stderr: lErrorStream,
});
const lResult = await import(lConfigResultFileName);

ajv.validate(configurationSchema, lResult.default);
Expand All @@ -78,14 +101,21 @@ 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",
RULES_FILE_JS,
)}`;

try {
initConfig("yes");
initConfig("yes", null, {
stdout: lOutStream,
stderr: lErrorStream,
});
const lResult = await import(lConfigResultFileName);

ajv.validate(configurationSchema, lResult.default);
Expand All @@ -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(
Expand All @@ -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);
Expand All @@ -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",
);
Expand All @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions 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}`);
}
}
5 changes: 5 additions & 0 deletions test/cli/init-config/write-config.spec.mjs
Expand Up @@ -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";
Expand All @@ -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";
Expand All @@ -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);
Expand Down

0 comments on commit 3d31cc3

Please sign in to comment.