@bevry/fs-list
Version:
List the entire contents of a directory.
157 lines (156 loc) • 5.42 kB
JavaScript
// builtin
import { readdir as _readdir, stat as _stat } from 'fs';
import { join } from 'path';
// versions
import { versions } from 'process';
import versionCompare from 'version-compare';
const nodeVersion = String(versions.node || '0');
// external
import accessible, { R_OK } from '@bevry/fs-accessible';
import Errlop from 'errlop';
/**
* List the entire contents of a directory, for Node.js versions 18.7.0 and up. 110-120ms.
* @returns the subpaths of the directory, sorted alphabetically.
* @experimental used internally, however external use is for your curiosity
*/
export async function readdirRecursive(directory) {
return new Promise(function (resolve, reject) {
_readdir(directory, { encoding: null, recursive: true }, function (err, files) {
if (err)
reject(err);
else
resolve(files.sort());
});
});
}
/** Helper for {@link readdirStat} to fetch {@link Stats} */
async function statHelper(path) {
return new Promise(function (resolve, reject) {
_stat(path, function (err, stats) {
if (err)
reject(err);
else
resolve(stats);
});
});
}
/** Helper for {@link readdirStat} to fetch {@link Paths} */
async function readdirHelper(directory) {
return new Promise(function (resolve, reject) {
_readdir(directory, function (err, files) {
if (err)
reject(err);
else
resolve(files);
});
});
}
/**
* List the entire contents of a directory, for all Node.js versions. 70-80ms.
* @returns the subpaths of the directory, sorted alphabetically.
* @experimental used internally, however external use is for your curiosity
*/
async function readdirStat(directory) {
// prepare
const pending = [directory];
const results = [];
const trim = directory === '.' ? 0 : directory.length + 1;
// add subsequent
while (pending.length) {
await Promise.all(pending.splice(0, pending.length).map(async function (subdirectory) {
const files = await readdirHelper(subdirectory);
for (const file of files) {
const path = join(subdirectory, file);
const stat = await statHelper(path);
results.push(trim ? path.substring(trim) : path);
if (stat.isDirectory())
pending.push(path);
}
}));
}
// return sorted results
return results.sort();
}
/** Helper for {@link readdirFileTypes} to fetch {@link Dirent} */
async function readdirFileTypesHelper(directory) {
return new Promise(function (resolve, reject) {
_readdir(directory, { withFileTypes: true }, function (err, files) {
if (err)
reject(err);
else
resolve(files);
});
});
}
/**
* List the entire contents of a directory, for Node.js versions 10 and up. 70-80ms.
* @returns the subpaths of the directory, sorted alphabetically.
* @experimental used internally, however external use is for your curiosity
*/
export async function readdirFileTypes(directory) {
// prepare
const pending = [directory];
const results = [];
const trim = directory === '.' ? 0 : directory.length + 1;
// add subsequent
while (pending.length) {
await Promise.all(pending.splice(0, pending.length).map(async function (subdirectory) {
const files = await readdirFileTypesHelper(subdirectory);
for (const file of files) {
const path = join(subdirectory, file.name);
results.push(trim ? path.substring(trim) : path);
if (file.isDirectory())
pending.push(path);
}
}));
}
// return sorted results
return results.sort();
}
/**
* List the entire contents of a directory, selecting the appropriate technique for the Node.js version.
* @returns the subpaths of the directory, sorted alphabetically.
*/
export default async function list(directory) {
// check accessible
try {
await accessible(directory);
}
catch (err) {
throw new Errlop(`unable to list contents of the non-accessible directory: ${directory}`, err);
}
// check readable
try {
await accessible(directory, R_OK);
}
catch (err) {
throw new Errlop(`unable to list contents of the non-readable directory: ${directory}`, err);
}
// fatest method
try {
return versionCompare(nodeVersion, '18.7.0') >= 0
? await readdirRecursive(directory)
: versionCompare(nodeVersion, '10') >= 0
? await readdirFileTypes(directory)
: await readdirStat(directory);
}
catch (err) {
throw new Errlop(`unable to list contents of the directory: ${directory}`, err);
}
}
// 100-110ms, macOS only
// import { exec } from 'child_process'
// async function readdirFind(directory: string): Promise<Paths> {
// return new Promise(function (resolve, reject) {
// exec('find .', { cwd: directory }, function (err, stdout: string) {
// if (err) return reject(err)
// return resolve(
// stdout
// .split('\n')
// .map((p) => p.replace(/^[./\\]+/, '')) // trim . and ./
// .filter(Boolean)
// .sort()
// )
// })
// })
// }