diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fe86324..079696dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ _Note: Gaps between patch versions are faulty, broken or test releases._ ## UNRELEASED +* **Bug Fix** + * Fix a race condition in `writeStats` that could lead to incorrect content in `stats.json` ([#711](https://github.com/webpack/webpack-bundle-analyzer/pull/711) by [@colinaaa](https://github.com/colinaaa)) + ## 5.2.0 * **New Feature** diff --git a/src/statsUtils.js b/src/statsUtils.js index 63e8d4c9..d2684947 100644 --- a/src/statsUtils.js +++ b/src/statsUtils.js @@ -1,5 +1,6 @@ const { createWriteStream } = require("node:fs"); const { Readable } = require("node:stream"); +const { pipeline } = require("node:stream/promises"); /** @typedef {import("./BundleAnalyzerPlugin").EXPECTED_ANY} EXPECTED_ANY */ /** @typedef {import("webpack").StatsCompilation} StatsCompilation */ @@ -91,12 +92,7 @@ class StatsSerializeStream extends Readable { * @returns {Promise} */ async function writeStats(stats, filepath) { - return new Promise((resolve, reject) => { - new StatsSerializeStream(stats) - .on("end", resolve) - .on("error", reject) - .pipe(createWriteStream(filepath)); - }); + await pipeline(new StatsSerializeStream(stats), createWriteStream(filepath)); } module.exports = { StatsSerializeStream, writeStats }; diff --git a/test/statsUtils.js b/test/statsUtils.js index 88b68095..2b24f091 100644 --- a/test/statsUtils.js +++ b/test/statsUtils.js @@ -1,7 +1,7 @@ const { readFileSync } = require("node:fs"); const path = require("node:path"); const { globSync } = require("tinyglobby"); -const { StatsSerializeStream } = require("../src/statsUtils"); +const { StatsSerializeStream, writeStats } = require("../src/statsUtils"); async function stringify(json) { return new Promise((resolve, reject) => { @@ -78,3 +78,16 @@ describe("StatsSerializeStream", () => { }); } }); + +describe("writeStats", () => { + it("should fail gracefully if writing to a non-existent directory", async () => { + const nonExistentPath = path.join( + __dirname, + "non-existent-dir", + "stats.json", + ); + await expect( + writeStats({ foo: "bar" }, nonExistentPath), + ).rejects.toMatchObject({ code: "ENOENT" }); + }); +});