diff --git a/src/compression.ts b/src/compression.ts index 62fd8cc..3d57a20 100644 --- a/src/compression.ts +++ b/src/compression.ts @@ -40,7 +40,7 @@ export async function convertImages(settings: ScriptOptions): Promise { const { srcDir, distDir, compressionOptions } = settings as ScriptOptions; // check if the srcDir is a directory - if (!fs.existsSync(srcDir)) { + if (srcDir && !fs.existsSync(srcDir)) { return new Promise(() => { console.warn( `🎃 Error! The specified source directory ${srcDir} does not exist.`, @@ -52,7 +52,7 @@ export async function convertImages(settings: ScriptOptions): Promise { logMessage( "🎃 No compression options found. Using default compression options.", ); - const inputFormats = getImageFormatsInFolder(settings.srcDir); + const inputFormats = getImageFormatsInFolder(srcDir as string); settings.compressionOptions = defaultCompressionOptions(inputFormats); } @@ -70,6 +70,11 @@ export async function convertImages(settings: ScriptOptions): Promise { cwd: process.cwd(), }; + logMessage( + `🎃 Converting images in ${srcDir} to ${distDir} ... Please wait!`, + true, + ); + // Loop through the files in the directory for await (const res of globResults as AsyncIterable) { /** Collect the source and destination paths */ @@ -78,15 +83,15 @@ export async function convertImages(settings: ScriptOptions): Promise { ...paths, ...file, res, - srcPath: path.join(process.cwd(), srcDir, res), - distPath: path.join(process.cwd(), distDir, file.dir), + srcPath: path.join(process.cwd(), srcDir as string, res), + distPath: path.join(process.cwd(), distDir as string, file.dir), }; /** If the src is a directory */ const srcLstat = lstatSync(filePaths.srcPath); // if is a directory creating the copy of the directory if the src is different from the dist if (srcLstat?.isDirectory()) { - const dirPath = path.join(process.cwd(), distDir, res); + const dirPath = path.join(process.cwd(), distDir as string, res); // check if the directory exists const exists = fs.existsSync(dirPath); if (exists) { diff --git a/src/index.ts b/src/index.ts index 117aa2d..5912f3f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,18 +26,17 @@ export default async function main() { // Parse the settings let options = parseOptions(cliOptions, iniOptions); + // Prompt the user for the script settings + if (cliOptions.interactive === true) { + options = await getPromptOptions(options); + } + // Check for missing settings const missingOptions = ["srcDir", "distDir"].filter( (option) => !options[option as keyof typeof options], ); - - // Prompt the user for the script settings - if (cliOptions.interactive === true && missingOptions.length > 0) { - options = await getPromptOptions(options); - } else { - if (missingOptions.length > 0) { - throw new Error(`Missing required options: ${missingOptions.join(", ")}`); - } + if (missingOptions.length > 0) { + throw new Error(`Missing required options: ${missingOptions.join(", ")}`); } // Print the settings to the console diff --git a/src/options.ts b/src/options.ts index 02a325e..ba7da74 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,17 +1,14 @@ /* eslint-disable no-console */ import fs from "fs"; - -import prompts, { type PromptObject } from "prompts"; +import { PromptObject } from "prompts"; import { compressors, - svgOptions, - type InputFormats, - defaultSrc, defaultDist, + defaultSrc, + type InputFormats, + svgOptions, } from "./constants.js"; -import { type CompressionOption } from "./types.js"; -import { logMessage } from "./utils.js"; /** * Prompts the user for the source directory @@ -46,6 +43,13 @@ export const distDirQuestion: PromptObject = { initial: defaultDist, }; +export const toggleQuestion: PromptObject = { + type: "confirm", + name: "loadDefaults", + message: "Do you want to use the default compression settings?", + initial: true, +}; + /** * The function prompts the user for the image compression settings for different image formats * @@ -136,39 +140,3 @@ export const promptsToAsk = (format: InputFormats): PromptObject[] => { ]; } }; - -/** - * This function prompts the user for settings to compress different image formats, - * including SVG files with custom SVGO plugins. - * - * @param imageFormats - An array of image file formats (e.g. ['.jpg', '.png', '.svg']) - * that the function will prompt the user about compressing. - * @param verbose - Whether to log messages - * @returns an object containing compression settings for different image formats. The - * settings are obtained through a series of prompts that ask the user whether they want - * to compress each format, which compressor to use (if applicable), and the quality - * level (if applicable). For SVG files, the user can also choose which SVGO plugins to - * use for compression. - */ -export async function getImageCompressionOptions( - imageFormats: InputFormats[], - verbose = false, -): Promise<{ [key in InputFormats]: CompressionOption }> { - const options = {} as { [key in InputFormats]: CompressionOption }; - - for (const format of imageFormats) { - logMessage("==".concat(format, "=="), verbose); - const response: CompressionOption = (await prompts( - promptsToAsk(format), - )) as CompressionOption; - - if (!response.compressor) { - logMessage(`Skipping ${format} files...`, verbose); - continue; - } - - options[format] = response; - } - - return options; -} diff --git a/src/parseArgs.ts b/src/parseArgs.ts index 46f506a..6c6eee5 100644 --- a/src/parseArgs.ts +++ b/src/parseArgs.ts @@ -94,10 +94,10 @@ export function getCliOptions(rawArgs: Argv | undefined): CliOptions { }; return { - srcDir: argv.input ?? "", - distDir: argv.output ?? "", + srcDir: argv.input ?? undefined, + distDir: argv.output ?? undefined, configFile: configFileName, - interactive: Boolean(argv.interactive), + interactive: argv.interactive ?? false, verbose: Boolean(argv.verbose), options: JSON.parse(JSON.stringify(options)), }; diff --git a/src/parseOptions.ts b/src/parseOptions.ts index a4151dd..18abb94 100644 --- a/src/parseOptions.ts +++ b/src/parseOptions.ts @@ -8,10 +8,9 @@ export function parseOptions( const newSettings: Partial = {}; // the source and destination directories - newSettings.srcDir = settings.srcDir || String(iniOptions?.srcDir) || ""; - newSettings.distDir = settings.distDir || String(iniOptions?.distDir) || ""; - newSettings.verbose = settings.verbose || false; - newSettings.interactive = settings.interactive || false; + newSettings.srcDir = settings.srcDir ?? iniOptions?.srcDir ?? undefined; + newSettings.distDir = settings.distDir ?? iniOptions?.distDir ?? undefined; + newSettings.verbose = settings.verbose ?? false; newSettings.options = { ...iniOptions?.options, ...settings.options }; diff --git a/src/prompts.ts b/src/prompts.ts index 2b29d03..3e20680 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -2,38 +2,17 @@ import prompts from "prompts"; import { distDirQuestion, - getImageCompressionOptions, + promptsToAsk, srcDirQuestion, + toggleQuestion, } from "./options.js"; -import { CompressionOptionsMap, type ScriptOptions } from "./types.js"; +import { type CompressionOption, type ScriptOptions } from "./types.js"; import { defaultCompressionOptions, getImageFormatsInFolder, logMessage, } from "./utils.js"; -import { InputFormats } from "./constants.js"; - -export async function getInteractiveCompressorOptions( - imageFormats: InputFormats[], - verbose = false, -): Promise { - const response = await prompts({ - type: "confirm", - name: "useDefaultCompressionOptions", - message: "Do you want to use the default compression settings?", - initial: true, - }); - - if (response.useDefaultCompressionOptions) { - //return a promise that resolves with the default compression settings - return await new Promise((resolve) => { - resolve(defaultCompressionOptions(imageFormats)); - }); - } else { - // Prompt the user for compression settings - return await getImageCompressionOptions(imageFormats, verbose); - } -} +import type { InputFormats } from "./constants.js"; export async function getPromptOptions( options: ScriptOptions, @@ -51,29 +30,30 @@ export async function getPromptOptions( } // Get the image formats - const imageFormats = getImageFormatsInFolder(options.srcDir); + const imageFormats = getImageFormatsInFolder(options.srcDir as string); // If no image formats are found, return - if (imageFormats.length === 0) { - logMessage( - "No image formats found in the source directory", - options.verbose, - ); - return options; - } - - // If the compression settings are not specified, use the default compression settings - if (Object.keys(options.compressionOptions).length === 0) { + if (!imageFormats.length) { + throw new Error("No image formats found in the source directory, aborting"); + } else { logMessage( - "No compression settings found, so we will use the default compression settings", + `Found ${imageFormats.length} image formats in the source directory, ` + + imageFormats.join(", "), true, ); - options.compressionOptions = defaultCompressionOptions(); } - // If the compression settings are not specified, prompt the user if he wants to use the default compression settings - if (Object.keys(options.compressionOptions).length === 0) { - options.compressionOptions = await getInteractiveCompressorOptions( + // Check if the user wants to use the default compression options + const response = await prompts(toggleQuestion); + + if (response?.loadDefaults !== false) { + //return a promise that resolves with the default compression settings + options.compressionOptions = await new Promise((resolve) => { + resolve(defaultCompressionOptions(imageFormats)); + }); + } else { + // Prompt the user for compression settings + options.compressionOptions = await getImageCompressionOptions( imageFormats, options.verbose, ); @@ -81,3 +61,39 @@ export async function getPromptOptions( return options; } + +/** + * This function prompts the user for settings to compress different image formats, + * including SVG files with custom SVGO plugins. + * + * @param imageFormats - An array of image file formats (e.g. ['.jpg', '.png', '.svg']) + * that the function will prompt the user about compressing. + * @param verbose - Whether to log messages + * @returns an object containing compression settings for different image formats. The + * settings are obtained through a series of prompts that ask the user whether they want + * to compress each format, which compressor to use (if applicable), and the quality + * level (if applicable). For SVG files, the user can also choose which SVGO plugins to + * use for compression. + */ +export async function getImageCompressionOptions( + imageFormats: InputFormats[], + verbose = false, +): Promise<{ [key in InputFormats]: CompressionOption }> { + const options = {} as { [key in InputFormats]: CompressionOption }; + + for (const format of imageFormats) { + logMessage("==".concat(format, "=="), verbose); + const response: CompressionOption = (await prompts( + promptsToAsk(format), + )) as CompressionOption; + + if (!response.compressor) { + logMessage(`Skipping ${format} files...`, verbose); + continue; + } + + options[format] = response; + } + + return options; +} diff --git a/src/types.ts b/src/types.ts index 33edb2b..8265ff8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -55,8 +55,8 @@ export type SquashOptions = { }; export interface CliOptions { - srcDir: string; - distDir: string; + srcDir?: string; + distDir?: string; verbose?: boolean; configFile?: string; interactive?: boolean; diff --git a/src/utils.ts b/src/utils.ts index a2ce255..24973b7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -71,7 +71,10 @@ export function getImageFormatsInFolder(folderPath: string): InputFormats[] { * @param {string} dir The folder to search for images in. */ function searchForImages(dir: string) { - const files = fs.readdirSync(dir); + const sourceDir = path.join(process.cwd(), dir); + logMessage("Searching for images in " + path.resolve(sourceDir), true); + + const files = fs.readdirSync(sourceDir); // iterate over each file files.forEach((file) => { @@ -316,7 +319,7 @@ export function generateDefaultConfigFile( resizeType: (argv.resizeType as ResizeType) || undefined, } as Record; - let defaultConfig: Record = { + const defaultConfig: Record = { path: { in: argv.input ?? "images", out: argv.output ?? "optimized", diff --git a/tests/unit/cli.test.ts b/tests/unit/cli.test.ts index 0652d05..4bb8d53 100644 --- a/tests/unit/cli.test.ts +++ b/tests/unit/cli.test.ts @@ -18,8 +18,8 @@ describe("getCliOptions", () => { it("Should return the default settings if no arguments are passed", () => { const options = getCliOptions(yargs([""])); - expect(options.srcDir).toBe(""); - expect(options.distDir).toBe(""); + expect(options.srcDir).toBe(undefined); + expect(options.distDir).toBe(undefined); expect(options.configFile).toBe(defaultConfigFile); expect(options.verbose).toBe(false); expect(options.interactive).toBe(false); diff --git a/tests/unit/parseOptions.test.ts b/tests/unit/parseOptions.test.ts index cd61d0c..5d70659 100644 --- a/tests/unit/parseOptions.test.ts +++ b/tests/unit/parseOptions.test.ts @@ -26,17 +26,17 @@ describe("getPromptOptions", () => { // Mock the prompts function to return the user's input (prompts as MockedFunction).mockResolvedValueOnce({ - srcDir: "./src", + srcDir: "tests/images", }); const result = await getPromptOptions(options as any); - expect(result.srcDir).toBe("./src"); - expect(prompts).toBeCalledTimes(1); + expect(result.srcDir).toBe("tests/images"); + expect(prompts).toBeCalledTimes(2); }); it("Should prompt for destination directory when not provided", async () => { - const options = { srcDir: "./src" }; + const options = { srcDir: "./tests/images" }; // Mock the prompts function to return the user's input (prompts as MockedFunction).mockResolvedValueOnce({ @@ -46,12 +46,12 @@ describe("getPromptOptions", () => { const result = await getPromptOptions(options as any); expect(result.distDir).toBe("./dist"); - expect(prompts).toBeCalledTimes(1); + expect(prompts).toBeCalledTimes(2); }); it("Should not prompt for compression settings when already provided", async () => { const options = { - srcDir: "./src", + srcDir: "./tests/images", distDir: "./dist", compressionOptions: { jpeg: { quality: 75 }, @@ -62,7 +62,7 @@ describe("getPromptOptions", () => { const result = await getPromptOptions(options as any); expect(result.compressionOptions).toEqual(options.compressionOptions); - expect(prompts).toBeCalledTimes(0); + expect(prompts).toBeCalledTimes(1); }); }); diff --git a/tests/unit/prompts.test.ts b/tests/unit/prompts.test.ts index 51b9ee7..a127688 100644 --- a/tests/unit/prompts.test.ts +++ b/tests/unit/prompts.test.ts @@ -1,7 +1,7 @@ import prompts from "prompts"; import { beforeEach, describe, expect, it, Mock, vitest } from "vitest"; -import { getImageCompressionOptions } from "../../src/options.js"; import { logMessage } from "../../src/utils.js"; +import { getImageCompressionOptions } from "../../src/prompts.js"; vitest.mock("prompts");