@openclaw/fs-safe 0.10.0 → 0.12.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 +114 -0
- package/LICENSE +1 -0
- package/README.md +39 -6
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +3 -2
- package/dist/advanced.d.ts +5 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +5 -1
- package/dist/archive-durability.d.ts +6 -6
- package/dist/archive-durability.d.ts.map +1 -1
- package/dist/archive-durability.js +1 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +8 -8
- package/dist/archive-gzip-tail.d.ts +1 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +16 -9
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +4 -2
- package/dist/archive-merge.d.ts +5 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -12
- package/dist/archive-native.js +4 -4
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +54 -42
- package/dist/archive-staging.d.ts +6 -3
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +42 -22
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +7 -6
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +16 -13
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +48 -12
- package/dist/archive-zip-loader.d.ts +6 -0
- package/dist/archive-zip-loader.d.ts.map +1 -0
- package/dist/archive-zip-loader.js +38 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +26 -9
- package/dist/archive-zip-preflight.d.ts +2 -3
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +2 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +12 -10
- package/dist/bounded-read.d.ts +5 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +18 -11
- package/dist/copy-file-input.d.ts +0 -1
- package/dist/copy-file-input.d.ts.map +1 -1
- package/dist/copy-file-input.js +4 -26
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +57 -26
- package/dist/copy.d.ts +1 -1
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +3 -1
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -3
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +5 -4
- package/dist/directory-guard.d.ts +11 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +53 -11
- package/dist/durability.d.ts +1 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +1 -1
- package/dist/file-handle-transfer.d.ts +14 -0
- package/dist/file-handle-transfer.d.ts.map +1 -0
- package/dist/file-handle-transfer.js +64 -0
- package/dist/file-hash.d.ts +3 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +99 -32
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -4
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +7 -5
- package/dist/file-store-path.d.ts +3 -0
- package/dist/file-store-path.d.ts.map +1 -0
- package/dist/file-store-path.js +27 -0
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +15 -5
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +56 -44
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +2 -18
- package/dist/filename.d.ts +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +32 -14
- package/dist/guarded-mkdir.d.ts +2 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +52 -16
- package/dist/guest-dispatch-python.d.ts +2 -0
- package/dist/guest-dispatch-python.d.ts.map +1 -0
- package/dist/guest-dispatch-python.js +117 -0
- package/dist/guest-native-python.d.ts +4 -0
- package/dist/guest-native-python.d.ts.map +1 -0
- package/dist/guest-native-python.js +135 -0
- package/dist/guest.d.ts +9 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +421 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/install-path.d.ts +6 -0
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +16 -2
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue-ownership.d.ts +2 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +47 -3
- package/dist/json-durable-queue-read.d.ts +6 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -0
- package/dist/json-durable-queue-read.js +59 -0
- package/dist/json-durable-queue.d.ts +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +43 -84
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -1
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +2 -1
- package/dist/move-path-stage.d.ts.map +1 -1
- package/dist/move-path-stage.js +2 -1
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +4 -3
- package/dist/mutation-authority.d.ts +1 -0
- package/dist/mutation-authority.d.ts.map +1 -1
- package/dist/mutation-authority.js +4 -4
- package/dist/native-binding.d.ts +14 -2
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +3 -2
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +2 -1
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +5 -4
- package/dist/output.d.ts +2 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +2 -0
- package/dist/overwrite-file-handle.d.ts +8 -0
- package/dist/overwrite-file-handle.d.ts.map +1 -0
- package/dist/overwrite-file-handle.js +42 -0
- package/dist/path-case.d.ts +7 -0
- package/dist/path-case.d.ts.map +1 -0
- package/dist/path-case.js +136 -0
- package/dist/path-scope-lexical.d.ts +14 -0
- package/dist/path-scope-lexical.d.ts.map +1 -0
- package/dist/path-scope-lexical.js +27 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +22 -3
- package/dist/permissions-windows.d.ts +1 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +48 -6
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +3 -1
- package/dist/pinned-write.d.ts +2 -2
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +6 -4
- package/dist/realpath.d.ts +4 -0
- package/dist/realpath.d.ts.map +1 -0
- package/dist/realpath.js +43 -0
- package/dist/recursive-mkdir-path.d.ts +3 -0
- package/dist/recursive-mkdir-path.d.ts.map +1 -0
- package/dist/recursive-mkdir-path.js +8 -0
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +2 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +23 -31
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +7 -12
- package/dist/replace-file-mode.d.ts +3 -0
- package/dist/replace-file-mode.d.ts.map +1 -0
- package/dist/replace-file-mode.js +10 -0
- package/dist/replace-file.d.ts +1 -0
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +14 -8
- package/dist/root-boundary.d.ts +25 -0
- package/dist/root-boundary.d.ts.map +1 -0
- package/dist/root-boundary.js +177 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +40 -13
- package/dist/root-create-input.d.ts +10 -0
- package/dist/root-create-input.d.ts.map +1 -0
- package/dist/root-create-input.js +80 -0
- package/dist/root-directory-list.d.ts +3 -1
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +31 -5
- package/dist/root-entries.d.ts +11 -0
- package/dist/root-entries.d.ts.map +1 -0
- package/dist/root-entries.js +61 -0
- package/dist/root-errors.d.ts +5 -5
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +13 -12
- package/dist/root-impl.d.ts +13 -3
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +93 -70
- package/dist/root-move-preflight.d.ts +8 -0
- package/dist/root-move-preflight.d.ts.map +1 -0
- package/dist/root-move-preflight.js +16 -0
- package/dist/root-options.d.ts +12 -1
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-existing.d.ts +2 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +14 -5
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +3 -2
- package/dist/root-path.d.ts +2 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +54 -26
- package/dist/root-paths.d.ts +2 -6
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +24 -32
- package/dist/root-remove.d.ts +5 -0
- package/dist/root-remove.d.ts.map +1 -0
- package/dist/root-remove.js +286 -0
- package/dist/root-symlink-policy.d.ts +2 -1
- package/dist/root-symlink-policy.d.ts.map +1 -1
- package/dist/root-symlink-policy.js +2 -2
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +2 -1
- package/dist/root-write-mode.d.ts +2 -0
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +21 -7
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +12 -3
- package/dist/root.d.ts +2 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +3 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +2 -1
- package/dist/secure-file-windows.d.ts +10 -0
- package/dist/secure-file-windows.d.ts.map +1 -0
- package/dist/secure-file-windows.js +186 -0
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +27 -7
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +2 -1
- package/dist/sibling-staged-file.d.ts +2 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +49 -8
- package/dist/sibling-temp.d.ts +2 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +8 -5
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +16 -5
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +17 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -3
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +4 -3
- package/dist/temp-target.d.ts +14 -12
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +15 -7
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +7 -5
- package/dist/unicode-path.d.ts +3 -0
- package/dist/unicode-path.d.ts.map +1 -0
- package/dist/unicode-path.js +13 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +14 -12
- package/dist/write-file-handle.d.ts +1 -0
- package/dist/write-file-handle.d.ts.map +1 -1
- package/dist/write-file-handle.js +3 -2
- package/docs/advanced.md +7 -1
- package/docs/archive.md +31 -5
- package/docs/atomic.md +17 -1
- package/docs/config.md +1 -0
- package/docs/contributing.md +33 -2
- package/docs/copy.md +75 -6
- package/docs/directory-identity.md +85 -0
- package/docs/durability.md +40 -3
- package/docs/entries.md +109 -0
- package/docs/errors.md +3 -3
- package/docs/file-store.md +21 -0
- package/docs/filename.md +9 -2
- package/docs/guest.md +146 -0
- package/docs/in-place-write.md +81 -0
- package/docs/index.md +2 -0
- package/docs/install-path.md +59 -13
- package/docs/install.md +34 -0
- package/docs/native-helper.md +14 -5
- package/docs/native.md +18 -2
- package/docs/output.md +32 -6
- package/docs/path-case.md +64 -0
- package/docs/path-scope.md +1 -1
- package/docs/path.md +1 -1
- package/docs/permissions.md +37 -3
- package/docs/public-api.md +36 -2
- package/docs/root.md +33 -3
- package/docs/secure-file.md +17 -14
- package/docs/security-model.md +1 -1
- package/docs/sidecar-lock.md +12 -3
- package/docs/store.md +22 -1
- package/docs/temp.md +39 -6
- package/docs/types.md +1 -1
- package/docs/writing.md +153 -3
- package/package.json +14 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,UAAU,EAAE,oBAAoB,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAgKF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAmB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAuCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAuBD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA8ElC"}
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { ensureAbsoluteDirectory } from "./absolute-path.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { sameFileIdentity } from "./file-identity.js";
|
|
7
|
+
import { realpathSync } from "./realpath.js";
|
|
7
8
|
function directoryOpenFlags() {
|
|
8
9
|
if (process.platform === "win32") {
|
|
9
10
|
return "r";
|
|
@@ -47,7 +48,7 @@ async function createDirectoryReceipt(directoryPath, label) {
|
|
|
47
48
|
assertDirectory(identity, resolvedPath, label);
|
|
48
49
|
return {
|
|
49
50
|
path: resolvedPath,
|
|
50
|
-
realPath:
|
|
51
|
+
realPath: realpathSync.native(resolvedPath),
|
|
51
52
|
identity,
|
|
52
53
|
};
|
|
53
54
|
}
|
|
@@ -57,7 +58,7 @@ function createDirectoryReceiptSync(directoryPath, label) {
|
|
|
57
58
|
assertDirectory(identity, resolvedPath, label);
|
|
58
59
|
return {
|
|
59
60
|
path: resolvedPath,
|
|
60
|
-
realPath:
|
|
61
|
+
realPath: realpathSync(resolvedPath),
|
|
61
62
|
identity,
|
|
62
63
|
};
|
|
63
64
|
}
|
|
@@ -65,7 +66,7 @@ async function assertDirectoryReceiptCurrent(receipt, label) {
|
|
|
65
66
|
const currentIdentity = fsSync.lstatSync(receipt.path);
|
|
66
67
|
assertDirectory(currentIdentity, receipt.path, label);
|
|
67
68
|
if (!sameFileIdentity(receipt.identity, currentIdentity) ||
|
|
68
|
-
(
|
|
69
|
+
(realpathSync.native(receipt.path)) !== receipt.realPath) {
|
|
69
70
|
throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
@@ -73,7 +74,7 @@ function assertDirectoryReceiptCurrentSync(receipt, label) {
|
|
|
73
74
|
const currentIdentity = fsSync.lstatSync(receipt.path);
|
|
74
75
|
assertDirectory(currentIdentity, receipt.path, label);
|
|
75
76
|
if (!sameFileIdentity(receipt.identity, currentIdentity) ||
|
|
76
|
-
|
|
77
|
+
realpathSync(receipt.path) !== receipt.realPath) {
|
|
77
78
|
throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
|
|
78
79
|
}
|
|
79
80
|
}
|
|
@@ -10,6 +10,15 @@ export type SyncDirectoryGuard = {
|
|
|
10
10
|
realPath: string;
|
|
11
11
|
stat: Stats;
|
|
12
12
|
};
|
|
13
|
+
export type DirectoryIdentity = Readonly<{
|
|
14
|
+
dev: bigint;
|
|
15
|
+
ino: bigint;
|
|
16
|
+
realPath: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function readDirectoryIdentity(dir: string): Promise<DirectoryIdentity>;
|
|
19
|
+
export declare function assertDirectoryIdentitySync(observedPath: string, expected: Pick<DirectoryIdentity, "dev" | "ino"> & {
|
|
20
|
+
realPath?: string;
|
|
21
|
+
}): void;
|
|
13
22
|
export declare function createAsyncDirectoryGuard(dir: string, options: {
|
|
14
23
|
bigint: true;
|
|
15
24
|
}): Promise<AsyncDirectoryGuard<BigIntStats>>;
|
|
@@ -21,11 +30,12 @@ export declare function createAsyncDirectoryGuard(dir: string, options: {
|
|
|
21
30
|
}): Promise<AnyAsyncDirectoryGuard>;
|
|
22
31
|
export declare function assertAsyncDirectoryGuard(guard: AnyAsyncDirectoryGuard): Promise<void>;
|
|
23
32
|
export declare function createSyncDirectoryGuard(dir: string): SyncDirectoryGuard;
|
|
24
|
-
export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard): void;
|
|
33
|
+
export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard | AnyAsyncDirectoryGuard): void;
|
|
25
34
|
export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string): Promise<AsyncDirectoryGuard>;
|
|
26
35
|
export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
|
|
27
36
|
bigint: boolean;
|
|
28
37
|
}): Promise<AnyAsyncDirectoryGuard>;
|
|
29
38
|
export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
|
|
30
39
|
export declare function inspectDirectoryIdentity(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): Promise<BigIntStats>;
|
|
40
|
+
export declare function inspectDirectoryIdentitySync(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): BigIntStats;
|
|
31
41
|
//# sourceMappingURL=directory-guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUlD,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,KAAK,GAAG,WAAW,GAAG,KAAK,IAAI;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAGnF;AAED,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE,IAAI,CAKN;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7H,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACnH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAStH,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5F;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,sBAAsB,GAAG,IAAI,CAajG;AAED,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACxH,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAqBzJ,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB;AAGD,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAE7H;AAmBD,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAC1C,WAAW,CAQb"}
|
package/dist/directory-guard.js
CHANGED
|
@@ -2,24 +2,37 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { sameFileIdentity } from "./file-identity.js";
|
|
5
|
-
import {
|
|
5
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
6
6
|
import { isNotFoundPathError } from "./path.js";
|
|
7
7
|
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
8
|
+
import { realpathSync } from "./realpath.js";
|
|
9
|
+
export async function readDirectoryIdentity(dir) {
|
|
10
|
+
const guard = await createAsyncDirectoryGuard(dir, { bigint: true });
|
|
11
|
+
return Object.freeze({ dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
|
|
12
|
+
}
|
|
13
|
+
export function assertDirectoryIdentitySync(observedPath, expected) {
|
|
14
|
+
inspectDirectoryIdentitySync(observedPath, expected);
|
|
15
|
+
if (expected.realPath !== undefined && realpathSync.native(observedPath) !== expected.realPath) {
|
|
16
|
+
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
8
19
|
export async function createAsyncDirectoryGuard(dir, options) {
|
|
9
|
-
const stat = options?.bigint ?
|
|
20
|
+
const stat = options?.bigint ? inspectDirectoryIdentitySync(dir) : fsSync.lstatSync(dir);
|
|
10
21
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
11
22
|
throw directoryComponentNotDirectoryError();
|
|
12
23
|
}
|
|
13
|
-
return { dir, realPath:
|
|
24
|
+
return { dir, realPath: realpathSync.native(dir), stat };
|
|
14
25
|
}
|
|
15
26
|
export async function assertAsyncDirectoryGuard(guard) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
if (typeof guard.stat.dev === "bigint" && typeof guard.stat.ino === "bigint") {
|
|
28
|
+
assertDirectoryIdentitySync(guard.dir, { dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const stat = fsSync.lstatSync(guard.dir);
|
|
19
32
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
20
33
|
throw directoryComponentNotDirectoryError();
|
|
21
34
|
}
|
|
22
|
-
if (!sameFileIdentity(stat, guard.stat) ||
|
|
35
|
+
if (!sameFileIdentity(stat, guard.stat) || realpathSync.native(guard.dir) !== guard.realPath) {
|
|
23
36
|
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
24
37
|
}
|
|
25
38
|
}
|
|
@@ -28,14 +41,19 @@ export function createSyncDirectoryGuard(dir) {
|
|
|
28
41
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
29
42
|
throw directoryComponentNotDirectoryError();
|
|
30
43
|
}
|
|
31
|
-
return { dir, realPath:
|
|
44
|
+
return { dir, realPath: realpathSync(dir), stat };
|
|
32
45
|
}
|
|
33
46
|
export function assertSyncDirectoryGuard(guard) {
|
|
47
|
+
if (typeof guard.stat.dev === "bigint" && typeof guard.stat.ino === "bigint") {
|
|
48
|
+
assertDirectoryIdentitySync(guard.dir, { dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
34
51
|
const stat = fsSync.lstatSync(guard.dir);
|
|
35
52
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
36
53
|
throw directoryComponentNotDirectoryError();
|
|
37
54
|
}
|
|
38
|
-
|
|
55
|
+
const realPath = realpathSync(guard.dir);
|
|
56
|
+
if (!sameFileIdentity(stat, guard.stat) || realPath !== guard.realPath) {
|
|
39
57
|
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
40
58
|
}
|
|
41
59
|
}
|
|
@@ -73,8 +91,32 @@ export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
|
|
|
73
91
|
}
|
|
74
92
|
// Recovery receipts must retain every identity bit, including on Windows.
|
|
75
93
|
export async function inspectDirectoryIdentity(dir, expected) {
|
|
76
|
-
return
|
|
77
|
-
|
|
94
|
+
return inspectDirectoryIdentitySync(dir, expected);
|
|
95
|
+
}
|
|
96
|
+
function directoryEntryPath(dir) {
|
|
97
|
+
const windows = process.platform === "win32";
|
|
98
|
+
if (typeof dir === "string") {
|
|
99
|
+
const last = dir[dir.length - 1];
|
|
100
|
+
if (last !== "/" && (!windows || last !== "\\"))
|
|
101
|
+
return dir;
|
|
102
|
+
}
|
|
103
|
+
let rootLength = (windows ? path.win32 : path.posix).parse(dir).root.length;
|
|
104
|
+
if (windows && /^[\\/]{2}[?.][\\/]UNC[\\/]/i.test(dir)) {
|
|
105
|
+
// Node parses the namespace prefix as the root; retain the complete UNC share instead.
|
|
106
|
+
const uncRoot = path.win32.parse(`\\\\${dir.slice(8)}`).root;
|
|
107
|
+
if (uncRoot.length > 1)
|
|
108
|
+
rootLength = Math.max(rootLength, uncRoot.length + 6);
|
|
109
|
+
}
|
|
110
|
+
let end = dir.length;
|
|
111
|
+
while (end > rootLength && (dir[end - 1] === "/" || (windows && dir[end - 1] === "\\")))
|
|
112
|
+
end--;
|
|
113
|
+
return end === rootLength || end === dir.length ? dir : dir.slice(0, end);
|
|
114
|
+
}
|
|
115
|
+
export function inspectDirectoryIdentitySync(dir, expected) {
|
|
116
|
+
// A trailing separator makes lstat follow a final directory symlink.
|
|
117
|
+
const entryPath = directoryEntryPath(dir);
|
|
118
|
+
return inspectFileIdentitySync(() => {
|
|
119
|
+
const stat = fsSync.lstatSync(entryPath, { bigint: true });
|
|
78
120
|
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
79
121
|
throw directoryComponentNotDirectoryError();
|
|
80
122
|
return stat;
|
package/dist/durability.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
|
|
2
2
|
export { isHardlinkFallbackError, publishFileExclusive, type PublishFileExclusiveResult, type PublishFileExclusiveStrategy, type PublishFileExclusiveCleanup, type PublishFileExclusiveDirectorySyncFailure, type PublishFileExclusiveFailureDetails, type PublishFileExclusiveFailurePhase, type PublishFileExclusiveSyncFailurePolicy, } from "./publish-file.js";
|
|
3
|
-
export { sha256File, type Sha256FileInput, type Sha256FileOptions, type Sha256FileResult, } from "./file-hash.js";
|
|
3
|
+
export { sha256File, sha256FileSync, type Sha256FileInput, type Sha256FileSyncInput, type Sha256FileOptions, type Sha256FileResult, } from "./file-hash.js";
|
|
4
4
|
//# sourceMappingURL=durability.d.ts.map
|
package/dist/durability.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
|
package/dist/durability.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, } from "./directory-durability.js";
|
|
2
2
|
export { isHardlinkFallbackError, publishFileExclusive, } from "./publish-file.js";
|
|
3
|
-
export { sha256File, } from "./file-hash.js";
|
|
3
|
+
export { sha256File, sha256FileSync, } from "./file-hash.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FileHandle } from "node:fs/promises";
|
|
2
|
+
export type CopyFileHandleOptions = {
|
|
3
|
+
maxBytes?: number;
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
onChunk?: (chunk: Uint8Array) => void;
|
|
6
|
+
assertBeforeMutation?: () => void;
|
|
7
|
+
};
|
|
8
|
+
/** Copies caller-owned regular files from offset zero without moving either cursor. */
|
|
9
|
+
export declare function copyFileHandle(source: FileHandle, target: FileHandle, options?: CopyFileHandleOptions): Promise<number>;
|
|
10
|
+
export declare function transferFileHandle(source: FileHandle, target: FileHandle | number, options: CopyFileHandleOptions & {
|
|
11
|
+
sizeHint: number;
|
|
12
|
+
targetPosition?: number;
|
|
13
|
+
}): Promise<number>;
|
|
14
|
+
//# sourceMappingURL=file-handle-transfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-handle-transfer.d.ts","sourceRoot":"","sources":["../src/file-handle-transfer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAWnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;AAEF,uFAAuF;AACvF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAGD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,OAAO,EAAE,qBAAqB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E,OAAO,CAAC,MAAM,CAAC,CAyBjB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import { assertSynchronousCallbackResult, composeMutationAssertions, rethrowMutationAuthorityError, } from "./mutation-authority.js";
|
|
4
|
+
import { writeAllToFile } from "./write-file-handle.js";
|
|
5
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
6
|
+
/** Copies caller-owned regular files from offset zero without moving either cursor. */
|
|
7
|
+
export async function copyFileHandle(source, target, options = {}) {
|
|
8
|
+
options.signal?.throwIfAborted();
|
|
9
|
+
const maxBytes = normalizeMaxBytes(options.maxBytes);
|
|
10
|
+
const sourceStat = await inspectFileIdentity(() => {
|
|
11
|
+
options.signal?.throwIfAborted();
|
|
12
|
+
return source.stat({ bigint: true });
|
|
13
|
+
});
|
|
14
|
+
options.signal?.throwIfAborted();
|
|
15
|
+
if (!sourceStat.isFile())
|
|
16
|
+
throw new FsSafeError("not-file", "copy source handle must be a regular file");
|
|
17
|
+
if (maxBytes !== undefined && Number.isFinite(maxBytes) && sourceStat.size > BigInt(maxBytes)) {
|
|
18
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`);
|
|
19
|
+
}
|
|
20
|
+
const targetStat = await inspectFileIdentity(() => {
|
|
21
|
+
options.signal?.throwIfAborted();
|
|
22
|
+
return target.stat({ bigint: true });
|
|
23
|
+
});
|
|
24
|
+
options.signal?.throwIfAborted();
|
|
25
|
+
if (!targetStat.isFile())
|
|
26
|
+
throw new FsSafeError("not-file", "copy target handle must be a regular file");
|
|
27
|
+
if (sourceStat.dev === targetStat.dev && sourceStat.ino === targetStat.ino) {
|
|
28
|
+
throw new FsSafeError("path-alias", "copy source and target handles refer to the same file");
|
|
29
|
+
}
|
|
30
|
+
return await transferFileHandle(source, target, {
|
|
31
|
+
...options,
|
|
32
|
+
maxBytes,
|
|
33
|
+
sizeHint: Number(sourceStat.size),
|
|
34
|
+
targetPosition: 0,
|
|
35
|
+
assertBeforeMutation: composeMutationAssertions(undefined, options.assertBeforeMutation),
|
|
36
|
+
}).catch(rethrowMutationAuthorityError);
|
|
37
|
+
}
|
|
38
|
+
// Root.copyIn supplies already-admitted identities and retains its owned target cursor.
|
|
39
|
+
export async function transferFileHandle(source, target, options) {
|
|
40
|
+
const maxBytes = options.maxBytes ?? Infinity;
|
|
41
|
+
const buffer = Buffer.allocUnsafe(Math.min(512 * 1024, Math.max(64 * 1024, options.sizeHint), maxBytes + 1));
|
|
42
|
+
let position = 0;
|
|
43
|
+
while (true) {
|
|
44
|
+
options.signal?.throwIfAborted();
|
|
45
|
+
const { bytesRead } = await source.read(buffer, 0, Math.min(buffer.length, maxBytes - position + 1), position);
|
|
46
|
+
options.signal?.throwIfAborted();
|
|
47
|
+
if (bytesRead > maxBytes - position) {
|
|
48
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`);
|
|
49
|
+
}
|
|
50
|
+
if (bytesRead === 0)
|
|
51
|
+
return position;
|
|
52
|
+
const chunk = buffer.subarray(0, bytesRead);
|
|
53
|
+
if (options.onChunk)
|
|
54
|
+
assertSynchronousCallbackResult(options.onChunk(chunk), "onChunk");
|
|
55
|
+
await writeAllToFile(target, chunk, {
|
|
56
|
+
position: options.targetPosition === undefined ? undefined : options.targetPosition + position,
|
|
57
|
+
assertBeforeMutation: () => {
|
|
58
|
+
options.signal?.throwIfAborted();
|
|
59
|
+
options.assertBeforeMutation?.();
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
position += bytesRead;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/file-hash.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FileHandle } from "node:fs/promises";
|
|
2
2
|
import { type NativeBinding } from "./native.js";
|
|
3
3
|
export type Sha256FileInput = string | FileHandle;
|
|
4
|
+
export type Sha256FileSyncInput = string | number;
|
|
4
5
|
export type Sha256FileOptions = {
|
|
5
6
|
maxBytes?: number;
|
|
6
7
|
signal?: AbortSignal;
|
|
@@ -11,4 +12,6 @@ export type Sha256FileResult = {
|
|
|
11
12
|
};
|
|
12
13
|
export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined, { maxBytes, signal }?: Sha256FileOptions): Promise<Sha256FileResult>;
|
|
13
14
|
export declare function sha256File(input: Sha256FileInput, options?: Sha256FileOptions): Promise<Sha256FileResult>;
|
|
15
|
+
/** Hash synchronously from offset zero, retaining ownership and position of a borrowed fd. */
|
|
16
|
+
export declare function sha256FileSync(input: Sha256FileSyncInput, options?: Sha256FileOptions): Sha256FileResult;
|
|
14
17
|
//# sourceMappingURL=file-hash.d.ts.map
|
package/dist/file-hash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAInE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,EACtD,EAAE,QAAmB,EAAE,MAAM,EAAE,GAAE,iBAAsB,GACtD,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAInE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAClD,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,EACtD,EAAE,QAAmB,EAAE,MAAM,EAAE,GAAE,iBAAsB,GACtD,OAAO,CAAC,gBAAgB,CAAC,CAwD3B;AAyDD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAuDD,8FAA8F;AAC9F,wBAAgB,cAAc,CAC5B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAMlB"}
|
package/dist/file-hash.js
CHANGED
|
@@ -5,7 +5,7 @@ import { normalizeMaxBytes } from "./byte-budget.js";
|
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { getNativeBinding } from "./native.js";
|
|
7
7
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
8
|
-
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
8
|
+
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
9
9
|
export async function hashFileHandle(handle, native = getNativeBinding(), { maxBytes = Infinity, signal } = {}) {
|
|
10
10
|
signal?.throwIfAborted();
|
|
11
11
|
const stat = fsSync.fstatSync(handle.fd);
|
|
@@ -38,7 +38,7 @@ export async function hashFileHandle(handle, native = getNativeBinding(), { maxB
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
const hash = createHash("sha256");
|
|
41
|
-
|
|
41
|
+
let buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(1, stat.size + 1), maxBytes + 1));
|
|
42
42
|
let position = 0;
|
|
43
43
|
while (true) {
|
|
44
44
|
signal?.throwIfAborted();
|
|
@@ -53,48 +53,54 @@ export async function hashFileHandle(handle, native = getNativeBinding(), { maxB
|
|
|
53
53
|
}
|
|
54
54
|
hash.update(buffer.subarray(0, bytesRead));
|
|
55
55
|
position += bytesRead;
|
|
56
|
+
// A full small buffer can mean growth or a virtual file with an unhelpful size.
|
|
57
|
+
if (bytesRead === buffer.length && buffer.length < 64 * 1024) {
|
|
58
|
+
buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function hashPathIdentity(filePath, afterOpen = false) {
|
|
63
|
+
const stat = fsSync.lstatSync(filePath, { bigint: true });
|
|
64
|
+
if (afterOpen && (stat.isSymbolicLink() || !stat.isFile())) {
|
|
65
|
+
throw new FsSafeError("path-mismatch", "SHA-256 path changed while opening");
|
|
66
|
+
}
|
|
67
|
+
if (stat.isSymbolicLink()) {
|
|
68
|
+
throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link");
|
|
56
69
|
}
|
|
70
|
+
if (!stat.isFile()) {
|
|
71
|
+
throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
|
|
72
|
+
}
|
|
73
|
+
return stat;
|
|
74
|
+
}
|
|
75
|
+
function hashDescriptorIdentity(fd) {
|
|
76
|
+
const stat = fsSync.fstatSync(fd, { bigint: true });
|
|
77
|
+
if (!stat.isFile()) {
|
|
78
|
+
throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
|
|
79
|
+
}
|
|
80
|
+
return stat;
|
|
81
|
+
}
|
|
82
|
+
function rethrowHashOpenError(error) {
|
|
83
|
+
if (error?.code === "ELOOP") {
|
|
84
|
+
throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link", {
|
|
85
|
+
cause: error,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
57
89
|
}
|
|
58
90
|
async function hashPath(filePath, options) {
|
|
59
|
-
const before = await inspectFileIdentity(
|
|
60
|
-
const stat = fsSync.lstatSync(filePath, { bigint: true });
|
|
61
|
-
if (stat.isSymbolicLink()) {
|
|
62
|
-
throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link");
|
|
63
|
-
}
|
|
64
|
-
if (!stat.isFile()) {
|
|
65
|
-
throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
|
|
66
|
-
}
|
|
67
|
-
return stat;
|
|
68
|
-
});
|
|
91
|
+
const before = await inspectFileIdentity(() => hashPathIdentity(filePath));
|
|
69
92
|
let handle;
|
|
70
93
|
try {
|
|
71
94
|
options.signal?.throwIfAborted();
|
|
72
95
|
handle = await fs.open(filePath, resolveReadOpenFlags());
|
|
73
96
|
}
|
|
74
97
|
catch (error) {
|
|
75
|
-
|
|
76
|
-
throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link", {
|
|
77
|
-
cause: error,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
throw error;
|
|
98
|
+
rethrowHashOpenError(error);
|
|
81
99
|
}
|
|
82
100
|
try {
|
|
83
101
|
options.signal?.throwIfAborted();
|
|
84
|
-
const opened = await inspectFileIdentity(
|
|
85
|
-
|
|
86
|
-
if (!stat.isFile()) {
|
|
87
|
-
throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
|
|
88
|
-
}
|
|
89
|
-
return stat;
|
|
90
|
-
}, before);
|
|
91
|
-
await inspectFileIdentity(async () => {
|
|
92
|
-
const stat = fsSync.lstatSync(filePath, { bigint: true });
|
|
93
|
-
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
94
|
-
throw new FsSafeError("path-mismatch", "SHA-256 path changed while opening");
|
|
95
|
-
}
|
|
96
|
-
return stat;
|
|
97
|
-
}, opened);
|
|
102
|
+
const opened = await inspectFileIdentity(() => hashDescriptorIdentity(handle.fd), before);
|
|
103
|
+
await inspectFileIdentity(() => hashPathIdentity(filePath, true), opened);
|
|
98
104
|
return await hashFileHandle(handle, getNativeBinding(), options);
|
|
99
105
|
}
|
|
100
106
|
finally {
|
|
@@ -108,3 +114,64 @@ export async function sha256File(input, options = {}) {
|
|
|
108
114
|
? await hashPath(input, normalized)
|
|
109
115
|
: await hashFileHandle(input, getNativeBinding(), normalized);
|
|
110
116
|
}
|
|
117
|
+
function hashDescriptorSync(fd, { maxBytes = Infinity, signal }) {
|
|
118
|
+
signal?.throwIfAborted();
|
|
119
|
+
const stat = fsSync.fstatSync(fd);
|
|
120
|
+
if (!stat.isFile()) {
|
|
121
|
+
throw new FsSafeError("not-file", "SHA-256 input is not a regular file");
|
|
122
|
+
}
|
|
123
|
+
if (stat.size > maxBytes) {
|
|
124
|
+
throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
|
|
125
|
+
}
|
|
126
|
+
const hash = createHash("sha256");
|
|
127
|
+
let buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(1, stat.size + 1), maxBytes + 1));
|
|
128
|
+
let position = 0;
|
|
129
|
+
while (true) {
|
|
130
|
+
signal?.throwIfAborted();
|
|
131
|
+
const length = Math.min(buffer.length, maxBytes - position + 1);
|
|
132
|
+
const bytesRead = fsSync.readSync(fd, buffer, 0, length, position);
|
|
133
|
+
signal?.throwIfAborted();
|
|
134
|
+
if (bytesRead > maxBytes - position) {
|
|
135
|
+
throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
|
|
136
|
+
}
|
|
137
|
+
if (bytesRead === 0) {
|
|
138
|
+
return { bytes: position, digest: hash.digest("hex") };
|
|
139
|
+
}
|
|
140
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
141
|
+
position += bytesRead;
|
|
142
|
+
if (bytesRead === buffer.length && buffer.length < 64 * 1024) {
|
|
143
|
+
buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function hashPathSync(filePath, options) {
|
|
148
|
+
const before = inspectFileIdentitySync(() => hashPathIdentity(filePath));
|
|
149
|
+
let fd;
|
|
150
|
+
try {
|
|
151
|
+
options.signal?.throwIfAborted();
|
|
152
|
+
fd = fsSync.openSync(filePath, resolveReadOpenFlags());
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
rethrowHashOpenError(error);
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
options.signal?.throwIfAborted();
|
|
159
|
+
const opened = inspectFileIdentitySync(() => hashDescriptorIdentity(fd), before);
|
|
160
|
+
inspectFileIdentitySync(() => hashPathIdentity(filePath, true), opened);
|
|
161
|
+
return hashDescriptorSync(fd, options);
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
try {
|
|
165
|
+
fsSync.closeSync(fd);
|
|
166
|
+
}
|
|
167
|
+
catch { /* Preserve the hashing outcome. */ }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** Hash synchronously from offset zero, retaining ownership and position of a borrowed fd. */
|
|
171
|
+
export function sha256FileSync(input, options = {}) {
|
|
172
|
+
options.signal?.throwIfAborted();
|
|
173
|
+
const normalized = { maxBytes: normalizeMaxBytes(options.maxBytes), signal: options.signal };
|
|
174
|
+
return typeof input === "string"
|
|
175
|
+
? hashPathSync(input, normalized)
|
|
176
|
+
: hashDescriptorSync(input, normalized);
|
|
177
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAWnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAMjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AAoJF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CA0MpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
|
package/dist/file-lock-sync.js
CHANGED
|
@@ -2,9 +2,11 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { readSidecarLockSnapshotSync, readSidecarLockRawSnapshotSync, parseSidecarLockSnapshot, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
5
|
-
import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, sidecarLockPayloadCreatedAtMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
5
|
+
import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, sidecarLockPayloadCreatedAtMs, validateSidecarLockStaleMs, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
6
6
|
import { getFsSafeLockConfig } from "./lock-config.js";
|
|
7
7
|
import { sleepSync } from "./timing.js";
|
|
8
|
+
import { realpathSync } from "./realpath.js";
|
|
9
|
+
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
8
10
|
const SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncSidecarLocks");
|
|
9
11
|
const SYNC_CLEANUP_REGISTERED_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupRegistered");
|
|
10
12
|
const SYNC_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupHandler");
|
|
@@ -91,11 +93,11 @@ function createSyncHeldLockHandle(held) {
|
|
|
91
93
|
}
|
|
92
94
|
function canonicalLockParentSync(parent) {
|
|
93
95
|
// Match Root's async realpath, including Windows short-name expansion.
|
|
94
|
-
return process.platform === "win32" ?
|
|
96
|
+
return process.platform === "win32" ? realpathSync.native(parent) : realpathSync(parent);
|
|
95
97
|
}
|
|
96
98
|
function normalizeTargetPath(targetPath) {
|
|
97
99
|
const resolved = path.resolve(targetPath);
|
|
98
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
100
|
+
fs.mkdirSync(recursiveMkdirPath(path.dirname(resolved)), { recursive: true });
|
|
99
101
|
try {
|
|
100
102
|
return path.join(canonicalLockParentSync(path.dirname(resolved)), path.basename(resolved));
|
|
101
103
|
}
|
|
@@ -140,6 +142,9 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
140
142
|
const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
|
|
141
143
|
validateSidecarLockRetryOptions(retry);
|
|
142
144
|
validateSidecarLockTimeoutMs(timeoutMs);
|
|
145
|
+
const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
|
|
146
|
+
validateSidecarLockStaleMs(staleMs);
|
|
147
|
+
validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
|
|
143
148
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
144
149
|
const lockPath = boundedLockPath(options.lockPath ?? `${normalizedTargetPath}.lock`, options.lockRoot);
|
|
145
150
|
const heldLocks = getSyncHeldLocks();
|
|
@@ -153,7 +158,6 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
153
158
|
}
|
|
154
159
|
// Process defaults fill the same fields here as withLockDefaults() fills for
|
|
155
160
|
// the asynchronous manager, so both acquirers honor configureFsSafeLocks().
|
|
156
|
-
const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
|
|
157
161
|
const staleRecovery = options.staleRecovery ?? defaults.staleRecovery;
|
|
158
162
|
const startedAt = Date.now();
|
|
159
163
|
let attempt = 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIvD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIvD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAK1D,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAOjD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,IAAI,EAChB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,GACnD,OAAO,CAAC,IAAI,CAAC,CASf;AAsCD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAwC1D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAWrE;AAUD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CAOlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC;CAC1C,GAAG,eAAe,CAyDlB"}
|
|
@@ -13,6 +13,8 @@ import { ensureTrailingSep } from "./root-context.js";
|
|
|
13
13
|
import { RootHandle } from "./root-impl.js";
|
|
14
14
|
import { prepareSecretFileWrite } from "./secret-file.js";
|
|
15
15
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
16
|
+
import { realpathSync } from "./realpath.js";
|
|
17
|
+
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
16
18
|
function parentRelativePath(relativePath) {
|
|
17
19
|
const parent = path.posix.dirname(relativePath);
|
|
18
20
|
return parent === "." ? "" : parent;
|
|
@@ -27,7 +29,7 @@ export async function ensureParentInRoot(scopedRoot, relativePath, mode) {
|
|
|
27
29
|
}
|
|
28
30
|
export async function openWritableStoreRoot(params) {
|
|
29
31
|
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
30
|
-
await fs.mkdir(params.rootDir, { recursive: true, mode: params.dirMode });
|
|
32
|
+
await fs.mkdir(recursiveMkdirPath(params.rootDir), { recursive: true, mode: params.dirMode });
|
|
31
33
|
await fs.chmod(params.rootDir, params.dirMode).catch(() => undefined);
|
|
32
34
|
return await root(params.rootDir, { hardlinks: "reject", maxBytes });
|
|
33
35
|
}
|
|
@@ -149,12 +151,12 @@ export function ensureStoreDirectorySync(params) {
|
|
|
149
151
|
if (isPathRelativeEscape(relative)) {
|
|
150
152
|
throw new FsSafeError("outside-workspace", "file path escapes store root");
|
|
151
153
|
}
|
|
152
|
-
syncFs.mkdirSync(rootDir, { recursive: true, mode: params.mode });
|
|
154
|
+
syncFs.mkdirSync(recursiveMkdirPath(rootDir), { recursive: true, mode: params.mode });
|
|
153
155
|
const rootStat = syncFs.lstatSync(rootDir);
|
|
154
156
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
155
157
|
throw new FsSafeError("not-file", `${params.messagePrefix} root must be a directory: ${rootDir}`);
|
|
156
158
|
}
|
|
157
|
-
const rootReal =
|
|
159
|
+
const rootReal = realpathSync(rootDir);
|
|
158
160
|
chmodDirectorySyncBestEffort(rootDir, params.mode);
|
|
159
161
|
let current = rootDir;
|
|
160
162
|
for (const segment of path.relative(rootDir, dir).split(path.sep).filter(Boolean)) {
|
|
@@ -172,8 +174,8 @@ export function ensureStoreDirectorySync(params) {
|
|
|
172
174
|
syncFs.mkdirSync(current, { mode: params.mode });
|
|
173
175
|
}
|
|
174
176
|
const currentRootStat = syncFs.lstatSync(rootDir);
|
|
175
|
-
const currentRootReal =
|
|
176
|
-
const currentReal =
|
|
177
|
+
const currentRootReal = realpathSync(rootDir);
|
|
178
|
+
const currentReal = realpathSync(current);
|
|
177
179
|
if (currentRootStat.isSymbolicLink() ||
|
|
178
180
|
!currentRootStat.isDirectory() ||
|
|
179
181
|
!sameFileIdentity(rootStat, currentRootStat) ||
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store-path.d.ts","sourceRoot":"","sources":["../src/file-store-path.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAe/D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAS9E"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import { isPathInside, splitSafeRelativePath } from "./path.js";
|
|
4
|
+
export function assertRelativePath(relativePath) {
|
|
5
|
+
const raw = relativePath.trim();
|
|
6
|
+
if (!raw || raw !== relativePath) {
|
|
7
|
+
throw new FsSafeError("invalid-path", "store key must be non-empty and unpadded");
|
|
8
|
+
}
|
|
9
|
+
const segments = splitSafeRelativePath(raw);
|
|
10
|
+
if (segments.length === 0 ||
|
|
11
|
+
segments.join("/") !== raw ||
|
|
12
|
+
raw.normalize("NFC") !== raw ||
|
|
13
|
+
segments.some((segment) => /[ .]$/u.test(segment))) {
|
|
14
|
+
throw new FsSafeError("invalid-path", "store key must use one canonical relative spelling");
|
|
15
|
+
}
|
|
16
|
+
return raw;
|
|
17
|
+
}
|
|
18
|
+
export function resolveStorePath(rootDir, relativePath) {
|
|
19
|
+
const key = assertRelativePath(relativePath);
|
|
20
|
+
const root = path.resolve(rootDir);
|
|
21
|
+
// The immutable key already passed segment validation; keep the containment check.
|
|
22
|
+
const target = path.resolve(root, key);
|
|
23
|
+
if (!isPathInside(root, target)) {
|
|
24
|
+
throw new FsSafeError("outside-workspace", "relative path escapes root");
|
|
25
|
+
}
|
|
26
|
+
return target;
|
|
27
|
+
}
|