jg-fs 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/API.md +1716 -0
  2. package/CHANGELOG.md +20 -16
  3. package/README.md +6 -21
  4. package/dist/async/directories/directories.d.ts +14 -0
  5. package/dist/async/directories/directories.js +20 -2
  6. package/dist/async/directories/directories.js.map +1 -1
  7. package/dist/async/files/files.d.ts +46 -8
  8. package/dist/async/files/files.js +48 -10
  9. package/dist/async/files/files.js.map +1 -1
  10. package/dist/async/files/index.d.ts +1 -1
  11. package/dist/async/files/index.js +1 -1
  12. package/dist/async/files/index.js.map +1 -1
  13. package/dist/async/general/general.d.ts +4 -0
  14. package/dist/async/general/general.js +4 -0
  15. package/dist/async/general/general.js.map +1 -1
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +1 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/shared/index.d.ts +2 -2
  20. package/dist/shared/index.js +1 -1
  21. package/dist/shared/index.js.map +1 -1
  22. package/dist/shared/paths.d.ts +2 -0
  23. package/dist/shared/paths.js +2 -0
  24. package/dist/shared/paths.js.map +1 -1
  25. package/dist/shared/types.d.ts +6 -0
  26. package/dist/shared/types.js.map +1 -1
  27. package/dist/shared/validations.d.ts +5 -3
  28. package/dist/shared/validations.js +30 -14
  29. package/dist/shared/validations.js.map +1 -1
  30. package/dist/sync/directories/directories.d.ts +14 -0
  31. package/dist/sync/directories/directories.js +20 -2
  32. package/dist/sync/directories/directories.js.map +1 -1
  33. package/dist/sync/files/files.d.ts +46 -8
  34. package/dist/sync/files/files.js +48 -10
  35. package/dist/sync/files/files.js.map +1 -1
  36. package/dist/sync/files/index.d.ts +1 -1
  37. package/dist/sync/files/index.js +1 -1
  38. package/dist/sync/files/index.js.map +1 -1
  39. package/dist/sync/general/general.d.ts +4 -0
  40. package/dist/sync/general/general.js +4 -0
  41. package/dist/sync/general/general.js.map +1 -1
  42. package/dist/sync/index.d.ts +2 -2
  43. package/dist/sync/index.js +1 -1
  44. package/dist/sync/index.js.map +1 -1
  45. package/package.json +10 -6
