@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.
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,513 +0,0 @@
1
- // fs stream tests — createReadStream, createWriteStream, pipe integration
2
- // Reference: refs/node-test/parallel/test-fs-read-stream*.js, test-fs-write-stream*.js
3
-
4
- import { describe, it, expect } from '@gjsify/unit';
5
- import { createReadStream, createWriteStream, writeFileSync, readFileSync, mkdtempSync, rmSync, mkdirSync } from 'node:fs';
6
- import { join } from 'node:path';
7
- import { tmpdir } from 'node:os';
8
- import { Buffer } from 'node:buffer';
9
- import { Transform, PassThrough } from 'node:stream';
10
-
11
- let tmpDir: string;
12
-
13
- function setup(): void {
14
- tmpDir = mkdtempSync(join(tmpdir(), 'gjsify-fs-stream-'));
15
- }
16
-
17
- function cleanup(): void {
18
- try { rmSync(tmpDir, { recursive: true }); } catch {}
19
- }
20
-
21
- export default async () => {
22
- // ---- createReadStream ----
23
-
24
- await describe('fs.createReadStream', async () => {
25
- await it('should read a file and emit data + end events', async () => {
26
- setup();
27
- try {
28
- const filePath = join(tmpDir, 'read-test.txt');
29
- writeFileSync(filePath, 'hello world');
30
- const chunks: string[] = [];
31
- await new Promise<void>((resolve, reject) => {
32
- const stream = createReadStream(filePath, { encoding: 'utf8' });
33
- stream.on('data', (chunk) => chunks.push(chunk as string));
34
- stream.on('end', () => resolve());
35
- stream.on('error', reject);
36
- });
37
- expect(chunks.join('')).toBe('hello world');
38
- } finally {
39
- cleanup();
40
- }
41
- });
42
-
43
- await it('should read file as Buffer by default', async () => {
44
- setup();
45
- try {
46
- const filePath = join(tmpDir, 'buf-test.txt');
47
- writeFileSync(filePath, 'buffer data');
48
- const chunks: Buffer[] = [];
49
- await new Promise<void>((resolve, reject) => {
50
- const stream = createReadStream(filePath);
51
- stream.on('data', (chunk) => chunks.push(Buffer.from(chunk as Uint8Array)));
52
- stream.on('end', () => resolve());
53
- stream.on('error', reject);
54
- });
55
- const result = Buffer.concat(chunks).toString('utf8');
56
- expect(result).toBe('buffer data');
57
- } finally {
58
- cleanup();
59
- }
60
- });
61
-
62
- await it('should handle empty file', async () => {
63
- setup();
64
- try {
65
- const filePath = join(tmpDir, 'empty.txt');
66
- writeFileSync(filePath, '');
67
- const chunks: string[] = [];
68
- await new Promise<void>((resolve, reject) => {
69
- const stream = createReadStream(filePath, { encoding: 'utf8' });
70
- stream.on('data', (chunk) => chunks.push(chunk as string));
71
- stream.on('end', () => resolve());
72
- stream.on('error', reject);
73
- });
74
- expect(chunks.join('')).toBe('');
75
- } finally {
76
- cleanup();
77
- }
78
- });
79
-
80
- await it('should emit error for non-existent file', async () => {
81
- const err = await new Promise<Error>((resolve) => {
82
- const stream = createReadStream('/nonexistent_gjsify_test_12345');
83
- stream.on('error', (e) => resolve(e));
84
- });
85
- expect(err).toBeDefined();
86
- // GJS Gio errors have numeric codes; Node.js has string 'ENOENT'
87
- const code: unknown = (err as NodeJS.ErrnoException).code;
88
- expect(code === 'ENOENT' || code === 1 || (err as any).code !== undefined).toBeTruthy();
89
- });
90
-
91
- await it('should read file with start and end options', async () => {
92
- setup();
93
- try {
94
- const filePath = join(tmpDir, 'range-test.txt');
95
- writeFileSync(filePath, 'abcdefghij');
96
- const chunks: string[] = [];
97
- await new Promise<void>((resolve, reject) => {
98
- const stream = createReadStream(filePath, { encoding: 'utf8', start: 2, end: 6 });
99
- stream.on('data', (chunk) => chunks.push(chunk as string));
100
- stream.on('end', () => resolve());
101
- stream.on('error', reject);
102
- });
103
- // start=2 (index 'c'), end=6 (index 'g', inclusive)
104
- expect(chunks.join('')).toBe('cdefg');
105
- } finally {
106
- cleanup();
107
- }
108
- });
109
-
110
- await it('should read a larger file (64KB)', async () => {
111
- setup();
112
- try {
113
- const filePath = join(tmpDir, 'large.txt');
114
- const size = 64 * 1024;
115
- writeFileSync(filePath, 'A'.repeat(size));
116
- let totalLength = 0;
117
- await new Promise<void>((resolve, reject) => {
118
- const stream = createReadStream(filePath);
119
- stream.on('data', (chunk) => { totalLength += (chunk as Buffer).length; });
120
- stream.on('end', () => resolve());
121
- stream.on('error', reject);
122
- });
123
- expect(totalLength).toBe(size);
124
- } finally {
125
- cleanup();
126
- }
127
- });
128
-
129
- await it('should track bytesRead', async () => {
130
- setup();
131
- try {
132
- const filePath = join(tmpDir, 'bytes-read.txt');
133
- const content = 'hello bytes';
134
- writeFileSync(filePath, content);
135
- const stream = createReadStream(filePath);
136
- await new Promise<void>((resolve, reject) => {
137
- stream.on('data', () => {});
138
- stream.on('end', () => resolve());
139
- stream.on('error', reject);
140
- });
141
- expect(stream.bytesRead).toBe(Buffer.byteLength(content));
142
- } finally {
143
- cleanup();
144
- }
145
- });
146
-
147
- await it('should have path property', async () => {
148
- setup();
149
- try {
150
- const filePath = join(tmpDir, 'path-prop.txt');
151
- writeFileSync(filePath, 'test');
152
- const stream = createReadStream(filePath);
153
- expect(stream.path).toBe(filePath);
154
- await new Promise<void>((resolve) => {
155
- stream.on('data', () => {});
156
- stream.on('end', () => resolve());
157
- });
158
- } finally {
159
- cleanup();
160
- }
161
- });
162
-
163
- // Regression: ReadStream uses _construct() for async file opening.
164
- // Verifies that 'open' and 'ready' fire and that pending starts as true.
165
- await it('should emit open and ready events before data, pending starts true', async () => {
166
- setup();
167
- try {
168
- const filePath = join(tmpDir, 'open-ready.txt');
169
- writeFileSync(filePath, 'hello');
170
- const stream = createReadStream(filePath);
171
- expect(stream.pending).toBeTruthy();
172
- const events: string[] = [];
173
- let openFd: number | undefined;
174
- await new Promise<void>((resolve, reject) => {
175
- stream.on('open', (fd: number) => { openFd = fd; events.push('open'); });
176
- stream.on('ready', () => events.push('ready'));
177
- stream.on('data', () => { if (!events.includes('data')) events.push('data'); });
178
- stream.on('end', () => resolve());
179
- stream.on('error', reject);
180
- stream.resume();
181
- });
182
- expect(openFd).toBeDefined();
183
- // 'open' and 'ready' must have fired (file was opened)
184
- expect(events).toContain('open');
185
- expect(events).toContain('ready');
186
- // 'open' must fire before any data event
187
- const openIdx = events.indexOf('open');
188
- const dataIdx = events.indexOf('data');
189
- if (dataIdx !== -1) expect(openIdx).toBeLessThan(dataIdx);
190
- } finally {
191
- cleanup();
192
- }
193
- });
194
-
195
- // Regression: piping a ReadStream before the file is open (while pending=true)
196
- // must still deliver all data. Previously _pendingReadSize approach hung under
197
- // backpressure on GJS 1.88; _construct() fixes this properly.
198
- await it('should pipe correctly when pipe() is called before file opens', async () => {
199
- setup();
200
- try {
201
- const srcPath = join(tmpDir, 'pre-pipe-src.txt');
202
- const dstPath = join(tmpDir, 'pre-pipe-dst.txt');
203
- const size = 3 * 64 * 1024; // 3 × 64KB — forces multiple read chunks + backpressure
204
- writeFileSync(srcPath, 'X'.repeat(size));
205
- await new Promise<void>((resolve, reject) => {
206
- const src = createReadStream(srcPath);
207
- const dst = createWriteStream(dstPath);
208
- // pipe() is called immediately — src is still pending (file not open yet)
209
- expect(src.pending).toBeTruthy();
210
- src.on('error', reject);
211
- dst.on('error', reject);
212
- dst.on('finish', resolve);
213
- src.pipe(dst);
214
- });
215
- expect(readFileSync(dstPath, 'utf8').length).toBe(size);
216
- } finally {
217
- cleanup();
218
- }
219
- });
220
- });
221
-
222
- // ---- createWriteStream ----
223
-
224
- await describe('fs.createWriteStream', async () => {
225
- await it('should write data to a file', async () => {
226
- setup();
227
- try {
228
- const filePath = join(tmpDir, 'write-test.txt');
229
- await new Promise<void>((resolve, reject) => {
230
- const stream = createWriteStream(filePath);
231
- stream.on('error', reject);
232
- stream.write('hello ');
233
- stream.write('world');
234
- stream.end(() => resolve());
235
- });
236
- expect(readFileSync(filePath, 'utf8')).toBe('hello world');
237
- } finally {
238
- cleanup();
239
- }
240
- });
241
-
242
- await it('should emit finish event', async () => {
243
- setup();
244
- try {
245
- const filePath = join(tmpDir, 'finish-test.txt');
246
- const stream = createWriteStream(filePath);
247
- const finished = await new Promise<boolean>((resolve) => {
248
- stream.on('finish', () => resolve(true));
249
- stream.end('done');
250
- });
251
- expect(finished).toBe(true);
252
- expect(readFileSync(filePath, 'utf8')).toBe('done');
253
- } finally {
254
- cleanup();
255
- }
256
- });
257
-
258
- await it('should write Buffer data', async () => {
259
- setup();
260
- try {
261
- const filePath = join(tmpDir, 'buf-write.txt');
262
- await new Promise<void>((resolve, reject) => {
263
- const stream = createWriteStream(filePath);
264
- stream.on('error', reject);
265
- stream.write(Buffer.from('buffer '));
266
- stream.end(Buffer.from('content'));
267
- stream.on('finish', () => resolve());
268
- });
269
- expect(readFileSync(filePath, 'utf8')).toBe('buffer content');
270
- } finally {
271
- cleanup();
272
- }
273
- });
274
-
275
- await it('should write a large file (64KB)', async () => {
276
- setup();
277
- try {
278
- const filePath = join(tmpDir, 'large-write.txt');
279
- const chunkSize = 1024;
280
- const numChunks = 64;
281
- await new Promise<void>((resolve, reject) => {
282
- const stream = createWriteStream(filePath);
283
- stream.on('error', reject);
284
- for (let i = 0; i < numChunks; i++) {
285
- stream.write('B'.repeat(chunkSize));
286
- }
287
- stream.end(() => resolve());
288
- });
289
- const content = readFileSync(filePath, 'utf8');
290
- expect(content.length).toBe(chunkSize * numChunks);
291
- } finally {
292
- cleanup();
293
- }
294
- });
295
-
296
- await it('should have path property', async () => {
297
- setup();
298
- try {
299
- const filePath = join(tmpDir, 'path-prop-ws.txt');
300
- const stream = createWriteStream(filePath);
301
- expect(stream.path).toBe(filePath);
302
- await new Promise<void>((resolve, reject) => {
303
- stream.on('error', reject);
304
- stream.end('test', () => resolve());
305
- });
306
- } finally {
307
- cleanup();
308
- }
309
- });
310
-
311
- await it('should track bytesWritten', async () => {
312
- setup();
313
- try {
314
- const filePath = join(tmpDir, 'bytes-written.txt');
315
- const stream = createWriteStream(filePath);
316
- await new Promise<void>((resolve, reject) => {
317
- stream.on('error', reject);
318
- stream.write('hello');
319
- stream.end(' world', () => resolve());
320
- });
321
- expect(stream.bytesWritten).toBe(11); // "hello world"
322
- } finally {
323
- cleanup();
324
- }
325
- });
326
- });
327
-
328
- // ---- pipe: ReadStream → WriteStream ----
329
-
330
- await describe('fs pipe: createReadStream → createWriteStream', async () => {
331
- await it('should copy a file via pipe', async () => {
332
- setup();
333
- try {
334
- const srcPath = join(tmpDir, 'src.txt');
335
- const dstPath = join(tmpDir, 'dst.txt');
336
- writeFileSync(srcPath, 'pipe copy test');
337
- await new Promise<void>((resolve, reject) => {
338
- const src = createReadStream(srcPath);
339
- const dst = createWriteStream(dstPath);
340
- src.on('error', reject);
341
- dst.on('error', reject);
342
- dst.on('finish', () => resolve());
343
- src.pipe(dst);
344
- });
345
- expect(readFileSync(dstPath, 'utf8')).toBe('pipe copy test');
346
- } finally {
347
- cleanup();
348
- }
349
- });
350
-
351
- await it('should copy a large file via pipe', async () => {
352
- setup();
353
- try {
354
- const srcPath = join(tmpDir, 'large-src.txt');
355
- const dstPath = join(tmpDir, 'large-dst.txt');
356
- const size = 128 * 1024; // 128KB
357
- writeFileSync(srcPath, 'D'.repeat(size));
358
- await new Promise<void>((resolve, reject) => {
359
- const src = createReadStream(srcPath);
360
- const dst = createWriteStream(dstPath);
361
- src.on('error', reject);
362
- dst.on('error', reject);
363
- dst.on('finish', () => resolve());
364
- src.pipe(dst);
365
- });
366
- const result = readFileSync(dstPath, 'utf8');
367
- expect(result.length).toBe(size);
368
- } finally {
369
- cleanup();
370
- }
371
- });
372
-
373
- await it('should pipe through a Transform (uppercase)', async () => {
374
- setup();
375
- try {
376
- const srcPath = join(tmpDir, 'transform-src.txt');
377
- const dstPath = join(tmpDir, 'transform-dst.txt');
378
- writeFileSync(srcPath, 'hello transform');
379
- const upper = new Transform({
380
- transform(chunk, _enc, cb) {
381
- cb(null, chunk.toString().toUpperCase());
382
- },
383
- });
384
- await new Promise<void>((resolve, reject) => {
385
- const src = createReadStream(srcPath, { encoding: 'utf8' });
386
- const dst = createWriteStream(dstPath);
387
- dst.on('error', reject);
388
- dst.on('finish', () => resolve());
389
- src.pipe(upper).pipe(dst);
390
- });
391
- expect(readFileSync(dstPath, 'utf8')).toBe('HELLO TRANSFORM');
392
- } finally {
393
- cleanup();
394
- }
395
- });
396
-
397
- await it('should pipe through a PassThrough', async () => {
398
- setup();
399
- try {
400
- const srcPath = join(tmpDir, 'pt-src.txt');
401
- const dstPath = join(tmpDir, 'pt-dst.txt');
402
- writeFileSync(srcPath, 'passthrough test');
403
- const pt = new PassThrough();
404
- await new Promise<void>((resolve, reject) => {
405
- const src = createReadStream(srcPath);
406
- const dst = createWriteStream(dstPath);
407
- dst.on('error', reject);
408
- dst.on('finish', () => resolve());
409
- src.pipe(pt).pipe(dst);
410
- });
411
- expect(readFileSync(dstPath, 'utf8')).toBe('passthrough test');
412
- } finally {
413
- cleanup();
414
- }
415
- });
416
- });
417
-
418
- // ---- Multiple sequential reads ----
419
-
420
- await describe('fs streams: sequential operations', async () => {
421
- await it('should read the same file twice sequentially', async () => {
422
- setup();
423
- try {
424
- const filePath = join(tmpDir, 'multi-read.txt');
425
- writeFileSync(filePath, 'read me twice');
426
- const readOnce = (): Promise<string> => new Promise((resolve, reject) => {
427
- const chunks: string[] = [];
428
- const stream = createReadStream(filePath, { encoding: 'utf8' });
429
- stream.on('data', (chunk) => chunks.push(chunk as string));
430
- stream.on('end', () => resolve(chunks.join('')));
431
- stream.on('error', reject);
432
- });
433
- const r1 = await readOnce();
434
- const r2 = await readOnce();
435
- expect(r1).toBe('read me twice');
436
- expect(r2).toBe('read me twice');
437
- } finally {
438
- cleanup();
439
- }
440
- });
441
-
442
- await it('should write then read the same file', async () => {
443
- setup();
444
- try {
445
- const filePath = join(tmpDir, 'write-then-read.txt');
446
- // Write
447
- await new Promise<void>((resolve, reject) => {
448
- const ws = createWriteStream(filePath);
449
- ws.on('error', reject);
450
- ws.end('written content', () => resolve());
451
- });
452
- // Read
453
- const chunks: string[] = [];
454
- await new Promise<void>((resolve, reject) => {
455
- const rs = createReadStream(filePath, { encoding: 'utf8' });
456
- rs.on('data', (chunk) => chunks.push(chunk as string));
457
- rs.on('end', () => resolve());
458
- rs.on('error', reject);
459
- });
460
- expect(chunks.join('')).toBe('written content');
461
- } finally {
462
- cleanup();
463
- }
464
- });
465
- });
466
-
467
- // ---- Unicode and special content ----
468
-
469
- await describe('fs streams: unicode and binary', async () => {
470
- await it('should handle UTF-8 content with multibyte chars', async () => {
471
- setup();
472
- try {
473
- const filePath = join(tmpDir, 'unicode.txt');
474
- const content = 'Hallo Welt! 你好世界 🌍🎉';
475
- writeFileSync(filePath, content);
476
- const chunks: string[] = [];
477
- await new Promise<void>((resolve, reject) => {
478
- const stream = createReadStream(filePath, { encoding: 'utf8' });
479
- stream.on('data', (chunk) => chunks.push(chunk as string));
480
- stream.on('end', () => resolve());
481
- stream.on('error', reject);
482
- });
483
- expect(chunks.join('')).toBe(content);
484
- } finally {
485
- cleanup();
486
- }
487
- });
488
-
489
- await it('should handle binary data round-trip', async () => {
490
- setup();
491
- try {
492
- const filePath = join(tmpDir, 'binary.dat');
493
- const data = Buffer.from([0x00, 0x01, 0x80, 0xff, 0xfe, 0x7f, 0x00, 0x42]);
494
- writeFileSync(filePath, data);
495
- const chunks: Buffer[] = [];
496
- await new Promise<void>((resolve, reject) => {
497
- const stream = createReadStream(filePath);
498
- stream.on('data', (chunk) => chunks.push(Buffer.from(chunk as Uint8Array)));
499
- stream.on('end', () => resolve());
500
- stream.on('error', reject);
501
- });
502
- const result = Buffer.concat(chunks);
503
- expect(result.length).toBe(8);
504
- expect(result[0]).toBe(0x00);
505
- expect(result[2]).toBe(0x80);
506
- expect(result[3]).toBe(0xff);
507
- expect(result[7]).toBe(0x42);
508
- } finally {
509
- cleanup();
510
- }
511
- });
512
- });
513
- };
@@ -1,188 +0,0 @@
1
- // Ported from refs/node-test/parallel/test-fs-symlink.js,
2
- // test-fs-readlink.js, test-fs-realpath.js
3
- // Original: MIT license, Node.js contributors
4
- import { describe, it, expect } from '@gjsify/unit';
5
- import {
6
- symlink as symlinkCb,
7
- mkdtempSync,
8
- rmdirSync,
9
- unlinkSync,
10
- lstatSync,
11
- readlinkSync,
12
- realpathSync,
13
- writeFileSync,
14
- } from 'node:fs';
15
- import { symlink, readlink, realpath, unlink, rmdir, lstat, writeFile, mkdtemp } from 'node:fs/promises';
16
- import { join } from 'node:path';
17
- import { tmpdir } from 'node:os';
18
-
19
- export default async () => {
20
- await describe('fs.symlink (callback)', async () => {
21
-
22
- await it('should throw when no callback provided', async () => {
23
- expect(() => {
24
- // @ts-ignore
25
- symlinkCb('some/path', 'some/other/path', 'dir');
26
- }).toThrow(Error);
27
- });
28
-
29
- await it('should create a symlink pointing to a file', async () => {
30
- const dir = mkdtempSync(join(tmpdir(), 'fs-sym-cb-'));
31
- const target = join(dir, 'target.txt');
32
- const link = join(dir, 'link.txt');
33
- writeFileSync(target, 'symlink target');
34
-
35
- await new Promise<void>((resolve, reject) => {
36
- symlinkCb(target, link, (err) => {
37
- if (err) return reject(err);
38
- try {
39
- const s = lstatSync(link);
40
- expect(s.isSymbolicLink()).toBe(true);
41
- resolve();
42
- } catch (e) {
43
- reject(e);
44
- }
45
- });
46
- });
47
-
48
- unlinkSync(link);
49
- unlinkSync(target);
50
- rmdirSync(dir);
51
- });
52
- });
53
-
54
- await describe('fs.symlink (promise)', async () => {
55
- await it('should create a symlink pointing to a file', async () => {
56
- const dir = await mkdtemp(join(tmpdir(), 'fs-sym-p-'));
57
- const target = join(dir, 'target.txt');
58
- const link = join(dir, 'link.txt');
59
- await writeFile(target, 'symlink target content');
60
-
61
- await symlink(target, link);
62
-
63
- const s = lstatSync(link);
64
- expect(s.isSymbolicLink()).toBe(true);
65
-
66
- await unlink(link);
67
- await unlink(target);
68
- await rmdir(dir);
69
- });
70
-
71
- await it('should throw ENOENT when symlinking to non-existent target', async () => {
72
- const dir = await mkdtemp(join(tmpdir(), 'fs-sym-err-'));
73
- const link = join(dir, 'link.txt');
74
- // Creating a dangling symlink — target doesn't exist
75
- await symlink('/nonexistent/path/12345abc', link);
76
- const s = await lstat(link);
77
- expect(s.isSymbolicLink()).toBe(true);
78
- await unlink(link);
79
- await rmdir(dir);
80
- });
81
-
82
- await it('should throw EEXIST when symlink already exists', async () => {
83
- const dir = await mkdtemp(join(tmpdir(), 'fs-sym-exist-'));
84
- const target = join(dir, 'target.txt');
85
- const link = join(dir, 'link.txt');
86
- await writeFile(target, 'data');
87
- await symlink(target, link);
88
-
89
- let threw = false;
90
- try {
91
- await symlink(target, link);
92
- } catch (e: unknown) {
93
- threw = true;
94
- expect((e as NodeJS.ErrnoException).code).toBe('EEXIST');
95
- }
96
- expect(threw).toBe(true);
97
-
98
- await unlink(link);
99
- await unlink(target);
100
- await rmdir(dir);
101
- });
102
- });
103
-
104
- await describe('fs.readlink', async () => {
105
- await it('should read the symlink target synchronously', async () => {
106
- const dir = mkdtempSync(join(tmpdir(), 'fs-rl-'));
107
- const target = join(dir, 'target.txt');
108
- const link2 = join(dir, 'link2.txt');
109
- writeFileSync(target, 'data');
110
-
111
- await new Promise<void>(resolve => {
112
- symlinkCb(target, link2, () => {
113
- const dest = readlinkSync(link2);
114
- expect(dest).toBe(target);
115
- unlinkSync(link2);
116
- unlinkSync(target);
117
- rmdirSync(dir);
118
- resolve();
119
- });
120
- });
121
- });
122
-
123
- await it('should read the symlink target via promise', async () => {
124
- const dir = await mkdtemp(join(tmpdir(), 'fs-rl-p-'));
125
- const target = join(dir, 'target.txt');
126
- const link = join(dir, 'link.txt');
127
- await writeFile(target, 'data');
128
- await symlink(target, link);
129
-
130
- const dest = await readlink(link);
131
- expect(dest).toBe(target);
132
-
133
- await unlink(link);
134
- await unlink(target);
135
- await rmdir(dir);
136
- });
137
-
138
- await it('should throw ENOENT when reading non-existent symlink', async () => {
139
- let threw = false;
140
- try {
141
- await readlink('/nonexistent/path/no-link-xyz');
142
- } catch (e: unknown) {
143
- threw = true;
144
- expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
145
- }
146
- expect(threw).toBe(true);
147
- });
148
- });
149
-
150
- await describe('fs.realpath', async () => {
151
- await it('should resolve a symlink to real path (sync)', async () => {
152
- const dir = mkdtempSync(join(tmpdir(), 'fs-rp-'));
153
- const target = join(dir, 'realfile.txt');
154
- const link = join(dir, 'link.txt');
155
- writeFileSync(target, 'realpath test');
156
-
157
- await new Promise<void>(resolve => {
158
- symlinkCb(target, link, () => {
159
- const real = realpathSync(link);
160
- // realpath resolves symlinks to actual path
161
- expect(typeof real).toBe('string');
162
- expect(real.length).toBeGreaterThan(0);
163
- unlinkSync(link);
164
- resolve();
165
- });
166
- });
167
-
168
- unlinkSync(target);
169
- rmdirSync(dir);
170
- });
171
-
172
- await it('should resolve a symlink to real path (promise)', async () => {
173
- const dir = await mkdtemp(join(tmpdir(), 'fs-rp-p-'));
174
- const target = join(dir, 'realfile.txt');
175
- const link = join(dir, 'link.txt');
176
- await writeFile(target, 'realpath test');
177
- await symlink(target, link);
178
-
179
- const real = await realpath(link);
180
- expect(typeof real).toBe('string');
181
- expect(real.length).toBeGreaterThan(0);
182
-
183
- await unlink(link);
184
- await unlink(target);
185
- await rmdir(dir);
186
- });
187
- });
188
- };