@zenfs/core 1.2.8 → 1.2.9
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/store/fs.js +10 -10
- package/dist/config.js +32 -9
- package/dist/credentials.d.ts +3 -0
- package/dist/credentials.js +3 -0
- package/dist/devices.d.ts +4 -0
- package/dist/devices.js +9 -0
- package/dist/emulation/promises.js +3 -13
- package/dist/emulation/shared.d.ts +3 -0
- package/dist/emulation/shared.js +3 -0
- package/dist/emulation/sync.js +3 -14
- package/dist/mixins/async.js +2 -2
- package/package.json +2 -1
- package/src/backends/store/fs.ts +18 -18
- package/src/config.ts +33 -10
- package/src/credentials.ts +3 -0
- package/src/devices.ts +10 -0
- package/src/emulation/promises.ts +3 -14
- package/src/emulation/shared.ts +3 -0
- package/src/emulation/sync.ts +3 -15
- package/src/mixins/async.ts +2 -2
- package/tests/fs/directory.test.ts +83 -1
- package/tests/fs/permissions.test.ts +45 -2
- package/tests/mounts.test.ts +18 -0
- package/tests/fs/chmod.test.ts +0 -44
- package/tests/fs/readdir.test.ts +0 -87
|
@@ -642,17 +642,17 @@ export class StoreFS extends FileSystem {
|
|
|
642
642
|
async commitNew(path, type, mode, data) {
|
|
643
643
|
const env_15 = { stack: [], error: void 0, hasError: false };
|
|
644
644
|
try {
|
|
645
|
-
const tx = __addDisposableResource(env_15, this.store.transaction(), true);
|
|
646
|
-
const parentPath = dirname(path), parent = await this.findINode(tx, parentPath);
|
|
647
|
-
const fname = basename(path), listing = await this.getDirListing(tx, parent, parentPath);
|
|
648
645
|
/*
|
|
649
646
|
The root always exists.
|
|
650
647
|
If we don't check this prior to taking steps below,
|
|
651
|
-
we will create a file with name '' in root
|
|
648
|
+
we will create a file with name '' in root if path is '/'.
|
|
652
649
|
*/
|
|
653
|
-
if (path
|
|
650
|
+
if (path == '/') {
|
|
654
651
|
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
|
655
652
|
}
|
|
653
|
+
const tx = __addDisposableResource(env_15, this.store.transaction(), true);
|
|
654
|
+
const parentPath = dirname(path), parent = await this.findINode(tx, parentPath);
|
|
655
|
+
const fname = basename(path), listing = await this.getDirListing(tx, parent, parentPath);
|
|
656
656
|
// Check if file already exists.
|
|
657
657
|
if (listing[fname]) {
|
|
658
658
|
await tx.abort();
|
|
@@ -693,17 +693,17 @@ export class StoreFS extends FileSystem {
|
|
|
693
693
|
commitNewSync(path, type, mode, data = new Uint8Array()) {
|
|
694
694
|
const env_16 = { stack: [], error: void 0, hasError: false };
|
|
695
695
|
try {
|
|
696
|
-
const tx = __addDisposableResource(env_16, this.store.transaction(), false);
|
|
697
|
-
const parentPath = dirname(path), parent = this.findINodeSync(tx, parentPath);
|
|
698
|
-
const fname = basename(path), listing = this.getDirListingSync(tx, parent, parentPath);
|
|
699
696
|
/*
|
|
700
697
|
The root always exists.
|
|
701
698
|
If we don't check this prior to taking steps below,
|
|
702
|
-
we will create a file with name '' in root
|
|
699
|
+
we will create a file with name '' in root if path is '/'.
|
|
703
700
|
*/
|
|
704
|
-
if (path
|
|
701
|
+
if (path == '/') {
|
|
705
702
|
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
|
706
703
|
}
|
|
704
|
+
const tx = __addDisposableResource(env_16, this.store.transaction(), false);
|
|
705
|
+
const parentPath = dirname(path), parent = this.findINodeSync(tx, parentPath);
|
|
706
|
+
const fname = basename(path), listing = this.getDirListingSync(tx, parent, parentPath);
|
|
707
707
|
// Check if file already exists.
|
|
708
708
|
if (listing[fname]) {
|
|
709
709
|
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
package/dist/config.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
|
|
2
2
|
import { credentials } from './credentials.js';
|
|
3
|
-
import { DeviceFS
|
|
3
|
+
import { DeviceFS } from './devices.js';
|
|
4
4
|
import * as cache from './emulation/cache.js';
|
|
5
|
-
import * as fs from './emulation/index.js';
|
|
6
5
|
import { config } from './emulation/config.js';
|
|
6
|
+
import * as fs from './emulation/index.js';
|
|
7
7
|
import { Errno, ErrnoError } from './error.js';
|
|
8
8
|
import { FileSystem } from './filesystem.js';
|
|
9
9
|
function isMountConfig(arg) {
|
|
@@ -60,6 +60,26 @@ export async function configureSingle(configuration) {
|
|
|
60
60
|
fs.umount('/');
|
|
61
61
|
fs.mount('/', resolved);
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Like `fs.mount`, but it also creates missing directories.
|
|
65
|
+
* @privateRemarks
|
|
66
|
+
* This is implemented as a separate function to avoid a circular dependency between emulation/shared.ts and other emulation layer files.
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
async function mount(path, mount) {
|
|
70
|
+
if (path == '/') {
|
|
71
|
+
fs.mount(path, mount);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const stats = await fs.promises.stat(path).catch(() => null);
|
|
75
|
+
if (!stats) {
|
|
76
|
+
await fs.promises.mkdir(path, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
else if (!stats.isDirectory()) {
|
|
79
|
+
throw ErrnoError.With('ENOTDIR', path, 'configure');
|
|
80
|
+
}
|
|
81
|
+
fs.mount(path, mount);
|
|
82
|
+
}
|
|
63
83
|
/**
|
|
64
84
|
* Configures ZenFS with `configuration`
|
|
65
85
|
* @see Configuration
|
|
@@ -74,16 +94,15 @@ export async function configure(configuration) {
|
|
|
74
94
|
config.syncImmediately = !configuration.onlySyncOnClose;
|
|
75
95
|
if (configuration.addDevices) {
|
|
76
96
|
const devfs = new DeviceFS();
|
|
77
|
-
devfs.
|
|
78
|
-
devfs.createDevice('/zero', zeroDevice);
|
|
79
|
-
devfs.createDevice('/full', fullDevice);
|
|
80
|
-
devfs.createDevice('/random', randomDevice);
|
|
97
|
+
devfs.addDefaults();
|
|
81
98
|
await devfs.ready();
|
|
82
|
-
|
|
99
|
+
await mount('/dev', devfs);
|
|
83
100
|
}
|
|
84
101
|
if (!configuration.mounts) {
|
|
85
102
|
return;
|
|
86
103
|
}
|
|
104
|
+
const toMount = [];
|
|
105
|
+
let unmountRoot = false;
|
|
87
106
|
for (const [point, mountConfig] of Object.entries(configuration.mounts)) {
|
|
88
107
|
if (!point.startsWith('/')) {
|
|
89
108
|
throw new ErrnoError(Errno.EINVAL, 'Mount points must have absolute paths');
|
|
@@ -91,7 +110,11 @@ export async function configure(configuration) {
|
|
|
91
110
|
if (isBackendConfig(mountConfig)) {
|
|
92
111
|
mountConfig.disableAsyncCache ?? (mountConfig.disableAsyncCache = configuration.disableAsyncCache || false);
|
|
93
112
|
}
|
|
94
|
-
|
|
113
|
+
if (point == '/')
|
|
114
|
+
unmountRoot = true;
|
|
115
|
+
toMount.push([point, await resolveMountConfig(mountConfig)]);
|
|
95
116
|
}
|
|
96
|
-
|
|
117
|
+
if (unmountRoot)
|
|
118
|
+
fs.umount('/');
|
|
119
|
+
await Promise.all(toMount.map(([point, fs]) => mount(point, fs)));
|
|
97
120
|
}
|
package/dist/credentials.d.ts
CHANGED
package/dist/credentials.js
CHANGED
package/dist/devices.d.ts
CHANGED
|
@@ -124,6 +124,10 @@ export declare class DeviceFS extends StoreFS<InMemoryStore> {
|
|
|
124
124
|
* Creates a new device at `path` relative to the `DeviceFS` root.
|
|
125
125
|
*/
|
|
126
126
|
createDevice<TData = any>(path: string, driver: DeviceDriver<TData>): Device<TData | Record<string, never>>;
|
|
127
|
+
/**
|
|
128
|
+
* Adds default devices
|
|
129
|
+
*/
|
|
130
|
+
addDefaults(): void;
|
|
127
131
|
constructor();
|
|
128
132
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
129
133
|
renameSync(oldPath: string, newPath: string): void;
|
package/dist/devices.js
CHANGED
|
@@ -168,6 +168,15 @@ export class DeviceFS extends StoreFS {
|
|
|
168
168
|
this.devices.set(path, dev);
|
|
169
169
|
return dev;
|
|
170
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Adds default devices
|
|
173
|
+
*/
|
|
174
|
+
addDefaults() {
|
|
175
|
+
this.createDevice('/null', nullDevice);
|
|
176
|
+
this.createDevice('/zero', zeroDevice);
|
|
177
|
+
this.createDevice('/full', fullDevice);
|
|
178
|
+
this.createDevice('/random', randomDevice);
|
|
179
|
+
}
|
|
171
180
|
constructor() {
|
|
172
181
|
super(new InMemoryStore('devfs'));
|
|
173
182
|
this.devices = new Map();
|
|
@@ -53,11 +53,11 @@ import '../polyfills.js';
|
|
|
53
53
|
import { BigIntStats } from '../stats.js';
|
|
54
54
|
import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
|
|
55
55
|
import * as cache from './cache.js';
|
|
56
|
+
import { config } from './config.js';
|
|
56
57
|
import * as constants from './constants.js';
|
|
57
58
|
import { Dir, Dirent } from './dir.js';
|
|
58
59
|
import { dirname, join, parse } from './path.js';
|
|
59
|
-
import { _statfs, fd2file, fdMap, file2fd, fixError,
|
|
60
|
-
import { config } from './config.js';
|
|
60
|
+
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount } from './shared.js';
|
|
61
61
|
import { ReadStream, WriteStream } from './streams.js';
|
|
62
62
|
import { FSWatcher, emitChange } from './watchers.js';
|
|
63
63
|
export * as constants from './constants.js';
|
|
@@ -585,7 +585,7 @@ export async function rmdir(path) {
|
|
|
585
585
|
try {
|
|
586
586
|
const stats = await (cache.getStats(path) || fs.stat(resolved));
|
|
587
587
|
if (!stats) {
|
|
588
|
-
throw ErrnoError.With('ENOENT', path, '
|
|
588
|
+
throw ErrnoError.With('ENOENT', path, 'rmdir');
|
|
589
589
|
}
|
|
590
590
|
if (!stats.isDirectory()) {
|
|
591
591
|
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
@@ -655,16 +655,6 @@ export async function readdir(path, options) {
|
|
|
655
655
|
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
656
656
|
}
|
|
657
657
|
const entries = await fs.readdir(resolved).catch(handleError);
|
|
658
|
-
for (const point of mounts.keys()) {
|
|
659
|
-
if (point.startsWith(path)) {
|
|
660
|
-
const entry = point.slice(path.length);
|
|
661
|
-
if (entry.includes('/') || entry.length == 0) {
|
|
662
|
-
// ignore FSs mounted in subdirectories and any FS mounted to `path`.
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
entries.push(entry);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
658
|
const values = [];
|
|
669
659
|
const addEntry = async (entry) => {
|
|
670
660
|
let entryStats;
|
|
@@ -38,6 +38,9 @@ export declare function fixPaths(text: string, paths: Record<string, string>): s
|
|
|
38
38
|
* @hidden
|
|
39
39
|
*/
|
|
40
40
|
export declare function fixError<E extends ErrnoError>(e: E, paths: Record<string, string>): E;
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated
|
|
43
|
+
*/
|
|
41
44
|
export declare function mountObject(mounts: MountObject): void;
|
|
42
45
|
/**
|
|
43
46
|
* @hidden
|
package/dist/emulation/shared.js
CHANGED
package/dist/emulation/sync.js
CHANGED
|
@@ -50,13 +50,13 @@ 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
52
|
import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
|
|
53
|
+
import * as cache from './cache.js';
|
|
54
|
+
import { config } from './config.js';
|
|
53
55
|
import * as constants from './constants.js';
|
|
54
56
|
import { Dir, Dirent } from './dir.js';
|
|
55
57
|
import { dirname, join, parse } from './path.js';
|
|
56
|
-
import { _statfs, fd2file, fdMap, file2fd, fixError,
|
|
57
|
-
import { config } from './config.js';
|
|
58
|
+
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount } from './shared.js';
|
|
58
59
|
import { emitChange } from './watchers.js';
|
|
59
|
-
import * as cache from './cache.js';
|
|
60
60
|
export function renameSync(oldPath, newPath) {
|
|
61
61
|
oldPath = normalizePath(oldPath);
|
|
62
62
|
newPath = normalizePath(newPath);
|
|
@@ -459,17 +459,6 @@ export function readdirSync(path, options) {
|
|
|
459
459
|
catch (e) {
|
|
460
460
|
throw fixError(e, { [resolved]: path });
|
|
461
461
|
}
|
|
462
|
-
for (const mount of mounts.keys()) {
|
|
463
|
-
if (!mount.startsWith(path)) {
|
|
464
|
-
continue;
|
|
465
|
-
}
|
|
466
|
-
const entry = mount.slice(path.length);
|
|
467
|
-
if (entry.includes('/') || entry.length == 0) {
|
|
468
|
-
// ignore FSs mounted in subdirectories and any FS mounted to `path`.
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
entries.push(entry);
|
|
472
|
-
}
|
|
473
462
|
// Iterate over entries and handle recursive case if needed
|
|
474
463
|
const values = [];
|
|
475
464
|
for (const entry of entries) {
|
package/dist/mixins/async.js
CHANGED
|
@@ -90,8 +90,8 @@ export function Async(FS) {
|
|
|
90
90
|
const sync = this._sync['store'].transaction();
|
|
91
91
|
const async = this['store'].transaction();
|
|
92
92
|
const promises = [];
|
|
93
|
-
for (const key of
|
|
94
|
-
promises.push(async.
|
|
93
|
+
for (const key of await async.keys()) {
|
|
94
|
+
promises.push(async.get(key).then(data => sync.setSync(key, data)));
|
|
95
95
|
}
|
|
96
96
|
await Promise.all(promises);
|
|
97
97
|
this._isInitialized = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"lint": "eslint src tests",
|
|
59
59
|
"test:common": "tsx --test --experimental-test-coverage 'tests/**/!(fs)/*.test.ts' 'tests/*.test.ts'",
|
|
60
60
|
"test": "tsx --test --experimental-test-coverage",
|
|
61
|
+
"pretest": "npm run build",
|
|
61
62
|
"build": "tsc -p tsconfig.json",
|
|
62
63
|
"build:docs": "typedoc",
|
|
63
64
|
"dev": "npm run build -- --watch",
|
package/src/backends/store/fs.ts
CHANGED
|
@@ -534,22 +534,22 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
534
534
|
* @param data The data to store at the file's data node.
|
|
535
535
|
*/
|
|
536
536
|
private async commitNew(path: string, type: FileType, mode: number, data: Uint8Array): Promise<Inode> {
|
|
537
|
-
await using tx = this.store.transaction();
|
|
538
|
-
const parentPath = dirname(path),
|
|
539
|
-
parent = await this.findINode(tx, parentPath);
|
|
540
|
-
|
|
541
|
-
const fname = basename(path),
|
|
542
|
-
listing = await this.getDirListing(tx, parent, parentPath);
|
|
543
|
-
|
|
544
537
|
/*
|
|
545
538
|
The root always exists.
|
|
546
539
|
If we don't check this prior to taking steps below,
|
|
547
|
-
we will create a file with name '' in root
|
|
540
|
+
we will create a file with name '' in root if path is '/'.
|
|
548
541
|
*/
|
|
549
|
-
if (path
|
|
542
|
+
if (path == '/') {
|
|
550
543
|
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
|
551
544
|
}
|
|
552
545
|
|
|
546
|
+
await using tx = this.store.transaction();
|
|
547
|
+
const parentPath = dirname(path),
|
|
548
|
+
parent = await this.findINode(tx, parentPath);
|
|
549
|
+
|
|
550
|
+
const fname = basename(path),
|
|
551
|
+
listing = await this.getDirListing(tx, parent, parentPath);
|
|
552
|
+
|
|
553
553
|
// Check if file already exists.
|
|
554
554
|
if (listing[fname]) {
|
|
555
555
|
await tx.abort();
|
|
@@ -581,22 +581,22 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
581
581
|
* @return The Inode for the new file.
|
|
582
582
|
*/
|
|
583
583
|
protected commitNewSync(path: string, type: FileType, mode: number, data: Uint8Array = new Uint8Array()): Inode {
|
|
584
|
-
using tx = this.store.transaction();
|
|
585
|
-
const parentPath = dirname(path),
|
|
586
|
-
parent = this.findINodeSync(tx, parentPath);
|
|
587
|
-
|
|
588
|
-
const fname = basename(path),
|
|
589
|
-
listing = this.getDirListingSync(tx, parent, parentPath);
|
|
590
|
-
|
|
591
584
|
/*
|
|
592
585
|
The root always exists.
|
|
593
586
|
If we don't check this prior to taking steps below,
|
|
594
|
-
we will create a file with name '' in root
|
|
587
|
+
we will create a file with name '' in root if path is '/'.
|
|
595
588
|
*/
|
|
596
|
-
if (path
|
|
589
|
+
if (path == '/') {
|
|
597
590
|
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
|
598
591
|
}
|
|
599
592
|
|
|
593
|
+
using tx = this.store.transaction();
|
|
594
|
+
const parentPath = dirname(path),
|
|
595
|
+
parent = this.findINodeSync(tx, parentPath);
|
|
596
|
+
|
|
597
|
+
const fname = basename(path),
|
|
598
|
+
listing = this.getDirListingSync(tx, parent, parentPath);
|
|
599
|
+
|
|
600
600
|
// Check if file already exists.
|
|
601
601
|
if (listing[fname]) {
|
|
602
602
|
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
package/src/config.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js';
|
|
2
2
|
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
|
|
3
3
|
import { credentials } from './credentials.js';
|
|
4
|
-
import { DeviceFS
|
|
4
|
+
import { DeviceFS } from './devices.js';
|
|
5
5
|
import * as cache from './emulation/cache.js';
|
|
6
|
+
import { config } from './emulation/config.js';
|
|
6
7
|
import * as fs from './emulation/index.js';
|
|
7
8
|
import type { AbsolutePath } from './emulation/path.js';
|
|
8
|
-
import { type MountObject } from './emulation/shared.js';
|
|
9
|
-
import { config } from './emulation/config.js';
|
|
10
9
|
import { Errno, ErrnoError } from './error.js';
|
|
11
10
|
import { FileSystem } from './filesystem.js';
|
|
12
11
|
|
|
@@ -151,6 +150,27 @@ export async function configureSingle<T extends Backend>(configuration: MountCon
|
|
|
151
150
|
fs.mount('/', resolved);
|
|
152
151
|
}
|
|
153
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Like `fs.mount`, but it also creates missing directories.
|
|
155
|
+
* @privateRemarks
|
|
156
|
+
* This is implemented as a separate function to avoid a circular dependency between emulation/shared.ts and other emulation layer files.
|
|
157
|
+
* @internal
|
|
158
|
+
*/
|
|
159
|
+
async function mount(path: string, mount: FileSystem): Promise<void> {
|
|
160
|
+
if (path == '/') {
|
|
161
|
+
fs.mount(path, mount);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const stats = await fs.promises.stat(path).catch(() => null);
|
|
166
|
+
if (!stats) {
|
|
167
|
+
await fs.promises.mkdir(path, { recursive: true });
|
|
168
|
+
} else if (!stats.isDirectory()) {
|
|
169
|
+
throw ErrnoError.With('ENOTDIR', path, 'configure');
|
|
170
|
+
}
|
|
171
|
+
fs.mount(path, mount);
|
|
172
|
+
}
|
|
173
|
+
|
|
154
174
|
/**
|
|
155
175
|
* Configures ZenFS with `configuration`
|
|
156
176
|
* @see Configuration
|
|
@@ -168,18 +188,18 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
|
|
|
168
188
|
|
|
169
189
|
if (configuration.addDevices) {
|
|
170
190
|
const devfs = new DeviceFS();
|
|
171
|
-
devfs.
|
|
172
|
-
devfs.createDevice('/zero', zeroDevice);
|
|
173
|
-
devfs.createDevice('/full', fullDevice);
|
|
174
|
-
devfs.createDevice('/random', randomDevice);
|
|
191
|
+
devfs.addDefaults();
|
|
175
192
|
await devfs.ready();
|
|
176
|
-
|
|
193
|
+
await mount('/dev', devfs);
|
|
177
194
|
}
|
|
178
195
|
|
|
179
196
|
if (!configuration.mounts) {
|
|
180
197
|
return;
|
|
181
198
|
}
|
|
182
199
|
|
|
200
|
+
const toMount: [string, FileSystem][] = [];
|
|
201
|
+
let unmountRoot = false;
|
|
202
|
+
|
|
183
203
|
for (const [point, mountConfig] of Object.entries(configuration.mounts)) {
|
|
184
204
|
if (!point.startsWith('/')) {
|
|
185
205
|
throw new ErrnoError(Errno.EINVAL, 'Mount points must have absolute paths');
|
|
@@ -189,8 +209,11 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
|
|
|
189
209
|
mountConfig.disableAsyncCache ??= configuration.disableAsyncCache || false;
|
|
190
210
|
}
|
|
191
211
|
|
|
192
|
-
|
|
212
|
+
if (point == '/') unmountRoot = true;
|
|
213
|
+
toMount.push([point, await resolveMountConfig(mountConfig)]);
|
|
193
214
|
}
|
|
194
215
|
|
|
195
|
-
fs.
|
|
216
|
+
if (unmountRoot) fs.umount('/');
|
|
217
|
+
|
|
218
|
+
await Promise.all(toMount.map(([point, fs]) => mount(point, fs)));
|
|
196
219
|
}
|
package/src/credentials.ts
CHANGED
package/src/devices.ts
CHANGED
|
@@ -246,6 +246,16 @@ export class DeviceFS extends StoreFS<InMemoryStore> {
|
|
|
246
246
|
return dev;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Adds default devices
|
|
251
|
+
*/
|
|
252
|
+
public addDefaults(): void {
|
|
253
|
+
this.createDevice('/null', nullDevice);
|
|
254
|
+
this.createDevice('/zero', zeroDevice);
|
|
255
|
+
this.createDevice('/full', fullDevice);
|
|
256
|
+
this.createDevice('/random', randomDevice);
|
|
257
|
+
}
|
|
258
|
+
|
|
249
259
|
public constructor() {
|
|
250
260
|
super(new InMemoryStore('devfs'));
|
|
251
261
|
}
|
|
@@ -13,11 +13,11 @@ import '../polyfills.js';
|
|
|
13
13
|
import { BigIntStats, type Stats } from '../stats.js';
|
|
14
14
|
import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
|
|
15
15
|
import * as cache from './cache.js';
|
|
16
|
+
import { config } from './config.js';
|
|
16
17
|
import * as constants from './constants.js';
|
|
17
18
|
import { Dir, Dirent } from './dir.js';
|
|
18
19
|
import { dirname, join, parse } from './path.js';
|
|
19
|
-
import { _statfs, fd2file, fdMap, file2fd, fixError,
|
|
20
|
-
import { config } from './config.js';
|
|
20
|
+
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
|
|
21
21
|
import { ReadStream, WriteStream } from './streams.js';
|
|
22
22
|
import { FSWatcher, emitChange } from './watchers.js';
|
|
23
23
|
export * as constants from './constants.js';
|
|
@@ -625,7 +625,7 @@ export async function rmdir(path: fs.PathLike): Promise<void> {
|
|
|
625
625
|
try {
|
|
626
626
|
const stats = await (cache.getStats(path) || fs.stat(resolved));
|
|
627
627
|
if (!stats) {
|
|
628
|
-
throw ErrnoError.With('ENOENT', path, '
|
|
628
|
+
throw ErrnoError.With('ENOENT', path, 'rmdir');
|
|
629
629
|
}
|
|
630
630
|
if (!stats.isDirectory()) {
|
|
631
631
|
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
@@ -736,17 +736,6 @@ export async function readdir(
|
|
|
736
736
|
|
|
737
737
|
const entries = await fs.readdir(resolved).catch(handleError);
|
|
738
738
|
|
|
739
|
-
for (const point of mounts.keys()) {
|
|
740
|
-
if (point.startsWith(path)) {
|
|
741
|
-
const entry = point.slice(path.length);
|
|
742
|
-
if (entry.includes('/') || entry.length == 0) {
|
|
743
|
-
// ignore FSs mounted in subdirectories and any FS mounted to `path`.
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
|
-
entries.push(entry);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
739
|
const values: (string | Dirent | Buffer)[] = [];
|
|
751
740
|
const addEntry = async (entry: string) => {
|
|
752
741
|
let entryStats: Stats | undefined;
|
package/src/emulation/shared.ts
CHANGED
package/src/emulation/sync.ts
CHANGED
|
@@ -6,13 +6,13 @@ import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWrit
|
|
|
6
6
|
import type { FileContents } from '../filesystem.js';
|
|
7
7
|
import { BigIntStats, type Stats } from '../stats.js';
|
|
8
8
|
import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
|
|
9
|
+
import * as cache from './cache.js';
|
|
10
|
+
import { config } from './config.js';
|
|
9
11
|
import * as constants from './constants.js';
|
|
10
12
|
import { Dir, Dirent } from './dir.js';
|
|
11
13
|
import { dirname, join, parse } from './path.js';
|
|
12
|
-
import { _statfs, fd2file, fdMap, file2fd, fixError,
|
|
13
|
-
import { config } from './config.js';
|
|
14
|
+
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
|
|
14
15
|
import { emitChange } from './watchers.js';
|
|
15
|
-
import * as cache from './cache.js';
|
|
16
16
|
|
|
17
17
|
export function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void {
|
|
18
18
|
oldPath = normalizePath(oldPath);
|
|
@@ -472,18 +472,6 @@ export function readdirSync(
|
|
|
472
472
|
throw fixError(e as ErrnoError, { [resolved]: path });
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
for (const mount of mounts.keys()) {
|
|
476
|
-
if (!mount.startsWith(path)) {
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
const entry = mount.slice(path.length);
|
|
480
|
-
if (entry.includes('/') || entry.length == 0) {
|
|
481
|
-
// ignore FSs mounted in subdirectories and any FS mounted to `path`.
|
|
482
|
-
continue;
|
|
483
|
-
}
|
|
484
|
-
entries.push(entry);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
475
|
// Iterate over entries and handle recursive case if needed
|
|
488
476
|
const values: (string | Dirent | Buffer)[] = [];
|
|
489
477
|
for (const entry of entries) {
|
package/src/mixins/async.ts
CHANGED
|
@@ -80,8 +80,8 @@ export function Async<T extends typeof FileSystem>(
|
|
|
80
80
|
const async = (this as StoreFS<Store>)['store'].transaction();
|
|
81
81
|
|
|
82
82
|
const promises = [];
|
|
83
|
-
for (const key of
|
|
84
|
-
promises.push(async.
|
|
83
|
+
for (const key of await async.keys()) {
|
|
84
|
+
promises.push(async.get(key).then(data => sync.setSync(key, data)));
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
await Promise.all(promises);
|
|
@@ -3,7 +3,22 @@ import { suite, test } from 'node:test';
|
|
|
3
3
|
import { ErrnoError } from '../../dist/error.js';
|
|
4
4
|
import { fs } from '../common.js';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const testDir = 'test-dir';
|
|
7
|
+
const testFiles = ['file1.txt', 'file2.txt', 'file3.txt'];
|
|
8
|
+
const testDirectories = ['subdir1', 'subdir2'];
|
|
9
|
+
|
|
10
|
+
await fs.promises.mkdir(testDir);
|
|
11
|
+
for (const file of testFiles) {
|
|
12
|
+
await fs.promises.writeFile(`${testDir}/${file}`, 'Sample content');
|
|
13
|
+
}
|
|
14
|
+
for (const dir of testDirectories) {
|
|
15
|
+
await fs.promises.mkdir(`${testDir}/${dir}`);
|
|
16
|
+
for (const file of ['file4.txt', 'file5.txt']) {
|
|
17
|
+
await fs.promises.writeFile(`${testDir}/${dir}/${file}`, 'Sample content');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
suite('Directories', () => {
|
|
7
22
|
test('mkdir', async () => {
|
|
8
23
|
await fs.promises.mkdir('/one', 0o755);
|
|
9
24
|
assert(await fs.promises.exists('/one'));
|
|
@@ -126,4 +141,71 @@ suite('Directory', () => {
|
|
|
126
141
|
|
|
127
142
|
fs.rmSync('/rmDirRecusrively', { recursive: true });
|
|
128
143
|
});
|
|
144
|
+
|
|
145
|
+
test('readdir returns files and directories', async () => {
|
|
146
|
+
const dirents = await fs.promises.readdir(testDir, { withFileTypes: true });
|
|
147
|
+
const files = dirents.filter(dirent => dirent.isFile()).map(dirent => dirent.name);
|
|
148
|
+
const dirs = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
|
|
149
|
+
|
|
150
|
+
assert(testFiles.every(file => files.includes(file)));
|
|
151
|
+
assert(testDirectories.every(dir => dirs.includes(dir)));
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('readdirSync returns files and directories', () => {
|
|
155
|
+
const dirents = fs.readdirSync(testDir, { withFileTypes: true });
|
|
156
|
+
const files = dirents.filter(dirent => dirent.isFile()).map(dirent => dirent.name);
|
|
157
|
+
const dirs = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
|
|
158
|
+
|
|
159
|
+
assert(testFiles.every(file => files.includes(file)));
|
|
160
|
+
assert(testDirectories.every(dir => dirs.includes(dir)));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('readdir returns Dirent objects', async () => {
|
|
164
|
+
const dirents = await fs.promises.readdir(testDir, { withFileTypes: true });
|
|
165
|
+
assert(dirents[0] instanceof fs.Dirent);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('readdirSync returns Dirent objects', () => {
|
|
169
|
+
const dirents = fs.readdirSync(testDir, { withFileTypes: true });
|
|
170
|
+
assert(dirents[0] instanceof fs.Dirent);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('readdir works without withFileTypes option', async () => {
|
|
174
|
+
const files = await fs.promises.readdir(testDir);
|
|
175
|
+
assert(testFiles.every(entry => files.includes(entry)));
|
|
176
|
+
assert(testDirectories.every(entry => files.includes(entry)));
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('readdirSync works without withFileTypes option', () => {
|
|
180
|
+
const files = fs.readdirSync(testDir);
|
|
181
|
+
assert(testFiles.every(entry => files.includes(entry)));
|
|
182
|
+
assert(testDirectories.every(entry => files.includes(entry)));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('readdir returns files recursively', async () => {
|
|
186
|
+
const entries = await fs.promises.readdir(testDir, { recursive: true });
|
|
187
|
+
assert(entries.includes('file1.txt'));
|
|
188
|
+
assert(entries.includes('subdir1/file4.txt'));
|
|
189
|
+
assert(entries.includes('subdir2/file5.txt'));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('readdir returns Dirent recursively', async () => {
|
|
193
|
+
const entries = await fs.promises.readdir(testDir, { recursive: true, withFileTypes: true });
|
|
194
|
+
assert(entries.find(entry => entry.path === 'file1.txt'));
|
|
195
|
+
assert(entries.find(entry => entry.path === 'subdir1/file4.txt'));
|
|
196
|
+
assert(entries.find(entry => entry.path === 'subdir2/file5.txt'));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// New test for readdirSync with recursive: true
|
|
200
|
+
test('readdirSync returns files recursively', () => {
|
|
201
|
+
const entries = fs.readdirSync(testDir, { recursive: true });
|
|
202
|
+
assert(entries.includes('file1.txt'));
|
|
203
|
+
assert(entries.includes('subdir1/file4.txt'));
|
|
204
|
+
assert(entries.includes('subdir2/file5.txt'));
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('Cyrillic file names', () => {
|
|
208
|
+
fs.writeFileSync('/мой-файл.txt', 'HELLO!', 'utf-8');
|
|
209
|
+
assert(fs.readdirSync('/').includes('мой-файл.txt'));
|
|
210
|
+
});
|
|
129
211
|
});
|
|
@@ -1,12 +1,52 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
|
+
import { credentials } from '../../dist/credentials.js';
|
|
3
4
|
import { R_OK, W_OK, X_OK } from '../../dist/emulation/constants.js';
|
|
4
5
|
import { join } from '../../dist/emulation/path.js';
|
|
5
6
|
import { ErrnoError } from '../../dist/error.js';
|
|
6
7
|
import { encodeUTF8 } from '../../dist/utils.js';
|
|
7
8
|
import { fs } from '../common.js';
|
|
8
9
|
|
|
10
|
+
const asyncMode = 0o777;
|
|
11
|
+
const syncMode = 0o644;
|
|
12
|
+
const file = 'a.js';
|
|
13
|
+
|
|
9
14
|
suite('Permissions', () => {
|
|
15
|
+
test('chmod', async () => {
|
|
16
|
+
await fs.promises.chmod(file, asyncMode.toString(8));
|
|
17
|
+
|
|
18
|
+
const stats = await fs.promises.stat(file);
|
|
19
|
+
assert.equal(stats.mode & 0o777, asyncMode);
|
|
20
|
+
|
|
21
|
+
fs.chmodSync(file, syncMode);
|
|
22
|
+
assert.equal(fs.statSync(file).mode & 0o777, syncMode);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('fchmod', async () => {
|
|
26
|
+
const handle = await fs.promises.open(file, 'a', 0o644);
|
|
27
|
+
|
|
28
|
+
await handle.chmod(asyncMode);
|
|
29
|
+
const stats = await handle.stat();
|
|
30
|
+
|
|
31
|
+
assert.equal(stats.mode & 0o777, asyncMode);
|
|
32
|
+
|
|
33
|
+
fs.fchmodSync(handle.fd, syncMode);
|
|
34
|
+
assert.equal(fs.statSync(file).mode & 0o777, syncMode);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('lchmod', async () => {
|
|
38
|
+
const link = 'symbolic-link';
|
|
39
|
+
|
|
40
|
+
await fs.promises.symlink(file, link);
|
|
41
|
+
await fs.promises.lchmod(link, asyncMode);
|
|
42
|
+
|
|
43
|
+
const stats = await fs.promises.lstat(link);
|
|
44
|
+
assert.equal(stats.mode & 0o777, asyncMode);
|
|
45
|
+
|
|
46
|
+
fs.lchmodSync(link, syncMode);
|
|
47
|
+
assert.equal(fs.lstatSync(link).mode & 0o777, syncMode);
|
|
48
|
+
});
|
|
49
|
+
|
|
10
50
|
async function test_item(path: string): Promise<void> {
|
|
11
51
|
const stats = await fs.promises.stat(path).catch((error: ErrnoError) => {
|
|
12
52
|
assert(error instanceof ErrnoError);
|
|
@@ -27,7 +67,7 @@ suite('Permissions', () => {
|
|
|
27
67
|
|
|
28
68
|
if (stats.isDirectory()) {
|
|
29
69
|
for (const dir of await fs.promises.readdir(path)) {
|
|
30
|
-
await test_item(join(path, dir));
|
|
70
|
+
await test('Access controls: ' + join(path, dir), () => test_item(join(path, dir)));
|
|
31
71
|
}
|
|
32
72
|
} else {
|
|
33
73
|
await fs.promises.readFile(path).catch(checkError(R_OK));
|
|
@@ -48,5 +88,8 @@ suite('Permissions', () => {
|
|
|
48
88
|
assert(stats.hasAccess(R_OK));
|
|
49
89
|
}
|
|
50
90
|
|
|
51
|
-
|
|
91
|
+
const copy = { ...credentials };
|
|
92
|
+
Object.assign(credentials, { uid: 1000, gid: 1000, euid: 1000, egid: 1000 });
|
|
93
|
+
test('Access controls: /', () => test_item('/'));
|
|
94
|
+
Object.assign(credentials, copy);
|
|
52
95
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
3
|
+
import { configure } from '../src/config.js';
|
|
4
|
+
import * as fs from '../src/emulation/index.js';
|
|
5
|
+
import { InMemory } from '../src/index.js';
|
|
6
|
+
|
|
7
|
+
suite('Mounts', () => {
|
|
8
|
+
test('Mount in nested directory', async () => {
|
|
9
|
+
await configure({
|
|
10
|
+
mounts: {
|
|
11
|
+
'/nested/dir': InMemory,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
assert.deepStrictEqual(fs.readdirSync('/'), ['nested']);
|
|
16
|
+
assert.deepStrictEqual(fs.readdirSync('/nested'), ['dir']);
|
|
17
|
+
});
|
|
18
|
+
});
|
package/tests/fs/chmod.test.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert';
|
|
2
|
-
import { suite, test } from 'node:test';
|
|
3
|
-
import { fs } from '../common.js';
|
|
4
|
-
|
|
5
|
-
const asyncMode = 0o777;
|
|
6
|
-
const syncMode = 0o644;
|
|
7
|
-
const file = 'a.js';
|
|
8
|
-
|
|
9
|
-
suite('chmod tests', () => {
|
|
10
|
-
test('chmod', async () => {
|
|
11
|
-
await fs.promises.chmod(file, asyncMode.toString(8));
|
|
12
|
-
|
|
13
|
-
const stats = await fs.promises.stat(file);
|
|
14
|
-
assert.equal(stats.mode & 0o777, asyncMode);
|
|
15
|
-
|
|
16
|
-
fs.chmodSync(file, syncMode);
|
|
17
|
-
assert.equal(fs.statSync(file).mode & 0o777, syncMode);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('fchmod', async () => {
|
|
21
|
-
const handle = await fs.promises.open(file, 'a', 0o644);
|
|
22
|
-
|
|
23
|
-
await handle.chmod(asyncMode);
|
|
24
|
-
const stats = await handle.stat();
|
|
25
|
-
|
|
26
|
-
assert.equal(stats.mode & 0o777, asyncMode);
|
|
27
|
-
|
|
28
|
-
fs.fchmodSync(handle.fd, syncMode);
|
|
29
|
-
assert.equal(fs.statSync(file).mode & 0o777, syncMode);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('lchmod', async () => {
|
|
33
|
-
const link = 'symbolic-link';
|
|
34
|
-
|
|
35
|
-
await fs.promises.symlink(file, link);
|
|
36
|
-
await fs.promises.lchmod(link, asyncMode);
|
|
37
|
-
|
|
38
|
-
const stats = await fs.promises.lstat(link);
|
|
39
|
-
assert.equal(stats.mode & 0o777, asyncMode);
|
|
40
|
-
|
|
41
|
-
fs.lchmodSync(link, syncMode);
|
|
42
|
-
assert.equal(fs.lstatSync(link).mode & 0o777, syncMode);
|
|
43
|
-
});
|
|
44
|
-
});
|
package/tests/fs/readdir.test.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert';
|
|
2
|
-
import { suite, test } from 'node:test';
|
|
3
|
-
import { fs } from '../common.js';
|
|
4
|
-
|
|
5
|
-
const testDir = 'test-dir';
|
|
6
|
-
const testFiles = ['file1.txt', 'file2.txt', 'file3.txt'];
|
|
7
|
-
const testDirectories = ['subdir1', 'subdir2'];
|
|
8
|
-
|
|
9
|
-
await fs.promises.mkdir(testDir);
|
|
10
|
-
for (const file of testFiles) {
|
|
11
|
-
await fs.promises.writeFile(`${testDir}/${file}`, 'Sample content');
|
|
12
|
-
}
|
|
13
|
-
for (const dir of testDirectories) {
|
|
14
|
-
await fs.promises.mkdir(`${testDir}/${dir}`);
|
|
15
|
-
for (const file of ['file4.txt', 'file5.txt']) {
|
|
16
|
-
await fs.promises.writeFile(`${testDir}/${dir}/${file}`, 'Sample content');
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
suite('readdir and readdirSync', () => {
|
|
21
|
-
test('readdir returns files and directories', async () => {
|
|
22
|
-
const dirents = await fs.promises.readdir(testDir, { withFileTypes: true });
|
|
23
|
-
const files = dirents.filter(dirent => dirent.isFile()).map(dirent => dirent.name);
|
|
24
|
-
const dirs = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
|
|
25
|
-
|
|
26
|
-
assert(testFiles.every(file => files.includes(file)));
|
|
27
|
-
assert(testDirectories.every(dir => dirs.includes(dir)));
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('readdirSync returns files and directories', () => {
|
|
31
|
-
const dirents = fs.readdirSync(testDir, { withFileTypes: true });
|
|
32
|
-
const files = dirents.filter(dirent => dirent.isFile()).map(dirent => dirent.name);
|
|
33
|
-
const dirs = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
|
|
34
|
-
|
|
35
|
-
assert(testFiles.every(file => files.includes(file)));
|
|
36
|
-
assert(testDirectories.every(dir => dirs.includes(dir)));
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('readdir returns Dirent objects', async () => {
|
|
40
|
-
const dirents = await fs.promises.readdir(testDir, { withFileTypes: true });
|
|
41
|
-
assert(dirents[0] instanceof fs.Dirent);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('readdirSync returns Dirent objects', () => {
|
|
45
|
-
const dirents = fs.readdirSync(testDir, { withFileTypes: true });
|
|
46
|
-
assert(dirents[0] instanceof fs.Dirent);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('readdir works without withFileTypes option', async () => {
|
|
50
|
-
const files = await fs.promises.readdir(testDir);
|
|
51
|
-
assert(testFiles.every(entry => files.includes(entry)));
|
|
52
|
-
assert(testDirectories.every(entry => files.includes(entry)));
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('readdirSync works without withFileTypes option', () => {
|
|
56
|
-
const files = fs.readdirSync(testDir);
|
|
57
|
-
assert(testFiles.every(entry => files.includes(entry)));
|
|
58
|
-
assert(testDirectories.every(entry => files.includes(entry)));
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('readdir returns files recursively', async () => {
|
|
62
|
-
const entries = await fs.promises.readdir(testDir, { recursive: true });
|
|
63
|
-
assert(entries.includes('file1.txt'));
|
|
64
|
-
assert(entries.includes('subdir1/file4.txt'));
|
|
65
|
-
assert(entries.includes('subdir2/file5.txt'));
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('readdir returns Dirent recursively', async () => {
|
|
69
|
-
const entries = await fs.promises.readdir(testDir, { recursive: true, withFileTypes: true });
|
|
70
|
-
assert(entries.find(entry => entry.path === 'file1.txt'));
|
|
71
|
-
assert(entries.find(entry => entry.path === 'subdir1/file4.txt'));
|
|
72
|
-
assert(entries.find(entry => entry.path === 'subdir2/file5.txt'));
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// New test for readdirSync with recursive: true
|
|
76
|
-
test('readdirSync returns files recursively', () => {
|
|
77
|
-
const entries = fs.readdirSync(testDir, { recursive: true });
|
|
78
|
-
assert(entries.includes('file1.txt'));
|
|
79
|
-
assert(entries.includes('subdir1/file4.txt'));
|
|
80
|
-
assert(entries.includes('subdir2/file5.txt'));
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('Cyrillic file names', () => {
|
|
84
|
-
fs.writeFileSync('/мой-файл.txt', 'HELLO!', 'utf-8');
|
|
85
|
-
assert(fs.readdirSync('/').includes('мой-файл.txt'));
|
|
86
|
-
});
|
|
87
|
-
});
|