@gjsify/fs 0.0.4 → 0.1.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.
Files changed (91) hide show
  1. package/README.md +31 -2
  2. package/lib/esm/callback.js +251 -15
  3. package/lib/esm/dirent.js +47 -6
  4. package/lib/esm/encoding.js +2 -3
  5. package/lib/esm/errors.js +13 -0
  6. package/lib/esm/file-handle.js +108 -66
  7. package/lib/esm/fs-watcher.js +44 -7
  8. package/lib/esm/index.js +140 -5
  9. package/lib/esm/promises.js +290 -69
  10. package/lib/esm/read-stream.js +82 -57
  11. package/lib/esm/stats.js +138 -18
  12. package/lib/esm/sync.js +293 -44
  13. package/lib/esm/write-stream.js +4 -4
  14. package/lib/types/callback.d.ts +233 -0
  15. package/lib/types/dirent.d.ts +77 -0
  16. package/lib/types/encoding.d.ts +6 -0
  17. package/lib/types/errors.d.ts +7 -0
  18. package/lib/types/file-handle.d.ts +367 -0
  19. package/lib/types/fs-watcher.d.ts +17 -0
  20. package/lib/types/index.d.ts +149 -0
  21. package/lib/types/promises.d.ts +158 -0
  22. package/lib/types/read-stream.d.ts +21 -0
  23. package/lib/types/stats.d.ts +67 -0
  24. package/lib/types/sync.d.ts +109 -0
  25. package/lib/types/types/encoding-option.d.ts +2 -0
  26. package/lib/types/types/file-read-options.d.ts +15 -0
  27. package/lib/types/types/file-read-result.d.ts +4 -0
  28. package/lib/types/types/flag-and-open-mode.d.ts +5 -0
  29. package/lib/types/types/index.d.ts +6 -0
  30. package/lib/types/types/open-flags.d.ts +1 -0
  31. package/lib/types/types/read-options.d.ts +5 -0
  32. package/lib/types/utils.d.ts +2 -0
  33. package/lib/types/write-stream.d.ts +45 -0
  34. package/package.json +22 -35
  35. package/src/callback.spec.ts +284 -30
  36. package/src/callback.ts +352 -39
  37. package/src/dirent.ts +56 -8
  38. package/src/encoding.ts +7 -2
  39. package/src/errors.spec.ts +389 -0
  40. package/src/errors.ts +19 -0
  41. package/src/extended.spec.ts +706 -0
  42. package/src/file-handle.spec.ts +104 -23
  43. package/src/file-handle.ts +147 -79
  44. package/src/fs-watcher.ts +55 -8
  45. package/src/index.ts +146 -2
  46. package/src/new-apis.spec.ts +505 -0
  47. package/src/promises.spec.ts +651 -11
  48. package/src/promises.ts +353 -81
  49. package/src/read-stream.ts +98 -74
  50. package/src/stat.spec.ts +22 -14
  51. package/src/stats.ts +176 -75
  52. package/src/streams.spec.ts +455 -0
  53. package/src/symlink.spec.ts +176 -26
  54. package/src/sync.spec.ts +204 -32
  55. package/src/sync.ts +363 -58
  56. package/src/test.mts +7 -2
  57. package/src/types/encoding-option.ts +1 -1
  58. package/src/types/flag-and-open-mode.ts +1 -1
  59. package/src/types/read-options.ts +2 -2
  60. package/src/utils.ts +2 -0
  61. package/src/write-stream.ts +9 -7
  62. package/tsconfig.json +23 -10
  63. package/tsconfig.tsbuildinfo +1 -0
  64. package/lib/cjs/callback.js +0 -112
  65. package/lib/cjs/dirent.js +0 -98
  66. package/lib/cjs/encoding.js +0 -34
  67. package/lib/cjs/file-handle.js +0 -444
  68. package/lib/cjs/fs-watcher.js +0 -50
  69. package/lib/cjs/index.js +0 -95
  70. package/lib/cjs/promises.js +0 -160
  71. package/lib/cjs/read-stream.js +0 -78
  72. package/lib/cjs/stats.js +0 -45
  73. package/lib/cjs/sync.js +0 -126
  74. package/lib/cjs/types/encoding-option.js +0 -0
  75. package/lib/cjs/types/file-read-options.js +0 -0
  76. package/lib/cjs/types/file-read-result.js +0 -0
  77. package/lib/cjs/types/flag-and-open-mode.js +0 -0
  78. package/lib/cjs/types/index.js +0 -6
  79. package/lib/cjs/types/open-flags.js +0 -0
  80. package/lib/cjs/types/read-options.js +0 -0
  81. package/lib/cjs/utils.js +0 -18
  82. package/lib/cjs/write-stream.js +0 -116
  83. package/test/watch.js +0 -1
  84. package/test.gjs.js +0 -35359
  85. package/test.gjs.js.map +0 -7
  86. package/test.gjs.mjs +0 -40534
  87. package/test.gjs.mjs.meta.json +0 -1
  88. package/test.node.js +0 -1479
  89. package/test.node.js.map +0 -7
  90. package/test.node.mjs +0 -710
  91. package/tsconfig.types.json +0 -8
