UNPKG

dejarun

Version:

A file system operation cache for Node.js, using a hash of the file or folder contents as the cache key.

106 lines (94 loc) 3.24 kB
import chalk from "chalk"; import { defaults, hashElement } from "folder-hash"; import { createHash } from "node:crypto"; import fs from "node:fs/promises"; import logger from "./logger.ts"; import { each } from "./utils/tagged-templates.ts"; function hashValue(value: any) { return createHash(defaults.algo ?? "sha1") .update(JSON.stringify(value)) .digest(defaults.encoding ?? "base64"); } const hashFileOrDirList = (paths: string[] = []) => Promise.all( paths.map(async (path) => { try { return (await hashElement(path)).hash; } catch { return null; } }), ); /** * Caches the result of a callback function based on the hashes of input and output files or directories and additional dependencies. * If the hashes of the inputs, outputs and other dependencies have not changed since the last run, the callback will not be executed. * * @param name - A unique name for the cache entry. Example: "build". * @param callback - The function to execute if the cache is missed. * @param options - An object containing the following optional properties: * - inputs: An array of file or directory paths to hash for cache validation. * - outputs: An array of file or directory paths to hash for cache validation. * - dependencies: An array of arbitrary values to hash for cache validation. * - clean: A boolean indicating whether to clean the cache (default: false). * - debug: A boolean indicating whether to log debug information (default: false). * - logo: A string or false to customize the logo in the logs (default: '[déjàrun]'). */ export default async function dejarun( name: string, callback: () => Promise<any>, options?: { inputs?: string[]; outputs?: string[]; dependencies?: any[]; clean?: boolean; debug?: boolean; logo?: string | false; }, ) { const cacheName = `${process.env.npm_package_name}/${name}`; const CACHE_FILE = new URL("../.cache.json", import.meta.url).pathname; let cache: Record<string, string> = {}; try { cache = JSON.parse(await fs.readFile(CACHE_FILE, "utf8")); } catch {} const inputs = await hashFileOrDirList(options?.inputs); const totalHash = hashValue({ inputs, outputs: await hashFileOrDirList(options?.outputs), dependencies: options?.dependencies, }); if (options?.debug) { logger( "debug", ` name: ${chalk.bold(name)} cache name: ${chalk.bold(cacheName)} cache file: ${chalk.bold(CACHE_FILE)} inputs:${chalk.bold(each` ${options.inputs}`)} outputs:${chalk.bold(each` ${options.outputs}`)} dependencies:${chalk.bold(each` ${options.dependencies}`)} total hash: ${totalHash}`, options.logo, ); } if (options?.clean) { logger("clean", name, options.logo); delete cache[cacheName]; } else { if (totalHash === cache[cacheName]) { logger("hit", name, options?.logo); return; } logger("miss", name, options?.logo); } await callback(); cache[cacheName] = hashValue({ inputs, outputs: await hashFileOrDirList(options?.outputs), dependencies: options?.dependencies, }); await fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2)); }