From 6e3233afe8e83498eed55786b826acdaeb63a267 Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Wed, 16 Oct 2024 22:59:15 +0500 Subject: [PATCH] fix: recursive symlinks with relative paths not working --- __tests__/symlinks.test.ts | 94 +++++++++++++++++++++++++++- src/api/functions/resolve-symlink.ts | 8 +-- src/api/functions/walk-directory.ts | 11 ++-- src/api/walker.ts | 14 ++++- 4 files changed, 116 insertions(+), 11 deletions(-) diff --git a/__tests__/symlinks.test.ts b/__tests__/symlinks.test.ts index c5f6213..60a1afa 100644 --- a/__tests__/symlinks.test.ts +++ b/__tests__/symlinks.test.ts @@ -66,9 +66,51 @@ const fsWithRecursiveSymlinks = { }, }; +const fsWithRecursiveRelativeSymlinks = { + "double/recursive": { + "another-file": "hello", + "recursive-4": mock.symlink({ + path: "../../recursive", + }), + }, + "just/some": { + "another-file": "hello", + "another-file2": "hello", + "symlink-to-earth": mock.symlink({ + path: "../../random/other", + }), + }, + "random/other": { + "another-file": "hello", + "another-file2": "hello", + }, + recursive: { + "random-file": "somecontent", + }, + "recursive/dir": { + "some-file": "somecontent2", + "recursive-1": mock.symlink({ + path: "../../recursive/dir", + }), + "recursive-2": mock.symlink({ + path: "./recursive-1", + }), + "recursive-3": mock.symlink({ + path: "../../recursive", + }), + "recursive-5": mock.symlink({ + path: "../../double/recursive", + }), + "not-recursive": mock.symlink({ + path: "../../just/some", + }), + }, +}; + const mockFs = { ...fsWithRelativeSymlinks, ...fsWithRecursiveSymlinks, + ...fsWithRecursiveRelativeSymlinks, "/sym/linked": { "file-1": "file contents", @@ -176,6 +218,56 @@ for (const type of apiTypes) { ); }); + test(`resolve recursive symlinks (real paths: false, relative paths: true)`, async (t) => { + const api = new fdir() + .withSymlinks({ resolvePaths: false }) + .withRelativePaths() + .withErrors() + .crawl("./recursive"); + const files = await api[type](); + t.expect(files.sort()).toStrictEqual( + normalize([ + "dir/not-recursive/another-file", + "dir/not-recursive/another-file2", + "dir/not-recursive/symlink-to-earth/another-file", + "dir/not-recursive/symlink-to-earth/another-file2", + + "dir/recursive-1/not-recursive/another-file", + "dir/recursive-1/not-recursive/another-file2", + "dir/recursive-1/not-recursive/symlink-to-earth/another-file", + "dir/recursive-1/not-recursive/symlink-to-earth/another-file2", + "dir/recursive-1/recursive-5/another-file", + "dir/recursive-1/some-file", + + "dir/recursive-2/not-recursive/another-file", + "dir/recursive-2/not-recursive/another-file2", + "dir/recursive-2/not-recursive/symlink-to-earth/another-file", + "dir/recursive-2/not-recursive/symlink-to-earth/another-file2", + "dir/recursive-2/recursive-5/another-file", + "dir/recursive-2/some-file", + + "dir/recursive-3/dir/not-recursive/another-file", + "dir/recursive-3/dir/not-recursive/another-file2", + "dir/recursive-3/dir/not-recursive/symlink-to-earth/another-file", + "dir/recursive-3/dir/not-recursive/symlink-to-earth/another-file2", + "dir/recursive-3/dir/recursive-5/another-file", + "dir/recursive-3/dir/some-file", + "dir/recursive-3/random-file", + + "dir/recursive-5/another-file", + "dir/recursive-5/recursive-4/dir/not-recursive/another-file", + "dir/recursive-5/recursive-4/dir/not-recursive/another-file2", + "dir/recursive-5/recursive-4/dir/not-recursive/symlink-to-earth/another-file", + "dir/recursive-5/recursive-4/dir/not-recursive/symlink-to-earth/another-file2", + "dir/recursive-5/recursive-4/dir/some-file", + "dir/recursive-5/recursive-4/random-file", + + "dir/some-file", + "random-file", + ]) + ); + }); + test(`resolve symlinks (real paths: false)`, async (t) => { const api = new fdir() .withSymlinks({ resolvePaths: false }) @@ -205,7 +297,7 @@ for (const type of apiTypes) { ); }); - test(`crawl all files and include resolved symlinks with real paths with relative paths on`, async (t) => { + test(`resolve symlinks (real paths: true, relative paths: true)`, async (t) => { const api = new fdir() .withSymlinks() .withRelativePaths() diff --git a/src/api/functions/resolve-symlink.ts b/src/api/functions/resolve-symlink.ts index 37a9aeb..e4743a2 100644 --- a/src/api/functions/resolve-symlink.ts +++ b/src/api/functions/resolve-symlink.ts @@ -15,7 +15,7 @@ const resolveSymlinksAsync: ResolveSymlinkFunction = function ( ) { const { queue, - options: { suppressErrors, useRealPaths }, + options: { suppressErrors }, } = state; queue.enqueue(); @@ -28,7 +28,7 @@ const resolveSymlinksAsync: ResolveSymlinkFunction = function ( if (stat.isDirectory() && isRecursive(path, resolvedPath, state)) return queue.dequeue(null, state); - callback(stat, useRealPaths ? resolvedPath : path); + callback(stat, resolvedPath); queue.dequeue(null, state); }); }); @@ -41,7 +41,7 @@ const resolveSymlinks: ResolveSymlinkFunction = function ( ) { const { queue, - options: { suppressErrors, useRealPaths }, + options: { suppressErrors }, } = state; queue.enqueue(); @@ -51,7 +51,7 @@ const resolveSymlinks: ResolveSymlinkFunction = function ( if (stat.isDirectory() && isRecursive(path, resolvedPath, state)) return; - callback(stat, useRealPaths ? resolvedPath : path); + callback(stat, resolvedPath); } catch (e) { if (!suppressErrors) throw e; } diff --git a/src/api/functions/walk-directory.ts b/src/api/functions/walk-directory.ts index 9e09638..6c961c9 100644 --- a/src/api/functions/walk-directory.ts +++ b/src/api/functions/walk-directory.ts @@ -3,6 +3,7 @@ import fs from "fs"; export type WalkDirectoryFunction = ( state: WalkerState, + crawlPath: string, directoryPath: string, depth: number, callback: (entries: fs.Dirent[], directoryPath: string, depth: number) => void @@ -12,19 +13,20 @@ const readdirOpts = { withFileTypes: true } as const; const walkAsync: WalkDirectoryFunction = ( state, + crawlPath, directoryPath, currentDepth, callback ) => { if (currentDepth < 0) return; - state.visited.push(directoryPath); + state.visited.push(crawlPath); state.counts.directories++; state.queue.enqueue(); // Perf: Node >= 10 introduced withFileTypes that helps us // skip an extra fs.stat call. - fs.readdir(directoryPath || ".", readdirOpts, (error, entries = []) => { + fs.readdir(crawlPath || ".", readdirOpts, (error, entries = []) => { callback(entries, directoryPath, currentDepth); state.queue.dequeue(state.options.suppressErrors ? null : error, state); @@ -33,17 +35,18 @@ const walkAsync: WalkDirectoryFunction = ( const walkSync: WalkDirectoryFunction = ( state, + crawlPath, directoryPath, currentDepth, callback ) => { if (currentDepth < 0) return; - state.visited.push(directoryPath); + state.visited.push(crawlPath); state.counts.directories++; let entries: fs.Dirent[] = []; try { - entries = fs.readdirSync(directoryPath || ".", readdirOpts); + entries = fs.readdirSync(crawlPath || ".", readdirOpts); } catch (e) { if (!state.options.suppressErrors) throw e; } diff --git a/src/api/walker.ts b/src/api/walker.ts index 9461fbe..590cea1 100644 --- a/src/api/walker.ts +++ b/src/api/walker.ts @@ -68,6 +68,7 @@ export class Walker { this.walkDirectory( this.state, this.root, + this.root, this.state.options.maxDepth, this.walk ); @@ -84,6 +85,8 @@ export class Walker { exclude, maxFiles, signal, + useRealPaths, + pathSeparator, }, } = this.state; @@ -109,7 +112,7 @@ export class Walker { this.state.options.pathSeparator ); if (exclude && exclude(entry.name, path)) continue; - this.walkDirectory(this.state, path, depth - 1, this.walk); + this.walkDirectory(this.state, path, path, depth - 1, this.walk); } else if (entry.isSymbolicLink() && this.resolveSymlink) { let path = joinPath.joinPathWithBasePath(entry.name, directoryPath); this.resolveSymlink(path, this.state, (stat, resolvedPath) => { @@ -117,8 +120,15 @@ export class Walker { resolvedPath = normalizePath(resolvedPath, this.state.options); if (exclude && exclude(entry.name, resolvedPath)) return; - this.walkDirectory(this.state, resolvedPath, depth - 1, this.walk); + this.walkDirectory( + this.state, + resolvedPath, + useRealPaths ? resolvedPath : path + pathSeparator, + depth - 1, + this.walk + ); } else { + resolvedPath = useRealPaths ? resolvedPath : path; const filename = basename(resolvedPath); const directoryPath = normalizePath( dirname(resolvedPath),