@@ -0,0 +1,706 @@
1
+ // Ported from refs/node-test/parallel/test-fs-*.js
2
+ // Original: MIT license, Node.js contributors
3
+
4
+ import { describe, it, expect } from '@gjsify/unit';
5
+ import {
6
+ existsSync, mkdtempSync, rmSync, writeFileSync, readFileSync,
7
+ realpathSync, symlinkSync, unlinkSync, linkSync, chmodSync,
8
+ statSync, lstatSync, mkdirSync, rmdirSync,
9
+ createReadStream, createWriteStream,
10
+ realpath, link, chmod, stat, lstat, readFile, writeFile,
11
+ } from 'node:fs';
12
+ import * as promises from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+ import { tmpdir } from 'node:os';
15
+ import { Buffer } from 'node:buffer';
16
+
17
+ export default async () => {
18
+
19
+ // ==================== realpathSync ====================
20
+
21
+ await describe('fs.realpathSync', async () => {
22
+ await it('should resolve the real path of a file', async () => {
23
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'realpath-');
24
+ const filePath = join(dir, 'file.txt');
25
+ writeFileSync(filePath, 'data');
26
+
27
+ const resolved = realpathSync(filePath);
28
+ expect(typeof resolved).toBe('string');
29
+ expect(resolved.length).toBeGreaterThan(0);
30
+ // Should be an absolute path
31
+ expect(resolved.startsWith('/')).toBeTruthy();
32
+
33
+ rmSync(filePath);
34
+ rmdirSync(dir);
35
+ });
36
+
37
+ await it('should resolve /tmp to its real path', async () => {
38
+ const resolved = realpathSync('/tmp');
39
+ expect(typeof resolved).toBe('string');
40
+ expect(resolved.length).toBeGreaterThan(0);
41
+ });
42
+
43
+ await it('should throw for non-existent path', async () => {
44
+ expect(() => realpathSync('/nonexistent_gjsify_test_path_12345')).toThrow();
45
+ });
46
+ });
47
+
48
+ // ==================== realpath (callback) ====================
49
+
50
+ await describe('fs.realpath (callback)', async () => {
51
+ await it('should resolve the real path of a file', async () => {
52
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'realpath-cb-');
53
+ const filePath = join(dir, 'file.txt');
54
+ writeFileSync(filePath, 'data');
55
+
56
+ const resolved = await new Promise<string>((resolve, reject) => {
57
+ realpath(filePath, (err, res) => {
58
+ if (err) reject(err);
59
+ else resolve(res);
60
+ });
61
+ });
62
+ expect(typeof resolved).toBe('string');
63
+ expect(resolved.startsWith('/')).toBeTruthy();
64
+
65
+ rmSync(filePath);
66
+ rmdirSync(dir);
67
+ });
68
+
69
+ await it('should callback with error for non-existent path', async () => {
70
+ const err = await new Promise<Error>((resolve) => {
71
+ realpath('/nonexistent_gjsify_12345', (e) => {
72
+ resolve(e!);
73
+ });
74
+ });
75
+ expect(err).toBeDefined();
76
+ });
77
+ });
78
+
79
+ // ==================== promises.stat / promises.lstat ====================
80
+
81
+ await describe('fs.promises.stat', async () => {
82
+ await it('should return stats for existing file', async () => {
83
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'pstat-');
84
+ const filePath = join(dir, 'file.txt');
85
+ writeFileSync(filePath, 'hello');
86
+
87
+ const stats = await promises.stat(filePath);
88
+ expect(stats).toBeDefined();
89
+ expect(stats.isFile()).toBeTruthy();
90
+ expect(stats.isDirectory()).toBeFalsy();
91
+ expect(stats.size).toBe(5);
92
+
93
+ rmSync(filePath);
94
+ rmdirSync(dir);
95
+ });
96
+
97
+ await it('should return stats for directory', async () => {
98
+ const stats = await promises.stat('/tmp');
99
+ expect(stats.isDirectory()).toBeTruthy();
100
+ expect(stats.isFile()).toBeFalsy();
101
+ });
102
+
103
+ await it('should reject for non-existent path', async () => {
104
+ let threw = false;
105
+ try {
106
+ await promises.stat('/nonexistent_gjsify_12345');
107
+ } catch {
108
+ threw = true;
109
+ }
110
+ expect(threw).toBeTruthy();
111
+ });
112
+ });
113
+
114
+ await describe('fs.promises.lstat', async () => {
115
+ await it('should return stats for existing file', async () => {
116
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'plstat-');
117
+ const filePath = join(dir, 'file.txt');
118
+ writeFileSync(filePath, 'data');
119
+
120
+ const stats = await promises.lstat(filePath);
121
+ expect(stats.isFile()).toBeTruthy();
122
+
123
+ rmSync(filePath);
124
+ rmdirSync(dir);
125
+ });
126
+ });
127
+
128
+ // ==================== linkSync / link ====================
129
+
130
+ await describe('fs.linkSync', async () => {
131
+ await it('should create a hard link', async () => {
132
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'link-');
133
+ const src = join(dir, 'original.txt');
134
+ const dest = join(dir, 'hardlink.txt');
135
+ writeFileSync(src, 'linked content');
136
+
137
+ linkSync(src, dest);
138
+ expect(existsSync(dest)).toBeTruthy();
139
+ expect(String(readFileSync(dest, 'utf-8'))).toBe('linked content');
140
+
141
+ // Hard links share the same inode
142
+ const srcStat = statSync(src);
143
+ const destStat = statSync(dest);
144
+ expect(srcStat.ino).toBe(destStat.ino);
145
+
146
+ rmSync(dest);
147
+ rmSync(src);
148
+ rmdirSync(dir);
149
+ });
150
+ });
151
+
152
+ await describe('fs.link (callback)', async () => {
153
+ await it('should create a hard link asynchronously', async () => {
154
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'link-cb-');
155
+ const src = join(dir, 'src.txt');
156
+ const dest = join(dir, 'link.txt');
157
+ writeFileSync(src, 'data');
158
+
159
+ await new Promise<void>((resolve, reject) => {
160
+ link(src, dest, (err) => {
161
+ if (err) reject(err);
162
+ else resolve();
163
+ });
164
+ });
165
+ expect(existsSync(dest)).toBeTruthy();
166
+
167
+ rmSync(dest);
168
+ rmSync(src);
169
+ rmdirSync(dir);
170
+ });
171
+ });
172
+
173
+ // ==================== stat / lstat (callback) extended ====================
174
+
175
+ await describe('fs.stat (callback) extended', async () => {
176
+ await it('should return file size', async () => {
177
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'stat-ext-');
178
+ const filePath = join(dir, 'sized.txt');
179
+ writeFileSync(filePath, 'hello world');
180
+
181
+ const stats = await new Promise<any>((resolve, reject) => {
182
+ stat(filePath, (err, s) => {
183
+ if (err) reject(err);
184
+ else resolve(s);
185
+ });
186
+ });
187
+ expect(stats.size).toBe(11);
188
+ expect(stats.isFile()).toBeTruthy();
189
+
190
+ rmSync(filePath);
191
+ rmdirSync(dir);
192
+ });
193
+
194
+ await it('should error for non-existent file', async () => {
195
+ const err = await new Promise<Error>((resolve) => {
196
+ stat('/nonexistent_gjsify_12345', (e) => resolve(e!));
197
+ });
198
+ expect(err).toBeDefined();
199
+ });
200
+ });
201
+
202
+ await describe('fs.lstat (callback)', async () => {
203
+ await it('should return stats for file', async () => {
204
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'lstat-cb-');
205
+ const filePath = join(dir, 'file.txt');
206
+ writeFileSync(filePath, 'data');
207
+
208
+ const stats = await new Promise<any>((resolve, reject) => {
209
+ lstat(filePath, (err, s) => {
210
+ if (err) reject(err);
211
+ else resolve(s);
212
+ });
213
+ });
214
+ expect(stats.isFile()).toBeTruthy();
215
+
216
+ rmSync(filePath);
217
+ rmdirSync(dir);
218
+ });
219
+ });
220
+
221
+ // ==================== chmodSync / chmod ====================
222
+
223
+ await describe('fs.chmodSync', async () => {
224
+ await it('should change file permissions', async () => {
225
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'chmod-');
226
+ const filePath = join(dir, 'file.txt');
227
+ writeFileSync(filePath, 'data');
228
+
229
+ chmodSync(filePath, 0o644);
230
+ const stats = statSync(filePath);
231
+ // Check permission bits (mask out file type bits)
232
+ expect(stats.mode & 0o777).toBe(0o644);
233
+
234
+ chmodSync(filePath, 0o755);
235
+ const stats2 = statSync(filePath);
236
+ expect(stats2.mode & 0o777).toBe(0o755);
237
+
238
+ rmSync(filePath);
239
+ rmdirSync(dir);
240
+ });
241
+ });
242
+
243
+ await describe('fs.chmod (callback)', async () => {
244
+ await it('should change file permissions asynchronously', async () => {
245
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'chmod-cb-');
246
+ const filePath = join(dir, 'file.txt');
247
+ writeFileSync(filePath, 'data');
248
+
249
+ await new Promise<void>((resolve, reject) => {
250
+ chmod(filePath, 0o600, (err) => {
251
+ if (err) reject(err);
252
+ else resolve();
253
+ });
254
+ });
255
+ const stats = statSync(filePath);
256
+ expect(stats.mode & 0o777).toBe(0o600);
257
+
258
+ rmSync(filePath);
259
+ rmdirSync(dir);
260
+ });
261
+ });
262
+
263
+ // ==================== promises.chmod ====================
264
+
265
+ await describe('fs.promises.chmod', async () => {
266
+ await it('should change file permissions', async () => {
267
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'pchmod-');
268
+ const filePath = join(dir, 'file.txt');
269
+ writeFileSync(filePath, 'data');
270
+
271
+ await promises.chmod(filePath, 0o640);
272
+ const stats = statSync(filePath);
273
+ expect(stats.mode & 0o777).toBe(0o640);
274
+
275
+ rmSync(filePath);
276
+ rmdirSync(dir);
277
+ });
278
+ });
279
+
280
+ // ==================== promises.writeFile / readFile ====================
281
+
282
+ await describe('fs.promises.writeFile / readFile extended', async () => {
283
+ await it('should write and read Buffer data', async () => {
284
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'prw-');
285
+ const filePath = join(dir, 'buf.bin');
286
+ const data = Buffer.from([0x00, 0x01, 0x02, 0xff]);
287
+
288
+ await promises.writeFile(filePath, data);
289
+ const read = await promises.readFile(filePath);
290
+ expect(read.length).toBe(4);
291
+
292
+ rmSync(filePath);
293
+ rmdirSync(dir);
294
+ });
295
+
296
+ await it('should write and read string with encoding', async () => {
297
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'prw-str-');
298
+ const filePath = join(dir, 'str.txt');
299
+
300
+ await promises.writeFile(filePath, 'hello promises', 'utf8');
301
+ const content = await promises.readFile(filePath, 'utf8');
302
+ expect(content).toBe('hello promises');
303
+
304
+ rmSync(filePath);
305
+ rmdirSync(dir);
306
+ });
307
+ });
308
+
309
+ // ==================== promises.rename ====================
310
+
311
+ await describe('fs.promises.rename', async () => {
312
+ await it('should rename a file', async () => {
313
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'prename-');
314
+ const old = join(dir, 'old.txt');
315
+ const newPath = join(dir, 'new.txt');
316
+ writeFileSync(old, 'rename me');
317
+
318
+ await promises.rename(old, newPath);
319
+ expect(existsSync(newPath)).toBeTruthy();
320
+ expect(existsSync(old)).toBeFalsy();
321
+
322
+ rmSync(newPath);
323
+ rmdirSync(dir);
324
+ });
325
+ });
326
+
327
+ // ==================== promises.access ====================
328
+
329
+ await describe('fs.promises.access', async () => {
330
+ await it('should resolve for existing file', async () => {
331
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'paccess-');
332
+ const filePath = join(dir, 'file.txt');
333
+ writeFileSync(filePath, 'data');
334
+
335
+ await promises.access(filePath);
336
+ // Should not throw
337
+
338
+ rmSync(filePath);
339
+ rmdirSync(dir);
340
+ });
341
+
342
+ await it('should reject for non-existent file', async () => {
343
+ let threw = false;
344
+ try {
345
+ await promises.access('/nonexistent_gjsify_12345');
346
+ } catch {
347
+ threw = true;
348
+ }
349
+ expect(threw).toBeTruthy();
350
+ });
351
+ });
352
+
353
+ // ==================== promises.appendFile ====================
354
+
355
+ await describe('fs.promises.appendFile', async () => {
356
+ await it('should append to a file', async () => {
357
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'pappend-');
358
+ const filePath = join(dir, 'append.txt');
359
+ writeFileSync(filePath, 'first');
360
+
361
+ await promises.appendFile(filePath, ' second');
362
+ const content = await promises.readFile(filePath, 'utf8');
363
+ expect(content).toBe('first second');
364
+
365
+ rmSync(filePath);
366
+ rmdirSync(dir);
367
+ });
368
+ });
369
+
370
+ // ==================== promises.copyFile ====================
371
+
372
+ await describe('fs.promises.copyFile', async () => {
373
+ await it('should copy a file', async () => {
374
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'pcopy-');
375
+ const src = join(dir, 'src.txt');
376
+ const dest = join(dir, 'copy.txt');
377
+ writeFileSync(src, 'copy me');
378
+
379
+ await promises.copyFile(src, dest);
380
+ const content = await promises.readFile(dest, 'utf8');
381
+ expect(content).toBe('copy me');
382
+
383
+ rmSync(dest);
384
+ rmSync(src);
385
+ rmdirSync(dir);
386
+ });
387
+ });
388
+
389
+ // ==================== promises.truncate ====================
390
+
391
+ await describe('fs.promises.truncate', async () => {
392
+ await it('should truncate a file', async () => {
393
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'ptrunc-');
394
+ const filePath = join(dir, 'trunc.txt');
395
+ writeFileSync(filePath, 'hello world');
396
+
397
+ await promises.truncate(filePath, 5);
398
+ const content = await promises.readFile(filePath, 'utf8');
399
+ expect(content).toBe('hello');
400
+
401
+ rmSync(filePath);
402
+ rmdirSync(dir);
403
+ });
404
+ });
405
+
406
+ // ==================== promises.realpath ====================
407
+
408
+ await describe('fs.promises.realpath', async () => {
409
+ await it('should resolve real path', async () => {
410
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'prealpath-');
411
+ const filePath = join(dir, 'file.txt');
412
+ writeFileSync(filePath, 'data');
413
+
414
+ const resolved = await promises.realpath(filePath);
415
+ expect(typeof resolved).toBe('string');
416
+ expect(resolved.startsWith('/')).toBeTruthy();
417
+
418
+ rmSync(filePath);
419
+ rmdirSync(dir);
420
+ });
421
+ });
422
+
423
+ // ==================== promises.link ====================
424
+
425
+ await describe('fs.promises.link', async () => {
426
+ await it('should create a hard link', async () => {
427
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'plink-');
428
+ const src = join(dir, 'src.txt');
429
+ const dest = join(dir, 'link.txt');
430
+ writeFileSync(src, 'linked');
431
+
432
+ await promises.link(src, dest);
433
+ const content = await promises.readFile(dest, 'utf8');
434
+ expect(content).toBe('linked');
435
+
436
+ rmSync(dest);
437
+ rmSync(src);
438
+ rmdirSync(dir);
439
+ });
440
+ });
441
+
442
+ // ==================== createReadStream extended ====================
443
+
444
+ await describe('createReadStream extended', async () => {
445
+ await it('should emit end event', async () => {
446
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'crs-');
447
+ const filePath = join(dir, 'read.txt');
448
+ writeFileSync(filePath, 'stream data');
449
+
450
+ const stream = createReadStream(filePath, { encoding: 'utf8' });
451
+ const chunks: string[] = [];
452
+ await new Promise<void>((resolve) => {
453
+ stream.on('data', (chunk) => chunks.push(chunk as string));
454
+ stream.on('end', () => resolve());
455
+ });
456
+ expect(chunks.join('')).toBe('stream data');
457
+
458
+ rmSync(filePath);
459
+ rmdirSync(dir);
460
+ });
461
+
462
+ await it('should handle empty file', async () => {
463
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'crs-empty-');
464
+ const filePath = join(dir, 'empty.txt');
465
+ writeFileSync(filePath, '');
466
+
467
+ const stream = createReadStream(filePath, { encoding: 'utf8' });
468
+ const chunks: string[] = [];
469
+ await new Promise<void>((resolve) => {
470
+ stream.on('data', (chunk) => chunks.push(chunk as string));
471
+ stream.on('end', () => resolve());
472
+ });
473
+ expect(chunks.join('')).toBe('');
474
+
475
+ rmSync(filePath);
476
+ rmdirSync(dir);
477
+ });
478
+
479
+ await it('should emit error for non-existent file', async () => {
480
+ const stream = createReadStream('/nonexistent_gjsify_12345');
481
+ const err = await new Promise<Error>((resolve) => {
482
+ stream.on('error', (e) => resolve(e));
483
+ });
484
+ expect(err).toBeDefined();
485
+ });
486
+ });
487
+
488
+ // ==================== createWriteStream extended ====================
489
+
490
+ await describe('createWriteStream extended', async () => {
491
+ await it('should write data to file', async () => {
492
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'cws-');
493
+ const filePath = join(dir, 'write.txt');
494
+
495
+ const stream = createWriteStream(filePath);
496
+ await new Promise<void>((resolve) => {
497
+ stream.write('hello ');
498
+ stream.write('world');
499
+ stream.end(() => resolve());
500
+ });
501
+
502
+ const content = readFileSync(filePath, 'utf8');
503
+ expect(content).toBe('hello world');
504
+
505
+ rmSync(filePath);
506
+ rmdirSync(dir);
507
+ });
508
+
509
+ await it('should emit finish event', async () => {
510
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'cws-fin-');
511
+ const filePath = join(dir, 'finish.txt');
512
+
513
+ const stream = createWriteStream(filePath);
514
+ const finished = await new Promise<boolean>((resolve) => {
515
+ stream.on('finish', () => resolve(true));
516
+ stream.end('done');
517
+ });
518
+ expect(finished).toBeTruthy();
519
+
520
+ rmSync(filePath);
521
+ rmdirSync(dir);
522
+ });
523
+ });
524
+
525
+ // ==================== readFile / writeFile callback extended ====================
526
+
527
+ await describe('fs.readFile / writeFile callback extended', async () => {
528
+ await it('readFile should accept encoding option', async () => {
529
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'rf-enc-');
530
+ const filePath = join(dir, 'encoded.txt');
531
+ writeFileSync(filePath, 'encoded content');
532
+
533
+ const data = await new Promise<string>((resolve, reject) => {
534
+ readFile(filePath, 'utf8', (err, d) => {
535
+ if (err) reject(err);
536
+ else resolve(d as string);
537
+ });
538
+ });
539
+ expect(data).toBe('encoded content');
540
+
541
+ rmSync(filePath);
542
+ rmdirSync(dir);
543
+ });
544
+
545
+ await it('readFile should return Buffer without encoding', async () => {
546
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'rf-buf-');
547
+ const filePath = join(dir, 'raw.txt');
548
+ writeFileSync(filePath, 'raw');
549
+
550
+ const data = await new Promise<Buffer>((resolve, reject) => {
551
+ readFile(filePath, (err, d) => {
552
+ if (err) reject(err);
553
+ else resolve(d as Buffer);
554
+ });
555
+ });
556
+ expect(data instanceof Uint8Array).toBeTruthy();
557
+
558
+ rmSync(filePath);
559
+ rmdirSync(dir);
560
+ });
561
+
562
+ await it('writeFile callback should complete without error', async () => {
563
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'wf-cb-');
564
+ const filePath = join(dir, 'written.txt');
565
+
566
+ await new Promise<void>((resolve, reject) => {
567
+ writeFile(filePath, 'callback write', (err) => {
568
+ if (err) reject(err);
569
+ else resolve();
570
+ });
571
+ });
572
+ expect(readFileSync(filePath, 'utf8')).toBe('callback write');
573
+
574
+ rmSync(filePath);
575
+ rmdirSync(dir);
576
+ });
577
+ });
578
+
579
+ // ==================== statSync extended ====================
580
+
581
+ await describe('fs.statSync extended', async () => {
582
+ await it('should return file timestamps', async () => {
583
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'stat-ts-');
584
+ const filePath = join(dir, 'timed.txt');
585
+ writeFileSync(filePath, 'data');
586
+
587
+ const stats = statSync(filePath);
588
+ expect(stats.atime).toBeDefined();
589
+ expect(stats.mtime).toBeDefined();
590
+ expect(stats.ctime).toBeDefined();
591
+ expect(stats.birthtime).toBeDefined();
592
+ expect(stats.atimeMs).toBeGreaterThan(0);
593
+ expect(stats.mtimeMs).toBeGreaterThan(0);
594
+
595
+ rmSync(filePath);
596
+ rmdirSync(dir);
597
+ });
598
+
599
+ await it('should return correct nlink for hard links', async () => {
600
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'stat-nlink-');
601
+ const src = join(dir, 'src.txt');
602
+ const link1 = join(dir, 'link1.txt');
603
+ writeFileSync(src, 'data');
604
+
605
+ const before = statSync(src);
606
+ expect(before.nlink).toBe(1);
607
+
608
+ linkSync(src, link1);
609
+ const after = statSync(src);
610
+ expect(after.nlink).toBe(2);
611
+
612
+ rmSync(link1);
613
+ rmSync(src);
614
+ rmdirSync(dir);
615
+ });
616
+ });
617
+
618
+ // ==================== symlink creation ====================
619
+
620
+ await describe('fs.symlinkSync (creation)', async () => {
621
+ await it('should create and read a symlink', async () => {
622
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'sym-');
623
+ const target = join(dir, 'target.txt');
624
+ const link = join(dir, 'symlink.txt');
625
+ writeFileSync(target, 'symlink content');
626
+
627
+ symlinkSync(target, link);
628
+ expect(existsSync(link)).toBeTruthy();
629
+
630
+ // lstatSync should show it as a symlink
631
+ const stats = lstatSync(link);
632
+ expect(stats.isSymbolicLink()).toBeTruthy();
633
+
634
+ // Reading through the symlink should give original content
635
+ expect(readFileSync(link, 'utf8')).toBe('symlink content');
636
+
637
+ unlinkSync(link);
638
+ rmSync(target);
639
+ rmdirSync(dir);
640
+ });
641
+ });
642
+
643
+ // ==================== promises.symlink ====================
644
+
645
+ await describe('fs.promises.symlink', async () => {
646
+ await it('should create a symlink', async () => {
647
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'psym-');
648
+ const target = join(dir, 'target.txt');
649
+ const link = join(dir, 'link.txt');
650
+ writeFileSync(target, 'data');
651
+
652
+ await promises.symlink(target, link);
653
+ expect(existsSync(link)).toBeTruthy();
654
+
655
+ const stats = await promises.lstat(link);
656
+ expect(stats.isSymbolicLink()).toBeTruthy();
657
+
658
+ unlinkSync(link);
659
+ rmSync(target);
660
+ rmdirSync(dir);
661
+ });
662
+ });
663
+
664
+ // ==================== promises.unlink ====================
665
+
666
+ await describe('fs.promises.unlink', async () => {
667
+ await it('should delete a file', async () => {
668
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'punlink-');
669
+ const filePath = join(dir, 'delete.txt');
670
+ writeFileSync(filePath, 'delete me');
671
+
672
+ await promises.unlink(filePath);
673
+ expect(existsSync(filePath)).toBeFalsy();
674
+
675
+ rmdirSync(dir);
676
+ });
677
+ });
678
+
679
+ // ==================== promises.mkdir / rmdir ====================
680
+
681
+ await describe('fs.promises.mkdir / rmdir', async () => {
682
+ await it('should create and remove a directory', async () => {
683
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'pmkdir-');
684
+ const subdir = join(dir, 'subdir');
685
+
686
+ await promises.mkdir(subdir);
687
+ const stats = await promises.stat(subdir);
688
+ expect(stats.isDirectory()).toBeTruthy();
689
+
690
+ await promises.rmdir(subdir);
691
+ expect(existsSync(subdir)).toBeFalsy();
692
+
693
+ rmdirSync(dir);
694
+ });
695
+
696
+ await it('should create nested directories with recursive', async () => {
697
+ const dir = mkdtempSync(join(tmpdir(), 'fs-') + 'pmkdir-rec-');
698
+ const nested = join(dir, 'a', 'b', 'c');
699
+
700
+ await promises.mkdir(nested, { recursive: true });
701
+ expect(existsSync(nested)).toBeTruthy();
702
+
703
+ rmSync(dir, { recursive: true });
704
+ });
705
+ });
706
+ };