@gjsify/fs 0.4.0 → 0.4.4
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/package.json +51 -48
- package/src/callback.spec.ts +0 -296
- package/src/callback.ts +0 -684
- package/src/cp.spec.ts +0 -181
- package/src/cp.ts +0 -328
- package/src/dir.spec.ts +0 -204
- package/src/dir.ts +0 -199
- package/src/dirent.ts +0 -165
- package/src/encoding.ts +0 -45
- package/src/errors.spec.ts +0 -389
- package/src/errors.ts +0 -19
- package/src/extended.spec.ts +0 -706
- package/src/fd-ops.spec.ts +0 -234
- package/src/fd-ops.ts +0 -251
- package/src/file-handle.spec.ts +0 -115
- package/src/file-handle.ts +0 -856
- package/src/fs-watcher.ts +0 -198
- package/src/glob.spec.ts +0 -201
- package/src/glob.ts +0 -205
- package/src/index.ts +0 -313
- package/src/new-apis.spec.ts +0 -505
- package/src/promises.spec.ts +0 -812
- package/src/promises.ts +0 -686
- package/src/read-stream.ts +0 -128
- package/src/stat-watcher.ts +0 -116
- package/src/stat.spec.ts +0 -87
- package/src/statfs.spec.ts +0 -67
- package/src/statfs.ts +0 -92
- package/src/stats.ts +0 -207
- package/src/streams.spec.ts +0 -513
- package/src/symlink.spec.ts +0 -188
- package/src/sync.spec.ts +0 -377
- package/src/sync.ts +0 -568
- package/src/test.mts +0 -27
- package/src/types/encoding-option.ts +0 -3
- package/src/types/file-read-options.ts +0 -15
- package/src/types/file-read-result.ts +0 -4
- package/src/types/flag-and-open-mode.ts +0 -6
- package/src/types/index.ts +0 -6
- package/src/types/open-flags.ts +0 -14
- package/src/types/read-options.ts +0 -9
- package/src/utils.ts +0 -31
- package/src/utimes.spec.ts +0 -113
- package/src/utimes.ts +0 -97
- package/src/watch.spec.ts +0 -171
- package/src/watchfile.spec.ts +0 -185
- package/src/write-stream.ts +0 -142
- package/test/file.txt +0 -1
- package/tsconfig.json +0 -29
- package/tsconfig.tsbuildinfo +0 -1
package/src/utimes.spec.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
// Ported from refs/node-test/parallel/test-fs-utimes.js (behavior)
|
|
2
|
-
// Original: MIT, Node.js contributors.
|
|
3
|
-
// Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from '@gjsify/unit';
|
|
6
|
-
import { utimesSync, utimes, lutimesSync, lchownSync, promises } from 'node:fs';
|
|
7
|
-
import { statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
8
|
-
import { tmpdir } from 'node:os';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
|
|
11
|
-
const TMP = tmpdir();
|
|
12
|
-
|
|
13
|
-
function tmpFile(name: string): string {
|
|
14
|
-
const p = join(TMP, `gjsify-utimes-${name}-${process.pid}`);
|
|
15
|
-
writeFileSync(p, 'test');
|
|
16
|
-
return p;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default async () => {
|
|
20
|
-
await describe('fs.utimes / fs.lutimes / fs.lchown / fs.lchmod', async () => {
|
|
21
|
-
await it('utimesSync sets mtime', async () => {
|
|
22
|
-
const f = tmpFile('mtime');
|
|
23
|
-
const mtime = new Date('2020-01-01T00:00:00Z');
|
|
24
|
-
utimesSync(f, mtime, mtime);
|
|
25
|
-
const stat = statSync(f);
|
|
26
|
-
expect(stat.mtime.getFullYear()).toBe(2020);
|
|
27
|
-
unlinkSync(f);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
await it('utimesSync sets atime', async () => {
|
|
31
|
-
const f = tmpFile('atime');
|
|
32
|
-
const atime = new Date('2021-06-15T12:00:00Z');
|
|
33
|
-
const mtime = new Date('2020-01-01T00:00:00Z');
|
|
34
|
-
utimesSync(f, atime, mtime);
|
|
35
|
-
const stat = statSync(f);
|
|
36
|
-
expect(stat.mtime.getFullYear()).toBe(2020);
|
|
37
|
-
unlinkSync(f);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
await it('utimes callback sets timestamps', async () => {
|
|
41
|
-
const f = tmpFile('cb');
|
|
42
|
-
const mtime = new Date('2019-03-10T00:00:00Z');
|
|
43
|
-
await new Promise<void>((resolve, reject) => {
|
|
44
|
-
utimes(f, mtime, mtime, (err) => {
|
|
45
|
-
if (err) reject(err);
|
|
46
|
-
else resolve();
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
const stat = statSync(f);
|
|
50
|
-
expect(stat.mtime.getFullYear()).toBe(2019);
|
|
51
|
-
unlinkSync(f);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
await it('promises.utimes sets timestamps', async () => {
|
|
55
|
-
const f = tmpFile('promise');
|
|
56
|
-
const mtime = new Date('2018-08-01T00:00:00Z');
|
|
57
|
-
await promises.utimes(f, mtime, mtime);
|
|
58
|
-
const stat = statSync(f);
|
|
59
|
-
expect(stat.mtime.getFullYear()).toBe(2018);
|
|
60
|
-
unlinkSync(f);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
await it('lutimesSync does not throw on a symlink', async () => {
|
|
64
|
-
const target = tmpFile('lutime-target');
|
|
65
|
-
const link = join(TMP, `gjsify-lutime-link-${process.pid}`);
|
|
66
|
-
try { unlinkSync(link); } catch {}
|
|
67
|
-
symlinkSync(target, link);
|
|
68
|
-
const mtime = new Date('2017-05-20T00:00:00Z');
|
|
69
|
-
// Just verify the call completes without throwing
|
|
70
|
-
expect(() => lutimesSync(link, mtime, mtime)).not.toThrow();
|
|
71
|
-
unlinkSync(link);
|
|
72
|
-
unlinkSync(target);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await it('lutimes callback completes without error', async () => {
|
|
76
|
-
const f = tmpFile('lutimes-cb');
|
|
77
|
-
const mtime = new Date('2016-01-01T00:00:00Z');
|
|
78
|
-
const { lutimes } = await import('node:fs') as any;
|
|
79
|
-
await new Promise<void>((resolve, reject) => {
|
|
80
|
-
lutimes(f, mtime, mtime, (err: any) => {
|
|
81
|
-
if (err) reject(err);
|
|
82
|
-
else resolve();
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
unlinkSync(f);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
await it('lchownSync does not throw (may need root to actually change)', async () => {
|
|
89
|
-
const f = tmpFile('lchown');
|
|
90
|
-
const link = join(TMP, `gjsify-lchown-link-${process.pid}`);
|
|
91
|
-
try { unlinkSync(link); } catch {}
|
|
92
|
-
symlinkSync(f, link);
|
|
93
|
-
// This will only actually change owner if running as root; just verify no throw
|
|
94
|
-
try {
|
|
95
|
-
lchownSync(link, process.getuid ? process.getuid() : 0, process.getgid ? process.getgid() : 0);
|
|
96
|
-
} catch {
|
|
97
|
-
// acceptable if kernel denies (EPERM) — just must not crash the process
|
|
98
|
-
}
|
|
99
|
-
unlinkSync(link);
|
|
100
|
-
unlinkSync(f);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
await it('lchmod is a no-op (does not throw)', async () => {
|
|
104
|
-
const f = tmpFile('lchmod');
|
|
105
|
-
// Node.js removed lchmod on Linux; on GJS we export it as a no-op
|
|
106
|
-
const fsModule = await import('node:fs') as any;
|
|
107
|
-
if (typeof fsModule.lchmod === 'function') {
|
|
108
|
-
expect(() => fsModule.lchmod(f, 0o644, () => {})).not.toThrow();
|
|
109
|
-
}
|
|
110
|
-
unlinkSync(f);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
};
|
package/src/utimes.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
// Reference: Node.js lib/fs.js (utimes/lutimes/lchown/lchmod)
|
|
2
|
-
// Reimplemented for GJS using Gio.FileInfo timestamp attributes
|
|
3
|
-
|
|
4
|
-
import GLib from '@girs/glib-2.0';
|
|
5
|
-
import Gio from '@girs/gio-2.0';
|
|
6
|
-
import { normalizePath } from './utils.js';
|
|
7
|
-
|
|
8
|
-
import type { PathLike, TimeLike } from 'node:fs';
|
|
9
|
-
|
|
10
|
-
function toGLibDateTime(t: TimeLike): GLib.DateTime {
|
|
11
|
-
const ms = t instanceof Date ? t.getTime()
|
|
12
|
-
: typeof t === 'bigint' ? Number(t)
|
|
13
|
-
: typeof t === 'string' ? Date.parse(t)
|
|
14
|
-
: t * 1000; // Node accepts float seconds
|
|
15
|
-
return GLib.DateTime.new_from_unix_utc(Math.floor(ms / 1000));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function setTimestamps(path: string, atime: TimeLike, mtime: TimeLike, flags: Gio.FileQueryInfoFlags): void {
|
|
19
|
-
const file = Gio.File.new_for_path(path);
|
|
20
|
-
const info = new Gio.FileInfo();
|
|
21
|
-
info.set_modification_date_time(toGLibDateTime(mtime));
|
|
22
|
-
info.set_access_date_time(toGLibDateTime(atime));
|
|
23
|
-
file.set_attributes_from_info(info, flags, null);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ─── utimes ───────────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
export function utimesSync(path: PathLike, atime: TimeLike, mtime: TimeLike): void {
|
|
29
|
-
setTimestamps(normalizePath(path), atime, mtime, Gio.FileQueryInfoFlags.NONE);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function utimes(
|
|
33
|
-
path: PathLike,
|
|
34
|
-
atime: TimeLike,
|
|
35
|
-
mtime: TimeLike,
|
|
36
|
-
callback: (err: NodeJS.ErrnoException | null) => void,
|
|
37
|
-
): void {
|
|
38
|
-
Promise.resolve().then(() => utimesSync(path, atime, mtime)).then(() => callback(null), callback);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function utimesAsync(path: PathLike, atime: TimeLike, mtime: TimeLike): Promise<void> {
|
|
42
|
-
utimesSync(path, atime, mtime);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ─── lutimes ──────────────────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
export function lutimesSync(path: PathLike, atime: TimeLike, mtime: TimeLike): void {
|
|
48
|
-
setTimestamps(normalizePath(path), atime, mtime, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function lutimes(
|
|
52
|
-
path: PathLike,
|
|
53
|
-
atime: TimeLike,
|
|
54
|
-
mtime: TimeLike,
|
|
55
|
-
callback: (err: NodeJS.ErrnoException | null) => void,
|
|
56
|
-
): void {
|
|
57
|
-
Promise.resolve().then(() => lutimesSync(path, atime, mtime)).then(() => callback(null), callback);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function lutimesAsync(path: PathLike, atime: TimeLike, mtime: TimeLike): Promise<void> {
|
|
61
|
-
lutimesSync(path, atime, mtime);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ─── lchown ───────────────────────────────────────────────────────────────────
|
|
65
|
-
// chown -h changes the ownership of the symlink itself, not its target.
|
|
66
|
-
|
|
67
|
-
export function lchownSync(path: PathLike, uid: number, gid: number): void {
|
|
68
|
-
GLib.spawn_command_line_sync(`chown -h ${uid}:${gid} ${normalizePath(path)}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function lchown(
|
|
72
|
-
path: PathLike,
|
|
73
|
-
uid: number,
|
|
74
|
-
gid: number,
|
|
75
|
-
callback: (err: NodeJS.ErrnoException | null) => void,
|
|
76
|
-
): void {
|
|
77
|
-
Promise.resolve().then(() => lchownSync(path, uid, gid)).then(() => callback(null), callback);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function lchownAsync(path: PathLike, uid: number, gid: number): Promise<void> {
|
|
81
|
-
lchownSync(path, uid, gid);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ─── lchmod ───────────────────────────────────────────────────────────────────
|
|
85
|
-
// Not supported on Linux — no-op, no throw (mirrors Node.js behavior on Linux).
|
|
86
|
-
|
|
87
|
-
export function lchmodSync(_path: PathLike, _mode: number): void {}
|
|
88
|
-
|
|
89
|
-
export function lchmod(
|
|
90
|
-
_path: PathLike,
|
|
91
|
-
_mode: number,
|
|
92
|
-
callback: (err: NodeJS.ErrnoException | null) => void,
|
|
93
|
-
): void {
|
|
94
|
-
callback(null);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export async function lchmodAsync(_path: PathLike, _mode: number): Promise<void> {}
|
package/src/watch.spec.ts
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
// Ported from refs/bun/test/js/node/watch/fs.watch.test.ts
|
|
2
|
-
// Original: MIT, Oven & contributors.
|
|
3
|
-
// Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from '@gjsify/unit';
|
|
6
|
-
import {
|
|
7
|
-
promises,
|
|
8
|
-
mkdirSync,
|
|
9
|
-
writeFileSync,
|
|
10
|
-
mkdtempSync,
|
|
11
|
-
rmSync,
|
|
12
|
-
} from 'node:fs';
|
|
13
|
-
import { join } from 'node:path';
|
|
14
|
-
import { tmpdir } from 'node:os';
|
|
15
|
-
|
|
16
|
-
function makeTmp(): string {
|
|
17
|
-
return mkdtempSync(join(tmpdir(), 'gjsify-watch-'));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default async () => {
|
|
21
|
-
await describe('fs.promises.watch', async () => {
|
|
22
|
-
await it('yields rename event when a file is created in a directory', async () => {
|
|
23
|
-
const tmp = makeTmp();
|
|
24
|
-
const ac = new AbortController();
|
|
25
|
-
let received = false;
|
|
26
|
-
|
|
27
|
-
// Write the file shortly after the iterator starts waiting
|
|
28
|
-
setTimeout(() => {
|
|
29
|
-
writeFileSync(join(tmp, 'new-file.txt'), 'hello');
|
|
30
|
-
}, 30);
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
for await (const event of promises.watch(tmp, { signal: ac.signal })) {
|
|
34
|
-
expect(typeof event.eventType).toBe('string');
|
|
35
|
-
expect(['rename', 'change']).toContain(event.eventType);
|
|
36
|
-
received = true;
|
|
37
|
-
ac.abort();
|
|
38
|
-
}
|
|
39
|
-
} catch (e: any) {
|
|
40
|
-
// AbortError from native Node.js is expected — our impl ends cleanly
|
|
41
|
-
if (e?.name !== 'AbortError' && e?.code !== 'ABORT_ERR') throw e;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
expect(received).toBe(true);
|
|
45
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
await it('yields change event when a watched file is modified', async () => {
|
|
49
|
-
const tmp = makeTmp();
|
|
50
|
-
const file = join(tmp, 'watch-me.txt');
|
|
51
|
-
writeFileSync(file, 'initial');
|
|
52
|
-
|
|
53
|
-
const ac = new AbortController();
|
|
54
|
-
let received = false;
|
|
55
|
-
|
|
56
|
-
setTimeout(() => {
|
|
57
|
-
writeFileSync(file, 'modified');
|
|
58
|
-
}, 30);
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
for await (const event of promises.watch(file, { signal: ac.signal })) {
|
|
62
|
-
expect(typeof event.eventType).toBe('string');
|
|
63
|
-
expect(['rename', 'change']).toContain(event.eventType);
|
|
64
|
-
received = true;
|
|
65
|
-
ac.abort();
|
|
66
|
-
}
|
|
67
|
-
} catch (e: any) {
|
|
68
|
-
if (e?.name !== 'AbortError' && e?.code !== 'ABORT_ERR') throw e;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
expect(received).toBe(true);
|
|
72
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await it('filename in event is a string or null', async () => {
|
|
76
|
-
const tmp = makeTmp();
|
|
77
|
-
const ac = new AbortController();
|
|
78
|
-
let filename: string | null | undefined = undefined;
|
|
79
|
-
|
|
80
|
-
setTimeout(() => {
|
|
81
|
-
writeFileSync(join(tmp, 'tracked.txt'), 'x');
|
|
82
|
-
}, 30);
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
for await (const event of promises.watch(tmp, { signal: ac.signal })) {
|
|
86
|
-
filename = event.filename;
|
|
87
|
-
ac.abort();
|
|
88
|
-
}
|
|
89
|
-
} catch (e: any) {
|
|
90
|
-
if (e?.name !== 'AbortError' && e?.code !== 'ABORT_ERR') throw e;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// filename is a string basename or null (GJS writeFileSync uses atomic writes
|
|
94
|
-
// via GLib.file_set_contents which may report a temp filename — any string is valid)
|
|
95
|
-
expect(typeof filename === 'string' || filename === null).toBe(true);
|
|
96
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
await it('stops iterating immediately when signal is pre-aborted', async () => {
|
|
100
|
-
const tmp = makeTmp();
|
|
101
|
-
const ac = new AbortController();
|
|
102
|
-
ac.abort(); // aborted BEFORE the watch starts
|
|
103
|
-
|
|
104
|
-
let count = 0;
|
|
105
|
-
try {
|
|
106
|
-
for await (const _ of promises.watch(tmp, { signal: ac.signal })) {
|
|
107
|
-
count++;
|
|
108
|
-
}
|
|
109
|
-
} catch (e: any) {
|
|
110
|
-
if (e?.name !== 'AbortError' && e?.code !== 'ABORT_ERR') throw e;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
expect(count).toBe(0);
|
|
114
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
await it('stops cleanly when AbortController is aborted during iteration', async () => {
|
|
118
|
-
const tmp = makeTmp();
|
|
119
|
-
const ac = new AbortController();
|
|
120
|
-
let eventCount = 0;
|
|
121
|
-
|
|
122
|
-
// Write repeatedly so events keep coming
|
|
123
|
-
const interval = setInterval(() => {
|
|
124
|
-
writeFileSync(join(tmp, 'file.txt'), String(Date.now()));
|
|
125
|
-
}, 20);
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
for await (const event of promises.watch(tmp, { signal: ac.signal })) {
|
|
129
|
-
expect(['rename', 'change']).toContain(event.eventType);
|
|
130
|
-
eventCount++;
|
|
131
|
-
if (eventCount >= 2) {
|
|
132
|
-
clearInterval(interval);
|
|
133
|
-
ac.abort();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
} catch (e: any) {
|
|
137
|
-
if (e?.name !== 'AbortError' && e?.code !== 'ABORT_ERR') throw e;
|
|
138
|
-
} finally {
|
|
139
|
-
clearInterval(interval);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
expect(eventCount).toBeGreaterThan(0);
|
|
143
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
await it('multiple events can be collected before abort', async () => {
|
|
147
|
-
const tmp = makeTmp();
|
|
148
|
-
const ac = new AbortController();
|
|
149
|
-
const events: string[] = [];
|
|
150
|
-
|
|
151
|
-
setTimeout(() => {
|
|
152
|
-
writeFileSync(join(tmp, 'a.txt'), '1');
|
|
153
|
-
writeFileSync(join(tmp, 'b.txt'), '2');
|
|
154
|
-
}, 30);
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
for await (const event of promises.watch(tmp, { signal: ac.signal })) {
|
|
158
|
-
events.push(event.eventType);
|
|
159
|
-
if (events.length >= 2) {
|
|
160
|
-
ac.abort();
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
} catch (e: any) {
|
|
164
|
-
if (e?.name !== 'AbortError' && e?.code !== 'ABORT_ERR') throw e;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
expect(events.length).toBeGreaterThan(0);
|
|
168
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
};
|
package/src/watchfile.spec.ts
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
// Ported from refs/bun/test/js/node/watch/fs.watchFile.test.ts (behavior)
|
|
2
|
-
// Original: MIT, Oven & contributors.
|
|
3
|
-
// Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from '@gjsify/unit';
|
|
6
|
-
import {
|
|
7
|
-
watchFile,
|
|
8
|
-
unwatchFile,
|
|
9
|
-
writeFileSync,
|
|
10
|
-
mkdtempSync,
|
|
11
|
-
rmSync,
|
|
12
|
-
} from 'node:fs';
|
|
13
|
-
import { join } from 'node:path';
|
|
14
|
-
import { tmpdir } from 'node:os';
|
|
15
|
-
|
|
16
|
-
const INTERVAL = 50;
|
|
17
|
-
|
|
18
|
-
function makeTmp(): string {
|
|
19
|
-
return mkdtempSync(join(tmpdir(), 'gjsify-watchfile-'));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function wait(ms: number): Promise<void> {
|
|
23
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default async () => {
|
|
27
|
-
await describe('fs.watchFile / fs.unwatchFile', async () => {
|
|
28
|
-
await it('calls listener when file is modified', async () => {
|
|
29
|
-
const tmp = makeTmp();
|
|
30
|
-
const file = join(tmp, 'watch.txt');
|
|
31
|
-
writeFileSync(file, 'initial');
|
|
32
|
-
|
|
33
|
-
let called = false;
|
|
34
|
-
const listener = () => { called = true; };
|
|
35
|
-
|
|
36
|
-
watchFile(file, { interval: INTERVAL }, listener);
|
|
37
|
-
|
|
38
|
-
await wait(20);
|
|
39
|
-
writeFileSync(file, 'modified');
|
|
40
|
-
await wait(INTERVAL * 3);
|
|
41
|
-
|
|
42
|
-
unwatchFile(file, listener);
|
|
43
|
-
expect(called).toBe(true);
|
|
44
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
await it('calls listener with curr and prev Stats', async () => {
|
|
48
|
-
const tmp = makeTmp();
|
|
49
|
-
const file = join(tmp, 'stats.txt');
|
|
50
|
-
writeFileSync(file, 'v1');
|
|
51
|
-
|
|
52
|
-
let curr: any = null;
|
|
53
|
-
let prev: any = null;
|
|
54
|
-
const listener = (c: any, p: any) => { curr = c; prev = p; };
|
|
55
|
-
|
|
56
|
-
watchFile(file, { interval: INTERVAL }, listener);
|
|
57
|
-
|
|
58
|
-
await wait(20);
|
|
59
|
-
writeFileSync(file, 'v2-longer');
|
|
60
|
-
await wait(INTERVAL * 3);
|
|
61
|
-
|
|
62
|
-
unwatchFile(file, listener);
|
|
63
|
-
expect(curr).not.toBeNull();
|
|
64
|
-
expect(prev).not.toBeNull();
|
|
65
|
-
expect(typeof curr.mtimeMs).toBe('number');
|
|
66
|
-
expect(typeof prev.mtimeMs).toBe('number');
|
|
67
|
-
expect(curr.size).toBeGreaterThan(prev.size);
|
|
68
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
await it('stops calling listener after unwatchFile', async () => {
|
|
72
|
-
const tmp = makeTmp();
|
|
73
|
-
const file = join(tmp, 'stop.txt');
|
|
74
|
-
writeFileSync(file, 'start');
|
|
75
|
-
|
|
76
|
-
let count = 0;
|
|
77
|
-
const listener = () => { count++; };
|
|
78
|
-
|
|
79
|
-
watchFile(file, { interval: INTERVAL }, listener);
|
|
80
|
-
|
|
81
|
-
await wait(20);
|
|
82
|
-
writeFileSync(file, 'change1');
|
|
83
|
-
await wait(INTERVAL * 3);
|
|
84
|
-
unwatchFile(file, listener);
|
|
85
|
-
|
|
86
|
-
const countAfterUnwatch = count;
|
|
87
|
-
writeFileSync(file, 'change2');
|
|
88
|
-
await wait(INTERVAL * 3);
|
|
89
|
-
|
|
90
|
-
expect(count).toBe(countAfterUnwatch);
|
|
91
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
await it('unwatchFile with no listener removes all listeners', async () => {
|
|
95
|
-
const tmp = makeTmp();
|
|
96
|
-
const file = join(tmp, 'all.txt');
|
|
97
|
-
writeFileSync(file, 'x');
|
|
98
|
-
|
|
99
|
-
let a = 0;
|
|
100
|
-
let b = 0;
|
|
101
|
-
const la = () => { a++; };
|
|
102
|
-
const lb = () => { b++; };
|
|
103
|
-
|
|
104
|
-
watchFile(file, { interval: INTERVAL }, la);
|
|
105
|
-
watchFile(file, { interval: INTERVAL }, lb);
|
|
106
|
-
|
|
107
|
-
await wait(20);
|
|
108
|
-
writeFileSync(file, 'y');
|
|
109
|
-
await wait(INTERVAL * 3);
|
|
110
|
-
|
|
111
|
-
unwatchFile(file); // remove all
|
|
112
|
-
|
|
113
|
-
const snapA = a;
|
|
114
|
-
const snapB = b;
|
|
115
|
-
writeFileSync(file, 'z');
|
|
116
|
-
await wait(INTERVAL * 3);
|
|
117
|
-
|
|
118
|
-
expect(a).toBe(snapA);
|
|
119
|
-
expect(b).toBe(snapB);
|
|
120
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
await it('returns StatWatcher with changeListenerCount', async () => {
|
|
124
|
-
const tmp = makeTmp();
|
|
125
|
-
const file = join(tmp, 'watcher.txt');
|
|
126
|
-
writeFileSync(file, '0');
|
|
127
|
-
|
|
128
|
-
const listener = () => {};
|
|
129
|
-
const watcher = watchFile(file, { interval: INTERVAL }, listener);
|
|
130
|
-
|
|
131
|
-
expect(typeof watcher.on).toBe('function');
|
|
132
|
-
expect(watcher.listenerCount('change')).toBe(1);
|
|
133
|
-
|
|
134
|
-
unwatchFile(file, listener);
|
|
135
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
await it('multiple listeners on same file share one watcher', async () => {
|
|
139
|
-
const tmp = makeTmp();
|
|
140
|
-
const file = join(tmp, 'shared.txt');
|
|
141
|
-
writeFileSync(file, '0');
|
|
142
|
-
|
|
143
|
-
let a = 0;
|
|
144
|
-
let b = 0;
|
|
145
|
-
const la = () => { a++; };
|
|
146
|
-
const lb = () => { b++; };
|
|
147
|
-
|
|
148
|
-
const w1 = watchFile(file, { interval: INTERVAL }, la);
|
|
149
|
-
const w2 = watchFile(file, { interval: INTERVAL }, lb);
|
|
150
|
-
|
|
151
|
-
expect(w1).toBe(w2); // same watcher instance
|
|
152
|
-
|
|
153
|
-
await wait(20);
|
|
154
|
-
writeFileSync(file, '1');
|
|
155
|
-
await wait(INTERVAL * 3);
|
|
156
|
-
|
|
157
|
-
unwatchFile(file, la);
|
|
158
|
-
unwatchFile(file, lb);
|
|
159
|
-
expect(a).toBeGreaterThan(0);
|
|
160
|
-
expect(b).toBeGreaterThan(0);
|
|
161
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
await it('listener called when file is created (was non-existent)', async () => {
|
|
165
|
-
const tmp = makeTmp();
|
|
166
|
-
const file = join(tmp, 'new.txt');
|
|
167
|
-
// file does NOT exist yet
|
|
168
|
-
|
|
169
|
-
let called = false;
|
|
170
|
-
const listener = (curr: any) => {
|
|
171
|
-
if (curr.size > 0) called = true;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
watchFile(file, { interval: INTERVAL }, listener);
|
|
175
|
-
|
|
176
|
-
await wait(20);
|
|
177
|
-
writeFileSync(file, 'created');
|
|
178
|
-
await wait(INTERVAL * 3);
|
|
179
|
-
|
|
180
|
-
unwatchFile(file, listener);
|
|
181
|
-
expect(called).toBe(true);
|
|
182
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
};
|