diff --git a/__tests__/fdir.test.ts b/__tests__/fdir.test.ts index 882e2afd..aea5022a 100644 --- a/__tests__/fdir.test.ts +++ b/__tests__/fdir.test.ts @@ -322,6 +322,21 @@ for (const type of apiTypes) { mock.restore(); }); + test(`[${type}] crawl all files and include resolved symlinks without real paths`, async (t) => { + mock(mockFsWithSymlinks); + + const api = new fdir().withSymlinks({ resolvePaths: false }).crawl("/some/dir"); + const files = await api[type](); + t.expect(files).toHaveLength(3); + t.expect( + files.indexOf(resolveSymlinkRoot("/some/dir/dirSymlink/file-1")) > -1 + ).toBeTruthy(); + t.expect( + files.indexOf(resolveSymlinkRoot("/some/dir/dirSymlink/file-excluded-1")) > -1 + ).toBeTruthy(); + mock.restore(); + }); + test("crawl all files and include resolved symlinks with exclusions", async (t) => { mock(mockFsWithSymlinks); const api = new fdir() @@ -379,6 +394,27 @@ for (const type of apiTypes) { } mock.restore(); }); + + test("crawl all files (including symlinks without real paths) and throw errors", async (t) => { + mock({ + "/other/dir": {}, + "/some/dir": { + fileSymlink: mock.symlink({ + path: "/other/dir/file-3", + }), + }, + }); + + try { + const api = new fdir().withErrors().withSymlinks({ resolvePaths: false }).crawl("/some/dir"); + + await api[type](); + } catch (e) { + if (e instanceof Error) + t.expect(e.message.includes("no such file or directory")).toBeTruthy(); + } + mock.restore(); + }); } test(`[async] crawl directory & use abort signal to abort`, async (t) => { diff --git a/documentation.md b/documentation.md index 54f8a47c..625822b0 100644 --- a/documentation.md +++ b/documentation.md @@ -102,7 +102,7 @@ Use this to also add the directories to the output. const crawler = new fdir().withDirs(); ``` -### `withSymlinks(boolean)` +### `withSymlinks({ resolvePaths: boolean })` Use this to follow all symlinks recursively. @@ -116,10 +116,10 @@ Use this to follow all symlinks recursively. ```js // to resolve all symlinked paths to their original path -const crawler = new fdir().withSymlinks(true); +const crawler = new fdir().withSymlinks({ resolvePaths: true }); // to disable path resolution -const crawler = new fdir().withSymlinks(false); +const crawler = new fdir().withSymlinks({ resolvePaths: false }); ``` ### `withMaxDepth(number)` @@ -379,6 +379,7 @@ type Options = { onlyCounts?: boolean; filters?: FilterFn[]; resolveSymlinks?: boolean; + useRealPaths?: boolean; excludeFiles?: boolean; exclude?: ExcludeFn; relativePaths?: boolean; diff --git a/src/api/functions/resolve-symlink.ts b/src/api/functions/resolve-symlink.ts index 2a91d5b3..e35c3110 100644 --- a/src/api/functions/resolve-symlink.ts +++ b/src/api/functions/resolve-symlink.ts @@ -18,6 +18,28 @@ const resolveSymlinksAsync: ResolveSymlinkFunction = function( } = state; queue.enqueue(); + fs.stat(path, (error, stat) => { + if (error) { + queue.dequeue(suppressErrors ? null : error, state); + return; + } + + callback(stat, path); + queue.dequeue(null, state); + }); +}; + +const resolveSymlinksWithRealPathsAsync: ResolveSymlinkFunction = function( + path, + state, + callback +) { + const { + queue, + options: { suppressErrors }, + } = state; + queue.enqueue(); + fs.realpath(path, (error, resolvedPath) => { if (error) { queue.dequeue(suppressErrors ? null : error, state); @@ -36,6 +58,19 @@ const resolveSymlinksSync: ResolveSymlinkFunction = function( path, state, callback +) { + try { + const stat = fs.statSync(path); + callback(stat, path); + } catch (e) { + if (!state.options.suppressErrors) throw e; + } +}; + +const resolveSymlinksWithRealPathsSync: ResolveSymlinkFunction = function( + path, + state, + callback ) { try { const resolvedPath = fs.realpathSync(path); @@ -52,5 +87,11 @@ export function build( ): ResolveSymlinkFunction | null { if (!options.resolveSymlinks) return null; - return isSynchronous ? resolveSymlinksSync : resolveSymlinksAsync; + if (options.useRealPaths) + return isSynchronous + ? resolveSymlinksWithRealPathsSync + : resolveSymlinksWithRealPathsAsync; + return isSynchronous + ? resolveSymlinksSync + : resolveSymlinksAsync; } diff --git a/src/builder/index.ts b/src/builder/index.ts index a4fabb6b..2bcf8042 100644 --- a/src/builder/index.ts +++ b/src/builder/index.ts @@ -80,8 +80,9 @@ export class Builder { return this; } - withSymlinks() { + withSymlinks({ resolvePaths = true } = {}) { this.options.resolveSymlinks = true; + this.options.useRealPaths = resolvePaths; return this.withFullPaths(); } diff --git a/src/types.ts b/src/types.ts index e2bc00df..3c5ccf9c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -53,6 +53,7 @@ export type Options = { onlyCounts?: boolean; filters: FilterPredicate[]; resolveSymlinks?: boolean; + useRealPaths?: boolean; excludeFiles?: boolean; exclude?: ExcludePredicate; relativePaths?: boolean;