Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 6.0.0-dev

- Added `--dry` option to process images without writing output files.
- Added `--print` option to print input and output metadata as JSON.
- Removed `jpg` output format alias; use `jpeg` instead.
- Internal refactor from CommonJS to ESM.
- Updated `sharp` dependency to 0.35.3.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ 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]
-n, --dry Process images without writing output files [boolean]
-i, --input Path to (an) image file(s) [array] [default: stdin]
-o, --output Directory or URI template to write the image files to [string] [default: stdout]
--print Print input and output metadata as JSON [boolean]
--timeout Number of seconds after which processing will be stopped [number]

Input Options
Expand Down
7 changes: 7 additions & 0 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ const globalOptions = {
type: "boolean",
},

print: {
desc: "Print input and output metadata as JSON",
group: global,
implies: IS_TEXT_TERMINAL ? undefined : "dry",
type: "boolean",
},

// @see https://sharp.pixelplumbing.com/api-constructor/
input: {
alias: "i",
Expand Down
33 changes: 21 additions & 12 deletions lib/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,19 +100,21 @@ export default {
});
}

const inputMetadata = { ...metadata, path: src };
const transformer = drain(context.queue, image, {
format,
metadata,
metadata: inputMetadata,
});

if (context.dry) {
return transformer
.toBuffer({ resolveWithObject: true })
.then(({ info }) => Object.assign({}, info, { path: dest }));
}
return transformer
.toFile(dest)
.then((info) => Object.assign({}, info, { path: dest }));
const promise = context.dry
? transformer
.toBuffer({ resolveWithObject: true })
.then(({ info }) => info)
: transformer.toFile(dest);
return promise.then((info) => ({
input: inputMetadata,
output: { ...info, path: dest },
}));
});
});
return Promise.all(promises);
Expand All @@ -124,22 +126,29 @@ export default {
return pipeline(inStream, image)
.then(() => image.metadata())
.then((metadata) => {
const inputMetadata = { ...metadata, path: "stdin" };
const transformer = drain(context.queue, image, {
format: context.format ?? metadata.format,
metadata,
metadata: inputMetadata,
});

if (context.dry) {
return transformer
.toBuffer({ resolveWithObject: true })
.then(({ info }) => info);
.then(({ info }) => ({
input: inputMetadata,
output: { ...info, path: "stdout" },
}));
}

// Gather return value.
const info = {};
transformer.on("info", (_info) => Object.assign(info, _info));

return pipeline(transformer, outStream).then(() => info);
return pipeline(transformer, outStream).then(() => ({
input: inputMetadata,
output: { ...info, path: "stdout" },
}));
});
},
};
14 changes: 11 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,19 @@ export default (args, options = {}) => {
return convert
.files(argv.input, argv.output, context)
.then((output) => {
const info = Array.isArray(output) ? output : [output];
info.forEach((file) => logger.log(file.path));
if (argv.print) {
logger.log(JSON.stringify(output));
} else {
const arr = Array.isArray(output) ? output : [output];
arr.forEach((file) => logger.log(file.output.path));
}
});
}
return convert.stream(process.stdin, process.stdout, context);
return convert
.stream(process.stdin, process.stdout, context)
.then((output) => {
if (argv.print) logger.log(JSON.stringify(output));
});
})
.catch((err) => {
if (err instanceof Error) {
Expand Down
30 changes: 20 additions & 10 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,16 @@ describe(`${pkg.name} <options> [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("--effort", () => {
// Default effort.
const effort = 1;
Expand Down Expand Up @@ -820,16 +830,6 @@ describe(`${pkg.name} <options> [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]));
Expand Down Expand Up @@ -967,6 +967,16 @@ describe(`${pkg.name} <options> [command..]`, () => {
});
});

describe("--print", () => {
// Run.
before(() => cli.parseAsync(["--print", "--dry", ...ioFlags]));

// Tests.
it("must set the print flag", () => {
expect(cli.parsed.argv).to.have.property("print", true);
});
});

