@zenfs/core 1.2.10 → 1.3.0
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 +2 -1
- 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/inode.d.ts +4 -13
- package/dist/inode.js +20 -27
- 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 +11 -1
- 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/inode.ts +8 -29
- 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/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/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,31 +8,13 @@ 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
|
|
33
14
|
*/
|
|
34
15
|
@struct()
|
|
35
16
|
export class Inode implements StatsLike {
|
|
36
|
-
public
|
|
37
|
-
return serialize(this);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public constructor(buffer?: ArrayBufferLike) {
|
|
17
|
+
public constructor(buffer?: ArrayBufferLike | ArrayBufferView) {
|
|
41
18
|
if (buffer) {
|
|
42
19
|
if (buffer.byteLength < sizeof(Inode)) {
|
|
43
20
|
throw new RangeError(`Can not create an inode from a buffer less than ${sizeof(Inode)} bytes`);
|
|
@@ -48,7 +25,8 @@ export class Inode implements StatsLike {
|
|
|
48
25
|
}
|
|
49
26
|
|
|
50
27
|
// set defaults on a fresh inode
|
|
51
|
-
this.ino =
|
|
28
|
+
this.ino = randomBigInt();
|
|
29
|
+
this.data = randomBigInt();
|
|
52
30
|
this.nlink = 1;
|
|
53
31
|
this.size = 4096;
|
|
54
32
|
const now = Date.now();
|
|
@@ -58,7 +36,7 @@ export class Inode implements StatsLike {
|
|
|
58
36
|
this.birthtimeMs = now;
|
|
59
37
|
}
|
|
60
38
|
|
|
61
|
-
@t.uint64 public
|
|
39
|
+
@t.uint64 public data!: bigint;
|
|
62
40
|
@t.uint32 public size!: number;
|
|
63
41
|
@t.uint16 public mode!: number;
|
|
64
42
|
@t.uint32 public nlink!: number;
|
|
@@ -68,6 +46,7 @@ export class Inode implements StatsLike {
|
|
|
68
46
|
@t.float64 public birthtimeMs!: number;
|
|
69
47
|
@t.float64 public mtimeMs!: number;
|
|
70
48
|
@t.float64 public ctimeMs!: number;
|
|
49
|
+
@t.uint64 public ino!: bigint;
|
|
71
50
|
|
|
72
51
|
/**
|
|
73
52
|
* 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);
|
package/tests/setup/cow+fetch.ts
CHANGED
|
@@ -33,11 +33,13 @@ server
|
|
|
33
33
|
.listen(port)
|
|
34
34
|
.unref();
|
|
35
35
|
|
|
36
|
+
const baseUrl = 'http://localhost:' + port;
|
|
37
|
+
|
|
36
38
|
await configureSingle({
|
|
37
39
|
backend: Overlay,
|
|
38
40
|
readable: Fetch.create({
|
|
39
|
-
baseUrl
|
|
40
|
-
index: '
|
|
41
|
+
baseUrl,
|
|
42
|
+
index: baseUrl + '/.index.json',
|
|
41
43
|
}),
|
|
42
44
|
writable: InMemory.create({ name: 'cow' }),
|
|
43
45
|
});
|