@zenfs/core 1.1.6 → 1.2.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/file_index.js +0 -3
- package/dist/backends/overlay.js +0 -8
- package/dist/backends/store/fs.js +4 -17
- package/dist/config.d.ts +24 -1
- package/dist/config.js +5 -0
- package/dist/devices.js +0 -12
- package/dist/emulation/cache.d.ts +21 -0
- package/dist/emulation/cache.js +36 -0
- package/dist/emulation/config.d.ts +10 -0
- package/dist/emulation/config.js +10 -0
- package/dist/emulation/promises.d.ts +9 -14
- package/dist/emulation/promises.js +71 -47
- package/dist/emulation/shared.d.ts +16 -0
- package/dist/emulation/sync.d.ts +11 -20
- package/dist/emulation/sync.js +44 -22
- package/dist/file.d.ts +1 -1
- package/dist/file.js +6 -3
- package/package.json +4 -2
- package/readme.md +1 -1
- package/scripts/test.js +19 -1
- package/src/backends/backend.ts +160 -0
- package/src/backends/fetch.ts +180 -0
- package/src/backends/file_index.ts +206 -0
- package/src/backends/memory.ts +50 -0
- package/src/backends/overlay.ts +560 -0
- package/src/backends/port/fs.ts +335 -0
- package/src/backends/port/readme.md +54 -0
- package/src/backends/port/rpc.ts +167 -0
- package/src/backends/readme.md +3 -0
- package/src/backends/store/fs.ts +700 -0
- package/src/backends/store/readme.md +9 -0
- package/src/backends/store/simple.ts +146 -0
- package/src/backends/store/store.ts +173 -0
- package/src/config.ts +185 -0
- package/src/credentials.ts +31 -0
- package/src/devices.ts +459 -0
- package/src/emulation/async.ts +834 -0
- package/src/emulation/cache.ts +44 -0
- package/src/emulation/config.ts +11 -0
- package/src/emulation/constants.ts +182 -0
- package/src/emulation/dir.ts +138 -0
- package/src/emulation/index.ts +8 -0
- package/src/emulation/path.ts +440 -0
- package/src/emulation/promises.ts +1134 -0
- package/src/emulation/shared.ts +153 -0
- package/src/emulation/streams.ts +34 -0
- package/src/emulation/sync.ts +868 -0
- package/src/emulation/watchers.ts +193 -0
- package/src/error.ts +307 -0
- package/src/file.ts +662 -0
- package/src/filesystem.ts +174 -0
- package/src/index.ts +25 -0
- package/src/inode.ts +132 -0
- package/src/mixins/async.ts +208 -0
- package/src/mixins/index.ts +5 -0
- package/src/mixins/mutexed.ts +257 -0
- package/src/mixins/readonly.ts +96 -0
- package/src/mixins/shared.ts +25 -0
- package/src/mixins/sync.ts +58 -0
- package/src/polyfills.ts +21 -0
- package/src/stats.ts +363 -0
- package/src/utils.ts +288 -0
- package/tests/common.ts +1 -11
- package/tests/fs/directory.test.ts +1 -1
- package/tests/fs/errors.test.ts +1 -1
- package/tests/fs/links.test.ts +1 -1
- package/tests/fs/open.test.ts +1 -1
- package/tests/fs/permissions.test.ts +4 -4
- package/tests/fs/readdir.test.ts +3 -3
- package/tests/fs/rename.test.ts +1 -1
- package/tests/fs/stat.test.ts +1 -1
- package/tests/fs/times.test.ts +2 -2
- package/tests/fs/truncate.test.ts +1 -1
- package/tests/port/channel.test.ts +3 -3
- package/tests/port/config.test.ts +4 -5
- package/tests/port/config.worker.js +5 -0
- package/tests/port/remote.test.ts +4 -5
- package/tests/port/remote.worker.js +5 -0
- package/tests/port/timeout.test.ts +2 -2
- package/tests/setup/common.ts +1 -1
- package/tests/setup/cow+fetch.ts +1 -1
- package/tests/port/config.worker.ts +0 -5
- package/tests/port/remote.worker.ts +0 -5
package/dist/emulation/sync.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type * as fs from 'node:fs';
|
|
|
3
3
|
import type { FileContents } from '../filesystem.js';
|
|
4
4
|
import { BigIntStats, type Stats } from '../stats.js';
|
|
5
5
|
import { Dir, Dirent } from './dir.js';
|
|
6
|
+
import { type InternalOptions, type ReaddirOptions } from './shared.js';
|
|
6
7
|
export declare function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void;
|
|
7
8
|
/**
|
|
8
9
|
* Test whether or not `path` exists by checking with the file system.
|
|
@@ -110,29 +111,19 @@ export declare function mkdirSync(path: fs.PathLike, options?: fs.Mode | (fs.Mak
|
|
|
110
111
|
recursive?: false;
|
|
111
112
|
}) | null): void;
|
|
112
113
|
export declare function mkdirSync(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined;
|
|
113
|
-
export declare function readdirSync(path: fs.PathLike, options?: {
|
|
114
|
-
recursive?: boolean;
|
|
115
|
-
encoding?: BufferEncoding | null;
|
|
114
|
+
export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
|
|
116
115
|
withFileTypes?: false;
|
|
117
|
-
} | BufferEncoding | null): string[];
|
|
118
|
-
export declare function readdirSync(path: fs.PathLike, options: {
|
|
119
|
-
recursive?: boolean;
|
|
120
|
-
encoding: 'buffer';
|
|
116
|
+
}) | BufferEncoding | null): string[];
|
|
117
|
+
export declare function readdirSync(path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & {
|
|
121
118
|
withFileTypes?: false;
|
|
122
|
-
}
|
|
123
|
-
export declare function readdirSync(path: fs.PathLike, options
|
|
124
|
-
recursive?: boolean;
|
|
125
|
-
withFileTypes: true;
|
|
126
|
-
}): Dirent[];
|
|
127
|
-
export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & {
|
|
119
|
+
}): Buffer[];
|
|
120
|
+
export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
|
|
128
121
|
withFileTypes?: false;
|
|
129
|
-
recursive?: boolean;
|
|
130
122
|
}) | BufferEncoding | null): string[] | Buffer[];
|
|
131
|
-
export declare function readdirSync(path: fs.PathLike, options
|
|
132
|
-
withFileTypes
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
} | BufferEncoding | 'buffer' | null): string[] | Dirent[] | Buffer[];
|
|
123
|
+
export declare function readdirSync(path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & {
|
|
124
|
+
withFileTypes: true;
|
|
125
|
+
}): Dirent[];
|
|
126
|
+
export declare function readdirSync(path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): string[] | Dirent[] | Buffer[];
|
|
136
127
|
export declare function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void;
|
|
137
128
|
/**
|
|
138
129
|
* Synchronous `symlink`.
|
|
@@ -163,7 +154,7 @@ export declare function accessSync(path: fs.PathLike, mode?: number): void;
|
|
|
163
154
|
* Synchronous `rm`. Removes files or directories (recursively).
|
|
164
155
|
* @param path The path to the file or directory to remove.
|
|
165
156
|
*/
|
|
166
|
-
export declare function rmSync(path: fs.PathLike, options?: fs.RmOptions): void;
|
|
157
|
+
export declare function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptions): void;
|
|
167
158
|
/**
|
|
168
159
|
* Synchronous `mkdtemp`. Creates a unique temporary directory.
|
|
169
160
|
* @param prefix The directory prefix.
|
package/dist/emulation/sync.js
CHANGED
|
@@ -54,13 +54,15 @@ import * as constants from './constants.js';
|
|
|
54
54
|
import { Dir, Dirent } from './dir.js';
|
|
55
55
|
import { dirname, join, parse } from './path.js';
|
|
56
56
|
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
|
|
57
|
+
import { config } from './config.js';
|
|
57
58
|
import { emitChange } from './watchers.js';
|
|
59
|
+
import * as cache from './cache.js';
|
|
58
60
|
export function renameSync(oldPath, newPath) {
|
|
59
61
|
oldPath = normalizePath(oldPath);
|
|
60
62
|
newPath = normalizePath(newPath);
|
|
61
63
|
const oldMount = resolveMount(oldPath);
|
|
62
64
|
const newMount = resolveMount(newPath);
|
|
63
|
-
if (!statSync(dirname(oldPath)).hasAccess(constants.W_OK)) {
|
|
65
|
+
if (config.checkAccess && !statSync(dirname(oldPath)).hasAccess(constants.W_OK)) {
|
|
64
66
|
throw ErrnoError.With('EACCES', oldPath, 'rename');
|
|
65
67
|
}
|
|
66
68
|
try {
|
|
@@ -100,7 +102,7 @@ export function statSync(path, options) {
|
|
|
100
102
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
101
103
|
try {
|
|
102
104
|
const stats = fs.statSync(resolved);
|
|
103
|
-
if (!stats.hasAccess(constants.R_OK)) {
|
|
105
|
+
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
104
106
|
throw ErrnoError.With('EACCES', resolved, 'stat');
|
|
105
107
|
}
|
|
106
108
|
return options?.bigint ? new BigIntStats(stats) : stats;
|
|
@@ -145,7 +147,7 @@ export function unlinkSync(path) {
|
|
|
145
147
|
path = normalizePath(path);
|
|
146
148
|
const { fs, path: resolved } = resolveMount(path);
|
|
147
149
|
try {
|
|
148
|
-
if (!fs.statSync(resolved).hasAccess(constants.W_OK)) {
|
|
150
|
+
if (config.checkAccess && !(cache.getStats(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
|
|
149
151
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
150
152
|
}
|
|
151
153
|
fs.unlinkSync(resolved);
|
|
@@ -174,7 +176,7 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
|
|
|
174
176
|
}
|
|
175
177
|
// Create the file
|
|
176
178
|
const parentStats = fs.statSync(dirname(resolved));
|
|
177
|
-
if (!parentStats.hasAccess(constants.W_OK)) {
|
|
179
|
+
if (config.checkAccess && !parentStats.hasAccess(constants.W_OK)) {
|
|
178
180
|
throw ErrnoError.With('EACCES', dirname(path), '_open');
|
|
179
181
|
}
|
|
180
182
|
if (!parentStats.isDirectory()) {
|
|
@@ -182,7 +184,7 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
|
|
|
182
184
|
}
|
|
183
185
|
return fs.createFileSync(resolved, flag, mode);
|
|
184
186
|
}
|
|
185
|
-
if (!stats.hasAccess(mode) || !stats.hasAccess(flagToMode(flag))) {
|
|
187
|
+
if (config.checkAccess && (!stats.hasAccess(mode) || !stats.hasAccess(flagToMode(flag)))) {
|
|
186
188
|
throw ErrnoError.With('EACCES', path, '_open');
|
|
187
189
|
}
|
|
188
190
|
if (isExclusive(flag)) {
|
|
@@ -392,7 +394,11 @@ export function rmdirSync(path) {
|
|
|
392
394
|
path = normalizePath(path);
|
|
393
395
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
394
396
|
try {
|
|
395
|
-
|
|
397
|
+
const stats = cache.getStats(path) || fs.statSync(resolved);
|
|
398
|
+
if (!stats.isDirectory()) {
|
|
399
|
+
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
400
|
+
}
|
|
401
|
+
if (config.checkAccess && !stats.hasAccess(constants.W_OK)) {
|
|
396
402
|
throw ErrnoError.With('EACCES', resolved, 'rmdir');
|
|
397
403
|
}
|
|
398
404
|
fs.rmdirSync(resolved);
|
|
@@ -406,12 +412,12 @@ rmdirSync;
|
|
|
406
412
|
export function mkdirSync(path, options) {
|
|
407
413
|
options = typeof options === 'object' ? options : { mode: options };
|
|
408
414
|
const mode = normalizeMode(options?.mode, 0o777);
|
|
409
|
-
path = realpathSync(
|
|
415
|
+
path = realpathSync(path);
|
|
410
416
|
const { fs, path: resolved } = resolveMount(path);
|
|
411
417
|
const errorPaths = { [resolved]: path };
|
|
412
418
|
try {
|
|
413
419
|
if (!options?.recursive) {
|
|
414
|
-
if (!fs.statSync(dirname(resolved)).hasAccess(constants.W_OK)) {
|
|
420
|
+
if (config.checkAccess && !fs.statSync(dirname(resolved)).hasAccess(constants.W_OK)) {
|
|
415
421
|
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir');
|
|
416
422
|
}
|
|
417
423
|
return fs.mkdirSync(resolved, mode);
|
|
@@ -422,7 +428,7 @@ export function mkdirSync(path, options) {
|
|
|
422
428
|
errorPaths[dir] = original;
|
|
423
429
|
}
|
|
424
430
|
for (const dir of dirs) {
|
|
425
|
-
if (!fs.statSync(dirname(dir)).hasAccess(constants.W_OK)) {
|
|
431
|
+
if (config.checkAccess && !fs.statSync(dirname(dir)).hasAccess(constants.W_OK)) {
|
|
426
432
|
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir');
|
|
427
433
|
}
|
|
428
434
|
fs.mkdirSync(dir, mode);
|
|
@@ -441,8 +447,13 @@ export function readdirSync(path, options) {
|
|
|
441
447
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
442
448
|
let entries;
|
|
443
449
|
try {
|
|
444
|
-
|
|
445
|
-
|
|
450
|
+
const stats = cache.getStats(path) || fs.statSync(resolved);
|
|
451
|
+
cache.setStats(path, stats);
|
|
452
|
+
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
453
|
+
throw ErrnoError.With('EACCES', resolved, 'readdir');
|
|
454
|
+
}
|
|
455
|
+
if (!stats.isDirectory()) {
|
|
456
|
+
throw ErrnoError.With('ENOTDIR', resolved, 'readdir');
|
|
446
457
|
}
|
|
447
458
|
entries = fs.readdirSync(resolved);
|
|
448
459
|
}
|
|
@@ -463,11 +474,12 @@ export function readdirSync(path, options) {
|
|
|
463
474
|
// Iterate over entries and handle recursive case if needed
|
|
464
475
|
const values = [];
|
|
465
476
|
for (const entry of entries) {
|
|
466
|
-
const entryStat = fs.statSync(join(resolved, entry));
|
|
477
|
+
const entryStat = cache.getStats(join(path, entry)) || fs.statSync(join(resolved, entry));
|
|
478
|
+
cache.setStats(join(path, entry), entryStat);
|
|
467
479
|
if (options?.withFileTypes) {
|
|
468
480
|
values.push(new Dirent(entry, entryStat));
|
|
469
481
|
}
|
|
470
|
-
else if (options?.encoding
|
|
482
|
+
else if (options?.encoding == 'buffer') {
|
|
471
483
|
values.push(Buffer.from(entry));
|
|
472
484
|
}
|
|
473
485
|
else {
|
|
@@ -475,7 +487,7 @@ export function readdirSync(path, options) {
|
|
|
475
487
|
}
|
|
476
488
|
if (!entryStat.isDirectory() || !options?.recursive)
|
|
477
489
|
continue;
|
|
478
|
-
for (const subEntry of readdirSync(join(path, entry), options)) {
|
|
490
|
+
for (const subEntry of readdirSync(join(path, entry), { ...options, _isIndirect: true })) {
|
|
479
491
|
if (subEntry instanceof Dirent) {
|
|
480
492
|
subEntry.path = join(entry, subEntry.path);
|
|
481
493
|
values.push(subEntry);
|
|
@@ -488,17 +500,20 @@ export function readdirSync(path, options) {
|
|
|
488
500
|
}
|
|
489
501
|
}
|
|
490
502
|
}
|
|
503
|
+
if (!options?._isIndirect) {
|
|
504
|
+
cache.clearStats();
|
|
505
|
+
}
|
|
491
506
|
return values;
|
|
492
507
|
}
|
|
493
508
|
readdirSync;
|
|
494
509
|
// SYMLINK METHODS
|
|
495
510
|
export function linkSync(targetPath, linkPath) {
|
|
496
511
|
targetPath = normalizePath(targetPath);
|
|
497
|
-
if (!statSync(dirname(targetPath)).hasAccess(constants.R_OK)) {
|
|
512
|
+
if (config.checkAccess && !statSync(dirname(targetPath)).hasAccess(constants.R_OK)) {
|
|
498
513
|
throw ErrnoError.With('EACCES', dirname(targetPath), 'link');
|
|
499
514
|
}
|
|
500
515
|
linkPath = normalizePath(linkPath);
|
|
501
|
-
if (!statSync(dirname(linkPath)).hasAccess(constants.W_OK)) {
|
|
516
|
+
if (config.checkAccess && !statSync(dirname(linkPath)).hasAccess(constants.W_OK)) {
|
|
502
517
|
throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
|
|
503
518
|
}
|
|
504
519
|
const { fs, path } = resolveMount(targetPath);
|
|
@@ -507,7 +522,7 @@ export function linkSync(targetPath, linkPath) {
|
|
|
507
522
|
throw ErrnoError.With('EXDEV', linkPath, 'link');
|
|
508
523
|
}
|
|
509
524
|
try {
|
|
510
|
-
if (!fs.statSync(path).hasAccess(constants.W_OK)) {
|
|
525
|
+
if (config.checkAccess && !fs.statSync(path).hasAccess(constants.W_OK)) {
|
|
511
526
|
throw ErrnoError.With('EACCES', path, 'link');
|
|
512
527
|
}
|
|
513
528
|
return fs.linkSync(path, linkPath);
|
|
@@ -608,6 +623,8 @@ export function realpathSync(path, options) {
|
|
|
608
623
|
}
|
|
609
624
|
realpathSync;
|
|
610
625
|
export function accessSync(path, mode = 0o600) {
|
|
626
|
+
if (!config.checkAccess)
|
|
627
|
+
return;
|
|
611
628
|
if (!statSync(path).hasAccess(mode)) {
|
|
612
629
|
throw new ErrnoError(Errno.EACCES);
|
|
613
630
|
}
|
|
@@ -621,7 +638,7 @@ export function rmSync(path, options) {
|
|
|
621
638
|
path = normalizePath(path);
|
|
622
639
|
let stats;
|
|
623
640
|
try {
|
|
624
|
-
stats = statSync(path);
|
|
641
|
+
stats = cache.getStats(path) || statSync(path);
|
|
625
642
|
}
|
|
626
643
|
catch (error) {
|
|
627
644
|
if (error.code != 'ENOENT' || !options?.force)
|
|
@@ -630,26 +647,31 @@ export function rmSync(path, options) {
|
|
|
630
647
|
if (!stats) {
|
|
631
648
|
return;
|
|
632
649
|
}
|
|
650
|
+
cache.setStats(path, stats);
|
|
633
651
|
switch (stats.mode & constants.S_IFMT) {
|
|
634
652
|
case constants.S_IFDIR:
|
|
635
653
|
if (options?.recursive) {
|
|
636
|
-
for (const entry of readdirSync(path)) {
|
|
637
|
-
rmSync(join(path, entry), options);
|
|
654
|
+
for (const entry of readdirSync(path, { _isIndirect: true })) {
|
|
655
|
+
rmSync(join(path, entry), { ...options, _isIndirect: true });
|
|
638
656
|
}
|
|
639
657
|
}
|
|
640
658
|
rmdirSync(path);
|
|
641
|
-
|
|
659
|
+
break;
|
|
642
660
|
case constants.S_IFREG:
|
|
643
661
|
case constants.S_IFLNK:
|
|
644
662
|
unlinkSync(path);
|
|
645
|
-
|
|
663
|
+
break;
|
|
646
664
|
case constants.S_IFBLK:
|
|
647
665
|
case constants.S_IFCHR:
|
|
648
666
|
case constants.S_IFIFO:
|
|
649
667
|
case constants.S_IFSOCK:
|
|
650
668
|
default:
|
|
669
|
+
cache.clearStats();
|
|
651
670
|
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
|
|
652
671
|
}
|
|
672
|
+
if (!options?._isIndirect) {
|
|
673
|
+
cache.clearStats();
|
|
674
|
+
}
|
|
653
675
|
}
|
|
654
676
|
rmSync;
|
|
655
677
|
export function mkdtempSync(prefix, options) {
|
package/dist/file.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FileReadResult } from 'node:fs/promises';
|
|
2
2
|
import type { FileSystem } from './filesystem.js';
|
|
3
|
-
import { Stats, type FileType } from './stats.js';
|
|
4
3
|
import './polyfills.js';
|
|
4
|
+
import { Stats, type FileType } from './stats.js';
|
|
5
5
|
/**
|
|
6
6
|
Typescript does not include a type declaration for resizable array buffers.
|
|
7
7
|
It has been standardized into ECMAScript though
|
package/dist/file.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT, size_max } from './emulation/constants.js';
|
|
2
|
+
import { config } from './emulation/config.js';
|
|
2
3
|
import { Errno, ErrnoError } from './error.js';
|
|
3
|
-
import { Stats } from './stats.js';
|
|
4
4
|
import './polyfills.js';
|
|
5
|
+
import { Stats } from './stats.js';
|
|
5
6
|
const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
|
|
6
7
|
export function parseFlag(flag) {
|
|
7
8
|
if (typeof flag === 'number') {
|
|
@@ -383,7 +384,8 @@ export class PreloadFile extends File {
|
|
|
383
384
|
*/
|
|
384
385
|
async read(buffer, offset, length, position) {
|
|
385
386
|
const bytesRead = this._read(buffer, offset, length, position);
|
|
386
|
-
|
|
387
|
+
if (config.syncOnRead)
|
|
388
|
+
await this.sync();
|
|
387
389
|
return { bytesRead, buffer };
|
|
388
390
|
}
|
|
389
391
|
/**
|
|
@@ -397,7 +399,8 @@ export class PreloadFile extends File {
|
|
|
397
399
|
*/
|
|
398
400
|
readSync(buffer, offset, length, position) {
|
|
399
401
|
const bytesRead = this._read(buffer, offset, length, position);
|
|
400
|
-
|
|
402
|
+
if (config.syncOnRead)
|
|
403
|
+
this.syncSync();
|
|
401
404
|
return bytesRead;
|
|
402
405
|
}
|
|
403
406
|
async chmod(mode) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"zenfs-test": "scripts/test.js"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
|
+
"src",
|
|
21
22
|
"dist",
|
|
22
23
|
"tests",
|
|
23
24
|
"license.md",
|
|
@@ -48,7 +49,8 @@
|
|
|
48
49
|
"./mixins": "./dist/mixins/index.js",
|
|
49
50
|
"./path": "./dist/emulation/path.js",
|
|
50
51
|
"./eslint": "./eslint.shared.js",
|
|
51
|
-
"./tests/*": "./tests/*"
|
|
52
|
+
"./tests/*": "./tests/*",
|
|
53
|
+
"./src/*": "./src/*"
|
|
52
54
|
},
|
|
53
55
|
"scripts": {
|
|
54
56
|
"format": "prettier --write .",
|
package/readme.md
CHANGED
|
@@ -32,7 +32,7 @@ npm install @zenfs/core
|
|
|
32
32
|
```js
|
|
33
33
|
import { fs } from '@zenfs/core'; // You can also use the default export
|
|
34
34
|
|
|
35
|
-
fs.writeFileSync('/test.txt', 'You can do this
|
|
35
|
+
fs.writeFileSync('/test.txt', 'You can do this anywhere, including browsers!');
|
|
36
36
|
|
|
37
37
|
const contents = fs.readFileSync('/test.txt', 'utf-8');
|
|
38
38
|
console.log(contents);
|
package/scripts/test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
4
5
|
import { join } from 'node:path';
|
|
5
6
|
import { parseArgs } from 'node:util';
|
|
6
7
|
|
|
@@ -8,6 +9,8 @@ const { values: options, positionals } = parseArgs({
|
|
|
8
9
|
options: {
|
|
9
10
|
help: { short: 'h', type: 'boolean', default: false },
|
|
10
11
|
verbose: { type: 'boolean', default: false },
|
|
12
|
+
test: { type: 'string' },
|
|
13
|
+
forceExit: { short: 'f', type: 'boolean', default: false },
|
|
11
14
|
},
|
|
12
15
|
allowPositionals: true,
|
|
13
16
|
});
|
|
@@ -20,12 +23,27 @@ paths: The setup files to run tests on
|
|
|
20
23
|
options:
|
|
21
24
|
--help, -h Outputs this help message
|
|
22
25
|
--verbose Output verbose messages
|
|
26
|
+
--test Which test to run
|
|
27
|
+
--forceExit Whether to use --test-force-exit
|
|
23
28
|
`);
|
|
24
29
|
process.exit();
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
if (options.verbose) console.debug('Forcing tests to exit (--test-force-exit)');
|
|
33
|
+
|
|
34
|
+
if (!existsSync(join(import.meta.dirname, '../dist'))) {
|
|
35
|
+
console.log('ERROR: Missing build. If you are using an installed package, please submit a bug report.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const testsGlob = join(import.meta.dirname, `../tests/fs/${options.test || '*'}.test.ts`);
|
|
40
|
+
|
|
27
41
|
for (const setupFile of positionals) {
|
|
28
42
|
if (options.verbose) console.debug('Running tests for:', setupFile);
|
|
29
43
|
process.env.SETUP = setupFile;
|
|
30
|
-
|
|
44
|
+
if (!existsSync(setupFile)) {
|
|
45
|
+
console.log('ERROR: Skipping non-existent file:', setupFile);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
execSync(['tsx --test --experimental-test-coverage', options.forceExit ? '--test-force-exit' : '', testsGlob, process.env.CMD].join(' '), { stdio: 'inherit' });
|
|
31
49
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { Entries, RequiredKeys } from 'utilium';
|
|
2
|
+
import { ErrnoError, Errno } from '../error.js';
|
|
3
|
+
import type { FileSystem } from '../filesystem.js';
|
|
4
|
+
import { levenshtein } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
type OptionType = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the type of Backend.options from the options interface
|
|
10
|
+
*/
|
|
11
|
+
export type OptionsConfig<T> = {
|
|
12
|
+
[K in keyof T]: {
|
|
13
|
+
/**
|
|
14
|
+
* The basic JavaScript type(s) for this option.
|
|
15
|
+
*/
|
|
16
|
+
type: OptionType | readonly OptionType[];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Description of the option. Used in error messages and documentation.
|
|
20
|
+
*/
|
|
21
|
+
description: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether or not the option is required (optional can be set to null or undefined). Defaults to false.
|
|
25
|
+
*/
|
|
26
|
+
required: K extends RequiredKeys<T> ? true : false;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A custom validation function to check if the option is valid.
|
|
30
|
+
* When async, resolves if valid and rejects if not.
|
|
31
|
+
* When sync, it will throw an error if not valid.
|
|
32
|
+
*/
|
|
33
|
+
validator?(opt: T[K]): void | Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Configuration options shared by backends and `Configuration`
|
|
39
|
+
*/
|
|
40
|
+
export interface SharedConfig {
|
|
41
|
+
/**
|
|
42
|
+
* If set, disables the sync cache and sync operations on async file systems.
|
|
43
|
+
*/
|
|
44
|
+
disableAsyncCache?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A backend
|
|
49
|
+
*/
|
|
50
|
+
export interface Backend<FS extends FileSystem = FileSystem, TOptions extends object = object> {
|
|
51
|
+
/**
|
|
52
|
+
* Create a new instance of the backend
|
|
53
|
+
*/
|
|
54
|
+
create(options: TOptions & Partial<SharedConfig>): FS | Promise<FS>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A name to identify the backend.
|
|
58
|
+
*/
|
|
59
|
+
name: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Describes all of the options available for this backend.
|
|
63
|
+
*/
|
|
64
|
+
options: OptionsConfig<TOptions>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Whether the backend is available in the current environment.
|
|
68
|
+
* It supports checking synchronously and asynchronously
|
|
69
|
+
*
|
|
70
|
+
* Returns 'true' if this backend is available in the current
|
|
71
|
+
* environment. For example, a backend using a browser API will return
|
|
72
|
+
* 'false' if the API is unavailable
|
|
73
|
+
*
|
|
74
|
+
*/
|
|
75
|
+
isAvailable(): boolean | Promise<boolean>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Gets the options type of a backend
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
export type OptionsOf<T extends Backend> = T extends Backend<FileSystem, infer TOptions> ? TOptions : never;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Gets the FileSystem type for a backend
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
export type FilesystemOf<T extends Backend> = T extends Backend<infer FS> ? FS : never;
|
|
89
|
+
|
|
90
|
+
/** @internal */
|
|
91
|
+
export function isBackend(arg: unknown): arg is Backend {
|
|
92
|
+
return arg != null && typeof arg == 'object' && 'isAvailable' in arg && typeof arg.isAvailable == 'function' && 'create' in arg && typeof arg.create == 'function';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Checks that `options` object is valid for the file system options.
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
export async function checkOptions<T extends Backend>(backend: T, options: Record<string, unknown>): Promise<void> {
|
|
100
|
+
if (typeof options != 'object' || options === null) {
|
|
101
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid options');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check for required options.
|
|
105
|
+
for (const [optName, opt] of Object.entries(backend.options) as Entries<OptionsConfig<Record<string, any>>>) {
|
|
106
|
+
const providedValue = options?.[optName];
|
|
107
|
+
|
|
108
|
+
if (providedValue === undefined || providedValue === null) {
|
|
109
|
+
if (!opt.required) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
/* Required option not provided.
|
|
113
|
+
if any incorrect options provided, which ones are close to the provided one?
|
|
114
|
+
(edit distance 5 === close)*/
|
|
115
|
+
const incorrectOptions = Object.keys(options)
|
|
116
|
+
.filter(o => !(o in backend.options))
|
|
117
|
+
.map((a: string) => {
|
|
118
|
+
return { str: a, distance: levenshtein(optName, a) };
|
|
119
|
+
})
|
|
120
|
+
.filter(o => o.distance < 5)
|
|
121
|
+
.sort((a, b) => a.distance - b.distance);
|
|
122
|
+
|
|
123
|
+
throw new ErrnoError(
|
|
124
|
+
Errno.EINVAL,
|
|
125
|
+
`${backend.name}: Required option '${optName}' not provided.${
|
|
126
|
+
incorrectOptions.length > 0 ? ` You provided '${incorrectOptions[0].str}', did you mean '${optName}'.` : ''
|
|
127
|
+
}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
// Option provided, check type.
|
|
131
|
+
const typeMatches = Array.isArray(opt.type) ? opt.type.indexOf(typeof providedValue) != -1 : typeof providedValue == opt.type;
|
|
132
|
+
if (!typeMatches) {
|
|
133
|
+
throw new ErrnoError(
|
|
134
|
+
Errno.EINVAL,
|
|
135
|
+
`${backend.name}: Value provided for option ${optName} is not the proper type. Expected ${
|
|
136
|
+
Array.isArray(opt.type) ? `one of {${opt.type.join(', ')}}` : (opt.type as string)
|
|
137
|
+
}, but received ${typeof providedValue}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (opt.validator) {
|
|
142
|
+
await opt.validator(providedValue);
|
|
143
|
+
}
|
|
144
|
+
// Otherwise: All good!
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Specifies a file system backend type and its options.
|
|
150
|
+
*
|
|
151
|
+
* Individual options can recursively contain BackendConfiguration objects for values that require file systems.
|
|
152
|
+
*
|
|
153
|
+
* The configuration for each file system corresponds to that file system's option object passed to its `create()` method.
|
|
154
|
+
*/
|
|
155
|
+
export type BackendConfiguration<T extends Backend> = OptionsOf<T> & Partial<SharedConfig> & { backend: T };
|
|
156
|
+
|
|
157
|
+
/** @internal */
|
|
158
|
+
export function isBackendConfig<T extends Backend>(arg: unknown): arg is BackendConfiguration<T> {
|
|
159
|
+
return arg != null && typeof arg == 'object' && 'backend' in arg && isBackend(arg.backend);
|
|
160
|
+
}
|