diff --git a/src/fsa-to-node/FsaNodeDirent.ts b/src/fsa-to-node/FsaNodeDirent.ts index 85b501798..7e8498ba0 100644 --- a/src/fsa-to-node/FsaNodeDirent.ts +++ b/src/fsa-to-node/FsaNodeDirent.ts @@ -5,23 +5,23 @@ 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 { + public isDirectory(): boolean { return this.handle instanceof NodeFileSystemDirectoryHandle; } - isFile(): boolean { + public isFile(): boolean { return this.handle instanceof NodeFileSystemFileHandle; } - isBlockDevice(): boolean { + public isBlockDevice(): boolean { return false; } - isCharacterDevice(): boolean { + public isCharacterDevice(): boolean { return false; } - isSymbolicLink(): boolean { + public isSymbolicLink(): boolean { return false; } diff --git a/src/fsa-to-node/FsaNodeFs.ts b/src/fsa-to-node/FsaNodeFs.ts index dfbe70c76..19e6e0f4e 100644 --- a/src/fsa-to-node/FsaNodeFs.ts +++ b/src/fsa-to-node/FsaNodeFs.ts @@ -8,6 +8,7 @@ import { getRmdirOptions, optsAndCbGenerator, getAppendFileOptsAndCb, + getStatOptsAndCb, } from '../node/options'; import { createError, @@ -38,6 +39,7 @@ import type * as misc from '../node/types/misc'; import type * as opts from '../node/types/options'; import type * as fsa from '../fsa/types'; import type {FsCommonObjects} from '../node/types/FsCommonObjects'; +import {FsaNodeStats} from './FsaNodeStats'; const notSupported: (...args: any[]) => any = () => { throw new Error('Method not supported by the File System Access API.'); @@ -305,11 +307,16 @@ export class FsaNodeFs implements FsCallbackApi, FsCommonObjects { throw new Error('Not implemented'); } - stat(path: misc.PathLike, callback: misc.TCallback): void; - stat(path: misc.PathLike, options: opts.IStatOptions, callback: misc.TCallback): void; - stat(path: misc.PathLike, a: misc.TCallback | opts.IStatOptions, b?: misc.TCallback): void { - throw new Error('Not implemented'); - } + public readonly stat: FsCallbackApi['stat'] = (path: misc.PathLike, a: misc.TCallback | opts.IStatOptions, b?: misc.TCallback): void => { + const [{ bigint = false, throwIfNoEntry = true }, callback] = getStatOptsAndCb(a, b); + const filename = pathToFilename(path); + const [folder, name] = pathToLocation(filename); + (async () => { + const handle = await this.getFileOrDir(folder, name, 'stat'); + const stats = new FsaNodeStats(bigint, handle); + return stats; + })().then(stats => callback(null, stats), error => callback(error)); + }; fstat(fd: number, callback: misc.TCallback): void; fstat(fd: number, options: opts.IFStatOptions, callback: misc.TCallback): void; @@ -682,7 +689,7 @@ export class FsaNodeFs implements FsCallbackApi, FsCommonObjects { public readonly X_OK = constants.X_OK; public readonly constants = constants; public readonly Dirent = FsaNodeDirent; - public readonly Stats = 0 as any; + public readonly Stats = FsaNodeStats; public readonly StatFs = 0 as any; public readonly Dir = 0 as any; public readonly StatsWatcher = 0 as any; diff --git a/src/fsa-to-node/FsaNodeStats.ts b/src/fsa-to-node/FsaNodeStats.ts new file mode 100644 index 000000000..12414d4eb --- /dev/null +++ b/src/fsa-to-node/FsaNodeStats.ts @@ -0,0 +1,78 @@ +import { NodeFileSystemDirectoryHandle, NodeFileSystemFileHandle } from '../node-to-fsa'; +import type { IFileSystemHandle } from '../fsa/types'; +import type * as misc from '../node/types/misc'; + +const time: number = 0; +const timex: bigint = typeof BigInt === 'function' ? BigInt(time) : time as any as bigint; +const date = new Date(time); + +export class FsaNodeStats implements misc.IStats { + public readonly uid: T; + public readonly gid: T; + public readonly rdev: T; + public readonly blksize: T; + public readonly ino: T; + public readonly size: T; + public readonly blocks: T; + public readonly atime: Date; + public readonly mtime: Date; + public readonly ctime: Date; + public readonly birthtime: Date; + public readonly atimeMs: T; + public readonly mtimeMs: T; + public readonly ctimeMs: T; + public readonly birthtimeMs: T; + public readonly dev: T; + public readonly mode: T; + public readonly nlink: T; + + public constructor(isBigInt: boolean, protected readonly handle: IFileSystemHandle) { + const dummy = (isBigInt ? timex : time) as any as T; + this.uid = dummy; + this.gid = dummy; + this.rdev = dummy; + this.blksize = dummy; + this.ino = dummy; + this.size = dummy; + this.blocks = dummy; + this.atime = date; + this.mtime = date; + this.ctime = date; + this.birthtime = date; + this.atimeMs = dummy; + this.mtimeMs = dummy; + this.ctimeMs = dummy; + this.birthtimeMs = dummy; + this.dev = dummy; + this.mode = dummy; + this.nlink = dummy; + } + + public isDirectory(): boolean { + return this.handle instanceof NodeFileSystemDirectoryHandle; + } + + public isFile(): boolean { + return this.handle instanceof NodeFileSystemFileHandle; + } + + public isBlockDevice(): boolean { + return false; + } + + public isCharacterDevice(): boolean { + return false; + } + + public isSymbolicLink(): boolean { + return false; + } + + public isFIFO(): boolean { + return false; + } + + public isSocket(): boolean { + return false; + } +} diff --git a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts index 0bcef711c..b9ed57ca7 100644 --- a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts +++ b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts @@ -463,3 +463,11 @@ describe('.rename()', () => { }); }); }); + +describe('.stat()', () => { + test('can stat a file', async () => { + const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null, 'f.html': 'test' }); + const stats = await fs.promises.stat('/folder/file'); + expect(stats.isFile()).toBe(true); + }); +}); diff --git a/src/node/options.ts b/src/node/options.ts index 9fcc5a429..e2025631f 100644 --- a/src/node/options.ts +++ b/src/node/options.ts @@ -87,3 +87,13 @@ const appendFileDefaults: opts.IAppendFileOptions = { }; export const getAppendFileOpts = optsGenerator(appendFileDefaults); export const getAppendFileOptsAndCb = optsAndCbGenerator(getAppendFileOpts); + +const statDefaults: opts.IStatOptions = { + bigint: false, +}; +export const getStatOptions: (options?: any) => opts.IStatOptions = (options = {}) => Object.assign({}, statDefaults, options); +export const getStatOptsAndCb: (options: any, callback?: misc.TCallback) => [opts.IStatOptions, misc.TCallback] = ( + options, + callback?, +) => + typeof options === 'function' ? [getStatOptions(), options] : [getStatOptions(options), validateCallback(callback)]; diff --git a/src/volume.ts b/src/volume.ts index 3ec1e5000..06db2d84c 100644 --- a/src/volume.ts +++ b/src/volume.ts @@ -29,6 +29,8 @@ import { optsGenerator, getAppendFileOptsAndCb, getAppendFileOpts, + getStatOptsAndCb, + getStatOptions, } from './node/options'; import { validateCallback, @@ -189,16 +191,6 @@ export interface IFStatOptions { bigint?: boolean; } -const statDefaults: IStatOptions = { - bigint: false, -}; -const getStatOptions: (options?: any) => IStatOptions = (options = {}) => Object.assign({}, statDefaults, options); -const getStatOptsAndCb: (options: any, callback?: TCallback) => [IStatOptions, TCallback] = ( - options, - callback?, -) => - typeof options === 'function' ? [getStatOptions(), options] : [getStatOptions(options), validateCallback(callback)]; - // ---------------------------------------- Utility functions type TResolve = (filename: string, base?: string) => string;