diff --git a/package-lock.json b/package-lock.json index afa8ae8..82d3155 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "yargs": "^17.7.2" }, "bin": { - "squashify": "lib/esm/index.js" + "squashify": "lib/esm/bin.js" }, "devDependencies": { "@babel/core": "^7.24.3", diff --git a/package.json b/package.json index e9852c1..325a4da 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,6 @@ "bugs": { "url": "https://github.com/wp-blocks/squashify/issues" }, - "type": "module", - "bin": "./lib/esm/index.js", "engines": { "node": ">=16.0.0" }, @@ -20,17 +18,20 @@ "type": "git", "url": "git+https://github.com/wp-blocks/squashify.git" }, - "main": "lib/esm/index.js", - "module": "lib/esm/", - "reqire": "lib/cjs/index.js", - "types": "lib/@types/index.d.ts", + "type": "module", + "bin": "lib/esm/bin.js", + "types": "./lib/@types/index.d.ts", + "module": "./lib/esm/", + "exports": { + "squashify": "./lib/esm/bin.js", + "default": "./lib/esm/bin.js" + }, "scripts": { "prebuild": "rimraf lib && tsc --emitDeclarationOnly --outDir lib/@types/", "build": "npx esbuild ./src/* --outdir=lib/esm/ --format=esm --platform=node --minify", - "postbuild": "npx esbuild src/index.ts --outfile=lib/cjs/index.js --bundle --platform=node --minify", "watch": "tsc --watch --sourceMap --outDir lib/esm/", - "squashify": "node . --in tests/images/ --out tests/output --verbose", - "squashifyDefaults": "node . -d", + "squashify": "node ./lib/esm/bin.js --in tests/images/ --out tests/output --verbose", + "squashifyDefaults": "node ./lib/esm/bin.js -d", "test": "vitest --coverage" }, "files": [ diff --git a/src/bin.ts b/src/bin.ts new file mode 100644 index 0000000..6cac4d8 --- /dev/null +++ b/src/bin.ts @@ -0,0 +1,49 @@ +#!/usr/bin/env node + +// Run the script as an async function +import { humanFileSize, logMessage } from "./utils.js"; +import { OutputData } from "./types.js"; +import squashify from "./index.js"; +import { getCliOptions } from "./parseArgs.js"; +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import process from "node:process"; + +// Get the cli settings +export default async function main() { + const cliOptions = getCliOptions(yargs(hideBin(process.argv))); + + squashify(cliOptions) + .then((response) => { + const { timeElapsed, result, verbose } = response; + + if (result.length) { + result.forEach((result) => { + if (result.status !== "fulfilled") { + logMessage("🔴 " + result.reason, true); + } else { + const { status, value } = + result as PromiseFulfilledResult; + logMessage( + "✅ " + + JSON.stringify( + "size" in value + ? { ...value, size: humanFileSize(value.size) } + : value, + ), + verbose, + ); + } + return; + }); + } + + // Print the time elapsed in seconds to the console + logMessage(`The end 🎉 - Time elapsed: ${timeElapsed} seconds`, true); + }) + .catch((err) => { + console.error(err); + }); +} + +await main(); diff --git a/src/compression.ts b/src/compression.ts index 3d57a20..1e7b53b 100644 --- a/src/compression.ts +++ b/src/compression.ts @@ -35,7 +35,9 @@ import { encodeFileAsync } from "./encodeFileAsync.js"; * have keys that correspond to image formats (e.g. "jpg", "png", "webp") and values * that are objects containing compression settings for that format (e.g. "no", "mozjpeg", "jpeg"). */ -export async function convertImages(settings: ScriptOptions): Promise { +export async function convertImages( + settings: ScriptOptions, +): Promise[]> { // destructuring the settings const { srcDir, distDir, compressionOptions } = settings as ScriptOptions; @@ -164,23 +166,5 @@ export async function convertImages(settings: ScriptOptions): Promise { } // Wait for all promises to resolve before returning - const res = await Promise.allSettled(promises); - if (res.length) { - res.forEach((result) => { - if (result.status !== "fulfilled") { - logMessage("🔴 " + result.reason, true); - } else { - logMessage( - "✅ " + - JSON.stringify( - (result as PromiseFulfilledResult).value, - ), - settings.verbose, - ); - } - return; - }); - } else { - logMessage("🔴 No files found", true); - } + return await Promise.allSettled(promises); } diff --git a/src/index.ts b/src/index.ts index 5912f3f..c944119 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,14 @@ -#!/usr/bin/env node -import yargs from "yargs"; -import { hideBin } from "yargs/helpers"; -import process from "node:process"; - -import { getCliOptions } from "./parseArgs.js"; import { convertImages } from "./compression.js"; import { getIniOptions } from "./parseIni.js"; import { getPromptOptions } from "./prompts.js"; import { parseOptions } from "./parseOptions.js"; -import { logMessage } from "./utils.js"; +import { getImageFormatsInFolder, logMessage } from "./utils.js"; +import { encodeFileAsync } from "./encodeFileAsync.js"; +import { encodeSvg } from "./encodeSvg.js"; +import { encodeAnimation } from "./encodeAnimation.js"; +import { encodeImage } from "./encodeImage.js"; +import { CliOptions } from "./types.js"; +import main from "./bin.js"; /** * Prompts the user for the source and destination directories @@ -16,10 +16,7 @@ import { logMessage } from "./utils.js"; * * @returns Promise that resolves when the image conversion is complete */ -export default async function main() { - // Get the cli settings - const cliOptions = getCliOptions(yargs(hideBin(process.argv))); - +export default async function squashify(cliOptions: CliOptions) { // Get the settings from the .ini file const iniOptions = getIniOptions(cliOptions.configFile); @@ -46,17 +43,22 @@ export default async function main() { const startTime = Date.now(); // Then convert the images in the source directory - await convertImages(options); - - // Print the time elapsed in seconds to the console - logMessage( - `The end 🎉 - Time elapsed: ${(Date.now() - startTime) / 1000} seconds`, - true, - ); + const result = await convertImages(options); - return; + return { + result, + timeElapsed: (Date.now() - startTime) / 1000, + verbose: cliOptions.verbose, + }; } -main().catch((err) => { - console.error(err); -}); +export { + main, + getIniOptions, + convertImages, + getImageFormatsInFolder, + encodeFileAsync, + encodeSvg, + encodeAnimation, + encodeImage, +}; diff --git a/src/utils.ts b/src/utils.ts index 24973b7..faafdc8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -337,3 +337,37 @@ export function generateDefaultConfigFile( return writeFileSync(filename, iniFileContent); } + +/** + * Format bytes as human-readable text. + * + * @param bytes Number of bytes. + * @param si True to use metric (SI) units, aka powers of 1000. False to use + * binary (IEC), aka powers of 1024. + * @param dp Number of decimal places to display. + * + * @return Formatted string. + */ +export function humanFileSize(bytes, si = false, dp = 1) { + const thresh = si ? 1000 : 1024; + + if (Math.abs(bytes) < thresh) { + return bytes + " B"; + } + + const units = si + ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] + : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; + let u = -1; + const r = 10 ** dp; + + do { + bytes /= thresh; + ++u; + } while ( + Math.round(Math.abs(bytes) * r) / r >= thresh && + u < units.length - 1 + ); + + return bytes.toFixed(dp) + " " + units[u]; +} diff --git a/tests/unit/ini.test.ts b/tests/unit/ini.test.ts index 2e87e2e..f67af68 100644 --- a/tests/unit/ini.test.ts +++ b/tests/unit/ini.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "vitest"; import { getIniOptions } from "../../src/parseIni.js"; -import { ScriptOptions } from "node:vm"; import { CliOptions } from "../../src/types.js"; describe("getIniOptions", () => {