@zenfs/core 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/devices.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FileReadResult } from 'fs/promises';
1
+ import type { FileReadResult } from 'node:fs/promises';
2
2
  import { InMemoryStore } from './backends/memory.js';
3
3
  import { StoreFS } from './backends/store/fs.js';
4
4
  import { File } from './file.js';
@@ -1,4 +1,4 @@
1
- import type { Dir as _Dir, Dirent as _Dirent } from 'fs';
1
+ import type { Dir as _Dir, Dirent as _Dirent } from 'node:fs';
2
2
  import type { Stats } from '../stats.js';
3
3
  import type { Callback } from '../utils.js';
4
4
  export declare class Dirent implements _Dirent {
@@ -1,7 +1,6 @@
1
1
  import { Buffer } from 'buffer';
2
2
  import type * as fs from 'node:fs';
3
3
  import type * as promises from 'node:fs/promises';
4
- import type { CreateReadStreamOptions, CreateWriteStreamOptions, FileChangeInfo, FileReadResult, FlagAndOpenMode } from 'node:fs/promises';
5
4
  import type { Stream } from 'node:stream';
6
5
  import type { ReadableStream as TReadableStream } from 'node:stream/web';
7
6
  import type { Interface as ReadlineInterface } from 'readline';
@@ -60,7 +59,7 @@ export declare class FileHandle implements promises.FileHandle {
60
59
  * - `mode` defaults to `0o666`.
61
60
  * - `flag` defaults to `'a'`.
62
61
  */
63
- appendFile(data: string | Uint8Array, _options?: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<void>;
62
+ appendFile(data: string | Uint8Array, _options?: (fs.ObjectEncodingOptions & promises.FlagAndOpenMode) | BufferEncoding): Promise<void>;
64
63
  /**
65
64
  * Asynchronously reads data from the file.
66
65
  * The `FileHandle` must have been opened for reading.
@@ -69,7 +68,7 @@ export declare class FileHandle implements promises.FileHandle {
69
68
  * @param length The number of bytes to read.
70
69
  * @param position The offset from the beginning of the file from which data should be read. If `null`, data will be read from the current position.
71
70
  */
72
- read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number | null): Promise<FileReadResult<TBuffer>>;
71
+ read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number | null): Promise<promises.FileReadResult<TBuffer>>;
73
72
  /**
74
73
  * Asynchronously reads the entire contents of a file. The underlying file will _not_ be closed automatically.
75
74
  * The `FileHandle` must have been opened for reading.
@@ -79,7 +78,7 @@ export declare class FileHandle implements promises.FileHandle {
79
78
  readFile(_options?: {
80
79
  flag?: fs.OpenMode;
81
80
  }): Promise<Buffer>;
82
- readFile(_options: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<string>;
81
+ readFile(_options: (fs.ObjectEncodingOptions & promises.FlagAndOpenMode) | BufferEncoding): Promise<string>;
83
82
  /**
84
83
  * Returns a `ReadableStream` that may be used to read the files data.
85
84
  *
@@ -153,12 +152,12 @@ export declare class FileHandle implements promises.FileHandle {
153
152
  * Creates a stream for reading from the file.
154
153
  * @param options Options for the readable stream
155
154
  */
156
- createReadStream(options?: CreateReadStreamOptions): ReadStream;
155
+ createReadStream(options?: promises.CreateReadStreamOptions): ReadStream;
157
156
  /**
158
157
  * Creates a stream for writing to the file.
159
158
  * @param options Options for the writeable stream.
160
159
  */
161
- createWriteStream(options?: CreateWriteStreamOptions): WriteStream;
160
+ createWriteStream(options?: promises.CreateWriteStreamOptions): WriteStream;
162
161
  }
163
162
  export declare function rename(oldPath: fs.PathLike, newPath: fs.PathLike): Promise<void>;
