@zenfs/core 1.7.2 → 1.8.1

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 (68) hide show
  1. package/dist/backends/backend.js +3 -4
  2. package/dist/backends/fetch.d.ts +17 -18
  3. package/dist/backends/fetch.js +95 -58
  4. package/dist/backends/index.d.ts +2 -1
  5. package/dist/backends/index.js +2 -1
  6. package/dist/backends/memory.d.ts +1 -1
  7. package/dist/backends/overlay.d.ts +8 -14
  8. package/dist/backends/overlay.js +38 -31
  9. package/dist/backends/passthrough.d.ts +8 -3
  10. package/dist/backends/passthrough.js +148 -4
  11. package/dist/backends/port/fs.d.ts +15 -49
  12. package/dist/backends/port/fs.js +28 -116
  13. package/dist/backends/port/rpc.d.ts +13 -6
  14. package/dist/backends/port/rpc.js +9 -7
  15. package/dist/backends/store/file_index.d.ts +38 -0
  16. package/dist/backends/store/file_index.js +76 -0
  17. package/dist/backends/store/fs.d.ts +39 -34
  18. package/dist/backends/store/fs.js +407 -238
  19. package/dist/backends/store/index_fs.d.ts +34 -0
  20. package/dist/backends/store/index_fs.js +67 -0
  21. package/dist/backends/store/inode.d.ts +26 -8
  22. package/dist/backends/store/inode.js +92 -91
  23. package/dist/backends/store/simple.d.ts +20 -20
  24. package/dist/backends/store/simple.js +3 -4
  25. package/dist/backends/store/store.d.ts +12 -12
  26. package/dist/backends/store/store.js +4 -6
  27. package/dist/devices.d.ts +44 -21
  28. package/dist/devices.js +110 -55
  29. package/dist/file.d.ts +111 -7
  30. package/dist/file.js +324 -92
  31. package/dist/filesystem.d.ts +44 -4
  32. package/dist/mixins/async.js +12 -6
  33. package/dist/mixins/mutexed.d.ts +8 -3
  34. package/dist/mixins/mutexed.js +57 -1
  35. package/dist/mixins/readonly.d.ts +17 -16
  36. package/dist/mixins/readonly.js +6 -0
  37. package/dist/mixins/sync.d.ts +1 -1
  38. package/dist/stats.d.ts +12 -6
  39. package/dist/stats.js +14 -6
  40. package/dist/utils.d.ts +23 -3
  41. package/dist/utils.js +58 -10
  42. package/dist/vfs/async.js +1 -1
  43. package/dist/vfs/constants.d.ts +2 -2
  44. package/dist/vfs/constants.js +2 -2
  45. package/dist/vfs/dir.js +3 -1
  46. package/dist/vfs/index.js +4 -1
  47. package/dist/vfs/promises.js +33 -13
  48. package/dist/vfs/shared.js +2 -0
  49. package/dist/vfs/sync.js +25 -13
  50. package/dist/vfs/types.d.ts +15 -0
  51. package/eslint.shared.js +1 -0
  52. package/package.json +2 -3
  53. package/readme.md +2 -2
  54. package/scripts/test.js +73 -11
  55. package/tests/common/mutex.test.ts +1 -1
  56. package/tests/fetch/run.sh +16 -0
  57. package/tests/fetch/server.ts +49 -0
  58. package/tests/fetch/setup.ts +13 -0
  59. package/tests/fs/read.test.ts +10 -10
  60. package/tests/fs/times.test.ts +2 -2
  61. package/tests/fs/write.test.ts +6 -11
  62. package/tests/setup/index.ts +38 -0
  63. package/tests/setup/port.ts +15 -0
  64. package/dist/backends/file_index.d.ts +0 -63
  65. package/dist/backends/file_index.js +0 -163
  66. package/tests/common/async.test.ts +0 -31
  67. package/tests/setup/cow+fetch.ts +0 -45
  68. /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
