@openclaw/fs-safe 0.1.1 → 0.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 +58 -0
- package/README.md +58 -2
- package/dist/absolute-path.d.ts +14 -0
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +203 -2
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -1
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +81 -7
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +25 -42
- package/dist/bounded-read-stream.d.ts +8 -0
- package/dist/bounded-read-stream.d.ts.map +1 -0
- package/dist/bounded-read-stream.js +20 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/directory-guard.d.ts +18 -0
- package/dist/directory-guard.d.ts.map +1 -0
- package/dist/directory-guard.js +70 -0
- package/dist/file-lock.d.ts +4 -2
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +13 -2
- package/dist/file-store-boundary.d.ts +25 -0
- package/dist/file-store-boundary.d.ts.map +1 -0
- package/dist/file-store-boundary.js +168 -0
- package/dist/file-store-prune.d.ts +12 -0
- package/dist/file-store-prune.d.ts.map +1 -0
- package/dist/file-store-prune.js +86 -0
- package/dist/file-store-source.d.ts +5 -0
- package/dist/file-store-source.d.ts.map +1 -0
- package/dist/file-store-source.js +30 -0
- package/dist/file-store.d.ts +2 -6
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +122 -125
- package/dist/guarded-mkdir.d.ts +6 -0
- package/dist/guarded-mkdir.d.ts.map +1 -0
- package/dist/guarded-mkdir.js +45 -0
- package/dist/guarded-mutation.d.ts +33 -0
- package/dist/guarded-mutation.d.ts.map +1 -0
- package/dist/guarded-mutation.js +76 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/json-document-store.d.ts +2 -0
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +7 -3
- package/dist/json-durable-queue.d.ts +45 -0
- package/dist/json-durable-queue.d.ts.map +1 -0
- package/dist/json-durable-queue.js +200 -0
- package/dist/json.d.ts +35 -5
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +69 -2
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +17 -3
- package/dist/lock-config.d.ts +10 -0
- package/dist/lock-config.d.ts.map +1 -0
- package/dist/lock-config.js +12 -0
- package/dist/move-path.d.ts +1 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +195 -7
- package/dist/output.d.ts +13 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +69 -0
- package/dist/path-stat.d.ts +4 -0
- package/dist/path-stat.d.ts.map +1 -0
- package/dist/path-stat.js +15 -0
- package/dist/path.d.ts +1 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +6 -3
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +44 -19
- package/dist/private-file-store.d.ts +5 -7
- package/dist/private-file-store.d.ts.map +1 -1
- package/dist/private-file-store.js +21 -34
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +28 -15
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +52 -8
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +5 -4
- package/dist/replace-file.d.ts +1 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +93 -31
- package/dist/root-context.d.ts +23 -0
- package/dist/root-context.d.ts.map +1 -0
- package/dist/root-context.js +62 -0
- package/dist/root-errors.d.ts +4 -0
- package/dist/root-errors.d.ts.map +1 -0
- package/dist/root-errors.js +21 -0
- package/dist/root-impl.d.ts +108 -0
- package/dist/root-impl.d.ts.map +1 -0
- package/dist/{safe-root.js → root-impl.js} +484 -265
- package/dist/root.d.ts +1 -107
- package/dist/root.d.ts.map +1 -1
- package/dist/root.js +1 -1332
- package/dist/safe-path-segment.d.ts +9 -0
- package/dist/safe-path-segment.d.ts.map +1 -0
- package/dist/safe-path-segment.js +51 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +6 -1
- package/dist/sibling-temp.d.ts +1 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +32 -7
- package/dist/sidecar-lock.d.ts +2 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +93 -13
- package/dist/store.d.ts +1 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +1 -0
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +7 -4
- package/dist/test-hooks.d.ts +6 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/text-atomic.d.ts +7 -0
- package/dist/text-atomic.d.ts.map +1 -1
- package/dist/text-atomic.js +3 -2
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +48 -13
- package/docs/advanced.md +110 -0
- package/docs/archive.md +199 -0
- package/docs/atomic.md +191 -0
- package/docs/config.md +94 -0
- package/docs/contributing.md +80 -0
- package/docs/errors.md +154 -0
- package/docs/file-store.md +213 -0
- package/docs/filename.md +0 -0
- package/docs/index.md +77 -0
- package/docs/install-path.md +167 -0
- package/docs/install.md +150 -0
- package/docs/json-store.md +204 -0
- package/docs/json.md +192 -0
- package/docs/local-roots.md +164 -0
- package/docs/output.md +92 -0
- package/docs/path-scope.md +130 -0
- package/docs/path.md +191 -0
- package/docs/permissions.md +95 -0
- package/docs/private-file-store.md +45 -0
- package/docs/python-helper.md +103 -0
- package/docs/quickstart.md +147 -0
- package/docs/reading.md +174 -0
- package/docs/regular-file.md +175 -0
- package/docs/root.md +205 -0
- package/docs/secret-file.md +154 -0
- package/docs/secure-file.md +82 -0
- package/docs/security-model.md +99 -0
- package/docs/sidecar-lock.md +230 -0
- package/docs/store.md +77 -0
- package/docs/temp.md +284 -0
- package/docs/test-hooks.md +74 -0
- package/docs/testing.md +178 -0
- package/docs/timing.md +121 -0
- package/docs/types.md +155 -0
- package/docs/walk.md +63 -0
- package/docs/writing.md +222 -0
- package/package.json +10 -2
- package/dist/archive-utils.d.ts +0 -3
- package/dist/archive-utils.d.ts.map +0 -1
- package/dist/archive-utils.js +0 -26
- package/dist/boundary-file-read.d.ts +0 -44
- package/dist/boundary-file-read.d.ts.map +0 -1
- package/dist/boundary-file-read.js +0 -129
- package/dist/boundary-path.d.ts +0 -39
- package/dist/boundary-path.d.ts.map +0 -1
- package/dist/boundary-path.js +0 -598
- package/dist/fs-pinned-path-helper.d.ts +0 -7
- package/dist/fs-pinned-path-helper.d.ts.map +0 -1
- package/dist/fs-pinned-path-helper.js +0 -182
- package/dist/fs-pinned-write-helper.d.ts +0 -21
- package/dist/fs-pinned-write-helper.d.ts.map +0 -1
- package/dist/fs-pinned-write-helper.js +0 -263
- package/dist/hardlink-guards.d.ts +0 -7
- package/dist/hardlink-guards.d.ts.map +0 -1
- package/dist/hardlink-guards.js +0 -30
- package/dist/install-safe-path.d.ts +0 -20
- package/dist/install-safe-path.d.ts.map +0 -1
- package/dist/install-safe-path.js +0 -94
- package/dist/json-file.d.ts +0 -3
- package/dist/json-file.d.ts.map +0 -1
- package/dist/json-file.js +0 -123
- package/dist/json-files.d.ts +0 -20
- package/dist/json-files.d.ts.map +0 -1
- package/dist/json-files.js +0 -153
- package/dist/path-alias-guards.d.ts +0 -19
- package/dist/path-alias-guards.d.ts.map +0 -1
- package/dist/path-alias-guards.js +0 -21
- package/dist/path-guards.d.ts +0 -7
- package/dist/path-guards.d.ts.map +0 -1
- package/dist/path-guards.js +0 -49
- package/dist/path-safety.d.ts +0 -12
- package/dist/path-safety.d.ts.map +0 -1
- package/dist/path-safety.js +0 -50
- package/dist/safe-open-sync.d.ts +0 -24
- package/dist/safe-open-sync.d.ts.map +0 -1
- package/dist/safe-open-sync.js +0 -71
- package/dist/safe-root.d.ts +0 -123
- package/dist/safe-root.d.ts.map +0 -1
- package/dist/secure-temp-workspace.d.ts +0 -25
- package/dist/secure-temp-workspace.d.ts.map +0 -1
- package/dist/secure-temp-workspace.js +0 -136
- package/dist/sibling-temp-file.d.ts +0 -16
- package/dist/sibling-temp-file.d.ts.map +0 -1
- package/dist/sibling-temp-file.js +0 -73
- package/dist/sibling-temp-write.d.ts +0 -8
- package/dist/sibling-temp-write.d.ts.map +0 -1
- package/dist/sibling-temp-write.js +0 -40
|
@@ -3,6 +3,7 @@ import fsSync from "node:fs";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileStore, fileStoreSync, } from "./file-store.js";
|
|
6
|
+
import { openRootFileSync } from "./root-file.js";
|
|
6
7
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
7
8
|
function sanitizeTempPrefix(prefix) {
|
|
8
9
|
const sanitized = prefix.trim().replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
@@ -12,21 +13,20 @@ function sanitizeTempPrefix(prefix) {
|
|
|
12
13
|
return sanitized.endsWith("-") ? sanitized : `${sanitized}-`;
|
|
13
14
|
}
|
|
14
15
|
function resolveWorkspaceLeaf(dir, fileName) {
|
|
15
|
-
|
|
16
|
-
if (!raw ||
|
|
17
|
-
raw === "." ||
|
|
18
|
-
raw === ".." ||
|
|
19
|
-
raw.includes("\0") ||
|
|
20
|
-
raw.includes("/") ||
|
|
21
|
-
raw.includes("\\") ||
|
|
22
|
-
path.basename(raw) !== raw) {
|
|
23
|
-
throw new Error(`Invalid temp workspace file name: ${JSON.stringify(fileName)}`);
|
|
24
|
-
}
|
|
25
|
-
return path.join(dir, raw);
|
|
16
|
+
return path.join(dir, assertWorkspaceFileName(fileName));
|
|
26
17
|
}
|
|
27
18
|
function assertWorkspaceFileName(fileName) {
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
const value = fileName.trim();
|
|
20
|
+
if (!value ||
|
|
21
|
+
value === "." ||
|
|
22
|
+
value === ".." ||
|
|
23
|
+
value.includes("\0") ||
|
|
24
|
+
value.includes("/") ||
|
|
25
|
+
value.includes("\\") ||
|
|
26
|
+
path.basename(value) !== value) {
|
|
27
|
+
throw new Error(`Invalid temp workspace file name: ${JSON.stringify(fileName)}`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
30
|
}
|
|
31
31
|
async function ensurePrivateDirectory(dir, mode) {
|
|
32
32
|
await fs.mkdir(dir, { recursive: true, mode });
|
|
@@ -144,8 +144,21 @@ export function tempWorkspaceSync(options) {
|
|
|
144
144
|
trailingNewline: writeOptions?.trailingNewline,
|
|
145
145
|
}),
|
|
146
146
|
read: (fileName) => {
|
|
147
|
-
const
|
|
148
|
-
|
|
147
|
+
const opened = openRootFileSync({
|
|
148
|
+
absolutePath: store.path(assertWorkspaceFileName(fileName)),
|
|
149
|
+
rootPath: dir,
|
|
150
|
+
boundaryLabel: "temp workspace",
|
|
151
|
+
rejectHardlinks: true,
|
|
152
|
+
});
|
|
153
|
+
if (!opened.ok) {
|
|
154
|
+
throw Object.assign(new Error(`File not found: ${fileName}`), { code: "ENOENT" });
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
return fsSync.readFileSync(opened.fd);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
fsSync.closeSync(opened.fd);
|
|
161
|
+
}
|
|
149
162
|
},
|
|
150
163
|
cleanup: () => {
|
|
151
164
|
try {
|
|
@@ -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;AAQ7B,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,CAgC3C;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
|
@@ -17,6 +17,44 @@ function resolveRegularFileReadFlags() {
|
|
|
17
17
|
? fsSync.constants.O_NOFOLLOW
|
|
18
18
|
: 0));
|
|
19
19
|
}
|
|
20
|
+
async function readFileHandleBounded(params) {
|
|
21
|
+
if (params.maxBytes === undefined) {
|
|
22
|
+
return await params.handle.readFile();
|
|
23
|
+
}
|
|
24
|
+
const chunks = [];
|
|
25
|
+
const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, Math.max(1, params.maxBytes + 1)));
|
|
26
|
+
let total = 0;
|
|
27
|
+
while (true) {
|
|
28
|
+
const { bytesRead } = await params.handle.read(scratch, 0, scratch.length, null);
|
|
29
|
+
if (bytesRead === 0) {
|
|
30
|
+
return Buffer.concat(chunks, total);
|
|
31
|
+
}
|
|
32
|
+
total += bytesRead;
|
|
33
|
+
if (total > params.maxBytes) {
|
|
34
|
+
throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`);
|
|
35
|
+
}
|
|
36
|
+
chunks.push(Buffer.from(scratch.subarray(0, bytesRead)));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function readFileDescriptorBounded(params) {
|
|
40
|
+
if (params.maxBytes === undefined) {
|
|
41
|
+
return fsSync.readFileSync(params.fd);
|
|
42
|
+
}
|
|
43
|
+
const chunks = [];
|
|
44
|
+
const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, Math.max(1, params.maxBytes + 1)));
|
|
45
|
+
let total = 0;
|
|
46
|
+
while (true) {
|
|
47
|
+
const bytesRead = fsSync.readSync(params.fd, scratch, 0, scratch.length, null);
|
|
48
|
+
if (bytesRead === 0) {
|
|
49
|
+
return Buffer.concat(chunks, total);
|
|
50
|
+
}
|
|
51
|
+
total += bytesRead;
|
|
52
|
+
if (total > params.maxBytes) {
|
|
53
|
+
throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`);
|
|
54
|
+
}
|
|
55
|
+
chunks.push(Buffer.from(scratch.subarray(0, bytesRead)));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
20
58
|
export async function statRegularFile(filePath) {
|
|
21
59
|
let stat;
|
|
22
60
|
try {
|
|
@@ -69,10 +107,13 @@ export async function readRegularFile(params) {
|
|
|
69
107
|
if (params.maxBytes !== undefined && stat.size > params.maxBytes) {
|
|
70
108
|
throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`);
|
|
71
109
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
110
|
+
// With a byte cap, avoid readFile(): a raced file growth would allocate
|
|
111
|
+
// the oversized content before the post-read check could reject it.
|
|
112
|
+
const buffer = await readFileHandleBounded({
|
|
113
|
+
handle,
|
|
114
|
+
filePath: params.filePath,
|
|
115
|
+
maxBytes: params.maxBytes,
|
|
116
|
+
});
|
|
76
117
|
return { buffer, stat };
|
|
77
118
|
}
|
|
78
119
|
finally {
|
|
@@ -99,10 +140,13 @@ function readOpenedRegularFileSync(params) {
|
|
|
99
140
|
if (params.maxBytes !== undefined && stat.size > params.maxBytes) {
|
|
100
141
|
throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`);
|
|
101
142
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
143
|
+
// Keep capped sync reads incremental for the same reason as async reads:
|
|
144
|
+
// readFileSync(fd) would buffer a raced oversized file before throwing.
|
|
145
|
+
const buffer = readFileDescriptorBounded({
|
|
146
|
+
fd: params.fd,
|
|
147
|
+
filePath: params.filePath,
|
|
148
|
+
maxBytes: params.maxBytes,
|
|
149
|
+
});
|
|
106
150
|
return { buffer, stat };
|
|
107
151
|
}
|
|
108
152
|
export function readRegularFileSync(params) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-directory.d.ts","sourceRoot":"","sources":["../src/replace-directory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replace-directory.d.ts","sourceRoot":"","sources":["../src/replace-directory.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC,CAiCf"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { guardedRename, guardedRm } from "./guarded-mutation.js";
|
|
3
4
|
export async function replaceDirectoryAtomic(options) {
|
|
4
5
|
const targetDir = path.resolve(options.targetDir);
|
|
5
6
|
const stagedDir = path.resolve(options.stagedDir);
|
|
@@ -8,7 +9,7 @@ export async function replaceDirectoryAtomic(options) {
|
|
|
8
9
|
let backupCreated = false;
|
|
9
10
|
await fs.mkdir(parentDir, { recursive: true });
|
|
10
11
|
try {
|
|
11
|
-
await
|
|
12
|
+
await guardedRename({ from: targetDir, to: backupDir });
|
|
12
13
|
backupCreated = true;
|
|
13
14
|
}
|
|
14
15
|
catch (err) {
|
|
@@ -17,16 +18,16 @@ export async function replaceDirectoryAtomic(options) {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
try {
|
|
20
|
-
await
|
|
21
|
+
await guardedRename({ from: stagedDir, to: targetDir });
|
|
21
22
|
}
|
|
22
23
|
catch (err) {
|
|
23
24
|
if (backupCreated) {
|
|
24
|
-
await
|
|
25
|
+
await guardedRename({ from: backupDir, to: targetDir }).catch(() => undefined);
|
|
25
26
|
backupCreated = false;
|
|
26
27
|
}
|
|
27
28
|
throw err;
|
|
28
29
|
}
|
|
29
30
|
if (backupCreated) {
|
|
30
|
-
await
|
|
31
|
+
await guardedRm({ target: backupDir, recursive: true, force: true, verifyAfter: false });
|
|
31
32
|
}
|
|
32
33
|
}
|
package/dist/replace-file.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
export type ReplaceFileAtomicFileSystem = {
|
|
4
4
|
promises: Pick<typeof fs, "mkdir" | "chmod" | "writeFile" | "rename" | "copyFile" | "unlink" | "rm" | "open" | "stat" | "lstat">;
|
|
5
5
|
};
|
|
6
|
-
export type ReplaceFileAtomicSyncFileSystem = Pick<typeof syncFs, "mkdirSync" | "chmodSync" | "writeFileSync" | "renameSync" | "copyFileSync" | "unlinkSync" | "rmSync" | "openSync" | "fsyncSync" | "closeSync" | "statSync" | "lstatSync">;
|
|
6
|
+
export type ReplaceFileAtomicSyncFileSystem = Pick<typeof syncFs, "mkdirSync" | "chmodSync" | "readFileSync" | "writeFileSync" | "renameSync" | "copyFileSync" | "unlinkSync" | "rmSync" | "openSync" | "fsyncSync" | "closeSync" | "statSync" | "lstatSync">;
|
|
7
7
|
type ReplaceFileAtomicBaseOptions = {
|
|
8
8
|
filePath: string;
|
|
9
9
|
content: string | Uint8Array;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file.d.ts","sourceRoot":"","sources":["../src/replace-file.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAE7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"replace-file.d.ts","sourceRoot":"","sources":["../src/replace-file.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAE7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAMlC,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,IAAI,CACZ,OAAO,EAAE,EACP,OAAO,GACP,OAAO,GACP,WAAW,GACX,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,IAAI,GACJ,MAAM,GACN,MAAM,GACN,OAAO,CACV,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,IAAI,CAChD,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,cAAc,GACd,eAAe,GACf,YAAY,GACZ,cAAc,GACd,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,WAAW,GACX,WAAW,GACX,UAAU,GACV,WAAW,CACd,CAAC;AAEF,KAAK,4BAA4B,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,4BAA4B,GAAG;IACpE,UAAU,CAAC,EAAE,2BAA2B,CAAC;IACzC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClF,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,4BAA4B,GAAG;IACxE,UAAU,CAAC,EAAE,+BAA+B,CAAC;IAC7C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACzE,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAAC;CACpC,CAAC;AAySF,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAMlC;AAyDD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,uBAAuB,CAiEzB"}
|
package/dist/replace-file.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import syncFs from "node:fs";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
5
6
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
6
7
|
import { serializePathWrite } from "./write-queue.js";
|
|
7
8
|
function isRetryableRenameError(error) {
|
|
@@ -11,6 +12,12 @@ function isPermissionRenameError(error) {
|
|
|
11
12
|
const code = error.code;
|
|
12
13
|
return code === "EPERM" || code === "EEXIST";
|
|
13
14
|
}
|
|
15
|
+
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in syncFs.constants;
|
|
16
|
+
const OPEN_READ_FLAGS = syncFs.constants.O_RDONLY | (SUPPORTS_NOFOLLOW ? syncFs.constants.O_NOFOLLOW : 0);
|
|
17
|
+
const OPEN_WRITE_EXCLUSIVE_FLAGS = syncFs.constants.O_WRONLY |
|
|
18
|
+
syncFs.constants.O_CREAT |
|
|
19
|
+
syncFs.constants.O_EXCL |
|
|
20
|
+
(SUPPORTS_NOFOLLOW ? syncFs.constants.O_NOFOLLOW : 0);
|
|
14
21
|
async function sleep(ms) {
|
|
15
22
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
23
|
}
|
|
@@ -26,17 +33,7 @@ async function renameWithRetry(params) {
|
|
|
26
33
|
continue;
|
|
27
34
|
}
|
|
28
35
|
if (params.copyFallbackOnPermissionError && isPermissionRenameError(error)) {
|
|
29
|
-
|
|
30
|
-
if (lstatError.code === "ENOENT") {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
throw lstatError;
|
|
34
|
-
});
|
|
35
|
-
if (stat?.isSymbolicLink()) {
|
|
36
|
-
throw new Error(`Refusing copy fallback through symlink destination: ${params.dest}`);
|
|
37
|
-
}
|
|
38
|
-
await params.fsModule.copyFile(params.src, params.dest);
|
|
39
|
-
await params.fsModule.unlink(params.src).catch(() => undefined);
|
|
36
|
+
await copyFallbackReplace(params.fsModule, params.src, params.dest);
|
|
40
37
|
return { method: "copy-fallback" };
|
|
41
38
|
}
|
|
42
39
|
throw error;
|
|
@@ -62,25 +59,7 @@ function renameWithRetrySync(params) {
|
|
|
62
59
|
continue;
|
|
63
60
|
}
|
|
64
61
|
if (params.copyFallbackOnPermissionError && isPermissionRenameError(error)) {
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
stat = params.fsModule.lstatSync(params.dest);
|
|
68
|
-
}
|
|
69
|
-
catch (lstatError) {
|
|
70
|
-
if (lstatError.code !== "ENOENT") {
|
|
71
|
-
throw lstatError;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (stat?.isSymbolicLink()) {
|
|
75
|
-
throw new Error(`Refusing copy fallback through symlink destination: ${params.dest}`);
|
|
76
|
-
}
|
|
77
|
-
params.fsModule.copyFileSync(params.src, params.dest);
|
|
78
|
-
try {
|
|
79
|
-
params.fsModule.unlinkSync(params.src);
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
// Best-effort cleanup after fallback replacement.
|
|
83
|
-
}
|
|
62
|
+
copyFallbackReplaceSync(params.fsModule, params.src, params.dest);
|
|
84
63
|
return { method: "copy-fallback" };
|
|
85
64
|
}
|
|
86
65
|
throw error;
|
|
@@ -88,6 +67,88 @@ function renameWithRetrySync(params) {
|
|
|
88
67
|
}
|
|
89
68
|
throw new Error("Atomic rename retry loop exhausted.");
|
|
90
69
|
}
|
|
70
|
+
async function copyFallbackReplace(fsModule, src, dest) {
|
|
71
|
+
const sourceStat = await fsModule.lstat(src);
|
|
72
|
+
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
|
73
|
+
throw new Error(`Refusing copy fallback from non-file source: ${src}`);
|
|
74
|
+
}
|
|
75
|
+
const destStat = await fsModule.lstat(dest).catch((lstatError) => {
|
|
76
|
+
if (lstatError.code === "ENOENT") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
throw lstatError;
|
|
80
|
+
});
|
|
81
|
+
if (destStat?.isSymbolicLink()) {
|
|
82
|
+
throw new Error(`Refusing copy fallback through symlink destination: ${dest}`);
|
|
83
|
+
}
|
|
84
|
+
if (destStat) {
|
|
85
|
+
await fsModule.rm(dest, { force: true });
|
|
86
|
+
}
|
|
87
|
+
let sourceHandle = null;
|
|
88
|
+
let destHandle = null;
|
|
89
|
+
try {
|
|
90
|
+
sourceHandle = await fsModule.open(src, OPEN_READ_FLAGS);
|
|
91
|
+
destHandle = await fsModule.open(dest, OPEN_WRITE_EXCLUSIVE_FLAGS, sourceStat.mode & 0o777);
|
|
92
|
+
await destHandle.writeFile(await sourceHandle.readFile());
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await destHandle?.close().catch(() => undefined);
|
|
96
|
+
await sourceHandle?.close().catch(() => undefined);
|
|
97
|
+
}
|
|
98
|
+
await fsModule.unlink(src).catch(() => undefined);
|
|
99
|
+
}
|
|
100
|
+
function copyFallbackReplaceSync(fsModule, src, dest) {
|
|
101
|
+
const sourceStat = fsModule.lstatSync(src);
|
|
102
|
+
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
|
103
|
+
throw new Error(`Refusing copy fallback from non-file source: ${src}`);
|
|
104
|
+
}
|
|
105
|
+
let destStat = null;
|
|
106
|
+
try {
|
|
107
|
+
destStat = fsModule.lstatSync(dest);
|
|
108
|
+
}
|
|
109
|
+
catch (lstatError) {
|
|
110
|
+
if (lstatError.code !== "ENOENT") {
|
|
111
|
+
throw lstatError;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (destStat?.isSymbolicLink()) {
|
|
115
|
+
throw new Error(`Refusing copy fallback through symlink destination: ${dest}`);
|
|
116
|
+
}
|
|
117
|
+
if (destStat) {
|
|
118
|
+
fsModule.rmSync(dest, { force: true });
|
|
119
|
+
}
|
|
120
|
+
let sourceFd;
|
|
121
|
+
let destFd;
|
|
122
|
+
try {
|
|
123
|
+
sourceFd = fsModule.openSync(src, OPEN_READ_FLAGS);
|
|
124
|
+
destFd = fsModule.openSync(dest, OPEN_WRITE_EXCLUSIVE_FLAGS, sourceStat.mode & 0o777);
|
|
125
|
+
fsModule.writeFileSync(destFd, fsModule.readFileSync(sourceFd));
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
if (destFd !== undefined) {
|
|
129
|
+
try {
|
|
130
|
+
fsModule.closeSync(destFd);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Best-effort close after fallback replacement.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (sourceFd !== undefined) {
|
|
137
|
+
try {
|
|
138
|
+
fsModule.closeSync(sourceFd);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Best-effort close after fallback replacement.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
fsModule.unlinkSync(src);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Best-effort cleanup after fallback replacement.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
91
152
|
function validateReplaceFilePath(filePath) {
|
|
92
153
|
if (!filePath || filePath.includes("\0")) {
|
|
93
154
|
throw new Error("Atomic replace file path must be non-empty.");
|
|
@@ -95,7 +156,8 @@ function validateReplaceFilePath(filePath) {
|
|
|
95
156
|
}
|
|
96
157
|
function buildReplaceTempPath(filePath, tempPrefix) {
|
|
97
158
|
const dir = path.dirname(filePath);
|
|
98
|
-
|
|
159
|
+
const safePrefix = assertSafePathPrefix(tempPrefix ?? ".fs-safe-replace", { label: "atomic replace temp prefix" });
|
|
160
|
+
return path.join(dir, `${safePrefix}.${process.pid}.${randomUUID()}.tmp`);
|
|
99
161
|
}
|
|
100
162
|
async function resolveMode(options) {
|
|
101
163
|
const defaultMode = options.mode ?? 0o600;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type RootContext = {
|
|
2
|
+
rootDir: string;
|
|
3
|
+
rootReal: string;
|
|
4
|
+
rootWithSep: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const ensureTrailingSep: (value: string) => string;
|
|
7
|
+
export declare function assertValidRootRelativePath(relativePath: string): void;
|
|
8
|
+
export declare function expandRelativePathWithHome(relativePath: string): Promise<string>;
|
|
9
|
+
export declare function resolveRootContext(rootDir: string): Promise<RootContext>;
|
|
10
|
+
export declare function resolvePathInRoot(root: RootContext, relativePath: string): Promise<{
|
|
11
|
+
rootReal: string;
|
|
12
|
+
rootWithSep: string;
|
|
13
|
+
resolved: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function resolvePathWithinRoot(params: {
|
|
16
|
+
rootDir: string;
|
|
17
|
+
relativePath: string;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
rootReal: string;
|
|
20
|
+
rootWithSep: string;
|
|
21
|
+
resolved: string;
|
|
22
|
+
}>;
|
|
23
|
+
//# sourceMappingURL=root-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,WACM,CAAC;AAEtD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAEtE;AAID,wBAAsB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYtF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAuB9E;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAQtE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAEvE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
5
|
+
import { expandHomePrefix } from "./home-dir.js";
|
|
6
|
+
import { assertNoNulPathInput, isNotFoundPathError, isPathInside } from "./path.js";
|
|
7
|
+
export const ensureTrailingSep = (value) => value.endsWith(path.sep) ? value : value + path.sep;
|
|
8
|
+
export function assertValidRootRelativePath(relativePath) {
|
|
9
|
+
assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
|
|
10
|
+
}
|
|
11
|
+
let cachedHomePath;
|
|
12
|
+
export async function expandRelativePathWithHome(relativePath) {
|
|
13
|
+
const rawHome = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
14
|
+
if (cachedHomePath?.raw !== rawHome) {
|
|
15
|
+
let realHome = rawHome;
|
|
16
|
+
try {
|
|
17
|
+
realHome = await fs.realpath(rawHome);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// If the home dir cannot be canonicalized, keep lexical expansion behavior.
|
|
21
|
+
}
|
|
22
|
+
cachedHomePath = { raw: rawHome, real: realHome };
|
|
23
|
+
}
|
|
24
|
+
return expandHomePrefix(relativePath, { home: cachedHomePath.real });
|
|
25
|
+
}
|
|
26
|
+
export async function resolveRootContext(rootDir) {
|
|
27
|
+
assertNoNulPathInput(rootDir, "root dir contains a NUL byte");
|
|
28
|
+
let rootReal;
|
|
29
|
+
try {
|
|
30
|
+
rootReal = await fs.realpath(rootDir);
|
|
31
|
+
const rootStat = await fs.stat(rootReal);
|
|
32
|
+
if (!rootStat.isDirectory()) {
|
|
33
|
+
throw new FsSafeError("invalid-path", "root dir is not a directory");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err instanceof FsSafeError) {
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
if (isNotFoundPathError(err)) {
|
|
41
|
+
throw new FsSafeError("not-found", "root dir not found");
|
|
42
|
+
}
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
rootDir: path.resolve(rootDir),
|
|
47
|
+
rootReal,
|
|
48
|
+
rootWithSep: ensureTrailingSep(rootReal),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function resolvePathInRoot(root, relativePath) {
|
|
52
|
+
assertValidRootRelativePath(relativePath);
|
|
53
|
+
const expanded = await expandRelativePathWithHome(relativePath);
|
|
54
|
+
const resolved = path.resolve(root.rootWithSep, expanded);
|
|
55
|
+
if (!isPathInside(root.rootWithSep, resolved)) {
|
|
56
|
+
throw new FsSafeError("outside-workspace", "file is outside workspace root");
|
|
57
|
+
}
|
|
58
|
+
return { rootReal: root.rootReal, rootWithSep: root.rootWithSep, resolved };
|
|
59
|
+
}
|
|
60
|
+
export async function resolvePathWithinRoot(params) {
|
|
61
|
+
return await resolvePathInRoot(await resolveRootContext(params.rootDir), params.relativePath);
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-errors.d.ts","sourceRoot":"","sources":["../src/root-errors.ts"],"names":[],"mappings":"AAGA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE5D;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAO/D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAO9D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { hasNodeErrorCode } from "./path.js";
|
|
3
|
+
export function isAlreadyExistsError(error) {
|
|
4
|
+
return hasNodeErrorCode(error, "EEXIST") || /File exists|EEXIST/i.test(String(error));
|
|
5
|
+
}
|
|
6
|
+
export function normalizePinnedWriteError(error) {
|
|
7
|
+
if (error instanceof FsSafeError) {
|
|
8
|
+
return error;
|
|
9
|
+
}
|
|
10
|
+
return new FsSafeError("invalid-path", "path is not a regular file under root", {
|
|
11
|
+
cause: error instanceof Error ? error : undefined,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function normalizePinnedPathError(error) {
|
|
15
|
+
if (error instanceof FsSafeError) {
|
|
16
|
+
return error;
|
|
17
|
+
}
|
|
18
|
+
return new FsSafeError("path-alias", "path is not under root", {
|
|
19
|
+
cause: error instanceof Error ? error : undefined,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
import type { DirEntry, PathStat } from "./types.js";
|
|
4
|
+
export type OpenResult = {
|
|
5
|
+
handle: FileHandle;
|
|
6
|
+
realPath: string;
|
|
7
|
+
stat: Stats;
|
|
8
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export type ReadResult = {
|
|
11
|
+
buffer: Buffer;
|
|
12
|
+
realPath: string;
|
|
13
|
+
stat: Stats;
|
|
14
|
+
};
|
|
15
|
+
export type RootOptions = {
|
|
16
|
+
rootDir: string;
|
|
17
|
+
defaults?: RootDefaults;
|
|
18
|
+
};
|
|
19
|
+
export type SymlinkPolicy = "reject" | "follow-within-root";
|
|
20
|
+
export type HardlinkPolicy = "reject" | "allow";
|
|
21
|
+
export type WritableOpenMode = "replace" | "append" | "update";
|
|
22
|
+
export type RootDefaults = {
|
|
23
|
+
hardlinks?: HardlinkPolicy;
|
|
24
|
+
maxBytes?: number;
|
|
25
|
+
mkdir?: boolean;
|
|
26
|
+
mode?: number;
|
|
27
|
+
nonBlockingRead?: boolean;
|
|
28
|
+
symlinks?: SymlinkPolicy;
|
|
29
|
+
};
|
|
30
|
+
export type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "nonBlockingRead" | "symlinks">;
|
|
31
|
+
export type RootOpenOptions = Omit<RootReadOptions, "maxBytes">;
|
|
32
|
+
export type RootWriteOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
|
|
33
|
+
encoding?: BufferEncoding;
|
|
34
|
+
overwrite?: boolean;
|
|
35
|
+
};
|
|
36
|
+
export type RootOpenWritableOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
|
|
37
|
+
writeMode?: WritableOpenMode;
|
|
38
|
+
};
|
|
39
|
+
export type RootCopyOptions = Pick<RootDefaults, "maxBytes" | "mkdir" | "mode"> & {
|
|
40
|
+
sourceHardlinks?: HardlinkPolicy;
|
|
41
|
+
};
|
|
42
|
+
export type RootWriteJsonOptions = RootWriteOptions & {
|
|
43
|
+
replacer?: Parameters<typeof JSON.stringify>[1];
|
|
44
|
+
space?: Parameters<typeof JSON.stringify>[2];
|
|
45
|
+
trailingNewline?: boolean;
|
|
46
|
+
};
|
|
47
|
+
export type RootCreateOptions = Omit<RootWriteOptions, "overwrite">;
|
|
48
|
+
export type RootCreateJsonOptions = Omit<RootWriteJsonOptions, "overwrite">;
|
|
49
|
+
export type RootAppendOptions = RootWriteOptions & {
|
|
50
|
+
prependNewlineIfNeeded?: boolean;
|
|
51
|
+
};
|
|
52
|
+
export declare const DEFAULT_ROOT_MAX_BYTES: number;
|
|
53
|
+
export interface Root {
|
|
54
|
+
readonly rootDir: string;
|
|
55
|
+
readonly rootReal: string;
|
|
56
|
+
readonly rootWithSep: string;
|
|
57
|
+
readonly defaults: RootDefaults;
|
|
58
|
+
resolve(relativePath: string): Promise<string>;
|
|
59
|
+
open(relativePath: string, options?: RootOpenOptions): Promise<OpenResult>;
|
|
60
|
+
read(relativePath: string, options?: RootReadOptions): Promise<ReadResult>;
|
|
61
|
+
readBytes(relativePath: string, options?: RootReadOptions): Promise<Buffer>;
|
|
62
|
+
readText(relativePath: string, options?: RootReadOptions & {
|
|
63
|
+
encoding?: BufferEncoding;
|
|
64
|
+
}): Promise<string>;
|
|
65
|
+
readJson<T = unknown>(relativePath: string, options?: RootReadOptions & {
|
|
66
|
+
encoding?: BufferEncoding;
|
|
67
|
+
}): Promise<T>;
|
|
68
|
+
readAbsolute(filePath: string, options?: RootReadOptions): Promise<ReadResult>;
|
|
69
|
+
reader(options?: RootReadOptions): (filePath: string) => Promise<Buffer>;
|
|
70
|
+
openWritable(relativePath: string, options?: RootOpenWritableOptions): Promise<WritableOpenResult>;
|
|
71
|
+
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>;
|
|
75
|
+
write(relativePath: string, data: string | Buffer, options?: RootWriteOptions): Promise<void>;
|
|
76
|
+
create(relativePath: string, data: string | Buffer, options?: RootCreateOptions): Promise<void>;
|
|
77
|
+
writeJson(relativePath: string, data: unknown, options?: RootWriteJsonOptions): Promise<void>;
|
|
78
|
+
createJson(relativePath: string, data: unknown, options?: RootCreateJsonOptions): Promise<void>;
|
|
79
|
+
copyIn(relativePath: string, sourcePath: string, options?: RootCopyOptions): Promise<void>;
|
|
80
|
+
exists(relativePath: string): Promise<boolean>;
|
|
81
|
+
stat(relativePath: string): Promise<PathStat>;
|
|
82
|
+
list(relativePath: string, options?: {
|
|
83
|
+
withFileTypes?: false;
|
|
84
|
+
}): Promise<string[]>;
|
|
85
|
+
list(relativePath: string, options: {
|
|
86
|
+
withFileTypes: true;
|
|
87
|
+
}): Promise<DirEntry[]>;
|
|
88
|
+
move(fromRelative: string, toRelative: string, options?: {
|
|
89
|
+
overwrite?: boolean;
|
|
90
|
+
}): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
export declare function root(rootDir: string, defaults?: RootDefaults): Promise<Root>;
|
|
93
|
+
export declare function readLocalFileSafely(params: {
|
|
94
|
+
filePath: string;
|
|
95
|
+
maxBytes?: number;
|
|
96
|
+
}): Promise<ReadResult>;
|
|
97
|
+
export declare function openLocalFileSafely(params: {
|
|
98
|
+
filePath: string;
|
|
99
|
+
}): Promise<OpenResult>;
|
|
100
|
+
export type WritableOpenResult = {
|
|
101
|
+
handle: FileHandle;
|
|
102
|
+
createdForWrite: boolean;
|
|
103
|
+
realPath: string;
|
|
104
|
+
stat: Stats;
|
|
105
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
106
|
+
};
|
|
107
|
+
export declare function resolveOpenedFileRealPathForHandle(handle: FileHandle, ioPath: string): Promise<string>;
|
|
108
|
+
//# sourceMappingURL=root-impl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"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"}
|