@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.
@@ -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 should path == '/'.
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 should p == '/'.
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, fullDevice, nullDevice, randomDevice, zeroDevice } from './devices.js';
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.createDevice('/null', nullDevice);
78
- devfs.createDevice('/zero', zeroDevice);
79
- devfs.createDevice('/full', fullDevice);
80
- devfs.createDevice('/random', randomDevice);
97
+ devfs.addDefaults();
81
98
  await devfs.ready();
82
- fs.mount('/dev', devfs);
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
- configuration.mounts[point] = await resolveMountConfig(mountConfig);
113
+ if (point == '/')
114
+ unmountRoot = true;
115
+ toMount.push([point, await resolveMountConfig(mountConfig)]);
95
116
  }
96
- fs.mountObject(configuration.mounts);
117
+ if (unmountRoot)
118
+ fs.umount('/');
119
+ await Promise.all(toMount.map(([point, fs]) => mount(point, fs)));
97
120
  }
@@ -12,4 +12,7 @@ export interface Credentials {
12
12
  egid: number;
13
13
  }
14
14
  export declare const credentials: Credentials;
15
+ /**
16
+ * @deprecated
17
+ */
15
18
  export declare const rootCredentials: Credentials;
@@ -6,6 +6,9 @@ export const credentials = {
6
6
  euid: 0,
7
7
  egid: 0,
8
8
  };
9
+ /**
10
+ * @deprecated
11
+ */
9
12
  export const rootCredentials = {
10
13
  uid: 0,
11
14
  gid: 0,
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, mounts, resolveMount } from './shared.js';
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, 'readdir');
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
@@ -90,6 +90,9 @@ export function fixError(e, paths) {
90
90
  e.message = fixPaths(e.message, paths);
91
91
  return e;
92
92
  }
93
+ /**
94
+ * @deprecated
95
+ */
93
96
  export function mountObject(mounts) {
94
97
  if ('/' in mounts) {
95
98
  umount('/');
@@ -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, mounts, resolveMount } from './shared.js';
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) {
@@ -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 sync.keysSync()) {
94
- promises.push(async.set(key, sync.getSync(key)));
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.8",
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",
@@ -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 should path == '/'.
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 should p == '/'.
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, fullDevice, nullDevice, randomDevice, zeroDevice } from './devices.js';
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.createDevice('/null', nullDevice);
172
- devfs.createDevice('/zero', zeroDevice);
173
- devfs.createDevice('/full', fullDevice);
174
- devfs.createDevice('/random', randomDevice);
191
+ devfs.addDefaults();
175
192
  await devfs.ready();
176
- fs.mount('/dev', devfs);
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
- configuration.mounts[point as keyof T & `/${string}`] = await resolveMountConfig(mountConfig);
212
+ if (point == '/') unmountRoot = true;
213
+ toMount.push([point, await resolveMountConfig(mountConfig)]);
193
214
  }
194
215
 
195
- fs.mountObject(configuration.mounts as MountObject);
216
+ if (unmountRoot) fs.umount('/');
217
+
218
+ await Promise.all(toMount.map(([point, fs]) => mount(point, fs)));
196
219
  }
@@ -21,6 +21,9 @@ export const credentials: Credentials = {
21
21
  egid: 0,
22
22
  };
23
23
 
24
+ /**
25
+ * @deprecated
26
+ */
24
27
  export const rootCredentials: Credentials = {
25
28
  uid: 0,
26
29
  gid: 0,
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, mounts, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
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, 'readdir');
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;
@@ -106,6 +106,9 @@ export function fixError<E extends ErrnoError>(e: E, paths: Record<string, strin
106
106
  return e;
107
107
  }
108
108
 
109
+ /**
110
+ * @deprecated
111
+ */
109
112
  export function mountObject(mounts: MountObject): void {
110
113
  if ('/' in mounts) {
111
114
  umount('/');
@@ -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, mounts, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
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) {
@@ -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 sync.keysSync()) {
84
- promises.push(async.set(key, sync.getSync(key)));
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
- suite('Directory', () => {
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
- test('recursive', () => test_item('/'));
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
+ });
@@ -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
- });
@@ -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
- });