@zenfs/core 1.8.8 → 1.9.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 (116) hide show
  1. package/dist/backends/backend.d.ts +1 -1
  2. package/dist/backends/backend.js +7 -4
  3. package/dist/backends/fetch.d.ts +23 -32
  4. package/dist/backends/fetch.js +94 -134
  5. package/dist/backends/index.d.ts +1 -4
  6. package/dist/backends/index.js +1 -4
  7. package/dist/backends/memory.d.ts +7 -5
  8. package/dist/backends/memory.js +6 -4
  9. package/dist/backends/overlay.d.ts +4 -5
  10. package/dist/backends/overlay.js +16 -20
  11. package/dist/backends/passthrough.d.ts +3 -3
  12. package/dist/backends/passthrough.js +4 -6
  13. package/dist/backends/port/fs.d.ts +4 -5
  14. package/dist/backends/port/fs.js +7 -12
  15. package/dist/backends/port/rpc.d.ts +1 -1
  16. package/dist/backends/port/rpc.js +15 -13
  17. package/dist/backends/store/fs.d.ts +51 -40
  18. package/dist/backends/store/fs.js +347 -241
  19. package/dist/backends/store/map.d.ts +41 -0
  20. package/dist/backends/store/map.js +45 -0
  21. package/dist/backends/store/simple.d.ts +10 -58
  22. package/dist/backends/store/simple.js +8 -115
  23. package/dist/backends/store/store.d.ts +111 -44
  24. package/dist/backends/store/store.js +230 -38
  25. package/dist/config.d.ts +7 -3
  26. package/dist/config.js +17 -14
  27. package/dist/context.d.ts +1 -1
  28. package/dist/context.js +1 -1
  29. package/dist/index.d.ts +1 -5
  30. package/dist/index.js +1 -5
  31. package/dist/{devices.d.ts → internal/devices.d.ts} +4 -4
  32. package/dist/{devices.js → internal/devices.js} +18 -14
  33. package/dist/{file.d.ts → internal/file.d.ts} +3 -2
  34. package/dist/{file.js → internal/file.js} +17 -12
  35. package/dist/{backends/store → internal}/file_index.d.ts +13 -3
  36. package/dist/{backends/store → internal}/file_index.js +28 -5
  37. package/dist/{filesystem.d.ts → internal/filesystem.d.ts} +99 -32
  38. package/dist/internal/filesystem.js +83 -0
  39. package/dist/internal/index.d.ts +9 -0
  40. package/dist/internal/index.js +9 -0
  41. package/dist/internal/index_fs.d.ts +56 -0
  42. package/dist/internal/index_fs.js +188 -0
  43. package/dist/{backends/store → internal}/inode.d.ts +6 -1
  44. package/dist/{backends/store → internal}/inode.js +14 -6
  45. package/dist/internal/log.d.ts +132 -0
  46. package/dist/internal/log.js +177 -0
  47. package/dist/mixins/async.d.ts +2 -2
  48. package/dist/mixins/async.js +19 -16
  49. package/dist/mixins/mutexed.d.ts +9 -3
  50. package/dist/mixins/mutexed.js +22 -3
  51. package/dist/mixins/readonly.d.ts +2 -2
  52. package/dist/mixins/readonly.js +4 -3
  53. package/dist/mixins/shared.d.ts +1 -1
  54. package/dist/mixins/sync.d.ts +2 -2
  55. package/dist/stats.d.ts +2 -3
  56. package/dist/stats.js +7 -5
  57. package/dist/utils.d.ts +2 -15
  58. package/dist/utils.js +10 -47
  59. package/dist/vfs/async.d.ts +2 -2
  60. package/dist/vfs/async.js +3 -3
  61. package/dist/vfs/dir.js +1 -1
  62. package/dist/vfs/promises.d.ts +6 -6
  63. package/dist/vfs/promises.js +54 -49
  64. package/dist/vfs/shared.d.ts +3 -3
  65. package/dist/vfs/shared.js +16 -10
  66. package/dist/vfs/streams.js +1 -1
  67. package/dist/vfs/sync.d.ts +1 -2
  68. package/dist/vfs/sync.js +14 -15
  69. package/dist/vfs/types.d.ts +1 -0
  70. package/dist/vfs/watchers.d.ts +5 -1
  71. package/dist/vfs/watchers.js +16 -19
  72. package/package.json +3 -3
  73. package/readme.md +12 -12
  74. package/scripts/test.js +15 -3
  75. package/tests/backend/fetch.test.ts +49 -0
  76. package/tests/backend/port.test.ts +130 -0
  77. package/tests/common/context.test.ts +9 -4
  78. package/tests/common.ts +21 -3
  79. package/tests/data/image.jpg +0 -0
  80. package/tests/data/utf8.txt +1 -0
  81. package/tests/fetch/config.js +40 -0
  82. package/tests/fetch/fetch.ts +20 -0
  83. package/tests/fetch/run.sh +3 -3
  84. package/tests/fetch/{server.ts → server.js} +15 -11
  85. package/tests/fs/directory.test.ts +1 -1
  86. package/tests/fs/errors.test.ts +1 -1
  87. package/tests/fs/links.test.ts +1 -1
  88. package/tests/fs/open.test.ts +1 -1
  89. package/tests/fs/permissions.test.ts +2 -3
  90. package/tests/fs/rename.test.ts +1 -1
  91. package/tests/fs/stat.test.ts +1 -1
  92. package/tests/fs/times.test.ts +1 -1
  93. package/tests/fs/watch.test.ts +21 -22
  94. package/tests/fs/writeFile.test.ts +8 -7
  95. package/tests/readme.md +3 -3
  96. package/tests/setup/_overlay.ts +7 -0
  97. package/tests/setup/context.ts +2 -2
  98. package/tests/setup/index.ts +3 -3
  99. package/tests/setup/memory.ts +2 -2
  100. package/tests/setup/port.ts +2 -2
  101. package/tests/setup.ts +25 -5
  102. package/tests/tsconfig.json +3 -2
  103. package/dist/backends/store/index_fs.d.ts +0 -34
  104. package/dist/backends/store/index_fs.js +0 -67
  105. package/dist/filesystem.js +0 -52
  106. package/tests/fetch/cow+fetch.ts +0 -13
  107. package/tests/port/channel.test.ts +0 -39
  108. package/tests/port/config.test.ts +0 -30
  109. package/tests/port/remote.test.ts +0 -32
  110. package/tests/port/timeout.test.ts +0 -48
  111. /package/dist/{credentials.d.ts → internal/credentials.d.ts} +0 -0
  112. /package/dist/{credentials.js → internal/credentials.js} +0 -0
  113. /package/dist/{error.d.ts → internal/error.d.ts} +0 -0
  114. /package/dist/{error.js → internal/error.js} +0 -0
  115. /package/tests/{port → backend}/config.worker.js +0 -0
  116. /package/tests/{port → backend}/remote.worker.js +0 -0
