@gjsify/fs 0.4.0 → 0.4.3

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.
Files changed (50) hide show
  1. package/package.json +51 -48
  2. package/src/callback.spec.ts +0 -296
  3. package/src/callback.ts +0 -684
  4. package/src/cp.spec.ts +0 -181
  5. package/src/cp.ts +0 -328
  6. package/src/dir.spec.ts +0 -204
  7. package/src/dir.ts +0 -199
  8. package/src/dirent.ts +0 -165
  9. package/src/encoding.ts +0 -45
  10. package/src/errors.spec.ts +0 -389
  11. package/src/errors.ts +0 -19
  12. package/src/extended.spec.ts +0 -706
  13. package/src/fd-ops.spec.ts +0 -234
  14. package/src/fd-ops.ts +0 -251
  15. package/src/file-handle.spec.ts +0 -115
  16. package/src/file-handle.ts +0 -856
  17. package/src/fs-watcher.ts +0 -198
  18. package/src/glob.spec.ts +0 -201
  19. package/src/glob.ts +0 -205
  20. package/src/index.ts +0 -313
  21. package/src/new-apis.spec.ts +0 -505
  22. package/src/promises.spec.ts +0 -812
  23. package/src/promises.ts +0 -686
  24. package/src/read-stream.ts +0 -128
  25. package/src/stat-watcher.ts +0 -116
  26. package/src/stat.spec.ts +0 -87
  27. package/src/statfs.spec.ts +0 -67
  28. package/src/statfs.ts +0 -92
  29. package/src/stats.ts +0 -207
  30. package/src/streams.spec.ts +0 -513
  31. package/src/symlink.spec.ts +0 -188
  32. package/src/sync.spec.ts +0 -377
  33. package/src/sync.ts +0 -568
  34. package/src/test.mts +0 -27
  35. package/src/types/encoding-option.ts +0 -3
  36. package/src/types/file-read-options.ts +0 -15
  37. package/src/types/file-read-result.ts +0 -4
  38. package/src/types/flag-and-open-mode.ts +0 -6
  39. package/src/types/index.ts +0 -6
  40. package/src/types/open-flags.ts +0 -14
  41. package/src/types/read-options.ts +0 -9
  42. package/src/utils.ts +0 -31
  43. package/src/utimes.spec.ts +0 -113
  44. package/src/utimes.ts +0 -97
  45. package/src/watch.spec.ts +0 -171
  46. package/src/watchfile.spec.ts +0 -185
  47. package/src/write-stream.ts +0 -142
  48. package/test/file.txt +0 -1
  49. package/tsconfig.json +0 -29
  50. package/tsconfig.tsbuildinfo +0 -1
@@ -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
- };
@@ -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
- };