164
163
  /**
@@ -265,6 +264,11 @@ export declare function readdir(path: fs.PathLike, options: fs.ObjectEncodingOpt
265
264
  withFileTypes: true;
266
265
  recursive?: boolean;
267
266
  }): Promise<Dirent[]>;
267
+ export declare function readdir(path: fs.PathLike, options?: {
268
+ withFileTypes?: boolean;
269
+ recursive?: boolean;
270
+ encoding?: BufferEncoding | 'buffer' | null;
271
+ } | BufferEncoding | 'buffer' | null): Promise<string[] | Dirent[] | Buffer[]>;
268
272
  export declare function link(targetPath: fs.PathLike, linkPath: fs.PathLike): Promise<void>;
269
273
  /**
270
274
  * `symlink`.
@@ -295,9 +299,9 @@ export declare function lutimes(path: fs.PathLike, atime: fs.TimeLike, mtime: fs
295
299
  */
296
300
  export declare function realpath(path: fs.PathLike, options: fs.BufferEncodingOption): Promise<Buffer>;
297
301
  export declare function realpath(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding): Promise<string>;
298
- export declare function watch(filename: fs.PathLike, options?: fs.WatchOptions | BufferEncoding): AsyncIterable<FileChangeInfo<string>>;
299
- export declare function watch(filename: fs.PathLike, options: fs.WatchOptions | fs.BufferEncodingOption): AsyncIterable<FileChangeInfo<Buffer>>;
300
- export declare function watch(filename: fs.PathLike, options?: fs.WatchOptions | string): AsyncIterable<FileChangeInfo<string>> | AsyncIterable<FileChangeInfo<Buffer>>;
302
+ export declare function watch(filename: fs.PathLike, options?: fs.WatchOptions | BufferEncoding): AsyncIterable<promises.FileChangeInfo<string>>;
303
+ export declare function watch(filename: fs.PathLike, options: fs.WatchOptions | fs.BufferEncodingOption): AsyncIterable<promises.FileChangeInfo<Buffer>>;
304
+ export declare function watch(filename: fs.PathLike, options?: fs.WatchOptions | string): AsyncIterable<promises.FileChangeInfo<string>> | AsyncIterable<promises.FileChangeInfo<Buffer>>;
301
305
  export declare function access(path: fs.PathLike, mode?: number): Promise<void>;
