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.
- package/API.md +1716 -0
- package/CHANGELOG.md +20 -16
- package/README.md +6 -21
- package/dist/async/directories/directories.d.ts +14 -0
- package/dist/async/directories/directories.js +20 -2
- package/dist/async/directories/directories.js.map +1 -1
- package/dist/async/files/files.d.ts +46 -8
- package/dist/async/files/files.js +48 -10
- package/dist/async/files/files.js.map +1 -1
- package/dist/async/files/index.d.ts +1 -1
- package/dist/async/files/index.js +1 -1
- package/dist/async/files/index.js.map +1 -1
- package/dist/async/general/general.d.ts +4 -0
- package/dist/async/general/general.js +4 -0
- package/dist/async/general/general.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/paths.d.ts +2 -0
- package/dist/shared/paths.js +2 -0
- package/dist/shared/paths.js.map +1 -1
- package/dist/shared/types.d.ts +6 -0
- package/dist/shared/types.js.map +1 -1
- package/dist/shared/validations.d.ts +5 -3
- package/dist/shared/validations.js +30 -14
- package/dist/shared/validations.js.map +1 -1
- package/dist/sync/directories/directories.d.ts +14 -0
- package/dist/sync/directories/directories.js +20 -2
- package/dist/sync/directories/directories.js.map +1 -1
- package/dist/sync/files/files.d.ts +46 -8
- package/dist/sync/files/files.js +48 -10
- package/dist/sync/files/files.js.map +1 -1
- package/dist/sync/files/index.d.ts +1 -1
- package/dist/sync/files/index.js +1 -1
- package/dist/sync/files/index.js.map +1 -1
- package/dist/sync/general/general.d.ts +4 -0
- package/dist/sync/general/general.js +4 -0
- package/dist/sync/general/general.js.map +1 -1
- package/dist/sync/index.d.ts +2 -2
- package/dist/sync/index.js +1 -1
- package/dist/sync/index.js.map +1 -1
- package/package.json +10 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
A fresh start: breaking changes in a minor version, once, because nothing depended on 1.0.x.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- A `schema` option for `readJsonFile`, in the async and sync APIs, which validates the parsed content and types the result. Its errors propagate unchanged. `ISchema` and `IReadJsonFileOptions` are exported.
|
|
10
|
+
- `API.md`, the full API reference, generated from the TSDoc of the version it ships with, so it can be read from `node_modules` without a browser.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Errors are `Exception`s from `jg-exception` 1.1.0: the code, the data, and the cause each have their own field, nothing is encoded into the message, and `toJSON()` and `toString()` follow `jg-exception`.
|
|
15
|
+
- Error messages say what failed at that step and keep the error underneath as `cause`, instead of repeating its message, so `extractMessage` prints each layer of the chain once.
|
|
16
|
+
- `readJsonFile` returns `unknown` without a schema or a type argument, instead of `Record<string, unknown>`.
|
|
17
|
+
- `readJSONFile` and `writeJSONFile` are `readJsonFile` and `writeJsonFile`, in the async and sync APIs, so acronyms are cased as words, like every other name in the packages.
|
|
18
|
+
|
|
3
19
|
## 1.0.1
|
|
4
20
|
|
|
5
21
|
### Changed
|
|
@@ -8,23 +24,11 @@
|
|
|
8
24
|
|
|
9
25
|
## 1.0.0
|
|
10
26
|
|
|
11
|
-
The first release
|
|
27
|
+
The first release.
|
|
12
28
|
|
|
13
29
|
### Added
|
|
14
30
|
|
|
15
|
-
-
|
|
31
|
+
- Async functions for paths, directories, and files, such as `getDirectoryElements`, `readJSONFile`, `writeFileAtomic`, and `readBoundedFile`.
|
|
32
|
+
- The `jg-fs/sync` entry, with the same functions, synchronous.
|
|
16
33
|
- `ERRORS`, the codes of every error the package throws.
|
|
17
|
-
|
|
18
|
-
### Changed
|
|
19
|
-
|
|
20
|
-
- Empty content is allowed when reading and writing text and binary files.
|
|
21
|
-
- `createDirectory` succeeds when the directory already exists.
|
|
22
|
-
- `includeExts` matches extensions case-insensitively.
|
|
23
|
-
- The sync `readDirectory` joins paths with the platform's separator, and the sync `getDirectoryElements` skips entries removed while it reads.
|
|
24
|
-
- The async `getDirectoryElements` reads at most 32 entries at once.
|
|
25
|
-
- `readFile` returns `string` with an encoding and `Buffer` without one.
|
|
26
|
-
- Errors are `Exception`s from `jg-exception`.
|
|
27
|
-
|
|
28
|
-
### Fixed
|
|
29
|
-
|
|
30
|
-
- Typos in several error messages.
|
|
34
|
+
- Empty content is allowed when reading and writing text and binary files, and `createDirectory` succeeds when the directory already exists.
|
package/README.md
CHANGED
|
@@ -13,11 +13,14 @@ npm install jg-fs
|
|
|
13
13
|
The root entry is async, and `jg-fs/sync` has the same functions, synchronous:
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import {
|
|
16
|
+
import { readJsonFile, writeFileAtomic, writeTextFile } from 'jg-fs';
|
|
17
17
|
import { readTextFile } from 'jg-fs/sync';
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
|
|
20
|
+
const Config = z.object({ port: z.number() });
|
|
18
21
|
|
|
19
22
|
await writeTextFile('notes/today.md', '# Today'); // creates notes/ when it's missing
|
|
20
|
-
const config = await
|
|
23
|
+
const config = await readJsonFile('config.json', { schema: Config }); // validated, and typed by the schema
|
|
21
24
|
await writeFileAtomic('state.json', JSON.stringify(state)); // readers never see a partial file
|
|
22
25
|
|
|
23
26
|
const readme = readTextFile('README.md');
|
|
@@ -47,27 +50,9 @@ if (isPathWithin(uploadsDirectory, path)) {
|
|
|
47
50
|
|
|
48
51
|
Failures throw `Exception`s from `jg-exception`, with the codes in `ERRORS`.
|
|
49
52
|
|
|
50
|
-
## Coming from `fs-utils-async` and `fs-utils-sync`
|
|
51
|
-
|
|
52
|
-
| Old | `jg-fs` |
|
|
53
|
-
| -------------------------------- | ---------------------------- |
|
|
54
|
-
| `import … from 'fs-utils-async'` | `import … from 'jg-fs'` |
|
|
55
|
-
| `import … from 'fs-utils-sync'` | `import … from 'jg-fs/sync'` |
|
|
56
|
-
| the codes, not exported | `ERRORS` |
|
|
57
|
-
|
|
58
|
-
Changed behavior:
|
|
59
|
-
|
|
60
|
-
- Empty content is allowed. `readTextFile`, `readBufferFile`, and `readFile` return empty content instead of throwing, and `writeTextFile('')` and `writeBufferFile` with an empty Buffer write empty files. `FILE_CONTENT_IS_EMPTY_OR_INVALID` now means data of the wrong type, or JSON that is empty or can't be parsed or stringified. Code that relied on the throw to detect empty files needs its own check.
|
|
61
|
-
- `createDirectory` succeeds when the directory already exists, keeping its contents. It throws `DIRECTORY_ALREADY_EXISTS` only when a file or something else that isn't a directory is at the path.
|
|
62
|
-
- `includeExts` matches extensions case-insensitively, so `.JSON` and `.json` are the same.
|
|
63
|
-
- The sync `readDirectory` joins paths with the platform's separator, and the sync `getDirectoryElements` skips entries removed while it reads the directory, as the async one already did.
|
|
64
|
-
- The async `getDirectoryElements` reads at most 32 entries at once.
|
|
65
|
-
- `readFile` returns `string` when given an encoding and `Buffer` otherwise, instead of `string | Buffer`.
|
|
66
|
-
- Errors are `Exception`s from `jg-exception`, with the same codes.
|
|
67
|
-
|
|
68
53
|
## Documentation
|
|
69
54
|
|
|
70
|
-
The full API reference is at https://jg-packages.jesusgraterol.dev/packages/jg-fs/.
|
|
55
|
+
The full API reference ships in the package as `API.md`, generated from its TSDoc for the installed version, and is at https://jg-packages.jesusgraterol.dev/packages/jg-fs/.
|
|
71
56
|
|
|
72
57
|
## License
|
|
73
58
|
|
|
@@ -4,6 +4,8 @@ import { type IDirectoryElementsOptions, type IDirectoryPathElements } from '../
|
|
|
4
4
|
* @param path The path.
|
|
5
5
|
* @returns A promise that resolves to whether the path is a directory.
|
|
6
6
|
* @example
|
|
7
|
+
* import { isDirectory } from 'jg-fs';
|
|
8
|
+
*
|
|
7
9
|
* await isDirectory('dist'); // true
|
|
8
10
|
*/
|
|
9
11
|
export declare const isDirectory: (path: string) => Promise<boolean>;
|
|
@@ -12,6 +14,8 @@ export declare const isDirectory: (path: string) => Promise<boolean>;
|
|
|
12
14
|
* @param path The directory's path.
|
|
13
15
|
* @returns A promise that resolves once the directory is gone.
|
|
14
16
|
* @example
|
|
17
|
+
* import { deleteDirectory } from 'jg-fs';
|
|
18
|
+
*
|
|
15
19
|
* await deleteDirectory('dist');
|
|
16
20
|
*/
|
|
17
21
|
export declare const deleteDirectory: (path: string) => Promise<void>;
|
|
@@ -22,6 +26,8 @@ export declare const deleteDirectory: (path: string) => Promise<void>;
|
|
|
22
26
|
* @param deleteIfExists Whether to delete an existing directory and its contents first.
|
|
23
27
|
* @returns A promise that resolves once the directory exists.
|
|
24
28
|
* @example
|
|
29
|
+
* import { createDirectory } from 'jg-fs';
|
|
30
|
+
*
|
|
25
31
|
* await createDirectory('dist/assets'); // creates missing parents too
|
|
26
32
|
* await createDirectory('dist', true); // replaces it with an empty directory
|
|
27
33
|
* @throws
|
|
@@ -34,6 +40,8 @@ export declare const createDirectory: (path: string, deleteIfExists?: boolean) =
|
|
|
34
40
|
* @param destPath Where to copy it.
|
|
35
41
|
* @returns A promise that resolves once the copy is complete.
|
|
36
42
|
* @example
|
|
43
|
+
* import { copyDirectory } from 'jg-fs';
|
|
44
|
+
*
|
|
37
45
|
* await copyDirectory('templates', 'dist/templates');
|
|
38
46
|
* @throws
|
|
39
47
|
* - NOT_A_DIRECTORY: if the srcPath is not a directory
|
|
@@ -45,6 +53,8 @@ export declare const copyDirectory: (srcPath: string, destPath: string) => Promi
|
|
|
45
53
|
* @param path Where to create the link.
|
|
46
54
|
* @returns A promise that resolves once the link exists.
|
|
47
55
|
* @example
|
|
56
|
+
* import { createDirectorySymLink } from 'jg-fs';
|
|
57
|
+
*
|
|
48
58
|
* await createDirectorySymLink('releases/1.4.0', 'current');
|
|
49
59
|
* @throws
|
|
50
60
|
* - NON_EXISTENT_DIRECTORY: if the target directory doesn't exist
|
|
@@ -57,6 +67,8 @@ export declare const createDirectorySymLink: (target: string, path: string) => P
|
|
|
57
67
|
* @param recursive Whether to include the contents of subdirectories.
|
|
58
68
|
* @returns A promise that resolves to the content paths.
|
|
59
69
|
* @example
|
|
70
|
+
* import { readDirectory } from 'jg-fs';
|
|
71
|
+
*
|
|
60
72
|
* await readDirectory('dist'); // ['dist/index.js', 'dist/assets']
|
|
61
73
|
* await readDirectory('dist', true); // includes the contents of dist/assets
|
|
62
74
|
* @throws
|
|
@@ -71,6 +83,8 @@ export declare const readDirectory: (path: string, recursive?: boolean) => Promi
|
|
|
71
83
|
* @param options The extensions to include and how to sort.
|
|
72
84
|
* @returns A promise that resolves to the grouped elements.
|
|
73
85
|
* @example
|
|
86
|
+
* import { getDirectoryElements } from 'jg-fs';
|
|
87
|
+
*
|
|
74
88
|
* const { files } = await getDirectoryElements('content', { includeExts: ['.md'], sortByKey: 'creation', sortOrder: 'desc' });
|
|
75
89
|
* @throws
|
|
76
90
|
* - NOT_A_DIRECTORY: if the path doesn't exist or is not a directory
|
|
@@ -9,6 +9,8 @@ import { mapWithConcurrency } from './utilities.js';
|
|
|
9
9
|
* @param path The path.
|
|
10
10
|
* @returns A promise that resolves to whether the path is a directory.
|
|
11
11
|
* @example
|
|
12
|
+
* import { isDirectory } from 'jg-fs';
|
|
13
|
+
*
|
|
12
14
|
* await isDirectory('dist'); // true
|
|
13
15
|
*/
|
|
14
16
|
export const isDirectory = async (path) => (await getPathElement(path))?.isDirectory ?? false;
|
|
@@ -17,6 +19,8 @@ export const isDirectory = async (path) => (await getPathElement(path))?.isDirec
|
|
|
17
19
|
* @param path The directory's path.
|
|
18
20
|
* @returns A promise that resolves once the directory is gone.
|
|
19
21
|
* @example
|
|
22
|
+
* import { deleteDirectory } from 'jg-fs';
|
|
23
|
+
*
|
|
20
24
|
* await deleteDirectory('dist');
|
|
21
25
|
*/
|
|
22
26
|
export const deleteDirectory = (path) => rm(path, { recursive: true, force: true });
|
|
@@ -27,6 +31,8 @@ export const deleteDirectory = (path) => rm(path, { recursive: true, force: true
|
|
|
27
31
|
* @param deleteIfExists Whether to delete an existing directory and its contents first.
|
|
28
32
|
* @returns A promise that resolves once the directory exists.
|
|
29
33
|
* @example
|
|
34
|
+
* import { createDirectory } from 'jg-fs';
|
|
35
|
+
*
|
|
30
36
|
* await createDirectory('dist/assets'); // creates missing parents too
|
|
31
37
|
* await createDirectory('dist', true); // replaces it with an empty directory
|
|
32
38
|
* @throws
|
|
@@ -35,7 +41,9 @@ export const deleteDirectory = (path) => rm(path, { recursive: true, force: true
|
|
|
35
41
|
export const createDirectory = async (path, deleteIfExists) => {
|
|
36
42
|
const element = await getPathElement(path);
|
|
37
43
|
if (element !== null && !element.isDirectory) {
|
|
38
|
-
throw new Exception(`The path '${path}' already exists and is not a directory.`,
|
|
44
|
+
throw new Exception(`The path '${path}' already exists and is not a directory.`, {
|
|
45
|
+
code: ERRORS.DIRECTORY_ALREADY_EXISTS,
|
|
46
|
+
});
|
|
39
47
|
}
|
|
40
48
|
if (element !== null && deleteIfExists) {
|
|
41
49
|
await deleteDirectory(path);
|
|
@@ -48,6 +56,8 @@ export const createDirectory = async (path, deleteIfExists) => {
|
|
|
48
56
|
* @param destPath Where to copy it.
|
|
49
57
|
* @returns A promise that resolves once the copy is complete.
|
|
50
58
|
* @example
|
|
59
|
+
* import { copyDirectory } from 'jg-fs';
|
|
60
|
+
*
|
|
51
61
|
* await copyDirectory('templates', 'dist/templates');
|
|
52
62
|
* @throws
|
|
53
63
|
* - NOT_A_DIRECTORY: if the srcPath is not a directory
|
|
@@ -65,6 +75,8 @@ export const copyDirectory = async (srcPath, destPath) => {
|
|
|
65
75
|
* @param path Where to create the link.
|
|
66
76
|
* @returns A promise that resolves once the link exists.
|
|
67
77
|
* @example
|
|
78
|
+
* import { createDirectorySymLink } from 'jg-fs';
|
|
79
|
+
*
|
|
68
80
|
* await createDirectorySymLink('releases/1.4.0', 'current');
|
|
69
81
|
* @throws
|
|
70
82
|
* - NON_EXISTENT_DIRECTORY: if the target directory doesn't exist
|
|
@@ -73,7 +85,9 @@ export const copyDirectory = async (srcPath, destPath) => {
|
|
|
73
85
|
export const createDirectorySymLink = async (target, path) => {
|
|
74
86
|
const element = await getPathElement(target);
|
|
75
87
|
if (element === null) {
|
|
76
|
-
throw new Exception(`The target directory '${target}' does not exist.`,
|
|
88
|
+
throw new Exception(`The target directory '${target}' does not exist.`, {
|
|
89
|
+
code: ERRORS.NON_EXISTENT_DIRECTORY,
|
|
90
|
+
});
|
|
77
91
|
}
|
|
78
92
|
if (!element.isDirectory) {
|
|
79
93
|
throw notADirectory(target);
|
|
@@ -86,6 +100,8 @@ export const createDirectorySymLink = async (target, path) => {
|
|
|
86
100
|
* @param recursive Whether to include the contents of subdirectories.
|
|
87
101
|
* @returns A promise that resolves to the content paths.
|
|
88
102
|
* @example
|
|
103
|
+
* import { readDirectory } from 'jg-fs';
|
|
104
|
+
*
|
|
89
105
|
* await readDirectory('dist'); // ['dist/index.js', 'dist/assets']
|
|
90
106
|
* await readDirectory('dist', true); // includes the contents of dist/assets
|
|
91
107
|
* @throws
|
|
@@ -106,6 +122,8 @@ export const readDirectory = async (path, recursive = false) => {
|
|
|
106
122
|
* @param options The extensions to include and how to sort.
|
|
107
123
|
* @returns A promise that resolves to the grouped elements.
|
|
108
124
|
* @example
|
|
125
|
+
* import { getDirectoryElements } from 'jg-fs';
|
|
126
|
+
*
|
|
109
127
|
* const { files } = await getDirectoryElements('content', { includeExts: ['.md'], sortByKey: 'creation', sortOrder: 'desc' });
|
|
110
128
|
* @throws
|
|
111
129
|
* - NOT_A_DIRECTORY: if the path doesn't exist or is not a directory
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directories.js","sourceRoot":"","sources":["../../../src/async/directories/directories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EACL,6BAA6B,EAC7B,8BAA8B,EAC9B,MAAM,EACN,sBAAsB,EACtB,aAAa,GAId,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD
|
|
1
|
+
{"version":3,"file":"directories.js","sourceRoot":"","sources":["../../../src/async/directories/directories.ts"],"names":[],"mappings":"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","sourcesContent":["import { cp, mkdir, readdir, rm, symlink } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { Exception } from 'jg-exception';\n\nimport {\n buildDirectoryElementsOptions,\n DIRECTORY_ELEMENTS_CONCURRENCY,\n ERRORS,\n groupDirectoryElements,\n notADirectory,\n type IDirectoryElementsOptions,\n type IDirectoryPathElements,\n type IPathElement,\n} from '../../shared/index.js';\nimport { getPathElement } from '../general/index.js';\n\nimport { mapWithConcurrency } from './utilities.js';\n\n/**\n * Checks whether a path exists and is a directory.\n * @param path The path.\n * @returns A promise that resolves to whether the path is a directory.\n * @example\n * import { isDirectory } from 'jg-fs';\n *\n * await isDirectory('dist'); // true\n */\nexport const isDirectory = async (path: string): Promise<boolean> =>\n (await getPathElement(path))?.isDirectory ?? false;\n\n/**\n * Deletes a directory and everything in it. A missing directory is not an error.\n * @param path The directory's path.\n * @returns A promise that resolves once the directory is gone.\n * @example\n * import { deleteDirectory } from 'jg-fs';\n *\n * await deleteDirectory('dist');\n */\nexport const deleteDirectory = (path: string): Promise<void> =>\n rm(path, { recursive: true, force: true });\n\n/**\n * Creates a directory, with any missing parents. An existing directory is kept, or replaced with\n * an empty one when `deleteIfExists` is set.\n * @param path The directory's path.\n * @param deleteIfExists Whether to delete an existing directory and its contents first.\n * @returns A promise that resolves once the directory exists.\n * @example\n * import { createDirectory } from 'jg-fs';\n *\n * await createDirectory('dist/assets'); // creates missing parents too\n * await createDirectory('dist', true); // replaces it with an empty directory\n * @throws\n * - DIRECTORY_ALREADY_EXISTS: if something other than a directory exists at the path\n */\nexport const createDirectory = async (path: string, deleteIfExists?: boolean): Promise<void> => {\n const element = await getPathElement(path);\n if (element !== null && !element.isDirectory) {\n throw new Exception(`The path '${path}' already exists and is not a directory.`, {\n code: ERRORS.DIRECTORY_ALREADY_EXISTS,\n });\n }\n if (element !== null && deleteIfExists) {\n await deleteDirectory(path);\n }\n await mkdir(path, { recursive: true });\n};\n\n/**\n * Copies a directory and everything in it, replacing the destination completely.\n * @param srcPath The directory to copy.\n * @param destPath Where to copy it.\n * @returns A promise that resolves once the copy is complete.\n * @example\n * import { copyDirectory } from 'jg-fs';\n *\n * await copyDirectory('templates', 'dist/templates');\n * @throws\n * - NOT_A_DIRECTORY: if the srcPath is not a directory\n */\nexport const copyDirectory = async (srcPath: string, destPath: string): Promise<void> => {\n if (!(await isDirectory(srcPath))) {\n throw notADirectory(srcPath);\n }\n await deleteDirectory(destPath);\n await cp(srcPath, destPath, { recursive: true });\n};\n\n/**\n * Creates a symbolic link to a directory.\n * @param target The directory the link points to.\n * @param path Where to create the link.\n * @returns A promise that resolves once the link exists.\n * @example\n * import { createDirectorySymLink } from 'jg-fs';\n *\n * await createDirectorySymLink('releases/1.4.0', 'current');\n * @throws\n * - NON_EXISTENT_DIRECTORY: if the target directory doesn't exist\n * - NOT_A_DIRECTORY: if the target directory is not considered a directory by the OS\n */\nexport const createDirectorySymLink = async (target: string, path: string): Promise<void> => {\n const element = await getPathElement(target);\n if (element === null) {\n throw new Exception(`The target directory '${target}' does not exist.`, {\n code: ERRORS.NON_EXISTENT_DIRECTORY,\n });\n }\n if (!element.isDirectory) {\n throw notADirectory(target);\n }\n await symlink(target, path, 'dir');\n};\n\n/**\n * Lists a directory's contents as paths joined to the directory's path.\n * @param path The directory's path.\n * @param recursive Whether to include the contents of subdirectories.\n * @returns A promise that resolves to the content paths.\n * @example\n * import { readDirectory } from 'jg-fs';\n *\n * await readDirectory('dist'); // ['dist/index.js', 'dist/assets']\n * await readDirectory('dist', true); // includes the contents of dist/assets\n * @throws\n * - NOT_A_DIRECTORY: if the path doesn't exist or is not a directory\n */\nexport const readDirectory = async (\n path: string,\n recursive: boolean = false,\n): Promise<string[]> => {\n if (!(await isDirectory(path))) {\n throw notADirectory(path);\n }\n const contentPaths = await readdir(path, { encoding: 'utf-8', recursive });\n return contentPaths.map((contentPath) => join(path, contentPath));\n};\n\n/**\n * Lists a directory's elements grouped by kind: directories, files (filtered by extension), and\n * symbolic links, each group sorted. Entries removed while the directory is read are left out, and\n * at most 32 entries are read at once.\n * @param path The directory's path.\n * @param options The extensions to include and how to sort.\n * @returns A promise that resolves to the grouped elements.\n * @example\n * import { getDirectoryElements } from 'jg-fs';\n *\n * const { files } = await getDirectoryElements('content', { includeExts: ['.md'], sortByKey: 'creation', sortOrder: 'desc' });\n * @throws\n * - NOT_A_DIRECTORY: if the path doesn't exist or is not a directory\n */\nexport const getDirectoryElements = async (\n path: string,\n options?: Partial<IDirectoryElementsOptions>,\n): Promise<IDirectoryPathElements> => {\n const contentPaths = await readDirectory(path);\n const elements = (\n await mapWithConcurrency(contentPaths, DIRECTORY_ELEMENTS_CONCURRENCY, getPathElement)\n ).filter((element): element is IPathElement => element !== null);\n return groupDirectoryElements(elements, buildDirectoryElementsOptions(options));\n};\n"]}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import type { WriteFileOptions } from 'node:fs';
|
|
3
|
-
import { type IReadBufferFileOptions, type IReadFileOptions, type IReadStringFileOptions } from '../../shared/index.js';
|
|
3
|
+
import { type IReadBufferFileOptions, type IReadFileOptions, type IReadJsonFileOptions, type IReadStringFileOptions } from '../../shared/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* Checks whether a path exists and is a regular file.
|
|
6
6
|
* @param path The path.
|
|
7
7
|
* @returns A promise that resolves to whether the path is a file.
|
|
8
8
|
* @example
|
|
9
|
+
* import { isFile } from 'jg-fs';
|
|
10
|
+
*
|
|
9
11
|
* await isFile('package.json'); // true
|
|
10
12
|
*/
|
|
11
13
|
export declare const isFile: (path: string) => Promise<boolean>;
|
|
@@ -16,6 +18,9 @@ export declare const isFile: (path: string) => Promise<boolean>;
|
|
|
16
18
|
* @param options Node.js's writeFile options, such as the encoding or mode.
|
|
17
19
|
* @returns A promise that resolves once the file is written.
|
|
18
20
|
* @example
|
|
21
|
+
* import { writeFile } from 'jg-fs';
|
|
22
|
+
*
|
|
23
|
+
* const csv = 'id,name\n1,Ada\n';
|
|
19
24
|
* await writeFile('out/report.csv', csv, { encoding: 'utf-8' });
|
|
20
25
|
*/
|
|
21
26
|
export declare const writeFile: (path: string, data: string | NodeJS.ArrayBufferView, options?: WriteFileOptions) => Promise<void>;
|
|
@@ -25,6 +30,8 @@ export declare const writeFile: (path: string, data: string | NodeJS.ArrayBuffer
|
|
|
25
30
|
* @param data The text.
|
|
26
31
|
* @returns A promise that resolves once the file is written.
|
|
27
32
|
* @example
|
|
33
|
+
* import { writeTextFile } from 'jg-fs';
|
|
34
|
+
*
|
|
28
35
|
* await writeTextFile('notes/today.md', '# Today');
|
|
29
36
|
* @throws
|
|
30
37
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a string
|
|
@@ -37,17 +44,22 @@ export declare const writeTextFile: (path: string, data: string) => Promise<void
|
|
|
37
44
|
* @param space The indentation for stringified values.
|
|
38
45
|
* @returns A promise that resolves once the file is written.
|
|
39
46
|
* @example
|
|
40
|
-
*
|
|
47
|
+
* import { writeJsonFile } from 'jg-fs';
|
|
48
|
+
*
|
|
49
|
+
* await writeJsonFile('config.json', { port: 8080 });
|
|
41
50
|
* @throws
|
|
42
51
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the JSON content cannot be stringified or is empty
|
|
43
52
|
*/
|
|
44
|
-
export declare const
|
|
53
|
+
export declare const writeJsonFile: <T = Record<string, unknown>>(path: string, data: T | string, space?: number) => Promise<void>;
|
|
45
54
|
/**
|
|
46
55
|
* Writes a binary file. An empty Buffer writes an empty file.
|
|
47
56
|
* @param path The file's path.
|
|
48
57
|
* @param data The content.
|
|
49
58
|
* @returns A promise that resolves once the file is written.
|
|
50
59
|
* @example
|
|
60
|
+
* import { writeBufferFile } from 'jg-fs';
|
|
61
|
+
*
|
|
62
|
+
* const imageBuffer = Buffer.from('iVBORw0KGgo=', 'base64');
|
|
51
63
|
* await writeBufferFile('avatar.png', imageBuffer);
|
|
52
64
|
* @throws
|
|
53
65
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a Buffer
|
|
@@ -59,6 +71,8 @@ export declare const writeBufferFile: (path: string, data: Buffer) => Promise<vo
|
|
|
59
71
|
* @param options Node.js's readFile options, such as the encoding.
|
|
60
72
|
* @returns A promise that resolves to the content.
|
|
61
73
|
* @example
|
|
74
|
+
* import { readFile } from 'jg-fs';
|
|
75
|
+
*
|
|
62
76
|
* await readFile('notes.md', 'utf-8'); // a string
|
|
63
77
|
* await readFile('avatar.png'); // a Buffer
|
|
64
78
|
* @throws
|
|
@@ -72,27 +86,39 @@ export declare function readFile(path: string, options?: IReadFileOptions): Prom
|
|
|
72
86
|
* @param path The file's path.
|
|
73
87
|
* @returns A promise that resolves to the text.
|
|
74
88
|
* @example
|
|
89
|
+
* import { readTextFile } from 'jg-fs';
|
|
90
|
+
*
|
|
75
91
|
* const markdown = await readTextFile('README.md');
|
|
76
92
|
* @throws
|
|
77
93
|
* - NOT_A_FILE: if the path doesn't exist or is not a file
|
|
78
94
|
*/
|
|
79
95
|
export declare const readTextFile: (path: string) => Promise<string>;
|
|
80
96
|
/**
|
|
81
|
-
* Reads and parses a JSON file.
|
|
97
|
+
* Reads and parses a JSON file, and validates it with the schema when there is one. Without a
|
|
98
|
+
* schema, the value is `unknown`, or the type you pass.
|
|
82
99
|
* @param path The file's path.
|
|
100
|
+
* @param options The schema, such as a Zod schema, whose errors propagate unchanged.
|
|
83
101
|
* @returns A promise that resolves to the parsed value.
|
|
84
102
|
* @example
|
|
85
|
-
*
|
|
103
|
+
* import { readJsonFile } from 'jg-fs';
|
|
104
|
+
*
|
|
105
|
+
* const config = await readJsonFile('config.json', { schema: Config }); // Config's type
|
|
106
|
+
* @example
|
|
107
|
+
* import { readJsonFile } from 'jg-fs';
|
|
108
|
+
*
|
|
109
|
+
* const value = await readJsonFile('data.json'); // unknown
|
|
86
110
|
* @throws
|
|
87
111
|
* - NOT_A_FILE: if the path doesn't exist or is not a file
|
|
88
112
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the file's JSON content cannot be parsed
|
|
89
113
|
*/
|
|
90
|
-
export declare const
|
|
114
|
+
export declare const readJsonFile: <T = unknown>(path: string, options?: IReadJsonFileOptions<T>) => Promise<T>;
|
|
91
115
|
/**
|
|
92
116
|
* Reads a binary file. An empty file reads as an empty Buffer.
|
|
93
117
|
* @param path The file's path.
|
|
94
118
|
* @returns A promise that resolves to the content.
|
|
95
119
|
* @example
|
|
120
|
+
* import { readBufferFile } from 'jg-fs';
|
|
121
|
+
*
|
|
96
122
|
* const image = await readBufferFile('avatar.png');
|
|
97
123
|
* @throws
|
|
98
124
|
* - NOT_A_FILE: if the path doesn't exist or is not a file
|
|
@@ -104,6 +130,8 @@ export declare const readBufferFile: (path: string) => Promise<Buffer>;
|
|
|
104
130
|
* @param destPath Where to copy it.
|
|
105
131
|
* @returns A promise that resolves once the copy exists.
|
|
106
132
|
* @example
|
|
133
|
+
* import { copyFile } from 'jg-fs';
|
|
134
|
+
*
|
|
107
135
|
* await copyFile('config.example.json', 'config.json');
|
|
108
136
|
* @throws
|
|
109
137
|
* - NOT_A_FILE: if the srcPath doesn't exist or is not a file
|
|
@@ -114,6 +142,8 @@ export declare const copyFile: (srcPath: string, destPath: string) => Promise<vo
|
|
|
114
142
|
* @param path The file's path.
|
|
115
143
|
* @returns A promise that resolves once the file is gone.
|
|
116
144
|
* @example
|
|
145
|
+
* import { deleteFile } from 'jg-fs';
|
|
146
|
+
*
|
|
117
147
|
* await deleteFile('cache.json');
|
|
118
148
|
* @throws
|
|
119
149
|
* - NOT_A_FILE: if the path doesn't exist or is not a file
|
|
@@ -125,6 +155,8 @@ export declare const deleteFile: (path: string) => Promise<void>;
|
|
|
125
155
|
* @param path Where to create the link.
|
|
126
156
|
* @returns A promise that resolves once the link exists.
|
|
127
157
|
* @example
|
|
158
|
+
* import { createFileSymLink } from 'jg-fs';
|
|
159
|
+
*
|
|
128
160
|
* await createFileSymLink('config.production.json', 'config.json');
|
|
129
161
|
* @throws
|
|
130
162
|
* - NON_EXISTENT_FILE: if the target file does not exist
|
|
@@ -143,8 +175,11 @@ export declare const createFileSymLink: (target: string, path: string) => Promis
|
|
|
143
175
|
* @param mode The permission bits of the written file, such as `0o600`.
|
|
144
176
|
* @returns A promise that resolves once the file is written and flushed.
|
|
145
177
|
* @example
|
|
178
|
+
* import { writeFileAtomic } from 'jg-fs';
|
|
179
|
+
*
|
|
180
|
+
* const state = { step: 2 };
|
|
146
181
|
* await writeFileAtomic('state.json', JSON.stringify(state)); // readers never see a partial file
|
|
147
|
-
* await writeFileAtomic('secrets.env',
|
|
182
|
+
* await writeFileAtomic('secrets.env', 'TOKEN=abc\n', 0o600); // only the owner can read it
|
|
148
183
|
*/
|
|
149
184
|
export declare const writeFileAtomic: (path: string, data: string | NodeJS.ArrayBufferView, mode?: number) => Promise<void>;
|
|
150
185
|
/**
|
|
@@ -156,7 +191,10 @@ export declare const writeFileAtomic: (path: string, data: string | NodeJS.Array
|
|
|
156
191
|
* @param maxBytes The largest size accepted, in bytes.
|
|
157
192
|
* @returns A promise that resolves to the content.
|
|
158
193
|
* @example
|
|
159
|
-
*
|
|
194
|
+
* import { join } from 'node:path';
|
|
195
|
+
* import { readBoundedFile } from 'jg-fs';
|
|
196
|
+
*
|
|
197
|
+
* const upload = await readBoundedFile(join('uploads', 'avatar.png'), 5 * 1024 * 1024); // at most 5 MB
|
|
160
198
|
* @throws
|
|
161
199
|
* - NOT_A_FILE: if the path doesn't exist, its final component is a symbolic link, or it isn't a regular file
|
|
162
200
|
* - FILE_TOO_LARGE: if the file holds more than maxBytes bytes
|
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { copyFile as copyFileNode, mkdir, open, readFile as readFileNode, rename, rm, symlink, unlink, writeFile as writeFileNode, } from 'node:fs/promises';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { Exception } from 'jg-exception';
|
|
6
|
-
import { assertBufferData, assertTextData, BOUNDED_READ_CHUNK_SIZE, ERRORS, fileTooLarge, notAFile,
|
|
6
|
+
import { assertBufferData, assertTextData, BOUNDED_READ_CHUNK_SIZE, ERRORS, fileTooLarge, notAFile, parseJsonContent, serializeJsonData, } from '../../shared/index.js';
|
|
7
7
|
import { getPathElement } from '../general/index.js';
|
|
8
8
|
import { openWithoutFollowing, readPermissionBits, syncDirectory } from './utilities.js';
|
|
9
9
|
/**
|
|
@@ -11,6 +11,8 @@ import { openWithoutFollowing, readPermissionBits, syncDirectory } from './utili
|
|
|
11
11
|
* @param path The path.
|
|
12
12
|
* @returns A promise that resolves to whether the path is a file.
|
|
13
13
|
* @example
|
|
14
|
+
* import { isFile } from 'jg-fs';
|
|
15
|
+
*
|
|
14
16
|
* await isFile('package.json'); // true
|
|
15
17
|
*/
|
|
16
18
|
export const isFile = async (path) => (await getPathElement(path))?.isFile ?? false;
|
|
@@ -21,6 +23,9 @@ export const isFile = async (path) => (await getPathElement(path))?.isFile ?? fa
|
|
|
21
23
|
* @param options Node.js's writeFile options, such as the encoding or mode.
|
|
22
24
|
* @returns A promise that resolves once the file is written.
|
|
23
25
|
* @example
|
|
26
|
+
* import { writeFile } from 'jg-fs';
|
|
27
|
+
*
|
|
28
|
+
* const csv = 'id,name\n1,Ada\n';
|
|
24
29
|
* await writeFile('out/report.csv', csv, { encoding: 'utf-8' });
|
|
25
30
|
*/
|
|
26
31
|
export const writeFile = async (path, data, options) => {
|
|
@@ -34,6 +39,8 @@ export const writeFile = async (path, data, options) => {
|
|
|
34
39
|
* @param data The text.
|
|
35
40
|
* @returns A promise that resolves once the file is written.
|
|
36
41
|
* @example
|
|
42
|
+
* import { writeTextFile } from 'jg-fs';
|
|
43
|
+
*
|
|
37
44
|
* await writeTextFile('notes/today.md', '# Today');
|
|
38
45
|
* @throws
|
|
39
46
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a string
|
|
@@ -49,12 +56,14 @@ export const writeTextFile = async (path, data) => {
|
|
|
49
56
|
* @param space The indentation for stringified values.
|
|
50
57
|
* @returns A promise that resolves once the file is written.
|
|
51
58
|
* @example
|
|
52
|
-
*
|
|
59
|
+
* import { writeJsonFile } from 'jg-fs';
|
|
60
|
+
*
|
|
61
|
+
* await writeJsonFile('config.json', { port: 8080 });
|
|
53
62
|
* @throws
|
|
54
63
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the JSON content cannot be stringified or is empty
|
|
55
64
|
*/
|
|
56
|
-
export const
|
|
57
|
-
await writeFile(path,
|
|
65
|
+
export const writeJsonFile = async (path, data, space = 2) => {
|
|
66
|
+
await writeFile(path, serializeJsonData(path, data, space), { encoding: 'utf-8' });
|
|
58
67
|
};
|
|
59
68
|
/**
|
|
60
69
|
* Writes a binary file. An empty Buffer writes an empty file.
|
|
@@ -62,6 +71,9 @@ export const writeJSONFile = async (path, data, space = 2) => {
|
|
|
62
71
|
* @param data The content.
|
|
63
72
|
* @returns A promise that resolves once the file is written.
|
|
64
73
|
* @example
|
|
74
|
+
* import { writeBufferFile } from 'jg-fs';
|
|
75
|
+
*
|
|
76
|
+
* const imageBuffer = Buffer.from('iVBORw0KGgo=', 'base64');
|
|
65
77
|
* await writeBufferFile('avatar.png', imageBuffer);
|
|
66
78
|
* @throws
|
|
67
79
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a Buffer
|
|
@@ -81,27 +93,39 @@ export async function readFile(path, options = null) {
|
|
|
81
93
|
* @param path The file's path.
|
|
82
94
|
* @returns A promise that resolves to the text.
|
|
83
95
|
* @example
|
|
96
|
+
* import { readTextFile } from 'jg-fs';
|
|
97
|
+
*
|
|
84
98
|
* const markdown = await readTextFile('README.md');
|
|
85
99
|
* @throws
|
|
86
100
|
* - NOT_A_FILE: if the path doesn't exist or is not a file
|
|
87
101
|
*/
|
|
88
102
|
export const readTextFile = (path) => readFile(path, { encoding: 'utf-8' });
|
|
89
103
|
/**
|
|
90
|
-
* Reads and parses a JSON file.
|
|
104
|
+
* Reads and parses a JSON file, and validates it with the schema when there is one. Without a
|
|
105
|
+
* schema, the value is `unknown`, or the type you pass.
|
|
91
106
|
* @param path The file's path.
|
|
107
|
+
* @param options The schema, such as a Zod schema, whose errors propagate unchanged.
|
|
92
108
|
* @returns A promise that resolves to the parsed value.
|
|
93
109
|
* @example
|
|
94
|
-
*
|
|
110
|
+
* import { readJsonFile } from 'jg-fs';
|
|
111
|
+
*
|
|
112
|
+
* const config = await readJsonFile('config.json', { schema: Config }); // Config's type
|
|
113
|
+
* @example
|
|
114
|
+
* import { readJsonFile } from 'jg-fs';
|
|
115
|
+
*
|
|
116
|
+
* const value = await readJsonFile('data.json'); // unknown
|
|
95
117
|
* @throws
|
|
96
118
|
* - NOT_A_FILE: if the path doesn't exist or is not a file
|
|
97
119
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the file's JSON content cannot be parsed
|
|
98
120
|
*/
|
|
99
|
-
export const
|
|
121
|
+
export const readJsonFile = async (path, options = {}) => parseJsonContent(path, await readTextFile(path), options.schema);
|
|
100
122
|
/**
|
|
101
123
|
* Reads a binary file. An empty file reads as an empty Buffer.
|
|
102
124
|
* @param path The file's path.
|
|
103
125
|
* @returns A promise that resolves to the content.
|
|
104
126
|
* @example
|
|
127
|
+
* import { readBufferFile } from 'jg-fs';
|
|
128
|
+
*
|
|
105
129
|
* const image = await readBufferFile('avatar.png');
|
|
106
130
|
* @throws
|
|
107
131
|
* - NOT_A_FILE: if the path doesn't exist or is not a file
|
|
@@ -113,6 +137,8 @@ export const readBufferFile = (path) => readFile(path, null);
|
|
|
113
137
|
* @param destPath Where to copy it.
|
|
114
138
|
* @returns A promise that resolves once the copy exists.
|
|
115
139
|
* @example
|
|
140
|
+
* import { copyFile } from 'jg-fs';
|
|
141
|
+
*
|
|
116
142
|
* await copyFile('config.example.json', 'config.json');
|
|
117
143
|
* @throws
|
|
118
144
|
* - NOT_A_FILE: if the srcPath doesn't exist or is not a file
|
|
@@ -128,6 +154,8 @@ export const copyFile = async (srcPath, destPath) => {
|
|
|
128
154
|
* @param path The file's path.
|
|
129
155
|
* @returns A promise that resolves once the file is gone.
|
|
130
156
|
* @example
|
|
157
|
+
* import { deleteFile } from 'jg-fs';
|
|
158
|
+
*
|
|
131
159
|
* await deleteFile('cache.json');
|
|
132
160
|
* @throws
|
|
133
161
|
* - NOT_A_FILE: if the path doesn't exist or is not a file
|
|
@@ -144,6 +172,8 @@ export const deleteFile = async (path) => {
|
|
|
144
172
|
* @param path Where to create the link.
|
|
145
173
|
* @returns A promise that resolves once the link exists.
|
|
146
174
|
* @example
|
|
175
|
+
* import { createFileSymLink } from 'jg-fs';
|
|
176
|
+
*
|
|
147
177
|
* await createFileSymLink('config.production.json', 'config.json');
|
|
148
178
|
* @throws
|
|
149
179
|
* - NON_EXISTENT_FILE: if the target file does not exist
|
|
@@ -152,7 +182,9 @@ export const deleteFile = async (path) => {
|
|
|
152
182
|
export const createFileSymLink = async (target, path) => {
|
|
153
183
|
const element = await getPathElement(target);
|
|
154
184
|
if (element === null) {
|
|
155
|
-
throw new Exception(`The target file '${target}' does not exist.`,
|
|
185
|
+
throw new Exception(`The target file '${target}' does not exist.`, {
|
|
186
|
+
code: ERRORS.NON_EXISTENT_FILE,
|
|
187
|
+
});
|
|
156
188
|
}
|
|
157
189
|
if (!element.isFile) {
|
|
158
190
|
throw notAFile(target);
|
|
@@ -171,8 +203,11 @@ export const createFileSymLink = async (target, path) => {
|
|
|
171
203
|
* @param mode The permission bits of the written file, such as `0o600`.
|
|
172
204
|
* @returns A promise that resolves once the file is written and flushed.
|
|
173
205
|
* @example
|
|
206
|
+
* import { writeFileAtomic } from 'jg-fs';
|
|
207
|
+
*
|
|
208
|
+
* const state = { step: 2 };
|
|
174
209
|
* await writeFileAtomic('state.json', JSON.stringify(state)); // readers never see a partial file
|
|
175
|
-
* await writeFileAtomic('secrets.env',
|
|
210
|
+
* await writeFileAtomic('secrets.env', 'TOKEN=abc\n', 0o600); // only the owner can read it
|
|
176
211
|
*/
|
|
177
212
|
export const writeFileAtomic = async (path, data, mode) => {
|
|
178
213
|
const directory = dirname(path);
|
|
@@ -214,7 +249,10 @@ export const writeFileAtomic = async (path, data, mode) => {
|
|
|
214
249
|
* @param maxBytes The largest size accepted, in bytes.
|
|
215
250
|
* @returns A promise that resolves to the content.
|
|
216
251
|
* @example
|
|
217
|
-
*
|
|
252
|
+
* import { join } from 'node:path';
|
|
253
|
+
* import { readBoundedFile } from 'jg-fs';
|
|
254
|
+
*
|
|
255
|
+
* const upload = await readBoundedFile(join('uploads', 'avatar.png'), 5 * 1024 * 1024); // at most 5 MB
|
|
218
256
|
* @throws
|
|
219
257
|
* - NOT_A_FILE: if the path doesn't exist, its final component is a symbolic link, or it isn't a regular file
|
|
220
258
|
* - FILE_TOO_LARGE: if the file holds more than maxBytes bytes
|