@zenfs/core 1.3.3 → 1.3.5
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/backends/port/fs.d.ts +1 -2
- package/dist/backends/port/fs.js +0 -3
- package/dist/config.d.ts +5 -3
- package/dist/config.js +4 -6
- package/dist/credentials.d.ts +14 -2
- package/dist/credentials.js +11 -9
- package/dist/emulation/cache.d.ts +10 -6
- package/dist/emulation/cache.js +14 -9
- package/dist/emulation/promises.js +25 -18
- package/dist/emulation/shared.js +3 -0
- package/dist/emulation/sync.js +28 -20
- package/dist/emulation/watchers.js +9 -9
- package/dist/file.d.ts +1 -13
- package/dist/file.js +7 -23
- package/dist/stats.d.ts +11 -7
- package/dist/stats.js +14 -15
- package/package.json +4 -2
- package/readme.md +2 -0
- package/scripts/make-index.js +24 -7
- package/src/backends/port/fs.ts +1 -5
- package/src/config.ts +9 -9
- package/src/credentials.ts +24 -9
- package/src/emulation/cache.ts +15 -10
- package/src/emulation/promises.ts +24 -18
- package/src/emulation/shared.ts +3 -0
- package/src/emulation/sync.ts +27 -20
- package/src/emulation/watchers.ts +10 -9
- package/src/file.ts +7 -37
- package/src/stats.ts +21 -18
- package/tests/fs/links.test.ts +4 -0
- package/tests/fs/watch.test.ts +36 -4
package/src/emulation/sync.ts
CHANGED
|
@@ -10,7 +10,7 @@ import * as cache from './cache.js';
|
|
|
10
10
|
import { config } from './config.js';
|
|
11
11
|
import * as constants from './constants.js';
|
|
12
12
|
import { Dir, Dirent } from './dir.js';
|
|
13
|
-
import { dirname, join, parse } from './path.js';
|
|
13
|
+
import { dirname, join, parse, resolve } from './path.js';
|
|
14
14
|
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
|
|
15
15
|
import { emitChange } from './watchers.js';
|
|
16
16
|
|
|
@@ -26,6 +26,7 @@ export function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void {
|
|
|
26
26
|
if (oldMount === newMount) {
|
|
27
27
|
oldMount.fs.renameSync(oldMount.path, newMount.path);
|
|
28
28
|
emitChange('rename', oldPath.toString());
|
|
29
|
+
emitChange('change', newPath.toString());
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -106,7 +107,7 @@ export function unlinkSync(path: fs.PathLike): void {
|
|
|
106
107
|
path = normalizePath(path);
|
|
107
108
|
const { fs, path: resolved } = resolveMount(path);
|
|
108
109
|
try {
|
|
109
|
-
if (config.checkAccess && !(cache.stats.
|
|
110
|
+
if (config.checkAccess && !(cache.stats.get(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
|
|
110
111
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
111
112
|
}
|
|
112
113
|
fs.unlinkSync(resolved);
|
|
@@ -386,7 +387,7 @@ export function rmdirSync(path: fs.PathLike): void {
|
|
|
386
387
|
path = normalizePath(path);
|
|
387
388
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
388
389
|
try {
|
|
389
|
-
const stats = cache.stats.
|
|
390
|
+
const stats = cache.stats.get(path) || fs.statSync(resolved);
|
|
390
391
|
if (!stats.isDirectory()) {
|
|
391
392
|
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
392
393
|
}
|
|
@@ -459,8 +460,8 @@ export function readdirSync(
|
|
|
459
460
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
460
461
|
let entries: string[];
|
|
461
462
|
try {
|
|
462
|
-
const stats = cache.stats.
|
|
463
|
-
cache.stats.
|
|
463
|
+
const stats = cache.stats.get(path) || fs.statSync(resolved);
|
|
464
|
+
cache.stats.set(path, stats);
|
|
464
465
|
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
465
466
|
throw ErrnoError.With('EACCES', resolved, 'readdir');
|
|
466
467
|
}
|
|
@@ -475,8 +476,8 @@ export function readdirSync(
|
|
|
475
476
|
// Iterate over entries and handle recursive case if needed
|
|
476
477
|
const values: (string | Dirent | Buffer)[] = [];
|
|
477
478
|
for (const entry of entries) {
|
|
478
|
-
const entryStat = cache.stats.
|
|
479
|
-
cache.stats.
|
|
479
|
+
const entryStat = cache.stats.get(join(path, entry)) || fs.statSync(join(resolved, entry));
|
|
480
|
+
cache.stats.set(join(path, entry), entryStat);
|
|
480
481
|
|
|
481
482
|
if (options?.withFileTypes) {
|
|
482
483
|
values.push(new Dirent(entry, entryStat));
|
|
@@ -500,7 +501,7 @@ export function readdirSync(
|
|
|
500
501
|
}
|
|
501
502
|
|
|
502
503
|
if (!options?._isIndirect) {
|
|
503
|
-
cache.stats.
|
|
504
|
+
cache.stats.clear();
|
|
504
505
|
}
|
|
505
506
|
return values as string[] | Dirent[] | Buffer[];
|
|
506
507
|
}
|
|
@@ -550,7 +551,7 @@ export function symlinkSync(target: fs.PathLike, path: fs.PathLike, type: fs.sym
|
|
|
550
551
|
|
|
551
552
|
writeFileSync(path, target.toString());
|
|
552
553
|
const file = _openSync(path, 'r+', 0o644, false);
|
|
553
|
-
file.
|
|
554
|
+
file.chmodSync(constants.S_IFLNK);
|
|
554
555
|
}
|
|
555
556
|
symlinkSync satisfies typeof fs.symlinkSync;
|
|
556
557
|
|
|
@@ -621,18 +622,24 @@ export function realpathSync(path: fs.PathLike, options: fs.BufferEncodingOption
|
|
|
621
622
|
export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption): string;
|
|
622
623
|
export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs.BufferEncodingOption): string | Buffer {
|
|
623
624
|
path = normalizePath(path);
|
|
625
|
+
if (cache.paths.has(path)) return cache.paths.get(path)!;
|
|
624
626
|
const { base, dir } = parse(path);
|
|
625
|
-
const
|
|
626
|
-
const
|
|
627
|
+
const realDir = dir == '/' ? '/' : cache.paths.get(dir) || realpathSync(dir);
|
|
628
|
+
const lpath = join(realDir, base);
|
|
629
|
+
const { fs, path: resolvedPath } = resolveMount(lpath);
|
|
627
630
|
|
|
628
631
|
try {
|
|
629
|
-
const stats = fs.statSync(resolvedPath);
|
|
632
|
+
const stats = cache.stats.get(lpath) || fs.statSync(resolvedPath);
|
|
633
|
+
cache.stats.set(lpath, stats);
|
|
630
634
|
if (!stats.isSymbolicLink()) {
|
|
635
|
+
cache.paths.set(path, lpath);
|
|
631
636
|
return lpath;
|
|
632
637
|
}
|
|
633
638
|
|
|
634
|
-
const target =
|
|
635
|
-
|
|
639
|
+
const target = resolve(realDir, readlinkSync(lpath, options).toString());
|
|
640
|
+
const real = cache.paths.get(target) || realpathSync(target);
|
|
641
|
+
cache.paths.set(path, real);
|
|
642
|
+
return real;
|
|
636
643
|
} catch (e) {
|
|
637
644
|
if ((e as ErrnoError).code == 'ENOENT') {
|
|
638
645
|
return path;
|
|
@@ -659,7 +666,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
|
|
|
659
666
|
|
|
660
667
|
let stats: Stats | undefined;
|
|
661
668
|
try {
|
|
662
|
-
stats = cache.stats.
|
|
669
|
+
stats = cache.stats.get(path) || statSync(path);
|
|
663
670
|
} catch (error) {
|
|
664
671
|
if ((error as ErrnoError).code != 'ENOENT' || !options?.force) throw error;
|
|
665
672
|
}
|
|
@@ -668,7 +675,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
|
|
|
668
675
|
return;
|
|
669
676
|
}
|
|
670
677
|
|
|
671
|
-
cache.stats.
|
|
678
|
+
cache.stats.set(path, stats);
|
|
672
679
|
|
|
673
680
|
switch (stats.mode & constants.S_IFMT) {
|
|
674
681
|
case constants.S_IFDIR:
|
|
@@ -682,19 +689,19 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
|
|
|
682
689
|
break;
|
|
683
690
|
case constants.S_IFREG:
|
|
684
691
|
case constants.S_IFLNK:
|
|
685
|
-
unlinkSync(path);
|
|
686
|
-
break;
|
|
687
692
|
case constants.S_IFBLK:
|
|
688
693
|
case constants.S_IFCHR:
|
|
694
|
+
unlinkSync(path);
|
|
695
|
+
break;
|
|
689
696
|
case constants.S_IFIFO:
|
|
690
697
|
case constants.S_IFSOCK:
|
|
691
698
|
default:
|
|
692
|
-
cache.stats.
|
|
699
|
+
cache.stats.clear();
|
|
693
700
|
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
|
|
694
701
|
}
|
|
695
702
|
|
|
696
703
|
if (!options?._isIndirect) {
|
|
697
|
-
cache.stats.
|
|
704
|
+
cache.stats.clear();
|
|
698
705
|
}
|
|
699
706
|
}
|
|
700
707
|
rmSync satisfies typeof fs.rmSync;
|
|
@@ -4,7 +4,7 @@ import type * as fs from 'node:fs';
|
|
|
4
4
|
import { ErrnoError } from '../error.js';
|
|
5
5
|
import { isStatsEqual, type Stats } from '../stats.js';
|
|
6
6
|
import { normalizePath } from '../utils.js';
|
|
7
|
-
import {
|
|
7
|
+
import { basename, dirname } from './path.js';
|
|
8
8
|
import { statSync } from './sync.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -171,23 +171,24 @@ export function removeWatcher(path: string, watcher: FSWatcher) {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
export function emitChange(eventType: fs.WatchEventType, filename: string) {
|
|
174
|
-
|
|
174
|
+
filename = normalizePath(filename);
|
|
175
175
|
// Notify watchers on the specific file
|
|
176
|
-
if (watchers.has(
|
|
177
|
-
for (const watcher of watchers.get(
|
|
176
|
+
if (watchers.has(filename)) {
|
|
177
|
+
for (const watcher of watchers.get(filename)!) {
|
|
178
178
|
watcher.emit('change', eventType, basename(filename));
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
// Notify watchers on parent directories if they are watching recursively
|
|
183
|
-
let parent =
|
|
184
|
-
|
|
183
|
+
let parent = filename,
|
|
184
|
+
normalizedFilename;
|
|
185
|
+
while (parent !== normalizedFilename) {
|
|
186
|
+
normalizedFilename = parent;
|
|
187
|
+
parent = dirname(parent);
|
|
185
188
|
if (watchers.has(parent)) {
|
|
186
189
|
for (const watcher of watchers.get(parent)!) {
|
|
187
|
-
watcher.emit('change', eventType,
|
|
190
|
+
watcher.emit('change', eventType, filename.slice(parent.length + (parent == '/' ? 0 : 1)));
|
|
188
191
|
}
|
|
189
192
|
}
|
|
190
|
-
normalizedFilename = parent;
|
|
191
|
-
parent = dirname(parent);
|
|
192
193
|
}
|
|
193
194
|
}
|
package/src/file.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY,
|
|
|
4
4
|
import { Errno, ErrnoError } from './error.js';
|
|
5
5
|
import type { FileSystem } from './filesystem.js';
|
|
6
6
|
import './polyfills.js';
|
|
7
|
-
import {
|
|
7
|
+
import { _chown, Stats } from './stats.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
Typescript does not include a type declaration for resizable array buffers.
|
|
@@ -251,18 +251,6 @@ export abstract class File<FS extends FileSystem = FileSystem> {
|
|
|
251
251
|
* Change the file timestamps of the file.
|
|
252
252
|
*/
|
|
253
253
|
public abstract utimesSync(atime: Date, mtime: Date): void;
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Set the file type
|
|
257
|
-
* @internal
|
|
258
|
-
*/
|
|
259
|
-
public abstract _setType(type: FileType): Promise<void>;
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Set the file type
|
|
263
|
-
* @internal
|
|
264
|
-
*/
|
|
265
|
-
public abstract _setTypeSync(type: FileType): void;
|
|
266
254
|
}
|
|
267
255
|
|
|
268
256
|
/**
|
|
@@ -573,8 +561,8 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
573
561
|
throw ErrnoError.With('EBADF', this.path, 'File.chmod');
|
|
574
562
|
}
|
|
575
563
|
this.dirty = true;
|
|
576
|
-
this.stats.
|
|
577
|
-
if (config.syncImmediately) await this.sync();
|
|
564
|
+
this.stats.mode = (this.stats.mode & (mode > S_IFMT ? ~S_IFMT : S_IFMT)) | mode;
|
|
565
|
+
if (config.syncImmediately || mode > S_IFMT) await this.sync();
|
|
578
566
|
}
|
|
579
567
|
|
|
580
568
|
public chmodSync(mode: number): void {
|
|
@@ -582,8 +570,8 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
582
570
|
throw ErrnoError.With('EBADF', this.path, 'File.chmod');
|
|
583
571
|
}
|
|
584
572
|
this.dirty = true;
|
|
585
|
-
this.stats.
|
|
586
|
-
if (config.syncImmediately) this.syncSync();
|
|
573
|
+
this.stats.mode = (this.stats.mode & (mode > S_IFMT ? ~S_IFMT : S_IFMT)) | mode;
|
|
574
|
+
if (config.syncImmediately || mode > S_IFMT) this.syncSync();
|
|
587
575
|
}
|
|
588
576
|
|
|
589
577
|
public async chown(uid: number, gid: number): Promise<void> {
|
|
@@ -591,7 +579,7 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
591
579
|
throw ErrnoError.With('EBADF', this.path, 'File.chown');
|
|
592
580
|
}
|
|
593
581
|
this.dirty = true;
|
|
594
|
-
this.stats
|
|
582
|
+
_chown(this.stats, uid, gid);
|
|
595
583
|
if (config.syncImmediately) await this.sync();
|
|
596
584
|
}
|
|
597
585
|
|
|
@@ -600,7 +588,7 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
600
588
|
throw ErrnoError.With('EBADF', this.path, 'File.chown');
|
|
601
589
|
}
|
|
602
590
|
this.dirty = true;
|
|
603
|
-
this.stats
|
|
591
|
+
_chown(this.stats, uid, gid);
|
|
604
592
|
if (config.syncImmediately) this.syncSync();
|
|
605
593
|
}
|
|
606
594
|
|
|
@@ -623,24 +611,6 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
623
611
|
this.stats.mtime = mtime;
|
|
624
612
|
if (config.syncImmediately) this.syncSync();
|
|
625
613
|
}
|
|
626
|
-
|
|
627
|
-
public async _setType(type: FileType): Promise<void> {
|
|
628
|
-
if (this.closed) {
|
|
629
|
-
throw ErrnoError.With('EBADF', this.path, 'File._setType');
|
|
630
|
-
}
|
|
631
|
-
this.dirty = true;
|
|
632
|
-
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
633
|
-
await this.sync();
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
public _setTypeSync(type: FileType): void {
|
|
637
|
-
if (this.closed) {
|
|
638
|
-
throw ErrnoError.With('EBADF', this.path, 'File._setType');
|
|
639
|
-
}
|
|
640
|
-
this.dirty = true;
|
|
641
|
-
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
642
|
-
this.syncSync();
|
|
643
|
-
}
|
|
644
614
|
}
|
|
645
615
|
|
|
646
616
|
/**
|
package/src/stats.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as Node from 'node:fs';
|
|
2
|
-
import { credentials
|
|
2
|
+
import { credentials } from './credentials.js';
|
|
3
3
|
import {
|
|
4
4
|
R_OK,
|
|
5
5
|
S_IFBLK,
|
|
@@ -67,6 +67,10 @@ export interface StatsLike<T extends number | bigint = number | bigint> {
|
|
|
67
67
|
* Inode number
|
|
68
68
|
*/
|
|
69
69
|
ino: T;
|
|
70
|
+
/**
|
|
71
|
+
* Number of hard links
|
|
72
|
+
*/
|
|
73
|
+
nlink: T;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
/**
|
|
@@ -258,7 +262,7 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
// Group permissions
|
|
261
|
-
if (credentials.gid === this.gid) {
|
|
265
|
+
if (credentials.gid === this.gid || credentials.groups.includes(Number(this.gid))) {
|
|
262
266
|
if (this.mode & S_IRGRP) perm |= R_OK;
|
|
263
267
|
if (this.mode & S_IWGRP) perm |= W_OK;
|
|
264
268
|
if (this.mode & S_IXGRP) perm |= X_OK;
|
|
@@ -273,25 +277,11 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
273
277
|
return (perm & mode) === mode;
|
|
274
278
|
}
|
|
275
279
|
|
|
276
|
-
/**
|
|
277
|
-
* Convert the current stats object into a credentials object
|
|
278
|
-
* @internal
|
|
279
|
-
*/
|
|
280
|
-
public cred(uid: number = Number(this.uid), gid: number = Number(this.gid)): Credentials {
|
|
281
|
-
return {
|
|
282
|
-
uid,
|
|
283
|
-
gid,
|
|
284
|
-
suid: Number(this.uid),
|
|
285
|
-
sgid: Number(this.gid),
|
|
286
|
-
euid: uid,
|
|
287
|
-
egid: gid,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
|
|
291
280
|
/**
|
|
292
281
|
* Change the mode of the file.
|
|
293
282
|
* We use this helper function to prevent messing up the type of the file.
|
|
294
283
|
* @internal
|
|
284
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
295
285
|
*/
|
|
296
286
|
public chmod(mode: number): void {
|
|
297
287
|
this.mode = this._convert((this.mode & S_IFMT) | mode);
|
|
@@ -301,8 +291,9 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
301
291
|
* Change the owner user/group of the file.
|
|
302
292
|
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
303
293
|
* @internal
|
|
294
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
304
295
|
*/
|
|
305
|
-
public chown(uid: number
|
|
296
|
+
public chown(uid: number, gid: number): void {
|
|
306
297
|
uid = Number(uid);
|
|
307
298
|
gid = Number(gid);
|
|
308
299
|
if (!isNaN(uid) && 0 <= uid && uid < 2 ** 32) {
|
|
@@ -330,6 +321,18 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
330
321
|
}
|
|
331
322
|
}
|
|
332
323
|
|
|
324
|
+
/**
|
|
325
|
+
* @hidden @internal
|
|
326
|
+
*/
|
|
327
|
+
export function _chown(stats: Partial<StatsLike<number>>, uid: number, gid: number) {
|
|
328
|
+
if (!isNaN(uid) && 0 <= uid && uid < 2 ** 32) {
|
|
329
|
+
stats.uid = uid;
|
|
330
|
+
}
|
|
331
|
+
if (!isNaN(gid) && 0 <= gid && gid < 2 ** 32) {
|
|
332
|
+
stats.gid = gid;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
333
336
|
/**
|
|
334
337
|
* Implementation of Node's `Stats`.
|
|
335
338
|
*
|
package/tests/fs/links.test.ts
CHANGED
|
@@ -23,6 +23,10 @@ suite('Links', () => {
|
|
|
23
23
|
assert.strictEqual(destination, target);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
test('read target contents', async () => {
|
|
27
|
+
assert.equal(await fs.promises.readFile(target, 'utf-8'), await fs.promises.readFile(symlink, 'utf-8'));
|
|
28
|
+
});
|
|
29
|
+
|
|
26
30
|
test('unlink', async () => {
|
|
27
31
|
await fs.promises.unlink(symlink);
|
|
28
32
|
assert(!(await fs.promises.exists(symlink)));
|
package/tests/fs/watch.test.ts
CHANGED
|
@@ -71,18 +71,29 @@ suite('Watch Features', () => {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
test('fs.watch should detect file renames', async () => {
|
|
74
|
-
const
|
|
75
|
-
const
|
|
74
|
+
const oldFileName = `oldFile.txt`;
|
|
75
|
+
const newFileName = `newFile.txt`;
|
|
76
|
+
const oldFile = `${testDir}/${oldFileName}`;
|
|
77
|
+
const newFile = `${testDir}/${newFileName}`;
|
|
76
78
|
|
|
77
79
|
await fs.promises.writeFile(oldFile, 'Some content');
|
|
80
|
+
const oldFileResolver = Promise.withResolvers<void>();
|
|
81
|
+
const newFileResolver = Promise.withResolvers<void>();
|
|
78
82
|
|
|
83
|
+
const fileResolvers: Record<string, { resolver: PromiseWithResolvers<void>; eventType: string }> = {
|
|
84
|
+
[oldFileName]: { resolver: oldFileResolver, eventType: 'rename' },
|
|
85
|
+
[newFileName]: { resolver: newFileResolver, eventType: 'change' },
|
|
86
|
+
};
|
|
79
87
|
using watcher = fs.watch(testDir, (eventType, filename) => {
|
|
80
|
-
|
|
81
|
-
assert.
|
|
88
|
+
const resolver = fileResolvers[filename];
|
|
89
|
+
assert.notEqual(resolver, undefined); // should have a resolver so file is expected
|
|
90
|
+
assert.strictEqual(eventType, resolver.eventType);
|
|
91
|
+
resolver.resolver.resolve();
|
|
82
92
|
});
|
|
83
93
|
|
|
84
94
|
// Rename the file to trigger the event
|
|
85
95
|
await fs.promises.rename(oldFile, newFile);
|
|
96
|
+
await Promise.all([newFileResolver.promise, oldFileResolver.promise]);
|
|
86
97
|
});
|
|
87
98
|
|
|
88
99
|
test('fs.watch should detect file deletions', async () => {
|
|
@@ -115,6 +126,27 @@ suite('Watch Features', () => {
|
|
|
115
126
|
resolve();
|
|
116
127
|
})();
|
|
117
128
|
|
|
129
|
+
await fs.promises.unlink(tempFile);
|
|
130
|
+
await promise;
|
|
131
|
+
});
|
|
132
|
+
test('fs.promises.watch should detect file creations recursively', async () => {
|
|
133
|
+
const rootDir = '/';
|
|
134
|
+
const subDir = `${testDir}sub-dir`;
|
|
135
|
+
const tempFile = `${subDir}/tempFile.txt`;
|
|
136
|
+
await fs.promises.mkdir(subDir);
|
|
137
|
+
const watcher = fs.promises.watch(rootDir);
|
|
138
|
+
|
|
139
|
+
await fs.promises.writeFile(tempFile, 'Temporary content');
|
|
140
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
141
|
+
(async () => {
|
|
142
|
+
for await (const event of watcher) {
|
|
143
|
+
assert.equal(event.eventType, 'rename');
|
|
144
|
+
assert.equal(event.filename, tempFile.substring(rootDir.length));
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
resolve();
|
|
148
|
+
})();
|
|
149
|
+
|
|
118
150
|
await fs.promises.unlink(tempFile);
|
|
119
151
|
await promise;
|
|
120
152
|
});
|