@@ -113,6 +113,7 @@ export function fixError(e, paths) {
113
113
  e.path = fixPaths(e.path, paths);
114
114
  return e;
115
115
  }
116
+ /* node:coverage disable */
116
117
  /**
117
118
  * @internal @deprecated
118
119
  */
@@ -124,6 +125,7 @@ export function mountObject(mounts) {
124
125
  mount(point, fs);
125
126
  }
126
127
  }
128
+ /* node:coverage enable */
127
129
  /**
128
130
  * @internal @hidden
129
131
  */
package/dist/vfs/sync.js CHANGED
@@ -134,7 +134,7 @@ lstatSync;
134
134
  export function truncateSync(path, len = 0) {
135
135
  const env_1 = { stack: [], error: void 0, hasError: false };
136
136
  try {
137
- const file = __addDisposableResource(env_1, _openSync.call(this, path, 'r+'), false);
137
+ const file = __addDisposableResource(env_1, _openSync.call(this, path, { flag: 'r+' }), false);
138
138
  len || (len = 0);
139
139
  if (len < 0) {
140
140
  throw new ErrnoError(Errno.EINVAL);
@@ -176,11 +176,11 @@ function applySetId(file, uid, gid) {
176
176
  file.chownSync(parent.mode & constants.S_ISUID ? parent.uid : uid, // manually apply setuid/setgid
177
177
  parent.mode & constants.S_ISGID ? parent.gid : gid);
178
178
  }
179
- function _openSync(path, _flag, _mode, resolveSymlinks = true) {
179
+ function _openSync(path, opt) {
180
180
  var _a;
181
181
  path = normalizePath(path);
182
- const mode = normalizeMode(_mode, 0o644), flag = parseFlag(_flag);
183
- path = resolveSymlinks ? realpathSync.call(this, path) : path;
182
+ const mode = normalizeMode(opt.mode, 0o644), flag = parseFlag(opt.flag);
183
+ path = opt.preserveSymlinks ? path : realpathSync.call(this, path);
184
184
  const { fs, path: resolved } = resolveMount(path, this);
185
185
  let stats;
186
186
  try {
@@ -203,6 +203,8 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
203
203
  }
204
204
  const { euid: uid, egid: gid } = (_a = this === null || this === void 0 ? void 0 : this.credentials) !== null && _a !== void 0 ? _a : credentials;
205
205
  const file = fs.createFileSync(resolved, flag, mode, { uid, gid });
206
+ if (!opt.allowDirectory && mode & constants.S_IFDIR)
207
+ throw ErrnoError.With('EISDIR', path, '_open');
206
208
  applySetId(file, uid, gid);
207
209
  return file;
208
210
  }
@@ -216,6 +218,8 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
216
218
  if (isTruncating(flag)) {
217
219
  file.truncateSync(0);
218
220
  }
221
+ if (!opt.allowDirectory && stats.mode & constants.S_IFDIR)
222
+ throw ErrnoError.With('EISDIR', path, '_open');
219
223
  return file;
220
224
  }
221
225
  /**
@@ -223,7 +227,7 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
223
227
  * @see http://www.manpagez.com/man/2/open/
224
228
  */
225
229
  export function openSync(path, flag, mode = constants.F_OK) {
226
- return file2fd(_openSync.call(this, path, flag, mode, true));
230
+ return file2fd(_openSync.call(this, path, { flag, mode }));
227
231
  }
228
232
  openSync;
229
233
  /**
@@ -231,13 +235,13 @@ openSync;
231
235
  * @internal
232
236
  */
233
237
  export function lopenSync(path, flag, mode) {
234
- return file2fd(_openSync.call(this, path, flag, mode, false));
238
+ return file2fd(_openSync.call(this, path, { flag, mode, preserveSymlinks: true }));
235
239
  }
236
- function _readFileSync(fname, flag, resolveSymlinks) {
240
+ function _readFileSync(fname, flag, preserveSymlinks) {
237
241
  const env_2 = { stack: [], error: void 0, hasError: false };
238
242
  try {
239
243
  // Get file.
240
- const file = __addDisposableResource(env_2, _openSync.call(this, fname, flag, 0o644, resolveSymlinks), false);
244
+ const file = __addDisposableResource(env_2, _openSync.call(this, fname, { flag, mode: 0o644, preserveSymlinks }), false);
241
245
  const stat = file.statSync();
242
246
  // Allocate buffer.
243
247
  const data = new Uint8Array(stat.size);
@@ -258,7 +262,7 @@ export function readFileSync(path, _options = {}) {
258
262
  if (!isReadable(flag)) {
259
263
  throw new ErrnoError(Errno.EINVAL, 'Flag passed to readFile must allow for reading.');
260
264
  }
261
- const data = Buffer.from(_readFileSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), options.flag, true));
265
+ const data = Buffer.from(_readFileSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), options.flag, false));
262
266
  return options.encoding ? data.toString(options.encoding) : data;
263
267
  }
264
268
  readFileSync;
@@ -277,7 +281,11 @@ export function writeFileSync(path, data, _options = {}) {
277
281
  if (!encodedData) {
278
282
  throw new ErrnoError(Errno.EINVAL, 'Data not specified');
279
283
  }
280
- const file = __addDisposableResource(env_3, _openSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), flag, options.mode, true), false);
284
+ const file = __addDisposableResource(env_3, _openSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), {
285
+ flag,
286
+ mode: options.mode,
287
+ preserveSymlinks: true,
288
+ }), false);
281
289
  file.writeSync(encodedData, 0, encodedData.byteLength, 0);
