@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
package/src/callback.ts CHANGED
@@ -1,14 +1,104 @@
1
+ // Reference: Node.js lib/fs.js (callback API)
2
+ // Reimplemented for GJS using Gio.File async operations
3
+
4
+ import GLib from '@girs/glib-2.0';
5
+ import Gio from '@girs/gio-2.0';
1
6
  import { open as openP, rm as rmP } from './promises.js'
2
- import { warnNotImplemented } from '@gjsify/utils';
3
- import { PathLike, OpenMode, Mode, ReadPosition, ReadAsyncOptions, NoParamCallback, RmOptions } from 'fs';
7
+ import { PathLike, OpenMode, Mode, ReadPosition, ReadAsyncOptions, NoParamCallback, RmOptions, RmDirOptions, MakeDirectoryOptions } from 'node:fs';
4
8
  import { FileHandle } from './file-handle.js';
5
- import { Buffer } from 'buffer';
9
+ import { Buffer } from 'node:buffer';
10
+ import { Stats, BigIntStats, STAT_ATTRIBUTES } from './stats.js';
11
+ import { createNodeError } from './errors.js';
12
+ import { realpathSync, readdirSync, renameSync, copyFileSync, accessSync, appendFileSync, readlinkSync, truncateSync, chmodSync, chownSync, mkdirSync, rmdirSync, readFileSync, writeFileSync } from './sync.js';
13
+ // encoding helpers available if needed in future
14
+
15
+ import type { OpenFlags } from './types/index.js';
16
+
17
+ // --- helpers ---
18
+
19
+ function parseOptsCb(optionsOrCallback: unknown, maybeCallback?: Function): { options: Record<string, unknown>; callback: Function } {
20
+ return typeof optionsOrCallback === 'function'
21
+ ? { options: {}, callback: optionsOrCallback }
22
+ : { options: (optionsOrCallback ?? {}) as Record<string, unknown>, callback: maybeCallback! };
23
+ }
24
+
25
+ function statImpl(path: PathLike, flags: Gio.FileQueryInfoFlags, syscall: string, options: Record<string, unknown>, callback: Function): void {
26
+ const file = Gio.File.new_for_path(path.toString());
27
+ file.query_info_async(STAT_ATTRIBUTES, flags, GLib.PRIORITY_DEFAULT, null, (_s: Gio.File, res: Gio.AsyncResult) => {
28
+ try {
29
+ const info = file.query_info_finish(res);
30
+ callback(null, options?.bigint ? new BigIntStats(info, path) : new Stats(info, path));
31
+ } catch (err: unknown) {
32
+ callback(createNodeError(err, syscall, path));
33
+ }
34
+ });
35
+ }
36
+
37
+ // --- stat / lstat ---
38
+
39
+ export function stat(path: PathLike, callback: (err: NodeJS.ErrnoException | null, stats: Stats) => void): void;
40
+ export function stat(path: PathLike, options: { bigint?: boolean }, callback: (err: NodeJS.ErrnoException | null, stats: Stats | BigIntStats) => void): void;
41
+ export function stat(path: PathLike, optionsOrCallback: { bigint?: boolean } | ((err: NodeJS.ErrnoException | null, stats: Stats) => void), maybeCallback?: Function): void {
42
+ const { options, callback } = parseOptsCb(optionsOrCallback, maybeCallback);
43
+ statImpl(path, Gio.FileQueryInfoFlags.NONE, 'stat', options, callback);
44
+ }
45
+
46
+ export function lstat(path: PathLike, callback: (err: NodeJS.ErrnoException | null, stats: Stats) => void): void;
47
+ export function lstat(path: PathLike, options: { bigint?: boolean }, callback: (err: NodeJS.ErrnoException | null, stats: Stats | BigIntStats) => void): void;
48
+ export function lstat(path: PathLike, optionsOrCallback: { bigint?: boolean } | ((err: NodeJS.ErrnoException | null, stats: Stats) => void), maybeCallback?: Function): void {
49
+ const { options, callback } = parseOptsCb(optionsOrCallback, maybeCallback);
50
+ statImpl(path, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, 'lstat', options, callback);
51
+ }
52
+
53
+ // --- readdir ---
54
+
55
+ export function readdir(path: PathLike, callback: (err: NodeJS.ErrnoException | null, files: string[]) => void): void;
56
+ export function readdir(path: PathLike, options: { withFileTypes?: boolean; encoding?: string; recursive?: boolean }, callback: (err: NodeJS.ErrnoException | null, files: string[] | unknown[]) => void): void;
57
+ export function readdir(path: PathLike, optionsOrCallback: { withFileTypes?: boolean; encoding?: string; recursive?: boolean } | ((err: NodeJS.ErrnoException | null, files: string[]) => void), maybeCallback?: Function): void {
58
+ const { options, callback } = parseOptsCb(optionsOrCallback, maybeCallback);
59
+ Promise.resolve().then(() => {
60
+ try {
61
+ callback(null, readdirSync(path, options as { withFileTypes?: boolean; encoding?: string; recursive?: boolean }));
62
+ } catch (err: unknown) {
63
+ callback(createNodeError(err, 'readdir', path));
64
+ }
65
+ });
66
+ }
67
+
68
+ // --- realpath ---
69
+
70
+ export function realpath(path: PathLike, callback: (err: NodeJS.ErrnoException | null, resolvedPath: string) => void): void;
71
+ export function realpath(path: PathLike, options: { encoding?: BufferEncoding }, callback: (err: NodeJS.ErrnoException | null, resolvedPath: string) => void): void;
72
+ export function realpath(path: PathLike, optionsOrCallback: { encoding?: BufferEncoding } | ((err: NodeJS.ErrnoException | null, resolvedPath: string) => void), maybeCallback?: Function): void {
73
+ const { callback } = parseOptsCb(optionsOrCallback, maybeCallback);
74
+ Promise.resolve().then(() => {
75
+ try {
76
+ callback(null, realpathSync(path));
77
+ } catch (err: unknown) {
78
+ callback(err as NodeJS.ErrnoException);
79
+ }
80
+ });
81
+ }
6
82
 
