scripts/walk-files.ts
Rama main · réplica de lectura del repositorio original.
import { join } from "@std/path";
/**
* What `directory` holds, as a list. A directory that is not there fails here,
* loudly: the caller asks about a root it believes in, and an empty answer
* reads as "nothing to do".
*/
export const directoryEntries = async (
directory: string,
): Promise<Deno.DirEntry[]> => {
const entries: Deno.DirEntry[] = [];
for await (const entry of Deno.readDir(directory)) entries.push(entry);
return entries;
};
/** Recursively yield every file path beneath `directory` (depth-first). */
export async function* walkFiles(directory: string): AsyncGenerator<string> {
for (const entry of await directoryEntries(directory)) {
const path = join(directory, entry.name);
if (entry.isDirectory) {
yield* walkFiles(path);
continue;
}
yield path;
}
}
/** Every file beneath `directory` whose path passes `keep`, sorted. */
export const collectFiles = async (
directory: string,
keep: (path: string) => boolean,
): Promise<string[]> => {
const files: string[] = [];
for await (const path of walkFiles(directory)) {
if (keep(path)) files.push(path);
}
return files.sort();
};
/** Every file beneath a directory whose path matches, sorted. */
const collectMatching =
(pattern: RegExp): ((directory: string) => Promise<string[]>) =>
(directory) =>
collectFiles(directory, (path) => pattern.test(path));
/** Every TypeScript file beneath `directory`, sorted. */
export const collectSourceFiles = collectMatching(/\.tsx?$/);
/** Every TypeScript or JavaScript file beneath `directory`, sorted. A browser
* script we ship as plain `.js` is source too, so a check that has to read all
* of our code reaches for this one. */
export const collectScriptFiles = collectMatching(/\.[cm]?[jt]sx?$/);
/**
* A script collector that leaves out every file `frozen` names. One mechanism
* behind both collectors below, so they cannot drift apart.
*/
const scriptsSkipping =
(frozen: (file: string) => boolean) =>
async (directory: string): Promise<string[]> =>
(await collectScriptFiles(directory)).filter((file) => !frozen(file));
/**
* Files no code check reads: the browser bundles in `ui/static`, which esbuild
* builds, and the schema-columns module, which a script generates. Nobody
* writes them by hand, so a finding in one has no author to fix it.
*/
export const isGeneratedFile = (file: string): boolean =>
/(^|\/)migrations\/schema\/columns\.ts$/.test(file) ||
/(^|\/)ui\/static\//.test(file);
/** Every script the rule gates read: all TypeScript and JavaScript beneath
* `directory`, built output left out. */
export const collectGateScriptFiles = scriptsSkipping(isGeneratedFile);
/** Every file a rule gate reads: the gate scripts, the authored stylesheets,
* and the shell scripts. The `.scss` under `ui/static` is written by hand
* even though the bundles beside it are built, so it joins this collector.
* The built `style.css` and the vendored Leaflet stylesheet stay out. */
export const collectGateFiles = async (
directory: string,
): Promise<string[]> => {
const gateScripts = await collectGateScriptFiles(directory);
const stylesheets = (
await collectMatching(/\.(?:css|scss)$/)(directory)
).filter((file) => file.endsWith(".scss") || !isGeneratedFile(file));
const shellScripts = await collectMatching(/\.sh$/)(directory);
return [...gateScripts, ...stylesheets, ...shellScripts].sort();
};
/**
* Read every file the collector gathers under each root, and collect what one
* reader derives from a file's path and text. The reader returns its items
* for that file. A reader with nothing to say about a file returns an empty
* list for it. */
export const collectFromFiles = async <Item>(
roots: readonly string[],
collect: (root: string) => Promise<string[]>,
read: (file: string, content: string) => Item[],
): Promise<Item[]> => {
const items: Item[] = [];
for (const root of roots) {
for (const file of await collect(root)) {
items.push(...read(file, await Deno.readTextFile(file)));
}
}
return items;
};