describe("--pyramid", () => {
// Run.
before(() => cli.parseAsync(["--pyramid", ...ioFlags]));
Expand Down
61 changes: 49 additions & 12 deletions test/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,45 @@ describe("convert", () => {
it("must convert a file", () => {
return convert
.files([input], dest, createContext())
.then(([info]) => expect(fs.existsSync(info.path)).to.be.true);
.then(([info]) => expect(fs.existsSync(info.output.path)).to.be.true());
});
it("must convert a file formatted based on extension", () => {
return convert
.files([input], path.join(dest, "{name}.avif"), createContext())
.then(([info]) => {
expect(info).to.have.property("format", "heif");
expect(info).to.have.property("path");
expect(info.path).to.contain(".avif");
expect(info.output).to.have.property("format", "heif");
expect(info.output).to.have.property("path");
expect(info.output.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(info.output).to.have.property("format", "jpeg");
expect(info.output).to.have.property("size");
expect(info.output.path).to.equal(dryDest);
expect(fs.existsSync(dryDest)).to.be.false();
});
});
it("must return input and output metadata", () => {
const output = path.join(dest, "output.jpg");
const context = { ...createContext(), dry: true, print: true };
return convert.files([input], output, context).then(([info]) => {
expect(info.input).to.have.property("format", "jpeg");
expect(info.input.path).to.equal(input);
expect(info.output).to.have.property("format", "jpeg");
expect(info.output.path).to.equal(output);
});
});
it("must return metadata for each file in a batch", () => {
const context = { ...createContext(), dry: true, print: true };
return convert.files([input, input], dest, context).then((info) => {
expect(info).to.have.length(2);
expect(info[0].input).to.have.property("format", "jpeg");
expect(info[0].output).to.have.property("format", "jpeg");
});
});
it("must pass the output extension format to queued handlers", () => {
const context = createContext();
let format;
Expand Down Expand Up @@ -120,7 +138,9 @@ describe("convert", () => {
const rand = Math.random();
return convert
.files([input], path.join(dest, `{name}-${rand}{ext}`), createContext())
.then(([info]) => expect(info.path).to.contain(`input-${rand}.jpg`));
.then(([info]) =>
expect(info.output.path).to.contain(`input-${rand}.jpg`),
);
});
it("must allow the same file as input and output", () => {
return convert.files([copy], path.dirname(copy), createContext());
Expand Down Expand Up @@ -160,8 +180,8 @@ describe("convert", () => {
createContext(),
)
.then((info) => {
expect(info.format).to.exist();
expect(info.path).not.to.exist();
expect(info.output.format).to.exist();
expect(info.output.path).to.equal("stdout");
expect(fs.existsSync(dest)).to.be.true();
});
});
Expand Down Expand Up @@ -207,8 +227,25 @@ describe("convert", () => {
dry: true,
})
.then((info) => {
expect(info).to.have.property("format", "jpeg");
expect(info).to.have.property("size");
expect(info.output).to.have.property("format", "jpeg");
expect(info.output).to.have.property("size");
});
});
it("must return input and output metadata", () => {
const context = { ...createContext(), dry: true, print: true };
return convert
.stream(
fs.createReadStream(input),
new Writable({
write(_chunk, _encoding, callback) {
callback(new Error("should not write output"));
},
}),
context,
)
.then((info) => {
expect(info.input).to.have.property("format", "jpeg");
expect(info.output).to.have.property("format", "jpeg");
});
});
});
Expand Down
10 changes: 10 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ describe("CLI", () => {
expect(process.exitCode).not.to.equal(1);
});
});
it("must print metadata", () => {
return cli(["--dry", "--print", "-i", input, "-o", dest, "resize", "100"], {
logger,
}).then(() => {
const [metadata] = JSON.parse(logger.log.firstCall.args[0]);
expect(metadata.input).to.have.property("format", "jpeg");
expect(metadata.output).to.have.property("width", 100);
sinon.assert.notCalled(logger.error);
});
});
it("must display errors", () => {
return cli(["-i", missing, "-o", dest], { logger }).then(() => {
sinon.assert.notCalled(logger.log);
Expand Down
Loading