@zenfs/core 1.2.10 → 1.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/fetch.js +1 -1
- package/dist/backends/memory.d.ts +1 -2
- package/dist/backends/overlay.js +2 -3
- package/dist/backends/port/fs.d.ts +2 -15
- package/dist/backends/store/fs.d.ts +17 -28
- package/dist/backends/store/fs.js +169 -191
- package/dist/backends/store/simple.d.ts +20 -21
- package/dist/backends/store/simple.js +24 -24
- package/dist/backends/store/store.d.ts +22 -23
- package/dist/backends/store/store.js +6 -6
- package/dist/config.d.ts +8 -0
- package/dist/config.js +20 -20
- package/dist/devices.d.ts +2 -3
- package/dist/emulation/cache.d.ts +40 -31
- package/dist/emulation/cache.js +62 -53
- package/dist/emulation/index.d.ts +1 -1
- package/dist/emulation/index.js +1 -1
- package/dist/emulation/promises.js +18 -17
- package/dist/emulation/shared.d.ts +6 -0
- package/dist/emulation/shared.js +13 -1
- package/dist/emulation/sync.js +14 -13
- package/dist/file.d.ts +3 -15
- package/dist/file.js +6 -18
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -0
- package/dist/inode.d.ts +5 -13
- package/dist/inode.js +38 -29
- package/dist/mixins/async.d.ts +15 -10
- package/dist/mixins/async.js +3 -1
- package/dist/utils.d.ts +5 -7
- package/dist/utils.js +11 -20
- package/package.json +1 -1
- package/src/backends/fetch.ts +1 -1
- package/src/backends/memory.ts +1 -2
- package/src/backends/overlay.ts +2 -2
- package/src/backends/store/fs.ts +187 -220
- package/src/backends/store/simple.ts +36 -37
- package/src/backends/store/store.ts +25 -26
- package/src/config.ts +31 -23
- package/src/devices.ts +2 -3
- package/src/emulation/cache.ts +68 -60
- package/src/emulation/index.ts +1 -1
- package/src/emulation/promises.ts +20 -19
- package/src/emulation/shared.ts +13 -1
- package/src/emulation/sync.ts +14 -13
- package/src/file.ts +8 -20
- package/src/index.ts +10 -0
- package/src/inode.ts +27 -31
- package/src/mixins/async.ts +27 -24
- package/src/utils.ts +11 -23
- package/tests/fs/dir.test.ts +21 -31
- package/tests/fs/directory.test.ts +6 -4
- package/tests/fs/links.test.ts +9 -2
- package/tests/fs/permissions.test.ts +2 -2
- package/tests/fs/times.test.ts +22 -11
- package/tests/setup/cow+fetch.ts +4 -2
package/src/emulation/sync.ts
CHANGED
|
@@ -106,7 +106,7 @@ export function unlinkSync(path: fs.PathLike): void {
|
|
|
106
106
|
path = normalizePath(path);
|
|
107
107
|
const { fs, path: resolved } = resolveMount(path);
|
|
108
108
|
try {
|
|
109
|
-
if (config.checkAccess && !(cache.
|
|
109
|
+
if (config.checkAccess && !(cache.stats.getSync(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
|
|
110
110
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
111
111
|
}
|
|
112
112
|
fs.unlinkSync(resolved);
|
|
@@ -386,7 +386,7 @@ export function rmdirSync(path: fs.PathLike): void {
|
|
|
386
386
|
path = normalizePath(path);
|
|
387
387
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
388
388
|
try {
|
|
389
|
-
const stats = cache.
|
|
389
|
+
const stats = cache.stats.getSync(path) || fs.statSync(resolved);
|
|
390
390
|
if (!stats.isDirectory()) {
|
|
391
391
|
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
392
392
|
}
|
|
@@ -459,8 +459,8 @@ export function readdirSync(
|
|
|
459
459
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
460
460
|
let entries: string[];
|
|
461
461
|
try {
|
|
462
|
-
const stats = cache.
|
|
463
|
-
cache.
|
|
462
|
+
const stats = cache.stats.getSync(path) || fs.statSync(resolved);
|
|
463
|
+
cache.stats.setSync(path, stats);
|
|
464
464
|
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
465
465
|
throw ErrnoError.With('EACCES', resolved, 'readdir');
|
|
466
466
|
}
|
|
@@ -475,8 +475,8 @@ export function readdirSync(
|
|
|
475
475
|
// Iterate over entries and handle recursive case if needed
|
|
476
476
|
const values: (string | Dirent | Buffer)[] = [];
|
|
477
477
|
for (const entry of entries) {
|
|
478
|
-
const entryStat = cache.
|
|
479
|
-
cache.
|
|
478
|
+
const entryStat = cache.stats.getSync(join(path, entry)) || fs.statSync(join(resolved, entry));
|
|
479
|
+
cache.stats.setSync(join(path, entry), entryStat);
|
|
480
480
|
|
|
481
481
|
if (options?.withFileTypes) {
|
|
482
482
|
values.push(new Dirent(entry, entryStat));
|
|
@@ -500,7 +500,7 @@ export function readdirSync(
|
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
if (!options?._isIndirect) {
|
|
503
|
-
cache.
|
|
503
|
+
cache.stats.clearSync();
|
|
504
504
|
}
|
|
505
505
|
return values as string[] | Dirent[] | Buffer[];
|
|
506
506
|
}
|
|
@@ -622,7 +622,7 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption): st
|
|
|
622
622
|
export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs.BufferEncodingOption): string | Buffer {
|
|
623
623
|
path = normalizePath(path);
|
|
624
624
|
const { base, dir } = parse(path);
|
|
625
|
-
const lpath = join(dir == '/' ? '/' : realpathSync(dir), base);
|
|
625
|
+
const lpath = join(dir == '/' ? '/' : cache.paths.getSync(dir) || realpathSync(dir), base);
|
|
626
626
|
const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath);
|
|
627
627
|
|
|
628
628
|
try {
|
|
@@ -631,7 +631,8 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs
|
|
|
631
631
|
return lpath;
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
-
|
|
634
|
+
const target = mountPoint + readlinkSync(lpath, options).toString();
|
|
635
|
+
return cache.paths.getSync(target) || realpathSync(target);
|
|
635
636
|
} catch (e) {
|
|
636
637
|
if ((e as ErrnoError).code == 'ENOENT') {
|
|
637
638
|
return path;
|
|
@@ -658,7 +659,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
|
|
|
658
659
|
|
|
659
660
|
let stats: Stats | undefined;
|
|
660
661
|
try {
|
|
661
|
-
stats = cache.
|
|
662
|
+
stats = cache.stats.getSync(path) || statSync(path);
|
|
662
663
|
} catch (error) {
|
|
663
664
|
if ((error as ErrnoError).code != 'ENOENT' || !options?.force) throw error;
|
|
664
665
|
}
|
|
@@ -667,7 +668,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
|
|
|
667
668
|
return;
|
|
668
669
|
}
|
|
669
670
|
|
|
670
|
-
cache.
|
|
671
|
+
cache.stats.setSync(path, stats);
|
|
671
672
|
|
|
672
673
|
switch (stats.mode & constants.S_IFMT) {
|
|
673
674
|
case constants.S_IFDIR:
|
|
@@ -688,12 +689,12 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
|
|
|
688
689
|
case constants.S_IFIFO:
|
|
689
690
|
case constants.S_IFSOCK:
|
|
690
691
|
default:
|
|
691
|
-
cache.
|
|
692
|
+
cache.stats.clearSync();
|
|
692
693
|
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
|
|
693
694
|
}
|
|
694
695
|
|
|
695
696
|
if (!options?._isIndirect) {
|
|
696
|
-
cache.
|
|
697
|
+
cache.stats.clearSync();
|
|
697
698
|
}
|
|
698
699
|
}
|
|
699
700
|
rmSync satisfies typeof fs.rmSync;
|
package/src/file.ts
CHANGED
|
@@ -145,7 +145,7 @@ export function isExclusive(flag: string): boolean {
|
|
|
145
145
|
return flag.indexOf('x') !== -1;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
export abstract class File {
|
|
148
|
+
export abstract class File<FS extends FileSystem = FileSystem> {
|
|
149
149
|
public constructor(
|
|
150
150
|
/**
|
|
151
151
|
* @internal
|
|
@@ -166,12 +166,12 @@ export abstract class File {
|
|
|
166
166
|
public abstract close(): Promise<void>;
|
|
167
167
|
public abstract closeSync(): void;
|
|
168
168
|
|
|
169
|
-
public [Symbol.asyncDispose](): Promise<void> {
|
|
170
|
-
|
|
169
|
+
public async [Symbol.asyncDispose](): Promise<void> {
|
|
170
|
+
await this.close();
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
public [Symbol.dispose](): void {
|
|
174
|
-
|
|
174
|
+
this.closeSync();
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
public abstract truncate(len: number): Promise<void>;
|
|
@@ -269,7 +269,7 @@ export abstract class File {
|
|
|
269
269
|
* An implementation of `File` that operates completely in-memory.
|
|
270
270
|
* `PreloadFile`s are backed by a `Uint8Array`.
|
|
271
271
|
*/
|
|
272
|
-
export class PreloadFile<FS extends FileSystem> extends File {
|
|
272
|
+
export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
273
273
|
/**
|
|
274
274
|
* Current position
|
|
275
275
|
*/
|
|
@@ -290,11 +290,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
290
290
|
* Note that, if contents is specified, it will be mutated by the file.
|
|
291
291
|
*/
|
|
292
292
|
public constructor(
|
|
293
|
-
|
|
294
|
-
* The file system that created the file.
|
|
295
|
-
* @internal
|
|
296
|
-
*/
|
|
297
|
-
public fs: FS,
|
|
293
|
+
fs: FS,
|
|
298
294
|
path: string,
|
|
299
295
|
public readonly flag: string,
|
|
300
296
|
public readonly stats: Stats,
|
|
@@ -431,12 +427,12 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
431
427
|
if (length > this._buffer.length) {
|
|
432
428
|
const data = new Uint8Array(length - this._buffer.length);
|
|
433
429
|
// Write will set stats.size and handle syncing.
|
|
434
|
-
this.
|
|
430
|
+
this._write(data, 0, data.length, this._buffer.length);
|
|
435
431
|
return;
|
|
436
432
|
}
|
|
437
433
|
this.stats.size = length;
|
|
438
434
|
// Truncate.
|
|
439
|
-
this._buffer = this._buffer.slice(0, length);
|
|
435
|
+
this._buffer = length ? this._buffer.slice(0, length) : new Uint8Array();
|
|
440
436
|
}
|
|
441
437
|
|
|
442
438
|
public async truncate(length: number): Promise<void> {
|
|
@@ -645,14 +641,6 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
645
641
|
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
646
642
|
this.syncSync();
|
|
647
643
|
}
|
|
648
|
-
|
|
649
|
-
public async [Symbol.asyncDispose]() {
|
|
650
|
-
await this.close();
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
public [Symbol.dispose]() {
|
|
654
|
-
this.closeSync();
|
|
655
|
-
}
|
|
656
644
|
}
|
|
657
645
|
|
|
658
646
|
/**
|
package/src/index.ts
CHANGED
|
@@ -23,3 +23,13 @@ export * from './emulation/index.js';
|
|
|
23
23
|
import * as fs from './emulation/index.js';
|
|
24
24
|
export { fs };
|
|
25
25
|
export default fs;
|
|
26
|
+
|
|
27
|
+
declare global {
|
|
28
|
+
/**
|
|
29
|
+
* Global FS emulation. Do not use unless absolutely needed.
|
|
30
|
+
* @hidden
|
|
31
|
+
*/
|
|
32
|
+
// eslint-disable-next-line no-var
|
|
33
|
+
var __zenfs__: typeof fs;
|
|
34
|
+
}
|
|
35
|
+
globalThis.__zenfs__ = fs;
|
package/src/inode.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { deserialize,
|
|
1
|
+
import { deserialize, sizeof, struct, types as t } from 'utilium';
|
|
2
2
|
import { Stats, type StatsLike } from './stats.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Alias for an ino.
|
|
6
|
-
* This will be helpful if in the future inode numbers/IDs are changed to strings or numbers.
|
|
7
|
-
*/
|
|
8
|
-
export type Ino = bigint;
|
|
3
|
+
import { randomBigInt } from './utils.js';
|
|
9
4
|
|
|
10
5
|
/**
|
|
11
6
|
* Room inode
|
|
@@ -13,34 +8,33 @@ export type Ino = bigint;
|
|
|
13
8
|
*/
|
|
14
9
|
export const rootIno = 0n;
|
|
15
10
|
|
|
16
|
-
/**
|
|
17
|
-
* Generates a random 32 bit integer, then converts to a hex string
|
|
18
|
-
*/
|
|
19
|
-
function _random() {
|
|
20
|
-
return Math.round(Math.random() * 2 ** 32).toString(16);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Generate a random ino
|
|
25
|
-
* @internal
|
|
26
|
-
*/
|
|
27
|
-
export function randomIno(): Ino {
|
|
28
|
-
return BigInt('0x' + _random() + _random());
|
|
29
|
-
}
|
|
30
|
-
|
|
31
11
|
/**
|
|
32
12
|
* Generic inode definition that can easily be serialized.
|
|
13
|
+
* @internal
|
|
14
|
+
* @todo [BREAKING]
|
|
33
15
|
*/
|
|
34
16
|
@struct()
|
|
35
17
|
export class Inode implements StatsLike {
|
|
36
|
-
public
|
|
37
|
-
return serialize(this);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public constructor(buffer?: ArrayBufferLike) {
|
|
18
|
+
public constructor(buffer?: ArrayBufferLike | ArrayBufferView) {
|
|
41
19
|
if (buffer) {
|
|
42
|
-
|
|
43
|
-
|
|
20
|
+
const sz_inode = sizeof(Inode);
|
|
21
|
+
const oldSize = sz_inode - sizeof('uint64');
|
|
22
|
+
if (buffer.byteLength < oldSize) {
|
|
23
|
+
throw new RangeError(`Can not create an inode from a buffer less than ${oldSize} bytes`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Expand the buffer so it is the right size
|
|
27
|
+
if (buffer.byteLength < sz_inode) {
|
|
28
|
+
const newBuffer = new Uint32Array(sz_inode);
|
|
29
|
+
// Fill the new buffer with current data
|
|
30
|
+
newBuffer.set(new Uint32Array(ArrayBuffer.isView(buffer) ? buffer.buffer : buffer));
|
|
31
|
+
/* Add a random ino.
|
|
32
|
+
This will be different from the actual one,
|
|
33
|
+
but `ino` isn't used anywhere so it should be fine.
|
|
34
|
+
*/
|
|
35
|
+
const randomIno = crypto.getRandomValues(new Uint32Array(2));
|
|
36
|
+
newBuffer.set(randomIno, sz_inode - 2);
|
|
37
|
+
buffer = newBuffer;
|
|
44
38
|
}
|
|
45
39
|
|
|
46
40
|
deserialize(this, buffer);
|
|
@@ -48,7 +42,8 @@ export class Inode implements StatsLike {
|
|
|
48
42
|
}
|
|
49
43
|
|
|
50
44
|
// set defaults on a fresh inode
|
|
51
|
-
this.ino =
|
|
45
|
+
this.ino = randomBigInt();
|
|
46
|
+
this.data = randomBigInt();
|
|
52
47
|
this.nlink = 1;
|
|
53
48
|
this.size = 4096;
|
|
54
49
|
const now = Date.now();
|
|
@@ -58,7 +53,7 @@ export class Inode implements StatsLike {
|
|
|
58
53
|
this.birthtimeMs = now;
|
|
59
54
|
}
|
|
60
55
|
|
|
61
|
-
@t.uint64 public
|
|
56
|
+
@t.uint64 public data!: bigint;
|
|
62
57
|
@t.uint32 public size!: number;
|
|
63
58
|
@t.uint16 public mode!: number;
|
|
64
59
|
@t.uint32 public nlink!: number;
|
|
@@ -68,6 +63,7 @@ export class Inode implements StatsLike {
|
|
|
68
63
|
@t.float64 public birthtimeMs!: number;
|
|
69
64
|
@t.float64 public mtimeMs!: number;
|
|
70
65
|
@t.float64 public ctimeMs!: number;
|
|
66
|
+
@t.uint64 public ino!: bigint;
|
|
71
67
|
|
|
72
68
|
/**
|
|
73
69
|
* Handy function that converts the Inode to a Node Stats object.
|
package/src/mixins/async.ts
CHANGED
|
@@ -12,6 +12,28 @@ export type AsyncOperation = {
|
|
|
12
12
|
[K in keyof AsyncFSMethods]: [K, ...Parameters<FileSystem[K]>];
|
|
13
13
|
}[keyof AsyncFSMethods];
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export interface Async {
|
|
19
|
+
/**
|
|
20
|
+
* @internal @protected
|
|
21
|
+
*/
|
|
22
|
+
_sync?: FileSystem;
|
|
23
|
+
queueDone(): Promise<void>;
|
|
24
|
+
ready(): Promise<void>;
|
|
25
|
+
renameSync(oldPath: string, newPath: string): void;
|
|
26
|
+
statSync(path: string): Stats;
|
|
27
|
+
createFileSync(path: string, flag: string, mode: number): File;
|
|
28
|
+
openFileSync(path: string, flag: string): File;
|
|
29
|
+
unlinkSync(path: string): void;
|
|
30
|
+
rmdirSync(path: string): void;
|
|
31
|
+
mkdirSync(path: string, mode: number): void;
|
|
32
|
+
readdirSync(path: string): string[];
|
|
33
|
+
linkSync(srcpath: string, dstpath: string): void;
|
|
34
|
+
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
15
37
|
/**
|
|
16
38
|
* Async() implements synchronous methods on an asynchronous file system
|
|
17
39
|
*
|
|
@@ -22,29 +44,7 @@ export type AsyncOperation = {
|
|
|
22
44
|
* During loading, the contents of the async file system are preloaded into the synchronous store.
|
|
23
45
|
*
|
|
24
46
|
*/
|
|
25
|
-
export function Async<T extends typeof FileSystem>(
|
|
26
|
-
FS: T
|
|
27
|
-
): Mixin<
|
|
28
|
-
T,
|
|
29
|
-
{
|
|
30
|
-
/**
|
|
31
|
-
* @internal @protected
|
|
32
|
-
*/
|
|
33
|
-
_sync?: FileSystem;
|
|
34
|
-
queueDone(): Promise<void>;
|
|
35
|
-
ready(): Promise<void>;
|
|
36
|
-
renameSync(oldPath: string, newPath: string): void;
|
|
37
|
-
statSync(path: string): Stats;
|
|
38
|
-
createFileSync(path: string, flag: string, mode: number): File;
|
|
39
|
-
openFileSync(path: string, flag: string): File;
|
|
40
|
-
unlinkSync(path: string): void;
|
|
41
|
-
rmdirSync(path: string): void;
|
|
42
|
-
mkdirSync(path: string, mode: number): void;
|
|
43
|
-
readdirSync(path: string): string[];
|
|
44
|
-
linkSync(srcpath: string, dstpath: string): void;
|
|
45
|
-
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
|
|
46
|
-
}
|
|
47
|
-
> {
|
|
47
|
+
export function Async<const T extends typeof FileSystem>(FS: T): Mixin<T, Async> {
|
|
48
48
|
abstract class AsyncFS extends FS {
|
|
49
49
|
/**
|
|
50
50
|
* Queue of pending asynchronous operations.
|
|
@@ -67,6 +67,7 @@ export function Async<T extends typeof FileSystem>(
|
|
|
67
67
|
|
|
68
68
|
public async ready(): Promise<void> {
|
|
69
69
|
await super.ready();
|
|
70
|
+
await this.queueDone();
|
|
70
71
|
if (this._isInitialized || this._disableSync) {
|
|
71
72
|
return;
|
|
72
73
|
}
|
|
@@ -128,7 +129,7 @@ export function Async<T extends typeof FileSystem>(
|
|
|
128
129
|
|
|
129
130
|
public openFileSync(path: string, flag: string): PreloadFile<this> {
|
|
130
131
|
this.checkSync(path, 'openFile');
|
|
131
|
-
const file = this._sync.openFileSync(path, flag);
|
|
132
|
+
const file = this._sync.openFileSync(path, flag + '+');
|
|
132
133
|
const stats = file.statSync();
|
|
133
134
|
const buffer = new Uint8Array(stats.size);
|
|
134
135
|
file.readSync(buffer);
|
|
@@ -225,3 +226,5 @@ export function Async<T extends typeof FileSystem>(
|
|
|
225
226
|
|
|
226
227
|
return AsyncFS;
|
|
227
228
|
}
|
|
229
|
+
|
|
230
|
+
export function asyncPatch<T extends typeof FileSystem>(fs: Mixin<T, Async>) {}
|
package/src/utils.ts
CHANGED
|
@@ -182,22 +182,6 @@ export function encodeDirListing(data: Record<string, bigint>): Uint8Array {
|
|
|
182
182
|
|
|
183
183
|
export type Callback<Args extends unknown[] = []> = (e?: ErrnoError, ...args: OptionalTuple<Args>) => unknown;
|
|
184
184
|
|
|
185
|
-
/**
|
|
186
|
-
* converts Date or number to a integer UNIX timestamp
|
|
187
|
-
* Grabbed from NodeJS sources (lib/fs.js)
|
|
188
|
-
*
|
|
189
|
-
* @internal
|
|
190
|
-
*/
|
|
191
|
-
export function _toUnixTimestamp(time: Date | number): number {
|
|
192
|
-
if (typeof time === 'number') {
|
|
193
|
-
return Math.floor(time);
|
|
194
|
-
}
|
|
195
|
-
if (time instanceof Date) {
|
|
196
|
-
return Math.floor(time.getTime() / 1000);
|
|
197
|
-
}
|
|
198
|
-
throw new Error('Cannot parse time');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
185
|
/**
|
|
202
186
|
* Normalizes a mode
|
|
203
187
|
* @internal
|
|
@@ -230,15 +214,11 @@ export function normalizeTime(time: string | number | Date): Date {
|
|
|
230
214
|
return time;
|
|
231
215
|
}
|
|
232
216
|
|
|
233
|
-
|
|
234
|
-
return new Date(time * 1000);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (typeof time == 'string') {
|
|
217
|
+
try {
|
|
238
218
|
return new Date(time);
|
|
219
|
+
} catch {
|
|
220
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid time.');
|
|
239
221
|
}
|
|
240
|
-
|
|
241
|
-
throw new ErrnoError(Errno.EINVAL, 'Invalid time.');
|
|
242
222
|
}
|
|
243
223
|
|
|
244
224
|
/**
|
|
@@ -286,3 +266,11 @@ export function normalizeOptions(
|
|
|
286
266
|
}
|
|
287
267
|
|
|
288
268
|
export type Concrete<T extends ClassLike> = Pick<T, keyof T> & (new (...args: any[]) => InstanceType<T>);
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Generate a random ino
|
|
272
|
+
* @internal
|
|
273
|
+
*/
|
|
274
|
+
export function randomBigInt(): bigint {
|
|
275
|
+
return crypto.getRandomValues(new BigUint64Array(1))[0];
|
|
276
|
+
}
|
package/tests/fs/dir.test.ts
CHANGED
|
@@ -3,18 +3,20 @@ import { suite, test } from 'node:test';
|
|
|
3
3
|
import { fs } from '../common.js';
|
|
4
4
|
|
|
5
5
|
const testFile = 'test-file.txt';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
fs.writeFileSync(testFile, 'Sample content');
|
|
7
|
+
fs.mkdirSync('test-directory');
|
|
8
|
+
fs.symlinkSync(testFile, 'test-symlink');
|
|
9
9
|
const testDirPath = 'test-dir';
|
|
10
10
|
const testFiles = ['file1.txt', 'file2.txt'];
|
|
11
|
-
|
|
11
|
+
fs.mkdirSync(testDirPath);
|
|
12
12
|
for (const file of testFiles) {
|
|
13
|
-
|
|
13
|
+
fs.writeFileSync(`${testDirPath}/${file}`, 'Sample content');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
await fs._synced();
|
|
17
|
+
|
|
16
18
|
suite('Dirent', () => {
|
|
17
|
-
test('
|
|
19
|
+
test('name and parentPath getters', async () => {
|
|
18
20
|
const stats = await fs.promises.lstat(testFile);
|
|
19
21
|
const dirent = new fs.Dirent(testFile, stats);
|
|
20
22
|
|
|
@@ -22,7 +24,7 @@ suite('Dirent', () => {
|
|
|
22
24
|
assert.equal(dirent.parentPath, testFile);
|
|
23
25
|
});
|
|
24
26
|
|
|
25
|
-
test('
|
|
27
|
+
test('isFile', async () => {
|
|
26
28
|
const fileStats = await fs.promises.lstat(testFile);
|
|
27
29
|
const fileDirent = new fs.Dirent(testFile, fileStats);
|
|
28
30
|
|
|
@@ -30,7 +32,7 @@ suite('Dirent', () => {
|
|
|
30
32
|
assert(!fileDirent.isDirectory());
|
|
31
33
|
});
|
|
32
34
|
|
|
33
|
-
test('
|
|
35
|
+
test('isDirectory', async () => {
|
|
34
36
|
const dirStats = await fs.promises.lstat('test-directory');
|
|
35
37
|
const dirDirent = new fs.Dirent('test-directory', dirStats);
|
|
36
38
|
|
|
@@ -38,14 +40,14 @@ suite('Dirent', () => {
|
|
|
38
40
|
assert(dirDirent.isDirectory());
|
|
39
41
|
});
|
|
40
42
|
|
|
41
|
-
test('
|
|
43
|
+
test('isSymbolicLink', async () => {
|
|
42
44
|
const symlinkStats = await fs.promises.lstat('test-symlink');
|
|
43
45
|
const symlinkDirent = new fs.Dirent('test-symlink', symlinkStats);
|
|
44
46
|
|
|
45
47
|
assert(symlinkDirent.isSymbolicLink());
|
|
46
48
|
});
|
|
47
49
|
|
|
48
|
-
test('
|
|
50
|
+
test('other methods return false', async () => {
|
|
49
51
|
const fileStats = await fs.promises.lstat(testFile);
|
|
50
52
|
const fileDirent = new fs.Dirent(testFile, fileStats);
|
|
51
53
|
|
|
@@ -56,7 +58,7 @@ suite('Dirent', () => {
|
|
|
56
58
|
});
|
|
57
59
|
|
|
58
60
|
suite('Dir', () => {
|
|
59
|
-
test('
|
|
61
|
+
test('read()', async () => {
|
|
60
62
|
const dir = new fs.Dir(testDirPath);
|
|
61
63
|
|
|
62
64
|
const dirent1 = await dir.read();
|
|
@@ -73,19 +75,7 @@ suite('Dir', () => {
|
|
|
73
75
|
await dir.close();
|
|
74
76
|
});
|
|
75
77
|
|
|
76
|
-
test('
|
|
77
|
-
const dir = new fs.Dir(testDirPath);
|
|
78
|
-
dir.read((err, dirent) => {
|
|
79
|
-
assert.strictEqual(err, undefined);
|
|
80
|
-
assert.notEqual(dirent, undefined);
|
|
81
|
-
assert(dirent instanceof fs.Dirent);
|
|
82
|
-
assert(testFiles.includes(dirent?.name));
|
|
83
|
-
dir.closeSync();
|
|
84
|
-
done();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('Dir readSync() method', () => {
|
|
78
|
+
test('readSync()', () => {
|
|
89
79
|
const dir = new fs.Dir(testDirPath);
|
|
90
80
|
|
|
91
81
|
const dirent1 = dir.readSync();
|
|
@@ -102,19 +92,19 @@ suite('Dir', () => {
|
|
|
102
92
|
dir.closeSync();
|
|
103
93
|
});
|
|
104
94
|
|
|
105
|
-
test('
|
|
95
|
+
test('close()', async () => {
|
|
106
96
|
const dir = new fs.Dir(testDirPath);
|
|
107
97
|
await dir.close();
|
|
108
98
|
rejects(dir.read(), 'Can not use closed Dir');
|
|
109
99
|
});
|
|
110
100
|
|
|
111
|
-
test('
|
|
101
|
+
test('closeSync()', () => {
|
|
112
102
|
const dir = new fs.Dir(testDirPath);
|
|
113
103
|
dir.closeSync();
|
|
114
104
|
assert.throws(() => dir.readSync(), 'Can not use closed Dir');
|
|
115
105
|
});
|
|
116
106
|
|
|
117
|
-
test('
|
|
107
|
+
test('asynchronous iteration', async () => {
|
|
118
108
|
const dir = new fs.Dir(testDirPath);
|
|
119
109
|
const dirents: fs.Dirent[] = [];
|
|
120
110
|
|
|
@@ -128,26 +118,26 @@ suite('Dir', () => {
|
|
|
128
118
|
assert(testFiles.includes(dirents[1].name));
|
|
129
119
|
});
|
|
130
120
|
|
|
131
|
-
test('
|
|
121
|
+
test('read after directory is closed', async () => {
|
|
132
122
|
const dir = new fs.Dir(testDirPath);
|
|
133
123
|
await dir.close();
|
|
134
124
|
await assert.rejects(dir.read(), 'Can not use closed Dir');
|
|
135
125
|
});
|
|
136
126
|
|
|
137
|
-
test('
|
|
127
|
+
test('readSync after directory is closed', () => {
|
|
138
128
|
const dir = new fs.Dir(testDirPath);
|
|
139
129
|
dir.closeSync();
|
|
140
130
|
assert.throws(() => dir.readSync(), 'Can not use closed Dir');
|
|
141
131
|
});
|
|
142
132
|
|
|
143
|
-
test('
|
|
133
|
+
test('close multiple times', async () => {
|
|
144
134
|
const dir = new fs.Dir(testDirPath);
|
|
145
135
|
await dir.close();
|
|
146
136
|
await dir.close(); // Should not throw an error
|
|
147
137
|
assert(dir['closed']);
|
|
148
138
|
});
|
|
149
139
|
|
|
150
|
-
test('
|
|
140
|
+
test('closeSync multiple times', () => {
|
|
151
141
|
const dir = new fs.Dir(testDirPath);
|
|
152
142
|
dir.closeSync();
|
|
153
143
|
dir.closeSync(); // Should not throw an error
|
|
@@ -7,17 +7,19 @@ const testDir = 'test-dir';
|
|
|
7
7
|
const testFiles = ['file1.txt', 'file2.txt', 'file3.txt'];
|
|
8
8
|
const testDirectories = ['subdir1', 'subdir2'];
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
fs.mkdirSync(testDir);
|
|
11
11
|
for (const file of testFiles) {
|
|
12
|
-
|
|
12
|
+
fs.writeFileSync(`${testDir}/${file}`, 'Sample content');
|
|
13
13
|
}
|
|
14
14
|
for (const dir of testDirectories) {
|
|
15
|
-
|
|
15
|
+
fs.mkdirSync(`${testDir}/${dir}`);
|
|
16
16
|
for (const file of ['file4.txt', 'file5.txt']) {
|
|
17
|
-
|
|
17
|
+
fs.writeFileSync(`${testDir}/${dir}/${file}`, 'Sample content');
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
await fs._synced();
|
|
22
|
+
|
|
21
23
|
suite('Directories', () => {
|
|
22
24
|
test('mkdir', async () => {
|
|
23
25
|
await fs.promises.mkdir('/one', 0o755);
|
package/tests/fs/links.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import assert from 'node:assert';
|
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
3
|
import { join } from '../../dist/emulation/path.js';
|
|
4
4
|
import { fs } from '../common.js';
|
|
5
|
+
import type { ErrnoError } from '../../dist/error.js';
|
|
5
6
|
|
|
6
7
|
suite('Links', () => {
|
|
7
8
|
const target = '/a1.js',
|
|
@@ -28,8 +29,14 @@ suite('Links', () => {
|
|
|
28
29
|
assert(await fs.promises.exists(target));
|
|
29
30
|
});
|
|
30
31
|
|
|
31
|
-
test('link', async
|
|
32
|
-
await fs.promises.link(target, hardlink)
|
|
32
|
+
test('link', async t => {
|
|
33
|
+
const _ = await fs.promises.link(target, hardlink).catch((e: ErrnoError) => {
|
|
34
|
+
if (e.code == 'ENOSYS') return e;
|
|
35
|
+
throw e;
|
|
36
|
+
});
|
|
37
|
+
if (_) {
|
|
38
|
+
return t.skip('Backend does not support hard links');
|
|
39
|
+
}
|
|
33
40
|
const targetContent = await fs.promises.readFile(target, 'utf8');
|
|
34
41
|
const linkContent = await fs.promises.readFile(hardlink, 'utf8');
|
|
35
42
|
assert.strictEqual(targetContent, linkContent);
|
|
@@ -43,8 +43,8 @@ suite('Permissions', () => {
|
|
|
43
43
|
const stats = await fs.promises.lstat(link);
|
|
44
44
|
assert.equal(stats.mode & 0o777, asyncMode);
|
|
45
45
|
|
|
46
|
-
fs.
|
|
47
|
-
assert.equal(fs.
|
|
46
|
+
await fs.promises.lchmod(link, syncMode);
|
|
47
|
+
assert.equal((await fs.promises.lstat(link)).mode & 0o777, syncMode);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
async function test_item(path: string): Promise<void> {
|
package/tests/fs/times.test.ts
CHANGED
|
@@ -2,22 +2,33 @@ import assert from 'node:assert';
|
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
3
|
import { wait } from 'utilium';
|
|
4
4
|
import { ErrnoError } from '../../dist/error.js';
|
|
5
|
-
import {
|
|
5
|
+
import type { StatsLike } from '../../dist/stats.js';
|
|
6
6
|
import { fs } from '../common.js';
|
|
7
7
|
|
|
8
8
|
const path = 'x.txt';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Gets unix timestamps from stats
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export function unixTimestamps(stats: StatsLike<number>): Record<'atime' | 'mtime', number> {
|
|
16
|
+
return {
|
|
17
|
+
atime: Math.floor(stats.atimeMs),
|
|
18
|
+
mtime: Math.floor(stats.mtimeMs),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
17
21
|
|
|
22
|
+
suite('times', () => {
|
|
18
23
|
async function runTest(atime: Date | number, mtime: Date | number): Promise<void> {
|
|
24
|
+
const times = {
|
|
25
|
+
atime: typeof atime == 'number' ? Math.floor(atime) : atime.getTime(),
|
|
26
|
+
mtime: typeof mtime == 'number' ? Math.floor(mtime) : mtime.getTime(),
|
|
27
|
+
};
|
|
28
|
+
|
|
19
29
|
await fs.promises.utimes(path, atime, mtime);
|
|
20
|
-
|
|
30
|
+
|
|
31
|
+
assert.deepStrictEqual(unixTimestamps(await fs.promises.stat(path)), times);
|
|
21
32
|
|
|
22
33
|
await fs.promises.utimes('foobarbaz', atime, mtime).catch((error: ErrnoError) => {
|
|
23
34
|
assert(error instanceof ErrnoError);
|
|
@@ -27,10 +38,10 @@ suite('times', () => {
|
|
|
27
38
|
await using handle = await fs.promises.open(path, 'r');
|
|
28
39
|
|
|
29
40
|
await handle.utimes(atime, mtime);
|
|
30
|
-
|
|
41
|
+
assert.deepStrictEqual(unixTimestamps(await handle.stat()), times);
|
|
31
42
|
|
|
32
43
|
fs.utimesSync(path, atime, mtime);
|
|
33
|
-
|
|
44
|
+
assert.deepStrictEqual(unixTimestamps(fs.statSync(path)), times);
|
|
34
45
|
|
|
35
46
|
try {
|
|
36
47
|
fs.utimesSync('foobarbaz', atime, mtime);
|