7
- export { realpath } from '@gjsify/deno_std/node/_fs/_fs_realpath';
8
- export { readdir } from '@gjsify/deno_std/node/_fs/_fs_readdir';
9
- export { symlink } from '@gjsify/deno_std/node/_fs/_fs_symlink';
10
- export { lstat } from '@gjsify/deno_std/node/_fs/_fs_lstat';
11
- export { stat } from '@gjsify/deno_std/node/_fs/_fs_stat';
83
+ // --- symlink ---
84
+
85
+ export function symlink(target: PathLike, path: PathLike, callback: NoParamCallback): void;
86
+ export function symlink(target: PathLike, path: PathLike, type: string | null, callback: NoParamCallback): void;
87
+ export function symlink(target: PathLike, path: PathLike, typeOrCallback: string | null | NoParamCallback, maybeCallback?: NoParamCallback): void {
88
+ const callback: NoParamCallback = typeof typeOrCallback === 'function' ? typeOrCallback : maybeCallback!;
89
+ if (typeof callback !== 'function') {
90
+ throw new TypeError('Callback must be a function. Received ' + typeof callback);
91
+ }
92
+ const file = Gio.File.new_for_path(path.toString());
93
+ file.make_symbolic_link_async(target.toString(), GLib.PRIORITY_DEFAULT, null, (_s: Gio.File, res: Gio.AsyncResult) => {
94
+ try {
95
+ file.make_symbolic_link_finish(res);
96
+ callback(null);
97
+ } catch (err: unknown) {
98
+ callback(createNodeError(err, 'symlink', target, path));
99
+ }
100
+ });
101
+ }
12
102
 
13
103
  type OpenCallback = (err: NodeJS.ErrnoException | null, fd: number) => void;
