core/persistence/folder.persistence.ts
core/persistence/folder.persistence.ts is a file in Bane's Lab Build Scripts. 89 lines of code and 23 definitions.
import { copyFile, mkdir, readFile, readdir, rm, rmdir, stat } from "node:fs/promises";
import { dirname, join, relative } from "node:path";
import type { FolderSync } from "#types/folder.types";
import { existsSync } from "node:fs";
import { toPosix } from "#core/resolvers/asset.resolver";
const BATCH = 64;
const admitAll = function admitAll(): boolean {
return true;
};
const filesUnder = async function filesUnder(
root: string,
dir: string,
admit: (path: string) => boolean,
): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const nested = await Promise.all(
entries
.map((entry) => ({ entry, path: join(dir, entry.name) }))
.filter(({ path }) => admit(path))
.map(async ({ entry, path }) =>
entry.isDirectory() ? filesUnder(root, path, admit) : [toPosix(relative(root, path))],
),
);
return nested.flat();
};
const differs = async function differs(source: string, target: string): Promise<boolean> {
if (!existsSync(target)) {
return true;
}
const [from, to] = await Promise.all([stat(source), stat(target)]);
if (from.size !== to.size) {
return true;
}
const [left, right] = await Promise.all([readFile(source), readFile(target)]);
return !left.equals(right);
};
const inBatches = async function inBatches<T>(
items: readonly T[],
each: (item: T) => Promise<boolean>,
): Promise<number> {
const batches = Array.from({ length: Math.ceil(items.length / BATCH) }, (_, index) =>
items.slice(index * BATCH, (index + 1) * BATCH),
);
return batches.reduce(async (previous, batch) => {
const done = await previous;
const results = await Promise.all(batch.map(each));
return done + results.filter(Boolean).length;
}, Promise.resolve(0));
};
const pruneEmptyFolders = async function pruneEmptyFolders(dir: string): Promise<boolean> {
const entries = await readdir(dir, { withFileTypes: true });
const emptied = await Promise.all(
entries.filter((entry) => entry.isDirectory()).map(async (entry) => pruneEmptyFolders(join(dir, entry.name))),
);
const left = entries.length - emptied.filter(Boolean).length;
if (left === 0) {
await rmdir(dir);
return true;
}
return false;
};
export const syncFolder = async function syncFolder(
from: string,
to: string,
admit: (path: string) => boolean = admitAll,
): Promise<FolderSync> {
const wanted = await filesUnder(from, from, admit);
const kept = new Set(wanted);
const present = existsSync(to) ? await filesUnder(to, to, admitAll) : [];
const copied = await inBatches(wanted, async (file) => {
const source = join(from, file);
const target = join(to, file);
if (!(await differs(source, target))) {
return false;
}
await mkdir(dirname(target), { recursive: true });
await copyFile(source, target);
return true;
});
const stale = present.filter((file) => !kept.has(file));
await inBatches(stale, async (file) => {
await rm(join(to, file), { force: true });
return true;
});
if (existsSync(to)) {
await pruneEmptyFolders(to);
}
return { copied, removed: stale.length };
};