282
290
  emitChange('change', path.toString());
283
291
  }
@@ -308,7 +316,11 @@ export function appendFileSync(filename, data, _options = {}) {
308
316
  throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
309
317
  }
310
318
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
311
- const file = __addDisposableResource(env_4, _openSync.call(this, typeof filename == 'number' ? fd2file(filename).path : filename.toString(), flag, options.mode, true), false);
319
+ const file = __addDisposableResource(env_4, _openSync.call(this, typeof filename == 'number' ? fd2file(filename).path : filename.toString(), {
320
+ flag,
321
+ mode: options.mode,
322
+ preserveSymlinks: true,
323
+ }), false);
312
324
  file.writeSync(encodedData, 0, encodedData.byteLength);
313
325
  }
314
326
  catch (e_4) {
@@ -560,12 +572,12 @@ export function symlinkSync(target, path, type = 'file') {
560
572
  throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
561
573
  }
562
574
  writeFileSync.call(this, path, target.toString());
563
- const file = _openSync.call(this, path, 'r+', 0o644, false);
575
+ const file = _openSync.call(this, path, { flag: 'r+', mode: 0o644, preserveSymlinks: true });
564
576
  file.chmodSync(constants.S_IFLNK);
565
577
  }
566
578
  symlinkSync;
