@openclaw/fs-safe 0.2.6 → 0.3.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 +17 -0
- package/README.md +21 -7
- package/dist/archive-deadline.d.ts +9 -0
- package/dist/archive-deadline.d.ts.map +1 -0
- package/dist/archive-deadline.js +67 -0
- package/dist/archive-file-io.d.ts +9 -0
- package/dist/archive-file-io.d.ts.map +1 -0
- package/dist/archive-file-io.js +11 -0
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +43 -3
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +213 -105
- package/dist/deny-mutations.d.ts +11 -0
- package/dist/deny-mutations.d.ts.map +1 -0
- package/dist/deny-mutations.js +102 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +5 -0
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +51 -4
- package/dist/opened-realpath.d.ts +3 -0
- package/dist/opened-realpath.d.ts.map +1 -0
- package/dist/opened-realpath.js +79 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +1 -0
- package/dist/pinned-write.js +3 -9
- package/dist/read-opened-file.d.ts +18 -0
- package/dist/read-opened-file.d.ts.map +1 -0
- package/dist/read-opened-file.js +15 -0
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +23 -3
- package/dist/root-impl.d.ts +18 -15
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +60 -100
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +16 -5
- package/dist/root.d.ts +1 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts +1 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +23 -3
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +9 -3
- package/dist/symlink-parents.js +2 -2
- package/docs/errors.md +2 -0
- package/docs/python-helper.md +3 -3
- package/docs/root.md +19 -9
- package/docs/secret-file.md +3 -2
- package/docs/security-model.md +4 -0
- package/docs/timing.md +1 -1
- package/docs/types.md +21 -9
- package/docs/writing.md +19 -1
- package/package.json +1 -1
package/dist/json.js
CHANGED
|
@@ -1,10 +1,49 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import fsSync from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
4
5
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
5
|
-
import { readRegularFile, readRegularFileSync } from "./regular-file.js";
|
|
6
|
+
import { readRegularFile, readRegularFileSync, statRegularFile } from "./regular-file.js";
|
|
6
7
|
import { openRootFileSync } from "./root-file.js";
|
|
7
8
|
import { writeTextAtomic } from "./text-atomic.js";
|
|
9
|
+
const READ_RETRY_MAX_ATTEMPTS = 5;
|
|
10
|
+
const READ_RETRY_BASE_DELAY_MS = 50;
|
|
11
|
+
function isRetryableReadError(err, options) {
|
|
12
|
+
if (err instanceof FsSafeError && err.code === "path-mismatch") {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (options.retryOpenRaceErrors !== true) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const code = getErrorCode(err);
|
|
19
|
+
return code === "ENOENT" || code === "EPERM";
|
|
20
|
+
}
|
|
21
|
+
function sleep(ms) {
|
|
22
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
+
}
|
|
24
|
+
async function readRegularFileWithRetry(filePath, options = {}) {
|
|
25
|
+
let lastErr;
|
|
26
|
+
for (let attempt = 0; attempt < READ_RETRY_MAX_ATTEMPTS; attempt++) {
|
|
27
|
+
try {
|
|
28
|
+
return (await readRegularFile({ filePath })).buffer;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
lastErr = err;
|
|
32
|
+
if (!isRetryableReadError(err, options) || attempt === READ_RETRY_MAX_ATTEMPTS - 1) {
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
await sleep(READ_RETRY_BASE_DELAY_MS * Math.pow(2, attempt));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw lastErr;
|
|
39
|
+
}
|
|
40
|
+
async function readRegularFileIfExistsWithRetry(filePath) {
|
|
41
|
+
const initial = await statRegularFile(filePath);
|
|
42
|
+
if (initial.missing) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return await readRegularFileWithRetry(filePath, { retryOpenRaceErrors: true });
|
|
46
|
+
}
|
|
8
47
|
const JSON_FILE_MODE = 0o600;
|
|
9
48
|
const JSON_DIR_MODE = 0o700;
|
|
10
49
|
const SUPPORTS_SYNC_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsSync.constants;
|
|
@@ -200,7 +239,11 @@ export function readRootJsonObjectSync(options) {
|
|
|
200
239
|
}
|
|
201
240
|
export async function tryReadJson(filePath) {
|
|
202
241
|
try {
|
|
203
|
-
const
|
|
242
|
+
const buffer = await readRegularFileIfExistsWithRetry(filePath);
|
|
243
|
+
if (buffer === null) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const raw = buffer.toString("utf8");
|
|
204
247
|
return JSON.parse(raw);
|
|
205
248
|
}
|
|
206
249
|
catch {
|
|
@@ -210,7 +253,7 @@ export async function tryReadJson(filePath) {
|
|
|
210
253
|
export async function readJson(filePath) {
|
|
211
254
|
let raw;
|
|
212
255
|
try {
|
|
213
|
-
raw = (await
|
|
256
|
+
raw = (await readRegularFileWithRetry(filePath, { retryOpenRaceErrors: true })).toString("utf8");
|
|
214
257
|
}
|
|
215
258
|
catch (err) {
|
|
216
259
|
throw new JsonFileReadError(filePath, "read", err);
|
|
@@ -225,7 +268,11 @@ export async function readJson(filePath) {
|
|
|
225
268
|
export async function readJsonIfExists(filePath) {
|
|
226
269
|
let raw;
|
|
227
270
|
try {
|
|
228
|
-
|
|
271
|
+
const buffer = await readRegularFileIfExistsWithRetry(filePath);
|
|
272
|
+
if (buffer === null) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
raw = buffer.toString("utf8");
|
|
229
276
|
}
|
|
230
277
|
catch (err) {
|
|
231
278
|
if (getErrorCode(err) === "ENOENT") {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAoCjB"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { sameFileIdentity } from "./file-identity.js";
|
|
5
|
+
import { isNotFoundPathError } from "./path.js";
|
|
6
|
+
export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
7
|
+
const handleStat = await handle.stat();
|
|
8
|
+
const fdCandidates = process.platform === "linux"
|
|
9
|
+
? [`/proc/self/fd/${handle.fd}`, `/dev/fd/${handle.fd}`]
|
|
10
|
+
: process.platform === "win32"
|
|
11
|
+
? []
|
|
12
|
+
: [`/dev/fd/${handle.fd}`];
|
|
13
|
+
for (const fdPath of fdCandidates) {
|
|
14
|
+
try {
|
|
15
|
+
const fdRealPath = await fs.realpath(fdPath);
|
|
16
|
+
const fdRealStat = await fs.stat(fdRealPath);
|
|
17
|
+
if (sameFileIdentity(handleStat, fdRealStat)) {
|
|
18
|
+
return fdRealPath;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// try next fd path
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const ioRealPath = await fs.realpath(ioPath);
|
|
27
|
+
const ioRealStat = await fs.stat(ioRealPath);
|
|
28
|
+
if (sameFileIdentity(handleStat, ioRealStat)) {
|
|
29
|
+
return ioRealPath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (!isNotFoundPathError(err)) {
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath);
|
|
38
|
+
if (parentResolved) {
|
|
39
|
+
return parentResolved;
|
|
40
|
+
}
|
|
41
|
+
throw new FsSafeError("path-mismatch", "unable to resolve opened file path");
|
|
42
|
+
}
|
|
43
|
+
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
|
|
44
|
+
let parentReal;
|
|
45
|
+
try {
|
|
46
|
+
parentReal = await fs.realpath(path.dirname(ioPath));
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (isNotFoundPathError(err)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = await fs.readdir(parentReal);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isNotFoundPathError(err)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
for (const entry of entries.toSorted()) {
|
|
65
|
+
const candidatePath = path.join(parentReal, entry);
|
|
66
|
+
try {
|
|
67
|
+
const candidateStat = await fs.lstat(candidatePath);
|
|
68
|
+
if (candidateStat.isFile() && sameFileIdentity(handleStat, candidateStat)) {
|
|
69
|
+
return await fs.realpath(candidatePath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (!isNotFoundPathError(err)) {
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
package/dist/output.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,wBAAwB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AA6CF,wBAAsB,2BAA2B,CAAC,CAAC,GAAG,IAAI,EACxD,OAAO,EAAE,wBAAwB,CAAC,CAAC,CAAC,GACnC,OAAO,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,wBAAwB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AA6CF,wBAAsB,2BAA2B,CAAC,CAAC,GAAG,IAAI,EACxD,OAAO,EAAE,wBAAwB,CAAC,CAAC,CAAC,GACnC,OAAO,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CA+BrC"}
|
package/dist/output.js
CHANGED
|
@@ -42,6 +42,7 @@ export async function writeExternalFileWithinRoot(options) {
|
|
|
42
42
|
if (requestedTargetPath.length === 0) {
|
|
43
43
|
throw new FsSafeError("invalid-path", "target path is required");
|
|
44
44
|
}
|
|
45
|
+
assertFileTargetPath(requestedTargetPath);
|
|
45
46
|
const targetPath = toRootPathInput({
|
|
46
47
|
rootDir: targetRoot.rootDir,
|
|
47
48
|
rootReal: targetRoot.rootReal,
|
package/dist/pinned-write.js
CHANGED
|
@@ -67,7 +67,7 @@ export async function runPinnedWriteHelper(params) {
|
|
|
67
67
|
relativeParentPath: params.relativeParentPath,
|
|
68
68
|
});
|
|
69
69
|
if (getFsSafePythonConfig().mode === "off") {
|
|
70
|
-
return await
|
|
70
|
+
return await runPinnedWriteFallback(params);
|
|
71
71
|
}
|
|
72
72
|
if (params.input.kind === "stream") {
|
|
73
73
|
try {
|
|
@@ -75,7 +75,7 @@ export async function runPinnedWriteHelper(params) {
|
|
|
75
75
|
}
|
|
76
76
|
catch (error) {
|
|
77
77
|
if (canFallbackFromPythonError(error)) {
|
|
78
|
-
return await
|
|
78
|
+
return await runPinnedWriteFallback(params);
|
|
79
79
|
}
|
|
80
80
|
throw error;
|
|
81
81
|
}
|
|
@@ -99,7 +99,7 @@ export async function runPinnedWriteHelper(params) {
|
|
|
99
99
|
}
|
|
100
100
|
catch (error) {
|
|
101
101
|
if (canFallbackFromPythonError(error)) {
|
|
102
|
-
return await
|
|
102
|
+
return await runPinnedWriteFallback(params);
|
|
103
103
|
}
|
|
104
104
|
throw error;
|
|
105
105
|
}
|
|
@@ -126,12 +126,6 @@ export async function runPinnedCopyHelper(params) {
|
|
|
126
126
|
},
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
|
-
async function runPinnedWriteFallbackOrThrow(params, cause) {
|
|
130
|
-
if (process.platform !== "win32") {
|
|
131
|
-
throw new FsSafeError("helper-unavailable", "Python helper is required for pinned writes on this platform", { cause });
|
|
132
|
-
}
|
|
133
|
-
return await runPinnedWriteFallback(params);
|
|
134
|
-
}
|
|
135
129
|
async function runPinnedWriteFallback(params) {
|
|
136
130
|
const parentPath = params.relativeParentPath
|
|
137
131
|
? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
export type ReadResult = {
|
|
4
|
+
buffer: Buffer;
|
|
5
|
+
realPath: string;
|
|
6
|
+
stat: Stats;
|
|
7
|
+
};
|
|
8
|
+
type OpenedFile = {
|
|
9
|
+
handle: FileHandle;
|
|
10
|
+
realPath: string;
|
|
11
|
+
stat: Stats;
|
|
12
|
+
};
|
|
13
|
+
export declare function readOpenedFileSafely(params: {
|
|
14
|
+
opened: OpenedFile;
|
|
15
|
+
maxBytes?: number;
|
|
16
|
+
}): Promise<ReadResult>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=read-opened-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-opened-file.d.ts","sourceRoot":"","sources":["../src/read-opened-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmBtB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
export async function readOpenedFileSafely(params) {
|
|
3
|
+
if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
|
|
4
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
|
|
5
|
+
}
|
|
6
|
+
const buffer = await params.opened.handle.readFile();
|
|
7
|
+
if (params.maxBytes !== undefined && buffer.byteLength > params.maxBytes) {
|
|
8
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${buffer.byteLength})`);
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
buffer,
|
|
12
|
+
realPath: params.opened.realPath,
|
|
13
|
+
stat: params.opened.stat,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,MAAM,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,MAAM,MAAM,SAAS,CAAC;AAS7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CAQR;AA2DD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CActF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAc3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAiD3C;AA6CD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;CACb,CAoBA;AAmBD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDxF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CA2D7E"}
|
package/dist/regular-file.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
4
5
|
import { sameFileIdentity } from "./file-identity.js";
|
|
5
6
|
import { isNotFoundPathError } from "./path.js";
|
|
6
7
|
import { assertNoSymlinkParents, assertNoSymlinkParentsSync } from "./symlink-parents.js";
|
|
@@ -95,12 +96,31 @@ export async function readRegularFile(params) {
|
|
|
95
96
|
if (params.maxBytes !== undefined && result.stat.size > params.maxBytes) {
|
|
96
97
|
throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`);
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
+
let handle;
|
|
100
|
+
try {
|
|
101
|
+
handle = await fs.open(params.filePath, resolveRegularFileReadFlags());
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (isNotFoundPathError(err)) {
|
|
105
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
99
109
|
try {
|
|
100
110
|
const stat = await handle.stat();
|
|
111
|
+
let pathStat;
|
|
112
|
+
try {
|
|
113
|
+
pathStat = await fs.lstat(params.filePath);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (isNotFoundPathError(err)) {
|
|
117
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
118
|
+
}
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
101
121
|
verifyStableReadTarget({
|
|
102
122
|
filePath: params.filePath,
|
|
103
|
-
pathStat
|
|
123
|
+
pathStat,
|
|
104
124
|
postOpenStat: stat,
|
|
105
125
|
preOpenStat: result.stat,
|
|
106
126
|
});
|
|
@@ -126,7 +146,7 @@ function verifyStableReadTarget(params) {
|
|
|
126
146
|
}
|
|
127
147
|
if (!sameFileIdentity(params.preOpenStat, params.postOpenStat) ||
|
|
128
148
|
!sameFileIdentity(params.pathStat, params.postOpenStat)) {
|
|
129
|
-
throw new
|
|
149
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
130
150
|
}
|
|
131
151
|
}
|
|
132
152
|
function readOpenedRegularFileSync(params) {
|
package/dist/root-impl.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { Stats } from "node:fs";
|
|
2
2
|
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
import { type DenyMutationPolicy } from "./deny-mutations.js";
|
|
4
|
+
import { type ReadResult } from "./read-opened-file.js";
|
|
3
5
|
import type { DirEntry, PathStat } from "./types.js";
|
|
6
|
+
export type { DenyMutationPolicy } from "./deny-mutations.js";
|
|
7
|
+
export { resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
|
|
8
|
+
export type { ReadResult } from "./read-opened-file.js";
|
|
4
9
|
export type OpenResult = {
|
|
5
10
|
handle: FileHandle;
|
|
6
11
|
realPath: string;
|
|
7
12
|
stat: Stats;
|
|
8
13
|
[Symbol.asyncDispose](): Promise<void>;
|
|
9
14
|
};
|
|
10
|
-
export type ReadResult = {
|
|
11
|
-
buffer: Buffer;
|
|
12
|
-
realPath: string;
|
|
13
|
-
stat: Stats;
|
|
14
|
-
};
|
|
15
15
|
export type RootOptions = {
|
|
16
16
|
rootDir: string;
|
|
17
17
|
defaults?: RootDefaults;
|
|
@@ -24,19 +24,20 @@ export type RootDefaults = {
|
|
|
24
24
|
maxBytes?: number;
|
|
25
25
|
mkdir?: boolean;
|
|
26
26
|
mode?: number;
|
|
27
|
+
denyMutations?: DenyMutationPolicy;
|
|
27
28
|
nonBlockingRead?: boolean;
|
|
28
29
|
symlinks?: SymlinkPolicy;
|
|
29
30
|
};
|
|
30
31
|
export type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "nonBlockingRead" | "symlinks">;
|
|
31
32
|
export type RootOpenOptions = Omit<RootReadOptions, "maxBytes">;
|
|
32
|
-
export type RootWriteOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
|
|
33
|
+
export type RootWriteOptions = Pick<RootDefaults, "denyMutations" | "mkdir" | "mode"> & {
|
|
33
34
|
encoding?: BufferEncoding;
|
|
34
35
|
overwrite?: boolean;
|
|
35
36
|
};
|
|
36
|
-
export type RootOpenWritableOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
|
|
37
|
+
export type RootOpenWritableOptions = Pick<RootDefaults, "denyMutations" | "mkdir" | "mode"> & {
|
|
37
38
|
writeMode?: WritableOpenMode;
|
|
38
39
|
};
|
|
39
|
-
export type RootCopyOptions = Pick<RootDefaults, "maxBytes" | "mkdir" | "mode"> & {
|
|
40
|
+
export type RootCopyOptions = Pick<RootDefaults, "denyMutations" | "maxBytes" | "mkdir" | "mode"> & {
|
|
40
41
|
sourceHardlinks?: HardlinkPolicy;
|
|
41
42
|
};
|
|
42
43
|
export type RootWriteJsonOptions = RootWriteOptions & {
|
|
@@ -49,6 +50,11 @@ export type RootCreateJsonOptions = Omit<RootWriteJsonOptions, "overwrite">;
|
|
|
49
50
|
export type RootAppendOptions = RootWriteOptions & {
|
|
50
51
|
prependNewlineIfNeeded?: boolean;
|
|
51
52
|
};
|
|
53
|
+
export type RootMoveOptions = Pick<RootDefaults, "denyMutations"> & {
|
|
54
|
+
overwrite?: boolean;
|
|
55
|
+
};
|
|
56
|
+
export type RootRemoveOptions = Pick<RootDefaults, "denyMutations">;
|
|
57
|
+
export type RootMkdirOptions = Pick<RootDefaults, "denyMutations">;
|
|
52
58
|
export declare const DEFAULT_ROOT_MAX_BYTES: number;
|
|
53
59
|
export interface Root {
|
|
54
60
|
readonly rootDir: string;
|
|
@@ -69,9 +75,9 @@ export interface Root {
|
|
|
69
75
|
reader(options?: RootReadOptions): (filePath: string) => Promise<Buffer>;
|
|
70
76
|
openWritable(relativePath: string, options?: RootOpenWritableOptions): Promise<WritableOpenResult>;
|
|
71
77
|
append(relativePath: string, data: string | Buffer, options?: RootAppendOptions): Promise<void>;
|
|
72
|
-
remove(relativePath: string): Promise<void>;
|
|
73
|
-
mkdir(relativePath: string): Promise<void>;
|
|
74
|
-
ensureRoot(): Promise<void>;
|
|
78
|
+
remove(relativePath: string, options?: RootRemoveOptions): Promise<void>;
|
|
79
|
+
mkdir(relativePath: string, options?: RootMkdirOptions): Promise<void>;
|
|
80
|
+
ensureRoot(options?: RootMkdirOptions): Promise<void>;
|
|
75
81
|
write(relativePath: string, data: string | Buffer, options?: RootWriteOptions): Promise<void>;
|
|
76
82
|
create(relativePath: string, data: string | Buffer, options?: RootCreateOptions): Promise<void>;
|
|
77
83
|
writeJson(relativePath: string, data: unknown, options?: RootWriteJsonOptions): Promise<void>;
|
|
@@ -85,9 +91,7 @@ export interface Root {
|
|
|
85
91
|
list(relativePath: string, options: {
|
|
86
92
|
withFileTypes: true;
|
|
87
93
|
}): Promise<DirEntry[]>;
|
|
88
|
-
move(fromRelative: string, toRelative: string, options?:
|
|
89
|
-
overwrite?: boolean;
|
|
90
|
-
}): Promise<void>;
|
|
94
|
+
move(fromRelative: string, toRelative: string, options?: RootMoveOptions): Promise<void>;
|
|
91
95
|
}
|
|
92
96
|
export declare function root(rootDir: string, defaults?: RootDefaults): Promise<Root>;
|
|
93
97
|
export declare function readLocalFileSafely(params: {
|
|
@@ -104,5 +108,4 @@ export type WritableOpenResult = {
|
|
|
104
108
|
stat: Stats;
|
|
105
109
|
[Symbol.asyncDispose](): Promise<void>;
|
|
106
110
|
};
|
|
107
|
-
export declare function resolveOpenedFileRealPathForHandle(handle: FileHandle, ioPath: string): Promise<string>;
|
|
108
111
|
//# sourceMappingURL=root-impl.d.ts.map
|
package/dist/root-impl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"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"}
|