@openclaw/fs-safe 0.1.0 → 0.1.2
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 +24 -0
- package/README.md +23 -3
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +51 -7
- package/dist/archive.js +2 -2
- package/dist/file-store-boundary.d.ts +27 -0
- package/dist/file-store-boundary.d.ts.map +1 -0
- package/dist/file-store-boundary.js +164 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +62 -72
- package/dist/path.d.ts +1 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +6 -3
- 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/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} +464 -251
- package/dist/root.d.ts +1 -107
- package/dist/root.d.ts.map +1 -1
- package/dist/root.js +1 -1332
- package/docs/advanced.md +100 -0
- package/docs/archive.md +199 -0
- package/docs/atomic.md +163 -0
- package/docs/config.md +65 -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 +76 -0
- package/docs/install-path.md +167 -0
- package/docs/install.md +150 -0
- package/docs/json-store.md +200 -0
- package/docs/json.md +158 -0
- package/docs/local-roots.md +164 -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 +229 -0
- package/docs/store.md +44 -0
- package/docs/temp.md +276 -0
- package/docs/test-hooks.md +74 -0
- package/docs/testing.md +168 -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 +5 -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
package/dist/path.js
CHANGED
|
@@ -22,6 +22,11 @@ export function isNodeError(value) {
|
|
|
22
22
|
export function hasNodeErrorCode(value, code) {
|
|
23
23
|
return isNodeError(value) && value.code === code;
|
|
24
24
|
}
|
|
25
|
+
export function assertNoNulPathInput(filePath, message = "path contains a NUL byte") {
|
|
26
|
+
if (filePath.includes("\0")) {
|
|
27
|
+
throw new FsSafeError("invalid-path", message);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
25
30
|
export function isNotFoundPathError(value) {
|
|
26
31
|
return isNodeError(value) && typeof value.code === "string" && NOT_FOUND_CODES.has(value.code);
|
|
27
32
|
}
|
|
@@ -94,9 +99,7 @@ export function splitSafeRelativePath(relativePath) {
|
|
|
94
99
|
if (relativePath.length === 0 || relativePath === ".") {
|
|
95
100
|
return [];
|
|
96
101
|
}
|
|
97
|
-
|
|
98
|
-
throw new FsSafeError("invalid-path", "relative path contains a NUL byte");
|
|
99
|
-
}
|
|
102
|
+
assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
|
|
100
103
|
if (relativePath.includes("\\")) {
|
|
101
104
|
throw new FsSafeError("invalid-path", "relative path must use forward slashes");
|
|
102
105
|
}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
export type PrivateStateStoreOptions = {
|
|
1
|
+
export type PrivateFileStore = {
|
|
3
2
|
rootDir: string;
|
|
4
|
-
|
|
5
|
-
export type PrivateStateStore = Omit<FileStore, "readText" | "readJson" | "writeText" | "writeJson"> & {
|
|
3
|
+
path(relativePath: string): string;
|
|
6
4
|
readText(relativePath: string, options?: {
|
|
7
5
|
maxBytes?: number;
|
|
8
6
|
}): Promise<string | null>;
|
|
9
7
|
readJson<T = unknown>(relativePath: string, options?: {
|
|
10
8
|
maxBytes?: number;
|
|
11
9
|
}): Promise<T | null>;
|
|
12
|
-
writeText(relativePath: string, content: string | Uint8Array): Promise<
|
|
10
|
+
writeText(relativePath: string, content: string | Uint8Array): Promise<void>;
|
|
13
11
|
writeJson(relativePath: string, value: unknown, options?: {
|
|
14
12
|
trailingNewline?: boolean;
|
|
15
|
-
}): Promise<
|
|
13
|
+
}): Promise<void>;
|
|
16
14
|
};
|
|
17
15
|
export declare function writePrivateTextAtomic(params: {
|
|
18
16
|
rootDir: string;
|
|
@@ -56,5 +54,5 @@ export declare function writePrivateJsonAtomicSync(params: {
|
|
|
56
54
|
value: unknown;
|
|
57
55
|
trailingNewline?: boolean;
|
|
58
56
|
}): void;
|
|
59
|
-
export declare function
|
|
57
|
+
export declare function privateFileStore(rootDir: string): PrivateFileStore;
|
|
60
58
|
//# sourceMappingURL=private-file-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"private-file-store.d.ts","sourceRoot":"","sources":["../src/private-file-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"private-file-store.d.ts","sourceRoot":"","sources":["../src/private-file-store.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxF,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChG,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzG,CAAC;AAgBF,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;CAC9B,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhB;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBzB;AAoBD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,GAAG,IAAI,CAiBhB;AAED,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAGpB;AAED,wBAAgB,mBAAmB,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,CAAC,GAAG,IAAI,CAGX;AAoCD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;CAC9B,GAAG,IAAI,CA4CP;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,IAAI,CAOP;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAiClE"}
|
|
@@ -2,11 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
|
-
import { fileStore } from "./file-store.js";
|
|
6
5
|
import { isPathInside } from "./path.js";
|
|
7
6
|
import { readRegularFileSync } from "./regular-file.js";
|
|
8
7
|
import { root } from "./root.js";
|
|
9
|
-
import {
|
|
8
|
+
import { writePrivateSecretFileAtomic } from "./secret-file.js";
|
|
10
9
|
function resolvePrivateStorePath(rootDir, relativePath) {
|
|
11
10
|
const root = path.resolve(rootDir);
|
|
12
11
|
const raw = relativePath.trim();
|
|
@@ -21,7 +20,7 @@ function resolvePrivateStorePath(rootDir, relativePath) {
|
|
|
21
20
|
return resolved;
|
|
22
21
|
}
|
|
23
22
|
export async function writePrivateTextAtomic(params) {
|
|
24
|
-
await
|
|
23
|
+
await writePrivateSecretFileAtomic(params);
|
|
25
24
|
}
|
|
26
25
|
export async function readPrivateText(params) {
|
|
27
26
|
const rootDir = path.resolve(params.rootDir);
|
|
@@ -173,7 +172,7 @@ export function writePrivateTextAtomicSync(params) {
|
|
|
173
172
|
}
|
|
174
173
|
export async function writePrivateJsonAtomic(params) {
|
|
175
174
|
const json = JSON.stringify(params.value, null, 2);
|
|
176
|
-
await
|
|
175
|
+
await writePrivateSecretFileAtomic({
|
|
177
176
|
rootDir: params.rootDir,
|
|
178
177
|
filePath: params.filePath,
|
|
179
178
|
content: params.trailingNewline && !json.endsWith("\n") ? `${json}\n` : json,
|
|
@@ -187,47 +186,35 @@ export function writePrivateJsonAtomicSync(params) {
|
|
|
187
186
|
content: params.trailingNewline && !json.endsWith("\n") ? `${json}\n` : json,
|
|
188
187
|
});
|
|
189
188
|
}
|
|
190
|
-
export function
|
|
191
|
-
const
|
|
192
|
-
const store = fileStore({ rootDir, private: true });
|
|
189
|
+
export function privateFileStore(rootDir) {
|
|
190
|
+
const root = path.resolve(rootDir);
|
|
193
191
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const safePath = resolvePrivateStorePath(rootDir, relativePath);
|
|
207
|
-
return await readPrivateJson({
|
|
208
|
-
rootDir,
|
|
209
|
-
filePath: safePath,
|
|
210
|
-
maxBytes: options?.maxBytes,
|
|
211
|
-
});
|
|
212
|
-
},
|
|
192
|
+
rootDir: root,
|
|
193
|
+
path: (relativePath) => resolvePrivateStorePath(root, relativePath),
|
|
194
|
+
readText: async (relativePath, options) => await readPrivateText({
|
|
195
|
+
rootDir: root,
|
|
196
|
+
filePath: resolvePrivateStorePath(root, relativePath),
|
|
197
|
+
maxBytes: options?.maxBytes,
|
|
198
|
+
}),
|
|
199
|
+
readJson: async (relativePath, options) => await readPrivateJson({
|
|
200
|
+
rootDir: root,
|
|
201
|
+
filePath: resolvePrivateStorePath(root, relativePath),
|
|
202
|
+
maxBytes: options?.maxBytes,
|
|
203
|
+
}),
|
|
213
204
|
writeText: async (relativePath, content) => {
|
|
214
|
-
const safePath = resolvePrivateStorePath(rootDir, relativePath);
|
|
215
205
|
await writePrivateTextAtomic({
|
|
216
|
-
rootDir,
|
|
217
|
-
filePath:
|
|
206
|
+
rootDir: root,
|
|
207
|
+
filePath: resolvePrivateStorePath(root, relativePath),
|
|
218
208
|
content,
|
|
219
209
|
});
|
|
220
|
-
return safePath;
|
|
221
210
|
},
|
|
222
211
|
writeJson: async (relativePath, value, options) => {
|
|
223
|
-
const safePath = resolvePrivateStorePath(rootDir, relativePath);
|
|
224
212
|
await writePrivateJsonAtomic({
|
|
225
|
-
rootDir,
|
|
226
|
-
filePath:
|
|
213
|
+
rootDir: root,
|
|
214
|
+
filePath: resolvePrivateStorePath(root, relativePath),
|
|
227
215
|
value,
|
|
228
216
|
trailingNewline: options?.trailingNewline,
|
|
229
217
|
});
|
|
230
|
-
return safePath;
|
|
231
218
|
},
|
|
232
219
|
};
|
|
233
220
|
}
|
|
@@ -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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"}
|