diff --git a/src/catalog.js b/src/catalog.js index 9495c30..1f61f1a 100644 --- a/src/catalog.js +++ b/src/catalog.js @@ -42,6 +42,15 @@ export const cacheRules = [ }, ]; +/** + * Find the catalog rule for a path relative to a scan root. + * + * Windows paths are normalized to lowercase to match the platform filesystem's + * case-insensitive behavior. + * + * @param {string} relativePath Path relative to the scan root. + * @returns {{ id: string, label: string, matches: Array } | undefined} + */ export function findCacheRule(relativePath) { const parts = relativePath .split(/[\\/]+/) @@ -53,6 +62,13 @@ export function findCacheRule(relativePath) { ); } +/** + * Resolve a candidate path into the relative form used by cache catalog checks. + * + * @param {string} root Scan root. + * @param {string} candidate Candidate cache path. + * @returns {string} Candidate path relative to the resolved root. + */ export function relativeCachePath(root, candidate) { return path.relative(path.resolve(root), path.resolve(candidate)); } diff --git a/src/cleaner.js b/src/cleaner.js index feec42a..dbe2070 100644 --- a/src/cleaner.js +++ b/src/cleaner.js @@ -20,6 +20,16 @@ function assertSafeTarget(root, candidate) { return rule; } +/** + * Remove cache directories that still match the catalog under the scan root. + * + * Each target is resolved again before deletion so stale scan results cannot + * remove paths outside the root or paths that no longer look like known caches. + * + * @param {string} root Original scan root. + * @param {Array} caches Cache entries returned by the scanner/filter. + * @returns {Promise>} Removal results with `removed` or `failed` status. + */ export async function removeCaches(root, caches) { const resolvedRoot = path.resolve(root); const results = []; diff --git a/src/cli.js b/src/cli.js index 80858a1..640bcb8 100644 --- a/src/cli.js +++ b/src/cli.js @@ -16,6 +16,13 @@ async function readVersion() { return packageJson.version; } +/** + * Execute the CLI command and return a process-style exit code. + * + * @param {string[]} args Raw command-line arguments without the executable name. + * @param {{ log: Function, error: Function }} [io] Output adapter for tests. + * @returns {Promise} Exit code. + */ export async function run(args, io = console) { let options; try { diff --git a/src/filter.js b/src/filter.js index 0af34b1..b0f805a 100644 --- a/src/filter.js +++ b/src/filter.js @@ -1,3 +1,11 @@ +/** + * Apply user-selected type, size, and age constraints to scanned cache entries. + * + * @param {Array} caches Cache entries returned by the scanner. + * @param {{ minSize?: number, olderThan?: number, types?: Set }} options + * @param {number} [now] Timestamp used for age comparisons. + * @returns {Array} Cache entries that match all active filters. + */ export function filterCaches( caches, { minSize = 0, olderThan = 0, types = new Set() }, diff --git a/src/options.js b/src/options.js index 43f9bcb..91106a7 100644 --- a/src/options.js +++ b/src/options.js @@ -26,6 +26,13 @@ function readValue(args, index, flag) { return value; } +/** + * Parse CLI arguments into normalized scan, filter, and output options. + * + * @param {string[]} args Raw command-line arguments without the executable name. + * @param {string} [cwd] Current working directory used to resolve the root. + * @returns {object} Normalized CLI options. + */ export function parseOptions(args, cwd = process.cwd()) { const options = { root: cwd, diff --git a/src/report.js b/src/report.js index b579409..92e923e 100644 --- a/src/report.js +++ b/src/report.js @@ -4,6 +4,12 @@ function pad(value, width) { return String(value).padEnd(width); } +/** + * Build aggregate counts for a cache result set. + * + * @param {Array} caches Cache entries included in a report. + * @returns {{ directories: number, files: number, bytes: number }} + */ export function createSummary(caches) { return { directories: caches.length, @@ -12,6 +18,12 @@ export function createSummary(caches) { }; } +/** + * Render a human-readable scan report for terminal output. + * + * @param {object} report Scan, warning, and optional removal details. + * @returns {string} Formatted text report. + */ export function formatTextReport({ root, caches, @@ -82,6 +94,12 @@ export function formatTextReport({ return lines.join("\n"); } +/** + * Render a stable JSON report for scripts and CI jobs. + * + * @param {object} report Scan, warning, and optional removal details. + * @returns {string} Pretty-printed JSON report. + */ export function formatJsonReport({ root, caches, diff --git a/src/scanner.js b/src/scanner.js index cdadfc1..ced506e 100644 --- a/src/scanner.js +++ b/src/scanner.js @@ -51,6 +51,16 @@ async function measureDirectory(directory, warnings) { return measurement; } +/** + * Walk a repository tree and return directories that match the cache catalog. + * + * The scanner skips symbolic links, avoids `.git`, records read failures as + * warnings, and measures each matched cache before returning it. + * + * @param {string} root Directory to scan. + * @param {{ maxDepth?: number }} [options] Traversal options. + * @returns {Promise<{ root: string, caches: Array, warnings: string[] }>} + */ export async function scanCaches(root, { maxDepth = 12 } = {}) { const resolvedRoot = path.resolve(root); const rootStats = await lstat(resolvedRoot); diff --git a/src/units.js b/src/units.js index fb957d8..0dc9947 100644 --- a/src/units.js +++ b/src/units.js @@ -29,14 +29,32 @@ function parseUnitValue(input, units, label) { return Number(match[1]) * multiplier; } +/** + * Parse a size string such as `10mb` into bytes. + * + * @param {string} input Size value with a supported unit suffix. + * @returns {number} Rounded byte count. + */ export function parseSize(input) { return Math.round(parseUnitValue(input, sizeUnits, "size")); } +/** + * Parse a duration string such as `7d` into milliseconds. + * + * @param {string} input Duration value with a supported unit suffix. + * @returns {number} Rounded millisecond count. + */ export function parseDuration(input) { return Math.round(parseUnitValue(input, durationUnits, "duration")); } +/** + * Format a byte count for terminal reports. + * + * @param {number} bytes Byte count. + * @returns {string} Human-readable size. + */ export function formatBytes(bytes) { if (bytes < 1024) { return `${bytes} B`; @@ -56,6 +74,13 @@ export function formatBytes(bytes) { return `${value >= 10 ? value.toFixed(1) : value.toFixed(2)} ${unit}`; } +/** + * Format elapsed time since a cache entry was last modified. + * + * @param {number} modifiedAtMs Last modified timestamp in milliseconds. + * @param {number} [now] Current timestamp for deterministic tests. + * @returns {string} Compact age string. + */ export function formatAge(modifiedAtMs, now = Date.now()) { const elapsed = Math.max(0, now - modifiedAtMs); const hours = Math.floor(elapsed / (60 * 60_000));