From 03e60d0d607d859b654d4c6d4569f012f53950c2 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 16 Jun 2023 22:08:32 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20implement=20Dirent=20lis?= =?UTF-8?q?tings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Dirent.ts | 3 +- src/fsa-to-node/FsaNodeDirent.ts | 35 +++++++++++++++++++++ src/fsa-to-node/FsaNodeFs.ts | 24 ++++++++++++-- src/fsa-to-node/__tests__/FsaNodeFs.test.ts | 13 ++++++++ src/node/util.ts | 2 ++ src/volume.ts | 3 +- 6 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/fsa-to-node/FsaNodeDirent.ts diff --git a/src/Dirent.ts b/src/Dirent.ts index d21e70a3d..362e139a1 100644 --- a/src/Dirent.ts +++ b/src/Dirent.ts @@ -1,13 +1,14 @@ import { Link } from './node'; import { constants } from './constants'; import { TEncodingExtended, strToEncoding, TDataOut } from './encoding'; +import type {IDirent} from './node/types/misc'; const { S_IFMT, S_IFDIR, S_IFREG, S_IFBLK, S_IFCHR, S_IFLNK, S_IFIFO, S_IFSOCK } = constants; /** * A directory entry, like `fs.Dirent`. */ -export class Dirent { +export class Dirent implements IDirent { static build(link: Link, encoding: TEncodingExtended | undefined) { const dirent = new Dirent(); const { mode } = link.getNode(); diff --git a/src/fsa-to-node/FsaNodeDirent.ts b/src/fsa-to-node/FsaNodeDirent.ts new file mode 100644 index 000000000..62317f093 --- /dev/null +++ b/src/fsa-to-node/FsaNodeDirent.ts @@ -0,0 +1,35 @@ +import {NodeFileSystemDirectoryHandle, NodeFileSystemFileHandle} from "../node-to-fsa"; +import type {IFileSystemHandle} from "../fsa/types"; +import type {IDirent, TDataOut} from "../node/types/misc"; + +export class FsaNodeDirent implements IDirent { + public constructor(public readonly name: TDataOut, protected readonly handle: IFileSystemHandle) {} + + isDirectory(): boolean { + return this.handle instanceof NodeFileSystemDirectoryHandle; + } + + isFile(): boolean { + return this.handle instanceof NodeFileSystemFileHandle; + } + + isBlockDevice(): boolean { + return false; + } + + isCharacterDevice(): boolean { + return false; + } + + isSymbolicLink(): boolean { + return false; + } + + public isFIFO(): boolean { + return false; + } + + public isSocket(): boolean { + return false; + } +} diff --git a/src/fsa-to-node/FsaNodeFs.ts b/src/fsa-to-node/FsaNodeFs.ts index 74cd713ac..c43bb3b60 100644 --- a/src/fsa-to-node/FsaNodeFs.ts +++ b/src/fsa-to-node/FsaNodeFs.ts @@ -13,6 +13,7 @@ import { flagsToNumber, genRndStr6, isFd, + isWin, modeToNumber, nullCheck, pathToFilename, @@ -25,6 +26,7 @@ import { strToEncoding } from '../encoding'; import { FsaToNodeConstants } from './constants'; import { bufferToEncoding } from '../volume'; import { FsaNodeFsOpenFile } from './FsaNodeFsOpenFile'; +import {FsaNodeDirent} from './FsaNodeDirent'; import type { FsCallbackApi, FsPromisesApi } from '../node/types'; import type * as misc from '../node/types/misc'; import type * as opts from '../node/types/options'; @@ -296,9 +298,25 @@ export class FsaNodeFs implements FsCallbackApi { folder.push(name); this.getDir(folder, false, 'readdir') .then(dir => (async () => { - const list: string[] = []; - for await (const key of dir.keys()) list.push(key); - return list; + if (options.withFileTypes) { + const list: misc.IDirent[] = []; + for await (const [name, handle] of dir.entries()) { + const dirent = new FsaNodeDirent(name, handle); + list.push(dirent); + } + if (!isWin && options.encoding !== 'buffer') + list.sort((a, b) => { + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + return list; + } else { + const list: string[] = []; + for await (const key of dir.keys()) list.push(key); + if (!isWin && options.encoding !== 'buffer') list.sort(); + return list; + } })()) .then(res => callback(null, res), err => callback(err)); }; diff --git a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts index 4c0993717..d082a0d53 100644 --- a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts +++ b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts @@ -1,5 +1,6 @@ import { IFsWithVolume, NestedDirectoryJSON, memfs } from '../..'; import { nodeToFsa } from '../../node-to-fsa'; +import {IDirent} from '../../node/types/misc'; import { FsaNodeFs } from '../FsaNodeFs'; const setup = (json: NestedDirectoryJSON | null = null) => { @@ -272,4 +273,16 @@ describe('.readdir()', () => { expect(res.includes('empty-folder')).toBe(true); expect(res.includes('f.html')).toBe(true); }); + + test('can read directory contents with "withFileTypes" flag set', async () => { + const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null, 'f.html': 'test' }); + const list = await fs.promises.readdir('/', {withFileTypes: true}) as IDirent[]; + expect(list.length).toBe(3); + const names = list.map((item) => item.name); + expect(names).toStrictEqual(['empty-folder', 'f.html', 'folder']); + expect(list.find(item => item.name === 'folder')?.isDirectory()).toBe(true); + expect(list.find(item => item.name === 'empty-folder')?.isDirectory()).toBe(true); + expect(list.find(item => item.name === 'f.html')?.isFile()).toBe(true); + expect(list.find(item => item.name === 'f.html')?.isDirectory()).toBe(false); + }); }); diff --git a/src/node/util.ts b/src/node/util.ts index 00b0eedb9..f37e24d38 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -3,6 +3,8 @@ import * as errors from '../internal/errors'; import type { FsCallbackApi } from './types'; import type * as misc from './types/misc'; +export const isWin = process.platform === 'win32'; + export function promisify( fs: FsCallbackApi, fn: string, diff --git a/src/volume.ts b/src/volume.ts index c177348f8..d8e9f97d7 100644 --- a/src/volume.ts +++ b/src/volume.ts @@ -38,6 +38,7 @@ import { flagsToNumber, validateFd, isFd, + isWin, } from './node/util'; import type { PathLike, symlink } from 'fs'; @@ -59,8 +60,6 @@ const { const { sep, relative, join, dirname } = pathModule.posix ? pathModule.posix : pathModule; -const isWin = process.platform === 'win32'; - // ---------------------------------------- Types // Node-style errors with a `code` property.