package/API.md ADDED
@@ -0,0 +1,1716 @@
1
+ # <a id="api-reference"></a>jg-fs API reference
2
+
3
+ Every export of `jg-fs@1.1.0`, 62 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.
4
+
5
+ ## <a id="api-entry-main"></a>`jg-fs`
6
+
7
+ - Functions: [`copyDirectory`](#api-copyDirectory), [`copyFile`](#api-copyFile), [`createDirectory`](#api-createDirectory), [`createDirectorySymLink`](#api-createDirectorySymLink), [`createFileSymLink`](#api-createFileSymLink), [`deleteDirectory`](#api-deleteDirectory), [`deleteFile`](#api-deleteFile), [`getDirectoryElements`](#api-getDirectoryElements), [`getPathElement`](#api-getPathElement), [`isDirectory`](#api-isDirectory), [`isFile`](#api-isFile), [`isPathWithin`](#api-isPathWithin), [`pathExists`](#api-pathExists), [`readBoundedFile`](#api-readBoundedFile), [`readBufferFile`](#api-readBufferFile), [`readDirectory`](#api-readDirectory), [`readFile`](#api-readFile), [`readJsonFile`](#api-readJsonFile), [`readTextFile`](#api-readTextFile), [`writeBufferFile`](#api-writeBufferFile), [`writeFile`](#api-writeFile), [`writeFileAtomic`](#api-writeFileAtomic), [`writeJsonFile`](#api-writeJsonFile), [`writeTextFile`](#api-writeTextFile)
8
+ - Constants: [`ERRORS`](#api-ERRORS)
9
+ - Types: [`IDirectoryElementsKeySort`](#api-IDirectoryElementsKeySort), [`IDirectoryElementsOptions`](#api-IDirectoryElementsOptions), [`IDirectoryPathElements`](#api-IDirectoryPathElements), [`IPathElement`](#api-IPathElement), [`IReadJsonFileOptions`](#api-IReadJsonFileOptions), [`ISchema`](#api-ISchema)
10
+
11
+ ### <a id="api-functions"></a>Functions in `jg-fs`
12
+
13
+ #### <a id="api-copyDirectory"></a>`copyDirectory`
14
+
15
+ *Function*
16
+
17
+ Copies a directory and everything in it, replacing the destination completely.
18
+
19
+ ```ts
20
+ copyDirectory(srcPath: string, destPath: string): Promise<void>
21
+ ```
22
+
23
+ **Parameters**
24
+
25
+ - `srcPath` `string`: The directory to copy.
26
+ - `destPath` `string`: Where to copy it.
27
+
28
+ **Returns** `Promise<void>`: A promise that resolves once the copy is complete.
29
+
30
+ **Throws**
31
+
32
+ - `NOT_A_DIRECTORY`: if the srcPath is not a directory
33
+
34
+ **Example**
35
+
36
+ ```ts
37
+ import { copyDirectory } from 'jg-fs';
38
+
39
+ await copyDirectory('templates', 'dist/templates');
40
+ ```
41
+
42
+ #### <a id="api-copyFile"></a>`copyFile`
43
+
44
+ *Function*
45
+
46
+ Copies a file, replacing the destination when it exists.
47
+
48
+ ```ts
49
+ copyFile(srcPath: string, destPath: string): Promise<void>
50
+ ```
51
+
52
+ **Parameters**
53
+
54
+ - `srcPath` `string`: The file to copy.
55
+ - `destPath` `string`: Where to copy it.
56
+
57
+ **Returns** `Promise<void>`: A promise that resolves once the copy exists.
58
+
59
+ **Throws**
60
+
61
+ - `NOT_A_FILE`: if the srcPath doesn't exist or is not a file
62
+
63
+ **Example**
64
+
65
+ ```ts
66
+ import { copyFile } from 'jg-fs';
67
+
68
+ await copyFile('config.example.json', 'config.json');
69
+ ```
70
+
71
+ #### <a id="api-createDirectory"></a>`createDirectory`
72
+
73
+ *Function*
74
+
75
+ Creates a directory, with any missing parents. An existing directory is kept, or replaced with an empty one when `deleteIfExists` is set.
76
+
77
+ ```ts
78
+ createDirectory(path: string, deleteIfExists?: boolean): Promise<void>
79
+ ```
80
+
81
+ **Parameters**
82
+
83
+ - `path` `string`: The directory's path.
84
+ - `deleteIfExists?` `boolean`: Whether to delete an existing directory and its contents first.
85
+
86
+ **Returns** `Promise<void>`: A promise that resolves once the directory exists.
87
+
88
+ **Throws**
89
+
90
+ - `DIRECTORY_ALREADY_EXISTS`: if something other than a directory exists at the path
91
+
92
+ **Example**
93
+
94
+ ```ts
95
+ import { createDirectory } from 'jg-fs';
96
+
97
+ await createDirectory('dist/assets'); // creates missing parents too
98
+ await createDirectory('dist', true); // replaces it with an empty directory
99
+ ```
100
+
101
+ #### <a id="api-createDirectorySymLink"></a>`createDirectorySymLink`
102
+
103
+ *Function*
104
+
105
+ Creates a symbolic link to a directory.
106
+
107
+ ```ts
108
+ createDirectorySymLink(target: string, path: string): Promise<void>
109
+ ```
110
+
111
+ **Parameters**
112
+
113
+ - `target` `string`: The directory the link points to.
114
+ - `path` `string`: Where to create the link.
115
+
116
+ **Returns** `Promise<void>`: A promise that resolves once the link exists.
117
+
118
+ **Throws**
119
+
120
+ - `NON_EXISTENT_DIRECTORY`: if the target directory doesn't exist
121
+ - `NOT_A_DIRECTORY`: if the target directory is not considered a directory by the OS
122
+
123
+ **Example**
124
+
125
+ ```ts
126
+ import { createDirectorySymLink } from 'jg-fs';
127
+
128
+ await createDirectorySymLink('releases/1.4.0', 'current');
129
+ ```
130
+
131
+ #### <a id="api-createFileSymLink"></a>`createFileSymLink`
132
+
133
+ *Function*
134
+
135
+ Creates a symbolic link to a file.
136
+
137
+ ```ts
138
+ createFileSymLink(target: string, path: string): Promise<void>
139
+ ```
140
+
141
+ **Parameters**
142
+
143
+ - `target` `string`: The file the link points to.
144
+ - `path` `string`: Where to create the link.
145
+
146
+ **Returns** `Promise<void>`: A promise that resolves once the link exists.
147
+
148
+ **Throws**
149
+
150
+ - `NON_EXISTENT_FILE`: if the target file does not exist
151
+ - `NOT_A_FILE`: if the target is not a file
152
+
153
+ **Example**
154
+
155
+ ```ts
156
+ import { createFileSymLink } from 'jg-fs';
157
+
158
+ await createFileSymLink('config.production.json', 'config.json');
159
+ ```
160
+
161
+ #### <a id="api-deleteDirectory"></a>`deleteDirectory`
162
+
163
+ *Function*
164
+
165
+ Deletes a directory and everything in it. A missing directory is not an error.
166
+
167
+ ```ts
168
+ deleteDirectory(path: string): Promise<void>
169
+ ```
170
+
171
+ **Parameters**
172
+
173
+ - `path` `string`: The directory's path.
174
+
175
+ **Returns** `Promise<void>`: A promise that resolves once the directory is gone.
176
+
177
+ **Example**
178
+
179
+ ```ts
180
+ import { deleteDirectory } from 'jg-fs';
181
+
182
+ await deleteDirectory('dist');
183
+ ```
184
+
185
+ #### <a id="api-deleteFile"></a>`deleteFile`
186
+
187
+ *Function*
188
+
189
+ Deletes a file.
190
+
191
+ ```ts
192
+ deleteFile(path: string): Promise<void>
193
+ ```
194
+
195
+ **Parameters**
196
+
197
+ - `path` `string`: The file's path.
198
+
199
+ **Returns** `Promise<void>`: A promise that resolves once the file is gone.
200
+
201
+ **Throws**
202
+
203
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
204
+
205
+ **Example**
206
+
207
+ ```ts
208
+ import { deleteFile } from 'jg-fs';
209
+
210
+ await deleteFile('cache.json');
211
+ ```
212
+
213
+ #### <a id="api-getDirectoryElements"></a>`getDirectoryElements`
214
+
215
+ *Function*
216
+
217
+ Lists a directory's elements grouped by kind: directories, files (filtered by extension), and symbolic links, each group sorted. Entries removed while the directory is read are left out, and at most 32 entries are read at once.
218
+
219
+ ```ts
220
+ getDirectoryElements(
221
+ path: string,
222
+ options?: Partial<IDirectoryElementsOptions>,
223
+ ): Promise<IDirectoryPathElements>
224
+ ```
225
+
226
+ **Parameters**
227
+
228
+ - `path` `string`: The directory's path.
229
+ - `options?` `Partial<IDirectoryElementsOptions>`: The extensions to include and how to sort.
230
+
231
+ **Returns** `Promise<IDirectoryPathElements>`: A promise that resolves to the grouped elements.
232
+
233
+ **Throws**
234
+
235
+ - `NOT_A_DIRECTORY`: if the path doesn't exist or is not a directory
236
+
237
+ **Example**
238
+
239
+ ```ts
240
+ import { getDirectoryElements } from 'jg-fs';
241
+
242
+ const { files } = await getDirectoryElements('content', { includeExts: ['.md'], sortByKey: 'creation', sortOrder: 'desc' });
243
+ ```
244
+
245
+ #### <a id="api-getPathElement"></a>`getPathElement`
246
+
247
+ *Function*
248
+
249
+ Reads a path's details without following a symbolic link at the path.
250
+
251
+ ```ts
252
+ getPathElement(path: string): Promise<IPathElement | null>
253
+ ```
254
+
255
+ **Parameters**
256
+
257
+ - `path` `string`: The path.
258
+
259
+ **Returns** `Promise<IPathElement | null>`: A promise that resolves to the path's details, or `null` when it doesn't exist.
260
+
261
+ **Example**
262
+
263
+ ```ts
264
+ import { getPathElement } from 'jg-fs';
265
+
266
+ const element = await getPathElement('logo.png');
267
+ element?.size; // the size in bytes, or undefined when the path doesn't exist
268
+ ```
269
+
270
+ #### <a id="api-isDirectory"></a>`isDirectory`
271
+
272
+ *Function*
273
+
274
+ Checks whether a path exists and is a directory.
275
+
276
+ ```ts
277
+ isDirectory(path: string): Promise<boolean>
278
+ ```
279
+
280
+ **Parameters**
281
+
282
+ - `path` `string`: The path.
283
+
284
+ **Returns** `Promise<boolean>`: A promise that resolves to whether the path is a directory.
285
+
286
+ **Example**
287
+
288
+ ```ts
289
+ import { isDirectory } from 'jg-fs';
290
+
291
+ await isDirectory('dist'); // true
292
+ ```
293
+
294
+ #### <a id="api-isFile"></a>`isFile`
295
+
296
+ *Function*
297
+
298
+ Checks whether a path exists and is a regular file.
299
+
300
+ ```ts
301
+ isFile(path: string): Promise<boolean>
302
+ ```
303
+
304
+ **Parameters**
305
+
306
+ - `path` `string`: The path.
307
+
308
+ **Returns** `Promise<boolean>`: A promise that resolves to whether the path is a file.
309
+
310
+ **Example**
311
+
312
+ ```ts
313
+ import { isFile } from 'jg-fs';
314
+
315
+ await isFile('package.json'); // true
316
+ ```
317
+
318
+ #### <a id="api-isPathWithin"></a>`isPathWithin`
319
+
320
+ *Function*
321
+
322
+ Checks whether a path is a root path or nested inside it. The check is lexical: both paths are resolved without touching the filesystem, and symbolic links aren't followed. Sibling paths that share a prefix, such as `/data/app` and `/data/application`, aren't nested, and neither is a path on another drive on Windows.
323
+
324
+ ```ts
325
+ isPathWithin(rootPath: string, targetPath: string): boolean
326
+ ```
327
+
328
+ **Parameters**
329
+
330
+ - `rootPath` `string`: The root path.
331
+ - `targetPath` `string`: The path to check.
332
+
333
+ **Returns** `boolean`: Whether the target is the root or inside it.
334
+
335
+ **Example**
336
+
337
+ ```ts
338
+ import { isPathWithin } from 'jg-fs';
339
+
340
+ isPathWithin('/data/app', '/data/app/logs/today.log'); // true
341
+ isPathWithin('/data/app', '/data/app/../secrets'); // false
342
+ isPathWithin('/data/app', '/data/application'); // false
343
+ ```
344
+
345
+ #### <a id="api-pathExists"></a>`pathExists`
346
+
347
+ *Function*
348
+
349
+ Checks whether a path exists, whether it's a file, a directory, or anything else.
350
+
351
+ ```ts
352
+ pathExists(path: string): Promise<boolean>
353
+ ```
354
+
355
+ **Parameters**
356
+
357
+ - `path` `string`: The path.
358
+
359
+ **Returns** `Promise<boolean>`: A promise that resolves to whether the path exists.
360
+
361
+ **Example**
362
+
363
+ ```ts
364
+ import { pathExists } from 'jg-fs';
365
+
366
+ await pathExists('config.json'); // true when the file exists
367
+ ```
368
+
369
+ #### <a id="api-readBoundedFile"></a>`readBoundedFile`
370
+
371
+ *Function*
372
+
373
+ Reads a regular file of at most `maxBytes` bytes. It never follows a symbolic link in the path's last component, though links in parent directories are followed, so callers that need containment combine it with `isPathWithin`. It reads at most `maxBytes + 1` bytes, even when the file grows while it's read.
374
+
375
+ ```ts
376
+ readBoundedFile(
377
+ path: string,
378
+ maxBytes: number,
379
+ ): Promise<Buffer<ArrayBufferLike>>
380
+ ```
381
+
382
+ **Parameters**
383
+
384
+ - `path` `string`: The file's path.
385
+ - `maxBytes` `number`: The largest size accepted, in bytes.
386
+
387
+ **Returns** `Promise<Buffer<ArrayBufferLike>>`: A promise that resolves to the content.
388
+
389
+ **Throws**
390
+
391
+ - `NOT_A_FILE`: if the path doesn't exist, its final component is a symbolic link, or it isn't a regular file
392
+ - `FILE_TOO_LARGE`: if the file holds more than maxBytes bytes
393
+
394
+ **Example**
395
+
396
+ ```ts
397
+ import { join } from 'node:path';
398
+ import { readBoundedFile } from 'jg-fs';
399
+
400
+ const upload = await readBoundedFile(join('uploads', 'avatar.png'), 5 * 1024 * 1024); // at most 5 MB
401
+ ```
402
+
403
+ #### <a id="api-readBufferFile"></a>`readBufferFile`
404
+
405
+ *Function*
406
+
407
+ Reads a binary file. An empty file reads as an empty Buffer.
408
+
409
+ ```ts
410
+ readBufferFile(path: string): Promise<Buffer<ArrayBufferLike>>
411
+ ```
412
+
413
+ **Parameters**
414
+
415
+ - `path` `string`: The file's path.
416
+
417
+ **Returns** `Promise<Buffer<ArrayBufferLike>>`: A promise that resolves to the content.
418
+
419
+ **Throws**
420
+
421
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
422
+
423
+ **Example**
424
+
425
+ ```ts
426
+ import { readBufferFile } from 'jg-fs';
427
+
428
+ const image = await readBufferFile('avatar.png');
429
+ ```
430
+
431
+ #### <a id="api-readDirectory"></a>`readDirectory`
432
+
433
+ *Function*
434
+
435
+ Lists a directory's contents as paths joined to the directory's path.
436
+
437
+ ```ts
438
+ readDirectory(path: string, recursive?: boolean): Promise<string[]>
439
+ ```
440
+
441
+ **Parameters**
442
+
443
+ - `path` `string`: The directory's path.
444
+ - `recursive?` `boolean`: Whether to include the contents of subdirectories. Defaults to `false`.
445
+
446
+ **Returns** `Promise<string[]>`: A promise that resolves to the content paths.
447
+
448
+ **Throws**
449
+
450
+ - `NOT_A_DIRECTORY`: if the path doesn't exist or is not a directory
451
+
452
+ **Example**
453
+
454
+ ```ts
455
+ import { readDirectory } from 'jg-fs';
456
+
457
+ await readDirectory('dist'); // ['dist/index.js', 'dist/assets']
458
+ await readDirectory('dist', true); // includes the contents of dist/assets
459
+ ```
460
+
461
+ #### <a id="api-readFile"></a>`readFile`
462
+
463
+ *Function*
464
+
465
+ Reads a file as a string when an encoding is given, or as a Buffer otherwise.
466
+
467
+ ```ts
468
+ readFile(path: string, options: IReadStringFileOptions): Promise<string>
469
+ ```
470
+
471
+ **Parameters**
472
+
473
+ - `path` `string`: The file's path.
474
+ - `options` `IReadStringFileOptions`: Node.js's readFile options, such as the encoding.
475
+
476
+ **Returns** `Promise<string>`: A promise that resolves to the content.
477
+
478
+ **Throws**
479
+
480
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
481
+
482
+ ```ts
483
+ readFile(
484
+ path: string,
485
+ options?: IReadBufferFileOptions,
486
+ ): Promise<Buffer<ArrayBufferLike>>
487
+ ```
488
+
489
+ **Parameters**
490
+
491
+ - `path` `string`: The file's path.
492
+ - `options?` `IReadBufferFileOptions`: Node.js's readFile options, such as the encoding.
493
+
494
+ **Returns** `Promise<Buffer<ArrayBufferLike>>`: A promise that resolves to the content.
495
+
496
+ **Throws**
497
+
498
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
499
+
500
+ ```ts
501
+ readFile(
502
+ path: string,
503
+ options?: IReadFileOptions,
504
+ ): Promise<string | Buffer<ArrayBufferLike>>
505
+ ```
506
+
507
+ **Parameters**
508
+
509
+ - `path` `string`: The file's path.
510
+ - `options?` `IReadFileOptions`: Node.js's readFile options, such as the encoding.
511
+
512
+ **Returns** `Promise<string | Buffer<ArrayBufferLike>>`: A promise that resolves to the content.
513
+
514
+ **Throws**
515
+
516
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
517
+
518
+ **Example**
519
+
520
+ ```ts
521
+ import { readFile } from 'jg-fs';
522
+
523
+ await readFile('notes.md', 'utf-8'); // a string
524
+ await readFile('avatar.png'); // a Buffer
525
+ ```
526
+
527
+ #### <a id="api-readJsonFile"></a>`readJsonFile`
528
+
529
+ *Function*
530
+
531
+ Reads and parses a JSON file, and validates it with the schema when there is one. Without a schema, the value is `unknown`, or the type you pass.
532
+
533
+ ```ts
534
+ readJsonFile<T = unknown>(
535
+ path: string,
536
+ options?: IReadJsonFileOptions<T>,
537
+ ): Promise<T>
538
+ ```
539
+
540
+ **Parameters**
541
+
542
+ - `path` `string`: The file's path.
543
+ - `options?` `IReadJsonFileOptions<T>`: The schema, such as a Zod schema, whose errors propagate unchanged. Defaults to `{}`.
544
+
545
+ **Returns** `Promise<T>`: A promise that resolves to the parsed value.
546
+
547
+ **Throws**
548
+
549
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
550
+ - `FILE_CONTENT_IS_EMPTY_OR_INVALID`: if the file's JSON content cannot be parsed
551
+
552
+ **Example 1**
553
+
554
+ ```ts
555
+ import { readJsonFile } from 'jg-fs';
556
+
557
+ const config = await readJsonFile('config.json', { schema: Config }); // Config's type
558
+ ```
559
+
560
+ **Example 2**
561
+
562
+ ```ts
563
+ import { readJsonFile } from 'jg-fs';
564
+
565
+ const value = await readJsonFile('data.json'); // unknown
566
+ ```
567
+
568
+ #### <a id="api-readTextFile"></a>`readTextFile`
569
+
570
+ *Function*
571
+
572
+ Reads a UTF-8 text file. An empty file reads as an empty string.
573
+
574
+ ```ts
575
+ readTextFile(path: string): Promise<string>
576
+ ```
577
+
578
+ **Parameters**
579
+
580
+ - `path` `string`: The file's path.
581
+
582
+ **Returns** `Promise<string>`: A promise that resolves to the text.
583
+
584
+ **Throws**
585
+
586
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
587
+
588
+ **Example**
589
+
590
+ ```ts
591
+ import { readTextFile } from 'jg-fs';
592
+
593
+ const markdown = await readTextFile('README.md');
594
+ ```
595
+
596
+ #### <a id="api-writeBufferFile"></a>`writeBufferFile`
597
+
598
+ *Function*
599
+
600
+ Writes a binary file. An empty Buffer writes an empty file.
601
+
602
+ ```ts
603
+ writeBufferFile(path: string, data: Buffer): Promise<void>
604
+ ```
605
+
606
+ **Parameters**
607
+
608
+ - `path` `string`: The file's path.
609
+ - `data` `Buffer`: The content.
610
+
611
+ **Returns** `Promise<void>`: A promise that resolves once the file is written.
612
+
613
+ **Throws**
614
+
615
+ - `FILE_CONTENT_IS_EMPTY_OR_INVALID`: if the data isn't a Buffer
616
+
617
+ **Example**
618
+
619
+ ```ts
620
+ import { writeBufferFile } from 'jg-fs';
621
+
622
+ const imageBuffer = Buffer.from('iVBORw0KGgo=', 'base64');
623
+ await writeBufferFile('avatar.png', imageBuffer);
624
+ ```
625
+
626
+ #### <a id="api-writeFile"></a>`writeFile`
627
+
628
+ *Function*
629
+
630
+ Writes a file, creating its directory first when it doesn't exist.
631
+
632
+ ```ts
633
+ writeFile(
634
+ path: string,
635
+ data: string | ArrayBufferView<ArrayBufferLike>,
636
+ options?: WriteFileOptions,
637
+ ): Promise<void>
638
+ ```
639
+
640
+ **Parameters**
641
+
642
+ - `path` `string`: The file's path.
643
+ - `data` `string | ArrayBufferView<ArrayBufferLike>`: The content.
644
+ - `options?` `WriteFileOptions`: Node.js's writeFile options, such as the encoding or mode.
645
+
646
+ **Returns** `Promise<void>`: A promise that resolves once the file is written.
647
+
648
+ **Example**
649
+
650
+ ```ts
651
+ import { writeFile } from 'jg-fs';
652
+
653
+ const csv = 'id,name\n1,Ada\n';
654
+ await writeFile('out/report.csv', csv, { encoding: 'utf-8' });
655
+ ```
656
+
657
+ #### <a id="api-writeFileAtomic"></a>`writeFileAtomic`
658
+
659
+ *Function*
660
+
661
+ Writes a file atomically: readers see either the previous content or the complete new content, never a partial write. The data goes to a temporary file in the same directory, which is flushed to disk and renamed over the path; on POSIX, the directory is flushed too. An explicit mode wins; without one, a replaced file keeps its permission bits and a new file gets the platform default. The temporary file is never more widely readable than the final mode. When writing fails before the rename, the temporary file is removed and the destination is untouched.
662
+
663
+ ```ts
664
+ writeFileAtomic(
665
+ path: string,
666
+ data: string | ArrayBufferView<ArrayBufferLike>,
667
+ mode?: number,
668
+ ): Promise<void>
669
+ ```
670
+
671
+ **Parameters**
672
+
673
+ - `path` `string`: The file's path.
674
+ - `data` `string | ArrayBufferView<ArrayBufferLike>`: The content.
675
+ - `mode?` `number`: The permission bits of the written file, such as `0o600`.
676
+
677
+ **Returns** `Promise<void>`: A promise that resolves once the file is written and flushed.
678
+
679
+ **Example**
680
+
681
+ ```ts
682
+ import { writeFileAtomic } from 'jg-fs';
683
+
684
+ const state = { step: 2 };
685
+ await writeFileAtomic('state.json', JSON.stringify(state)); // readers never see a partial file
686
+ await writeFileAtomic('secrets.env', 'TOKEN=abc\n', 0o600); // only the owner can read it
687
+ ```
688
+
689
+ #### <a id="api-writeJsonFile"></a>`writeJsonFile`
690
+
691
+ *Function*
692
+
693
+ Writes a JSON file. A string is written as given; anything else is stringified.
694
+
695
+ ```ts
696
+ writeJsonFile<T = Record<string, unknown>>(
697
+ path: string,
698
+ data: string | T,
699
+ space?: number,
700
+ ): Promise<void>
701
+ ```
702
+
703
+ **Parameters**
704
+
705
+ - `path` `string`: The file's path.
706
+ - `data` `string | T`: The value, or its JSON text.
707
+ - `space?` `number`: The indentation for stringified values. Defaults to `2`.
708
+
709
+ **Returns** `Promise<void>`: A promise that resolves once the file is written.
710
+
711
+ **Throws**
712
+
713
+ - `FILE_CONTENT_IS_EMPTY_OR_INVALID`: if the JSON content cannot be stringified or is empty
714
+
715
+ **Example**
716
+
717
+ ```ts
718
+ import { writeJsonFile } from 'jg-fs';
719
+
720
+ await writeJsonFile('config.json', { port: 8080 });
721
+ ```
722
+
723
+ #### <a id="api-writeTextFile"></a>`writeTextFile`
724
+
725
+ *Function*
726
+
727
+ Writes a UTF-8 text file. An empty string writes an empty file.
728
+
729
+ ```ts
730
+ writeTextFile(path: string, data: string): Promise<void>
731
+ ```
732
+
733
+ **Parameters**
734
+
735
+ - `path` `string`: The file's path.
736
+ - `data` `string`: The text.
737
+
738
+ **Returns** `Promise<void>`: A promise that resolves once the file is written.
739
+
740
+ **Throws**
741
+
742
+ - `FILE_CONTENT_IS_EMPTY_OR_INVALID`: if the data isn't a string
743
+
744
+ **Example**
745
+
746
+ ```ts
747
+ import { writeTextFile } from 'jg-fs';
748
+
749
+ await writeTextFile('notes/today.md', '# Today');
750
+ ```
751
+
752
+ ### <a id="api-constants"></a>Constants in `jg-fs`
753
+
754
+ #### <a id="api-ERRORS"></a>`ERRORS`
755
+
756
+ *Constant*
757
+
758
+ the codes of every error the package throws, from both the async and the sync entry
759
+
760
+ ```ts
761
+ const ERRORS: { NOT_A_DIRECTORY: "NOT_A_DIRECTORY"; NON_EXISTENT_DIRECTORY: "NON_EXISTENT_DIRECTORY"; DIRECTORY_ALREADY_EXISTS: "DIRECTORY_ALREADY_EXISTS"; NON_EXISTENT_FILE: "NON_EXISTENT_FILE"; NOT_A_FILE: "NOT_A_FILE"; FILE_CONTENT_IS_EMPTY_OR_INVALID: "FILE_CONTENT_IS_EMPTY_OR_INVALID"; FILE_TOO_LARGE: "FILE_TOO_LARGE" }
762
+ ```
763
+
764
+ ### <a id="api-types"></a>Types in `jg-fs`
765
+
766
+ #### <a id="api-IDirectoryElementsKeySort"></a>`IDirectoryElementsKeySort`
767
+
768
+ *Type*
769
+
770
+ the keys a directory's elements can be sorted by
771
+
772
+ ```ts
773
+ type IDirectoryElementsKeySort = 'baseName' | 'size' | 'creation';
774
+ ```
775
+
776
+ #### <a id="api-IDirectoryElementsOptions"></a>`IDirectoryElementsOptions`
777
+
778
+ *Type*
779
+
780
+ how getDirectoryElements filters and sorts a directory's elements
781
+
782
+ ```ts
783
+ type IDirectoryElementsOptions = {
784
+ sortByKey: IDirectoryElementsKeySort;
785
+ sortOrder: ISortDirection;
786
+ includeExts: string[];
787
+ };
788
+ ```
789
+
790
+ **Members**
791
+
792
+ - `sortByKey: IDirectoryElementsKeySort` (property): the key the elements are sorted by. Defaults to 'baseName'
793
+ - `sortOrder: ISortDirection` (property): the sort direction. Defaults to 'asc'
794
+ - `includeExts: string[]` (property): the extensions of the files to include, such as ['.json'], matched case-insensitively. Defaults to all
795
+
796
+ #### <a id="api-IDirectoryPathElements"></a>`IDirectoryPathElements`
797
+
798
+ *Type*
799
+
800
+ a directory's elements, grouped by kind
801
+
802
+ ```ts
803
+ type IDirectoryPathElements = {
804
+ directories: IPathElement[];
805
+ files: IPathElement[];
806
+ symbolicLinks: IPathElement[];
807
+ };
808
+ ```
809
+
810
+ **Members**
811
+
812
+ - `directories: IPathElement[]` (property)
813
+ - `files: IPathElement[]` (property)
814
+ - `symbolicLinks: IPathElement[]` (property)
815
+
816
+ #### <a id="api-IPathElement"></a>`IPathElement`
817
+
818
+ *Type*
819
+
820
+ the details of a path, read with lstat, so a symbolic link is described rather than followed
821
+
822
+ ```ts
823
+ type IPathElement = {
824
+ path: string;
825
+ baseName: string;
826
+ extName: string;
827
+ isFile: boolean;
828
+ isDirectory: boolean;
829
+ isSymbolicLink: boolean;
830
+ size: number;
831
+ creation: number;
832
+ };
833
+ ```
834
+
835
+ **Members**
836
+
837
+ - `path: string` (property): the path, as given
838
+ - `baseName: string` (property): the last segment of the path
839
+ - `extName: string` (property): the extension, such as '.json', or an empty string when there is none
840
+ - `isFile: boolean` (property): whether it's a regular file
841
+ - `isDirectory: boolean` (property): whether it's a directory
842
+ - `isSymbolicLink: boolean` (property): whether it's a symbolic link, in which case isFile and isDirectory are false
843
+ - `size: number` (property): the size in bytes
844
+ - `creation: number` (property): when it was created, in milliseconds since the epoch
845
+
846
+ #### <a id="api-IReadJsonFileOptions"></a>`IReadJsonFileOptions`
847
+
848
+ *Type*
849
+
850
+ what readJsonFile accepts
851
+
852
+ ```ts
853
+ type IReadJsonFileOptions<T> = {
854
+ schema?: ISchema<T>;
855
+ };
856
+ ```
857
+
858
+ **Members**
859
+
860
+ - `schema: ISchema<T>` (property): validates the parsed content, such as a Zod schema. Its errors propagate unchanged
861
+
862
+ #### <a id="api-ISchema"></a>`ISchema`
863
+
864
+ *Type*
865
+
866
+ validates parsed data and returns it typed, which a Zod schema does
867
+
868
+ ```ts
869
+ type ISchema<T> = {
870
+ parse(value: unknown): T;
871
+ };
872
+ ```
873
+
874
+ **Members**
875
+
876
+ - `parse(value: unknown): T` (method)
877
+
878
+ ## <a id="api-entry-sync"></a>`jg-fs/sync`
879
+
880
+ - Functions: [`copyDirectory`](#api-sync-copyDirectory), [`copyFile`](#api-sync-copyFile), [`createDirectory`](#api-sync-createDirectory), [`createDirectorySymLink`](#api-sync-createDirectorySymLink), [`createFileSymLink`](#api-sync-createFileSymLink), [`deleteDirectory`](#api-sync-deleteDirectory), [`deleteFile`](#api-sync-deleteFile), [`getDirectoryElements`](#api-sync-getDirectoryElements), [`getPathElement`](#api-sync-getPathElement), [`isDirectory`](#api-sync-isDirectory), [`isFile`](#api-sync-isFile), [`isPathWithin`](#api-sync-isPathWithin), [`pathExists`](#api-sync-pathExists), [`readBoundedFile`](#api-sync-readBoundedFile), [`readBufferFile`](#api-sync-readBufferFile), [`readDirectory`](#api-sync-readDirectory), [`readFile`](#api-sync-readFile), [`readJsonFile`](#api-sync-readJsonFile), [`readTextFile`](#api-sync-readTextFile), [`writeBufferFile`](#api-sync-writeBufferFile), [`writeFile`](#api-sync-writeFile), [`writeFileAtomic`](#api-sync-writeFileAtomic), [`writeJsonFile`](#api-sync-writeJsonFile), [`writeTextFile`](#api-sync-writeTextFile)
881
+ - Constants: [`ERRORS`](#api-sync-ERRORS)
882
+ - Types: [`IDirectoryElementsKeySort`](#api-sync-IDirectoryElementsKeySort), [`IDirectoryElementsOptions`](#api-sync-IDirectoryElementsOptions), [`IDirectoryPathElements`](#api-sync-IDirectoryPathElements), [`IPathElement`](#api-sync-IPathElement), [`IReadJsonFileOptions`](#api-sync-IReadJsonFileOptions), [`ISchema`](#api-sync-ISchema)
883
+
884
+ ### <a id="api-sync-functions"></a>Functions in `jg-fs/sync`
885
+
886
+ #### <a id="api-sync-copyDirectory"></a>`copyDirectory`
887
+
888
+ *Function*
889
+
890
+ Copies a directory and everything in it, replacing the destination completely.
891
+
892
+ ```ts
893
+ copyDirectory(srcPath: string, destPath: string): void
894
+ ```
895
+
896
+ **Parameters**
897
+
898
+ - `srcPath` `string`: The directory to copy.
899
+ - `destPath` `string`: Where to copy it.
900
+
901
+ **Throws**
902
+
903
+ - `NOT_A_DIRECTORY`: if the srcPath is not a directory
904
+
905
+ **Example**
906
+
907
+ ```ts
908
+ import { copyDirectory } from 'jg-fs/sync';
909
+
910
+ copyDirectory('templates', 'dist/templates');
911
+ ```
912
+
913
+ #### <a id="api-sync-copyFile"></a>`copyFile`
914
+
915
+ *Function*
916
+
917
+ Copies a file, replacing the destination when it exists.
918
+
919
+ ```ts
920
+ copyFile(srcPath: string, destPath: string): void
921
+ ```
922
+
923
+ **Parameters**
924
+
925
+ - `srcPath` `string`: The file to copy.
926
+ - `destPath` `string`: Where to copy it.
927
+
928
+ **Throws**
929
+
930
+ - `NOT_A_FILE`: if the srcPath doesn't exist or is not a file
931
+
932
+ **Example**
933
+
934
+ ```ts
935
+ import { copyFile } from 'jg-fs/sync';
936
+
937
+ copyFile('config.example.json', 'config.json');
938
+ ```
939
+
940
+ #### <a id="api-sync-createDirectory"></a>`createDirectory`
941
+
942
+ *Function*
943
+
944
+ Creates a directory, with any missing parents. An existing directory is kept, or replaced with an empty one when `deleteIfExists` is set.
945
+
946
+ ```ts
947
+ createDirectory(path: string, deleteIfExists?: boolean): void
948
+ ```
949
+
950
+ **Parameters**
951
+
952
+ - `path` `string`: The directory's path.
953
+ - `deleteIfExists?` `boolean`: Whether to delete an existing directory and its contents first.
954
+
955
+ **Throws**
956
+
957
+ - `DIRECTORY_ALREADY_EXISTS`: if something other than a directory exists at the path
958
+
959
+ **Example**
960
+
961
+ ```ts
962
+ import { createDirectory } from 'jg-fs/sync';
963
+
964
+ createDirectory('dist/assets'); // creates missing parents too
965
+ createDirectory('dist', true); // replaces it with an empty directory
966
+ ```
967
+
968
+ #### <a id="api-sync-createDirectorySymLink"></a>`createDirectorySymLink`
969
+
970
+ *Function*
971
+
972
+ Creates a symbolic link to a directory.
973
+
974
+ ```ts
975
+ createDirectorySymLink(target: string, path: string): void
976
+ ```
977
+
978
+ **Parameters**
979
+
980
+ - `target` `string`: The directory the link points to.
981
+ - `path` `string`: Where to create the link.
982
+
983
+ **Throws**
984
+
985
+ - `NON_EXISTENT_DIRECTORY`: if the target directory doesn't exist
986
+ - `NOT_A_DIRECTORY`: if the target directory is not considered a directory by the OS
987
+
988
+ **Example**
989
+
990
+ ```ts
991
+ import { createDirectorySymLink } from 'jg-fs/sync';
992
+
993
+ createDirectorySymLink('releases/1.4.0', 'current');
994
+ ```
995
+
996
+ #### <a id="api-sync-createFileSymLink"></a>`createFileSymLink`
997
+
998
+ *Function*
999
+
1000
+ Creates a symbolic link to a file.
1001
+
1002
+ ```ts
1003
+ createFileSymLink(target: string, path: string): void
1004
+ ```
1005
+
1006
+ **Parameters**
1007
+
1008
+ - `target` `string`: The file the link points to.
1009
+ - `path` `string`: Where to create the link.
1010
+
1011
+ **Throws**
1012
+
1013
+ - `NON_EXISTENT_FILE`: if the target file does not exist
1014
+ - `NOT_A_FILE`: if the target is not a file
1015
+
1016
+ **Example**
1017
+
1018
+ ```ts
1019
+ import { createFileSymLink } from 'jg-fs/sync';
1020
+
1021
+ createFileSymLink('config.production.json', 'config.json');
1022
+ ```
1023
+
1024
+ #### <a id="api-sync-deleteDirectory"></a>`deleteDirectory`
1025
+
1026
+ *Function*
1027
+
1028
+ Deletes a directory and everything in it. A missing directory is not an error.
1029
+
1030
+ ```ts
1031
+ deleteDirectory(path: string): void
1032
+ ```
1033
+
1034
+ **Parameters**
1035
+
1036
+ - `path` `string`: The directory's path.
1037
+
1038
+ **Example**
1039
+
1040
+ ```ts
1041
+ import { deleteDirectory } from 'jg-fs/sync';
1042
+
1043
+ deleteDirectory('dist');
1044
+ ```
1045
+
1046
+ #### <a id="api-sync-deleteFile"></a>`deleteFile`
1047
+
1048
+ *Function*
1049
+
1050
+ Deletes a file.
1051
+
1052
+ ```ts
1053
+ deleteFile(path: string): void
1054
+ ```
1055
+
1056
+ **Parameters**
1057
+
1058
+ - `path` `string`: The file's path.
1059
+
1060
+ **Throws**
1061
+
1062
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
1063
+
1064
+ **Example**
1065
+
1066
+ ```ts
1067
+ import { deleteFile } from 'jg-fs/sync';
1068
+
1069
+ deleteFile('cache.json');
1070
+ ```
1071
+
1072
+ #### <a id="api-sync-getDirectoryElements"></a>`getDirectoryElements`
1073
+
1074
+ *Function*
1075
+
1076
+ Lists a directory's elements grouped by kind: directories, files (filtered by extension), and symbolic links, each group sorted. Entries removed while the directory is read are left out.
1077
+
1078
+ ```ts
1079
+ getDirectoryElements(
1080
+ path: string,
1081
+ options?: Partial<IDirectoryElementsOptions>,
1082
+ ): IDirectoryPathElements
1083
+ ```
1084
+
1085
+ **Parameters**
1086
+
1087
+ - `path` `string`: The directory's path.
1088
+ - `options?` `Partial<IDirectoryElementsOptions>`: The extensions to include and how to sort.
1089
+
1090
+ **Returns** `IDirectoryPathElements`: The grouped elements.
1091
+
1092
+ **Throws**
1093
+
1094
+ - `NOT_A_DIRECTORY`: if the path doesn't exist or is not a directory
1095
+
1096
+ **Example**
1097
+
1098
+ ```ts
1099
+ import { getDirectoryElements } from 'jg-fs/sync';
1100
+
1101
+ const { files } = getDirectoryElements('content', { includeExts: ['.md'], sortByKey: 'creation', sortOrder: 'desc' });
1102
+ ```
1103
+
1104
+ #### <a id="api-sync-getPathElement"></a>`getPathElement`
1105
+
1106
+ *Function*
1107
+
1108
+ Reads a path's details without following a symbolic link at the path.
1109
+
1110
+ ```ts
1111
+ getPathElement(path: string): IPathElement | null
1112
+ ```
1113
+
1114
+ **Parameters**
1115
+
1116
+ - `path` `string`: The path.
1117
+
1118
+ **Returns** `IPathElement | null`: The path's details, or `null` when it doesn't exist.
1119
+
1120
+ **Example**
1121
+
1122
+ ```ts
1123
+ import { getPathElement } from 'jg-fs/sync';
1124
+
1125
+ const element = getPathElement('logo.png');
1126
+ element?.size; // the size in bytes, or undefined when the path doesn't exist
1127
+ ```
1128
+
1129
+ #### <a id="api-sync-isDirectory"></a>`isDirectory`
1130
+
1131
+ *Function*
1132
+
1133
+ Checks whether a path exists and is a directory.
1134
+
1135
+ ```ts
1136
+ isDirectory(path: string): boolean
1137
+ ```
1138
+
1139
+ **Parameters**
1140
+
1141
+ - `path` `string`: The path.
1142
+
1143
+ **Returns** `boolean`: Whether the path is a directory.
1144
+
1145
+ **Example**
1146
+
1147
+ ```ts
1148
+ import { isDirectory } from 'jg-fs/sync';
1149
+
1150
+ isDirectory('dist'); // true
1151
+ ```
1152
+
1153
+ #### <a id="api-sync-isFile"></a>`isFile`
1154
+
1155
+ *Function*
1156
+
1157
+ Checks whether a path exists and is a regular file.
1158
+
1159
+ ```ts
1160
+ isFile(path: string): boolean
1161
+ ```
1162
+
1163
+ **Parameters**
1164
+
1165
+ - `path` `string`: The path.
1166
+
1167
+ **Returns** `boolean`: Whether the path is a file.
1168
+
1169
+ **Example**
1170
+
1171
+ ```ts
1172
+ import { isFile } from 'jg-fs/sync';
1173
+
1174
+ isFile('package.json'); // true
1175
+ ```
1176
+
1177
+ #### <a id="api-sync-isPathWithin"></a>`isPathWithin`
1178
+
1179
+ *Function*
1180
+
1181
+ Checks whether a path is a root path or nested inside it. The check is lexical: both paths are resolved without touching the filesystem, and symbolic links aren't followed. Sibling paths that share a prefix, such as `/data/app` and `/data/application`, aren't nested, and neither is a path on another drive on Windows.
1182
+
1183
+ ```ts
1184
+ isPathWithin(rootPath: string, targetPath: string): boolean
1185
+ ```
1186
+
1187
+ **Parameters**
1188
+
1189
+ - `rootPath` `string`: The root path.
1190
+ - `targetPath` `string`: The path to check.
1191
+
1192
+ **Returns** `boolean`: Whether the target is the root or inside it.
1193
+
1194
+ **Example**
1195
+
1196
+ ```ts
1197
+ import { isPathWithin } from 'jg-fs';
1198
+
1199
+ isPathWithin('/data/app', '/data/app/logs/today.log'); // true
1200
+ isPathWithin('/data/app', '/data/app/../secrets'); // false
1201
+ isPathWithin('/data/app', '/data/application'); // false
1202
+ ```
1203
+
1204
+ #### <a id="api-sync-pathExists"></a>`pathExists`
1205
+
1206
+ *Function*
1207
+
1208
+ Checks whether a path exists, whether it's a file, a directory, or anything else.
1209
+
1210
+ ```ts
1211
+ pathExists(path: string): boolean
1212
+ ```
1213
+
1214
+ **Parameters**
1215
+
1216
+ - `path` `string`: The path.
1217
+
1218
+ **Returns** `boolean`: Whether the path exists.
1219
+
1220
+ **Example**
1221
+
1222
+ ```ts
1223
+ import { pathExists } from 'jg-fs/sync';
1224
+
1225
+ pathExists('config.json'); // true when the file exists
1226
+ ```
1227
+
1228
+ #### <a id="api-sync-readBoundedFile"></a>`readBoundedFile`
1229
+
1230
+ *Function*
1231
+
1232
+ Reads a regular file of at most `maxBytes` bytes. It never follows a symbolic link in the path's last component, though links in parent directories are followed, so callers that need containment combine it with `isPathWithin`. It reads at most `maxBytes + 1` bytes, even when the file grows while it's read.
1233
+
1234
+ ```ts
1235
+ readBoundedFile(path: string, maxBytes: number): Buffer
1236
+ ```
1237
+
1238
+ **Parameters**
1239
+
1240
+ - `path` `string`: The file's path.
1241
+ - `maxBytes` `number`: The largest size accepted, in bytes.
1242
+
1243
+ **Returns** `Buffer`: The content.
1244
+
1245
+ **Throws**
1246
+
1247
+ - `NOT_A_FILE`: if the path doesn't exist, its final component is a symbolic link, or it isn't a regular file
1248
+ - `FILE_TOO_LARGE`: if the file holds more than maxBytes bytes
1249
+
1250
+ **Example**
1251
+
1252
+ ```ts
1253
+ import { join } from 'node:path';
1254
+ import { readBoundedFile } from 'jg-fs/sync';
1255
+
1256
+ const upload = readBoundedFile(join('uploads', 'avatar.png'), 5 * 1024 * 1024); // at most 5 MB
1257
+ ```
1258
+
1259
+ #### <a id="api-sync-readBufferFile"></a>`readBufferFile`
1260
+
1261
+ *Function*
1262
+
1263
+ Reads a binary file. An empty file reads as an empty Buffer.
1264
+
1265
+ ```ts
1266
+ readBufferFile(path: string): Buffer
1267
+ ```
1268
+
1269
+ **Parameters**
1270
+
1271
+ - `path` `string`: The file's path.
1272
+
1273
+ **Returns** `Buffer`: The content.
1274
+
1275
+ **Throws**
1276
+
1277
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
1278
+
1279
+ **Example**
1280
+
1281
+ ```ts
1282
+ import { readBufferFile } from 'jg-fs/sync';
1283
+
1284
+ const image = readBufferFile('avatar.png');
1285
+ ```
1286
+
1287
+ #### <a id="api-sync-readDirectory"></a>`readDirectory`
1288
+
1289
+ *Function*
1290
+
1291
+ Lists a directory's contents as paths joined to the directory's path.
1292
+
1293
+ ```ts
1294
+ readDirectory(path: string, recursive?: boolean): string[]
1295
+ ```
1296
+
1297
+ **Parameters**
1298
+
1299
+ - `path` `string`: The directory's path.
1300
+ - `recursive?` `boolean`: Whether to include the contents of subdirectories. Defaults to `false`.
1301
+
1302
+ **Returns** `string[]`: The content paths.
1303
+
1304
+ **Throws**
1305
+
1306
+ - `NOT_A_DIRECTORY`: if the path doesn't exist or is not a directory
1307
+
1308
+ **Example**
1309
+
1310
+ ```ts
1311
+ import { readDirectory } from 'jg-fs/sync';
1312
+
1313
+ readDirectory('dist'); // ['dist/index.js', 'dist/assets']
1314
+ readDirectory('dist', true); // includes the contents of dist/assets
1315
+ ```
1316
+
1317
+ #### <a id="api-sync-readFile"></a>`readFile`
1318
+
1319
+ *Function*
1320
+
1321
+ Reads a file as a string when an encoding is given, or as a Buffer otherwise.
1322
+
1323
+ ```ts
1324
+ readFile(path: string, options: IReadStringFileOptions): string
1325
+ ```
1326
+
1327
+ **Parameters**
1328
+
1329
+ - `path` `string`: The file's path.
1330
+ - `options` `IReadStringFileOptions`: Node.js's readFile options, such as the encoding.
1331
+
1332
+ **Returns** `string`: The content.
1333
+
1334
+ **Throws**
1335
+
1336
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
1337
+
1338
+ ```ts
1339
+ readFile(path: string, options?: IReadBufferFileOptions): Buffer
1340
+ ```
1341
+
1342
+ **Parameters**
1343
+
1344
+ - `path` `string`: The file's path.
1345
+ - `options?` `IReadBufferFileOptions`: Node.js's readFile options, such as the encoding.
1346
+
1347
+ **Returns** `Buffer`: The content.
1348
+
1349
+ **Throws**
1350
+
1351
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
1352
+
1353
+ ```ts
1354
+ readFile(
1355
+ path: string,
1356
+ options?: IReadFileOptions,
1357
+ ): string | Buffer<ArrayBufferLike>
1358
+ ```
1359
+
1360
+ **Parameters**
1361
+
1362
+ - `path` `string`: The file's path.
1363
+ - `options?` `IReadFileOptions`: Node.js's readFile options, such as the encoding.
1364
+
1365
+ **Returns** `string | Buffer<ArrayBufferLike>`: The content.
1366
+
1367
+ **Throws**
1368
+
1369
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
1370
+
1371
+ **Example**
1372
+
1373
+ ```ts
1374
+ import { readFile } from 'jg-fs/sync';
1375
+
1376
+ readFile('notes.md', 'utf-8'); // a string
1377
+ readFile('avatar.png'); // a Buffer
1378
+ ```
1379
+
1380
+ #### <a id="api-sync-readJsonFile"></a>`readJsonFile`
1381
+
1382
+ *Function*
1383
+
1384
+ Reads and parses a JSON file, and validates it with the schema when there is one. Without a schema, the value is `unknown`, or the type you pass.
1385
+
1386
+ ```ts
1387
+ readJsonFile<T = unknown>(path: string, options?: IReadJsonFileOptions<T>): T
1388
+ ```
1389
+
1390
+ **Parameters**
1391
+
1392
+ - `path` `string`: The file's path.
1393
+ - `options?` `IReadJsonFileOptions<T>`: The schema, such as a Zod schema, whose errors propagate unchanged. Defaults to `{}`.
1394
+
1395
+ **Returns** `T`: The parsed value.
1396
+
1397
+ **Throws**
1398
+
1399
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
1400
+ - `FILE_CONTENT_IS_EMPTY_OR_INVALID`: if the file's JSON content cannot be parsed
1401
+
1402
+ **Example 1**
1403
+
1404
+ ```ts
1405
+ import { readJsonFile } from 'jg-fs/sync';
1406
+
1407
+ const config = readJsonFile('config.json', { schema: Config }); // Config's type
1408
+ ```
1409
+
1410
+ **Example 2**
1411
+
1412
+ ```ts
1413
+ import { readJsonFile } from 'jg-fs/sync';
1414
+
1415
+ const value = readJsonFile('data.json'); // unknown
1416
+ ```
1417
+
1418
+ #### <a id="api-sync-readTextFile"></a>`readTextFile`
1419
+
1420
+ *Function*
1421
+
1422
+ Reads a UTF-8 text file. An empty file reads as an empty string.
1423
+
1424
+ ```ts
1425
+ readTextFile(path: string): string
1426
+ ```
1427
+
1428
+ **Parameters**
1429
+
1430
+ - `path` `string`: The file's path.
1431
+
1432
+ **Returns** `string`: The text.
1433
+
1434
+ **Throws**
1435
+
1436
+ - `NOT_A_FILE`: if the path doesn't exist or is not a file
1437
+
1438
+ **Example**
1439
+
1440
+ ```ts
1441
+ import { readTextFile } from 'jg-fs/sync';
1442
+
1443
+ const markdown = readTextFile('README.md');
1444
+ ```
1445
+
1446
+ #### <a id="api-sync-writeBufferFile"></a>`writeBufferFile`
1447
+
1448
+ *Function*
1449
+
1450
+ Writes a binary file. An empty Buffer writes an empty file.
1451
+
1452
+ ```ts
1453
+ writeBufferFile(path: string, data: Buffer): void
1454
+ ```
1455
+
1456
+ **Parameters**
1457
+
1458
+ - `path` `string`: The file's path.
1459
+ - `data` `Buffer`: The content.
1460
+
1461
+ **Throws**
1462
+
1463
+ - `FILE_CONTENT_IS_EMPTY_OR_INVALID`: if the data isn't a Buffer
1464
+
1465
+ **Example**
1466
+
1467
+ ```ts
1468
+ import { writeBufferFile } from 'jg-fs/sync';
1469
+
1470
+ const imageBuffer = Buffer.from('iVBORw0KGgo=', 'base64');
1471
+ writeBufferFile('avatar.png', imageBuffer);
1472
+ ```
1473
+
1474
+ #### <a id="api-sync-writeFile"></a>`writeFile`
1475
+
1476
+ *Function*
1477
+
1478
+ Writes a file, creating its directory first when it doesn't exist.
1479
+
1480
+ ```ts
1481
+ writeFile(
1482
+ path: string,
1483
+ data: string | ArrayBufferView<ArrayBufferLike>,
1484
+ options?: WriteFileOptions,
1485
+ ): void
1486
+ ```
1487
+
1488
+ **Parameters**
1489
+
1490
+ - `path` `string`: The file's path.
1491
+ - `data` `string | ArrayBufferView<ArrayBufferLike>`: The content.
1492
+ - `options?` `WriteFileOptions`: Node.js's writeFile options, such as the encoding or mode.
1493
+
1494
+ **Example**
1495
+
1496
+ ```ts
1497
+ import { writeFile } from 'jg-fs/sync';
1498
+
1499
+ const csv = 'id,name\n1,Ada\n';
1500
+ writeFile('out/report.csv', csv, { encoding: 'utf-8' });
1501
+ ```
1502
+
1503
+ #### <a id="api-sync-writeFileAtomic"></a>`writeFileAtomic`
1504
+
1505
+ *Function*
1506
+
1507
+ Writes a file atomically: readers see either the previous content or the complete new content, never a partial write. The data goes to a temporary file in the same directory, which is flushed to disk and renamed over the path; on POSIX, the directory is flushed too. An explicit mode wins; without one, a replaced file keeps its permission bits and a new file gets the platform default. The temporary file is never more widely readable than the final mode. When writing fails before the rename, the temporary file is removed and the destination is untouched.
1508
+
1509
+ ```ts
1510
+ writeFileAtomic(
1511
+ path: string,
1512
+ data: string | ArrayBufferView<ArrayBufferLike>,
1513
+ mode?: number,
1514
+ ): void
1515
+ ```
1516
+
1517
+ **Parameters**
1518
+
1519
+ - `path` `string`: The file's path.
1520
+ - `data` `string | ArrayBufferView<ArrayBufferLike>`: The content.
1521
+ - `mode?` `number`: The permission bits of the written file, such as `0o600`.
1522
+
1523
+ **Example**
1524
+
1525
+ ```ts
1526
+ import { writeFileAtomic } from 'jg-fs/sync';
1527
+
1528
+ const state = { step: 2 };
1529
+ writeFileAtomic('state.json', JSON.stringify(state)); // readers never see a partial file
1530
+ writeFileAtomic('secrets.env', 'TOKEN=abc\n', 0o600); // only the owner can read it
1531
+ ```
1532
+
1533
+ #### <a id="api-sync-writeJsonFile"></a>`writeJsonFile`
1534
+
1535
+ *Function*
1536
+
1537
+ Writes a JSON file. A string is written as given; anything else is stringified.
1538
+
1539
+ ```ts
1540
+ writeJsonFile<T = Record<string, unknown>>(
1541
+ path: string,
1542
+ data: string | T,
1543
+ space?: number,
1544
+ ): void
1545
+ ```
1546
+
1547
+ **Parameters**
1548
+
1549
+ - `path` `string`: The file's path.
1550
+ - `data` `string | T`: The value, or its JSON text.
1551
+ - `space?` `number`: The indentation for stringified values. Defaults to `2`.
1552
+
1553
+ **Throws**
1554
+
1555
+ - `FILE_CONTENT_IS_EMPTY_OR_INVALID`: if the JSON content cannot be stringified or is empty
1556
+
1557
+ **Example**
1558
+
1559
+ ```ts
1560
+ import { writeJsonFile } from 'jg-fs/sync';
1561
+
1562
+ writeJsonFile('config.json', { port: 8080 });
1563
+ ```
1564
+
1565
+ #### <a id="api-sync-writeTextFile"></a>`writeTextFile`
1566
+
1567
+ *Function*
1568
+
1569
+ Writes a UTF-8 text file. An empty string writes an empty file.
1570
+
1571
+ ```ts
1572
+ writeTextFile(path: string, data: string): void
1573
+ ```
1574
+
1575
+ **Parameters**
1576
+
1577
+ - `path` `string`: The file's path.
1578
+ - `data` `string`: The text.
1579
+
1580
+ **Throws**
1581
+
1582
+ - `FILE_CONTENT_IS_EMPTY_OR_INVALID`: if the data isn't a string
1583
+
1584
+ **Example**
1585
+
1586
+ ```ts
1587
+ import { writeTextFile } from 'jg-fs/sync';
1588
+
1589
+ writeTextFile('notes/today.md', '# Today');
1590
+ ```
1591
+
1592
+ ### <a id="api-sync-constants"></a>Constants in `jg-fs/sync`
1593
+
1594
+ #### <a id="api-sync-ERRORS"></a>`ERRORS`
1595
+
1596
+ *Constant*
1597
+
1598
+ the codes of every error the package throws, from both the async and the sync entry
1599
+
1600
+ ```ts
1601
+ const ERRORS: { NOT_A_DIRECTORY: "NOT_A_DIRECTORY"; NON_EXISTENT_DIRECTORY: "NON_EXISTENT_DIRECTORY"; DIRECTORY_ALREADY_EXISTS: "DIRECTORY_ALREADY_EXISTS"; NON_EXISTENT_FILE: "NON_EXISTENT_FILE"; NOT_A_FILE: "NOT_A_FILE"; FILE_CONTENT_IS_EMPTY_OR_INVALID: "FILE_CONTENT_IS_EMPTY_OR_INVALID"; FILE_TOO_LARGE: "FILE_TOO_LARGE" }
1602
+ ```
1603
+
1604
+ ### <a id="api-sync-types"></a>Types in `jg-fs/sync`
1605
+
1606
+ #### <a id="api-sync-IDirectoryElementsKeySort"></a>`IDirectoryElementsKeySort`
1607
+
1608
+ *Type*
1609
+
1610
+ the keys a directory's elements can be sorted by
1611
+
1612
+ ```ts
1613
+ type IDirectoryElementsKeySort = 'baseName' | 'size' | 'creation';
1614
+ ```
1615
+
1616
+ #### <a id="api-sync-IDirectoryElementsOptions"></a>`IDirectoryElementsOptions`
1617
+
1618
+ *Type*
1619
+
1620
+ how getDirectoryElements filters and sorts a directory's elements
1621
+
1622
+ ```ts
1623
+ type IDirectoryElementsOptions = {
1624
+ sortByKey: IDirectoryElementsKeySort;
1625
+ sortOrder: ISortDirection;
1626
+ includeExts: string[];
1627
+ };
1628
+ ```
1629
+
1630
+ **Members**
1631
+
1632
+ - `sortByKey: IDirectoryElementsKeySort` (property): the key the elements are sorted by. Defaults to 'baseName'
1633
+ - `sortOrder: ISortDirection` (property): the sort direction. Defaults to 'asc'
1634
+ - `includeExts: string[]` (property): the extensions of the files to include, such as ['.json'], matched case-insensitively. Defaults to all
1635
+
1636
+ #### <a id="api-sync-IDirectoryPathElements"></a>`IDirectoryPathElements`
1637
+
1638
+ *Type*
1639
+
1640
+ a directory's elements, grouped by kind
1641
+
1642
+ ```ts
1643
+ type IDirectoryPathElements = {
1644
+ directories: IPathElement[];
1645
+ files: IPathElement[];
1646
+ symbolicLinks: IPathElement[];
1647
+ };
1648
+ ```
1649
+
1650
+ **Members**
1651
+
1652
+ - `directories: IPathElement[]` (property)
1653
+ - `files: IPathElement[]` (property)
1654
+ - `symbolicLinks: IPathElement[]` (property)
1655
+
1656
+ #### <a id="api-sync-IPathElement"></a>`IPathElement`
1657
+
1658
+ *Type*
1659
+
1660
+ the details of a path, read with lstat, so a symbolic link is described rather than followed
1661
+
1662
+ ```ts
1663
+ type IPathElement = {
1664
+ path: string;
1665
+ baseName: string;
1666
+ extName: string;
1667
+ isFile: boolean;
1668
+ isDirectory: boolean;
1669
+ isSymbolicLink: boolean;
1670
+ size: number;
1671
+ creation: number;
1672
+ };
1673
+ ```
1674
+
1675
+ **Members**
1676
+
1677
+ - `path: string` (property): the path, as given
1678
+ - `baseName: string` (property): the last segment of the path
1679
+ - `extName: string` (property): the extension, such as '.json', or an empty string when there is none
1680
+ - `isFile: boolean` (property): whether it's a regular file
1681
+ - `isDirectory: boolean` (property): whether it's a directory
1682
+ - `isSymbolicLink: boolean` (property): whether it's a symbolic link, in which case isFile and isDirectory are false
1683
+ - `size: number` (property): the size in bytes
1684
+ - `creation: number` (property): when it was created, in milliseconds since the epoch
1685
+
1686
+ #### <a id="api-sync-IReadJsonFileOptions"></a>`IReadJsonFileOptions`
1687
+
1688
+ *Type*
1689
+
1690
+ what readJsonFile accepts
1691
+
1692
+ ```ts
1693
+ type IReadJsonFileOptions<T> = {
1694
+ schema?: ISchema<T>;
1695
+ };
1696
+ ```
1697
+
1698
+ **Members**
1699
+
1700
+ - `schema: ISchema<T>` (property): validates the parsed content, such as a Zod schema. Its errors propagate unchanged
1701
+
1702
+ #### <a id="api-sync-ISchema"></a>`ISchema`
1703
+
1704
+ *Type*
1705
+
1706
+ validates parsed data and returns it typed, which a Zod schema does
1707
+
1708
+ ```ts
1709
+ type ISchema<T> = {
1710
+ parse(value: unknown): T;
1711
+ };
1712
+ ```
1713
+
1714
+ **Members**
1715
+
1716
+ - `parse(value: unknown): T` (method)