@zenfs/core 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/backends/fetch.js +5 -5
  2. package/dist/backends/overlay.js +8 -8
  3. package/dist/backends/port/fs.js +2 -1
  4. package/dist/backends/port/rpc.js +1 -1
  5. package/dist/config.js +1 -0
  6. package/dist/devices.js +4 -2
  7. package/dist/emulation/async.d.ts +1 -1
  8. package/dist/emulation/promises.d.ts +4 -0
  9. package/dist/emulation/promises.js +26 -18
  10. package/dist/emulation/shared.d.ts +2 -1
  11. package/dist/emulation/sync.d.ts +1 -0
  12. package/dist/emulation/sync.js +24 -17
  13. package/dist/error.js +1 -1
  14. package/eslint.shared.js +1 -0
  15. package/license.md +1 -1
  16. package/package.json +5 -3
  17. package/readme.md +3 -8
  18. package/scripts/make-index.js +3 -3
  19. package/scripts/test.js +31 -0
  20. package/tests/assignment.ts +20 -0
  21. package/tests/common.ts +21 -0
  22. package/tests/data/49chars.txt +1 -0
  23. package/tests/data/a.js +46 -0
  24. package/tests/data/a1.js +46 -0
  25. package/tests/data/elipses.txt +1 -0
  26. package/tests/data/empty.txt +0 -0
  27. package/tests/data/exit.js +22 -0
  28. package/tests/data/x.txt +1 -0
  29. package/tests/devices.test.ts +29 -0
  30. package/tests/fs/appendFile.test.ts +33 -0
  31. package/tests/fs/chmod.test.ts +48 -0
  32. package/tests/fs/dir.test.ts +156 -0
  33. package/tests/fs/directory.test.ts +129 -0
  34. package/tests/fs/errors.test.ts +53 -0
  35. package/tests/fs/exists.test.ts +22 -0
  36. package/tests/fs/links.test.ts +46 -0
  37. package/tests/fs/open.test.ts +39 -0
  38. package/tests/fs/permissions.test.ts +52 -0
  39. package/tests/fs/read.test.ts +67 -0
  40. package/tests/fs/readFile.test.ts +73 -0
  41. package/tests/fs/readdir.test.ts +87 -0
  42. package/tests/fs/rename.test.ts +107 -0
  43. package/tests/fs/stat.test.ts +48 -0
  44. package/tests/fs/streams.test.ts +177 -0
  45. package/tests/fs/times.test.ts +84 -0
  46. package/tests/fs/truncate.test.ts +94 -0
  47. package/tests/fs/watch.test.ts +124 -0
  48. package/tests/fs/write.test.ts +58 -0
  49. package/tests/fs/writeFile.test.ts +69 -0
  50. package/tests/handle.test.ts +60 -0
  51. package/tests/mutex.test.ts +62 -0
  52. package/tests/path.test.ts +34 -0
  53. package/tests/port/channel.test.ts +39 -0
  54. package/tests/port/config.test.ts +31 -0
  55. package/tests/port/config.worker.ts +5 -0
  56. package/tests/port/remote.test.ts +33 -0
  57. package/tests/port/remote.worker.ts +5 -0
  58. package/tests/port/timeout.test.ts +48 -0
  59. package/tests/readme.md +5 -0
  60. package/tests/setup/common.ts +28 -0
  61. package/tests/setup/cow+fetch.ts +43 -0
  62. package/tests/setup/memory.ts +3 -0
  63. package/tests/tsconfig.json +14 -0
  64. package/src/backends/backend.ts +0 -160
  65. package/src/backends/fetch.ts +0 -179
  66. package/src/backends/file_index.ts +0 -210
  67. package/src/backends/memory.ts +0 -50
  68. package/src/backends/overlay.ts +0 -568
  69. package/src/backends/port/fs.ts +0 -334
  70. package/src/backends/port/readme.md +0 -54
  71. package/src/backends/port/rpc.ts +0 -166
  72. package/src/backends/readme.md +0 -3
  73. package/src/backends/store/fs.ts +0 -715
  74. package/src/backends/store/readme.md +0 -9
  75. package/src/backends/store/simple.ts +0 -146
  76. package/src/backends/store/store.ts +0 -173
  77. package/src/config.ts +0 -152
  78. package/src/credentials.ts +0 -31
  79. package/src/devices.ts +0 -469
  80. package/src/emulation/async.ts +0 -834
  81. package/src/emulation/constants.ts +0 -182
  82. package/src/emulation/dir.ts +0 -138
  83. package/src/emulation/index.ts +0 -8
  84. package/src/emulation/path.ts +0 -440
  85. package/src/emulation/promises.ts +0 -1090
  86. package/src/emulation/shared.ts +0 -135
  87. package/src/emulation/streams.ts +0 -34
  88. package/src/emulation/sync.ts +0 -839
  89. package/src/emulation/watchers.ts +0 -193
  90. package/src/error.ts +0 -307
  91. package/src/file.ts +0 -661
  92. package/src/filesystem.ts +0 -174
  93. package/src/index.ts +0 -25
  94. package/src/inode.ts +0 -132
  95. package/src/mixins/async.ts +0 -208
  96. package/src/mixins/index.ts +0 -5
  97. package/src/mixins/mutexed.ts +0 -257
  98. package/src/mixins/readonly.ts +0 -96
  99. package/src/mixins/shared.ts +0 -25
  100. package/src/mixins/sync.ts +0 -58
  101. package/src/polyfills.ts +0 -21
  102. package/src/stats.ts +0 -363
  103. package/src/utils.ts +0 -288
