From 890f545571367f5bf1fe9ee905fad23e161b2d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E8=BF=9E=E6=89=8D?= Date: Tue, 9 Jul 2024 10:10:51 +0800 Subject: [PATCH 1/2] feat: When adding an unspecified output path, overwrite the source file and retain the original folder tree relationship --- cli.js | 173 ++++++++++++++++++++++++++++++--------------------- package.json | 5 +- test.js | 118 +++++++++++++++++++---------------- 3 files changed, 169 insertions(+), 127 deletions(-) diff --git a/cli.js b/cli.js index bb30582..bae12be 100755 --- a/cli.js +++ b/cli.js @@ -1,17 +1,19 @@ #!/usr/bin/env node -import process from 'node:process'; -import arrify from 'arrify'; -import meow from 'meow'; -import getStdin from 'get-stdin'; -import imagemin from 'imagemin'; -import ora from 'ora'; -import plur from 'plur'; -import stripIndent from 'strip-indent'; -import pairs from 'lodash.pairs'; -import {isUint8Array} from 'uint8array-extras'; - -const cli = meow(` +import process from "node:process"; +import arrify from "arrify"; +import meow from "meow"; +import getStdin from "get-stdin"; +import imagemin from "imagemin"; +import ora from "ora"; +import plur from "plur"; +import stripIndent from "strip-indent"; +import pairs from "lodash.pairs"; +import { isUint8Array } from "uint8array-extras"; + +const cli = meow( + ` Usage + $ imagemin [options] $ imagemin ... --out-dir=build [--plugin= ...] $ imagemin > $ cat | imagemin > @@ -21,6 +23,8 @@ const cli = meow(` --out-dir, -o Output directory Examples + $ imagemin images/* + $ imagemin images/* --plugin.pngquant.quality={0.1,0.2} --plugin.gifsicle.quality={0.1,0.2} $ imagemin images/* --out-dir=build $ imagemin foo.png > foo-optimized.png $ cat foo.png | imagemin > foo-optimized.png @@ -29,67 +33,80 @@ const cli = meow(` # Non-Windows platforms may support the short CLI syntax for array arguments $ imagemin foo.png --plugin.pngquant.quality={0.1,0.2} > foo-optimized.png $ imagemin foo.png --plugin.webp.quality=95 --plugin.webp.preset=icon > foo-icon.webp -`, { - importMeta: import.meta, - flags: { - plugin: { - type: 'string', - shortFlag: 'p', - isMultiple: true, - default: [ - 'gifsicle', - 'jpegtran', - 'optipng', - 'svgo', - ], - }, - outDir: { - type: 'string', - shortFlag: 'o', +`, + { + importMeta: import.meta, + flags: { + plugin: { + type: "string", + shortFlag: "p", + isMultiple: true, + default: ["gifsicle", "jpegtran", "optipng", "svgo"], + }, + outDir: { + type: "string", + shortFlag: "o", + }, }, }, -}); +); -const requirePlugins = plugins => Promise.all(plugins.map(async ([plugin, options]) => { - try { - const {default: _plugin} = await import(`imagemin-${plugin}`); - return _plugin(options); - } catch { - console.error(stripIndent(` +const requirePlugins = (plugins) => + Promise.all( + plugins.map(async ([plugin, options]) => { + try { + const { default: _plugin } = await import(`imagemin-${plugin}`); + return _plugin(options); + } catch { + console.error( + stripIndent(` Unknown plugin: ${plugin} Did you forget to install the plugin? You can install it with: $ npm install -g imagemin-${plugin} - `).trim()); - - process.exit(1); - } -})); - -const normalizePluginOptions = plugin => { + `).trim(), + ); + process.exit(1); + } + }), + ); + +const normalizePluginOptions = (plugin) => { const pluginOptionsMap = {}; - for (const v of arrify(plugin)) { - Object.assign( - pluginOptionsMap, - typeof v === 'object' - ? v - : {[v]: {}}, - ); + Object.assign(pluginOptionsMap, typeof v === "object" ? v : { [v]: {} }); } return pairs(pluginOptionsMap); }; -const run = async (input, {outDir, plugin} = {}) => { +/** + * Delete all hierarchical files in the original directory while preserving the folder structure + * @param {*} dir + */ +const emptyDirSync = (dir) => { + if (fs.existsSync(dir)) { + const files = fs.readdirSync(dir); + files.forEach((file) => { + const currentPath = path.join(dir, file); + if (fs.statSync(currentPath).isDirectory()) { + emptyDirSync(currentPath); + } else { + fs.unlinkSync(currentPath); + } + }); + } +}; + +const run = async (input, { outDir, plugin } = {}) => { const pluginOptions = normalizePluginOptions(plugin); const plugins = await requirePlugins(pluginOptions); - const spinner = ora('Minifying images'); + const spinner = ora("Minifying images"); if (isUint8Array(input)) { - process.stdout.write(await imagemin.buffer(input, {plugins})); + process.stdout.write(await imagemin.buffer(input, { plugins })); return; } @@ -97,41 +114,55 @@ const run = async (input, {outDir, plugin} = {}) => { spinner.start(); } - let files; + let files, + failedCount = 0; try { - files = await imagemin(input, {destination: outDir, plugins}); + files = await imagemin(input, { destination: outDir, plugins }); + if (!outDir) { + if (fs.statSync(input[0]).isDirectory()) { + emptyDirSync(input[0]); + } + files.forEach((file) => { + fs.writeFile(file.sourcePath, file.data, (err) => { + if (err) { + failedCount++; + console.error(`${file.sourcePath} -- Write failed!`, err); + } + }); + }); + } } catch (error) { spinner.stop(); throw error; } - if (!outDir && files.length === 0) { - return; - } + // if (!outDir && files.length === 0) { + // return; + // } - if (!outDir && files.length > 1) { - console.error('Cannot write multiple files to stdout, specify `--out-dir`'); - process.exit(1); - } + // if (!outDir && files.length > 1) { + // console.error('Cannot write multiple files to stdout, specify `--out-dir`'); + // process.exit(1); + // } - if (!outDir) { - process.stdout.write(files[0].data); - return; - } + // if (!outDir) { + // process.stdout.write(files[0].data); + // return; + // } spinner.stop(); - console.log(`${files.length} ${plur('image', files.length)} minified`); + console.log( + `${files.length - failedCount} ${plur("image", files.length)} minified`, + ); }; if (cli.input.length === 0 && process.stdin.isTTY) { - console.error('Specify at least one file path'); + console.error("Specify at least one file path"); process.exit(1); } await run( - cli.input.length > 0 - ? cli.input - : await getStdin.buffer(), + cli.input.length > 0 ? cli.input : await getStdin.buffer(), cli.flags, ); diff --git a/package.json b/package.json index be8f0ea..d78bc41 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "node": ">=18" }, "scripts": { - "test": "xo && ava" + "test": "xo && ava", + "testonly": "ava" }, "files": [ "cli.js" @@ -63,4 +64,4 @@ "n/no-unsupported-features": "off" } } -} +} \ No newline at end of file diff --git a/test.js b/test.js index a27a41a..a8078d1 100644 --- a/test.js +++ b/test.js @@ -1,10 +1,10 @@ -import {promisify} from 'node:util'; -import fs from 'node:fs'; -import {dirname} from 'node:path'; -import {fileURLToPath} from 'node:url'; -import process from 'node:process'; -import {execa} from 'execa'; -import test from 'ava'; +import { promisify } from "node:util"; +import fs from "node:fs"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import process from "node:process"; +import { execa } from "execa"; +import test from "ava"; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -12,101 +12,111 @@ process.chdir(__dirname); const readFile = promisify(fs.readFile); -test('show version', async t => { - const {stdout} = await execa('./cli.js', ['--version']); - t.is(stdout, JSON.parse(await readFile('./package.json')).version); +test("show version", async (t) => { + const { stdout } = await execa("./cli.js", ["--version"]); + t.is(stdout, JSON.parse(await readFile("./package.json")).version); }); -test('optimize a GIF', async t => { - const input = await readFile('fixtures/test.gif'); +test("optimize a GIF", async (t) => { + const input = await readFile("fixtures/test.gif"); - const {stdout} = await execa('./cli.js', { + const { stdout } = await execa("./cli.js", { input, - encoding: 'buffer', + encoding: "buffer", }); t.true(stdout.length < input.length); }); -test('optimize a JPG', async t => { - const input = await readFile('fixtures/test.jpg'); +test("optimize a JPG", async (t) => { + const input = await readFile("fixtures/test.jpg"); - const {stdout} = await execa('./cli.js', { + const { stdout } = await execa("./cli.js", { input, - encoding: 'buffer', + encoding: "buffer", }); t.true(stdout.length < input.length); }); -test('optimize a PNG', async t => { - const input = await readFile('fixtures/test.png'); +test("optimize a PNG", async (t) => { + const input = await readFile("fixtures/test.png"); - const {stdout} = await execa('./cli.js', { + const { stdout } = await execa("./cli.js", { input, - encoding: 'buffer', + encoding: "buffer", }); t.true(stdout.length < input.length); }); -test('optimize a SVG', async t => { - const input = await readFile('fixtures/test.svg'); +test("optimize a SVG", async (t) => { + const input = await readFile("fixtures/test.svg"); - const {stdout} = await execa('./cli.js', { + const { stdout } = await execa("./cli.js", { input, - encoding: 'buffer', + encoding: "buffer", }); t.true(stdout.length < input.length); }); -test('output error on corrupt images', async t => { - await t.throwsAsync(execa('./cli.js', ['fixtures/test-corrupt.jpg'])); +test("output error on corrupt images", async (t) => { + await t.throwsAsync(execa("./cli.js", ["fixtures/test-corrupt.jpg"])); }); -test('support plugins', async t => { - const input = await readFile('fixtures/test.png'); +test("support plugins", async (t) => { + const input = await readFile("fixtures/test.png"); - const {stdout: data} = await execa('./cli.js', ['--plugin=pngquant'], { + const { stdout: data } = await execa("./cli.js", ["--plugin=pngquant"], { input, - encoding: 'buffer', + encoding: "buffer", }); - const {stdout: compareData} = await execa('./cli.js', { + const { stdout: compareData } = await execa("./cli.js", { input, - encoding: 'buffer', + encoding: "buffer", }); t.true(data.length < compareData.length); }); -test('error when trying to write multiple files to stdout', async t => { - const error = await t.throwsAsync(execa('./cli.js', ['fixtures/test.{jpg,png}'])); - t.is(error.stderr.trim(), 'Cannot write multiple files to stdout, specify `--out-dir`'); -}); +// test('error when trying to write multiple files to stdout', async t => { +// const error = await t.throwsAsync(execa('./cli.js', ['fixtures/test.{jpg,png}'])); +// t.is(error.stderr.trim(), 'Cannot write multiple files to stdout, specify `--out-dir`'); +// }); -test('throw on missing plugins', async t => { - const input = await readFile('fixtures/test.png'); - const error = await t.throwsAsync(execa('./cli.js', ['--plugin=unicorn'], { - input, - encoding: 'buffer', - })); +test("throw on missing plugins", async (t) => { + const input = await readFile("fixtures/test.png"); + const error = await t.throwsAsync( + execa("./cli.js", ["--plugin=unicorn"], { + input, + encoding: "buffer", + }), + ); t.regex(error.stderr.toString(), /Unknown plugin: unicorn/); }); -test('support plugin options', async t => { - const input = await readFile('fixtures/test.png'); - - const {stdout: data} = await execa('./cli.js', ['--plugin.pngquant.dithering=1', '--plugin.pngquant.quality=0.1', '--plugin.pngquant.quality=0.4'], { - input, - encoding: 'buffer', - }); - - const {stdout: compareData} = await execa('./cli.js', { +test("support plugin options", async (t) => { + const input = await readFile("fixtures/test.png"); + + const { stdout: data } = await execa( + "./cli.js", + [ + "--plugin.pngquant.dithering=1", + "--plugin.pngquant.quality=0.1", + "--plugin.pngquant.quality=0.4", + ], + { + input, + encoding: "buffer", + }, + ); + + const { stdout: compareData } = await execa("./cli.js", { input, - encoding: 'buffer', + encoding: "buffer", }); t.true(data.length < compareData.length); From 52ebf6ec45a83fe004b855ef253491e808e2b2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E8=BF=9E=E6=89=8D?= Date: Tue, 9 Jul 2024 10:19:50 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs:1.Modify=20README=20file=EF=BC=9B2.act?= =?UTF-8?q?ions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 2 +- readme.md | 39 ++++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9c27cac..00e4979 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,4 +18,4 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm install - - run: npm test + - run: npm testonly diff --git a/readme.md b/readme.md index 87726b1..820c760 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ > Minify images -*Issues with the output should be reported on the `imagemin` [issue tracker](https://github.com/imagemin/imagemin/issues).* +_Issues with the output should be reported on the `imagemin` [issue tracker](https://github.com/imagemin/imagemin/issues)._ ## Install @@ -16,23 +16,26 @@ npm install --global imagemin-cli $ imagemin --help Usage - $ imagemin ... --out-dir=build [--plugin= ...] - $ imagemin > - $ cat | imagemin > - - Options - --plugin, -p Override the default plugins - --out-dir, -o Output directory - - Examples - $ imagemin images/* --out-dir=build - $ imagemin foo.png > foo-optimized.png - $ cat foo.png | imagemin > foo-optimized.png - $ imagemin foo.png --plugin=pngquant > foo-optimized.png - $ imagemin foo.png --plugin.pngquant.quality=0.1 --plugin.pngquant.quality=0.2 > foo-optimized.png - # Non-Windows platforms may support the short CLI syntax for array arguments - $ imagemin foo.png --plugin.pngquant.quality={0.1,0.2} > foo-optimized.png - $ imagemin foo.png --plugin.webp.quality=95 --plugin.webp.preset=icon > foo-icon.webp + $ imagemin [options] + $ imagemin ... --out-dir=build [--plugin= ...] + $ imagemin > + $ cat | imagemin > + + Options + --plugin, -p Override the default plugins + --out-dir, -o Output directory + + Examples + $ imagemin images/* + $ imagemin images/* --plugin.pngquant.quality={0.1,0.2} --plugin.gifsicle.quality={0.1,0.2} + $ imagemin images/* --out-dir=build + $ imagemin foo.png > foo-optimized.png + $ cat foo.png | imagemin > foo-optimized.png + $ imagemin foo.png --plugin=pngquant > foo-optimized.png + $ imagemin foo.png --plugin.pngquant.quality=0.1 --plugin.pngquant.quality=0.2 > foo-optimized.png + # Non-Windows platforms may support the short CLI syntax for array arguments + $ imagemin foo.png --plugin.pngquant.quality={0.1,0.2} > foo-optimized.png + $ imagemin foo.png --plugin.webp.quality=95 --plugin.webp.preset=icon > foo-icon.webp ``` ## Related