@gjsify/fs 0.1.13 → 0.2.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/lib/esm/callback.js +22 -13
- package/lib/esm/cp.js +253 -0
- package/lib/esm/dir.js +160 -0
- package/lib/esm/fd-ops.js +189 -0
- package/lib/esm/file-handle.js +263 -84
- package/lib/esm/fs-watcher.js +88 -4
- package/lib/esm/glob.js +164 -0
- package/lib/esm/index.js +128 -2
- package/lib/esm/promises.js +90 -27
- package/lib/esm/read-stream.js +53 -43
- package/lib/esm/stat-watcher.js +121 -0
- package/lib/esm/statfs.js +57 -0
- package/lib/esm/sync.js +70 -52
- package/lib/esm/utils.js +7 -0
- package/lib/esm/utimes.js +62 -0
- package/lib/esm/write-stream.js +2 -5
- package/lib/types/cp.d.ts +18 -0
- package/lib/types/cp.spec.d.ts +2 -0
- package/lib/types/dir.d.ts +29 -0
- package/lib/types/dir.spec.d.ts +2 -0
- package/lib/types/fd-ops.d.ts +57 -0
- package/lib/types/fd-ops.spec.d.ts +2 -0
- package/lib/types/file-handle.d.ts +34 -4
- package/lib/types/fs-watcher.d.ts +9 -2
- package/lib/types/glob.d.ts +8 -0
- package/lib/types/glob.spec.d.ts +2 -0
- package/lib/types/index.d.ts +51 -1
- package/lib/types/promises.d.ts +31 -4
- package/lib/types/read-stream.d.ts +3 -1
- package/lib/types/stat-watcher.d.ts +21 -0
- package/lib/types/statfs.d.ts +35 -0
- package/lib/types/statfs.spec.d.ts +2 -0
- package/lib/types/sync.d.ts +4 -7
- package/lib/types/utils.d.ts +2 -0
- package/lib/types/utimes.d.ts +13 -0
- package/lib/types/utimes.spec.d.ts +2 -0
- package/lib/types/watch.spec.d.ts +2 -0
- package/lib/types/watchfile.spec.d.ts +2 -0
- package/lib/types/write-stream.d.ts +1 -2
- package/package.json +12 -12
- package/src/callback.ts +22 -13
- package/src/cp.spec.ts +181 -0
- package/src/cp.ts +328 -0
- package/src/dir.spec.ts +204 -0
- package/src/dir.ts +199 -0
- package/src/fd-ops.spec.ts +234 -0
- package/src/fd-ops.ts +251 -0
- package/src/file-handle.ts +264 -94
- package/src/fs-watcher.ts +101 -6
- package/src/glob.spec.ts +201 -0
- package/src/glob.ts +205 -0
- package/src/index.ts +74 -0
- package/src/promises.ts +94 -29
- package/src/read-stream.ts +49 -43
- package/src/stat-watcher.ts +116 -0
- package/src/statfs.spec.ts +67 -0
- package/src/statfs.ts +92 -0
- package/src/streams.spec.ts +58 -0
- package/src/sync.ts +75 -57
- package/src/test.mts +13 -2
- package/src/utils.ts +10 -0
- package/src/utimes.spec.ts +113 -0
- package/src/utimes.ts +97 -0
- package/src/watch.spec.ts +171 -0
- package/src/watchfile.spec.ts +185 -0
- package/src/write-stream.ts +5 -8
- package/tsconfig.tsbuildinfo +1 -1
package/src/utimes.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
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> {}
|
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
};
|
package/src/write-stream.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// Modifications: Rewritten to use Gio.File for GJS
|
|
5
5
|
|
|
6
6
|
import { Writable } from "node:stream";
|
|
7
|
-
import { fileURLToPath, URL } from "node:url";
|
|
8
7
|
import { open, write, close } from "./callback.js";
|
|
8
|
+
import { normalizePath } from "./utils.js";
|
|
9
9
|
|
|
10
10
|
import type { OpenFlags } from './types/index.js';
|
|
11
11
|
import type { PathLike, WriteStream as IWriteStream } from 'node:fs';
|
|
@@ -14,13 +14,10 @@ import type { CreateWriteStreamOptions } from 'node:fs/promises'; // Types from
|
|
|
14
14
|
const kIsPerformingIO = Symbol("kIsPerformingIO");
|
|
15
15
|
const kIoDone = Symbol("kIoDone");
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return fileURLOrPath;
|
|
22
|
-
}
|
|
23
|
-
return fileURLToPath(fileURLOrPath);
|
|
17
|
+
// Legacy alias — delegates to the fs-package-internal `normalizePath`.
|
|
18
|
+
// Accepts Buffer for compatibility with older call-sites; always returns a string.
|
|
19
|
+
export function toPathIfFileURL(fileURLOrPath: string | Buffer | URL): string {
|
|
20
|
+
return normalizePath(fileURLOrPath as any);
|
|
24
21
|
}
|
|
25
22
|
export class WriteStream extends Writable implements IWriteStream {
|
|
26
23
|
|