302
306
  /**
303
307
  * Asynchronous `rm`. Removes files or directories (recursively).
@@ -51,7 +51,7 @@ import { Errno, ErrnoError } from '../error.js';
51
51
  import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
52
52
  import '../polyfills.js';
53
53
  import { BigIntStats } from '../stats.js';
54
- import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
54
+ import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
55
55
  import * as constants from './constants.js';
56
56
  import { Dir, Dirent } from './dir.js';
57
57
  import { dirname, join, parse } from './path.js';
@@ -627,19 +627,16 @@ export async function mkdir(path, options) {
627
627
  }
628
628
  mkdir;
629
629
  export async function readdir(path, options) {
630
+ options = typeof options === 'object' ? options : { encoding: options };
630
631
  path = normalizePath(path);
631
632
  if (!(await stat(path)).hasAccess(constants.R_OK)) {
632
633
  throw ErrnoError.With('EACCES', path, 'readdir');
633
634
  }
634
635
  path = (await exists(path)) ? await realpath(path) : path;
635
636
  const { fs, path: resolved } = resolveMount(path);
636
- let entries;
637
- try {
638
- entries = await fs.readdir(resolved);
639
- }
640
- catch (e) {
637
+ const entries = await fs.readdir(resolved).catch((e) => {
641
638
  throw fixError(e, { [resolved]: path });
642
- }
639
+ });
643
640
  for (const point of mounts.keys()) {
644
641
  if (point.startsWith(path)) {
645
642
  const entry = point.slice(path.length);
@@ -652,12 +649,37 @@ export async function readdir(path, options) {
652
649
  }
653
650
  const values = [];
654
651
  for (const entry of entries) {
655
- values.push(typeof options == 'object' && options?.withFileTypes ? new Dirent(entry, await stat(join(path, entry))) : entry);
652
+ const fullPath = join(path, entry);
653
+ const stats = options?.recursive || options?.withFileTypes ? await stat(fullPath) : null;
654
+ if (options?.withFileTypes) {
655
+ values.push(new Dirent(entry, stats));
656
+ }
657
+ else if (options?.encoding === 'buffer') {
658
+ values.push(Buffer.from(entry));
659
+ }
660
+ else {
661
+ values.push(entry);
662
+ }
663
+ if (!options?.recursive || !stats?.isDirectory()) {
664
+ continue;
665
+ }
666
+ for (const subEntry of await readdir(fullPath, options)) {
667
+ if (subEntry instanceof Dirent) {
668
+ subEntry.path = join(entry, subEntry.path);
669
+ values.push(subEntry);
670
+ }
671
+ else if (Buffer.isBuffer(subEntry)) {
672
+ // Convert Buffer to string, prefix with the full path
673
+ values.push(Buffer.from(join(entry, decodeUTF8(subEntry))));
674
+ }
675
+ else {
676
+ values.push(join(entry, subEntry));
677
+ }
678
+ }
656
679
  }
657
680
  return values;
658
681
  }
659
682
  readdir;
660
- // SYMLINK METHODS
661
683
  export async function link(targetPath, linkPath) {
662
684
  targetPath = normalizePath(targetPath);
663
685
  if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK)) {
@@ -849,20 +871,28 @@ realpath;
849
871
  export function watch(filename, options = {}) {
850
872
  return {
851
873
  [Symbol.asyncIterator]() {
852
- const watcher = new FSWatcher(filename.toString(), typeof options != 'string' ? options : { encoding: options });
853
- function withDone(done) {
854
- return function () {
855
- const event = Promise.withResolvers();
856
- watcher.on('change', (eventType, filename) => {
857
- event.resolve({ value: { eventType, filename }, done });
858
- });
859
- return event.promise;
860
- };
874
+ const watcher = new FSWatcher(filename.toString(), typeof options !== 'string' ? options : { encoding: options });
875
+ // A queue to hold change events, since we need to resolve them in the async iterator
876
+ const eventQueue = [];
877
+ watcher.on('change', (eventType, filename) => {
878
+ eventQueue.shift()?.({ value: { eventType, filename }, done: false });
879
+ });
880
+ function cleanup() {
881
+ watcher.close();
882
+ for (const resolve of eventQueue) {
883
+ resolve({ value: null, done: true });
884
+ }
885
+ eventQueue.length = 0; // Clear the queue
886
+ return Promise.resolve({ value: null, done: true });
861
887
  }
862
888
  return {
863
- next: withDone(false),
864
- return: withDone(true),
865
- throw: withDone(true),
889
+ async next() {
890
+ const { promise, resolve } = Promise.withResolvers();
891
+ eventQueue.push(resolve);
892
+ return promise;
893
+ },
894
+ return: cleanup,
895
+ throw: cleanup,
866
896
  };
867
897
  },
868
898
  };
@@ -1,4 +1,4 @@
1
- import type * as Node from 'fs';
1
+ import type * as Node from 'node:fs';
2
2
  import { Readable, Writable } from 'readable-stream';
3
3
  import type { Callback } from '../utils.js';
4
4
  export declare class ReadStream extends Readable implements Node.ReadStream {
@@ -128,6 +128,11 @@ export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncod
128
128
  withFileTypes?: false;
129
129
  recursive?: boolean;
130
130
  }) | BufferEncoding | null): string[] | Buffer[];
131
+ export declare function readdirSync(path: fs.PathLike, options?: {
132
+ withFileTypes?: boolean;
133
+ recursive?: boolean;
134
+ encoding?: BufferEncoding | 'buffer' | null;
135
+ } | BufferEncoding | 'buffer' | null): string[] | Dirent[] | Buffer[];
131
136
  export declare function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void;
132
137
  /**
133
138
  * Synchronous `symlink`.
@@ -49,7 +49,7 @@ import { Buffer } from 'buffer';
49
49
  import { Errno, ErrnoError } from '../error.js';
50
50
  import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
51
51
  import { BigIntStats } from '../stats.js';
52
- import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
52
+ import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
53
53
  import * as constants from './constants.js';
54
54
  import { Dir, Dirent } from './dir.js';
55
55
  import { dirname, join, parse } from './path.js';
@@ -431,6 +431,7 @@ export function mkdirSync(path, options) {
431
431
  }
432
432
  mkdirSync;
433
433
  export function readdirSync(path, options) {
434
+ options = typeof options === 'object' ? options : { encoding: options };
434
435
  path = normalizePath(path);
435
436
  const { fs, path: resolved } = resolveMount(existsSync(path) ? realpathSync(path) : path);
436
437
  let entries;
@@ -454,15 +455,36 @@ export function readdirSync(path, options) {
454
455
  }
455
456
  entries.push(entry);
456
457
  }
457
- return entries.map((entry) => {
458
- if (typeof options == 'object' && options?.withFileTypes) {
459
- return new Dirent(entry, statSync(join(path.toString(), entry)));
458
+ // Iterate over entries and handle recursive case if needed
459
+ const values = [];
460
+ for (const entry of entries) {
461
+ const fullPath = join(path, entry);
462
+ const entryStat = statSync(fullPath);
463
+ if (options?.withFileTypes) {
464
+ values.push(new Dirent(entry, entryStat));
460
465
  }
461
- if (options == 'buffer' || (typeof options == 'object' && options?.encoding == 'buffer')) {
462
- return Buffer.from(entry);
466
+ else if (options?.encoding === 'buffer') {
467
+ values.push(Buffer.from(entry));
463
468
  }
464
- return entry;
465
- });
469
+ else {
470
+ values.push(entry);
471
+ }
472
+ if (!entryStat.isDirectory() || !options?.recursive)
473
+ continue;
474
+ for (const subEntry of readdirSync(fullPath, options)) {
475
+ if (subEntry instanceof Dirent) {
476
+ subEntry.path = join(entry, subEntry.path);
477
+ values.push(subEntry);
478
+ }
479
+ else if (Buffer.isBuffer(subEntry)) {
480
+ values.push(Buffer.from(join(entry, decodeUTF8(subEntry))));
481
+ }
482
+ else {
483
+ values.push(join(entry, subEntry));
484
+ }
485
+ }
486
+ }
487
+ return values;
466
488
  }
467
489
  readdirSync;
468
490
  // SYMLINK METHODS
package/dist/stats.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type * as Node from 'fs';
1
+ import type * as Node from 'node:fs';
2
2
  import { type Credentials } from './credentials.js';
3
3
  import { S_IFDIR, S_IFLNK, S_IFREG } from './emulation/constants.js';
4
4
  /**
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "A filesystem, anywhere",
5
+ "funding": {
6
+ "type": "individual",
7
+ "url": "https://github.com/sponsors/james-pre"
8
+ },
5
9
  "main": "dist/index.js",
6
10
  "types": "dist/index.d.ts",
7
11
  "keywords": [
package/src/devices.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FileReadResult } from 'fs/promises';
1
+ import type { FileReadResult } from 'node:fs/promises';
2
2
  import { InMemoryStore } from './backends/memory.js';
3
3
  import { StoreFS } from './backends/store/fs.js';
4
4
  import { S_IFBLK, S_IFCHR } from './emulation/constants.js';
@@ -1,4 +1,4 @@
1
- import type { Dir as _Dir, Dirent as _Dirent } from 'fs';
1
+ import type { Dir as _Dir, Dirent as _Dirent } from 'node:fs';
2
2
  import { Errno, ErrnoError } from '../error.js';
3
3
  import type { Stats } from '../stats.js';
4
4
  import type { Callback } from '../utils.js';
@@ -2,18 +2,16 @@
2
2
  import { Buffer } from 'buffer';
3
3
  import type * as fs from 'node:fs';
4
4
  import type * as promises from 'node:fs/promises';
5
- import type { CreateReadStreamOptions, CreateWriteStreamOptions, FileChangeInfo, FileReadResult, FlagAndOpenMode } from 'node:fs/promises';
6
5
  import type { Stream } from 'node:stream';
7
- import type { ReadableStream as TReadableStream } from 'node:stream/web';
6
+ import type { ReadableStreamController, ReadableStream as TReadableStream } from 'node:stream/web';
8
7
  import type { Interface as ReadlineInterface } from 'readline';
9
- import type { ReadableStreamController } from 'stream/web';
10
8
  import { Errno, ErrnoError } from '../error.js';
11
9
  import type { File } from '../file.js';
12
10
  import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
13
11
  import type { FileContents } from '../filesystem.js';
14
12
  import '../polyfills.js';
15
13
  import { BigIntStats, type Stats } from '../stats.js';
16
- import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
14
+ import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
17
15
  import * as constants from './constants.js';
18
16
  import { Dir, Dirent } from './dir.js';
19
17
  import { dirname, join, parse } from './path.js';
@@ -107,7 +105,7 @@ export class FileHandle implements promises.FileHandle {
107
105
  * - `mode` defaults to `0o666`.
108
106
  * - `flag` defaults to `'a'`.
109
107
  */
