diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ee265..d18dcee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,12 @@ ## 6.0.0-dev +- Added `--dry` option to process images without writing output files. +- Removed `jpg` output format alias; use `jpeg` instead. - Internal refactor from CommonJS to ESM. - Updated `sharp` dependency to 0.35.3. - Updated dependencies. - Requires Node.js 20.10. -- Removed `jpg` output format alias; use `jpeg` instead. ## 5.3.0 (August 13, 2026) diff --git a/README.md b/README.md index 1aa3cc8..d09b495 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ Commands: pixel values made fully transparent Global Options + --dry Process images without writing output files [boolean] -i, --input Path to (an) image file(s) [array] [required] [default: stdin] -o, --output Directory or URI template to write the image files to [string] [required] [default: stdout] diff --git a/lib/cli.js b/lib/cli.js index c755848..ea27439 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -78,6 +78,13 @@ const optimize = "Optimization Options"; const output = "Output Options"; const globalOptions = { + dry: { + alias: "n", + desc: "Process images without writing output files", + group: global, + type: "boolean", + }, + // @see https://sharp.pixelplumbing.com/api-constructor/ input: { alias: "i", diff --git a/lib/convert.js b/lib/convert.js index 2c3b3cc..6184ea3 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -104,9 +104,15 @@ export default { format, metadata, }); + + if (context.dry) { + return transformer + .toBuffer({ resolveWithObject: true }) + .then(({ info }) => Object.assign({}, info, { path: dest })); + } return transformer .toFile(dest) - .then((info) => Object.assign(info, { src, path: dest })); + .then((info) => Object.assign({}, info, { path: dest })); }); }); return Promise.all(promises); @@ -123,6 +129,12 @@ export default { metadata, }); + if (context.dry) { + return transformer + .toBuffer({ resolveWithObject: true }) + .then(({ info }) => info); + } + // Gather return value. const info = {}; transformer.on("info", (_info) => Object.assign(info, _info)); diff --git a/lib/index.js b/lib/index.js index da5a61c..87585b0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -35,6 +35,7 @@ export default (args, options = {}) => { .parseAsync(args) .then((argv) => { const context = { + dry: argv.dry, format: argv.format, options: pick(argv, Object.keys(inputOptions)), queue: argv["#queue"], diff --git a/test/cli.js b/test/cli.js index d519024..fc89435 100644 --- a/test/cli.js +++ b/test/cli.js @@ -820,6 +820,16 @@ describe(`${pkg.name} [command..]`, () => { }); }); + describe("--dry", () => { + // Run. + before(() => cli.parseAsync(["--dry", ...ioFlags])); + + // Tests. + it("must set the dry flag", () => { + expect(cli.parsed.argv).to.have.property("dry", true); + }); + }); + describe("--overshootDeringing", () => { // Run. before(() => cli.parseAsync(["--overshootDeringing", ...ioFlags])); diff --git a/test/convert.js b/test/convert.js index adad603..3b72f71 100644 --- a/test/convert.js +++ b/test/convert.js @@ -72,6 +72,16 @@ describe("convert", () => { expect(info.path).to.contain(".avif"); }); }); + it("must not write a file during a dry run", () => { + const dryDest = path.join(dest, "dry.jpg"); + const context = { ...createContext(), dry: true }; + return convert.files([input], dryDest, context).then(([info]) => { + expect(info).to.have.property("format", "jpeg"); + expect(info).to.have.property("size"); + expect(info.path).to.equal(dryDest); + expect(fs.existsSync(dryDest)).to.be.false(); + }); + }); it("must pass the output extension format to queued handlers", () => { const context = createContext(); let format; @@ -185,5 +195,21 @@ describe("convert", () => { }) .catch((err) => expect(err).to.equal(error)); }); + it("must not write to the output stream during a dry run", () => { + const failingOutput = new Writable({ + write(_chunk, _encoding, callback) { + callback(new Error("should not write output")); + }, + }); + return convert + .stream(fs.createReadStream(input), failingOutput, { + ...createContext(), + dry: true, + }) + .then((info) => { + expect(info).to.have.property("format", "jpeg"); + expect(info).to.have.property("size"); + }); + }); }); });