Skip to content

Commit

Permalink
fix: recursive symlinks with relative paths not working
Browse files Browse the repository at this point in the history
  • Loading branch information
thecodrr committed Oct 16, 2024
1 parent d8d2937 commit 6e3233a
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 11 deletions.
94 changes: 93 additions & 1 deletion __tests__/symlinks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -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()
Expand Down
8 changes: 4 additions & 4 deletions src/api/functions/resolve-symlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const resolveSymlinksAsync: ResolveSymlinkFunction = function (
) {
const {
queue,
options: { suppressErrors, useRealPaths },
options: { suppressErrors },
} = state;
queue.enqueue();

Expand All @@ -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);
});
});
Expand All @@ -41,7 +41,7 @@ const resolveSymlinks: ResolveSymlinkFunction = function (
) {
const {
queue,
options: { suppressErrors, useRealPaths },
options: { suppressErrors },
} = state;
queue.enqueue();

Expand All @@ -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;
}
Expand Down
11 changes: 7 additions & 4 deletions src/api/functions/walk-directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down
14 changes: 12 additions & 2 deletions src/api/walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class Walker<TOutput extends Output> {
this.walkDirectory(
this.state,
this.root,
this.root,
this.state.options.maxDepth,
this.walk
);
Expand All @@ -84,6 +85,8 @@ export class Walker<TOutput extends Output> {
exclude,
maxFiles,
signal,
useRealPaths,
pathSeparator,
},
} = this.state;

Expand All @@ -109,16 +112,23 @@ export class Walker<TOutput extends Output> {
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) => {
if (stat.isDirectory()) {
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),
Expand Down

0 comments on commit 6e3233a

Please sign in to comment.