567
579
  export function readlinkSync(path, options) {
568
- const value = Buffer.from(_readFileSync.call(this, path.toString(), 'r', false));
580
+ const value = Buffer.from(_readFileSync.call(this, path.toString(), 'r', true));
569
581
  const encoding = typeof options == 'object' ? options === null || options === void 0 ? void 0 : options.encoding : options;
570
582
  if (encoding == 'buffer') {
571
583
  return value;
@@ -11,6 +11,21 @@ export interface InternalOptions {
11
11
  */
12
12
  _isIndirect?: boolean;
13
13
  }
14
+ /**
15
+ * @internal @hidden Used for the internal `_open` functions
16
+ */
17
+ export interface OpenOptions {
18
+ flag: fs.OpenMode;
19
+ mode?: fs.Mode | null;
20
+ /**
21
+ * If true, do not resolve symlinks
22
+ */
23
+ preserveSymlinks?: boolean;
24
+ /**
25
+ * If true, allows opening directories
26
+ */
27
+ allowDirectory?: boolean;
28
+ }
14
29
  export interface ReaddirOptions extends InternalOptions {
15
30
  withFileTypes?: boolean;
16
31
  recursive?: boolean;
package/eslint.shared.js CHANGED
@@ -45,6 +45,7 @@ export default [
45
45
  '@typescript-eslint/no-unsafe-call': 'off',
46
46
  '@typescript-eslint/restrict-plus-operands': 'off',
47
47
  '@typescript-eslint/no-base-to-string': 'off',
48
+ '@typescript-eslint/no-unused-expressions': 'warn',
48
49
  },
49
50
  },
50
51
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.7.2",
3
+ "version": "1.8.1",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -57,8 +57,7 @@
57
57
  "format": "prettier --write .",
58
58
  "format:check": "prettier --check .",
59
59
  "lint": "eslint src tests",
60
- "test": "tsx --test --experimental-test-coverage",
61
- "pretest": "npm run build",
60
+ "test": "npx zenfs-test --clean && npx zenfs-test -abcfp && tests/fetch/run.sh && npx zenfs-test --report",
62
61
  "build": "tsc -p tsconfig.json",
63
62
  "build:docs": "typedoc",
64
63
  "dev": "npm run build -- --watch",
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ZenFS
2
2
 
3
- ZenFS is a file system that emulates the [NodeJS filesystem API](http://nodejs.org/api/fs.html). It works using a system of backends, which are used by ZenFS to store and retrieve data. ZenFS can also integrate with other tools.
3
+ ZenFS is a cross-platform library that emulates the [NodeJS filesystem API](http://nodejs.org/api/fs.html). It works using a system of backends, which are used by ZenFS to store and retrieve data. ZenFS can also integrate with other tools.
4
4
 
5
5
  ## Backends
6
6
 
@@ -8,7 +8,7 @@ ZenFS is modular and extensible. The core includes some built-in backends:
8
8
 
9
9
  - `InMemory`: Stores files in-memory. This is cleared when the runtime ends (e.g. a user navigating away from a web page or a Node process exiting)
10
10
  - `Overlay`: Use a read-only file system as read-write by overlaying a writable file system on top of it. ([copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write))
11
- - `Fetch`: Downloads files over HTTP with the `fetch` API (_readonly_)
11
+ - `Fetch`: Downloads files over HTTP with the `fetch` API
12
12
  - `Port`: Interacts with a remote over a `MessagePort`-like interface (e.g. a worker)
13
13
  - `Passthrough`: Use an existing `node:fs` interface with ZenFS
14
14
 
package/scripts/test.js CHANGED
@@ -16,7 +16,10 @@ const { values: options, positionals } = parseArgs({
16
16
  build: { short: 'b', type: 'boolean', default: false },
17
17
  common: { short: 'c', type: 'boolean', default: false },
18
18
  coverage: { type: 'string', default: 'tests/.coverage' },
19
+ preserve: { short: 'p', type: 'boolean' },
19
20
  'exit-on-fail': { short: 'e', type: 'boolean' },
21
+ report: { type: 'boolean' },
22
+ clean: { type: 'boolean' },
20
23
  },
21
24
  allowPositionals: true,
22
25
  });
@@ -36,7 +39,12 @@ Options:
36
39
  -q, --quiet Don't output normal messages
37
40
  -t, --test <glob> Which FS test suite(s) to run
38
41
  -f, --force Whether to use --test-force-exit
39
- --coverage <dir> Override the default coverage data directory`);
42
+
43
+ Coverage:
44
+ --coverage <dir> Override the default coverage data directory
45
+ -p,--preserve Do not delete or report coverage data
46
+ --report ONLY report coverage
47
+ --clean ONLY clean up coverage directory`);
40
48
  process.exit();
41
49
  }
42
50
 
@@ -45,6 +53,19 @@ if (options.quiet && options.verbose) {
45
53
  process.exit(1);
46
54
  }
47
55
 
56
+ process.env.NODE_V8_COVERAGE = options.coverage;
57
+
58
+ if (options.clean) {
59
+ rmSync(options.coverage, { recursive: true, force: true });
60
+ process.exit();
61
+ }
62
+
63
+ if (options.report) {
64
+ execSync('npx c8 report --reporter=text', { stdio: 'inherit' });
65
+ rmSync(options.coverage, { recursive: true, force: true });
66
+ process.exit();
67
+ }
68
+
48
69
  options.verbose && options.force && console.debug('Forcing tests to exit (--test-force-exit)');
49
70
 
50
71
  if (options.build) {
@@ -73,19 +94,56 @@ if (options.auto) {
73
94
  !options.quiet && console.log(`Auto-detected ${sum} test setup files`);
74
95
  }
75
96
 
76
- if (existsSync(options.coverage)) rmSync(options.coverage, { recursive: true });
77
- mkdirSync(options.coverage);
78
- process.env.NODE_V8_COVERAGE = options.coverage;
97
+ /**
98
+ * Colorizes some text
99
+ * @param {string} text Text to color
100
+ * @param {string | number} code ANSI escape code
101
+ * @returns
102
+ */
103
+ function color(text, code) {
104
+ return `\x1b[${code}m${text}\x1b[0m`;
105
+ }
106
+
107
+ function status(name) {
108
+ const start = performance.now();
109
+
110
+ const time = () => {
111
+ let delta = Math.round(performance.now() - start),
112
+ unit = 'ms';
113
+
114
+ if (delta > 5000) {
115
+ delta /= 1000;
116
+ unit = 's';
117
+ }
118
+
119
+ return color(`(${delta} ${unit})`, '2;37');
120
+ };
121
+
122
+ return {
123
+ pass() {
124
+ if (!options.quiet) console.log(`${color('passed', 32)}: ${name} ${time()}`);
125
+ },
126
+ fail() {
127
+ console.error(`${color('failed', '1;31')}: ${name} ${time()}`);
128
+ process.exitCode = 1;
129
+ if (options['exit-on-fail']) process.exit();
130
+ },
131
+ };
132
+ }
133
+
134
+ if (!options.preserve) rmSync(options.coverage, { force: true, recursive: true });
135
+ mkdirSync(options.coverage, { recursive: true });
79
136
 
80
137
  if (options.common) {
81
138
  !options.quiet && console.log('Running common tests...');
139
+ const { pass, fail } = status('Common tests');
82
140
  try {
83
141
  execSync("tsx --test --experimental-test-coverage 'tests/*.test.ts' 'tests/**/!(fs)/*.test.ts'", {
84
142
  stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
85
143
  });
144
+ pass();
86
145
  } catch {
87
- console.error('Common tests failed');
88
- if (options['exit-on-fail']) process.exit(1);
146
+ fail();
89
147
  }
90
148
  }
91
149
 
@@ -97,18 +155,22 @@ for (const setupFile of positionals) {
97
155
  continue;
98
156
  }
99
157
 
100
- !options.quiet && console.debug('Running tests using setup:', setupFile);
158
+ !options.quiet && console.log('Running tests:', setupFile);
101
159
  process.env.SETUP = setupFile;
102
160
 
161
+ const { pass, fail } = status(setupFile);
162
+
103
163
  try {
104
164
  execSync(['tsx --test --experimental-test-coverage', options.force ? '--test-force-exit' : '', testsGlob, process.env.CMD].join(' '), {
105
165
  stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
106
166
  });
167
+ pass();
107
168
  } catch {
108
- !options.quiet && console.error('Tests failed:', setupFile);
109
- if (options['exit-on-fail']) process.exit(1);
169
+ fail();
110
170
  }
111
171
  }
112
172
 
113
- execSync('npx c8 report --reporter=text', { stdio: 'inherit' });
114
- rmSync(options.coverage, { recursive: true });
173
+ if (!options.preserve) {
174
+ execSync('npx c8 report --reporter=text', { stdio: 'inherit' });
175
+ rmSync(options.coverage, { recursive: true });
176
+ }
@@ -51,7 +51,7 @@ suite('LockFS mutex', () => {
51
51
 
52
52
  async function foo() {
53
53
  const lock = await fs.lock('raceConditions', 'test');
54
- await wait(100);
54
+ await wait(25);
55
55
  x++;
56
56
  lock.unlock();
57
57
  }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/bash
2
+
3
+ # Credit: Dave Dopson, https://stackoverflow.com/a/246128/17637456
4
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
5
+
6
+ npx tsx $SCRIPT_DIR/server.ts &
7
+ PID=$!
8
+
9
+ echo "Waiting for server to start..."
10
+ until nc -z localhost 26514; do
11
+ sleep 0.5
12
+ done
13
+
14
+ npx zenfs-test $SCRIPT_DIR/setup.ts --preserve --force $@
15
+
16
+ kill $PID
@@ -0,0 +1,49 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import { createServer } from 'node:http';
4
+ import { join } from 'node:path';
5
+ import { data, tmp } from '../setup.js';
6
+
7
+ // If you change the port please update the setup file as well
8
+ const port = 26514;
9
+
10
+ const statusCodes = {
11
+ ENOENT: 404,
12
+ };
13
+
14
+ try {
15
+ execSync(`npm exec make-index -- ${data} --output ${tmp}/index.json --quiet`, { stdio: 'inherit' });
16
+ } catch (e: any) {
17
+ if (e.signal == 'SIGINT') {
18
+ console.log('Aborted whilst creating index.');
19
+ process.exit(0);
20
+ } else {
21
+ console.error('Index creation failed: ' + e.message);
22
+ process.exit(1);
23
+ }
24
+ }
25
+
26
+ const server = createServer((request, response) => {
27
+ const { url = '' } = request;
28
+
29
+ if (url == '/.ping') {
30
+ response.statusCode = 200;
31
+ response.end('pong');
32
+ return;
33
+ }
34
+
35
+ const path = url == '/.index.json' ? join(tmp, 'index.json') : join(data, url.slice(1) || '');
36
+ try {
37
+ response.statusCode = 200;
38
+ response.end(readFileSync(path));
39
+ } catch (e: any) {
40
+ response.statusCode = statusCodes[e.code as keyof typeof statusCodes] || 400;
41
+ response.end();
42
+ }
43
+ });
44
+
45
+ server.listen(port);
46
+
47
+ process.on('beforeExit', () => {
48
+ server.close().unref();
49
+ });
@@ -0,0 +1,13 @@
1
+ import { configureSingle, Fetch, InMemory, Overlay, resolveMountConfig } from '../../dist/index.js';
2
+
3
+ const baseUrl = 'http://localhost:26514';
4
+
5
+ await configureSingle({
6
+ backend: Overlay,
7
+ readable: await resolveMountConfig({
8
+ backend: Fetch,
9
+ baseUrl,
10
+ index: baseUrl + '/.index.json',
11
+ }),
12
+ writable: InMemory.create({ name: 'cow' }),
13
+ });
@@ -1,17 +1,17 @@
1
1
  import assert from 'node:assert';
2
2
  import { suite, test } from 'node:test';
3
3
  import { fs } from '../common.js';
4
+ import { Buffer } from 'buffer';
4
5
 
5
- const filepath: string = 'x.txt';
6
- const expected: string = 'xyz\n';
6
+ const filepath = 'x.txt';
7
+ const expected = 'xyz\n';
7
8
 
8
9
  suite('read', () => {
9
10
  test('read file asynchronously', async () => {
10
11
  const handle = await fs.promises.open(filepath, 'r');
11
12
  const { bytesRead, buffer } = await handle.read(Buffer.alloc(expected.length), 0, expected.length, 0);
12
-
13
13
  assert.equal(bytesRead, expected.length);
14
- assert(buffer.toString() == expected);
14
+ assert.equal(buffer.toString(), expected);
15
15
  });
16
16
 
17
17
  test('read file synchronously', () => {
@@ -20,19 +20,19 @@ suite('read', () => {
20
20
  const bytesRead = fs.readSync(fd, buffer, 0, expected.length, 0);
21
21
 
22
22
  assert.equal(bytesRead, expected.length);
23
- assert(buffer.toString() == expected);
23
+ assert.equal(buffer.toString(), expected);
24
24
  });
25
25
  });
26
26
 
27
27
  suite('read binary', () => {
28
28
  test('Read a file and check its binary bytes (asynchronous)', async () => {
29
29
  const buff = await fs.promises.readFile('elipses.txt');
30
- assert(((buff[1] << 8) | buff[0]) === 32994);
30
+ assert.strictEqual((buff[1] << 8) | buff[0], 32994);
31
31
  });
32
32
 
33
33
  test('Read a file and check its binary bytes (synchronous)', () => {
34
34
  const buff = fs.readFileSync('elipses.txt');
35
- assert(((buff[1] << 8) | buff[0]) === 32994);
35
+ assert.strictEqual((buff[1] << 8) | buff[0], 32994);
36
36
  });
37
37
  });
38
38
 
@@ -45,14 +45,14 @@ suite('read buffer', () => {
45
45
  const { bytesRead } = await handle.read(bufferAsync, 0, expected.length, 0);
46
46
 
47
47
  assert.strictEqual(bytesRead, expected.length);
48
- assert(bufferAsync.toString() === expected);
48
+ assert.strictEqual(bufferAsync.toString(), expected);
49
49
  });
50
50
 
51
51
  test('read file synchronously', () => {
52
52
  const fd = fs.openSync(filepath, 'r');
53
53
  const bytesRead = fs.readSync(fd, bufferSync, 0, expected.length, 0);
54
54
 
55
- assert(bufferSync.toString() === expected);
55
+ assert.strictEqual(bufferSync.toString(), expected);
56
56
  assert.strictEqual(bytesRead, expected.length);
57
57
  });
58
58
 
@@ -61,7 +61,7 @@ suite('read buffer', () => {
61
61
  const buffer = Buffer.alloc(expected.length + 10);
62
62
  const bytesRead = fs.readSync(fd, buffer, 10, expected.length, 0);
63
63
 
64
- assert(buffer.subarray(10, buffer.length).toString() === expected);
64
+ assert.strictEqual(buffer.subarray(10, buffer.length).toString(), expected);
65
65
  assert.strictEqual(bytesRead, expected.length);
66
66
  });
67
67
  });
@@ -69,7 +69,7 @@ suite('times', () => {
69
69
  test('read changes atime', async () => {
70
70
  const before = fs.statSync(path).atimeMs;
71
71
  fs.readFileSync(path);
72
- await wait(100);
72
+ await wait(25);
73
73
  const after = fs.statSync(path).atimeMs;
74
74
  assert(before < after);
75
75
  });
@@ -77,7 +77,7 @@ suite('times', () => {
77
77
  test('write changes mtime', async () => {
78
78
  const before = fs.statSync(path).mtimeMs;
79
79
  fs.writeFileSync(path, 'cool');
80
- await wait(100);
80
+ await wait(25);
81
81
  const after = fs.statSync(path).mtimeMs;
82
82
  assert(before < after);
83
83
  });
@@ -1,10 +1,9 @@
1
1
  import assert from 'node:assert';
2
2
  import { suite, test } from 'node:test';
3
3
  import { fs } from '../common.js';
4
-
4
+ const fn = 'write.txt';
5
5
  suite('write', () => {
6
6
  test('write file with specified content', async () => {
7
- const fn = 'write.txt';
8
7
  const expected = 'ümlaut.';
9
8
 
10
9
  const handle = await fs.promises.open(fn, 'w', 0o644);
@@ -20,10 +19,9 @@ suite('write', () => {
20
19
  });
21
20
 
22
21
  test('write a buffer to a file', async () => {
23
- const filename = 'write.txt';
24
22
  const expected = Buffer.from('hello');
25
23
 
26
- const handle = await fs.promises.open(filename, 'w', 0o644);
24
+ const handle = await fs.promises.open(fn, 'w', 0o644);
27
25
 
28
26
  const written = await handle.write(expected, 0, expected.length, null);
29
27
 
@@ -31,15 +29,12 @@ suite('write', () => {
31
29
 
32
30
  await handle.close();
33
31
 
34
- assert((await fs.promises.readFile(filename)).equals(expected));
32
+ assert((await fs.promises.readFile(fn)).equals(expected));
35
33
 
36
- await fs.promises.unlink(filename);
34
+ await fs.promises.unlink(fn);
37
35
  });
38
- });
39
36
 
40
- suite('writeSync', () => {
41
- test('write file with specified content', () => {
42
- const fn = 'write.txt';
37
+ test('writeSync file with specified content', () => {
43
38
  const fd = fs.openSync(fn, 'w');
44
39
 
45
40
  let written = fs.writeSync(fd, '');
@@ -53,6 +48,6 @@ suite('writeSync', () => {
53
48
 
54
49
  fs.closeSync(fd);
55
50
 
56
- assert(fs.readFileSync(fn, 'utf8') === 'foobár');
51
+ assert.strictEqual(fs.readFileSync(fn, 'utf8'), 'foobár');
57
52
  });
58
53
  });
@@ -0,0 +1,38 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path/posix';
3
+ import { configureSingle, InMemory, InMemoryStore, mounts, Overlay, Readonly, resolveMountConfig, StoreFS } from '../../dist/index.js';
4
+ import { S_IFDIR } from '../../dist/vfs/constants.js';
5
+ import { copy, data } from '../setup.js';
6
+
7
+ copy(data);
8
+
9
+ const index = (mounts.get('/') as StoreFS).createIndexSync();
10
+
11
+ class MockFS extends Readonly(StoreFS) {
12
+ constructor() {
13
+ super(new InMemoryStore());
14
+ this.loadIndexSync(index);
15
+
16
+ using tx = this.store.transaction();
17
+
18
+ for (const [path, node] of index) {
19
+ if (node.mode & S_IFDIR) continue;
20
+
21
+ const content = readFileSync(join(data, path));
22
+
23
+ tx.setSync(node.data, content);
24
+ }
25
+
26
+ tx.commitSync();
27
+ }
28
+ }
29
+
30
+ const readable = new MockFS();
31
+
32
+ await readable.ready();
33
+
34
+ await configureSingle({
35
+ backend: Overlay,
36
+ readable,
37
+ writable: await resolveMountConfig({ backend: InMemory, name: 'cow' }),
38
+ });
@@ -0,0 +1,15 @@
1
+ import { MessageChannel } from 'node:worker_threads';
2
+ import { InMemory, Port, configureSingle, fs, resolveMountConfig, resolveRemoteMount } from '../../dist/index.js';
3
+ import { copy, data } from '../setup.js';
4
+
5
+ const { port1: localPort, port2: remotePort } = new MessageChannel();
6
+
7
+ fs.umount('/');
8
+ const tmpfs = await resolveMountConfig({ backend: InMemory, name: 'tmp' });
9
+
10
+ fs.mount('/', tmpfs);
11
+ copy(data, fs);
12
+
13
+ await resolveRemoteMount(remotePort, tmpfs);
14
+
15
+ await configureSingle({ backend: Port, port: localPort });