@thi.ng/file-io 2.1.37 → 2.2.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/CHANGELOG.md +11 -1
- package/README.md +53 -3
- package/copy.d.ts +4 -0
- package/copy.js +15 -0
- package/delete.d.ts +11 -0
- package/delete.js +41 -3
- package/ext.d.ts +1 -1
- package/file-chunks.js +5 -3
- package/files.js +6 -2
- package/hash.js +2 -1
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/json.d.ts +24 -0
- package/json.js +11 -2
- package/mask.d.ts +43 -1
- package/mask.js +18 -1
- package/package.json +17 -9
- package/read.d.ts +7 -0
- package/read.js +10 -2
- package/temp.d.ts +1 -1
- package/temp.js +3 -2
- package/text.d.ts +17 -0
- package/text.js +18 -3
- package/watch.d.ts +5 -0
- package/watch.js +9 -7
- package/write.d.ts +10 -0
- package/write.js +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2025-
|
|
3
|
+
- **Last updated**: 2025-07-03T15:03:31Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -11,6 +11,16 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
11
11
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
12
12
|
and/or version bumps of transitive dependencies.
|
|
13
13
|
|
|
14
|
+
## [2.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/file-io@2.2.0) (2025-07-03)
|
|
15
|
+
|
|
16
|
+
#### 🚀 Features
|
|
17
|
+
|
|
18
|
+
- add makedPath() and mask registry fns ([74796f8](https://github.com/thi-ng/umbrella/commit/74796f8))
|
|
19
|
+
- add copyFile() fns (incl. async version) ([19ad5f3](https://github.com/thi-ng/umbrella/commit/19ad5f3))
|
|
20
|
+
- update logging calls to use masked paths only ([c25a2cb](https://github.com/thi-ng/umbrella/commit/c25a2cb))
|
|
21
|
+
- add async versions of various file fns ([eff47b7](https://github.com/thi-ng/umbrella/commit/eff47b7))
|
|
22
|
+
- add deleteFiles() and async versions ([1185911](https://github.com/thi-ng/umbrella/commit/1185911))
|
|
23
|
+
|
|
14
24
|
### [2.1.21](https://github.com/thi-ng/umbrella/tree/@thi.ng/file-io@2.1.21) (2025-01-14)
|
|
15
25
|
|
|
16
26
|
#### ♻️ Refactoring
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://mastodon.thi.ng/@toxi)
|
|
8
8
|
|
|
9
9
|
> [!NOTE]
|
|
10
|
-
> This is one of
|
|
10
|
+
> This is one of 209 standalone projects, maintained as part
|
|
11
11
|
> of the [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo
|
|
12
12
|
> and anti-framework.
|
|
13
13
|
>
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
> GitHub](https://github.com/sponsors/postspectacular). Thank you! ❤️
|
|
16
16
|
|
|
17
17
|
- [About](#about)
|
|
18
|
+
- [Available functions](#available-functions)
|
|
19
|
+
- [Files](#files)
|
|
20
|
+
- [Directories](#directories)
|
|
21
|
+
- [Logging](#logging)
|
|
18
22
|
- [Status](#status)
|
|
19
23
|
- [Installation](#installation)
|
|
20
24
|
- [Dependencies](#dependencies)
|
|
@@ -25,12 +29,58 @@
|
|
|
25
29
|
|
|
26
30
|
## About
|
|
27
31
|
|
|
28
|
-
Assorted file I/O utils (
|
|
32
|
+
Assorted file I/O utils (w/ logging support) for NodeJS/Bun.
|
|
29
33
|
|
|
30
34
|
Most functions in this package have optional support for the
|
|
31
35
|
[`ILogger`](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
|
|
32
36
|
logging interface.
|
|
33
37
|
|
|
38
|
+
## Available functions
|
|
39
|
+
|
|
40
|
+
### Files
|
|
41
|
+
|
|
42
|
+
- [bufferHash](https://docs.thi.ng/umbrella/file-io/functions/bufferHash.html)
|
|
43
|
+
- [copyFile](https://docs.thi.ng/umbrella/file-io/functions/copyFile.html)
|
|
44
|
+
- [copyFileAsync](https://docs.thi.ng/umbrella/file-io/functions/copyFileAsync.html)
|
|
45
|
+
- [createTempFile](https://docs.thi.ng/umbrella/file-io/functions/createTempFile.html)
|
|
46
|
+
- [deleteFile](https://docs.thi.ng/umbrella/file-io/functions/deleteFile.html)
|
|
47
|
+
- [deleteFileAsync](https://docs.thi.ng/umbrella/file-io/functions/deleteFileAsync.html)
|
|
48
|
+
- [deleteFiles](https://docs.thi.ng/umbrella/file-io/functions/deleteFiles.html)
|
|
49
|
+
- [deleteFilesAsync](https://docs.thi.ng/umbrella/file-io/functions/deleteFilesAsync.html)
|
|
50
|
+
- [files](https://docs.thi.ng/umbrella/file-io/functions/files.html)
|
|
51
|
+
- [fileChunks](https://docs.thi.ng/umbrella/file-io/functions/fileChunks.html)
|
|
52
|
+
- [fileExt](https://docs.thi.ng/umbrella/file-io/functions/fileExt.html)
|
|
53
|
+
- [fileHash](https://docs.thi.ng/umbrella/file-io/functions/fileHash.html)
|
|
54
|
+
- [fileWatcher](https://docs.thi.ng/umbrella/file-io/functions/fileWatcher.html)
|
|
55
|
+
- [readBinary](https://docs.thi.ng/umbrella/file-io/functions/readBinary.html)
|
|
56
|
+
- [readBinaryAsync](https://docs.thi.ng/umbrella/file-io/functions/readBinaryAsync.html)
|
|
57
|
+
- [readJSON](https://docs.thi.ng/umbrella/file-io/functions/readJSON.html)
|
|
58
|
+
- [readJSONAsync](https://docs.thi.ng/umbrella/file-io/functions/readJSONAsync.html)
|
|
59
|
+
- [readText](https://docs.thi.ng/umbrella/file-io/functions/readText.html)
|
|
60
|
+
- [readTextAsync](https://docs.thi.ng/umbrella/file-io/functions/readTextAsync.html)
|
|
61
|
+
- [streamHash](https://docs.thi.ng/umbrella/file-io/functions/streamHash.html)
|
|
62
|
+
- [tempFilePath](https://docs.thi.ng/umbrella/file-io/functions/tempFilePath.html)
|
|
63
|
+
- [writeFile](https://docs.thi.ng/umbrella/file-io/functions/writeFile.html)
|
|
64
|
+
- [writeFileAsync](https://docs.thi.ng/umbrella/file-io/functions/writeFileAsync.html)
|
|
65
|
+
- [writeJSON](https://docs.thi.ng/umbrella/file-io/functions/writeJSON.html)
|
|
66
|
+
- [writeJSONAsync](https://docs.thi.ng/umbrella/file-io/functions/writeJSONAsync.html)
|
|
67
|
+
- [writeText](https://docs.thi.ng/umbrella/file-io/functions/writeText.html)
|
|
68
|
+
- [writeTextAsync](https://docs.thi.ng/umbrella/file-io/functions/writeTextAsync.html)
|
|
69
|
+
|
|
70
|
+
### Directories
|
|
71
|
+
|
|
72
|
+
- [deleteDir](https://docs.thi.ng/umbrella/file-io/functions/deleteDir.html)
|
|
73
|
+
- [dirs](https://docs.thi.ng/umbrella/file-io/functions/dirs.html)
|
|
74
|
+
- [ensureDir](https://docs.thi.ng/umbrella/file-io/functions/ensureDir.html)
|
|
75
|
+
- [ensureDirForFile](https://docs.thi.ng/umbrella/file-io/functions/ensureDirForFile.html)
|
|
76
|
+
- [isDirectory](https://docs.thi.ng/umbrella/file-io/functions/isDirectory.html)
|
|
77
|
+
|
|
78
|
+
### Logging
|
|
79
|
+
|
|
80
|
+
- [maskedPath](https://docs.thi.ng/umbrella/file-io/functions/maskedPath.html)
|
|
81
|
+
- [addPathMask](https://docs.thi.ng/umbrella/file-io/functions/addPathMask.html)
|
|
82
|
+
- [setPathMasks](https://docs.thi.ng/umbrella/file-io/functions/setPathMasks.html)
|
|
83
|
+
|
|
34
84
|
## Status
|
|
35
85
|
|
|
36
86
|
**STABLE** - used in production
|
|
@@ -55,7 +105,7 @@ For Node.js REPL:
|
|
|
55
105
|
const fio = await import("@thi.ng/file-io");
|
|
56
106
|
```
|
|
57
107
|
|
|
58
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 2.
|
|
108
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 2.44 KB
|
|
59
109
|
|
|
60
110
|
## Dependencies
|
|
61
111
|
|
package/copy.d.ts
ADDED
package/copy.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { copyFileSync } from "node:fs";
|
|
2
|
+
import { copyFile as $copy } from "node:fs/promises";
|
|
3
|
+
import { maskedPath } from "./mask.js";
|
|
4
|
+
const copyFile = (src, dest, logger) => {
|
|
5
|
+
logger?.debug(maskedPath(`copying file: ${src} \u2192 ${dest}`));
|
|
6
|
+
copyFileSync(src, dest);
|
|
7
|
+
};
|
|
8
|
+
const copyFileAsync = (src, dest, logger) => {
|
|
9
|
+
logger?.debug(maskedPath(`copying file: ${src} \u2192 ${dest}`));
|
|
10
|
+
return $copy(src, dest);
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
copyFile,
|
|
14
|
+
copyFileAsync
|
|
15
|
+
};
|
package/delete.d.ts
CHANGED
|
@@ -9,6 +9,17 @@ import type { ILogger } from "@thi.ng/logger";
|
|
|
9
9
|
* @param dryRun
|
|
10
10
|
*/
|
|
11
11
|
export declare const deleteFile: (path: string, logger?: ILogger, dryRun?: boolean) => void;
|
|
12
|
+
export declare const deleteFileAsync: (path: string, logger?: ILogger, dryRun?: boolean) => Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Deletes multiple files via {@link deleteFile}. Any errors are caught and
|
|
15
|
+
* logged, but ignored otherwise. If `dryRun` is true, files WON'T be deleted.
|
|
16
|
+
*
|
|
17
|
+
* @param paths
|
|
18
|
+
* @param logger
|
|
19
|
+
* @param dryRun
|
|
20
|
+
*/
|
|
21
|
+
export declare const deleteFiles: (paths: Iterable<string>, logger?: ILogger, dryRun?: boolean) => void;
|
|
22
|
+
export declare const deleteFilesAsync: (paths: Iterable<string>, logger?: ILogger, dryRun?: boolean) => Promise<void>;
|
|
12
23
|
/**
|
|
13
24
|
* Like {@link deleteFile}, but attempts to recursively remove an entire
|
|
14
25
|
* directory at given path.
|
package/delete.js
CHANGED
|
@@ -1,15 +1,53 @@
|
|
|
1
1
|
import { rmSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { maskedPath } from "./mask.js";
|
|
2
4
|
const deleteFile = (path, logger, dryRun = false) => {
|
|
3
|
-
logger?.info(
|
|
5
|
+
logger?.info(
|
|
6
|
+
`${dryRun ? "[dryrun] " : ""}deleting file:`,
|
|
7
|
+
maskedPath(path)
|
|
8
|
+
);
|
|
4
9
|
if (dryRun) return;
|
|
5
10
|
unlinkSync(path);
|
|
6
11
|
};
|
|
12
|
+
const deleteFileAsync = async (path, logger, dryRun = false) => {
|
|
13
|
+
logger?.info(
|
|
14
|
+
`${dryRun ? "[dryrun] " : ""}deleting file:`,
|
|
15
|
+
maskedPath(path)
|
|
16
|
+
);
|
|
17
|
+
if (dryRun) return;
|
|
18
|
+
return unlink(path);
|
|
19
|
+
};
|
|
20
|
+
const deleteFiles = (paths, logger, dryRun = false) => {
|
|
21
|
+
for (let path of paths) {
|
|
22
|
+
try {
|
|
23
|
+
deleteFile(path, logger, dryRun);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
logger?.warn(
|
|
26
|
+
"error deleting file:",
|
|
27
|
+
maskedPath(e.message)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const deleteFilesAsync = async (paths, logger, dryRun = false) => {
|
|
33
|
+
const promises = [];
|
|
34
|
+
for (let path of paths) {
|
|
35
|
+
promises.push(deleteFileAsync(path, logger, dryRun));
|
|
36
|
+
}
|
|
37
|
+
await Promise.allSettled(promises);
|
|
38
|
+
};
|
|
7
39
|
const deleteDir = (path, logger, dryRun = false) => {
|
|
8
|
-
logger?.info(
|
|
40
|
+
logger?.info(
|
|
41
|
+
`${dryRun ? "[dryrun] " : ""}deleting directory:`,
|
|
42
|
+
maskedPath(path)
|
|
43
|
+
);
|
|
9
44
|
if (dryRun) return;
|
|
10
45
|
rmSync(path, { recursive: true, force: true });
|
|
11
46
|
};
|
|
12
47
|
export {
|
|
13
48
|
deleteDir,
|
|
14
|
-
deleteFile
|
|
49
|
+
deleteFile,
|
|
50
|
+
deleteFileAsync,
|
|
51
|
+
deleteFiles,
|
|
52
|
+
deleteFilesAsync
|
|
15
53
|
};
|
package/ext.d.ts
CHANGED
package/file-chunks.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { U32 } from "@thi.ng/hex";
|
|
2
2
|
import { open } from "node:fs/promises";
|
|
3
|
+
import { maskedPath } from "./mask.js";
|
|
3
4
|
async function* fileChunks(path, opts = {}) {
|
|
4
5
|
let { size = 1024, start = 0, end = Infinity, logger } = opts;
|
|
5
|
-
|
|
6
|
+
const mpath = maskedPath(path);
|
|
7
|
+
logger?.debug(`start reading file chunks (size: 0x${size}):`, mpath);
|
|
6
8
|
let fd = void 0;
|
|
7
9
|
try {
|
|
8
10
|
fd = await open(path, "r");
|
|
9
11
|
while (start < end) {
|
|
10
|
-
logger
|
|
12
|
+
logger?.debug(
|
|
11
13
|
`reading chunk: 0x${U32(start)} - 0x${U32(
|
|
12
14
|
start + size - 1
|
|
13
|
-
)} (${
|
|
15
|
+
)} (${mpath})`
|
|
14
16
|
);
|
|
15
17
|
const { buffer, bytesRead } = await fd.read({
|
|
16
18
|
buffer: Buffer.alloc(size),
|
package/files.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readdirSync } from "node:fs";
|
|
|
2
2
|
import { sep } from "node:path";
|
|
3
3
|
import { isDirectory } from "./dir.js";
|
|
4
4
|
import { __ensurePred } from "./internal/ensure.js";
|
|
5
|
+
import { maskedPath } from "./mask.js";
|
|
5
6
|
const files = (dir, match = "", maxDepth = Infinity, logger) => __files(dir, match, logger, maxDepth, 0);
|
|
6
7
|
function* __files(dir, match = "", logger, maxDepth = Infinity, depth = 0) {
|
|
7
8
|
if (depth >= maxDepth) return;
|
|
@@ -15,7 +16,7 @@ function* __files(dir, match = "", logger, maxDepth = Infinity, depth = 0) {
|
|
|
15
16
|
yield curr;
|
|
16
17
|
}
|
|
17
18
|
} catch (e) {
|
|
18
|
-
logger
|
|
19
|
+
__error(logger, f, e);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -31,10 +32,13 @@ function* __dirs(dir, match = "", logger, maxDepth = Infinity, depth = 0) {
|
|
|
31
32
|
yield* __dirs(curr, match, logger, maxDepth, depth + 1);
|
|
32
33
|
}
|
|
33
34
|
} catch (e) {
|
|
34
|
-
logger
|
|
35
|
+
__error(logger, f, e);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
}
|
|
39
|
+
const __error = (logger, path, e) => logger?.warn(
|
|
40
|
+
`ignoring: ${maskedPath(path)} (${maskedPath(e.message)})`
|
|
41
|
+
);
|
|
38
42
|
export {
|
|
39
43
|
dirs,
|
|
40
44
|
files
|
package/hash.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
|
+
import { maskedPath } from "./mask.js";
|
|
3
4
|
const fileHash = async (path, logger, algo = "sha256") => {
|
|
4
|
-
logger?.info("reading file:", path);
|
|
5
|
+
logger?.info("reading file:", maskedPath(path));
|
|
5
6
|
return await streamHash(createReadStream(path), logger, algo);
|
|
6
7
|
};
|
|
7
8
|
const streamHash = async (src, logger, algo = "sha256") => {
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/json.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import type { Fn3, Maybe, Nullable, NumOrString } from "@thi.ng/api";
|
|
2
2
|
import type { ILogger } from "@thi.ng/logger";
|
|
3
|
+
/**
|
|
4
|
+
* Reads given file as UTF-8 and parses result as JSON of type `T`.
|
|
5
|
+
*
|
|
6
|
+
* @param path
|
|
7
|
+
* @param logger
|
|
8
|
+
*/
|
|
3
9
|
export declare const readJSON: <T = any>(path: string, logger?: ILogger) => T;
|
|
10
|
+
/**
|
|
11
|
+
* Async version of {@link readJSON}.
|
|
12
|
+
*
|
|
13
|
+
* @param path
|
|
14
|
+
* @param logger
|
|
15
|
+
*/
|
|
16
|
+
export declare const readJSONAsync: <T = any>(path: string, logger?: ILogger) => Promise<T>;
|
|
4
17
|
/**
|
|
5
18
|
* Serializes `obj` to JSON and writes result to UTF-8 file `path`. See
|
|
6
19
|
* {@link writeText} for more details.
|
|
@@ -17,4 +30,15 @@ export declare const readJSON: <T = any>(path: string, logger?: ILogger) => T;
|
|
|
17
30
|
* @param dryRun
|
|
18
31
|
*/
|
|
19
32
|
export declare const writeJSON: (path: string, obj: any, replacer?: Fn3<any, string, any, any> | Nullable<NumOrString[]>, space?: Maybe<NumOrString>, logger?: ILogger, dryRun?: boolean) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Async version of {@link writeJSON}.
|
|
35
|
+
*
|
|
36
|
+
* @param path
|
|
37
|
+
* @param obj
|
|
38
|
+
* @param replacer
|
|
39
|
+
* @param space
|
|
40
|
+
* @param logger
|
|
41
|
+
* @param dryRun
|
|
42
|
+
*/
|
|
43
|
+
export declare const writeJSONAsync: (path: string, obj: any, replacer?: Fn3<any, string, any, any> | Nullable<NumOrString[]>, space?: Maybe<NumOrString>, logger?: ILogger, dryRun?: boolean) => Promise<void>;
|
|
20
44
|
//# sourceMappingURL=json.d.ts.map
|
package/json.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import { readText, writeText } from "./text.js";
|
|
1
|
+
import { readText, readTextAsync, writeText, writeTextAsync } from "./text.js";
|
|
2
2
|
const readJSON = (path, logger) => JSON.parse(readText(path, logger));
|
|
3
|
+
const readJSONAsync = async (path, logger) => JSON.parse(await readTextAsync(path, logger));
|
|
3
4
|
const writeJSON = (path, obj, replacer, space, logger, dryRun = false) => writeText(
|
|
4
5
|
path,
|
|
5
6
|
JSON.stringify(obj, replacer, space) + "\n",
|
|
6
7
|
logger,
|
|
7
8
|
dryRun
|
|
8
9
|
);
|
|
10
|
+
const writeJSONAsync = (path, obj, replacer, space, logger, dryRun = false) => writeTextAsync(
|
|
11
|
+
path,
|
|
12
|
+
JSON.stringify(obj, replacer, space) + "\n",
|
|
13
|
+
logger,
|
|
14
|
+
dryRun
|
|
15
|
+
);
|
|
9
16
|
export {
|
|
10
17
|
readJSON,
|
|
11
|
-
|
|
18
|
+
readJSONAsync,
|
|
19
|
+
writeJSON,
|
|
20
|
+
writeJSONAsync
|
|
12
21
|
};
|
package/mask.d.ts
CHANGED
|
@@ -1,9 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds given path pattern and its replacement string to registered path masks.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* By default only the `$HOME` dir will be masked (if that env var is defined).
|
|
6
|
+
*
|
|
7
|
+
* Also see {@link maskedPath} and {@link setPathMasks}.
|
|
8
|
+
*
|
|
9
|
+
* @param pattern
|
|
10
|
+
* @param mask
|
|
11
|
+
*/
|
|
12
|
+
export declare const addPathMask: (pattern: string | RegExp, mask?: string) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Overrides ALL registered path masks with those given.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Also see {@link maskedPath} and {@link addPathMasks}.
|
|
18
|
+
*
|
|
19
|
+
* @param masks
|
|
20
|
+
*/
|
|
21
|
+
export declare const setPathMasks: (masks: [RegExp, string][]) => [RegExp, string][];
|
|
22
|
+
/**
|
|
23
|
+
* Iteratively applies all registered path masks to given `path` and returns
|
|
24
|
+
* result.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* This function is used by all logging functions in this package. By default
|
|
28
|
+
* only the `$HOME` dir will be masked (if that env var is defined).
|
|
29
|
+
*
|
|
30
|
+
* See {@link addPathMask}, {@link setPathMasks}.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* maskedPath("/Users/asterix/foo/bar.txt");
|
|
35
|
+
* // "~/foo/bar.txt"
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @param path
|
|
39
|
+
*/
|
|
40
|
+
export declare const maskedPath: (path: string) => string;
|
|
1
41
|
/**
|
|
2
42
|
* Replaces `home` (default: `process.env.HOME`) sub-path with given `mask`
|
|
3
|
-
* (default: `~`).
|
|
43
|
+
* (default: `~`). Used by all path logging calls in this package.
|
|
4
44
|
*
|
|
5
45
|
* @param path
|
|
6
46
|
* @param home
|
|
47
|
+
*
|
|
48
|
+
* @deprecated use {@link maskedPath} instead
|
|
7
49
|
*/
|
|
8
50
|
export declare const maskHomeDir: (path: string, home?: string | undefined, mask?: string) => string;
|
|
9
51
|
//# sourceMappingURL=mask.d.ts.map
|
package/mask.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
|
+
import { isString } from "@thi.ng/checks/is-string";
|
|
2
|
+
let MASKS = [
|
|
3
|
+
[process.env.HOME ? new RegExp(process.env.HOME, "g") : /~/, "~"]
|
|
4
|
+
];
|
|
5
|
+
const addPathMask = (pattern, mask = "****") => {
|
|
6
|
+
MASKS.push([isString(pattern) ? new RegExp(pattern, "g") : pattern, mask]);
|
|
7
|
+
};
|
|
8
|
+
const setPathMasks = (masks) => MASKS = masks;
|
|
9
|
+
const maskedPath = (path) => {
|
|
10
|
+
for (let [re, mask] of MASKS) {
|
|
11
|
+
path = path.replace(re, mask);
|
|
12
|
+
}
|
|
13
|
+
return path;
|
|
14
|
+
};
|
|
1
15
|
const maskHomeDir = (path, home = process.env.HOME, mask = "~") => home ? path.replace(home, mask) : path;
|
|
2
16
|
export {
|
|
3
|
-
|
|
17
|
+
addPathMask,
|
|
18
|
+
maskHomeDir,
|
|
19
|
+
maskedPath,
|
|
20
|
+
setPathMasks
|
|
4
21
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/file-io",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Assorted file I/O utils (
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "Assorted file I/O utils (w/ logging support) for NodeJS/Bun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
7
7
|
"typings": "./index.d.ts",
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
"tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@thi.ng/api": "^8.11.
|
|
43
|
-
"@thi.ng/checks": "^3.7.
|
|
44
|
-
"@thi.ng/hex": "^2.3.
|
|
45
|
-
"@thi.ng/logger": "^3.1.
|
|
46
|
-
"@thi.ng/random": "^4.1.
|
|
42
|
+
"@thi.ng/api": "^8.11.29",
|
|
43
|
+
"@thi.ng/checks": "^3.7.9",
|
|
44
|
+
"@thi.ng/hex": "^2.3.73",
|
|
45
|
+
"@thi.ng/logger": "^3.1.10",
|
|
46
|
+
"@thi.ng/random": "^4.1.20"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^22.15.21",
|
|
@@ -53,14 +53,19 @@
|
|
|
53
53
|
},
|
|
54
54
|
"keywords": [
|
|
55
55
|
"async",
|
|
56
|
+
"delete",
|
|
57
|
+
"directory",
|
|
56
58
|
"file",
|
|
57
59
|
"hash",
|
|
60
|
+
"iterator",
|
|
58
61
|
"json",
|
|
59
62
|
"logger",
|
|
60
63
|
"no-browser",
|
|
61
64
|
"nodejs",
|
|
62
65
|
"stream",
|
|
63
|
-
"
|
|
66
|
+
"text",
|
|
67
|
+
"typescript",
|
|
68
|
+
"watch"
|
|
64
69
|
],
|
|
65
70
|
"publishConfig": {
|
|
66
71
|
"access": "public"
|
|
@@ -81,6 +86,9 @@
|
|
|
81
86
|
".": {
|
|
82
87
|
"default": "./index.js"
|
|
83
88
|
},
|
|
89
|
+
"./copy": {
|
|
90
|
+
"default": "./copy.js"
|
|
91
|
+
},
|
|
84
92
|
"./delete": {
|
|
85
93
|
"default": "./delete.js"
|
|
86
94
|
},
|
|
@@ -126,5 +134,5 @@
|
|
|
126
134
|
"status": "stable",
|
|
127
135
|
"year": 2022
|
|
128
136
|
},
|
|
129
|
-
"gitHead": "
|
|
137
|
+
"gitHead": "06e582f962a9cd3abb905e91d97d652e1d3bb971\n"
|
|
130
138
|
}
|
package/read.d.ts
CHANGED
|
@@ -6,4 +6,11 @@ import type { ILogger } from "@thi.ng/logger";
|
|
|
6
6
|
* @param logger
|
|
7
7
|
*/
|
|
8
8
|
export declare const readBinary: (path: string, logger?: ILogger) => Uint8Array<ArrayBuffer>;
|
|
9
|
+
/**
|
|
10
|
+
* Async version of {@link readBinary}.
|
|
11
|
+
*
|
|
12
|
+
* @param path
|
|
13
|
+
* @param logger
|
|
14
|
+
*/
|
|
15
|
+
export declare const readBinaryAsync: (path: string, logger?: ILogger) => Promise<Uint8Array<ArrayBufferLike>>;
|
|
9
16
|
//# sourceMappingURL=read.d.ts.map
|
package/read.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { maskedPath } from "./mask.js";
|
|
2
4
|
const readBinary = (path, logger) => {
|
|
3
|
-
logger?.debug("reading file:", path);
|
|
5
|
+
logger?.debug("reading file:", maskedPath(path));
|
|
4
6
|
const buf = readFileSync(path);
|
|
5
7
|
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
6
8
|
};
|
|
9
|
+
const readBinaryAsync = async (path, logger) => {
|
|
10
|
+
logger?.debug("reading file:", maskedPath(path));
|
|
11
|
+
const buf = await readFile(path);
|
|
12
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
13
|
+
};
|
|
7
14
|
export {
|
|
8
|
-
readBinary
|
|
15
|
+
readBinary,
|
|
16
|
+
readBinaryAsync
|
|
9
17
|
};
|
package/temp.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare const createTempFile: (body: string | TypedArray, logger?: ILogge
|
|
|
17
17
|
*
|
|
18
18
|
* @remarks
|
|
19
19
|
* If no `name` is given, constructs a random filename of `tmp-XXX` (16 random
|
|
20
|
-
* chars).
|
|
20
|
+
* chars). Default extension is an empty string.
|
|
21
21
|
*
|
|
22
22
|
* @param name
|
|
23
23
|
* @param ext
|
package/temp.js
CHANGED
|
@@ -4,14 +4,15 @@ import { realpathSync, writeFileSync } from "node:fs";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { sep } from "node:path";
|
|
6
6
|
import { ensureDirForFile } from "./dir.js";
|
|
7
|
+
import { maskedPath } from "./mask.js";
|
|
7
8
|
const createTempFile = (body, logger, name, ext) => {
|
|
8
9
|
const path = tempFilePath(name, ext);
|
|
9
|
-
logger?.
|
|
10
|
+
logger?.info("creating temp file:", maskedPath(path));
|
|
10
11
|
ensureDirForFile(path);
|
|
11
12
|
writeFileSync(path, body, isString(body) ? "utf-8" : void 0);
|
|
12
13
|
return path;
|
|
13
14
|
};
|
|
14
|
-
const tempFilePath = (name, ext = "") => realpathSync(tmpdir()) + sep +
|
|
15
|
+
const tempFilePath = (name = randomID(16, "tmp-"), ext = "") => realpathSync(tmpdir()) + sep + name + ext;
|
|
15
16
|
export {
|
|
16
17
|
createTempFile,
|
|
17
18
|
tempFilePath
|
package/text.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ import type { ILogger } from "@thi.ng/logger";
|
|
|
8
8
|
* @param encoding
|
|
9
9
|
*/
|
|
10
10
|
export declare const readText: (path: string, logger?: ILogger, encoding?: Extract<BufferEncoding, "ascii" | "latin1" | "utf-8" | "utf-16le" | "ucs-2">) => string;
|
|
11
|
+
/**
|
|
12
|
+
* Async version of {@link readText}.
|
|
13
|
+
*
|
|
14
|
+
* @param path
|
|
15
|
+
* @param logger
|
|
16
|
+
* @param encoding
|
|
17
|
+
*/
|
|
18
|
+
export declare const readTextAsync: (path: string, logger?: ILogger, encoding?: Extract<BufferEncoding, "ascii" | "latin1" | "utf-8" | "utf-16le" | "ucs-2">) => Promise<string>;
|
|
11
19
|
/**
|
|
12
20
|
* Writes `body` as UTF-8 file to given `path`. If `dryRun` is true (default:
|
|
13
21
|
* false), the file WON'T be written, however if a `logger` is provided then at
|
|
@@ -19,4 +27,13 @@ export declare const readText: (path: string, logger?: ILogger, encoding?: Extra
|
|
|
19
27
|
* @param dryRun
|
|
20
28
|
*/
|
|
21
29
|
export declare const writeText: (path: string, body: string | string[], logger?: ILogger, dryRun?: boolean) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Async version of {@link writeText}.
|
|
32
|
+
*
|
|
33
|
+
* @param path
|
|
34
|
+
* @param body
|
|
35
|
+
* @param logger
|
|
36
|
+
* @param dryRun
|
|
37
|
+
*/
|
|
38
|
+
export declare const writeTextAsync: (path: string, body: string | string[], logger?: ILogger, dryRun?: boolean) => Promise<void>;
|
|
22
39
|
//# sourceMappingURL=text.d.ts.map
|
package/text.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { isArray } from "@thi.ng/checks/is-array";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { maskedPath } from "./mask.js";
|
|
5
|
+
import { writeFile, writeFileAsync } from "./write.js";
|
|
4
6
|
const readText = (path, logger, encoding = "utf-8") => {
|
|
5
|
-
logger?.debug("reading file:", path);
|
|
7
|
+
logger?.debug("reading file:", maskedPath(path));
|
|
6
8
|
return readFileSync(path, encoding);
|
|
7
9
|
};
|
|
10
|
+
const readTextAsync = (path, logger, encoding = "utf-8") => {
|
|
11
|
+
logger?.debug("reading file:", maskedPath(path));
|
|
12
|
+
return readFile(path, encoding);
|
|
13
|
+
};
|
|
8
14
|
const writeText = (path, body, logger, dryRun = false) => writeFile(
|
|
9
15
|
path,
|
|
10
16
|
isArray(body) ? body.join("\n") : body,
|
|
@@ -12,7 +18,16 @@ const writeText = (path, body, logger, dryRun = false) => writeFile(
|
|
|
12
18
|
logger,
|
|
13
19
|
dryRun
|
|
14
20
|
);
|
|
21
|
+
const writeTextAsync = (path, body, logger, dryRun = false) => writeFileAsync(
|
|
22
|
+
path,
|
|
23
|
+
isArray(body) ? body.join("\n") : body,
|
|
24
|
+
"utf-8",
|
|
25
|
+
logger,
|
|
26
|
+
dryRun
|
|
27
|
+
);
|
|
15
28
|
export {
|
|
16
29
|
readText,
|
|
17
|
-
|
|
30
|
+
readTextAsync,
|
|
31
|
+
writeText,
|
|
32
|
+
writeTextAsync
|
|
18
33
|
};
|
package/watch.d.ts
CHANGED
|
@@ -44,5 +44,10 @@ export declare class Watcher implements IClear, INotify {
|
|
|
44
44
|
removeListener(id: string, fn: Listener<string>, scope?: any): boolean;
|
|
45
45
|
notify(event: Event<string>): boolean;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a new {@link Watcher} instance with given options.
|
|
49
|
+
*
|
|
50
|
+
* @param opts
|
|
51
|
+
*/
|
|
47
52
|
export declare const fileWatcher: (opts?: Partial<WatcherOpts>) => Watcher;
|
|
48
53
|
//# sourceMappingURL=watch.d.ts.map
|
package/watch.js
CHANGED
|
@@ -13,10 +13,11 @@ import {
|
|
|
13
13
|
} from "@thi.ng/api";
|
|
14
14
|
import { isString } from "@thi.ng/checks/is-string";
|
|
15
15
|
import { NULL_LOGGER } from "@thi.ng/logger/null";
|
|
16
|
-
import {
|
|
16
|
+
import { existsSync, watch } from "node:fs";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { isDirectory } from "./dir.js";
|
|
19
19
|
import { __ensurePred } from "./internal/ensure.js";
|
|
20
|
+
import { maskedPath } from "./mask.js";
|
|
20
21
|
const EVENT_ADDED = "added";
|
|
21
22
|
const EVENT_CHANGED = "changed";
|
|
22
23
|
const EVENT_REMOVED = "removed";
|
|
@@ -40,8 +41,8 @@ let Watcher = class {
|
|
|
40
41
|
if (this.watchers[path]) return false;
|
|
41
42
|
const isDir = isDirectory(path);
|
|
42
43
|
const pred = __ensurePred(opts?.ext || "");
|
|
43
|
-
this.opts.logger.debug(`adding watcher for
|
|
44
|
-
this.watchers[path] =
|
|
44
|
+
this.opts.logger.debug(`adding watcher for:`, maskedPath(path));
|
|
45
|
+
this.watchers[path] = watch(
|
|
45
46
|
path,
|
|
46
47
|
{ recursive: opts?.recursive !== false },
|
|
47
48
|
(event, currPath) => {
|
|
@@ -49,19 +50,20 @@ let Watcher = class {
|
|
|
49
50
|
currPath = isDir ? join(path, currPath) : path;
|
|
50
51
|
if (!pred(currPath)) return;
|
|
51
52
|
setTimeout(() => {
|
|
53
|
+
const mpath = maskedPath(currPath);
|
|
52
54
|
if (event === "change") {
|
|
53
|
-
this.opts.logger.info(`file changed: ${
|
|
55
|
+
this.opts.logger.info(`file changed: ${mpath}`);
|
|
54
56
|
this.notify({
|
|
55
57
|
id: EVENT_CHANGED,
|
|
56
58
|
value: currPath
|
|
57
59
|
});
|
|
58
60
|
} else if (!isDir || path === currPath) {
|
|
59
|
-
this.opts.logger.info(`file removed: ${
|
|
61
|
+
this.opts.logger.info(`file removed: ${mpath}`);
|
|
60
62
|
this.notify({ id: EVENT_REMOVED, value: path });
|
|
61
63
|
this.remove(path);
|
|
62
64
|
} else {
|
|
63
65
|
const id = existsSync(currPath) ? EVENT_ADDED : EVENT_REMOVED;
|
|
64
|
-
this.opts.logger.info(`file ${id}: ${
|
|
66
|
+
this.opts.logger.info(`file ${id}: ${mpath}`);
|
|
65
67
|
this.notify({ id, value: currPath });
|
|
66
68
|
}
|
|
67
69
|
}, this.opts.delay);
|
|
@@ -72,7 +74,7 @@ let Watcher = class {
|
|
|
72
74
|
remove(path) {
|
|
73
75
|
const watcher = this.watchers[path];
|
|
74
76
|
if (!watcher) return false;
|
|
75
|
-
this.opts.logger.debug(`removing watcher for
|
|
77
|
+
this.opts.logger.debug(`removing watcher for:`, maskedPath(path));
|
|
76
78
|
watcher.close();
|
|
77
79
|
delete this.watchers[path];
|
|
78
80
|
return true;
|
package/write.d.ts
CHANGED
|
@@ -13,4 +13,14 @@ import { type WriteFileOptions } from "node:fs";
|
|
|
13
13
|
* @param dryRun
|
|
14
14
|
*/
|
|
15
15
|
export declare const writeFile: (path: string, body: string | TypedArray, opts?: WriteFileOptions, logger?: ILogger, dryRun?: boolean) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Async version of {@link writeFile}.
|
|
18
|
+
*
|
|
19
|
+
* @param path
|
|
20
|
+
* @param body
|
|
21
|
+
* @param opts
|
|
22
|
+
* @param logger
|
|
23
|
+
* @param dryRun
|
|
24
|
+
*/
|
|
25
|
+
export declare const writeFileAsync: (path: string, body: string | TypedArray, opts?: WriteFileOptions, logger?: ILogger, dryRun?: boolean) => Promise<void>;
|
|
16
26
|
//# sourceMappingURL=write.d.ts.map
|
package/write.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { isString } from "@thi.ng/checks/is-string";
|
|
2
2
|
import { writeFileSync } from "node:fs";
|
|
3
|
+
import { writeFile as writeAsync } from "node:fs/promises";
|
|
3
4
|
import { ensureDirForFile } from "./dir.js";
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
import { maskedPath } from "./mask.js";
|
|
6
|
+
const writeFile = (path, body, opts, logger, dryRun = false) => __write(writeFileSync, path, body, opts, logger, dryRun);
|
|
7
|
+
const writeFileAsync = (path, body, opts, logger, dryRun = false) => __write(writeAsync, path, body, opts, logger, dryRun);
|
|
8
|
+
const __write = (writeFn, path, body, opts, logger, dryRun = false) => {
|
|
9
|
+
logger?.info(
|
|
10
|
+
`${dryRun ? "[dryrun] " : ""}writing file: ${maskedPath(path)}`
|
|
11
|
+
);
|
|
6
12
|
if (dryRun) return;
|
|
7
13
|
ensureDirForFile(path);
|
|
8
|
-
|
|
14
|
+
return writeFn(path, body, !opts && isString(body) ? "utf-8" : opts);
|
|
9
15
|
};
|
|
10
16
|
export {
|
|
11
|
-
writeFile
|
|
17
|
+
writeFile,
|
|
18
|
+
writeFileAsync
|
|
12
19
|
};
|