@zenfs/core 0.17.1 → 0.18.0
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/backend.d.ts +2 -3
- package/dist/backends/fetch.js +2 -2
- package/dist/backends/file_index.d.ts +14 -15
- package/dist/backends/file_index.js +3 -9
- package/dist/backends/overlay.d.ts +21 -22
- package/dist/backends/overlay.js +111 -114
- package/dist/backends/port/fs.d.ts +21 -22
- package/dist/backends/port/fs.js +23 -23
- package/dist/backends/store/fs.d.ts +20 -21
- package/dist/backends/store/fs.js +70 -138
- package/dist/browser.min.js +4 -4
- package/dist/browser.min.js.map +4 -4
- package/dist/config.js +2 -2
- package/dist/{cred.d.ts → credentials.d.ts} +3 -2
- package/dist/credentials.js +16 -0
- package/dist/emulation/async.d.ts +19 -4
- package/dist/emulation/async.js +55 -8
- package/dist/emulation/dir.d.ts +4 -7
- package/dist/emulation/dir.js +16 -24
- package/dist/emulation/promises.d.ts +3 -3
- package/dist/emulation/promises.js +103 -46
- package/dist/emulation/shared.d.ts +0 -3
- package/dist/emulation/shared.js +0 -6
- package/dist/emulation/sync.d.ts +3 -4
- package/dist/emulation/sync.js +107 -65
- package/dist/emulation/watchers.d.ts +40 -3
- package/dist/emulation/watchers.js +115 -9
- package/dist/error.d.ts +1 -1
- package/dist/error.js +1 -1
- package/dist/filesystem.d.ts +20 -21
- package/dist/filesystem.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mixins/async.d.ts +13 -14
- package/dist/mixins/async.js +45 -47
- package/dist/mixins/mutexed.d.ts +1 -1
- package/dist/mixins/mutexed.js +61 -53
- package/dist/mixins/readonly.d.ts +12 -13
- package/dist/mixins/readonly.js +12 -12
- package/dist/mixins/sync.js +20 -20
- package/dist/stats.d.ts +12 -5
- package/dist/stats.js +11 -2
- package/dist/utils.d.ts +3 -4
- package/dist/utils.js +7 -17
- package/package.json +2 -2
- package/src/backends/backend.ts +2 -3
- package/src/backends/fetch.ts +2 -2
- package/src/backends/file_index.ts +3 -12
- package/src/backends/overlay.ts +112 -116
- package/src/backends/port/fs.ts +25 -26
- package/src/backends/store/fs.ts +72 -151
- package/src/config.ts +3 -2
- package/src/{cred.ts → credentials.ts} +11 -2
- package/src/emulation/async.ts +72 -16
- package/src/emulation/dir.ts +21 -29
- package/src/emulation/promises.ts +107 -46
- package/src/emulation/shared.ts +0 -8
- package/src/emulation/sync.ts +109 -66
- package/src/emulation/watchers.ts +140 -10
- package/src/error.ts +1 -1
- package/src/filesystem.ts +22 -23
- package/src/index.ts +1 -1
- package/src/mixins/async.ts +54 -55
- package/src/mixins/mutexed.ts +62 -55
- package/src/mixins/readonly.ts +24 -25
- package/src/mixins/sync.ts +21 -22
- package/src/stats.ts +15 -5
- package/src/utils.ts +9 -26
- package/dist/cred.js +0 -8
package/src/emulation/sync.ts
CHANGED
|
@@ -2,14 +2,16 @@ import { Buffer } from 'buffer';
|
|
|
2
2
|
import type * as fs from 'node:fs';
|
|
3
3
|
import { Errno, ErrnoError } from '../error.js';
|
|
4
4
|
import type { File } from '../file.js';
|
|
5
|
-
import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
|
|
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
8
|
import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
|
|
9
|
-
import
|
|
9
|
+
import * as constants from './constants.js';
|
|
10
10
|
import { Dir, Dirent } from './dir.js';
|
|
11
11
|
import { dirname, join, parse } from './path.js';
|
|
12
|
-
import { _statfs,
|
|
12
|
+
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
|
|
13
|
+
import { credentials } from '../credentials.js';
|
|
14
|
+
import { emitChange } from './watchers.js';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Synchronous rename.
|
|
@@ -19,18 +21,23 @@ import { _statfs, cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount
|
|
|
19
21
|
export function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void {
|
|
20
22
|
oldPath = normalizePath(oldPath);
|
|
21
23
|
newPath = normalizePath(newPath);
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
24
|
+
const oldMount = resolveMount(oldPath);
|
|
25
|
+
const newMount = resolveMount(newPath);
|
|
26
|
+
if (!statSync(dirname(oldPath)).hasAccess(constants.W_OK, credentials)) {
|
|
27
|
+
throw ErrnoError.With('EACCES', oldPath, 'rename');
|
|
28
|
+
}
|
|
25
29
|
try {
|
|
26
|
-
if (
|
|
27
|
-
|
|
30
|
+
if (oldMount === newMount) {
|
|
31
|
+
oldMount.fs.renameSync(oldMount.path, newMount.path);
|
|
32
|
+
emitChange('rename', oldPath.toString());
|
|
33
|
+
return;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
writeFileSync(newPath, readFileSync(oldPath));
|
|
31
37
|
unlinkSync(oldPath);
|
|
38
|
+
emitChange('rename', oldPath.toString());
|
|
32
39
|
} catch (e) {
|
|
33
|
-
throw fixError(e as Error,
|
|
40
|
+
throw fixError(e as Error, { [oldMount.path]: oldPath, [newMount.path]: newPath });
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
renameSync satisfies typeof fs.renameSync;
|
|
@@ -43,7 +50,7 @@ export function existsSync(path: fs.PathLike): boolean {
|
|
|
43
50
|
path = normalizePath(path);
|
|
44
51
|
try {
|
|
45
52
|
const { fs, path: resolvedPath } = resolveMount(realpathSync(path));
|
|
46
|
-
return fs.existsSync(resolvedPath
|
|
53
|
+
return fs.existsSync(resolvedPath);
|
|
47
54
|
} catch (e) {
|
|
48
55
|
if ((e as ErrnoError).errno == Errno.ENOENT) {
|
|
49
56
|
return false;
|
|
@@ -65,7 +72,10 @@ export function statSync(path: fs.PathLike, options?: fs.StatOptions): Stats | B
|
|
|
65
72
|
path = normalizePath(path);
|
|
66
73
|
const { fs, path: resolved } = resolveMount(existsSync(path) ? realpathSync(path) : path);
|
|
67
74
|
try {
|
|
68
|
-
const stats = fs.statSync(resolved
|
|
75
|
+
const stats = fs.statSync(resolved);
|
|
76
|
+
if (!stats.hasAccess(constants.R_OK, credentials)) {
|
|
77
|
+
throw ErrnoError.With('EACCES', path, 'stat');
|
|
78
|
+
}
|
|
69
79
|
return options?.bigint ? new BigIntStats(stats) : stats;
|
|
70
80
|
} catch (e) {
|
|
71
81
|
throw fixError(e as Error, { [resolved]: path });
|
|
@@ -85,7 +95,7 @@ export function lstatSync(path: fs.PathLike, options?: fs.StatOptions): Stats |
|
|
|
85
95
|
path = normalizePath(path);
|
|
86
96
|
const { fs, path: resolved } = resolveMount(path);
|
|
87
97
|
try {
|
|
88
|
-
const stats = fs.statSync(resolved
|
|
98
|
+
const stats = fs.statSync(resolved);
|
|
89
99
|
return options?.bigint ? new BigIntStats(stats) : stats;
|
|
90
100
|
} catch (e) {
|
|
91
101
|
throw fixError(e as Error, { [resolved]: path });
|
|
@@ -116,7 +126,11 @@ export function unlinkSync(path: fs.PathLike): void {
|
|
|
116
126
|
path = normalizePath(path);
|
|
117
127
|
const { fs, path: resolved } = resolveMount(path);
|
|
118
128
|
try {
|
|
119
|
-
|
|
129
|
+
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) {
|
|
130
|
+
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
131
|
+
}
|
|
132
|
+
fs.unlinkSync(resolved);
|
|
133
|
+
emitChange('rename', path.toString());
|
|
120
134
|
} catch (e) {
|
|
121
135
|
throw fixError(e as Error, { [resolved]: path });
|
|
122
136
|
}
|
|
@@ -131,21 +145,24 @@ function _openSync(path: fs.PathLike, _flag: fs.OpenMode, _mode?: fs.Mode | null
|
|
|
131
145
|
path = resolveSymlinks && existsSync(path) ? realpathSync(path) : path;
|
|
132
146
|
const { fs, path: resolved } = resolveMount(path);
|
|
133
147
|
|
|
134
|
-
if (!fs.existsSync(resolved
|
|
148
|
+
if (!fs.existsSync(resolved)) {
|
|
135
149
|
if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') {
|
|
136
150
|
throw ErrnoError.With('ENOENT', path, '_open');
|
|
137
151
|
}
|
|
138
152
|
// Create the file
|
|
139
|
-
const parentStats: Stats = fs.statSync(dirname(resolved)
|
|
153
|
+
const parentStats: Stats = fs.statSync(dirname(resolved));
|
|
154
|
+
if (!parentStats.hasAccess(constants.W_OK, credentials)) {
|
|
155
|
+
throw ErrnoError.With('EACCES', dirname(path), '_open');
|
|
156
|
+
}
|
|
140
157
|
if (!parentStats.isDirectory()) {
|
|
141
158
|
throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
|
|
142
159
|
}
|
|
143
|
-
return fs.createFileSync(resolved, flag, mode
|
|
160
|
+
return fs.createFileSync(resolved, flag, mode);
|
|
144
161
|
}
|
|
145
162
|
|
|
146
|
-
const stats: Stats = fs.statSync(resolved
|
|
163
|
+
const stats: Stats = fs.statSync(resolved);
|
|
147
164
|
|
|
148
|
-
if (!stats.hasAccess(mode,
|
|
165
|
+
if (!stats.hasAccess(mode, credentials) || !stats.hasAccess(flagToMode(flag), credentials)) {
|
|
149
166
|
throw ErrnoError.With('EACCES', path, '_open');
|
|
150
167
|
}
|
|
151
168
|
|
|
@@ -153,19 +170,14 @@ function _openSync(path: fs.PathLike, _flag: fs.OpenMode, _mode?: fs.Mode | null
|
|
|
153
170
|
throw ErrnoError.With('EEXIST', path, '_open');
|
|
154
171
|
}
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
const file = fs.openFileSync(resolved, flag);
|
|
174
|
+
|
|
175
|
+
if (isTruncating(flag)) {
|
|
176
|
+
file.truncateSync(0);
|
|
177
|
+
file.syncSync();
|
|
158
178
|
}
|
|
159
179
|
|
|
160
|
-
|
|
161
|
-
fs.unlinkSync(resolved, cred);
|
|
162
|
-
/*
|
|
163
|
-
Create file. Use the same mode as the old file.
|
|
164
|
-
Node itself modifies the ctime when this occurs, so this action
|
|
165
|
-
will preserve that behavior if the underlying file system
|
|
166
|
-
supports those properties.
|
|
167
|
-
*/
|
|
168
|
-
return fs.createFileSync(resolved, flag, stats.mode, cred);
|
|
180
|
+
return file;
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
/**
|
|
@@ -176,7 +188,7 @@ function _openSync(path: fs.PathLike, _flag: fs.OpenMode, _mode?: fs.Mode | null
|
|
|
176
188
|
* @param mode Mode to use to open the file. Can be ignored if the
|
|
177
189
|
* filesystem doesn't support permissions.
|
|
178
190
|
*/
|
|
179
|
-
export function openSync(path: fs.PathLike, flag: fs.OpenMode, mode: fs.Mode | null = F_OK): number {
|
|
191
|
+
export function openSync(path: fs.PathLike, flag: fs.OpenMode, mode: fs.Mode | null = constants.F_OK): number {
|
|
180
192
|
return file2fd(_openSync(path, flag, mode, true));
|
|
181
193
|
}
|
|
182
194
|
openSync satisfies typeof fs.openSync;
|
|
@@ -252,6 +264,7 @@ export function writeFileSync(path: fs.PathOrFileDescriptor, data: FileContents,
|
|
|
252
264
|
}
|
|
253
265
|
using file = _openSync(typeof path == 'number' ? fd2file(path).path : path.toString(), flag, options.mode, true);
|
|
254
266
|
file.writeSync(encodedData, 0, encodedData.byteLength, 0);
|
|
267
|
+
emitChange('change', path.toString());
|
|
255
268
|
}
|
|
256
269
|
writeFileSync satisfies typeof fs.writeFileSync;
|
|
257
270
|
|
|
@@ -371,7 +384,9 @@ export function writeSync(fd: number, data: FileContents, posOrOff?: number | nu
|
|
|
371
384
|
|
|
372
385
|
const file = fd2file(fd);
|
|
373
386
|
position ??= file.position;
|
|
374
|
-
|
|
387
|
+
const bytesWritten = file.writeSync(buffer, offset, length, position);
|
|
388
|
+
emitChange('change', file.path);
|
|
389
|
+
return bytesWritten;
|
|
375
390
|
}
|
|
376
391
|
writeSync satisfies typeof fs.writeSync;
|
|
377
392
|
|
|
@@ -451,7 +466,11 @@ export function rmdirSync(path: fs.PathLike): void {
|
|
|
451
466
|
path = normalizePath(path);
|
|
452
467
|
const { fs, path: resolved } = resolveMount(existsSync(path) ? realpathSync(path) : path);
|
|
453
468
|
try {
|
|
454
|
-
fs.
|
|
469
|
+
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) {
|
|
470
|
+
throw ErrnoError.With('EACCES', resolved, 'rmdir');
|
|
471
|
+
}
|
|
472
|
+
fs.rmdirSync(resolved);
|
|
473
|
+
emitChange('rename', path.toString());
|
|
455
474
|
} catch (e) {
|
|
456
475
|
throw fixError(e as Error, { [resolved]: path });
|
|
457
476
|
}
|
|
@@ -462,7 +481,6 @@ rmdirSync satisfies typeof fs.rmdirSync;
|
|
|
462
481
|
* Synchronous `mkdir`.
|
|
463
482
|
* @param path
|
|
464
483
|
* @param mode defaults to o777
|
|
465
|
-
* @todo Implement recursion
|
|
466
484
|
*/
|
|
467
485
|
export function mkdirSync(path: fs.PathLike, options: fs.MakeDirectoryOptions & { recursive: true }): string | undefined;
|
|
468
486
|
export function mkdirSync(path: fs.PathLike, options?: fs.Mode | (fs.MakeDirectoryOptions & { recursive?: false }) | null): void;
|
|
@@ -478,16 +496,23 @@ export function mkdirSync(path: fs.PathLike, options?: fs.Mode | fs.MakeDirector
|
|
|
478
496
|
|
|
479
497
|
try {
|
|
480
498
|
if (!options?.recursive) {
|
|
481
|
-
|
|
499
|
+
if (!fs.statSync(dirname(resolved)).hasAccess(constants.W_OK, credentials)) {
|
|
500
|
+
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir');
|
|
501
|
+
}
|
|
502
|
+
return fs.mkdirSync(resolved, mode);
|
|
482
503
|
}
|
|
483
504
|
|
|
484
505
|
const dirs: string[] = [];
|
|
485
|
-
for (let dir = resolved, original = path; !fs.existsSync(dir
|
|
506
|
+
for (let dir = resolved, original = path; !fs.existsSync(dir); dir = dirname(dir), original = dirname(original)) {
|
|
486
507
|
dirs.unshift(dir);
|
|
487
508
|
errorPaths[dir] = original;
|
|
488
509
|
}
|
|
489
510
|
for (const dir of dirs) {
|
|
490
|
-
fs.
|
|
511
|
+
if (!fs.statSync(dirname(dir)).hasAccess(constants.W_OK, credentials)) {
|
|
512
|
+
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir');
|
|
513
|
+
}
|
|
514
|
+
fs.mkdirSync(dir, mode);
|
|
515
|
+
emitChange('rename', dir);
|
|
491
516
|
}
|
|
492
517
|
return dirs[0];
|
|
493
518
|
} catch (e) {
|
|
@@ -511,8 +536,11 @@ export function readdirSync(
|
|
|
511
536
|
path = normalizePath(path);
|
|
512
537
|
const { fs, path: resolved } = resolveMount(existsSync(path) ? realpathSync(path) : path);
|
|
513
538
|
let entries: string[];
|
|
539
|
+
if (!statSync(path).hasAccess(constants.R_OK, credentials)) {
|
|
540
|
+
throw ErrnoError.With('EACCES', path, 'readdir');
|
|
541
|
+
}
|
|
514
542
|
try {
|
|
515
|
-
entries = fs.readdirSync(resolved
|
|
543
|
+
entries = fs.readdirSync(resolved);
|
|
516
544
|
} catch (e) {
|
|
517
545
|
throw fixError(e as Error, { [resolved]: path });
|
|
518
546
|
}
|
|
@@ -545,17 +573,31 @@ readdirSync satisfies typeof fs.readdirSync;
|
|
|
545
573
|
|
|
546
574
|
/**
|
|
547
575
|
* Synchronous `link`.
|
|
548
|
-
* @param
|
|
549
|
-
* @param
|
|
576
|
+
* @param targetPath
|
|
577
|
+
* @param linkPath
|
|
550
578
|
*/
|
|
551
|
-
export function linkSync(
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
579
|
+
export function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void {
|
|
580
|
+
targetPath = normalizePath(targetPath);
|
|
581
|
+
if (!statSync(dirname(targetPath)).hasAccess(constants.R_OK, credentials)) {
|
|
582
|
+
throw ErrnoError.With('EACCES', dirname(targetPath), 'link');
|
|
583
|
+
}
|
|
584
|
+
linkPath = normalizePath(linkPath);
|
|
585
|
+
if (!statSync(dirname(linkPath)).hasAccess(constants.W_OK, credentials)) {
|
|
586
|
+
throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const { fs, path } = resolveMount(targetPath);
|
|
590
|
+
const link = resolveMount(linkPath);
|
|
591
|
+
if (fs != link.fs) {
|
|
592
|
+
throw ErrnoError.With('EXDEV', linkPath, 'link');
|
|
593
|
+
}
|
|
555
594
|
try {
|
|
556
|
-
|
|
595
|
+
if (!fs.statSync(path).hasAccess(constants.W_OK, credentials)) {
|
|
596
|
+
throw ErrnoError.With('EACCES', path, 'link');
|
|
597
|
+
}
|
|
598
|
+
return fs.linkSync(path, linkPath);
|
|
557
599
|
} catch (e) {
|
|
558
|
-
throw fixError(e as Error, { [
|
|
600
|
+
throw fixError(e as Error, { [path]: targetPath, [link.path]: linkPath });
|
|
559
601
|
}
|
|
560
602
|
}
|
|
561
603
|
linkSync satisfies typeof fs.linkSync;
|
|
@@ -576,7 +618,7 @@ export function symlinkSync(target: fs.PathLike, path: fs.PathLike, type: fs.sym
|
|
|
576
618
|
|
|
577
619
|
writeFileSync(path, target.toString());
|
|
578
620
|
const file = _openSync(path, 'r+', 0o644, false);
|
|
579
|
-
file._setTypeSync(S_IFLNK);
|
|
621
|
+
file._setTypeSync(constants.S_IFLNK);
|
|
580
622
|
}
|
|
581
623
|
symlinkSync satisfies typeof fs.symlinkSync;
|
|
582
624
|
|
|
@@ -692,7 +734,7 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs
|
|
|
692
734
|
const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath);
|
|
693
735
|
|
|
694
736
|
try {
|
|
695
|
-
const stats = fs.statSync(resolvedPath
|
|
737
|
+
const stats = fs.statSync(resolvedPath);
|
|
696
738
|
if (!stats.isSymbolicLink()) {
|
|
697
739
|
return lpath;
|
|
698
740
|
}
|
|
@@ -711,7 +753,7 @@ realpathSync satisfies Omit<typeof fs.realpathSync, 'native'>;
|
|
|
711
753
|
*/
|
|
712
754
|
export function accessSync(path: fs.PathLike, mode: number = 0o600): void {
|
|
713
755
|
const stats = statSync(path);
|
|
714
|
-
if (!stats.hasAccess(mode,
|
|
756
|
+
if (!stats.hasAccess(mode, credentials)) {
|
|
715
757
|
throw new ErrnoError(Errno.EACCES);
|
|
716
758
|
}
|
|
717
759
|
}
|
|
@@ -726,8 +768,8 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions): void {
|
|
|
726
768
|
|
|
727
769
|
const stats = statSync(path);
|
|
728
770
|
|
|
729
|
-
switch (stats.mode & S_IFMT) {
|
|
730
|
-
case S_IFDIR:
|
|
771
|
+
switch (stats.mode & constants.S_IFMT) {
|
|
772
|
+
case constants.S_IFDIR:
|
|
731
773
|
if (options?.recursive) {
|
|
732
774
|
for (const entry of readdirSync(path)) {
|
|
733
775
|
rmSync(join(path, entry), options);
|
|
@@ -736,14 +778,14 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions): void {
|
|
|
736
778
|
|
|
737
779
|
rmdirSync(path);
|
|
738
780
|
return;
|
|
739
|
-
case S_IFREG:
|
|
740
|
-
case S_IFLNK:
|
|
781
|
+
case constants.S_IFREG:
|
|
782
|
+
case constants.S_IFLNK:
|
|
741
783
|
unlinkSync(path);
|
|
742
784
|
return;
|
|
743
|
-
case S_IFBLK:
|
|
744
|
-
case S_IFCHR:
|
|
745
|
-
case S_IFIFO:
|
|
746
|
-
case S_IFSOCK:
|
|
785
|
+
case constants.S_IFBLK:
|
|
786
|
+
case constants.S_IFCHR:
|
|
787
|
+
case constants.S_IFIFO:
|
|
788
|
+
case constants.S_IFSOCK:
|
|
747
789
|
default:
|
|
748
790
|
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
|
|
749
791
|
}
|
|
@@ -780,11 +822,12 @@ export function copyFileSync(src: fs.PathLike, dest: fs.PathLike, flags?: number
|
|
|
780
822
|
src = normalizePath(src);
|
|
781
823
|
dest = normalizePath(dest);
|
|
782
824
|
|
|
783
|
-
if (flags && flags & COPYFILE_EXCL && existsSync(dest)) {
|
|
825
|
+
if (flags && flags & constants.COPYFILE_EXCL && existsSync(dest)) {
|
|
784
826
|
throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', dest, 'copyFile');
|
|
785
827
|
}
|
|
786
828
|
|
|
787
829
|
writeFileSync(dest, readFileSync(src));
|
|
830
|
+
emitChange('rename', dest.toString());
|
|
788
831
|
}
|
|
789
832
|
copyFileSync satisfies typeof fs.copyFileSync;
|
|
790
833
|
|
|
@@ -834,7 +877,7 @@ writevSync satisfies typeof fs.writevSync;
|
|
|
834
877
|
*/
|
|
835
878
|
export function opendirSync(path: fs.PathLike, options?: fs.OpenDirOptions): Dir {
|
|
836
879
|
path = normalizePath(path);
|
|
837
|
-
return new Dir(path);
|
|
880
|
+
return new Dir(path);
|
|
838
881
|
}
|
|
839
882
|
opendirSync satisfies typeof fs.opendirSync;
|
|
840
883
|
|
|
@@ -860,8 +903,8 @@ export function cpSync(source: fs.PathLike, destination: fs.PathLike, opts?: fs.
|
|
|
860
903
|
throw new ErrnoError(Errno.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
|
|
861
904
|
}
|
|
862
905
|
|
|
863
|
-
switch (srcStats.mode & S_IFMT) {
|
|
864
|
-
case S_IFDIR:
|
|
906
|
+
switch (srcStats.mode & constants.S_IFMT) {
|
|
907
|
+
case constants.S_IFDIR:
|
|
865
908
|
if (!opts?.recursive) {
|
|
866
909
|
throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp');
|
|
867
910
|
}
|
|
@@ -873,14 +916,14 @@ export function cpSync(source: fs.PathLike, destination: fs.PathLike, opts?: fs.
|
|
|
873
916
|
cpSync(join(source, dirent.name), join(destination, dirent.name), opts);
|
|
874
917
|
}
|
|
875
918
|
break;
|
|
876
|
-
case S_IFREG:
|
|
877
|
-
case S_IFLNK:
|
|
919
|
+
case constants.S_IFREG:
|
|
920
|
+
case constants.S_IFLNK:
|
|
878
921
|
copyFileSync(source, destination);
|
|
879
922
|
break;
|
|
880
|
-
case S_IFBLK:
|
|
881
|
-
case S_IFCHR:
|
|
882
|
-
case S_IFIFO:
|
|
883
|
-
case S_IFSOCK:
|
|
923
|
+
case constants.S_IFBLK:
|
|
924
|
+
case constants.S_IFCHR:
|
|
925
|
+
case constants.S_IFIFO:
|
|
926
|
+
case constants.S_IFSOCK:
|
|
884
927
|
default:
|
|
885
928
|
throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm');
|
|
886
929
|
}
|
|
@@ -2,7 +2,17 @@ import { EventEmitter } from 'eventemitter3';
|
|
|
2
2
|
import type { EventEmitter as NodeEventEmitter } from 'node:events';
|
|
3
3
|
import type * as fs from 'node:fs';
|
|
4
4
|
import { ErrnoError } from '../error.js';
|
|
5
|
+
import { isStatsEqual, type Stats } from '../stats.js';
|
|
6
|
+
import { normalizePath } from '../utils.js';
|
|
7
|
+
import { dirname, basename } from './path.js';
|
|
8
|
+
import { statSync } from './sync.js';
|
|
5
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Base class for file system watchers.
|
|
12
|
+
* Provides event handling capabilities for watching file system changes.
|
|
13
|
+
*
|
|
14
|
+
* @template TEvents The type of events emitted by the watcher.
|
|
15
|
+
*/
|
|
6
16
|
class Watcher<TEvents extends Record<string, unknown[]> = Record<string, unknown[]>> extends EventEmitter<TEvents> implements NodeEventEmitter {
|
|
7
17
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
8
18
|
public off<T extends EventEmitter.EventNames<TEvents>>(event: T, fn?: (...args: any[]) => void, context?: any, once?: boolean): this {
|
|
@@ -14,24 +24,28 @@ class Watcher<TEvents extends Record<string, unknown[]> = Record<string, unknown
|
|
|
14
24
|
}
|
|
15
25
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
16
26
|
|
|
27
|
+
public constructor(public readonly path: string) {
|
|
28
|
+
super();
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
public setMaxListeners(): never {
|
|
18
|
-
throw ErrnoError.With('
|
|
32
|
+
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.setMaxListeners');
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
public getMaxListeners(): never {
|
|
22
|
-
throw ErrnoError.With('
|
|
36
|
+
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.getMaxListeners');
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
public prependListener(): never {
|
|
26
|
-
throw ErrnoError.With('
|
|
40
|
+
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.prependListener');
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
public prependOnceListener(): never {
|
|
30
|
-
throw ErrnoError.With('
|
|
44
|
+
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.prependOnceListener');
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
public rawListeners(): never {
|
|
34
|
-
throw ErrnoError.With('
|
|
48
|
+
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.rawListeners');
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
public ref(): this {
|
|
@@ -44,7 +58,9 @@ class Watcher<TEvents extends Record<string, unknown[]> = Record<string, unknown
|
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
/**
|
|
47
|
-
*
|
|
61
|
+
* Watches for changes on the file system.
|
|
62
|
+
*
|
|
63
|
+
* @template T The type of the filename, either `string` or `Buffer`.
|
|
48
64
|
*/
|
|
49
65
|
export class FSWatcher<T extends string | Buffer = string | Buffer>
|
|
50
66
|
extends Watcher<{
|
|
@@ -54,10 +70,124 @@ export class FSWatcher<T extends string | Buffer = string | Buffer>
|
|
|
54
70
|
}>
|
|
55
71
|
implements fs.FSWatcher
|
|
56
72
|
{
|
|
57
|
-
public constructor(
|
|
58
|
-
|
|
73
|
+
public constructor(
|
|
74
|
+
path: string,
|
|
75
|
+
public readonly options: fs.WatchOptions
|
|
76
|
+
) {
|
|
77
|
+
super(path);
|
|
78
|
+
addWatcher(path.toString(), this);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public close(): void {
|
|
82
|
+
super.emit('close');
|
|
83
|
+
removeWatcher(this.path.toString(), this);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public [Symbol.dispose](): void {
|
|
87
|
+
this.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Watches for changes to a file's stats.
|
|
93
|
+
*
|
|
94
|
+
* Instances of `StatWatcher` are used by `fs.watchFile()` to monitor changes to a file's statistics.
|
|
95
|
+
*/
|
|
96
|
+
export class StatWatcher
|
|
97
|
+
extends Watcher<{
|
|
98
|
+
change: [current: Stats, previous: Stats];
|
|
99
|
+
close: [];
|
|
100
|
+
error: [error: Error];
|
|
101
|
+
}>
|
|
102
|
+
implements fs.StatWatcher
|
|
103
|
+
{
|
|
104
|
+
private intervalId?: NodeJS.Timeout | number;
|
|
105
|
+
private previous?: Stats;
|
|
106
|
+
|
|
107
|
+
public constructor(
|
|
108
|
+
path: string,
|
|
109
|
+
private options: { persistent?: boolean; interval?: number }
|
|
110
|
+
) {
|
|
111
|
+
super(path);
|
|
112
|
+
this.start();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
protected onInterval() {
|
|
116
|
+
try {
|
|
117
|
+
const current = statSync(this.path);
|
|
118
|
+
if (!isStatsEqual(this.previous!, current)) {
|
|
119
|
+
this.emit('change', current, this.previous!);
|
|
120
|
+
this.previous = current;
|
|
121
|
+
}
|
|
122
|
+
} catch (e) {
|
|
123
|
+
this.emit('error', e as Error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
protected start() {
|
|
128
|
+
const interval = this.options.interval || 5000;
|
|
129
|
+
try {
|
|
130
|
+
this.previous = statSync(this.path);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
this.emit('error', e as Error);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.intervalId = setInterval(this.onInterval.bind(this), interval);
|
|
136
|
+
if (!this.options.persistent && typeof this.intervalId == 'object') {
|
|
137
|
+
this.intervalId.unref();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
public stop() {
|
|
145
|
+
if (this.intervalId) {
|
|
146
|
+
clearInterval(this.intervalId);
|
|
147
|
+
this.intervalId = undefined;
|
|
148
|
+
}
|
|
149
|
+
this.removeAllListeners();
|
|
59
150
|
}
|
|
60
|
-
public close(): void {}
|
|
61
151
|
}
|
|
62
152
|
|
|
63
|
-
|
|
153
|
+
const watchers: Map<string, Set<FSWatcher>> = new Map();
|
|
154
|
+
|
|
155
|
+
export function addWatcher(path: string, watcher: FSWatcher) {
|
|
156
|
+
const normalizedPath = normalizePath(path);
|
|
157
|
+
if (!watchers.has(normalizedPath)) {
|
|
158
|
+
watchers.set(normalizedPath, new Set());
|
|
159
|
+
}
|
|
160
|
+
watchers.get(normalizedPath)!.add(watcher);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function removeWatcher(path: string, watcher: FSWatcher) {
|
|
164
|
+
const normalizedPath = normalizePath(path);
|
|
165
|
+
if (watchers.has(normalizedPath)) {
|
|
166
|
+
watchers.get(normalizedPath)!.delete(watcher);
|
|
167
|
+
if (watchers.get(normalizedPath)!.size === 0) {
|
|
168
|
+
watchers.delete(normalizedPath);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function emitChange(eventType: fs.WatchEventType, filename: string) {
|
|
174
|
+
let normalizedFilename: string = normalizePath(filename);
|
|
175
|
+
// Notify watchers on the specific file
|
|
176
|
+
if (watchers.has(normalizedFilename)) {
|
|
177
|
+
for (const watcher of watchers.get(normalizedFilename)!) {
|
|
178
|
+
watcher.emit('change', eventType, basename(filename));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Notify watchers on parent directories if they are watching recursively
|
|
183
|
+
let parent = dirname(normalizedFilename);
|
|
184
|
+
while (parent !== normalizedFilename && parent !== '/') {
|
|
185
|
+
if (watchers.has(parent)) {
|
|
186
|
+
for (const watcher of watchers.get(parent)!) {
|
|
187
|
+
watcher.emit('change', eventType, basename(filename));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
normalizedFilename = parent;
|
|
191
|
+
parent = dirname(parent);
|
|
192
|
+
}
|
|
193
|
+
}
|
package/src/error.ts
CHANGED