Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions apps/desktop/electron/fs-read-dir.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use strict'

const fs = require('node:fs')
const path = require('node:path')
const { resolveDirectoryForIpc } = require('./hardening.cjs')

const FS_READDIR_STAT_CONCURRENCY = 16

// Always-hidden noise (covers non-git projects too; gitignore catches many of
// these, but the project tree should keep the same hygiene without one).
const FS_READDIR_HIDDEN = new Set([
'.git',
'.hg',
'.svn',
'.cache',
'.next',
'.turbo',
'.venv',
'__pycache__',
'build',
'dist',
'node_modules',
'target',
'venv'
])

function direntIsDirectory(dirent) {
return typeof dirent.isDirectory === 'function' && dirent.isDirectory()
}

function direntIsFile(dirent) {
return typeof dirent.isFile === 'function' && dirent.isFile()
}

function direntIsSymbolicLink(dirent) {
return typeof dirent.isSymbolicLink === 'function' && dirent.isSymbolicLink()
}

function shouldStatDirent(dirent) {
if (direntIsDirectory(dirent)) return false

return direntIsSymbolicLink(dirent) || !direntIsFile(dirent)
}

async function entryForDirent(dirent, resolved, fsImpl) {
const fullPath = path.join(resolved, dirent.name)
let isDirectory = direntIsDirectory(dirent)

if (!isDirectory && shouldStatDirent(dirent)) {
try {
isDirectory = (await fsImpl.promises.stat(fullPath)).isDirectory()
} catch {
isDirectory = false
}
}

return { name: dirent.name, path: fullPath, isDirectory }
}

async function mapWithStatConcurrency(items, mapper) {
const results = new Array(items.length)
let nextIndex = 0

async function runWorker() {
while (nextIndex < items.length) {
const index = nextIndex
nextIndex += 1
results[index] = await mapper(items[index])
}
}

const workerCount = Math.min(FS_READDIR_STAT_CONCURRENCY, items.length)
const workers = Array.from({ length: workerCount }, () => runWorker())
await Promise.all(workers)

return results
}

async function readDirForIpc(dirPath, options = {}) {
const fsImpl = options.fs || fs
let resolved

try {
;({ resolvedPath: resolved } = await resolveDirectoryForIpc(dirPath, {
fs: fsImpl,
purpose: 'Directory read'
}))
} catch (error) {
return { entries: [], error: error?.code || 'read-error' }
}

try {
const dirents = await fsImpl.promises.readdir(resolved, { withFileTypes: true })
const visibleDirents = dirents.filter(dirent => !FS_READDIR_HIDDEN.has(dirent.name))
const entries = await mapWithStatConcurrency(visibleDirents, dirent =>
entryForDirent(dirent, resolved, fsImpl)
)

entries.sort((a, b) => Number(b.isDirectory) - Number(a.isDirectory) || a.name.localeCompare(b.name))

return { entries }
} catch (error) {
return { entries: [], error: error?.code || 'read-error' }
}
}

module.exports = {
readDirForIpc
}
Loading
Loading