14
104
 
@@ -47,29 +137,29 @@ export function open(path: PathLike, flags: OpenMode | undefined, callback: Open
47
137
  */
48
138
  export function open(path: PathLike, callback: OpenCallback): void;
49
139
 
50
- export function open(path: PathLike, ...args: any[]): void {
51
- let flags: number | OpenMode | undefined;
140
+ export function open(path: PathLike, ...args: (OpenMode | Mode | OpenCallback | undefined | null)[]): void {
141
+ let flags: OpenMode | undefined;
52
142
  let mode: Mode | undefined | null;
53
143
  let callback: OpenCallback
54
144
 
55
145
  switch (args.length) {
56
146
  case 1:
57
- callback = args[0]
147
+ callback = args[0] as OpenCallback
58
148
  break;
59
149
  case 2:
60
- flags = args[0]
61
- callback = args[1]
150
+ flags = args[0] as OpenMode | undefined
151
+ callback = args[1] as OpenCallback
62
152
  break;
63
153
  case 3:
64
- flags = args[0]
65
- mode = args[1]
66
- callback = args[2]
154
+ flags = args[0] as OpenMode | undefined
155
+ mode = args[1] as Mode | undefined | null
156
+ callback = args[2] as OpenCallback
67
157
  break;
68
158
  default:
69
159
  break;
70
160
  }
71
161
 
72
- openP(path, flags as any, mode)
162
+ openP(path, flags as OpenFlags | undefined, mode)
73
163
  .then((fileHandle) => {
74
164
  callback(null, fileHandle.fd);
75
165
  })
@@ -167,14 +257,16 @@ export function write(fd: number, string: string, position: number | undefined |
167
257
  */
168
258
  export function write(fd: number, string: string, callback: WriteStrCallback): void;
169
259
 
170
- export function write<TBuffer extends NodeJS.ArrayBufferView>(fd: number, data: string | TBuffer, ...args: any[]): void {
260
+ export function write<TBuffer extends NodeJS.ArrayBufferView>(fd: number, data: string | TBuffer, ...args: (number | string | BufferEncoding | WriteStrCallback | WriteBufCallback | undefined | null)[]): void {
171
261
 
172
262
  const fileHandle = FileHandle.getInstance(fd);
173
-
263
+
174
264
  if (typeof data === 'string') {
175
- const callback: WriteStrCallback = args[args.length -1];
265
+ const callback = args.pop() as WriteStrCallback;
266
+ const position = args[0] as number | undefined | null;
267
+ const encoding = args[1] as BufferEncoding | undefined | null;
176
268
 
177
- fileHandle.write(data, ...args.pop())
269
+ fileHandle.write(data, position, encoding)
178
270
  .then((res) => {
179
271
  callback(null, res.bytesWritten, res.buffer);
180
272
  })
@@ -185,10 +277,10 @@ export function write<TBuffer extends NodeJS.ArrayBufferView>(fd: number, data:
185
277
  return;
186
278
  }
187
279
 
188
- const callback: WriteBufCallback = args[args.length -1];
189
- const offset: number | undefined = args[0];
190
- const length: number | undefined = args[1];
191
- const position: number | undefined = args[2];
280
+ const callback = args[args.length -1] as WriteBufCallback;
281
+ const offset = args[0] as number | undefined;
282
+ const length = args[1] as number | undefined;
283
+ const position = args[2] as number | undefined;
192
284
 
193
285
  fileHandle.write(data, offset, length, position)
194
286
  .then((res) => {
@@ -281,33 +373,32 @@ export function read<TBuffer extends NodeJS.ArrayBufferView>(
281
373
  ): void;
282
374
  export function read(fd: number, callback: ReadCallback): void;
283
375
 
284
- export function read(fd: number, ...args: any[]): void {
376
+ export function read(fd: number, ...args: unknown[]): void {
285
377
 
286
378
  const fileHandle = FileHandle.getInstance(fd);
287
379
 
288
- const callback: ReadCallback = args[args.length -1];
289
- const err = new Error(warnNotImplemented('fs.read'));
290
- callback(err, 0, Buffer.from(''));
380
+ const callback: ReadCallback = args[args.length -1] as ReadCallback;
291
381
 
292
382
  let buffer: NodeJS.ArrayBufferView | undefined;
293
383
  let offset: number | null | undefined;
294
384
  let length: number | null | undefined;
295
385
  let position: ReadPosition | null | undefined;
296
386
 
297
- if (typeof args[0] === 'object') {
298
- const options: ReadAsyncOptions<any> = args[0];
387
+ if (args.length <= 1) {
388
+ // read(fd, callback) use defaults
389
+ } else if (typeof args[0] === 'object' && !ArrayBuffer.isView(args[0])) {
390
+ const options = args[0] as ReadAsyncOptions<NodeJS.ArrayBufferView>;
299
391
  buffer = options.buffer;
300
392
  offset = options.offset;
301
393
  length = options.length;
302
394
  position = options.position;
303
395
  } else {
304
- buffer = args[0];
305
- offset = args[1];
306
- length = args[2];
307
- position = args[3];
396
+ buffer = args[0] as NodeJS.ArrayBufferView | undefined;
397
+ offset = args[1] as number | null | undefined;
398
+ length = args[2] as number | null | undefined;
399
+ position = args[3] as ReadPosition | null | undefined;
308
400
  }
309
401
 
310
-
311
402
  fileHandle.read(buffer, offset, length, position)
312
403
  .then((res) => {
313
404
  callback(null, res.bytesRead, res.buffer);
@@ -343,13 +434,13 @@ export function close(fd: number, callback?: NoParamCallback): void {
343
434
  export function rm(path: PathLike, callback: NoParamCallback): void;
344
435
  export function rm(path: PathLike, options: RmOptions, callback: NoParamCallback): void;
345
436
 
346
- export function rm(path: PathLike, ...args: any[]): void {
437
+ export function rm(path: PathLike, ...args: (RmOptions | NoParamCallback)[]): void {
347
438
 
348
439
  let options: RmOptions = {};
349
- let callback: NoParamCallback = args[args.length -1];
440
+ let callback: NoParamCallback = args[args.length -1] as NoParamCallback;
350
441
 
351
442
  if (args.length >= 2) {
352
- options = args[0];
443
+ options = args[0] as RmOptions;
353
444
  }
354
445
 
355
446
  rmP(path, options)
@@ -359,4 +450,226 @@ export function rm(path: PathLike, ...args: any[]): void {
359
450
  .catch((err) => {
360
451
  callback(err);
361
452
  });
453
+ }
454
+
455
+ // --- rename ---
456
+
457
+ export function rename(oldPath: PathLike, newPath: PathLike, callback: NoParamCallback): void {
458
+ Promise.resolve().then(() => {
459
+ try {
460
+ renameSync(oldPath, newPath);
461
+ callback(null);
462
+ } catch (err: unknown) {
463
+ callback(err as NodeJS.ErrnoException);
464
+ }
465
+ });
466
+ }
467
+
468
+ // --- copyFile ---
469
+
470
+ export function copyFile(src: PathLike, dest: PathLike, callback: NoParamCallback): void;
471
+ export function copyFile(src: PathLike, dest: PathLike, mode: number, callback: NoParamCallback): void;
472
+ export function copyFile(src: PathLike, dest: PathLike, modeOrCb: number | NoParamCallback, maybeCb?: NoParamCallback): void {
473
+ const mode = typeof modeOrCb === 'function' ? 0 : modeOrCb;
474
+ const callback = typeof modeOrCb === 'function' ? modeOrCb : maybeCb!;
475
+ Promise.resolve().then(() => {
476
+ try {
477
+ copyFileSync(src, dest, mode);
478
+ callback(null);
479
+ } catch (err: unknown) {
480
+ callback(err as NodeJS.ErrnoException);
481
+ }
482
+ });
483
+ }
484
+
485
+ // --- access ---
486
+
487
+ export function access(path: PathLike, callback: NoParamCallback): void;
488
+ export function access(path: PathLike, mode: number, callback: NoParamCallback): void;
489
+ export function access(path: PathLike, modeOrCb: number | NoParamCallback, maybeCb?: NoParamCallback): void {
490
+ const mode = typeof modeOrCb === 'function' ? undefined : modeOrCb;
491
+ const callback = typeof modeOrCb === 'function' ? modeOrCb : maybeCb!;
492
+ Promise.resolve().then(() => {
493
+ try {
494
+ accessSync(path, mode);
495
+ callback(null);
496
+ } catch (err: unknown) {
497
+ callback(err as NodeJS.ErrnoException);
498
+ }
499
+ });
500
+ }
501
+
502
+ // --- appendFile ---
503
+
504
+ export function appendFile(path: PathLike, data: string | Uint8Array, callback: NoParamCallback): void;
505
+ export function appendFile(path: PathLike, data: string | Uint8Array, options: { encoding?: string; mode?: number; flag?: string } | string, callback: NoParamCallback): void;
506
+ export function appendFile(path: PathLike, data: string | Uint8Array, optsOrCb: { encoding?: string; mode?: number; flag?: string } | string | NoParamCallback, maybeCb?: NoParamCallback): void {
507
+ const callback = typeof optsOrCb === 'function' ? optsOrCb : maybeCb!;
508
+ const options = typeof optsOrCb === 'function' ? undefined : optsOrCb;
509
+ Promise.resolve().then(() => {
510
+ try {
511
+ appendFileSync(path, data, options);
512
+ callback(null);
513
+ } catch (err: unknown) {
514
+ callback(err as NodeJS.ErrnoException);
515
+ }
516
+ });
517
+ }
518
+
519
+ // --- readlink ---
520
+
521
+ export function readlink(path: PathLike, callback: (err: NodeJS.ErrnoException | null, linkString: string) => void): void;
522
+ export function readlink(path: PathLike, options: { encoding?: string } | string, callback: (err: NodeJS.ErrnoException | null, linkString: string | Buffer) => void): void;
523
+ export function readlink(path: PathLike, optsOrCb: { encoding?: string } | string | ((err: NodeJS.ErrnoException | null, linkString: string | Buffer) => void), maybeCb?: (err: NodeJS.ErrnoException | null, linkString: string | Buffer) => void): void {
524
+ const callback = typeof optsOrCb === 'function' ? optsOrCb : maybeCb!;
525
+ const options = typeof optsOrCb === 'function' ? undefined : optsOrCb;
526
+ Promise.resolve().then(() => {
527
+ try {
528
+ callback(null, readlinkSync(path, options));
529
+ } catch (err: unknown) {
530
+ callback(err as NodeJS.ErrnoException, '');
531
+ }
532
+ });
533
+ }
534
+
535
+ // --- truncate ---
536
+
537
+ export function truncate(path: PathLike, callback: NoParamCallback): void;
538
+ export function truncate(path: PathLike, len: number, callback: NoParamCallback): void;
539
+ export function truncate(path: PathLike, lenOrCb: number | NoParamCallback, maybeCb?: NoParamCallback): void {
540
+ const len = typeof lenOrCb === 'function' ? 0 : lenOrCb;
541
+ const callback = typeof lenOrCb === 'function' ? lenOrCb : maybeCb!;
542
+ Promise.resolve().then(() => {
543
+ try {
544
+ truncateSync(path, len);
545
+ callback(null);
546
+ } catch (err: unknown) {
547
+ callback(err as NodeJS.ErrnoException);
548
+ }
549
+ });
550
+ }
551
+
552
+ // --- chmod ---
553
+
554
+ export function chmod(path: PathLike, mode: Mode, callback: NoParamCallback): void {
555
+ Promise.resolve().then(() => {
556
+ try {
557
+ chmodSync(path, mode);
558
+ callback(null);
559
+ } catch (err: unknown) {
560
+ callback(err as NodeJS.ErrnoException);
561
+ }
562
+ });
563
+ }
564
+
565
+ // --- chown ---
566
+
567
+ export function chown(path: PathLike, uid: number, gid: number, callback: NoParamCallback): void {
568
+ Promise.resolve().then(() => {
569
+ try {
570
+ chownSync(path, uid, gid);
571
+ callback(null);
572
+ } catch (err: unknown) {
573
+ callback(err as NodeJS.ErrnoException);
574
+ }
575
+ });
576
+ }
577
+
578
+ // --- mkdir (callback) ---
579
+
580
+ export function mkdir(path: PathLike, callback: NoParamCallback): void;
581
+ export function mkdir(path: PathLike, options: MakeDirectoryOptions | Mode, callback: NoParamCallback): void;
582
+ export function mkdir(path: PathLike, optsOrCb: MakeDirectoryOptions | Mode | NoParamCallback, maybeCb?: NoParamCallback): void {
583
+ const callback = typeof optsOrCb === 'function' ? optsOrCb : maybeCb!;
584
+ const options = typeof optsOrCb === 'function' ? undefined : optsOrCb;
585
+ Promise.resolve().then(() => {
586
+ try {
587
+ mkdirSync(path, options as MakeDirectoryOptions | Mode | undefined);
588
+ callback(null);
589
+ } catch (err: unknown) {
590
+ callback(err as NodeJS.ErrnoException);
591
+ }
592
+ });
593
+ }
594
+
595
+ // --- rmdir (callback) ---
596
+
597
+ export function rmdir(path: PathLike, callback: NoParamCallback): void;
598
+ export function rmdir(path: PathLike, options: RmDirOptions, callback: NoParamCallback): void;
599
+ export function rmdir(path: PathLike, optsOrCb: RmDirOptions | NoParamCallback, maybeCb?: NoParamCallback): void {
600
+ const callback = typeof optsOrCb === 'function' ? optsOrCb : maybeCb!;
601
+ const options = typeof optsOrCb === 'function' ? undefined : optsOrCb;
602
+ Promise.resolve().then(() => {
603
+ try {
604
+ rmdirSync(path, options);
605
+ callback(null);
606
+ } catch (err: unknown) {
607
+ callback(err as NodeJS.ErrnoException);
608
+ }
609
+ });
610
+ }
611
+
612
+ // --- readFile (callback) ---
613
+
614
+ export function readFile(path: PathLike, callback: (err: NodeJS.ErrnoException | null, data: Buffer) => void): void;
615
+ export function readFile(path: PathLike, options: { encoding?: string; flag?: string } | string, callback: (err: NodeJS.ErrnoException | null, data: string | Buffer) => void): void;
616
+ export function readFile(path: PathLike, optsOrCb: { encoding?: string; flag?: string } | string | ((err: NodeJS.ErrnoException | null, data: Buffer) => void), maybeCb?: (err: NodeJS.ErrnoException | null, data: string | Buffer | null) => void): void {
617
+ const callback = typeof optsOrCb === 'function' ? optsOrCb : maybeCb!;
618
+ const options = typeof optsOrCb === 'function' ? undefined : optsOrCb;
619
+ Promise.resolve().then(() => {
620
+ try {
621
+ const readOpts = typeof options === 'string' ? { encoding: options as string | null, flag: 'r' } : { encoding: (options?.encoding ?? null) as string | null, flag: options?.flag ?? 'r' };
622
+ callback(null, readFileSync(path.toString(), readOpts) as unknown as Buffer);
623
+ } catch (err: unknown) {
624
+ callback(err as NodeJS.ErrnoException, null as unknown as Buffer);
625
+ }
626
+ });
627
+ }
628
+
629
+ // --- writeFile (callback) ---
630
+
631
+ export function writeFile(path: PathLike, data: string | Uint8Array, callback: NoParamCallback): void;
632
+ export function writeFile(path: PathLike, data: string | Uint8Array, options: { encoding?: string; mode?: number; flag?: string } | string, callback: NoParamCallback): void;
633
+ export function writeFile(path: PathLike, data: string | Uint8Array, optsOrCb: { encoding?: string; mode?: number; flag?: string } | string | NoParamCallback, maybeCb?: NoParamCallback): void {
634
+ const callback = typeof optsOrCb === 'function' ? optsOrCb : maybeCb!;
635
+ Promise.resolve().then(() => {
636
+ try {
637
+ writeFileSync(path.toString(), data);
638
+ callback(null);
639
+ } catch (err: unknown) {
640
+ callback(err as NodeJS.ErrnoException);
641
+ }
642
+ });
643
+ }
644
+
645
+ // --- link (callback) ---
646
+
647
+ export function link(existingPath: PathLike, newPath: PathLike, callback: NoParamCallback): void {
648
+ Promise.resolve().then(() => {
649
+ try {
650
+ const result = GLib.spawn_command_line_sync(`ln ${existingPath.toString()} ${newPath.toString()}`);
651
+ if (!result[0]) {
652
+ throw Object.assign(new Error(`EPERM: operation not permitted, link '${existingPath}' -> '${newPath}'`), {
653
+ code: 'EPERM', errno: -1, syscall: 'link',
654
+ path: existingPath.toString(), dest: newPath.toString()
655
+ });
656
+ }
657
+ callback(null);
658
+ } catch (err: unknown) {
659
+ callback(err as NodeJS.ErrnoException);
660
+ }
661
+ });
662
+ }
663
+
664
+ // --- unlink (callback) ---
665
+
666
+ export function unlink(path: PathLike, callback: NoParamCallback): void {
667
+ Promise.resolve().then(() => {
668
+ try {
669
+ GLib.unlink(path.toString());
670
+ callback(null);
671
+ } catch (err: unknown) {
672
+ callback(err as NodeJS.ErrnoException);
673
+ }
674
+ });
362
675
  }
package/src/dirent.ts CHANGED
@@ -1,7 +1,20 @@
1
+ // Reference: Node.js lib/internal/fs/utils.js (Dirent class)
2
+ // Reimplemented for GJS using Gio.FileInfo
3
+
1
4
  import Gio from '@girs/gio-2.0';
2
- import { basename } from 'path';
5
+ import { basename, dirname } from 'node:path';
6
+
7
+ import type { Dirent as OriginalDirent } from 'node:fs'; // Types from @types/node
3
8
 
4
- import type { Dirent as OriginalDirent } from 'fs'; // Types from @types/node
9
+ // POSIX file type constants from stat mode bits (S_IFMT mask = 0o170000)
10
+ const S_IFMT = 0o170000;
11
+ const S_IFSOCK = 0o140000;
12
+ const S_IFLNK = 0o120000;
13
+ const S_IFREG = 0o100000;
14
+ const S_IFBLK = 0o060000;
15
+ const S_IFDIR = 0o040000;
16
+ const S_IFCHR = 0o020000;
17
+ const S_IFIFO = 0o010000;
5
18
 
6
19
  /**
7
20
  * A representation of a directory entry, which can be a file or a subdirectory
@@ -21,6 +34,12 @@ export class Dirent implements OriginalDirent {
21
34
  */
22
35
  name: string;
23
36
 
37
+ /**
38
+ * The path to the parent directory of the file this `fs.Dirent` object refers to.
39
+ * @since v20.12.0, v18.20.0
40
+ */
41
+ parentPath: string;
42
+
24
43
  private _isFile = false;
25
44
  private _isDirectory = false;
26
45
  private _isBlockDevice = false;
@@ -33,11 +52,12 @@ export class Dirent implements OriginalDirent {
33
52
  protected _file: Gio.File;
34
53
 
35
54
  /** This is not part of node.fs and is used internal by gjsify */
36
- constructor(path: string, filename?: string) {
55
+ constructor(path: string, filename?: string, fileType?: Gio.FileType) {
37
56
  if (!filename) filename = basename(path);
38
57
  this.name = filename;
58
+ this.parentPath = dirname(path);
39
59
  this._file = Gio.File.new_for_path(path);
40
- const type = this._file.query_file_type(Gio.FileQueryInfoFlags.NONE, null);
60
+ const type = fileType ?? this._file.query_file_type(Gio.FileQueryInfoFlags.NONE, null);
41
61
 
42
62
  switch (type) {
43
63
  case Gio.FileType.DIRECTORY:
@@ -54,15 +74,43 @@ export class Dirent implements OriginalDirent {
54
74
  break;
55
75
  case Gio.FileType.SPECIAL:
56
76
  // File is a "special" file, such as a socket, fifo, block device, or character device.
57
- this._isBlockDevice = Gio.unix_is_system_device_path(path);
58
- // TODO: this._isCharacterDevice =
59
- // TODO: this._isSocket =
60
- // TODO: this._isFifo =
77
+ // Use unix::mode from Gio.FileInfo to distinguish the exact type via POSIX S_IFMT bits.
78
+ this._classifySpecialFile(path);
61
79
  break;
62
80
  }
63
81
 
64
82
  }
65
83
 
84
+ /**
85
+ * Classify a SPECIAL file type using the unix::mode attribute from Gio.FileInfo.
86
+ * Falls back to marking nothing if the mode attribute is unavailable.
87
+ */
88
+ private _classifySpecialFile(path: string): void {
89
+ try {
90
+ const file = Gio.File.new_for_path(path);
91
+ const info = file.query_info('unix::mode', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
92
+ const mode = info.get_attribute_uint32('unix::mode');
93
+ if (mode === 0) return;
94
+ const fmt = mode & S_IFMT;
95
+ switch (fmt) {
96
+ case S_IFBLK:
97
+ this._isBlockDevice = true;
98
+ break;
99
+ case S_IFCHR:
100
+ this._isCharacterDevice = true;
101
+ break;
102
+ case S_IFSOCK:
103
+ this._isSocket = true;
104
+ break;
105
+ case S_IFIFO:
106
+ this._isFIFO = true;
107
+ break;
108
+ }
109
+ } catch {
110
+ // If we can't query the mode (e.g. permission denied), leave all flags as false
111
+ }
112
+ }
113
+
66
114
  /**
67
115
  * Returns `true` if the `fs.Dirent` object describes a regular file.
68
116
  * @since v10.10.0
package/src/encoding.ts CHANGED
@@ -1,7 +1,12 @@
1
- import { Buffer } from 'buffer';
1
+ // SPDX-License-Identifier: MIT
2
+ // Adapted from Deno (refs/deno/ext/node/polyfills/_fs/_fs_mkdtemp.ts)
3
+ // Copyright (c) 2018-2026 the Deno authors. MIT license.
4
+ // Modifications: Extracted encoding helpers, adapted for GJS
5
+
6
+ import { Buffer } from 'node:buffer';
2
7
 
3
8
  import type { ReadOptions } from './types/index.js';
4
- import type { ObjectEncodingOptions, BufferEncodingOption } from 'fs'; // Types from @types/node
9
+ import type { ObjectEncodingOptions, BufferEncodingOption } from 'node:fs'; // Types from @types/node
5
10
 
6
11
  export function getEncodingFromOptions(options: ReadOptions | ObjectEncodingOptions | BufferEncodingOption= { encoding: null, flag: 'r' }, defaultEncoding: null | BufferEncoding | "buffer" = 'utf8'): BufferEncoding | 'buffer' {
7
12
  if (options === null) {