115 lines
5.1 KiB
JavaScript
115 lines
5.1 KiB
JavaScript
|
import fs from "fs/promises";
|
||
|
import path from "path";
|
||
|
/**
|
||
|
* Recursively reads a directory and returns the list of pathnames.
|
||
|
*
|
||
|
* @param rootDirectory the directory to read
|
||
|
* @param options options to control the behavior of the recursive read
|
||
|
* @returns the list of pathnames
|
||
|
*/ export async function recursiveReadDir(rootDirectory, options = {}) {
|
||
|
// Grab our options.
|
||
|
const { pathnameFilter, ignoreFilter, ignorePartFilter, sortPathnames = true, relativePathnames = true } = options;
|
||
|
// The list of pathnames to return.
|
||
|
const pathnames = [];
|
||
|
/**
|
||
|
* Coerces the pathname to be relative if requested.
|
||
|
*/ const coerce = relativePathnames ? (pathname)=>pathname.replace(rootDirectory, "") : (pathname)=>pathname;
|
||
|
// The queue of directories to scan.
|
||
|
let directories = [
|
||
|
rootDirectory
|
||
|
];
|
||
|
while(directories.length > 0){
|
||
|
// Load all the files in each directory at the same time.
|
||
|
const results = await Promise.all(directories.map(async (directory)=>{
|
||
|
const result = {
|
||
|
directories: [],
|
||
|
pathnames: [],
|
||
|
links: []
|
||
|
};
|
||
|
try {
|
||
|
const dir = await fs.readdir(directory, {
|
||
|
withFileTypes: true
|
||
|
});
|
||
|
for (const file of dir){
|
||
|
// If enabled, ignore the file if it matches the ignore filter.
|
||
|
if (ignorePartFilter && ignorePartFilter(file.name)) {
|
||
|
continue;
|
||
|
}
|
||
|
// Handle each file.
|
||
|
const absolutePathname = path.join(directory, file.name);
|
||
|
// If enabled, ignore the file if it matches the ignore filter.
|
||
|
if (ignoreFilter && ignoreFilter(absolutePathname)) {
|
||
|
continue;
|
||
|
}
|
||
|
// If the file is a directory, then add it to the list of directories,
|
||
|
// they'll be scanned on a later pass.
|
||
|
if (file.isDirectory()) {
|
||
|
result.directories.push(absolutePathname);
|
||
|
} else if (file.isSymbolicLink()) {
|
||
|
result.links.push(absolutePathname);
|
||
|
} else if (!pathnameFilter || pathnameFilter(absolutePathname)) {
|
||
|
result.pathnames.push(coerce(absolutePathname));
|
||
|
}
|
||
|
}
|
||
|
} catch (err) {
|
||
|
// This can only happen when the underlying directory was removed. If
|
||
|
// anything other than this error occurs, re-throw it.
|
||
|
// if (err.code !== 'ENOENT') throw err
|
||
|
if (err.code !== "ENOENT" || directory === rootDirectory) throw err;
|
||
|
// The error occurred, so abandon reading this directory.
|
||
|
return null;
|
||
|
}
|
||
|
return result;
|
||
|
}));
|
||
|
// Empty the directories, we'll fill it later if some of the files are
|
||
|
// directories.
|
||
|
directories = [];
|
||
|
// Keep track of any symbolic links we find, we'll resolve them later.
|
||
|
const links = [];
|
||
|
// For each result of directory scans...
|
||
|
for (const result of results){
|
||
|
// If the directory was removed, then skip it.
|
||
|
if (!result) continue;
|
||
|
// Add any directories to the list of directories to scan.
|
||
|
directories.push(...result.directories);
|
||
|
// Add any symbolic links to the list of symbolic links to resolve.
|
||
|
links.push(...result.links);
|
||
|
// Add any file pathnames to the list of pathnames.
|
||
|
pathnames.push(...result.pathnames);
|
||
|
}
|
||
|
// Resolve all the symbolic links we found if any.
|
||
|
if (links.length > 0) {
|
||
|
const resolved = await Promise.all(links.map(async (absolutePathname)=>{
|
||
|
try {
|
||
|
return await fs.stat(absolutePathname);
|
||
|
} catch (err) {
|
||
|
// This can only happen when the underlying link was removed. If
|
||
|
// anything other than this error occurs, re-throw it.
|
||
|
if (err.code !== "ENOENT") throw err;
|
||
|
// The error occurred, so abandon reading this directory.
|
||
|
return null;
|
||
|
}
|
||
|
}));
|
||
|
for(let i = 0; i < links.length; i++){
|
||
|
const stats = resolved[i];
|
||
|
// If the link was removed, then skip it.
|
||
|
if (!stats) continue;
|
||
|
// We would have already ignored the file if it matched the ignore
|
||
|
// filter, so we don't need to check it again.
|
||
|
const absolutePathname = links[i];
|
||
|
if (stats.isDirectory()) {
|
||
|
directories.push(absolutePathname);
|
||
|
} else if (!pathnameFilter || pathnameFilter(absolutePathname)) {
|
||
|
pathnames.push(coerce(absolutePathname));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Sort the pathnames in place if requested.
|
||
|
if (sortPathnames) {
|
||
|
pathnames.sort();
|
||
|
}
|
||
|
return pathnames;
|
||
|
}
|
||
|
|
||
|
//# sourceMappingURL=recursive-readdir.js.map
|