@zenfs/core 2.2.3 → 2.3.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.
- package/dist/backends/backend.js +6 -9
- package/dist/backends/cow.js +4 -4
- package/dist/backends/fetch.js +8 -6
- package/dist/backends/memory.js +4 -2
- package/dist/backends/passthrough.js +2 -0
- package/dist/backends/port.d.ts +16 -89
- package/dist/backends/port.js +35 -171
- package/dist/backends/single_buffer.d.ts +2 -2
- package/dist/backends/single_buffer.js +165 -198
- package/dist/backends/store/fs.js +50 -73
- package/dist/backends/store/map.js +1 -2
- package/dist/backends/store/store.js +23 -27
- package/dist/config.js +2 -3
- package/dist/context.js +2 -2
- package/dist/internal/devices.js +7 -10
- package/dist/internal/file_index.js +3 -8
- package/dist/internal/filesystem.js +19 -12
- package/dist/internal/index_fs.js +3 -4
- package/dist/internal/inode.js +144 -187
- package/dist/internal/rpc.d.ts +143 -0
- package/dist/internal/rpc.js +251 -0
- package/dist/mixins/async.js +5 -6
- package/dist/mixins/mutexed.js +16 -10
- package/dist/path.js +3 -4
- package/dist/polyfills.js +51 -22
- package/dist/readline.js +32 -30
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +11 -5
- package/dist/vfs/acl.js +44 -68
- package/dist/vfs/async.js +4 -4
- package/dist/vfs/dir.js +12 -8
- package/dist/vfs/file.js +22 -18
- package/dist/vfs/ioctl.js +39 -62
- package/dist/vfs/promises.js +48 -39
- package/dist/vfs/shared.js +4 -5
- package/dist/vfs/stats.js +104 -77
- package/dist/vfs/streams.js +11 -8
- package/dist/vfs/sync.js +23 -26
- package/dist/vfs/watchers.js +9 -3
- package/dist/vfs/xattr.js +6 -12
- package/package.json +2 -2
- package/scripts/test.js +16 -7
- package/tests/backend/fetch.test.ts +14 -14
- package/tests/backend/port.test.ts +25 -17
- package/tests/common/handle.test.ts +5 -3
- package/tests/fetch/run.sh +2 -1
- package/tests/fs/scaling.test.ts +32 -0
- package/tests/fs/watch.test.ts +2 -5
- package/tests/setup/single-buffer.ts +1 -1
- package/tests/tsconfig.json +3 -2
- package/types/uint8array.d.ts +64 -0
package/dist/vfs/sync.js
CHANGED
|
@@ -134,7 +134,7 @@ export function statSync(path, options) {
|
|
|
134
134
|
}
|
|
135
135
|
if (checkAccess && !hasAccess(this, stats, constants.R_OK))
|
|
136
136
|
throw UV('EACCES', { syscall: 'stat', path });
|
|
137
|
-
return
|
|
137
|
+
return options?.bigint ? new BigIntStats(stats) : new Stats(stats);
|
|
138
138
|
}
|
|
139
139
|
statSync;
|
|
140
140
|
export function lstatSync(path, options) {
|
|
@@ -143,14 +143,14 @@ export function lstatSync(path, options) {
|
|
|
143
143
|
const stats = wrap(fs, 'statSync', path)(resolved);
|
|
144
144
|
if (checkAccess && !hasAccess(this, stats, constants.R_OK))
|
|
145
145
|
throw UV('EACCES', { syscall: 'lstat', path });
|
|
146
|
-
return
|
|
146
|
+
return options?.bigint ? new BigIntStats(stats) : new Stats(stats);
|
|
147
147
|
}
|
|
148
148
|
lstatSync;
|
|
149
149
|
export function truncateSync(path, len = 0) {
|
|
150
150
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
151
151
|
try {
|
|
152
152
|
const file = __addDisposableResource(env_1, _openSync.call(this, path, { flag: 'r+' }), false);
|
|
153
|
-
len
|
|
153
|
+
len ||= 0;
|
|
154
154
|
if (len < 0)
|
|
155
155
|
throw UV('EINVAL', 'truncate', path.toString());
|
|
156
156
|
file.truncate(len);
|
|
@@ -180,7 +180,6 @@ export function unlinkSync(path) {
|
|
|
180
180
|
}
|
|
181
181
|
unlinkSync;
|
|
182
182
|
function _openSync(path, opt) {
|
|
183
|
-
var _a;
|
|
184
183
|
path = normalizePath(path);
|
|
185
184
|
const mode = normalizeMode(opt.mode, 0o644), flag = flags.parse(opt.flag);
|
|
186
185
|
path = opt.preserveSymlinks ? path : realpathSync.call(this, path);
|
|
@@ -209,7 +208,7 @@ function _openSync(path, opt) {
|
|
|
209
208
|
if (checkAccess && !hasAccess(this, parentStats, constants.W_OK)) {
|
|
210
209
|
throw UV('EACCES', 'open', path);
|
|
211
210
|
}
|
|
212
|
-
const { euid: uid, egid: gid } =
|
|
211
|
+
const { euid: uid, egid: gid } = this?.credentials ?? defaultContext.credentials;
|
|
213
212
|
const inode = fs.createFileSync(resolved, {
|
|
214
213
|
mode,
|
|
215
214
|
uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
|
|
@@ -339,7 +338,7 @@ export function appendFileSync(filename, data, _options = {}) {
|
|
|
339
338
|
appendFileSync;
|
|
340
339
|
export function fstatSync(fd, options) {
|
|
341
340
|
const stats = fromFD(this, fd).stat();
|
|
342
|
-
return
|
|
341
|
+
return options?.bigint ? new BigIntStats(stats) : new Stats(stats);
|
|
343
342
|
}
|
|
344
343
|
fstatSync;
|
|
345
344
|
export function closeSync(fd) {
|
|
@@ -348,7 +347,7 @@ export function closeSync(fd) {
|
|
|
348
347
|
}
|
|
349
348
|
closeSync;
|
|
350
349
|
export function ftruncateSync(fd, len = 0) {
|
|
351
|
-
len
|
|
350
|
+
len ||= 0;
|
|
352
351
|
if (len < 0) {
|
|
353
352
|
throw new Exception(Errno.EINVAL);
|
|
354
353
|
}
|
|
@@ -381,7 +380,7 @@ export function writeSync(fd, data, posOrOff, lenOrEnc, pos) {
|
|
|
381
380
|
position = typeof pos === 'number' ? pos : null;
|
|
382
381
|
}
|
|
383
382
|
const file = fromFD(this, fd);
|
|
384
|
-
position
|
|
383
|
+
position ??= file.position;
|
|
385
384
|
const bytesWritten = file.write(buffer, offset, length, position);
|
|
386
385
|
emitChange(this, 'change', file.path);
|
|
387
386
|
return bytesWritten;
|
|
@@ -441,10 +440,9 @@ export function rmdirSync(path) {
|
|
|
441
440
|
}
|
|
442
441
|
rmdirSync;
|
|
443
442
|
export function mkdirSync(path, options) {
|
|
444
|
-
|
|
445
|
-
const { euid: uid, egid: gid } = (_a = this === null || this === void 0 ? void 0 : this.credentials) !== null && _a !== void 0 ? _a : defaultContext.credentials;
|
|
443
|
+
const { euid: uid, egid: gid } = this?.credentials ?? defaultContext.credentials;
|
|
446
444
|
options = typeof options === 'object' ? options : { mode: options };
|
|
447
|
-
const mode = normalizeMode(options
|
|
445
|
+
const mode = normalizeMode(options?.mode, 0o777);
|
|
448
446
|
path = realpathSync.call(this, path);
|
|
449
447
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
450
448
|
const __create = (path, resolved, parent) => {
|
|
@@ -458,7 +456,7 @@ export function mkdirSync(path, options) {
|
|
|
458
456
|
emitChange(this, 'rename', path);
|
|
459
457
|
return inode;
|
|
460
458
|
};
|
|
461
|
-
if (!
|
|
459
|
+
if (!options?.recursive) {
|
|
462
460
|
__create(path, resolved, wrap(fs, 'statSync', dirname(path))(dirname(resolved)));
|
|
463
461
|
return;
|
|
464
462
|
}
|
|
@@ -495,16 +493,16 @@ export function readdirSync(path, options) {
|
|
|
495
493
|
catch {
|
|
496
494
|
continue;
|
|
497
495
|
}
|
|
498
|
-
if (options
|
|
496
|
+
if (options?.withFileTypes) {
|
|
499
497
|
values.push(new Dirent(entry, entryStat, options.encoding));
|
|
500
498
|
}
|
|
501
|
-
else if (
|
|
499
|
+
else if (options?.encoding == 'buffer') {
|
|
502
500
|
values.push(Buffer.from(entry));
|
|
503
501
|
}
|
|
504
502
|
else {
|
|
505
503
|
values.push(entry);
|
|
506
504
|
}
|
|
507
|
-
if (!isDirectory(entryStat) || !
|
|
505
|
+
if (!isDirectory(entryStat) || !options?.recursive)
|
|
508
506
|
continue;
|
|
509
507
|
for (const subEntry of readdirSync.call(this, join(path, entry), options)) {
|
|
510
508
|
if (subEntry instanceof Dirent) {
|
|
@@ -576,12 +574,12 @@ export function readlinkSync(path, options) {
|
|
|
576
574
|
const size = handle.inode.size;
|
|
577
575
|
const data = Buffer.alloc(size);
|
|
578
576
|
handle.read(data, 0, size, 0);
|
|
579
|
-
const encoding = typeof options == 'object' ? options
|
|
577
|
+
const encoding = typeof options == 'object' ? options?.encoding : options;
|
|
580
578
|
if (encoding == 'buffer') {
|
|
581
579
|
return data;
|
|
582
580
|
}
|
|
583
581
|
// always defaults to utf-8 to avoid wrangler (cloudflare) worker "unknown encoding" exception
|
|
584
|
-
return data.toString(encoding
|
|
582
|
+
return data.toString(encoding ?? 'utf-8');
|
|
585
583
|
}
|
|
586
584
|
catch (e_6) {
|
|
587
585
|
env_6.error = e_6;
|
|
@@ -680,8 +678,7 @@ function _resolveSync($, path, preserveSymlinks) {
|
|
|
680
678
|
return _resolveSync($, target);
|
|
681
679
|
}
|
|
682
680
|
export function realpathSync(path, options) {
|
|
683
|
-
|
|
684
|
-
const encoding = typeof options == 'string' ? options : ((_a = options === null || options === void 0 ? void 0 : options.encoding) !== null && _a !== void 0 ? _a : 'utf8');
|
|
681
|
+
const encoding = typeof options == 'string' ? options : (options?.encoding ?? 'utf8');
|
|
685
682
|
path = normalizePath(path);
|
|
686
683
|
const { fullPath } = _resolveSync(this, path);
|
|
687
684
|
if (encoding == 'utf8' || encoding == 'utf-8')
|
|
@@ -711,14 +708,14 @@ export function rmSync(path, options) {
|
|
|
711
708
|
stats = lstatSync.bind(this)(path);
|
|
712
709
|
}
|
|
713
710
|
catch (error) {
|
|
714
|
-
if (error.code != 'ENOENT' || !
|
|
711
|
+
if (error.code != 'ENOENT' || !options?.force)
|
|
715
712
|
throw error;
|
|
716
713
|
}
|
|
717
714
|
if (!stats)
|
|
718
715
|
return;
|
|
719
716
|
switch (stats.mode & constants.S_IFMT) {
|
|
720
717
|
case constants.S_IFDIR:
|
|
721
|
-
if (options
|
|
718
|
+
if (options?.recursive) {
|
|
722
719
|
for (const entry of readdirSync.call(this, path)) {
|
|
723
720
|
rmSync.call(this, join(path, entry), options);
|
|
724
721
|
}
|
|
@@ -739,7 +736,7 @@ export function rmSync(path, options) {
|
|
|
739
736
|
}
|
|
740
737
|
rmSync;
|
|
741
738
|
export function mkdtempSync(prefix, options) {
|
|
742
|
-
const encoding = typeof options === 'object' ? options
|
|
739
|
+
const encoding = typeof options === 'object' ? options?.encoding : options || 'utf8';
|
|
743
740
|
const fsName = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
744
741
|
const resolvedPath = '/tmp/' + fsName;
|
|
745
742
|
mkdirSync.call(this, resolvedPath);
|
|
@@ -820,11 +817,11 @@ export function cpSync(source, destination, opts) {
|
|
|
820
817
|
source = normalizePath(source);
|
|
821
818
|
destination = normalizePath(destination);
|
|
822
819
|
const srcStats = lstatSync.call(this, source); // Use lstat to follow symlinks if not dereferencing
|
|
823
|
-
if (
|
|
820
|
+
if (opts?.errorOnExist && existsSync.call(this, destination))
|
|
824
821
|
throw UV('EEXIST', 'cp', destination);
|
|
825
822
|
switch (srcStats.mode & constants.S_IFMT) {
|
|
826
823
|
case constants.S_IFDIR:
|
|
827
|
-
if (!
|
|
824
|
+
if (!opts?.recursive)
|
|
828
825
|
throw UV('EISDIR', 'cp', source);
|
|
829
826
|
mkdirSync.call(this, destination, { recursive: true }); // Ensure the destination directory exists
|
|
830
827
|
for (const dirent of readdirSync.call(this, source, { withFileTypes: true })) {
|
|
@@ -846,7 +843,7 @@ export function cpSync(source, destination, opts) {
|
|
|
846
843
|
throw UV('ENOSYS', 'cp', source);
|
|
847
844
|
}
|
|
848
845
|
// Optionally preserve timestamps
|
|
849
|
-
if (opts
|
|
846
|
+
if (opts?.preserveTimestamps) {
|
|
850
847
|
utimesSync.call(this, destination, srcStats.atime, srcStats.mtime);
|
|
851
848
|
}
|
|
852
849
|
}
|
|
@@ -854,7 +851,7 @@ cpSync;
|
|
|
854
851
|
export function statfsSync(path, options) {
|
|
855
852
|
path = normalizePath(path);
|
|
856
853
|
const { fs } = resolveMount(path, this);
|
|
857
|
-
return _statfs(fs, options
|
|
854
|
+
return _statfs(fs, options?.bigint);
|
|
858
855
|
}
|
|
859
856
|
export function globSync(pattern, options = {}) {
|
|
860
857
|
pattern = Array.isArray(pattern) ? pattern : [pattern];
|
package/dist/vfs/watchers.js
CHANGED
|
@@ -11,6 +11,8 @@ import { statSync } from './sync.js';
|
|
|
11
11
|
* @template TEvents The type of events emitted by the watcher.
|
|
12
12
|
*/
|
|
13
13
|
class Watcher extends EventEmitter {
|
|
14
|
+
_context;
|
|
15
|
+
path;
|
|
14
16
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
15
17
|
off(event, fn, context, once) {
|
|
16
18
|
return super.off(event, fn, context, once);
|
|
@@ -56,10 +58,12 @@ class Watcher extends EventEmitter {
|
|
|
56
58
|
* @template T The type of the filename, either `string` or `Buffer`.
|
|
57
59
|
*/
|
|
58
60
|
export class FSWatcher extends Watcher {
|
|
61
|
+
options;
|
|
62
|
+
realpath;
|
|
59
63
|
constructor(context, path, options) {
|
|
60
64
|
super(context, path);
|
|
61
65
|
this.options = options;
|
|
62
|
-
this.realpath =
|
|
66
|
+
this.realpath = context?.root ? join(context.root, path) : path;
|
|
63
67
|
addWatcher(this.realpath, this);
|
|
64
68
|
}
|
|
65
69
|
close() {
|
|
@@ -76,6 +80,9 @@ export class FSWatcher extends Watcher {
|
|
|
76
80
|
* Instances of `StatWatcher` are used by `fs.watchFile()` to monitor changes to a file's statistics.
|
|
77
81
|
*/
|
|
78
82
|
export class StatWatcher extends Watcher {
|
|
83
|
+
options;
|
|
84
|
+
intervalId;
|
|
85
|
+
previous;
|
|
79
86
|
constructor(context, path, options) {
|
|
80
87
|
super(context, path);
|
|
81
88
|
this.options = options;
|
|
@@ -139,9 +146,8 @@ export function removeWatcher(path, watcher) {
|
|
|
139
146
|
* @internal @hidden
|
|
140
147
|
*/
|
|
141
148
|
export function emitChange($, eventType, filename) {
|
|
142
|
-
var _a;
|
|
143
149
|
if ($)
|
|
144
|
-
filename = join(
|
|
150
|
+
filename = join($.root ?? '/', filename);
|
|
145
151
|
filename = normalizePath(filename);
|
|
146
152
|
// Notify watchers, including ones on parent directories if they are watching recursively
|
|
147
153
|
for (let path = filename; path != '/'; path = dirname(path)) {
|
package/dist/vfs/xattr.js
CHANGED
|
@@ -16,14 +16,13 @@ function checkName($, name, path, syscall) {
|
|
|
16
16
|
throw UV('ENOTSUP', syscall, path);
|
|
17
17
|
}
|
|
18
18
|
export async function get(path, name, opt = {}) {
|
|
19
|
-
var _a;
|
|
20
19
|
path = normalizePath(path);
|
|
21
20
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
22
21
|
checkName(this, name, path, 'xattr.get');
|
|
23
22
|
const inode = await fs.stat(resolved).catch(rethrow('xattr.get', path));
|
|
24
23
|
if (checkAccess && !hasAccess(this, inode, R_OK))
|
|
25
24
|
throw UV('EACCES', 'xattr.get', path);
|
|
26
|
-
|
|
25
|
+
inode.attributes ??= new Attributes();
|
|
27
26
|
const value = inode.attributes.get(name);
|
|
28
27
|
if (!value)
|
|
29
28
|
throw UV('ENODATA', 'xattr.get', path);
|
|
@@ -31,7 +30,6 @@ export async function get(path, name, opt = {}) {
|
|
|
31
30
|
return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
|
|
32
31
|
}
|
|
33
32
|
export function getSync(path, name, opt = {}) {
|
|
34
|
-
var _a;
|
|
35
33
|
path = normalizePath(path);
|
|
36
34
|
checkName(this, name, path, 'xattr.get');
|
|
37
35
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
@@ -44,7 +42,7 @@ export function getSync(path, name, opt = {}) {
|
|
|
44
42
|
}
|
|
45
43
|
if (checkAccess && !hasAccess(this, inode, R_OK))
|
|
46
44
|
throw UV('EACCES', 'xattr.get', path);
|
|
47
|
-
|
|
45
|
+
inode.attributes ??= new Attributes();
|
|
48
46
|
const value = inode.attributes.get(name);
|
|
49
47
|
if (!value)
|
|
50
48
|
throw UV('ENODATA', 'xattr.get', path);
|
|
@@ -60,14 +58,13 @@ export function getSync(path, name, opt = {}) {
|
|
|
60
58
|
* @param opt Options for the operation
|
|
61
59
|
*/
|
|
62
60
|
export async function set(path, name, value, opt = {}) {
|
|
63
|
-
var _a;
|
|
64
61
|
path = normalizePath(path);
|
|
65
62
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
66
63
|
checkName(this, name, path, 'xattr.set');
|
|
67
64
|
const inode = await fs.stat(resolved).catch(rethrow('xattr.set', path));
|
|
68
65
|
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
69
66
|
throw UV('EACCES', 'xattr.set', path);
|
|
70
|
-
|
|
67
|
+
inode.attributes ??= new Attributes();
|
|
71
68
|
const attr = inode.attributes.get(name);
|
|
72
69
|
if (opt.create && attr)
|
|
73
70
|
throw UV('EEXIST', 'xattr.set', path);
|
|
@@ -85,7 +82,6 @@ export async function set(path, name, value, opt = {}) {
|
|
|
85
82
|
* @param opt Options for the operation
|
|
86
83
|
*/
|
|
87
84
|
export function setSync(path, name, value, opt = {}) {
|
|
88
|
-
var _a;
|
|
89
85
|
path = normalizePath(path);
|
|
90
86
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
91
87
|
checkName(this, name, path, 'xattr.set');
|
|
@@ -98,7 +94,7 @@ export function setSync(path, name, value, opt = {}) {
|
|
|
98
94
|
}
|
|
99
95
|
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
100
96
|
throw UV('EACCES', 'xattr.set', path);
|
|
101
|
-
|
|
97
|
+
inode.attributes ??= new Attributes();
|
|
102
98
|
const attr = inode.attributes.get(name);
|
|
103
99
|
if (opt.create && attr)
|
|
104
100
|
throw UV('EEXIST', 'xattr.set', path);
|
|
@@ -119,14 +115,13 @@ export function setSync(path, name, value, opt = {}) {
|
|
|
119
115
|
* @param name Name of the attribute to remove
|
|
120
116
|
*/
|
|
121
117
|
export async function remove(path, name) {
|
|
122
|
-
var _a;
|
|
123
118
|
path = normalizePath(path);
|
|
124
119
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
125
120
|
checkName(this, name, path, 'xattr.remove');
|
|
126
121
|
const inode = await fs.stat(resolved).catch(rethrow('xattr.remove', path));
|
|
127
122
|
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
128
123
|
throw UV('EACCES', 'xattr.remove', path);
|
|
129
|
-
|
|
124
|
+
inode.attributes ??= new Attributes();
|
|
130
125
|
const attr = inode.attributes.get(name);
|
|
131
126
|
if (!attr)
|
|
132
127
|
throw UV('ENODATA', 'xattr.remove', path);
|
|
@@ -140,7 +135,6 @@ export async function remove(path, name) {
|
|
|
140
135
|
* @param name Name of the attribute to remove
|
|
141
136
|
*/
|
|
142
137
|
export function removeSync(path, name) {
|
|
143
|
-
var _a;
|
|
144
138
|
path = normalizePath(path);
|
|
145
139
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
146
140
|
checkName(this, name, path, 'xattr.remove');
|
|
@@ -153,7 +147,7 @@ export function removeSync(path, name) {
|
|
|
153
147
|
}
|
|
154
148
|
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
155
149
|
throw UV('EACCES', 'xattr.remove', path);
|
|
156
|
-
|
|
150
|
+
inode.attributes ??= new Attributes();
|
|
157
151
|
const attr = inode.attributes.get(name);
|
|
158
152
|
if (!attr)
|
|
159
153
|
throw UV('ENODATA', 'xattr.remove', path);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"buffer": "^6.0.3",
|
|
72
72
|
"eventemitter3": "^5.0.1",
|
|
73
73
|
"kerium": "^1.3.4",
|
|
74
|
-
"memium": "^0.2.
|
|
74
|
+
"memium": "^0.2.3",
|
|
75
75
|
"readable-stream": "^4.5.2",
|
|
76
76
|
"utilium": "^2.3.3"
|
|
77
77
|
},
|
package/scripts/test.js
CHANGED
|
@@ -22,6 +22,7 @@ const { values: options, positionals } = parseArgs({
|
|
|
22
22
|
build: { short: 'b', type: 'boolean', default: false },
|
|
23
23
|
common: { short: 'c', type: 'boolean', default: false },
|
|
24
24
|
inspect: { short: 'I', type: 'boolean', default: false },
|
|
25
|
+
skip: { short: 's', type: 'string' },
|
|
25
26
|
'exit-on-fail': { short: 'e', type: 'boolean' },
|
|
26
27
|
|
|
27
28
|
// Coverage
|
|
@@ -89,11 +90,12 @@ if (options.ci) ci = await import('./ci.js');
|
|
|
89
90
|
options.verbose && options.force && console.debug('Forcing tests to exit (--test-force-exit)');
|
|
90
91
|
|
|
91
92
|
if (options.build) {
|
|
92
|
-
!options.quiet &&
|
|
93
|
+
!options.quiet && process.stdout.write('Building... ');
|
|
93
94
|
try {
|
|
94
95
|
execSync('npm run build');
|
|
96
|
+
console.log('done.');
|
|
95
97
|
} catch {
|
|
96
|
-
console.warn('
|
|
98
|
+
console.warn('failed, continuing without it.');
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
|
|
@@ -141,17 +143,19 @@ async function status(name) {
|
|
|
141
143
|
return color(`(${delta} ${unit})`, '2;37');
|
|
142
144
|
};
|
|
143
145
|
|
|
146
|
+
const maybeName = options.verbose ? `: ${name}` : '';
|
|
147
|
+
|
|
144
148
|
return {
|
|
145
149
|
async pass() {
|
|
146
|
-
if (!options.quiet) console.log(`${color('passed', 32)}
|
|
150
|
+
if (!options.quiet) console.log(`${color('passed', 32)}${maybeName} ${time()}`);
|
|
147
151
|
if (options.ci) await ci.completeCheck(name, 'success');
|
|
148
152
|
},
|
|
149
153
|
async skip() {
|
|
150
|
-
if (!options.quiet) console.log(`${color('skipped', 33)}
|
|
154
|
+
if (!options.quiet) console.log(`${color('skipped', 33)}${maybeName} ${time()}`);
|
|
151
155
|
if (options.ci) await ci.completeCheck(name, 'skipped');
|
|
152
156
|
},
|
|
153
157
|
async fail() {
|
|
154
|
-
console.error(`${color('failed', '1;31')}
|
|
158
|
+
console.error(`${color('failed', '1;31')}${maybeName} ${time()}`);
|
|
155
159
|
if (options.ci) await ci.completeCheck(name, 'failure');
|
|
156
160
|
process.exitCode = 1;
|
|
157
161
|
if (options['exit-on-fail']) process.exit();
|
|
@@ -163,7 +167,7 @@ if (!options.preserve) rmSync(options.coverage, { force: true, recursive: true }
|
|
|
163
167
|
mkdirSync(options.coverage, { recursive: true });
|
|
164
168
|
|
|
165
169
|
if (options.common) {
|
|
166
|
-
!options.quiet &&
|
|
170
|
+
!options.quiet && process.stdout.write('Running common tests...' + (options.verbose ? '\n' : ' '));
|
|
167
171
|
const { pass, fail } = await status('Common tests');
|
|
168
172
|
try {
|
|
169
173
|
execSync(
|
|
@@ -187,10 +191,14 @@ for (const setupFile of positionals) {
|
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
process.env.SETUP = setupFile;
|
|
194
|
+
process.env.VERBOSE = +options.verbose;
|
|
190
195
|
|
|
191
196
|
const name = options['file-names'] && !options.ci ? setupFile : parse(setupFile).name;
|
|
192
197
|
|
|
193
|
-
!options.quiet
|
|
198
|
+
if (!options.quiet) {
|
|
199
|
+
if (options.verbose) console.log('Running tests:', name);
|
|
200
|
+
else process.stdout.write(`Running tests: ${name}... `);
|
|
201
|
+
}
|
|
194
202
|
|
|
195
203
|
const { pass, fail, skip } = await status(name);
|
|
196
204
|
|
|
@@ -206,6 +214,7 @@ for (const setupFile of positionals) {
|
|
|
206
214
|
options.inspect ? 'inspect' : '',
|
|
207
215
|
'--test --experimental-test-coverage',
|
|
208
216
|
options.force ? '--test-force-exit' : '',
|
|
217
|
+
options.skip ? `--test-skip-pattern=${options.skip}` : '',
|
|
209
218
|
testsGlob,
|
|
210
219
|
process.env.CMD,
|
|
211
220
|
].join(' '),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { suite, test } from 'node:test';
|
|
3
|
+
import { after, suite, test } from 'node:test';
|
|
4
4
|
import { Worker } from 'node:worker_threads';
|
|
5
5
|
import { Fetch, configureSingle, fs, mounts, type FetchFS } from '../../dist/index.js';
|
|
6
6
|
import { baseUrl, defaultEntries, indexPath, whenServerReady } from '../fetch/config.js';
|
|
@@ -12,17 +12,15 @@ const server = new Worker(join(import.meta.dirname, '../fetch/server.js'));
|
|
|
12
12
|
|
|
13
13
|
await whenServerReady();
|
|
14
14
|
|
|
15
|
-
await
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
index: baseUrl + indexPath,
|
|
23
|
-
});
|
|
24
|
-
});
|
|
15
|
+
await configureSingle({
|
|
16
|
+
backend: Fetch,
|
|
17
|
+
disableAsyncCache: true,
|
|
18
|
+
remoteWrite: true,
|
|
19
|
+
baseUrl,
|
|
20
|
+
index: baseUrl + indexPath,
|
|
21
|
+
});
|
|
25
22
|
|
|
23
|
+
suite('Fetch with `disableAsyncCache`', () => {
|
|
26
24
|
test('Read and write file', async () => {
|
|
27
25
|
await fs.promises.writeFile('/example', 'test');
|
|
28
26
|
|
|
@@ -45,9 +43,11 @@ await suite('Fetch with `disableAsyncCache`', () => {
|
|
|
45
43
|
|
|
46
44
|
test('Uncached synchronous operations throw', async () => {
|
|
47
45
|
assert.throws(() => fs.readFileSync('/x.txt', 'utf8'), { code: 'EAGAIN' });
|
|
48
|
-
await (mounts.get('/') as FetchFS)._asyncDone;
|
|
49
46
|
});
|
|
50
47
|
});
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
after(async () => {
|
|
50
|
+
await (mounts.get('/') as FetchFS)._asyncDone;
|
|
51
|
+
await server.terminate();
|
|
52
|
+
server.unref();
|
|
53
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { suite, test } from 'node:test';
|
|
2
|
+
import { after, suite, test } from 'node:test';
|
|
3
3
|
import { MessageChannel, Worker } from 'node:worker_threads';
|
|
4
|
-
import { Port, attachFS
|
|
4
|
+
import { Port, attachFS } from '../../dist/backends/port.js';
|
|
5
5
|
import type { InMemoryStore, StoreFS } from '../../dist/index.js';
|
|
6
|
-
import { InMemory, configure, configureSingle, fs, resolveMountConfig } from '../../dist/index.js';
|
|
6
|
+
import { InMemory, configure, configureSingle, fs, resolveMountConfig, waitOnline } from '../../dist/index.js';
|
|
7
7
|
import { setupLogs } from '../logs.js';
|
|
8
8
|
setupLogs();
|
|
9
9
|
|
|
@@ -29,16 +29,18 @@ await suite('Timeout', { timeout: 1000 }, () => {
|
|
|
29
29
|
|
|
30
30
|
await assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
|
|
31
31
|
});
|
|
32
|
-
});
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
after(() => {
|
|
34
|
+
timeoutChannel.port1.unref();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
35
37
|
|
|
36
38
|
// Test configuration
|
|
37
39
|
|
|
38
40
|
const configPort = new Worker(import.meta.dirname + '/config.worker.js');
|
|
39
41
|
await waitOnline(configPort);
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
suite('Remote FS with resolveRemoteMount', () => {
|
|
42
44
|
const content = 'FS is in a port';
|
|
43
45
|
|
|
44
46
|
test('Configuration', async () => {
|
|
@@ -52,10 +54,12 @@ await suite('Remote FS with resolveRemoteMount', () => {
|
|
|
52
54
|
test('Read', async () => {
|
|
53
55
|
assert.equal(await fs.promises.readFile('/test', 'utf8'), content);
|
|
54
56
|
});
|
|
55
|
-
});
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
configPort.
|
|
58
|
+
after(async () => {
|
|
59
|
+
await configPort.terminate();
|
|
60
|
+
configPort.unref();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
59
63
|
|
|
60
64
|
// Test using a message channel
|
|
61
65
|
|
|
@@ -87,12 +91,14 @@ await suite('FS with MessageChannel', () => {
|
|
|
87
91
|
test('readFileSync should throw', () => {
|
|
88
92
|
assert.throws(() => fs.readFileSync('/test', 'utf8'), { code: 'ENOTSUP' });
|
|
89
93
|
});
|
|
90
|
-
});
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
channel.
|
|
94
|
-
channel.
|
|
95
|
-
channel.
|
|
95
|
+
after(() => {
|
|
96
|
+
channel.port1.close();
|
|
97
|
+
channel.port2.close();
|
|
98
|
+
channel.port1.unref();
|
|
99
|
+
channel.port2.unref();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
96
102
|
|
|
97
103
|
// Test using a worker
|
|
98
104
|
|
|
@@ -112,7 +118,9 @@ await suite('Remote FS', () => {
|
|
|
112
118
|
test('Read', async () => {
|
|
113
119
|
assert.equal(await fs.promises.readFile('/test', 'utf8'), content);
|
|
114
120
|
});
|
|
115
|
-
});
|
|
116
121
|
|
|
117
|
-
|
|
118
|
-
remotePort.
|
|
122
|
+
after(async () => {
|
|
123
|
+
await remotePort.terminate();
|
|
124
|
+
remotePort.unref();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { suite, test } from 'node:test';
|
|
2
|
+
import { after, suite, test } from 'node:test';
|
|
3
3
|
import { wait } from 'utilium';
|
|
4
4
|
import { constants, type FileHandle, open } from '../../dist/vfs/promises.js';
|
|
5
5
|
|
|
6
6
|
const content = 'The cake is a lie',
|
|
7
7
|
appended = '\nAnother lie';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const handle: FileHandle = await open('./test.txt', 'ws+');
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
suite('FileHandle', () => {
|
|
12
12
|
test('writeFile', async () => {
|
|
13
13
|
await handle.writeFile(content);
|
|
14
14
|
await handle.sync();
|
|
@@ -63,3 +63,5 @@ await suite('FileHandle', () => {
|
|
|
63
63
|
assert.deepEqual(lines, ['first line', 'second line', 'third line']);
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
|
+
|
|
67
|
+
after(() => handle.close());
|
package/tests/fetch/run.sh
CHANGED
|
@@ -6,7 +6,8 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
|
6
6
|
node $SCRIPT_DIR/server.js &
|
|
7
7
|
PID=$!
|
|
8
8
|
|
|
9
|
-
echo "Waiting for server to start..."
|
|
9
|
+
echo -n "Waiting for server to start..."
|
|
10
|
+
if [ -n "$VERBOSE" ]; then echo; fi
|
|
10
11
|
until nc -z localhost 26514; do
|
|
11
12
|
sleep 0.25
|
|
12
13
|
done
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
3
|
+
import { fs } from '../common.js';
|
|
4
|
+
|
|
5
|
+
const n_files = 130;
|
|
6
|
+
const huge_size = 0x1000000;
|
|
7
|
+
|
|
8
|
+
// Tests for having a lot of various things (number of inodes/files, individual file size, etc.).
|
|
9
|
+
suite('Scaling', () => {
|
|
10
|
+
test('Lots of inodes/files', async () => {
|
|
11
|
+
fs.mkdirSync('/n');
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < n_files; i++) {
|
|
14
|
+
fs.writeFileSync('/n/' + i, i.toString(16));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
assert.equal(fs.readdirSync('/n').length, n_files);
|
|
18
|
+
|
|
19
|
+
const results = [];
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < n_files; i++) {
|
|
22
|
+
results.push(fs.promises.readFile('/n/' + i, 'utf8').then(val => assert.equal(val, i.toString(16))));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await Promise.all(results);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Singular file size', () => {
|
|
29
|
+
fs.writeFileSync('/huge', new Uint8Array(huge_size));
|
|
30
|
+
assert.equal(fs.statSync('/huge').size, huge_size);
|
|
31
|
+
});
|
|
32
|
+
});
|
package/tests/fs/watch.test.ts
CHANGED
|
@@ -9,9 +9,9 @@ await fs.promises.mkdir(testDir);
|
|
|
9
9
|
await fs.promises.writeFile(testFile, 'Initial content');
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* @todo convert using watcher to void discards pending ES proposal
|
|
12
|
+
* @todo convert `using watcher = ...` to void discards pending ES proposal
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
suite('Watch', async () => {
|
|
15
15
|
test('Events emitted on file change', async () => {
|
|
16
16
|
const { promise, resolve } = Promise.withResolvers<[string, string]>();
|
|
17
17
|
|
|
@@ -155,6 +155,3 @@ await suite('Watch', () => {
|
|
|
155
155
|
await promise;
|
|
156
156
|
});
|
|
157
157
|
});
|
|
158
|
-
|
|
159
|
-
await fs.promises.rm(testFile);
|
|
160
|
-
await fs.promises.rm(testDir, { recursive: true, force: true });
|
package/tests/tsconfig.json
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"noEmit": true,
|
|
7
7
|
"esModuleInterop": true,
|
|
8
8
|
"allowSyntheticDefaultImports": true,
|
|
9
|
-
"allowJs": true
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"rootDir": ".."
|
|
10
11
|
},
|
|
11
|
-
"include": ["**/*.ts", "*.ts", "**/*.js"]
|
|
12
|
+
"include": ["**/*.ts", "*.ts", "**/*.js", "../types/uint8array.d.ts"]
|
|
12
13
|
}
|