UNPKG

@jsenv/util

Version:

Set of functions often needed when using Node.js.

110 lines (99 loc) 3.94 kB
import { createCancellationToken, createOperation } from "@jsenv/cancellation" import { normalizeStructuredMetaMap, urlCanContainsMetaMatching, urlToMeta } from "@jsenv/url-meta" import { assertAndNormalizeDirectoryUrl } from "./assertAndNormalizeDirectoryUrl.js" import { readDirectory } from "./readDirectory.js" import { readFileSystemNodeStat } from "./readFileSystemNodeStat.js" import { urlToRelativeUrl } from "./urlToRelativeUrl.js" import { comparePathnames } from "./comparePathnames.js" export const collectFiles = async ({ cancellationToken = createCancellationToken(), directoryUrl, structuredMetaMap, predicate, matchingFileOperation = () => null, }) => { const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl) if (typeof predicate !== "function") { throw new TypeError(`predicate must be a function, got ${predicate}`) } if (typeof matchingFileOperation !== "function") { throw new TypeError(`matchingFileOperation must be a function, got ${matchingFileOperation}`) } const structuredMetaMapNormalized = normalizeStructuredMetaMap( structuredMetaMap, rootDirectoryUrl, ) const matchingFileResultArray = [] const visitDirectory = async (directoryUrl) => { const directoryItems = await createOperation({ cancellationToken, start: () => readDirectory(directoryUrl), }) await Promise.all( directoryItems.map(async (directoryItem) => { const directoryChildNodeUrl = `${directoryUrl}${directoryItem}` const directoryChildNodeStats = await createOperation({ cancellationToken, start: () => readFileSystemNodeStat(directoryChildNodeUrl, { // we ignore symlink because recursively traversed // so symlinked file will be discovered. // Moreover if they lead outside of directoryPath it can become a problem // like infinite recursion of whatever. // that we could handle using an object of pathname already seen but it will be useless // because directoryPath is recursively traversed followLink: false, }), }) if (directoryChildNodeStats.isDirectory()) { const subDirectoryUrl = `${directoryChildNodeUrl}/` if ( !urlCanContainsMetaMatching({ url: subDirectoryUrl, structuredMetaMap: structuredMetaMapNormalized, predicate, }) ) { return } await visitDirectory(subDirectoryUrl) return } if (directoryChildNodeStats.isFile()) { const meta = urlToMeta({ url: directoryChildNodeUrl, structuredMetaMap: structuredMetaMapNormalized, }) if (!predicate(meta)) return const relativeUrl = urlToRelativeUrl(directoryChildNodeUrl, rootDirectoryUrl) const operationResult = await createOperation({ cancellationToken, start: () => matchingFileOperation({ cancellationToken, relativeUrl, meta, fileStats: directoryChildNodeStats, }), }) matchingFileResultArray.push({ relativeUrl, meta, fileStats: directoryChildNodeStats, operationResult, }) return } }), ) } await visitDirectory(rootDirectoryUrl) // When we operate on thoose files later it feels more natural // to perform operation in the same order they appear in the filesystem. // It also allow to get a predictable return value. // For that reason we sort matchingFileResultArray matchingFileResultArray.sort((leftFile, rightFile) => { return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl) }) return matchingFileResultArray }