@@ -41,6 +41,7 @@ export declare class FSWatcher<T extends string | Buffer = string | Buffer> exte
41
41
  error: [error: Error];
42
42
  }> implements fs.FSWatcher {
43
43
  readonly options: fs.WatchOptions;
44
+ protected readonly realpath: string;
44
45
  constructor(context: V_Context, path: string, options: fs.WatchOptions);
45
46
  close(): void;
46
47
  [Symbol.dispose](): void;
@@ -71,5 +72,8 @@ export declare class StatWatcher extends Watcher<{
71
72
  }
72
73
  export declare function addWatcher(path: string, watcher: FSWatcher): void;
73
74
  export declare function removeWatcher(path: string, watcher: FSWatcher): void;
74
- export declare function emitChange(eventType: fs.WatchEventType, filename: string): void;
75
+ /**
76
+ * @internal @hidden
77
+ */
78
+ export declare function emitChange(context: V_Context, eventType: fs.WatchEventType, filename: string): void;
75
79
  export {};
@@ -1,8 +1,8 @@
1
1
  import { EventEmitter } from 'eventemitter3';
2
- import { ErrnoError } from '../error.js';
2
+ import { ErrnoError } from '../internal/error.js';
3
3
  import { isStatsEqual } from '../stats.js';
4
4
  import { normalizePath } from '../utils.js';
5
- import { basename, dirname } from './path.js';
5
+ import { dirname, join, relative } from './path.js';
6
6
  import { statSync } from './sync.js';
7
7
  /**
8
8
  * Base class for file system watchers.
@@ -59,11 +59,12 @@ export class FSWatcher extends Watcher {
59
59
  constructor(context, path, options) {
60
60
  super(context, path);
61
61
  this.options = options;
62
- addWatcher(path.toString(), this);
62
+ this.realpath = (context === null || context === void 0 ? void 0 : context.root) ? join(context.root, path) : path;
63
+ addWatcher(this.realpath, this);
63
64
  }
64
65
  close() {
65
66
  super.emit('close');
66
- removeWatcher(this.path.toString(), this);
67
+ removeWatcher(this.realpath, this);
67
68
  }
68
69
  [Symbol.dispose]() {
69
70
  this.close();
@@ -134,28 +135,24 @@ export function removeWatcher(path, watcher) {
134
135
  }
135
136
  }
136
137
  }
137
- export function emitChange(eventType, filename) {
138
+ /**
139
+ * @internal @hidden
140
+ */
141
+ export function emitChange(context, eventType, filename) {
138
142
  var _a;
143
+ if (context)
144
+ filename = join((_a = context.root) !== null && _a !== void 0 ? _a : '/', filename);
139
145
  filename = normalizePath(filename);
140
- // Notify watchers on the specific file
141
- if (watchers.has(filename)) {
142
- for (const watcher of watchers.get(filename)) {
143
- watcher.emit('change', eventType, basename(filename));
144
- }
145
- }
146
146
  // Notify watchers on parent directories if they are watching recursively
147
147
  let parent = filename, normalizedFilename;
148
- while (parent !== normalizedFilename) {
148
+ while (normalizedFilename != '/') {
149
149
  normalizedFilename = parent;
150
150
  parent = dirname(parent);
151
- if (!watchers.has(parent))
151
+ const parentWatchers = watchers.get(parent);
152
+ if (!parentWatchers)
152
153
  continue;
153
- for (const watcher of watchers.get(parent)) {
154
- // Strip the context root from the path if the watcher has a context
155
- const root = (_a = watcher._context) === null || _a === void 0 ? void 0 : _a.root;
156
- const contextPath = root && filename.startsWith(root) ? filename.slice(root.length) : filename;
157
- const relativePath = contextPath.slice(parent.length + (parent == '/' ? 0 : 1));
158
- watcher.emit('change', eventType, relativePath);
154
+ for (const watcher of parentWatchers) {
155
+ watcher.emit('change', eventType, relative(parent, filename));
159
156
  }
160
157
  }
161
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.8.8",
3
+ "version": "1.9.1",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -58,7 +58,7 @@
58
58
  "format": "prettier --write .",
59
59
  "format:check": "prettier --check .",
60
60
  "lint": "eslint src tests",
61
- "test": "npx zenfs-test --clean && npx zenfs-test -abcfp && tests/fetch/run.sh && npx zenfs-test --report",
61
+ "test": "npx zenfs-test --clean; npx zenfs-test -abcfp; tests/fetch/run.sh; npx zenfs-test --report",
62
62
  "build": "tsc -p tsconfig.json",
63
63
  "build:docs": "typedoc",
64
64
  "dev": "npm run build -- --watch",
@@ -70,7 +70,7 @@
70
70
  "buffer": "^6.0.3",
71
71
  "eventemitter3": "^5.0.1",
72
72
  "readable-stream": "^4.5.2",
73
- "utilium": "^1.1.1"
73
+ "utilium": "^1.2.10"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@eslint/js": "^9.8.0",
package/readme.md CHANGED
@@ -6,11 +6,11 @@ ZenFS is a cross-platform library that emulates the [NodeJS filesystem API](http
6
6
 
7
7
  ZenFS is modular and extensible. The core includes some built-in backends:
8
8
 
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
- - `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
12
- - `Port`: Interacts with a remote over a `MessagePort`-like interface (e.g. a worker)
13
- - `Passthrough`: Use an existing `node:fs` interface with ZenFS
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
+ - `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
12
+ - `Port`: Interacts with a remote over a `MessagePort`-like interface (e.g. a worker)
13
+ - `Passthrough`: Use an existing `node:fs` interface with ZenFS
14
14
 
15
15
  ZenFS supports a number of other backends. Many are provided as separate packages under `@zenfs`. More backends can be defined by separate libraries by extending the `FileSystem` class and providing a `Backend` object.
16
16
 
@@ -207,18 +207,18 @@ A huge thank you to [![Deco.cx logo](https://avatars.githubusercontent.com/deco-
207
207
 
208
208
  ## Building
209
209
 
210
- - Make sure you have Node and NPM installed. You must have Node v18 or newer.
211
- - Install dependencies with `npm install`
212
- - Build using `npm run build`
213
- - You can find the built code in `dist`.
210
+ - Make sure you have Node and NPM installed. You must have Node v18 or newer.
211
+ - Install dependencies with `npm install`
212
+ - Build using `npm run build`
213
+ - You can find the built code in `dist`.
214
214
 
215
215
  ### Testing
216
216
 
217
217
  Run unit tests with:
218
218
 
219
- - `npm test` to run all tests using the default configuration
220
- - `npx zenfs-test -abc` to run the common tests and run the full FS suite against all included backends
221
- - You can also run this command to test your own backends, the `--auto` (`-a`) flag will automatically detect any setup scripts matching `tests/setup/*` or `tests/setup-*.ts`. If you do, you'll need to include the `c8` dependency for coverage.
219
+ - `npm test` to run all tests using the default configuration
220
+ - `npx zenfs-test -abc` to run the common tests and run the full FS suite against all included backends
221
+ - You can also run this command to test your own backends, the `--auto` (`-a`) flag will automatically detect any setup scripts matching `tests/setup/*` or `tests/setup-*.ts`. If you do, you'll need to include the `c8` dependency for coverage.
222
222
 
223
223
  ### BrowserFS Fork
224
224
 
package/scripts/test.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { execSync } from 'node:child_process';
4
4
  import { existsSync, globSync, mkdirSync, rmSync } from 'node:fs';
5
- import { join, parse } from 'node:path';
5
+ import { join, parse, basename } from 'node:path';
6
6
  import { parseArgs } from 'node:util';
7
7
 
8
8
  const { values: options, positionals } = parseArgs({
@@ -11,6 +11,7 @@ const { values: options, positionals } = parseArgs({
11
11
  help: { short: 'h', type: 'boolean', default: false },
12
12
  verbose: { short: 'w', type: 'boolean', default: false },
13
13
  quiet: { short: 'q', type: 'boolean', default: false },
14
+ log: { short: 'l', type: 'string', default: '' },
14
15
  'file-names': { short: 'N', type: 'boolean', default: false },
15
16
  ci: { short: 'C', type: 'boolean', default: false },
16
17
 
@@ -50,6 +51,7 @@ Output:
50
51
  -h, --help Outputs this help message
51
52
  -w, --verbose Output verbose messages
52
53
  -q, --quiet Don't output normal messages
54
+ -l, --logs <level> Change the default log level for test output. Level can be a number or string
53
55
  -N, --file-names Use full file paths for tests from setup files instead of the base name
54
56
  -C, --ci Continuous integration (CI) mode. This interacts with the Github
55
57
  Checks API for better test status. Requires @octokit/action
@@ -68,6 +70,7 @@ if (options.quiet && options.verbose) {
68
70
  }
69
71
 
70
72
  process.env.NODE_V8_COVERAGE = options.coverage;
73
+ process.env.ZENFS_LOG_LEVEL = options.log;
71
74
 
72
75
  if (options.clean) {
73
76
  rmSync(options.coverage, { recursive: true, force: true });
@@ -143,6 +146,10 @@ async function status(name) {
143
146
  if (!options.quiet) console.log(`${color('passed', 32)}: ${name} ${time()}`);
144
147
  if (options.ci) await ci.completeCheck(name, 'success');
145
148
  },
149
+ async skip() {
150
+ if (!options.quiet) console.log(`${color('skipped', 33)}: ${name} ${time()}`);
151
+ if (options.ci) await ci.completeCheck(name, 'skipped');
152
+ },
146
153
  async fail() {
147
154
  console.error(`${color('failed', '1;31')}: ${name} ${time()}`);
148
155
  if (options.ci) await ci.completeCheck(name, 'failure');
@@ -182,12 +189,17 @@ for (const setupFile of positionals) {
182
189
 
183
190
  !options.quiet && console.log('Running tests:', name);
184
191
 
185
- const { pass, fail } = await status(name);
192
+ const { pass, fail, skip } = await status(name);
193
+
194
+ if (basename(setupFile).startsWith('_')) {
195
+ await skip();
196
+ continue;
197
+ }
186
198
 
187
199
  try {
188
200
  execSync(
189
201
  [
190
- 'tsx',
202
+ 'tsx --trace-deprecation',
191
203
  options.inspect ? 'inspect' : '',
192
204
  '--test --experimental-test-coverage',
193
205
  options.force ? '--test-force-exit' : '',
@@ -0,0 +1,49 @@
1
+ import assert from 'node:assert/strict';
2
+ import { join } from 'node:path';
3
+ import { suite, test } from 'node:test';
4
+ import { Worker } from 'node:worker_threads';
5
+ import { Fetch, configureSingle, fs } from '../../dist/index.js';
6
+ import { baseUrl, defaultEntries, indexPath, whenServerReady } from '../fetch/config.js';
7
+
8
+ const server = new Worker(join(import.meta.dirname, '../fetch/server.js'));
9
+
10
+ await whenServerReady();
11
+
12
+ await suite('Fetch with `disableAsyncCache`', () => {
13
+ test('Configuration', async () => {
14
+ await configureSingle({
15
+ backend: Fetch,
16
+ disableAsyncCache: true,
17
+ remoteWrite: true,
18
+ baseUrl,
19
+ index: baseUrl + indexPath,
20
+ });
21
+ });
22
+
23
+ test('Read and write file', async () => {
24
+ await fs.promises.writeFile('/example', 'test');
25
+
26
+ const contents = await fs.promises.readFile('/example', 'utf8');
27
+
28
+ assert.equal(contents, 'test');
29
+ });
30
+
31
+ test('Make new directory', async () => {
32
+ await fs.promises.mkdir('/duck');
33
+ const stats = await fs.promises.stat('/duck');
34
+ assert(stats.isDirectory());
35
+ });
36
+
37
+ test('Read directory', async () => {
38
+ const entries = await fs.promises.readdir('/');
39
+
40
+ assert.deepEqual(entries, [...defaultEntries, 'example', 'duck']);
41
+ });
42
+
43
+ test('Uncached synchronous operations throw', () => {
44
+ assert.throws(() => fs.readFileSync('/x.txt', 'utf8'), { code: 'EAGAIN' });
45
+ });
46
+ });
47
+
48
+ await server.terminate();
49
+ server.unref();
@@ -0,0 +1,130 @@
1
+ import assert from 'node:assert/strict';
2
+ import { suite, test } from 'node:test';
3
+ import { MessageChannel, Worker } from 'node:worker_threads';
4
+ import { Port, attachFS } from '../../dist/backends/port/fs.js';
5
+ import type { InMemoryStore, StoreFS } from '../../dist/index.js';
6
+ import { ErrnoError, InMemory, configure, configureSingle, fs, resolveMountConfig } from '../../dist/index.js';
7
+
8
+ // Tests a mis-configured `Port` using a MessageChannel
9
+
10
+ const timeoutChannel = new MessageChannel();
11
+ timeoutChannel.port2.unref();
12
+
13
+ await suite('Timeout', { timeout: 1000 }, () => {
14
+ test('Misconfiguration', async () => {
15
+ let error: ErrnoError;
16
+ try {
17
+ await configure({
18
+ mounts: {
19
+ '/tmp-timeout': { backend: InMemory, name: 'tmp' },
20
+ '/port': { backend: Port, port: timeoutChannel.port1, timeout: 100 },
21
+ },
22
+ });
23
+ } catch (e) {
24
+ assert(e instanceof ErrnoError);
25
+ error = e;
26
+ }
27
+ assert(error! instanceof ErrnoError);
28
+ assert.equal(error.code, 'EIO');
29
+ assert(error.message.includes('RPC Failed'));
30
+ });
31
+
32
+ test('Remote not attached', async () => {
33
+ let error: ErrnoError;
34
+ try {
35
+ await configureSingle({ backend: Port, port: timeoutChannel.port1, timeout: 100 });
36
+ await fs.promises.writeFile('/test', 'anything');
37
+ } catch (e) {
38
+ assert(e instanceof ErrnoError);
39
+ error = e;
40
+ }
41
+ assert(error! instanceof ErrnoError);
42
+ assert.equal(error.code, 'EIO');
43
+ assert(error.message.includes('RPC Failed'));
44
+ });
45
+ });
46
+
47
+ timeoutChannel.port1.unref();
48
+
49
+ // Test configuration
50
+
51
+ const configPort = new Worker(import.meta.dirname + '/config.worker.js');
52
+
53
+ await suite('Remote FS with resolveRemoteMount', () => {
54
+ const content = 'FS is in a port';
55
+
56
+ test('Configuration', async () => {
57
+ await configureSingle({ backend: Port, port: configPort, timeout: 500 });
58
+ });
59
+
60
+ test('Write', async () => {
61
+ await fs.promises.writeFile('/test', content);
62
+ });
63
+
64
+ test('Read', async () => {
65
+ assert((await fs.promises.readFile('/test', 'utf8')) === content);
66
+ });
67
+ });
68
+
69
+ await configPort?.terminate();
70
+ configPort.unref();
71
+
72
+ // Test using a message channel
73
+
74
+ const channel = new MessageChannel(),
75
+ content = 'FS is in a port';
76
+ let tmpfs: StoreFS<InMemoryStore>;
77
+
78
+ await suite('FS with MessageChannel', () => {
79
+ test('configuration', async () => {
80
+ tmpfs = await resolveMountConfig({ backend: InMemory, name: 'tmp' });
81
+ attachFS(channel.port2, tmpfs);
82
+ await configureSingle({ backend: Port, port: channel.port1, disableAsyncCache: true, timeout: 250 });
83
+ });
84
+
85
+ test('write', async () => {
86
+ await fs.promises.writeFile('/test', content);
87
+ });
88
+
89
+ test('remote content', () => {
90
+ fs.mount('/tmp', tmpfs);
91
+ assert(fs.readFileSync('/tmp/test', 'utf8') == content);
92
+ fs.umount('/tmp');
93
+ });
94
+
95
+ test('read', async () => {
96
+ assert((await fs.promises.readFile('/test', 'utf8')) === content);
97
+ });
98
+
99
+ test('readFileSync should throw', () => {
100
+ assert.throws(() => fs.readFileSync('/test', 'utf8'), { code: 'ENOTSUP' });
101
+ });
102
+ });
103
+
104
+ channel.port1.unref();
105
+ channel.port2.unref();
106
+
107
+ // Test using a worker
108
+
109
+ const remotePort = new Worker(import.meta.dirname + '/remote.worker.js');
110
+
111
+ await suite('Remote FS', () => {
112
+ const content = 'FS is in a port';
113
+
114
+ test('Configuration', async () => {
115
+ await configureSingle({ backend: Port, port: remotePort, timeout: 500 });
116
+ });
117
+
118
+ test('Write', async () => {
119
+ await fs.promises.writeFile('/test', content);
120
+ });
121
+
122
+ test('Read', async () => {
123
+ assert.equal(await fs.promises.readFile('/test', 'utf8'), content);
124
+ });
125
+
126
+ test('Cleanup', async () => {});
127
+ });
128
+
129
+ await remotePort.terminate();
130
+ remotePort.unref();
@@ -2,6 +2,7 @@ import { suite, test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { bindContext } from '../../dist/context.js';
4
4
  import * as fs from '../../dist/vfs/index.js';
5
+ import { canary } from 'utilium';
5
6
 
6
7
  fs.mkdirSync('/ctx');
7
8
  const { fs: ctx } = bindContext('/ctx');
@@ -38,19 +39,23 @@ suite('Context', () => {
38
39
  });
39
40
 
40
41
  test('watch should consider context', async () => {
41
- let lastFile: string,
42
+ let lastFile: string | null = null,
42
43
  events = 0;
43
44
  const watcher = ctx.promises.watch('/', { recursive: true });
44
45
 
45
- (async () => {
46
+ const silence = canary();
47
+ const promise = (async () => {
46
48
  for await (const event of watcher) {
47
- lastFile = event.filename!;
49
+ lastFile = event.filename;
48
50
  if (++events == 2) return;
49
51
  }
50
52
  })();
53
+ silence();
51
54
  await ctx.promises.writeFile('/xpto.txt', 'in real root');
52
- assert.equal(lastFile!, 'xpto.txt');
55
+ assert.equal(lastFile, 'xpto.txt');
53
56
  await ctx.promises.unlink('/xpto.txt');
54
57
  assert.equal(lastFile, 'xpto.txt');
58
+ await watcher.return!();
59
+ await promise;
55
60
  });
56
61
  });
package/tests/common.ts CHANGED
@@ -1,8 +1,26 @@
1
1
  import { join, resolve } from 'node:path';
2
- import { fs as defaultFS } from '../dist/index.js';
3
- export type * from '../dist/vfs/index.js';
2
+ import { fs as defaultFS, log } from '../dist/index.js';
3
+ export type * from '../dist/index.js';
4
4
 
5
- const setupPath = resolve(process.env.SETUP || join(import.meta.dirname, 'setup/memory.ts'));
5
+ const { ZENFS_LOG_LEVEL, SETUP } = process.env;
6
+
7
+ let level: log.Level | (typeof log.levels)[log.Level] = log.Level.CRIT;
8
+
9
+ if (ZENFS_LOG_LEVEL) {
10
+ const tmp = parseInt(ZENFS_LOG_LEVEL);
11
+ if (Number.isSafeInteger(tmp)) level = tmp;
12
+ else level = ZENFS_LOG_LEVEL as (typeof log.levels)[log.Level];
13
+ }
14
+
15
+ log.configure({
16
+ enabled: true,
17
+ format: log.formats.ansi_message,
18
+ dumpBacklog: true,
19
+ level,
20
+ output: console.error,
21
+ });
22
+
23
+ const setupPath = resolve(SETUP || join(import.meta.dirname, 'setup/memory.ts'));
6
24
 
7
25
  const setup = await import(setupPath).catch(error => {
8
26
  console.log('Failed to import test setup:');
Binary file
@@ -0,0 +1 @@
1
+ 南越国是前203年至前111年存在于岭南地区的一个国家,国都位于番禺,疆域包括今天中国的广东、广西两省区的大部份地区,福建省、湖南、贵州、云南的一小部份地区和越南的北部。南越国是秦朝灭亡后,由南海郡尉赵佗于前203年起兵兼并桂林郡和象郡后建立。前196年和前179年,南越国曾先后两次名义上臣属于西汉,成为西汉的“外臣”。前112年,南越国末代君主赵建德与西汉发生战争,被汉武帝于前111年所灭。南越国共存在93年,历经五代君主。南越国是岭南地区的第一个有记载的政权国家,采用封建制和郡县制并存的制度,它的建立保证了秦末乱世岭南地区社会秩序的稳定,有效的改善了岭南地区落后的政治、经济现状。
@@ -0,0 +1,40 @@
1
+ import { existsSync, mkdirSync, readdirSync } from 'node:fs';
2
+ import { createConnection } from 'node:net';
3
+ import { join } from 'node:path';
4
+
5
+ // Copied from setup.ts
6
+
7
+ export const data = join(import.meta.dirname, '../data');
8
+
9
+ export const defaultEntries = readdirSync(data);
10
+
11
+ export const tmp = join(import.meta.dirname, '../tmp');
12
+
13
+ if (!existsSync(tmp)) mkdirSync(tmp);
14
+
15
+ export const port = 26514;
16
+
17
+ export const baseUrl = 'http://localhost:' + port;
18
+
19
+ export const indexPath = '/.index.json';
20
+
21
+ export function whenServerReady() {
22
+ const { promise, resolve, reject } = Promise.withResolvers();
23
+
24
+ const timeout = setTimeout(reject, 10_000);
25
+
26
+ const interval = setInterval(() => {
27
+ const socket = createConnection(port)
28
+ .on('connect', () => {
29
+ socket.end();
30
+ clearTimeout(timeout);
31
+ clearInterval(interval);
32
+ resolve();
33
+ })
34
+ .on('error', () => {
35
+ // ignore
36
+ });
37
+ }, 250);
38
+
39
+ return promise;
40
+ }
@@ -0,0 +1,20 @@
1
+ import { configure, Fetch } from '../../dist/index.js';
2
+ import { baseUrl } from './config.js';
3
+ import * as log from '../../dist/internal/log.js';
4
+
5
+ await configure({
6
+ mounts: {
7
+ '/': {
8
+ backend: Fetch,
9
+ baseUrl,
10
+ index: baseUrl + '/.index.json',
11
+ },
12
+ },
13
+ log: {
14
+ enabled: true,
15
+ output: console.error,
16
+ format: log.formats.ansi_message,
17
+ level: log.Level.INFO,
18
+ dumpBacklog: true,
19
+ },
20
+ });
@@ -3,14 +3,14 @@
3
3
  # Credit: Dave Dopson, https://stackoverflow.com/a/246128/17637456
4
4
  SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
5
5
 
6
- npx tsx $SCRIPT_DIR/server.ts &
6
+ node $SCRIPT_DIR/server.js &
7
7
  PID=$!
8
8
 
9
9
  echo "Waiting for server to start..."
10
10
  until nc -z localhost 26514; do
11
- sleep 0.5
11
+ sleep 0.25
12
12
  done
13
13
 
14
- npx zenfs-test $SCRIPT_DIR/cow+fetch.ts --preserve --force "$@"
14
+ npx zenfs-test $SCRIPT_DIR/fetch.ts --preserve --force "$@"
15
15
 
16
16
  kill $PID
@@ -2,10 +2,7 @@ import { execSync } from 'node:child_process';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { createServer } from 'node:http';
4
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;
5
+ import { data, port, tmp, indexPath } from './config.js';
9
6
 
10
7
  const statusCodes = {
11
8
  ENOENT: 404,
@@ -13,7 +10,7 @@ const statusCodes = {
13
10
 
14
11
  try {
15
12
  execSync(`npm exec make-index -- ${data} --output ${tmp}/index.json --quiet`, { stdio: 'inherit' });
16
- } catch (e: any) {
13
+ } catch (e) {
17
14
  if (e.signal == 'SIGINT') {
18
15
  console.log('Aborted whilst creating index.');
19
16
  process.exit(0);
@@ -24,20 +21,27 @@ try {
24
21
  }
25
22
 
26
23
  const server = createServer((request, response) => {
27
- const { url = '' } = request;
24
+ response.statusCode = 200;
25
+ const { url = '', method } = request;
26
+
27
+ /**
28
+ * @todo Test POST/DELETE
29
+ */
30
+ if (method != 'GET') {
31
+ response.end();
32
+ return;
33
+ }
28
34
 
29
35
  if (url == '/.ping') {
30
- response.statusCode = 200;
31
36
  response.end('pong');
32
37
  return;
33
38
  }
34
39
 
35
- const path = url == '/.index.json' ? join(tmp, 'index.json') : join(data, url.slice(1) || '');
40
+ const path = url == indexPath ? join(tmp, 'index.json') : join(data, url.slice(1) || '');
36
41
  try {
37
- response.statusCode = 200;
38
42
  response.end(readFileSync(path));
39
- } catch (e: any) {
40
- response.statusCode = statusCodes[e.code as keyof typeof statusCodes] || 400;
43
+ } catch (e) {
44
+ response.statusCode = statusCodes[e.code] || 400;
41
45
  response.end();
42
46
  }
43
47
  });
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
- import { ErrnoError } from '../../dist/error.js';
3
+ import { ErrnoError } from '../../dist/index.js';
4
4
  import { fs } from '../common.js';
5
5
 
6
6
  const testDir = 'test-dir';
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import test, { suite } from 'node:test';
3
- import type { ErrnoError } from '../../dist/error.js';
4
3
  import { fs } from '../common.js';
4
+ import type { ErrnoError } from '../../dist/index.js';
5
5
 
6
6
  const existingFile = '/exit.js';
7
7
 
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
3
  import { join } from '../../dist/vfs/path.js';
4
4
  import { fs } from '../common.js';
5
- import type { ErrnoError } from '../../dist/error.js';
5
+ import type { ErrnoError } from '../../dist/index.js';
6
6
 
7
7
  suite('Links', () => {
8
8
  const target = '/a1.js',
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
- import { ErrnoError } from '../../dist/error.js';
3
+ import { ErrnoError } from '../../dist/index.js';
4
4
  import { fs } from '../common.js';
5
5
 
6
6
  suite('fs file opening', () => {
@@ -1,10 +1,9 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
- import { credentials } from '../../dist/credentials.js';
3
+ import { credentials, ErrnoError } from '../../dist/index.js';
4
+ import { encodeUTF8 } from '../../dist/utils.js';
4
5
  import { R_OK, W_OK, X_OK } from '../../dist/vfs/constants.js';
5
6
  import { join } from '../../dist/vfs/path.js';
6
- import { ErrnoError } from '../../dist/error.js';
7
- import { encodeUTF8 } from '../../dist/utils.js';
8
7
  import { fs } from '../common.js';
9
8
 
10
9
  const asyncMode = 0o777;
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
- import { ErrnoError } from '../../dist/error.js';
3
+ import { ErrnoError } from '../../dist/index.js';
4
4
  import { fs } from '../common.js';
5
5
 
6
6
  suite('Rename', () => {