Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prompts #12

Merged
merged 6 commits into from
Apr 8, 2024
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
15 changes: 10 additions & 5 deletions src/compression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export async function convertImages(settings: ScriptOptions): Promise<void> {
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.`,
Expand All @@ -52,7 +52,7 @@ export async function convertImages(settings: ScriptOptions): Promise<void> {
logMessage(
"🎃 No compression options found. Using default compression options.",
);
const inputFormats = getImageFormatsInFolder(settings.srcDir);
const inputFormats = getImageFormatsInFolder(srcDir as string);
settings.compressionOptions = defaultCompressionOptions(inputFormats);
}

Expand All @@ -70,6 +70,11 @@ export async function convertImages(settings: ScriptOptions): Promise<void> {
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<string>) {
/** Collect the source and destination paths */
Expand All @@ -78,15 +83,15 @@ export async function convertImages(settings: ScriptOptions): Promise<void> {
...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) {
Expand Down
15 changes: 7 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 11 additions & 43 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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;
}
6 changes: 3 additions & 3 deletions src/parseArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ export function getCliOptions(rawArgs: Argv<object> | 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)),
};
Expand Down
7 changes: 3 additions & 4 deletions src/parseOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ export function parseOptions(
const newSettings: Partial<ScriptOptions> = {};

// 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 };

Expand Down
98 changes: 57 additions & 41 deletions src/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CompressionOptionsMap> {
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,
Expand All @@ -51,33 +30,70 @@ 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,
);
}

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;
}
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export type SquashOptions = {
};

export interface CliOptions {
srcDir: string;
distDir: string;
srcDir?: string;
distDir?: string;
verbose?: boolean;
configFile?: string;
interactive?: boolean;
Expand Down
7 changes: 5 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -316,7 +319,7 @@ export function generateDefaultConfigFile(
resizeType: (argv.resizeType as ResizeType) || undefined,
} as Record<string, unknown>;

let defaultConfig: Record<string, unknown> = {
const defaultConfig: Record<string, unknown> = {
path: {
in: argv.input ?? "images",
out: argv.output ?? "optimized",
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 7 additions & 7 deletions tests/unit/parseOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ describe("getPromptOptions", () => {

// Mock the prompts function to return the user's input
(prompts as MockedFunction<typeof prompts>).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<typeof prompts>).mockResolvedValueOnce({
Expand All @@ -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 },
Expand All @@ -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);
});
});

Expand Down
Loading