'use strict' const assert = require('node:assert/strict') const fs = require('node:fs') const os = require('node:os') const path = require('node:path') const test = require('node:test') const { pathToFileURL } = require('node:url') const { readDirForIpc } = require('./fs-read-dir.cjs') function mkTmpDir() { return fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-fs-read-dir-')) } function fakeDirent(name, flags = {}) { return { name, isDirectory: () => Boolean(flags.directory), isFile: () => Boolean(flags.file), isSymbolicLink: () => Boolean(flags.symlink) } } test('readDirForIpc hides noisy directories and files from the project tree', async () => { const root = mkTmpDir() try { fs.mkdirSync(path.join(root, 'node_modules')) fs.mkdirSync(path.join(root, 'src')) fs.writeFileSync(path.join(root, 'target'), 'hidden file') fs.writeFileSync(path.join(root, 'README.md'), 'visible file') const result = await readDirForIpc(root) assert.equal(result.error, undefined) assert.deepEqual( result.entries.map(entry => entry.name), ['src', 'README.md'] ) } finally { fs.rmSync(root, { recursive: true, force: true }) } }) test('readDirForIpc filters a hidden basename whether it is a file or directory', async () => { const dirRoot = mkTmpDir() const fileRoot = mkTmpDir() try { fs.mkdirSync(path.join(dirRoot, 'node_modules')) fs.writeFileSync(path.join(dirRoot, 'visible.txt'), 'visible') fs.writeFileSync(path.join(fileRoot, 'node_modules'), 'hidden file') fs.writeFileSync(path.join(fileRoot, 'visible.txt'), 'visible') assert.deepEqual( (await readDirForIpc(dirRoot)).entries.map(entry => entry.name), ['visible.txt'] ) assert.deepEqual( (await readDirForIpc(fileRoot)).entries.map(entry => entry.name), ['visible.txt'] ) } finally { fs.rmSync(dirRoot, { recursive: true, force: true }) fs.rmSync(fileRoot, { recursive: true, force: true }) } }) test('readDirForIpc returns directories before files and sorts by name within groups', async () => { const root = mkTmpDir() try { fs.writeFileSync(path.join(root, 'z.txt'), 'z') fs.mkdirSync(path.join(root, 'src')) fs.writeFileSync(path.join(root, 'a.txt'), 'a') fs.mkdirSync(path.join(root, 'lib')) const result = await readDirForIpc(root) assert.equal(result.error, undefined) assert.deepEqual( result.entries.map(entry => entry.name), ['lib', 'src', 'a.txt', 'z.txt'] ) } finally { fs.rmSync(root, { recursive: true, force: true }) } }) test('readDirForIpc accepts file URLs for directories', async () => { const root = mkTmpDir() try { fs.mkdirSync(path.join(root, 'src')) fs.writeFileSync(path.join(root, 'README.md'), 'visible file') const result = await readDirForIpc(pathToFileURL(root).toString()) assert.equal(result.error, undefined) assert.deepEqual( result.entries.map(entry => entry.name), ['src', 'README.md'] ) } finally { fs.rmSync(root, { recursive: true, force: true }) } }) test('readDirForIpc returns invalid-path for blank or non-string input', async () => { let readdirCalls = 0 const fsImpl = { promises: { readdir: async () => { readdirCalls += 1 return [] } } } assert.deepEqual(await readDirForIpc('', { fs: fsImpl }), { entries: [], error: 'invalid-path' }) assert.deepEqual(await readDirForIpc(' ', { fs: fsImpl }), { entries: [], error: 'invalid-path' }) assert.deepEqual(await readDirForIpc(null, { fs: fsImpl }), { entries: [], error: 'invalid-path' }) assert.equal(readdirCalls, 0) }) test('readDirForIpc rejects Windows device paths before readdir', async () => { let readdirCalls = 0 const fsImpl = { promises: { readdir: async () => { readdirCalls += 1 return [] } } } assert.deepEqual(await readDirForIpc('\\\\?\\C:\\secret', { fs: fsImpl }), { entries: [], error: 'device-path' }) assert.equal(readdirCalls, 0) }) test('readDirForIpc returns filesystem error codes instead of throwing', async () => { const root = mkTmpDir() try { const result = await readDirForIpc(path.join(root, 'missing')) assert.deepEqual(result, { entries: [], error: 'ENOENT' }) } finally { fs.rmSync(root, { recursive: true, force: true }) } }) test('readDirForIpc marks a symlink to a directory as a directory', async t => { const root = mkTmpDir() try { fs.mkdirSync(path.join(root, 'actual-dir')) try { fs.symlinkSync(path.join(root, 'actual-dir'), path.join(root, 'linked-dir'), 'dir') } catch (error) { if (error?.code === 'EPERM' || error?.code === 'EACCES') { t.skip(`symlink creation is not permitted on this platform (${error.code})`) return } throw error } const result = await readDirForIpc(root) const linked = result.entries.find(entry => entry.name === 'linked-dir') assert.equal(result.error, undefined) assert.equal(linked?.isDirectory, true) } finally { fs.rmSync(root, { recursive: true, force: true }) } }) test('readDirForIpc marks a Windows junction to a directory as a directory', async t => { if (process.platform !== 'win32') { t.skip('junctions are a Windows-specific symlink type') return } const root = mkTmpDir() try { fs.mkdirSync(path.join(root, 'actual-dir')) try { fs.symlinkSync(path.join(root, 'actual-dir'), path.join(root, 'junction-dir'), 'junction') } catch (error) { if (error?.code === 'EPERM' || error?.code === 'EACCES') { t.skip(`junction creation is not permitted on this platform (${error.code})`) return } throw error } const result = await readDirForIpc(root) const junction = result.entries.find(entry => entry.name === 'junction-dir') assert.equal(result.error, undefined) assert.equal(junction?.isDirectory, true) } finally { fs.rmSync(root, { recursive: true, force: true }) } }) test('readDirForIpc allows expanding symlink or junction directories outside the project root', async t => { const root = mkTmpDir() const outside = mkTmpDir() try { fs.writeFileSync(path.join(outside, 'outside.txt'), 'ok') const linkPath = path.join(root, 'outside-link') try { fs.symlinkSync(outside, linkPath, process.platform === 'win32' ? 'junction' : 'dir') } catch (error) { if (error?.code === 'EPERM' || error?.code === 'EACCES') { t.skip(`directory symlink creation is not permitted on this platform (${error.code})`) return } throw error } const result = await readDirForIpc(linkPath) assert.equal(result.error, undefined) assert.deepEqual(result.entries, [ { name: 'outside.txt', path: path.join(linkPath, 'outside.txt'), isDirectory: false } ]) } finally { fs.rmSync(root, { recursive: true, force: true }) fs.rmSync(outside, { recursive: true, force: true }) } }) test('readDirForIpc stats symbolic links and unknown entries without dropping the whole listing', async () => { const input = path.join('virtual-root') const resolved = path.resolve(input) const statCalls = [] const fsImpl = { promises: { readdir: async () => [ fakeDirent('unknown-entry'), fakeDirent('linked-dir', { symlink: true }), fakeDirent('broken-link', { symlink: true }), fakeDirent('plain.txt', { file: true }) ], stat: async fullPath => { if (fullPath === resolved) { return { isDirectory: () => true } } statCalls.push(fullPath) if (fullPath.endsWith(`${path.sep}linked-dir`)) { return { isDirectory: () => true } } throw Object.assign(new Error('gone'), { code: 'ENOENT' }) } } } const result = await readDirForIpc(input, { fs: fsImpl }) assert.equal(result.error, undefined) assert.deepEqual( statCalls.sort(), [path.join(resolved, 'broken-link'), path.join(resolved, 'linked-dir'), path.join(resolved, 'unknown-entry')].sort() ) assert.deepEqual(result.entries, [ { name: 'linked-dir', path: path.join(resolved, 'linked-dir'), isDirectory: true }, { name: 'broken-link', path: path.join(resolved, 'broken-link'), isDirectory: false }, { name: 'plain.txt', path: path.join(resolved, 'plain.txt'), isDirectory: false }, { name: 'unknown-entry', path: path.join(resolved, 'unknown-entry'), isDirectory: false } ]) }) test('readDirForIpc bounds concurrent stats while preserving complete sorted output', async () => { const input = path.join('virtual-root') const resolved = path.resolve(input) const names = Array.from({ length: 105 }, (_, index) => `entry-${String(104 - index).padStart(3, '0')}`) const failedName = 'entry-100' const directoryNames = new Set(names.filter((_, index) => index % 10 === 4)) const successfulDirectoryNames = new Set([...directoryNames].filter(name => name !== failedName)) const statCalls = [] let active = 0 let peak = 0 let releaseStats let markFirstStatStarted const statsReleased = new Promise(resolve => { releaseStats = resolve }) const firstStatStarted = new Promise(resolve => { markFirstStatStarted = resolve }) const fsImpl = { promises: { readdir: async () => [ fakeDirent('node_modules', { symlink: true }), ...names.map((name, index) => fakeDirent(name, { symlink: index % 2 === 0 })) ], stat: async fullPath => { if (fullPath === resolved) { return { isDirectory: () => true } } statCalls.push(fullPath) active += 1 peak = Math.max(peak, active) markFirstStatStarted() await statsReleased active -= 1 const name = path.basename(fullPath) if (name === failedName) { throw Object.assign(new Error('gone'), { code: 'ENOENT' }) } return { isDirectory: () => successfulDirectoryNames.has(name) } } } } const resultPromise = readDirForIpc(input, { fs: fsImpl }) await firstStatStarted await new Promise(resolve => setImmediate(resolve)) releaseStats() const result = await resultPromise const expectedNames = [ ...names.filter(name => successfulDirectoryNames.has(name)).sort(), ...names.filter(name => !successfulDirectoryNames.has(name)).sort() ] assert.equal(result.error, undefined) assert.equal(result.entries.length, names.length) assert.equal(statCalls.length, names.length) assert.equal(statCalls.some(fullPath => fullPath.endsWith(`${path.sep}node_modules`)), false) assert.ok(peak > 1, `expected concurrent stats, observed peak ${peak}`) assert.ok(peak <= 16, `expected at most 16 concurrent stats, observed peak ${peak}`) assert.deepEqual( result.entries.map(entry => entry.name), expectedNames ) assert.equal(result.entries.find(entry => entry.name === failedName)?.isDirectory, false) assert.equal( result.entries.filter(entry => entry.isDirectory).length, successfulDirectoryNames.size ) })