@@ -2,21 +2,21 @@ import { Errno, ErrnoError } from '../error.js';
2
2
  import { IndexFS } from './file_index.js';
3
3
  async function fetchFile(path, type) {
4
4
  const response = await fetch(path).catch((e) => {
5
- throw new ErrnoError(Errno.EIO, e.message);
5
+ throw new ErrnoError(Errno.EIO, e.message, path);
6
6
  });
7
7
  if (!response.ok) {
8
- throw new ErrnoError(Errno.EIO, 'fetch failed: response returned code ' + response.status);
8
+ throw new ErrnoError(Errno.EIO, 'fetch failed: response returned code ' + response.status, path);
9
9
  }
10
10
  switch (type) {
11
11
  case 'buffer': {
12
12
  const arrayBuffer = await response.arrayBuffer().catch((e) => {
13
- throw new ErrnoError(Errno.EIO, e.message);
13
+ throw new ErrnoError(Errno.EIO, e.message, path);
14
14
  });
15
15
  return new Uint8Array(arrayBuffer);
16
16
  }
17
17
  case 'json':
18
18
  return response.json().catch((e) => {
19
- throw new ErrnoError(Errno.EIO, e.message);
19
+ throw new ErrnoError(Errno.EIO, e.message, path);
20
20
  });
21
21
  default:
22
22
  throw new ErrnoError(Errno.EINVAL, 'Invalid download type: ' + type);
@@ -58,11 +58,11 @@ export class FetchFS extends IndexFS {
58
58
  }
59
59
  }
60
60
  constructor({ index = 'index.json', baseUrl = '' }) {
61
- super(typeof index != 'string' ? index : fetchFile(index, 'json'));
62
61
  // prefix url must end in a directory separator.
63
62
  if (baseUrl.at(-1) != '/') {
64
63
  baseUrl += '/';
65
64
  }
65
+ super(typeof index != 'string' ? index : fetchFile(baseUrl + index, 'json'));
66
66
  this.baseUrl = baseUrl;
67
67
  }
68
68
  metadata() {
@@ -141,7 +141,7 @@ export class UnmutexedOverlayFS extends FileSystem {
141
141
  try {
142
142
  await this.writable.rename(oldPath, newPath);
143
143
  }
144
- catch (e) {
144
+ catch {
145
145
  if (this._deletedFiles.has(oldPath)) {
146
146
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
147
147
  }
@@ -155,7 +155,7 @@ export class UnmutexedOverlayFS extends FileSystem {
155
155
  try {
156
156
  this.writable.renameSync(oldPath, newPath);
157
157
  }
158
- catch (e) {
158
+ catch {
159
159
  if (this._deletedFiles.has(oldPath)) {
160
160
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
161
161
  }
@@ -166,7 +166,7 @@ export class UnmutexedOverlayFS extends FileSystem {
166
166
  try {
167
167
  return await this.writable.stat(path);
168
168
  }
169
- catch (e) {
169
+ catch {
170
170
  if (this._deletedFiles.has(path)) {
171
171
  throw ErrnoError.With('ENOENT', path, 'stat');
172
172
  }
@@ -181,7 +181,7 @@ export class UnmutexedOverlayFS extends FileSystem {
181
181
  try {
182
182
  return this.writable.statSync(path);
183
183
  }
184
- catch (e) {
184
+ catch {
185
185
  if (this._deletedFiles.has(path)) {
186
186
  throw ErrnoError.With('ENOENT', path, 'stat');
187
187
  }
@@ -323,13 +323,13 @@ export class UnmutexedOverlayFS extends FileSystem {
323
323
  try {
324
324
  contents.push(...(await this.writable.readdir(path)));
325
325
  }
326
- catch (e) {
326
+ catch {
327
327
  // NOP.
328
328
  }
329
329
  try {
330
330
  contents.push(...(await this.readable.readdir(path)).filter((fPath) => !this._deletedFiles.has(`${path}/${fPath}`)));
331
331
  }
332
- catch (e) {
332
+ catch {
333
333
  // NOP.
334
334
  }
335
335
  const seenMap = {};
@@ -350,13 +350,13 @@ export class UnmutexedOverlayFS extends FileSystem {
350
350
  try {
351
351
  contents = contents.concat(this.writable.readdirSync(path));
352
352
  }
353
- catch (e) {
353
+ catch {
354
354
  // NOP.
355
355
  }
356
356
  try {
357
357
  contents = contents.concat(this.readable.readdirSync(path).filter((fPath) => !this._deletedFiles.has(`${path}/${fPath}`)));
358
358
  }
359
- catch (e) {
359
+ catch {
360
360
  // NOP.
361
361
  }
362
362
  const seenMap = {};
@@ -180,7 +180,7 @@ export async function handleRequest(port, fs, request) {
180
180
  };
181
181
  }
182
182
  break;
183
- case 'file':
183
+ case 'file': {
184
184
  const { fd } = request;
185
185
  if (!descriptors.has(fd)) {
186
186
  throw new ErrnoError(Errno.EBADF);
@@ -191,6 +191,7 @@ export async function handleRequest(port, fs, request) {
191
191
  descriptors.delete(fd);
192
192
  }
193
193
  break;
194
+ }
194
195
  default:
195
196
  return;
196
197
  }
@@ -79,7 +79,7 @@ export function catchMessages(port) {
79
79
  return function (fs) {
80
80
  detach(port, handler);
81
81
  for (const event of events) {
82
- const request = 'data' in event ? event.data : event;
82
+ const request = ('data' in event ? event.data : event);
83
83
  void handleRequest(port, fs, request);
84
84
  }
85
85
  };
package/dist/config.js CHANGED
@@ -19,6 +19,7 @@ export async function resolveMountConfig(configuration, _depth = 0) {
19
19
  throw new ErrnoError(Errno.EINVAL, 'Invalid mount configuration');
20
20
  }
21
21
  if (configuration instanceof FileSystem) {
22
+ await configuration.ready();
22
23
  return configuration;
23
24
  }
24
25
  if (isBackend(configuration)) {
package/dist/devices.js CHANGED
@@ -105,14 +105,16 @@ export class DeviceFile extends File {
105
105
  closeSync() {
106
106
  this.driver.close?.(this);
107
107
  }
108
- async close() {
108
+ close() {
109
109
  this.closeSync();
110
+ return Promise.resolve();
110
111
  }
111
112
  syncSync() {
112
113
  this.driver.sync?.(this);
113
114
  }
114
- async sync() {
115
+ sync() {
115
116
  this.syncSync();
117
+ return Promise.resolve();
116
118
  }
117
119
  chown() {
118
120
  throw ErrnoError.With('ENOTSUP', this.path, 'chown');
@@ -181,7 +181,7 @@ export declare function link(existing: fs.PathLike, newpath: fs.PathLike, cb?: C
181
181
  */
182
182
  export declare function symlink(target: fs.PathLike, path: fs.PathLike, cb?: Callback): void;
183
183
  export declare function symlink(target: fs.PathLike, path: fs.PathLike, type?: fs.symlink.Type, cb?: Callback): void;
184
- export declare function readlink(path: fs.PathLike, callback: Callback<[string]> & any): void;
184
+ export declare function readlink(path: fs.PathLike, callback: Callback<[string]>): void;
185
185
  export declare function readlink(path: fs.PathLike, options: fs.BufferEncodingOption, callback: Callback<[Uint8Array]>): void;
186
186
  export declare function readlink(path: fs.PathLike, options: fs.EncodingOption, callback: Callback<[string | Uint8Array]>): void;
187
187
  export declare function readlink(path: fs.PathLike, options: fs.EncodingOption, callback: Callback<[string]>): void;
@@ -92,6 +92,9 @@ export declare class FileHandle implements promises.FileHandle {
92
92
  * @experimental
93
93
  */
94
94
  readableWebStream(options?: promises.ReadableWebStreamOptions): TReadableStream<Uint8Array>;
95
+ /**
96
+ * @todo Implement
97
+ */
95
98
  readLines(options?: promises.CreateReadStreamOptions): ReadlineInterface;
96
99
  [Symbol.asyncDispose](): Promise<void>;
97
100
  /**
@@ -296,6 +299,7 @@ export declare function lutimes(path: fs.PathLike, atime: fs.TimeLike, mtime: fs
296
299
  * Asynchronous realpath(3) - return the canonicalized absolute pathname.
297
300
  * @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
298
301
  * @param options The encoding (or an object specifying the encoding), used as the encoding of the result. Defaults to `'utf8'`.
302
+ * @todo handle options
299
303
  */
300
304
  export declare function realpath(path: fs.PathLike, options: fs.BufferEncodingOption): Promise<Buffer>;
301
305
  export declare function realpath(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding): Promise<string>;
@@ -206,6 +206,9 @@ export class FileHandle {
206
206
  }
207
207
  return new _gt.ReadableStream({ start, type: options.type });
208
208
  }
209
+ /**
210
+ * @todo Implement
211
+ */
209
212
  readLines(options) {
210
213
  throw ErrnoError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
211
214
  }
@@ -383,11 +386,11 @@ export async function exists(path) {
383
386
  }
384
387
  export async function stat(path, options) {
385
388
  path = normalizePath(path);
386
- const { fs, path: resolved } = resolveMount((await exists(path)) ? await realpath(path) : path);
389
+ const { fs, path: resolved } = resolveMount(await realpath(path));
387
390
  try {
388
391
  const stats = await fs.stat(resolved);
389
392
  if (!stats.hasAccess(constants.R_OK)) {
390
- throw ErrnoError.With('EACCES', path, 'stat');
393
+ throw ErrnoError.With('EACCES', resolved, 'stat');
391
394
  }
392
395
  return options?.bigint ? new BigIntStats(stats) : stats;
393
396
  }
@@ -448,7 +451,7 @@ unlink;
448
451
  async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
449
452
  path = normalizePath(path);
450
453
  const mode = normalizeMode(_mode, 0o644), flag = parseFlag(_flag);
451
- path = resolveSymlinks && (await exists(path)) ? await realpath(path) : path;
454
+ path = resolveSymlinks ? await realpath(path) : path;
452
455
  const { fs, path: resolved } = resolveMount(path);
453
456
  const stats = await fs.stat(resolved).catch(() => null);
454
457
  if (!stats) {
@@ -577,7 +580,7 @@ appendFile;
577
580
  // DIRECTORY-ONLY METHODS
578
581
  export async function rmdir(path) {
579
582
  path = normalizePath(path);
580
- path = (await exists(path)) ? await realpath(path) : path;
583
+ path = await realpath(path);
581
584
  const { fs, path: resolved } = resolveMount(path);
582
585
  try {
583
586
  if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) {
@@ -594,8 +597,7 @@ rmdir;
594
597
  export async function mkdir(path, options) {
595
598
  options = typeof options === 'object' ? options : { mode: options };
596
599
  const mode = normalizeMode(options?.mode, 0o777);
597
- path = normalizePath(path);
598
- path = (await exists(path)) ? await realpath(path) : path;
600
+ path = await realpath(normalizePath(path));
599
601
  const { fs, path: resolved } = resolveMount(path);
600
602
  const errorPaths = { [resolved]: path };
601
603
  try {
@@ -628,12 +630,11 @@ export async function mkdir(path, options) {
628
630
  mkdir;
629
631
  export async function readdir(path, options) {
630
632
  options = typeof options === 'object' ? options : { encoding: options };
631
- path = normalizePath(path);
632
- if (!(await stat(path)).hasAccess(constants.R_OK)) {
633
+ path = await realpath(normalizePath(path));
634
+ const { fs, path: resolved } = resolveMount(path);
635
+ if (!(await fs.stat(resolved)).hasAccess(constants.R_OK)) {
633
636
  throw ErrnoError.With('EACCES', path, 'readdir');
634
637
  }
635
- path = (await exists(path)) ? await realpath(path) : path;
636
- const { fs, path: resolved } = resolveMount(path);
637
638
  const entries = await fs.readdir(resolved).catch((e) => {
638
639
  throw fixError(e, { [resolved]: path });
639
640
  });
@@ -649,8 +650,12 @@ export async function readdir(path, options) {
649
650
  }
650
651
  const values = [];
651
652
  for (const entry of entries) {
652
- const fullPath = join(path, entry);
653
- const stats = options?.recursive || options?.withFileTypes ? await stat(fullPath) : null;
653
+ let stats;
654
+ if (options?.recursive || options?.withFileTypes) {
655
+ stats = await fs.stat(join(resolved, entry)).catch((error) => {
656
+ throw fixError(error, { [resolved]: path });
657
+ });
658
+ }
654
659
  if (options?.withFileTypes) {
655
660
  values.push(new Dirent(entry, stats));
656
661
  }
@@ -663,7 +668,7 @@ export async function readdir(path, options) {
663
668
  if (!options?.recursive || !stats?.isDirectory()) {
664
669
  continue;
665
670
  }
666
- for (const subEntry of await readdir(fullPath, options)) {
671
+ for (const subEntry of await readdir(join(path, entry), options)) {
667
672
  if (subEntry instanceof Dirent) {
668
673
  subEntry.path = join(entry, subEntry.path);
669
674
  values.push(subEntry);
@@ -861,9 +866,12 @@ export async function realpath(path, options) {
861
866
  if (!stats.isSymbolicLink()) {
862
867
  return lpath;
863
868
  }
864
- return realpath(mountPoint + (await readlink(lpath)));
869
+ return await realpath(mountPoint + (await readlink(lpath)));
865
870
  }
866
871
  catch (e) {
872
+ if (e.code == 'ENOENT') {
873
+ return path;
874
+ }
867
875
  throw fixError(e, { [resolvedPath]: lpath });
868
876
  }
869
877
  }
@@ -972,9 +980,9 @@ copyFile;
972
980
  * @returns A `Dir` object representing the opened directory.
973
981
  * @todo Use options
974
982
  */
975
- export async function opendir(path, options) {
983
+ export function opendir(path, options) {
976
984
  path = normalizePath(path);
977
- return new Dir(path);
985
+ return Promise.resolve(new Dir(path));
978
986
  }
979
987
  opendir;
980
988
  /**
@@ -1026,8 +1034,8 @@ export async function cp(source, destination, opts) {
1026
1034
  }
1027
1035
  }
1028
1036
  cp;
1029
- export async function statfs(path, opts) {
1037
+ export function statfs(path, opts) {
1030
1038
  path = normalizePath(path);
1031
1039
  const { fs } = resolveMount(path);
1032
- return _statfs(fs, opts?.bigint);
1040
+ return Promise.resolve(_statfs(fs, opts?.bigint));
1033
1041
  }
@@ -1,4 +1,5 @@
1
1
  import type { BigIntStatsFs, StatsFs } from 'node:fs';
2
+ import { ErrnoError } from '../error.js';
2
3
  import type { File } from '../file.js';
3
4
  import type { FileSystem } from '../filesystem.js';
4
5
  import { type AbsolutePath } from './path.js';
@@ -36,7 +37,7 @@ export declare function fixPaths(text: string, paths: Record<string, string>): s
36
37
  * Fix paths in error stacks
37
38
  * @hidden
38
39
  */
39
- export declare function fixError<E extends Error>(e: E, paths: Record<string, string>): E;
40
+ export declare function fixError<E extends ErrnoError>(e: E, paths: Record<string, string>): E;
40
41
  export declare function mountObject(mounts: MountObject): void;
41
42
  /**
42
43
  * @hidden
@@ -199,6 +199,7 @@ export declare function writevSync(fd: number, buffers: readonly ArrayBufferView
199
199
  * @param path The path to the directory.
200
200
  * @param options Options for opening the directory.
201
201
  * @returns A `Dir` object representing the opened directory.
202
+ * @todo Handle options
202
203
  */
203
204
  export declare function opendirSync(path: fs.PathLike, options?: fs.OpenDirOptions): Dir;
204
205
  /**
@@ -97,11 +97,11 @@ export function existsSync(path) {
97
97
  existsSync;
98
98
  export function statSync(path, options) {
99
99
  path = normalizePath(path);
100
- const { fs, path: resolved } = resolveMount(existsSync(path) ? realpathSync(path) : path);
100
+ const { fs, path: resolved } = resolveMount(realpathSync(path));
101
101
  try {
102
102
  const stats = fs.statSync(resolved);
103
103
  if (!stats.hasAccess(constants.R_OK)) {
104
- throw ErrnoError.With('EACCES', path, 'stat');
104
+ throw ErrnoError.With('EACCES', resolved, 'stat');
105
105
  }
106
106
  return options?.bigint ? new BigIntStats(stats) : stats;
107
107
  }
@@ -159,9 +159,16 @@ unlinkSync;
159
159
  function _openSync(path, _flag, _mode, resolveSymlinks = true) {
160
160
  path = normalizePath(path);
161
161
  const mode = normalizeMode(_mode, 0o644), flag = parseFlag(_flag);
162
- path = resolveSymlinks && existsSync(path) ? realpathSync(path) : path;
162
+ path = resolveSymlinks ? realpathSync(path) : path;
163
163
  const { fs, path: resolved } = resolveMount(path);
164
- if (!fs.existsSync(resolved)) {
164
+ let stats;
165
+ try {
166
+ stats = fs.statSync(resolved);
167
+ }
168
+ catch {
169
+ // nothing
170
+ }
171
+ if (!stats) {
165
172
  if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') {
166
173
  throw ErrnoError.With('ENOENT', path, '_open');
167
174
  }
@@ -175,7 +182,6 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
175
182
  }
176
183
  return fs.createFileSync(resolved, flag, mode);
177
184
  }
178
- const stats = fs.statSync(resolved);
179
185
  if (!stats.hasAccess(mode) || !stats.hasAccess(flagToMode(flag))) {
180
186
  throw ErrnoError.With('EACCES', path, '_open');
181
187
  }
@@ -384,7 +390,7 @@ export function futimesSync(fd, atime, mtime) {
384
390
  futimesSync;
385
391
  export function rmdirSync(path) {
386
392
  path = normalizePath(path);
387
- const { fs, path: resolved } = resolveMount(existsSync(path) ? realpathSync(path) : path);
393
+ const { fs, path: resolved } = resolveMount(realpathSync(path));
388
394
  try {
389
395
  if (!fs.statSync(resolved).hasAccess(constants.W_OK)) {
390
396
  throw ErrnoError.With('EACCES', resolved, 'rmdir');
@@ -400,8 +406,7 @@ rmdirSync;
400
406
  export function mkdirSync(path, options) {
401
407
  options = typeof options === 'object' ? options : { mode: options };
402
408
  const mode = normalizeMode(options?.mode, 0o777);
403
- path = normalizePath(path);
404
- path = existsSync(path) ? realpathSync(path) : path;
409
+ path = realpathSync(normalizePath(path));
405
410
  const { fs, path: resolved } = resolveMount(path);
406
411
  const errorPaths = { [resolved]: path };
407
412
  try {
@@ -433,12 +438,12 @@ mkdirSync;
433
438
  export function readdirSync(path, options) {
434
439
  options = typeof options === 'object' ? options : { encoding: options };
435
440
  path = normalizePath(path);
436
- const { fs, path: resolved } = resolveMount(existsSync(path) ? realpathSync(path) : path);
441
+ const { fs, path: resolved } = resolveMount(realpathSync(path));
437
442
  let entries;
438
- if (!statSync(path).hasAccess(constants.R_OK)) {
439
- throw ErrnoError.With('EACCES', path, 'readdir');
440
- }
441
443
  try {
444
+ if (!fs.statSync(resolved).hasAccess(constants.R_OK)) {
445
+ throw ErrnoError.With('EACCES', path, 'readdir');
446
+ }
442
447
  entries = fs.readdirSync(resolved);
443
448
  }
444
449
  catch (e) {
@@ -458,8 +463,7 @@ export function readdirSync(path, options) {
458
463
  // Iterate over entries and handle recursive case if needed
459
464
  const values = [];
460
465
  for (const entry of entries) {
461
- const fullPath = join(path, entry);
462
- const entryStat = statSync(fullPath);
466
+ const entryStat = fs.statSync(join(resolved, entry));
463
467
  if (options?.withFileTypes) {
464
468
  values.push(new Dirent(entry, entryStat));
465
469
  }
@@ -471,7 +475,7 @@ export function readdirSync(path, options) {
471
475
  }
472
476
  if (!entryStat.isDirectory() || !options?.recursive)
473
477
  continue;
474
- for (const subEntry of readdirSync(fullPath, options)) {
478
+ for (const subEntry of readdirSync(join(path, entry), options)) {
475
479
  if (subEntry instanceof Dirent) {
476
480
  subEntry.path = join(entry, subEntry.path);
477
481
  values.push(subEntry);
@@ -596,13 +600,15 @@ export function realpathSync(path, options) {
596
600
  return realpathSync(mountPoint + readlinkSync(lpath, options).toString());
597
601
  }
598
602
  catch (e) {
603
+ if (e.code == 'ENOENT') {
604
+ return path;
605
+ }
599
606
  throw fixError(e, { [resolvedPath]: lpath });
600
607
  }
601
608
  }
602
609
  realpathSync;
603
610
  export function accessSync(path, mode = 0o600) {
604
- const stats = statSync(path);
605
- if (!stats.hasAccess(mode)) {
611
+ if (!statSync(path).hasAccess(mode)) {
606
612
  throw new ErrnoError(Errno.EACCES);
607
613
  }
608
614
  }
@@ -706,6 +712,7 @@ writevSync;
706
712
  * @param path The path to the directory.
707
713
  * @param options Options for opening the directory.
708
714
  * @returns A `Dir` object representing the opened directory.
715
+ * @todo Handle options
709
716
  */
710
717
  export function opendirSync(path, options) {
711
718
  path = normalizePath(path);
package/dist/error.js CHANGED
@@ -261,7 +261,7 @@ export class ErrnoError extends Error {
261
261
  this.path = path;
262
262
  this.syscall = syscall;
263
263
  this.code = Errno[errno];
264
- this.message = `${this.code}: ${message}${this.path ? `, '${this.path}'` : ''}`;
264
+ this.message = this.code + ': ' + message + (this.path ? `, '${this.path}'` : '');
265
265
  }
266
266
  /**
267
267
  * @returns A friendly error message.
package/eslint.shared.js CHANGED
@@ -44,6 +44,7 @@ export default [
44
44
  '@typescript-eslint/no-redundant-type-constituents': 'warn',
45
45
  '@typescript-eslint/no-unsafe-call': 'warn',
46
46
  '@typescript-eslint/restrict-plus-operands': 'off',
47
+ '@typescript-eslint/no-base-to-string': 'off',
47
48
  },
48
49
  },
49
50
  {
package/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  Copyright (c) James Prevett and other ZenFS contributors.
2
2
  Copyright (c) 2013-2023 John Vilk and other BrowserFS contributors.
3
- Copyright (c) Joyent, Inc. and other Node contributors for `test/fixtures/node`.
3
+ Copyright (c) Joyent, Inc. and other Node contributors for `test/data`.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -14,11 +14,12 @@
14
14
  "storage"
15
15
  ],
16
16
  "bin": {
17
- "make-index": "scripts/make-index.js"
17
+ "make-index": "scripts/make-index.js",
18
+ "zenfs-test": "scripts/test.js"
18
19
  },
19
20
  "files": [
20
21
  "dist",
21
- "src",
22
+ "tests",
22
23
  "license.md",
23
24
  "tsconfig.json",
24
25
  "eslint.shared.js"
@@ -52,6 +53,7 @@
52
53
  "format": "prettier --write .",
53
54
  "format:check": "prettier --check .",
54
55
  "lint": "eslint src tests",
56
+ "test:common": "tsx --test --experimental-test-coverage 'tests/**/!(fs)/*.test.ts' 'tests/*.test.ts'",
55
57
  "test": "tsx --test --experimental-test-coverage",
56
58
  "build": "tsc -p tsconfig.json",
57
59
  "build:docs": "typedoc",
package/readme.md CHANGED
@@ -29,15 +29,10 @@ npm install @zenfs/core
29
29
 
30
30
  ## Usage
31
31
 
32
- > [!NOTE]
33
- > The examples are written in ESM.
34
- > If you are using CJS, you can `require` the package.
35
- > If using a browser environment, you can use a `<script>` with `type=module` (you may need to use import maps)
36
-
37
32
  ```js
38
- import fs from '@zenfs/core'; // You can also use the named export, `fs`
33
+ import { fs } from '@zenfs/core'; // You can also use the default export
39
34
 
40
- fs.writeFileSync('/test.txt', 'Cool, I can do this in any JS environment (including browsers)!');
35
+ fs.writeFileSync('/test.txt', 'You can do this in anywhere (including browsers)!');
41
36
 
42
37
  const contents = fs.readFileSync('/test.txt', 'utf-8');
43
38
  console.log(contents);
@@ -148,7 +143,7 @@ fs.umount('/mnt/zip'); // finished using the zip
148
143
  > [!CAUTION]
149
144
  > Instances of backends follow the _internal_ API. You should never use a backend's methods unless you are extending a backend.
150
145
 
151
- #### Devices and device files
146
+ ### Devices and device files
152
147
 
153
148
  > [!WARNING]
154
149
  > This is an **experimental** feature. Breaking changes may occur during non-major releases. Using this feature is the fastest way to make it stable.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { readdirSync, statSync, writeFileSync } from 'fs';
2
+ import { readdirSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { minimatch } from 'minimatch';
4
- import { join, relative, resolve } from 'path/posix';
5
- import { parseArgs } from 'util';
4
+ import { join, relative, resolve } from 'node:path/posix';
5
+ import { parseArgs } from 'node:util';
6
6
 
7
7
  const { values: options, positionals } = parseArgs({
8
8
  options: {
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'node:child_process';
4
+ import { join } from 'node:path';
5
+ import { parseArgs } from 'node:util';
6
+
7
+ const { values: options, positionals } = parseArgs({
8
+ options: {
9
+ help: { short: 'h', type: 'boolean', default: false },
10
+ verbose: { type: 'boolean', default: false },
11
+ },
12
+ allowPositionals: true,
13
+ });
14
+
15
+ if (options.help) {
16
+ console.log(`zenfs-test [...options] <...paths>
17
+
18
+ paths: The setup files to run tests on
19
+
20
+ options:
21
+ --help, -h Outputs this help message
22
+ --verbose Output verbose messages
23
+ `);
24
+ process.exit();
25
+ }
26
+
27
+ for (const setupFile of positionals) {
28
+ if (options.verbose) console.debug('Running tests for:', setupFile);
29
+ process.env.SETUP = setupFile;
30
+ execSync('tsx --test --experimental-test-coverage ' + join(import.meta.dirname, '../tests/fs/*.test.ts'), { stdio: 'inherit' });
31
+ }
@@ -0,0 +1,20 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+
3
+ /*
4
+ This test assigns the exported fs module from ZenFS with the one exported from Node.
5
+ This ensures anything new that is added will be caught
6
+
7
+ Notes on omissions and exclusions:
8
+ - __promisify__ is omitted as it is metadata
9
+ - native is omitted as zenfs isn't native
10
+ - ReadStream and WriteStream are excluded since they are polfilled from another module
11
+ */
12
+
13
+ import { fs as zen } from '../src/index.js';
14
+ import type * as node from 'node:fs';
15
+
16
+ type Mock = {
17
+ [K in Exclude<keyof typeof node, 'ReadStream' | 'WriteStream'>]: Omit<(typeof node)[K], '__promisify__' | 'native'>;
18
+ };
19
+
20
+ const _module: Mock = zen;
@@ -0,0 +1,21 @@
1
+ import { join, resolve } from 'node:path';
2
+ import { Worker } from 'node:worker_threads';
3
+ import { fs } from '../src/index.js';
4
+
5
+ /**
6
+ * Creates a Typescript Worker
7
+ * @see https://github.com/privatenumber/tsx/issues/354
8
+ * @see https://github.com/nodejs/node/issues/47747#issuecomment-2287745567
9
+ */
10
+ export function createTSWorker(source: string): Worker {
11
+ return new Worker(`import('tsx/esm/api').then(tsx => {tsx.register();import('${source}');});`, { eval: true });
12
+ }
13
+
14
+ const setupPath = resolve(process.env.SETUP || join(import.meta.dirname, 'setup/memory.ts'));
15
+
16
+ await import(setupPath).catch(error => {
17
+ console.log('Failed to import test setup:');
18
+ throw error;
19
+ });
20
+
21
+ export { fs };
@@ -0,0 +1 @@
1
+ 0123456789abcdef0123456789abcdef0123456789abcdef