Fix issues with symlink directories

This commit is contained in:
Jacob Truman 2026-03-23 16:07:42 -06:00
parent 8b89b27654
commit 08c568a3fc
5 changed files with 18 additions and 8 deletions

View file

@ -67,7 +67,8 @@ class FolderWatcher extends EventEmitter {
renameTimeout: 2000,
recursive: true,
ignoreInitial: true,
persistent: true
persistent: true,
followSymlinks: true
})
watcher
.on('add', (path) => {

View file

@ -71,13 +71,13 @@ const Utils = {
poll: (targetPath, timeout = constants_1.POLLING_TIMEOUT) => {
return ripstat_1.default(targetPath, timeout).catch(Utils.lang.noop);
},
readdir: async (rootPath, ignore, depth = Infinity, signal, readdirMap) => {
readdir: async (rootPath, ignore, depth = Infinity, signal, readdirMap, followSymlinks = false) => {
if (readdirMap && depth === 1 && rootPath in readdirMap) { // Reusing cached data
const result = readdirMap[rootPath];
return [result.directories, result.files];
}
else { // Retrieving fresh data
const result = await tiny_readdir_1.default(rootPath, { depth, ignore, signal });
const result = await tiny_readdir_1.default(rootPath, { depth, ignore, signal, followSymlinks });
return [result.directories, result.files];
}
}

View file

@ -91,7 +91,7 @@ class WatcherHandler {
var _a, _b;
if (isInitial)
return events;
const depth = this.options.recursive ? (_a = this.options.depth) !== null && _a !== void 0 ? _a : constants_1.DEPTH : Math.min(1, (_b = this.options.depth) !== null && _b !== void 0 ? _b : constants_1.DEPTH), [directories, files] = await utils_1.default.fs.readdir(targetPath, this.options.ignore, depth, this.watcher._closeSignal), targetSubPaths = [...directories, ...files];
const depth = this.options.recursive ? (_a = this.options.depth) !== null && _a !== void 0 ? _a : constants_1.DEPTH : Math.min(1, (_b = this.options.depth) !== null && _b !== void 0 ? _b : constants_1.DEPTH), [directories, files] = await utils_1.default.fs.readdir(targetPath, this.options.ignore, depth, this.watcher._closeSignal, undefined, this.options.followSymlinks), targetSubPaths = [...directories, ...files];
await Promise.all(targetSubPaths.map(targetSubPath => {
if (this.watcher.isIgnored(targetSubPath, this.options.ignore))
return;
@ -229,7 +229,7 @@ class WatcherHandler {
await this.onWatcherEvent("change" /* CHANGE */, this.filePath, isInitial);
}
else { // Multiple initial paths
const depth = this.options.recursive && (constants_1.HAS_NATIVE_RECURSION && this.options.native !== false) ? (_a = this.options.depth) !== null && _a !== void 0 ? _a : constants_1.DEPTH : Math.min(1, (_b = this.options.depth) !== null && _b !== void 0 ? _b : constants_1.DEPTH), [directories, files] = await utils_1.default.fs.readdir(this.folderPath, this.options.ignore, depth, this.watcher._closeSignal, this.options.readdirMap), targetPaths = [this.folderPath, ...directories, ...files];
const depth = this.options.recursive && (constants_1.HAS_NATIVE_RECURSION && this.options.native !== false) ? (_a = this.options.depth) !== null && _a !== void 0 ? _a : constants_1.DEPTH : Math.min(1, (_b = this.options.depth) !== null && _b !== void 0 ? _b : constants_1.DEPTH), [directories, files] = await utils_1.default.fs.readdir(this.folderPath, this.options.ignore, depth, this.watcher._closeSignal, this.options.readdirMap, this.options.followSymlinks), targetPaths = [this.folderPath, ...directories, ...files];
await Promise.all(targetPaths.map(targetPath => {
if (this.watcher._poller.stats.has(targetPath))
return; // Already polled

View file

@ -199,7 +199,7 @@ module.exports.recurseFiles = async (path, relPathToReplace = null) => {
ignoreFolders: true,
extensions: true,
deep: true,
realPath: true,
realPath: false,
normalizePath: false
}
let list = await rra.list(path, options)
@ -517,7 +517,15 @@ module.exports.getDirectoriesInPath = async (dirPath, level) => {
Logger.debug(`Failed to lstat "${fullPath}"`, error)
return null
})
if (!lstat?.isDirectory()) return null
if (!lstat) return null
let isDir = lstat.isDirectory()
if (!isDir && lstat.isSymbolicLink()) {
// Follow symlink to check if target is a directory
const targetStat = await fs.stat(fullPath).catch(() => null)
isDir = targetStat?.isDirectory() ?? false
}
if (!isDir) return null
return {
path: this.filePathToPOSIX(fullPath),

View file

@ -66,7 +66,8 @@ describe('fileUtils', () => {
// Stub fs.readdir
readdirStub = sinon.stub(fs, 'readdir')
readdirStub.callsFake((path, callback) => {
const contents = mockDirContents.get(path)
const normalizedPath = path.replace(/\/+$/, '')
const contents = mockDirContents.get(normalizedPath)
if (contents) {
callback(null, contents)
} else {