110
- public async appendFile(data: string | Uint8Array, _options: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding = {}): Promise<void> {
108
+ public async appendFile(data: string | Uint8Array, _options: (fs.ObjectEncodingOptions & promises.FlagAndOpenMode) | BufferEncoding = {}): Promise<void> {
111
109
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
112
110
  const flag = parseFlag(options.flag);
113
111
  if (!isAppendable(flag)) {
@@ -129,7 +127,7 @@ export class FileHandle implements promises.FileHandle {
129
127
  * @param length The number of bytes to read.
130
128
  * @param position The offset from the beginning of the file from which data should be read. If `null`, data will be read from the current position.
131
129
  */
132
- public read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number | null): Promise<FileReadResult<TBuffer>> {
130
+ public read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number | null): Promise<promises.FileReadResult<TBuffer>> {
133
131
  if (isNaN(+position!)) {
134
132
  position = this.file.position;
135
133
  }
@@ -143,8 +141,8 @@ export class FileHandle implements promises.FileHandle {
143
141
  * If a flag is not provided, it defaults to `'r'`.
144
142
  */
145
143
  public async readFile(_options?: { flag?: fs.OpenMode }): Promise<Buffer>;
146
- public async readFile(_options: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<string>;
147
- public async readFile(_options?: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<string | Buffer> {
144
+ public async readFile(_options: (fs.ObjectEncodingOptions & promises.FlagAndOpenMode) | BufferEncoding): Promise<string>;
145
+ public async readFile(_options?: (fs.ObjectEncodingOptions & promises.FlagAndOpenMode) | BufferEncoding): Promise<string | Buffer> {
148
146
  const options = normalizeOptions(_options, null, 'r', 0o444);
149
147
  const flag = parseFlag(options.flag);
150
148
  if (!isReadable(flag)) {
@@ -334,7 +332,7 @@ export class FileHandle implements promises.FileHandle {
334
332
  * Creates a stream for reading from the file.
335
333
  * @param options Options for the readable stream
336
334
  */
337
- public createReadStream(options?: CreateReadStreamOptions): ReadStream {
335
+ public createReadStream(options?: promises.CreateReadStreamOptions): ReadStream {
338
336
  const stream = new ReadStream({
339
337
  highWaterMark: options?.highWaterMark || 64 * 1024,
340
338
  encoding: options!.encoding!,
@@ -359,7 +357,7 @@ export class FileHandle implements promises.FileHandle {
359
357
  * Creates a stream for writing to the file.
360
358
  * @param options Options for the writeable stream.
361
359
  */
362
- public createWriteStream(options?: CreateWriteStreamOptions): WriteStream {
360
+ public createWriteStream(options?: promises.CreateWriteStreamOptions): WriteStream {
363
361
  const streamOptions = {
364
362
  highWaterMark: options?.highWaterMark,
365
363
  encoding: options?.encoding,
@@ -692,22 +690,28 @@ export async function readdir(
692
690
  options?: (fs.ObjectEncodingOptions & { withFileTypes?: false; recursive?: boolean }) | BufferEncoding | null
693
691
  ): Promise<string[] | Buffer[]>;
694
692
  export async function readdir(path: fs.PathLike, options: fs.ObjectEncodingOptions & { withFileTypes: true; recursive?: boolean }): Promise<Dirent[]>;
693
+ export async function readdir(
694
+ path: fs.PathLike,
695
+ options?: { withFileTypes?: boolean; recursive?: boolean; encoding?: BufferEncoding | 'buffer' | null } | BufferEncoding | 'buffer' | null
696
+ ): Promise<string[] | Dirent[] | Buffer[]>;
695
697
  export async function readdir(
696
698
  path: fs.PathLike,
697
699
  options?: { withFileTypes?: boolean; recursive?: boolean; encoding?: BufferEncoding | 'buffer' | null } | BufferEncoding | 'buffer' | null
698
700
  ): Promise<string[] | Dirent[] | Buffer[]> {
701
+ options = typeof options === 'object' ? options : { encoding: options };
699
702
  path = normalizePath(path);
703
+
700
704
  if (!(await stat(path)).hasAccess(constants.R_OK)) {
701
705
  throw ErrnoError.With('EACCES', path, 'readdir');
702
706
  }
707
+
703
708
  path = (await exists(path)) ? await realpath(path) : path;
704
709
  const { fs, path: resolved } = resolveMount(path);
705
- let entries: string[];
706
- try {
707
- entries = await fs.readdir(resolved);
708
- } catch (e) {
710
+
711
+ const entries = await fs.readdir(resolved).catch((e: Error) => {
709
712
  throw fixError(e as Error, { [resolved]: path });
710
- }
713
+ });
714
+
711
715
  for (const point of mounts.keys()) {
712
716
  if (point.startsWith(path)) {
713
717
  const entry = point.slice(path.length);
@@ -718,16 +722,41 @@ export async function readdir(
718
722
  entries.push(entry);
719
723
  }
720
724
  }
721
- const values: (string | Dirent)[] = [];
725
+
726
+ const values: (string | Dirent | Buffer)[] = [];
722
727
  for (const entry of entries) {
723
- values.push(typeof options == 'object' && options?.withFileTypes ? new Dirent(entry, await stat(join(path, entry))) : entry);
728
+ const fullPath = join(path, entry);
729
+
730
+ const stats = options?.recursive || options?.withFileTypes ? await stat(fullPath) : null;
731
+ if (options?.withFileTypes) {
732
+ values.push(new Dirent(entry, stats!));
733
+ } else if (options?.encoding === 'buffer') {
734
+ values.push(Buffer.from(entry));
735
+ } else {
736
+ values.push(entry);
737
+ }
738
+
739
+ if (!options?.recursive || !stats?.isDirectory()) {
740
+ continue;
741
+ }
742
+
743
+ for (const subEntry of await readdir(fullPath, options)) {
744
+ if (subEntry instanceof Dirent) {
745
+ subEntry.path = join(entry, subEntry.path);
746
+ values.push(subEntry);
747
+ } else if (Buffer.isBuffer(subEntry)) {
748
+ // Convert Buffer to string, prefix with the full path
749
+ values.push(Buffer.from(join(entry, decodeUTF8(subEntry))));
750
+ } else {
751
+ values.push(join(entry, subEntry));
752
+ }
753
+ }
724
754
  }
755
+
725
756
  return values as string[] | Dirent[];
726
757
  }
727
758
  readdir satisfies typeof promises.readdir;
728
759
 
729
- // SYMLINK METHODS
730
-
731
760
  export async function link(targetPath: fs.PathLike, linkPath: fs.PathLike): Promise<void> {
732
761
  targetPath = normalizePath(targetPath);
733
762
  if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK)) {
@@ -856,28 +885,38 @@ export async function realpath(path: fs.PathLike, options?: fs.EncodingOption |
856
885
  }
857
886
  realpath satisfies typeof promises.realpath;
858
887
 
859
- export function watch(filename: fs.PathLike, options?: fs.WatchOptions | BufferEncoding): AsyncIterable<FileChangeInfo<string>>;
860
- export function watch(filename: fs.PathLike, options: fs.WatchOptions | fs.BufferEncodingOption): AsyncIterable<FileChangeInfo<Buffer>>;
861
- export function watch(filename: fs.PathLike, options?: fs.WatchOptions | string): AsyncIterable<FileChangeInfo<string>> | AsyncIterable<FileChangeInfo<Buffer>>;
862
- export function watch<T extends string | Buffer>(filename: fs.PathLike, options: fs.WatchOptions | string = {}): AsyncIterable<FileChangeInfo<T>> {
888
+ export function watch(filename: fs.PathLike, options?: fs.WatchOptions | BufferEncoding): AsyncIterable<promises.FileChangeInfo<string>>;
889
+ export function watch(filename: fs.PathLike, options: fs.WatchOptions | fs.BufferEncodingOption): AsyncIterable<promises.FileChangeInfo<Buffer>>;
890
+ export function watch(filename: fs.PathLike, options?: fs.WatchOptions | string): AsyncIterable<promises.FileChangeInfo<string>> | AsyncIterable<promises.FileChangeInfo<Buffer>>;
891
+ export function watch<T extends string | Buffer>(filename: fs.PathLike, options: fs.WatchOptions | string = {}): AsyncIterable<promises.FileChangeInfo<T>> {
863
892
  return {
864
- [Symbol.asyncIterator](): AsyncIterator<FileChangeInfo<T>> {
865
- const watcher = new FSWatcher<T>(filename.toString(), typeof options != 'string' ? options : { encoding: options as BufferEncoding | 'buffer' });
866
-
867
- function withDone(done: boolean) {
868
- return function () {
869
- const event = Promise.withResolvers<IteratorResult<FileChangeInfo<T>>>();
870
- watcher.on('change', (eventType, filename) => {
871
- event.resolve({ value: { eventType, filename }, done });
872
- });
873
- return event.promise;
874
- };
893
+ [Symbol.asyncIterator](): AsyncIterator<promises.FileChangeInfo<T>> {
894
+ const watcher = new FSWatcher<T>(filename.toString(), typeof options !== 'string' ? options : { encoding: options as BufferEncoding | 'buffer' });
895
+
896
+ // A queue to hold change events, since we need to resolve them in the async iterator
897
+ const eventQueue: ((value: IteratorResult<promises.FileChangeInfo<T>>) => void)[] = [];
898
+
899
+ watcher.on('change', (eventType: promises.FileChangeInfo<T>['eventType'], filename: T) => {
900
+ eventQueue.shift()?.({ value: { eventType, filename }, done: false });
901
+ });
902
+
903
+ function cleanup() {
904
+ watcher.close();
905
+ for (const resolve of eventQueue) {
906
+ resolve({ value: null, done: true });
907
+ }
908
+ eventQueue.length = 0; // Clear the queue
909
+ return Promise.resolve({ value: null, done: true as const });
875
910
  }
876
911
 
877
912
  return {
878
- next: withDone(false),
879
- return: withDone(true),
880
- throw: withDone(true),
913
+ async next() {
914
+ const { promise, resolve } = Promise.withResolvers<IteratorResult<promises.FileChangeInfo<T>>>();
915
+ eventQueue.push(resolve);
916
+ return promise;
917
+ },
918
+ return: cleanup,
919
+ throw: cleanup,
881
920
  };
882
921
  },
883
922
  };
@@ -1,4 +1,4 @@
1
- import type * as Node from 'fs';
1
+ import type * as Node from 'node:fs';
2
2
  import { Readable, Writable } from 'readable-stream';
3
3
  import type { Callback } from '../utils.js';
4
4
  import { ErrnoError, Errno } from '../error.js';
@@ -5,7 +5,7 @@ import type { File } from '../file.js';
5
5
  import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
6
6
  import type { FileContents } from '../filesystem.js';
7
7
  import { BigIntStats, type Stats } from '../stats.js';
8
- import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
8
+ import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
9
9
  import * as constants from './constants.js';
10
10
  import { Dir, Dirent } from './dir.js';
11
11
  import { dirname, join, parse } from './path.js';
@@ -437,10 +437,15 @@ export function readdirSync(path: fs.PathLike, options?: { recursive?: boolean;
437
437
  export function readdirSync(path: fs.PathLike, options: { recursive?: boolean; encoding: 'buffer'; withFileTypes?: false } | 'buffer'): Buffer[];
438
438
  export function readdirSync(path: fs.PathLike, options: { recursive?: boolean; withFileTypes: true }): Dirent[];
439
439
  export function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & { withFileTypes?: false; recursive?: boolean }) | BufferEncoding | null): string[] | Buffer[];
440
+ export function readdirSync(
441
+ path: fs.PathLike,
442
+ options?: { withFileTypes?: boolean; recursive?: boolean; encoding?: BufferEncoding | 'buffer' | null } | BufferEncoding | 'buffer' | null
443
+ ): string[] | Dirent[] | Buffer[];
440
444
  export function readdirSync(
441
445
  path: fs.PathLike,
442
446
  options?: { recursive?: boolean; encoding?: BufferEncoding | 'buffer' | null; withFileTypes?: boolean } | BufferEncoding | 'buffer' | null
443
447
  ): string[] | Dirent[] | Buffer[] {
448
+ options = typeof options === 'object' ? options : { encoding: options };
444
449
  path = normalizePath(path);
445
450
  const { fs, path: resolved } = resolveMount(existsSync(path) ? realpathSync(path) : path);
446
451
  let entries: string[];
@@ -452,6 +457,7 @@ export function readdirSync(
452
457
  } catch (e) {
453
458
  throw fixError(e as Error, { [resolved]: path });
454
459
  }
460
+
455
461
  for (const mount of mounts.keys()) {
456
462
  if (!mount.startsWith(path)) {
457
463
  continue;
@@ -463,17 +469,35 @@ export function readdirSync(
463
469
  }
464
470
  entries.push(entry);
465
471
  }
466
- return entries.map((entry: string) => {
467
- if (typeof options == 'object' && options?.withFileTypes) {
468
- return new Dirent(entry, statSync(join(path.toString(), entry)));
469
- }
470
472
 
471
- if (options == 'buffer' || (typeof options == 'object' && options?.encoding == 'buffer')) {
472
- return Buffer.from(entry);
473
+ // Iterate over entries and handle recursive case if needed
474
+ const values: (string | Dirent | Buffer)[] = [];
475
+ for (const entry of entries) {
476
+ const fullPath = join(path, entry);
477
+ const entryStat = statSync(fullPath);
478
+
479
+ if (options?.withFileTypes) {
480
+ values.push(new Dirent(entry, entryStat));
481
+ } else if (options?.encoding === 'buffer') {
482
+ values.push(Buffer.from(entry));
483
+ } else {
484
+ values.push(entry);
485
+ }
486
+ if (!entryStat.isDirectory() || !options?.recursive) continue;
487
+
488
+ for (const subEntry of readdirSync(fullPath, options)) {
489
+ if (subEntry instanceof Dirent) {
490
+ subEntry.path = join(entry, subEntry.path);
491
+ values.push(subEntry);
492
+ } else if (Buffer.isBuffer(subEntry)) {
493
+ values.push(Buffer.from(join(entry, decodeUTF8(subEntry))));
494
+ } else {
495
+ values.push(join(entry, subEntry));
496
+ }
473
497
  }
498
+ }
474
499
 
475
- return entry;
476
- }) as string[] | Dirent[] | Buffer[];
500
+ return values as string[] | Dirent[] | Buffer[];
477
501
  }
478
502
  readdirSync satisfies typeof fs.readdirSync;
479
503
 
package/src/stats.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type * as Node from 'fs';
1
+ import type * as Node from 'node:fs';
2
2
  import { credentials, type Credentials } from './credentials.js';
3
3
  import { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRWXG, S_IRWXO, S_IRWXU, size_max } from './emulation/constants.js';
4
4