diff --git a/Herebyfile.mjs b/Herebyfile.mjs index 38de8d780e..db64305c1b 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -852,6 +852,65 @@ function baselineAcceptTask(localBaseline, refBaseline) { return path.join(refBaseline, relative); } + /** + * Recursively find top-most empty subtrees under root, excluding root itself. + * Returns the highest ancestors whose entire subtrees contain only empty + * directories, so a single rimraf can remove each whole subtree at once. + * @param {string} root + * @returns {string[]} + */ + function findEmptySubtrees(root) { + /** @type {string[]} */ + const result = []; + + /** + * @param {string} dir + * @returns {boolean} true if dir is entirely empty + */ + function visit(dir) { + /** @type {import("fs").Dirent[]} */ + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } + catch { + return false; + } + + let empty = true; + /** @type {string[]} */ + const childEmpty = []; + for (const entry of entries) { + if (entry.isDirectory()) { + const childPath = path.join(dir, entry.name); + if (visit(childPath)) { + childEmpty.push(childPath); + } + else { + empty = false; + } + } + else { + empty = false; + } + } + + if (!empty) { + // This dir is not fully empty, so collect the empty children + // we found (they are top-most empty subtrees). + result.push(...childEmpty); + } + // If empty, don't collect children — the caller will either + // collect this dir or propagate emptiness further up. + return empty; + } + + // If root itself is entirely empty, there's nothing to collect + // (we exclude root). Otherwise, visit already pushed the right subtrees. + visit(root); + return result; + } + return async () => { const toCopy = await glob(`${localBaseline}/**`, { nodir: true, ignore: `${localBaseline}/**/*.delete` }); for (const p of toCopy) { @@ -865,6 +924,14 @@ function baselineAcceptTask(localBaseline, refBaseline) { await rimraf(out); await rimraf(p); // also delete the .delete file so that it no longer shows up in a diff tool. } + + // Remove empty directory trees left behind by deletions. + for (const dir of findEmptySubtrees(refBaseline)) { + await rimraf(dir); + } + for (const dir of findEmptySubtrees(localBaseline)) { + await rimraf(dir); + } }; }