@openclaw/fs-safe 0.12.0 → 0.13.1
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 +73 -0
- package/README.md +12 -7
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +2 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +6 -2
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -19
- package/dist/archive-plan.js +1 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +18 -13
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +43 -10
- package/dist/archive-tar-inspect.d.ts.map +1 -1
- package/dist/archive-tar-inspect.js +4 -1
- 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 +2 -2
- package/dist/archive-zip-directory.d.ts +3 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +13 -2
- package/dist/archive-zip-loader.d.ts +3 -2
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +30 -4
- package/dist/archive-zip-manifest.d.ts +5 -0
- package/dist/archive-zip-manifest.d.ts.map +1 -0
- package/dist/archive-zip-manifest.js +22 -0
- package/dist/archive-zip-names.d.ts +6 -1
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +35 -14
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +3 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/copy-file-input.d.ts +1 -1
- package/dist/copy-file-input.d.ts.map +1 -1
- package/dist/copy-file-input.js +2 -0
- package/dist/darwin-acl.d.ts +4 -0
- package/dist/darwin-acl.d.ts.map +1 -0
- package/dist/darwin-acl.js +24 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +8 -2
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +67 -23
- package/dist/directory-entry-path.d.ts +3 -0
- package/dist/directory-entry-path.d.ts.map +1 -0
- package/dist/directory-entry-path.js +21 -0
- package/dist/directory-guard.d.ts +17 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +134 -48
- package/dist/directory-mode-node.d.ts +12 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +102 -4
- package/dist/effective-uid.d.ts +2 -0
- package/dist/effective-uid.d.ts.map +1 -0
- package/dist/effective-uid.js +25 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +98 -27
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +3 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +36 -6
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +29 -9
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +6 -2
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -65
- package/dist/file-store-copy-source.d.ts +5 -0
- package/dist/file-store-copy-source.d.ts.map +1 -0
- package/dist/file-store-copy-source.js +31 -0
- package/dist/file-store-path.d.ts.map +1 -1
- package/dist/file-store-path.js +4 -1
- package/dist/file-store-sync-directory.d.ts +16 -0
- package/dist/file-store-sync-directory.d.ts.map +1 -0
- package/dist/file-store-sync-directory.js +349 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +11 -28
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +65 -20
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +8 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +176 -27
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +4 -1
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +54 -19
- package/dist/json-durable-queue-ownership.d.ts +6 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +89 -42
- package/dist/json-durable-queue-paths.d.ts +11 -0
- package/dist/json-durable-queue-paths.d.ts.map +1 -0
- package/dist/json-durable-queue-paths.js +42 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -1
- package/dist/json-durable-queue-read.js +2 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +50 -58
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +5 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +54 -22
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +4 -0
- package/dist/local-file-descriptor.d.ts +17 -0
- package/dist/local-file-descriptor.d.ts.map +1 -0
- package/dist/local-file-descriptor.js +84 -0
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -7
- package/dist/move-path-cleanup.d.ts +2 -0
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +44 -18
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +31 -6
- package/dist/native-binding.d.ts +17 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-binding.js +8 -1
- package/dist/native-directory-observation.d.ts +17 -0
- package/dist/native-directory-observation.d.ts.map +1 -0
- package/dist/native-directory-observation.js +37 -0
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +6 -4
- package/dist/native-parent-admission.d.ts +32 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +127 -0
- package/dist/native-pinned-write-windows.d.ts +0 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +8 -9
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +314 -42
- package/dist/native-staged-file.d.ts +4 -4
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +20 -10
- package/dist/native.d.ts +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +7 -2
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +15 -5
- package/dist/overwrite-file-handle.d.ts.map +1 -1
- package/dist/overwrite-file-handle.js +5 -1
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -0
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +7 -2
- package/dist/path-prefix.d.ts +7 -0
- package/dist/path-prefix.d.ts.map +1 -0
- package/dist/path-prefix.js +82 -0
- package/dist/path-scope-lexical.d.ts.map +1 -1
- package/dist/path-scope-lexical.js +18 -8
- package/dist/path-segment-route.d.ts +7 -0
- package/dist/path-segment-route.d.ts.map +1 -0
- package/dist/path-segment-route.js +24 -0
- package/dist/path-suffix-aliases.d.ts +10 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -0
- package/dist/path-suffix-aliases.js +386 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +16 -5
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +14 -3
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +37 -8
- package/dist/pinned-mutation-admission.d.ts +25 -0
- package/dist/pinned-mutation-admission.d.ts.map +1 -0
- package/dist/pinned-mutation-admission.js +425 -0
- package/dist/pinned-mutation-observation.d.ts +34 -0
- package/dist/pinned-mutation-observation.d.ts.map +1 -0
- package/dist/pinned-mutation-observation.js +142 -0
- package/dist/pinned-mutation-shared-route.d.ts +24 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
- package/dist/pinned-mutation-shared-route.js +70 -0
- package/dist/pinned-open.d.ts +6 -0
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +28 -10
- package/dist/pinned-write-types.d.ts +75 -0
- package/dist/pinned-write-types.d.ts.map +1 -0
- package/dist/pinned-write-types.js +1 -0
- package/dist/pinned-write.d.ts +7 -33
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +151 -17
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +2 -0
- package/dist/private-producer-handoff.d.ts +16 -0
- package/dist/private-producer-handoff.d.ts.map +1 -0
- package/dist/private-producer-handoff.js +272 -0
- package/dist/private-temp-workspace.d.ts +2 -39
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +183 -77
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +38 -32
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +56 -45
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +12 -5
- package/dist/replace-file-temp-owner.d.ts +1 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +12 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +24 -24
- package/dist/root-boundary.d.ts +4 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +6 -1
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +57 -11
- package/dist/root-directory-creation.d.ts +13 -0
- package/dist/root-directory-creation.d.ts.map +1 -0
- package/dist/root-directory-creation.js +212 -0
- package/dist/root-directory-list.d.ts +16 -4
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +180 -39
- package/dist/root-directory.d.ts +23 -0
- package/dist/root-directory.d.ts.map +1 -0
- package/dist/root-directory.js +141 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +12 -1
- package/dist/root-file-final-admission.d.ts +21 -0
- package/dist/root-file-final-admission.d.ts.map +1 -0
- package/dist/root-file-final-admission.js +83 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +103 -24
- package/dist/root-impl.d.ts +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +289 -317
- package/dist/root-move-noreplace.d.ts +14 -0
- package/dist/root-move-noreplace.d.ts.map +1 -0
- package/dist/root-move-noreplace.js +202 -0
- package/dist/root-observed-path.d.ts +9 -0
- package/dist/root-observed-path.d.ts.map +1 -0
- package/dist/root-observed-path.js +95 -0
- package/dist/root-path-errors.d.ts +12 -0
- package/dist/root-path-errors.d.ts.map +1 -0
- package/dist/root-path-errors.js +13 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +53 -20
- package/dist/root-path-observation.d.ts +63 -0
- package/dist/root-path-observation.d.ts.map +1 -0
- package/dist/root-path-observation.js +180 -0
- package/dist/root-path-stat.d.ts +5 -0
- package/dist/root-path-stat.d.ts.map +1 -0
- package/dist/root-path-stat.js +101 -0
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +23 -4
- package/dist/root-path.d.ts +11 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +215 -53
- package/dist/root-paths-lexical.d.ts +9 -0
- package/dist/root-paths-lexical.d.ts.map +1 -0
- package/dist/root-paths-lexical.js +22 -0
- package/dist/root-paths.d.ts +3 -25
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +77 -154
- package/dist/root-read-admission.d.ts +26 -0
- package/dist/root-read-admission.d.ts.map +1 -0
- package/dist/root-read-admission.js +94 -0
- package/dist/root-remove-identity.d.ts +15 -0
- package/dist/root-remove-identity.d.ts.map +1 -0
- package/dist/root-remove-identity.js +89 -0
- package/dist/root-remove-receipt.d.ts +17 -0
- package/dist/root-remove-receipt.d.ts.map +1 -0
- package/dist/root-remove-receipt.js +37 -0
- package/dist/root-remove.d.ts +2 -1
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +172 -10
- package/dist/root-write-admission.d.ts +68 -0
- package/dist/root-write-admission.d.ts.map +1 -0
- package/dist/root-write-admission.js +322 -0
- package/dist/root-write-compatibility.d.ts +13 -0
- package/dist/root-write-compatibility.d.ts.map +1 -0
- package/dist/root-write-compatibility.js +74 -0
- package/dist/root-write-complete-parent.d.ts +42 -0
- package/dist/root-write-complete-parent.d.ts.map +1 -0
- package/dist/root-write-complete-parent.js +195 -0
- package/dist/root-write-publication.d.ts +24 -0
- package/dist/root-write-publication.d.ts.map +1 -0
- package/dist/root-write-publication.js +85 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +6 -4
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +62 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +18 -13
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +5 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +92 -7
- package/dist/secure-temp-dir.d.ts +6 -3
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +121 -101
- package/dist/secure-temp-repair.d.ts +35 -0
- package/dist/secure-temp-repair.d.ts.map +1 -0
- package/dist/secure-temp-repair.js +106 -0
- package/dist/sibling-staged-file.d.ts +3 -2
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +179 -55
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -13
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +44 -14
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- package/dist/staged-directory.d.ts +14 -5
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +54 -3
- package/dist/standalone-publication-path.d.ts +2 -0
- package/dist/standalone-publication-path.d.ts.map +1 -0
- package/dist/standalone-publication-path.js +6 -0
- package/dist/stat-observation.d.ts +11 -0
- package/dist/stat-observation.d.ts.map +1 -0
- package/dist/stat-observation.js +64 -0
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +39 -2
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +7 -2
- package/dist/temp-target.d.ts +2 -0
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +130 -3
- package/dist/temp-workspace-admission.d.ts +22 -0
- package/dist/temp-workspace-admission.d.ts.map +1 -0
- package/dist/temp-workspace-admission.js +374 -0
- package/dist/temp-workspace-child-admission.d.ts +13 -0
- package/dist/temp-workspace-child-admission.d.ts.map +1 -0
- package/dist/temp-workspace-child-admission.js +95 -0
- package/dist/temp-workspace-descriptor.d.ts +48 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -0
- package/dist/temp-workspace-descriptor.js +363 -0
- package/dist/temp-workspace-identity.d.ts +15 -0
- package/dist/temp-workspace-identity.d.ts.map +1 -0
- package/dist/temp-workspace-identity.js +41 -0
- package/dist/temp-workspace-owner.d.ts +7 -6
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +121 -61
- package/dist/temp-workspace-permissions.d.ts +4 -0
- package/dist/temp-workspace-permissions.d.ts.map +1 -0
- package/dist/temp-workspace-permissions.js +32 -0
- package/dist/temp-workspace-types.d.ts +40 -0
- package/dist/temp-workspace-types.d.ts.map +1 -0
- package/dist/temp-workspace-types.js +1 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/temp.js +1 -1
- package/dist/test-hooks.d.ts +7 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/text-atomic.d.ts.map +1 -1
- package/dist/text-atomic.js +3 -1
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +54 -8
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +9 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +5 -0
- package/dist/windows-path-alias.d.ts +39 -0
- package/dist/windows-path-alias.d.ts.map +1 -0
- package/dist/windows-path-alias.js +153 -0
- package/docs/advanced.md +25 -0
- package/docs/archive.md +23 -2
- package/docs/atomic.md +27 -3
- package/docs/copy.md +3 -0
- package/docs/durability.md +14 -0
- package/docs/errors.md +14 -6
- package/docs/file-store.md +24 -3
- package/docs/filename.md +14 -7
- package/docs/install-path.md +2 -2
- package/docs/install.md +8 -7
- package/docs/json-store.md +5 -1
- package/docs/json.md +11 -0
- package/docs/mutation-policy-proof.md +65 -0
- package/docs/native-helper.md +41 -11
- package/docs/native.md +32 -8
- package/docs/output.md +33 -11
- package/docs/path-prefix.md +64 -0
- package/docs/path-suffix-aliases.md +159 -0
- package/docs/path.md +11 -0
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +9 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +18 -3
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +6 -2
- package/docs/security-model.md +75 -8
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +5 -1
- package/docs/temp.md +260 -36
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
package/dist/deny-mutations.js
CHANGED
|
@@ -2,10 +2,15 @@ import path from "node:path";
|
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
3
|
import { assertNoNulPathInput, isPathInside } from "./path.js";
|
|
4
4
|
import { resolvePathViaExistingAncestor } from "./root-path-existing.js";
|
|
5
|
+
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
5
6
|
export async function resolveMutationComparablePaths(rawPath) {
|
|
6
7
|
assertNoNulPathInput(rawPath, "path contains a NUL byte");
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
assertNoWindowsPathAlias(rawPath, "filesystem", "mutation path uses a Windows filesystem namespace alias");
|
|
9
|
+
const resolved = resolvePathPreservingWindowsRoot(rawPath);
|
|
10
|
+
assertNoWindowsPathAlias(resolved, "filesystem", "mutation path uses a Windows filesystem namespace alias");
|
|
11
|
+
const canonical = await resolvePathViaExistingAncestor(resolved);
|
|
12
|
+
assertNoWindowsPathAlias(canonical, "filesystem", "mutation path uses a Windows filesystem namespace alias");
|
|
13
|
+
return new Set([resolved, canonical]);
|
|
9
14
|
}
|
|
10
15
|
function isSamePath(left, right) {
|
|
11
16
|
return isPathInside(left, right) && isPathInside(right, left);
|
|
@@ -20,6 +25,7 @@ function policyPathEntries(entries) {
|
|
|
20
25
|
throw new FsSafeError("invalid-path", "deny mutation paths must be non-empty");
|
|
21
26
|
}
|
|
22
27
|
assertNoNulPathInput(entry, "deny mutation path contains a NUL byte");
|
|
28
|
+
assertNoWindowsPathAlias(entry, "filesystem", "deny mutation path uses a Windows filesystem namespace alias");
|
|
23
29
|
if (!path.isAbsolute(entry)) {
|
|
24
30
|
throw new FsSafeError("invalid-path", "deny mutation paths must be absolute");
|
|
25
31
|
}
|
|
@@ -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;AAI7E,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;AAwOF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAe1B;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,CAqB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAyCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAiCD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA2FlC"}
|
|
@@ -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 { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot } from "./windows-path-alias.js";
|
|
7
8
|
import { realpathSync } from "./realpath.js";
|
|
8
9
|
function directoryOpenFlags() {
|
|
9
10
|
if (process.platform === "win32") {
|
|
@@ -42,39 +43,67 @@ function assertDirectory(identity, pathname, label) {
|
|
|
42
43
|
throw new FsSafeError("not-file", `${label} must be a real directory: ${pathname}`);
|
|
43
44
|
}
|
|
44
45
|
}
|
|
46
|
+
function ownDirectoryReceipt(receipt) {
|
|
47
|
+
const pathname = receipt.path;
|
|
48
|
+
const realPath = receipt.realPath;
|
|
49
|
+
const identity = receipt.identity;
|
|
50
|
+
return { path: pathname, realPath, identity };
|
|
51
|
+
}
|
|
52
|
+
function assertDirectoryReceiptPaths(receipt, label) {
|
|
53
|
+
assertNoWindowsPathAlias(receipt.path, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
|
|
54
|
+
assertNoWindowsPathAlias(receipt.realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
|
|
55
|
+
}
|
|
45
56
|
async function createDirectoryReceipt(directoryPath, label) {
|
|
46
|
-
|
|
47
|
-
const
|
|
57
|
+
assertNoWindowsPathAlias(directoryPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
|
|
58
|
+
const resolvedPath = resolvePathPreservingWindowsRoot(directoryPath);
|
|
59
|
+
assertNoWindowsPathAlias(resolvedPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
|
|
60
|
+
const operationPath = pathForWindowsFilesystem(resolvedPath);
|
|
61
|
+
const identity = fsSync.lstatSync(operationPath);
|
|
48
62
|
assertDirectory(identity, resolvedPath, label);
|
|
63
|
+
const realPath = realpathSync.native(operationPath);
|
|
64
|
+
assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
|
|
49
65
|
return {
|
|
50
66
|
path: resolvedPath,
|
|
51
|
-
realPath
|
|
67
|
+
realPath,
|
|
52
68
|
identity,
|
|
53
69
|
};
|
|
54
70
|
}
|
|
55
71
|
function createDirectoryReceiptSync(directoryPath, label) {
|
|
56
|
-
|
|
57
|
-
const
|
|
72
|
+
assertNoWindowsPathAlias(directoryPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
|
|
73
|
+
const resolvedPath = resolvePathPreservingWindowsRoot(directoryPath);
|
|
74
|
+
assertNoWindowsPathAlias(resolvedPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
|
|
75
|
+
const operationPath = pathForWindowsFilesystem(resolvedPath);
|
|
76
|
+
const identity = fsSync.lstatSync(operationPath);
|
|
58
77
|
assertDirectory(identity, resolvedPath, label);
|
|
78
|
+
const realPath = realpathSync(operationPath);
|
|
79
|
+
assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
|
|
59
80
|
return {
|
|
60
81
|
path: resolvedPath,
|
|
61
|
-
realPath
|
|
82
|
+
realPath,
|
|
62
83
|
identity,
|
|
63
84
|
};
|
|
64
85
|
}
|
|
65
86
|
async function assertDirectoryReceiptCurrent(receipt, label) {
|
|
66
|
-
|
|
87
|
+
assertDirectoryReceiptPaths(receipt, label);
|
|
88
|
+
const operationPath = pathForWindowsFilesystem(receipt.path);
|
|
89
|
+
const currentIdentity = fsSync.lstatSync(operationPath);
|
|
67
90
|
assertDirectory(currentIdentity, receipt.path, label);
|
|
91
|
+
const realPath = realpathSync.native(operationPath);
|
|
92
|
+
assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
|
|
68
93
|
if (!sameFileIdentity(receipt.identity, currentIdentity) ||
|
|
69
|
-
|
|
94
|
+
realPath !== receipt.realPath) {
|
|
70
95
|
throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
|
|
71
96
|
}
|
|
72
97
|
}
|
|
73
98
|
function assertDirectoryReceiptCurrentSync(receipt, label) {
|
|
74
|
-
|
|
99
|
+
assertDirectoryReceiptPaths(receipt, label);
|
|
100
|
+
const operationPath = pathForWindowsFilesystem(receipt.path);
|
|
101
|
+
const currentIdentity = fsSync.lstatSync(operationPath);
|
|
75
102
|
assertDirectory(currentIdentity, receipt.path, label);
|
|
103
|
+
const realPath = realpathSync(operationPath);
|
|
104
|
+
assertNoWindowsPathAlias(realPath, "filesystem", `${label} real path uses a Windows filesystem namespace alias`);
|
|
76
105
|
if (!sameFileIdentity(receipt.identity, currentIdentity) ||
|
|
77
|
-
|
|
106
|
+
realPath !== receipt.realPath) {
|
|
78
107
|
throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
|
|
79
108
|
}
|
|
80
109
|
}
|
|
@@ -88,19 +117,21 @@ async function assertOpenDirectoryCurrent(handle, receipt, label) {
|
|
|
88
117
|
}
|
|
89
118
|
class PinnedDirectoryImpl {
|
|
90
119
|
receipt;
|
|
120
|
+
#authority;
|
|
91
121
|
#handle;
|
|
92
122
|
#label;
|
|
93
123
|
#closed = false;
|
|
94
124
|
constructor(handle, receipt, label) {
|
|
95
125
|
this.#handle = handle;
|
|
96
|
-
this
|
|
126
|
+
this.#authority = receipt;
|
|
127
|
+
this.receipt = ownDirectoryReceipt(receipt);
|
|
97
128
|
this.#label = label;
|
|
98
129
|
}
|
|
99
130
|
async assertCurrent() {
|
|
100
131
|
if (this.#closed) {
|
|
101
132
|
throw new FsSafeError("helper-failed", `${this.#label} pin is already closed`);
|
|
102
133
|
}
|
|
103
|
-
await assertOpenDirectoryCurrent(this.#handle, this
|
|
134
|
+
await assertOpenDirectoryCurrent(this.#handle, this.#authority, this.#label);
|
|
104
135
|
}
|
|
105
136
|
async sync() {
|
|
106
137
|
await this.assertCurrent();
|
|
@@ -127,9 +158,11 @@ class PinnedDirectoryImpl {
|
|
|
127
158
|
}
|
|
128
159
|
export async function pinDirectory(directory, options = {}) {
|
|
129
160
|
const label = options.label ?? "directory";
|
|
130
|
-
const receipt = typeof directory === "string"
|
|
161
|
+
const receipt = typeof directory === "string"
|
|
162
|
+
? await createDirectoryReceipt(directory, label)
|
|
163
|
+
: ownDirectoryReceipt(directory);
|
|
131
164
|
await assertDirectoryReceiptCurrent(receipt, label);
|
|
132
|
-
const handle = await fs.open(receipt.path, directoryOpenFlags());
|
|
165
|
+
const handle = await fs.open(pathForWindowsFilesystem(receipt.path), directoryOpenFlags());
|
|
133
166
|
try {
|
|
134
167
|
await assertOpenDirectoryCurrent(handle, receipt, label);
|
|
135
168
|
return new PinnedDirectoryImpl(handle, receipt, label);
|
|
@@ -141,7 +174,9 @@ export async function pinDirectory(directory, options = {}) {
|
|
|
141
174
|
}
|
|
142
175
|
export async function syncDirectory(directory, options = {}) {
|
|
143
176
|
const label = options.label ?? "directory";
|
|
144
|
-
const receipt = typeof directory === "string"
|
|
177
|
+
const receipt = typeof directory === "string"
|
|
178
|
+
? await createDirectoryReceipt(directory, label)
|
|
179
|
+
: ownDirectoryReceipt(directory);
|
|
145
180
|
let pinned;
|
|
146
181
|
try {
|
|
147
182
|
pinned = await pinDirectory(receipt, { label });
|
|
@@ -162,11 +197,13 @@ export async function syncDirectory(directory, options = {}) {
|
|
|
162
197
|
}
|
|
163
198
|
export function syncDirectorySync(directory, options = {}) {
|
|
164
199
|
const label = options.label ?? "directory";
|
|
165
|
-
const receipt = typeof directory === "string"
|
|
200
|
+
const receipt = typeof directory === "string"
|
|
201
|
+
? createDirectoryReceiptSync(directory, label)
|
|
202
|
+
: ownDirectoryReceipt(directory);
|
|
166
203
|
assertDirectoryReceiptCurrentSync(receipt, label);
|
|
167
204
|
let descriptor;
|
|
168
205
|
try {
|
|
169
|
-
descriptor = fsSync.openSync(receipt.path, directoryOpenFlags());
|
|
206
|
+
descriptor = fsSync.openSync(pathForWindowsFilesystem(receipt.path), directoryOpenFlags());
|
|
170
207
|
}
|
|
171
208
|
catch (error) {
|
|
172
209
|
if (!isWindowsDirectoryOpenUnsupported(error)) {
|
|
@@ -211,7 +248,9 @@ export function syncDirectoryBestEffortSync(directoryPath) {
|
|
|
211
248
|
}
|
|
212
249
|
}
|
|
213
250
|
async function findExistingAncestorReceipt(targetPath, label) {
|
|
214
|
-
|
|
251
|
+
assertNoWindowsPathAlias(targetPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
|
|
252
|
+
let currentPath = resolvePathPreservingWindowsRoot(targetPath);
|
|
253
|
+
assertNoWindowsPathAlias(currentPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
|
|
215
254
|
while (true) {
|
|
216
255
|
try {
|
|
217
256
|
return await createDirectoryReceipt(currentPath, label);
|
|
@@ -229,12 +268,16 @@ async function findExistingAncestorReceipt(targetPath, label) {
|
|
|
229
268
|
}
|
|
230
269
|
}
|
|
231
270
|
export async function ensureDurableDirectory(options) {
|
|
232
|
-
const directoryPath = path.resolve(options.directoryPath);
|
|
233
271
|
const label = options.label ?? "directory";
|
|
272
|
+
const requestedDirectoryPath = options.directoryPath;
|
|
273
|
+
assertNoWindowsPathAlias(requestedDirectoryPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
|
|
274
|
+
const directoryPath = resolvePathPreservingWindowsRoot(requestedDirectoryPath);
|
|
275
|
+
assertNoWindowsPathAlias(directoryPath, "filesystem", `${label} path uses a Windows filesystem namespace alias`);
|
|
234
276
|
const ancestorReceipt = await findExistingAncestorReceipt(directoryPath, label);
|
|
235
277
|
const targetExists = ancestorReceipt.path === directoryPath;
|
|
236
|
-
|
|
237
|
-
|
|
278
|
+
const expectedExistingIdentity = options.expectedExistingIdentity;
|
|
279
|
+
if (expectedExistingIdentity &&
|
|
280
|
+
(!targetExists || !sameFileIdentity(expectedExistingIdentity, ancestorReceipt.identity))) {
|
|
238
281
|
throw new FsSafeError("path-mismatch", `${label} changed before durable directory pinning: ${directoryPath}`);
|
|
239
282
|
}
|
|
240
283
|
const ancestor = await pinDirectory(ancestorReceipt, { label });
|
|
@@ -242,8 +285,9 @@ export async function ensureDurableDirectory(options) {
|
|
|
242
285
|
try {
|
|
243
286
|
await ancestor.assertCurrent();
|
|
244
287
|
if (!targetExists) {
|
|
245
|
-
|
|
246
|
-
|
|
288
|
+
const create = options.create;
|
|
289
|
+
if (create) {
|
|
290
|
+
await Reflect.apply(create, options, [directoryPath]);
|
|
247
291
|
}
|
|
248
292
|
else {
|
|
249
293
|
const created = await ensureAbsoluteDirectory(directoryPath, {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory-entry-path.d.ts","sourceRoot":"","sources":["../src/directory-entry-path.ts"],"names":[],"mappings":"AAEA,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,kBAAmB,GAAG,MAAM,CAenF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/** Strip only trailing separators: lstat must inspect the entry, not follow a leaf link. */
|
|
3
|
+
export function directoryEntryPath(dir, platform = process.platform) {
|
|
4
|
+
const windows = platform === "win32";
|
|
5
|
+
if (typeof dir === "string") {
|
|
6
|
+
const last = dir[dir.length - 1];
|
|
7
|
+
if (last !== "/" && (!windows || last !== "\\"))
|
|
8
|
+
return dir;
|
|
9
|
+
}
|
|
10
|
+
let rootLength = (windows ? path.win32 : path.posix).parse(dir).root.length;
|
|
11
|
+
if (windows && /^[\\/]{2}[?.][\\/]UNC[\\/]/i.test(dir)) {
|
|
12
|
+
// Node parses the namespace prefix as the root; retain the complete UNC share.
|
|
13
|
+
const uncRoot = path.win32.parse(`\\\\${dir.slice(8)}`).root;
|
|
14
|
+
if (uncRoot.length > 1)
|
|
15
|
+
rootLength = Math.max(rootLength, uncRoot.length + 6);
|
|
16
|
+
}
|
|
17
|
+
let end = dir.length;
|
|
18
|
+
while (end > rootLength && (dir[end - 1] === "/" || (windows && dir[end - 1] === "\\")))
|
|
19
|
+
end--;
|
|
20
|
+
return end === rootLength || end === dir.length ? dir : dir.slice(0, end);
|
|
21
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BigIntStats, Stats } from "node:fs";
|
|
2
|
+
import { type ExactStatIdentity, type StatObservationReceipt } from "./stat-observation.js";
|
|
2
3
|
export type AsyncDirectoryGuard<T extends Stats | BigIntStats = Stats> = {
|
|
3
4
|
dir: string;
|
|
4
5
|
realPath: string;
|
|
@@ -21,6 +22,7 @@ export declare function assertDirectoryIdentitySync(observedPath: string, expect
|
|
|
21
22
|
}): void;
|
|
22
23
|
export declare function createAsyncDirectoryGuard(dir: string, options: {
|
|
23
24
|
bigint: true;
|
|
25
|
+
initial?: BigIntStats;
|
|
24
26
|
}): Promise<AsyncDirectoryGuard<BigIntStats>>;
|
|
25
27
|
export declare function createAsyncDirectoryGuard(dir: string, options?: {
|
|
26
28
|
bigint?: false;
|
|
@@ -37,5 +39,19 @@ export declare function createNearestExistingDirectoryGuard(rootReal: string, ta
|
|
|
37
39
|
}): Promise<AnyAsyncDirectoryGuard>;
|
|
38
40
|
export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
|
|
39
41
|
export declare function inspectDirectoryIdentity(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): Promise<BigIntStats>;
|
|
40
|
-
export declare function
|
|
42
|
+
export declare function observeDirectoryIdentitySync(dir: string, options: {
|
|
43
|
+
bigint: true;
|
|
44
|
+
}): BigIntStats;
|
|
45
|
+
export declare function observeDirectoryIdentitySync(dir: string, options?: {
|
|
46
|
+
bigint?: false;
|
|
47
|
+
}): Stats;
|
|
48
|
+
export declare function inspectDirectoryIdentitySync(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">, initial?: BigIntStats, platform?: NodeJS.Platform): BigIntStats;
|
|
49
|
+
export type DirectoryObservationGuard = StatObservationReceipt & {
|
|
50
|
+
dir: string;
|
|
51
|
+
realPath: string;
|
|
52
|
+
};
|
|
53
|
+
export declare function extendDirectoryObservationGuard(observation: StatObservationReceipt, dir: string, realPath: string): DirectoryObservationGuard;
|
|
54
|
+
export declare function inspectDirectoryObservationSync(dir: string, expected?: ExactStatIdentity): StatObservationReceipt;
|
|
55
|
+
export declare function assertDirectoryObservationSync(dir: string, expected: ExactStatIdentity): Stats | BigIntStats;
|
|
56
|
+
export declare function assertDirectoryObservationGuardSync(guard: DirectoryObservationGuard): void;
|
|
41
57
|
//# 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":"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"}
|
package/dist/directory-guard.js
CHANGED
|
@@ -5,61 +5,102 @@ import { sameFileIdentity } from "./file-identity.js";
|
|
|
5
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 { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
8
9
|
import { realpathSync } from "./realpath.js";
|
|
10
|
+
import { directoryEntryPath } from "./directory-entry-path.js";
|
|
11
|
+
import { assertStatObservationSync, inspectStatObservationSync, } from "./stat-observation.js";
|
|
9
12
|
export async function readDirectoryIdentity(dir) {
|
|
10
13
|
const guard = await createAsyncDirectoryGuard(dir, { bigint: true });
|
|
11
14
|
return Object.freeze({ dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
|
|
12
15
|
}
|
|
13
16
|
export function assertDirectoryIdentitySync(observedPath, expected) {
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
const operationPath = directoryOperationPath(observedPath);
|
|
18
|
+
const expectedDev = expected.dev;
|
|
19
|
+
const expectedIno = expected.ino;
|
|
20
|
+
const expectedRealPath = expected.realPath;
|
|
21
|
+
if (expectedRealPath !== undefined)
|
|
22
|
+
assertNoWindowsPathAlias(expectedRealPath, "filesystem");
|
|
23
|
+
inspectDirectoryIdentityAtPathSync(operationPath, { dev: expectedDev, ino: expectedIno });
|
|
24
|
+
if (expectedRealPath === undefined)
|
|
25
|
+
return;
|
|
26
|
+
const realPath = realpathSync.native(operationPath);
|
|
27
|
+
assertNoWindowsPathAlias(realPath, "filesystem");
|
|
28
|
+
if (realPath !== expectedRealPath) {
|
|
16
29
|
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
17
30
|
}
|
|
18
31
|
}
|
|
19
32
|
export async function createAsyncDirectoryGuard(dir, options) {
|
|
20
|
-
const
|
|
33
|
+
const operationPath = directoryOperationPath(dir);
|
|
34
|
+
const stat = options?.bigint
|
|
35
|
+
? inspectDirectoryIdentityAtPathSync(operationPath, undefined, options.initial)
|
|
36
|
+
: fsSync.lstatSync(operationPath);
|
|
21
37
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
22
38
|
throw directoryComponentNotDirectoryError();
|
|
23
39
|
}
|
|
24
|
-
|
|
40
|
+
const realPath = realpathSync.native(operationPath);
|
|
41
|
+
assertNoWindowsPathAlias(realPath, "filesystem");
|
|
42
|
+
return { dir, realPath, stat };
|
|
25
43
|
}
|
|
26
44
|
export async function assertAsyncDirectoryGuard(guard) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
45
|
+
const dir = guard.dir;
|
|
46
|
+
const operationPath = directoryOperationPath(dir);
|
|
47
|
+
const expectedRealPath = guard.realPath;
|
|
48
|
+
assertNoWindowsPathAlias(expectedRealPath, "filesystem");
|
|
49
|
+
const expectedStat = guard.stat;
|
|
50
|
+
const expectedIdentity = { dev: expectedStat.dev, ino: expectedStat.ino };
|
|
51
|
+
const stat = typeof expectedIdentity.dev === "bigint" && typeof expectedIdentity.ino === "bigint"
|
|
52
|
+
? inspectDirectoryIdentityAtPathSync(operationPath, {
|
|
53
|
+
dev: expectedIdentity.dev,
|
|
54
|
+
ino: expectedIdentity.ino,
|
|
55
|
+
})
|
|
56
|
+
: fsSync.lstatSync(operationPath);
|
|
32
57
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
33
58
|
throw directoryComponentNotDirectoryError();
|
|
34
59
|
}
|
|
35
|
-
if (!sameFileIdentity(stat,
|
|
60
|
+
if (!sameFileIdentity(stat, expectedIdentity)) {
|
|
61
|
+
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
62
|
+
}
|
|
63
|
+
const realPath = realpathSync.native(operationPath);
|
|
64
|
+
assertNoWindowsPathAlias(realPath, "filesystem");
|
|
65
|
+
if (realPath !== expectedRealPath) {
|
|
36
66
|
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
37
67
|
}
|
|
38
68
|
}
|
|
39
69
|
export function createSyncDirectoryGuard(dir) {
|
|
40
|
-
const
|
|
70
|
+
const operationPath = directoryOperationPath(dir);
|
|
71
|
+
const stat = fsSync.lstatSync(operationPath);
|
|
41
72
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
42
73
|
throw directoryComponentNotDirectoryError();
|
|
43
74
|
}
|
|
44
|
-
|
|
75
|
+
const realPath = realpathSync(operationPath);
|
|
76
|
+
assertNoWindowsPathAlias(realPath, "filesystem");
|
|
77
|
+
return { dir, realPath, stat };
|
|
45
78
|
}
|
|
46
79
|
export function assertSyncDirectoryGuard(guard) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
80
|
+
const dir = guard.dir;
|
|
81
|
+
const operationPath = directoryOperationPath(dir);
|
|
82
|
+
const expectedRealPath = guard.realPath;
|
|
83
|
+
assertNoWindowsPathAlias(expectedRealPath, "filesystem");
|
|
84
|
+
const expectedStat = guard.stat;
|
|
85
|
+
const expectedIdentity = { dev: expectedStat.dev, ino: expectedStat.ino };
|
|
86
|
+
const stat = typeof expectedIdentity.dev === "bigint" && typeof expectedIdentity.ino === "bigint"
|
|
87
|
+
? inspectDirectoryIdentityAtPathSync(operationPath, { dev: expectedIdentity.dev, ino: expectedIdentity.ino })
|
|
88
|
+
: fsSync.lstatSync(operationPath);
|
|
52
89
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
53
90
|
throw directoryComponentNotDirectoryError();
|
|
54
91
|
}
|
|
55
|
-
const realPath =
|
|
56
|
-
|
|
92
|
+
const realPath = typeof expectedIdentity.ino === "bigint"
|
|
93
|
+
? realpathSync.native(operationPath) : realpathSync(operationPath);
|
|
94
|
+
assertNoWindowsPathAlias(realPath, "filesystem");
|
|
95
|
+
if (!sameFileIdentity(stat, expectedIdentity) || realPath !== expectedRealPath) {
|
|
57
96
|
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
58
97
|
}
|
|
59
98
|
}
|
|
60
99
|
export async function createNearestExistingDirectoryGuard(rootReal, targetPath, options = { bigint: false }) {
|
|
61
|
-
|
|
62
|
-
|
|
100
|
+
assertNoWindowsPathAlias(rootReal, "filesystem");
|
|
101
|
+
assertNoWindowsPathAlias(targetPath, "filesystem");
|
|
102
|
+
let current = resolvePathPreservingWindowsRoot(targetPath);
|
|
103
|
+
const root = resolvePathPreservingWindowsRoot(rootReal);
|
|
63
104
|
while (current !== root) {
|
|
64
105
|
try {
|
|
65
106
|
return await createAsyncDirectoryGuard(current, options);
|
|
@@ -74,8 +115,10 @@ export async function createNearestExistingDirectoryGuard(rootReal, targetPath,
|
|
|
74
115
|
return await createAsyncDirectoryGuard(root, options);
|
|
75
116
|
}
|
|
76
117
|
export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
|
|
77
|
-
|
|
78
|
-
|
|
118
|
+
assertNoWindowsPathAlias(rootReal, "filesystem");
|
|
119
|
+
assertNoWindowsPathAlias(targetPath, "filesystem");
|
|
120
|
+
let current = resolvePathPreservingWindowsRoot(targetPath);
|
|
121
|
+
const root = resolvePathPreservingWindowsRoot(rootReal);
|
|
79
122
|
while (current !== root) {
|
|
80
123
|
try {
|
|
81
124
|
return createSyncDirectoryGuard(current);
|
|
@@ -91,34 +134,77 @@ export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
|
|
|
91
134
|
}
|
|
92
135
|
// Recovery receipts must retain every identity bit, including on Windows.
|
|
93
136
|
export async function inspectDirectoryIdentity(dir, expected) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
137
|
+
const operationPath = directoryOperationPath(dir);
|
|
138
|
+
const expectedIdentity = expected === undefined
|
|
139
|
+
? undefined
|
|
140
|
+
: { dev: expected.dev, ino: expected.ino };
|
|
141
|
+
return inspectDirectoryIdentityAtPathSync(operationPath, expectedIdentity);
|
|
142
|
+
}
|
|
143
|
+
function directoryOperationPath(dir) {
|
|
144
|
+
assertNoWindowsPathAlias(dir, "filesystem");
|
|
145
|
+
return pathForWindowsFilesystem(directoryEntryPath(dir));
|
|
146
|
+
}
|
|
147
|
+
export function observeDirectoryIdentitySync(dir, options) {
|
|
148
|
+
const entryPath = directoryOperationPath(dir);
|
|
149
|
+
const stat = options?.bigint
|
|
150
|
+
? fsSync.lstatSync(entryPath, { bigint: true })
|
|
151
|
+
: fsSync.lstatSync(entryPath);
|
|
152
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
153
|
+
throw directoryComponentNotDirectoryError();
|
|
154
|
+
return stat;
|
|
155
|
+
}
|
|
156
|
+
export function inspectDirectoryIdentitySync(dir, expected, initial, platform = process.platform) {
|
|
157
|
+
const operationPath = directoryOperationPath(dir);
|
|
158
|
+
const expectedIdentity = expected === undefined
|
|
159
|
+
? undefined
|
|
160
|
+
: { dev: expected.dev, ino: expected.ino };
|
|
161
|
+
return inspectDirectoryIdentityAtPathSync(operationPath, expectedIdentity, initial, platform);
|
|
162
|
+
}
|
|
163
|
+
function inspectDirectoryIdentityAtPathSync(operationPath, expected, initial, platform = process.platform) {
|
|
118
164
|
return inspectFileIdentitySync(() => {
|
|
119
|
-
|
|
165
|
+
// Traversal can supply the first exact observation. A bounded retry still
|
|
166
|
+
// uses the admitted operation path and retains every known identity bit.
|
|
167
|
+
const stat = initial ?? fsSync.lstatSync(operationPath, { bigint: true });
|
|
168
|
+
initial = undefined;
|
|
169
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
170
|
+
throw directoryComponentNotDirectoryError();
|
|
171
|
+
return stat;
|
|
172
|
+
}, expected, platform);
|
|
173
|
+
}
|
|
174
|
+
export function extendDirectoryObservationGuard(observation, dir, realPath) {
|
|
175
|
+
const guard = observation;
|
|
176
|
+
guard.dir = dir;
|
|
177
|
+
guard.realPath = realPath;
|
|
178
|
+
return guard;
|
|
179
|
+
}
|
|
180
|
+
// Only stat/list receipts use this metadata fast path. Recovery and publication
|
|
181
|
+
// callers retain the BigIntStats contract of inspectDirectoryIdentitySync.
|
|
182
|
+
export function inspectDirectoryObservationSync(dir, expected) {
|
|
183
|
+
const entryPath = directoryOperationPath(dir);
|
|
184
|
+
return inspectStatObservationSync(bigint => {
|
|
185
|
+
const stat = bigint ? fsSync.lstatSync(entryPath, { bigint: true }) : fsSync.lstatSync(entryPath);
|
|
120
186
|
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
121
187
|
throw directoryComponentNotDirectoryError();
|
|
122
188
|
return stat;
|
|
123
189
|
}, expected);
|
|
124
190
|
}
|
|
191
|
+
export function assertDirectoryObservationSync(dir, expected) {
|
|
192
|
+
const entryPath = directoryOperationPath(dir);
|
|
193
|
+
return assertStatObservationSync(bigint => {
|
|
194
|
+
const stat = bigint ? fsSync.lstatSync(entryPath, { bigint: true }) : fsSync.lstatSync(entryPath);
|
|
195
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
196
|
+
throw directoryComponentNotDirectoryError();
|
|
197
|
+
return stat;
|
|
198
|
+
}, expected);
|
|
199
|
+
}
|
|
200
|
+
export function assertDirectoryObservationGuardSync(guard) {
|
|
201
|
+
const dir = guard.dir;
|
|
202
|
+
const expectedRealPath = guard.realPath;
|
|
203
|
+
assertNoWindowsPathAlias(expectedRealPath, "filesystem");
|
|
204
|
+
assertDirectoryObservationSync(dir, guard.identity);
|
|
205
|
+
const realPath = realpathSync.native(directoryOperationPath(dir));
|
|
206
|
+
assertNoWindowsPathAlias(realPath, "filesystem");
|
|
207
|
+
if (realPath !== expectedRealPath) {
|
|
208
|
+
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { type BigIntStats } from "node:fs";
|
|
2
2
|
import { type DirectoryModeOwner } from "./directory-mode-owner.js";
|
|
3
|
+
export declare function nodeDirectorySearchOnlyFlags(): {
|
|
4
|
+
flags: number;
|
|
5
|
+
proc: boolean;
|
|
6
|
+
} | undefined;
|
|
3
7
|
/** Real Node only: injected filesystem adapters must retain descriptor-chmod semantics. */
|
|
4
8
|
export declare function pinNodeDirectoryForMode(dirPath: string, options?: {
|
|
5
9
|
expectedIdentity?: BigIntStats;
|
|
6
10
|
ownerUid?: number;
|
|
7
11
|
}): Promise<DirectoryModeOwner>;
|
|
12
|
+
/** Synchronous counterpart with the same no-follow, identity, and owner checks. */
|
|
13
|
+
export declare function pinNodeDirectoryForModeSync(dirPath: string, options?: {
|
|
14
|
+
expectedIdentity?: BigIntStats;
|
|
15
|
+
ownerUid?: number;
|
|
16
|
+
}): {
|
|
17
|
+
apply(mode: number, check?: () => void): void;
|
|
18
|
+
close(): void;
|
|
19
|
+
};
|
|
8
20
|
//# sourceMappingURL=directory-mode-node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-mode-node.d.ts","sourceRoot":"","sources":["../src/directory-mode-node.ts"],"names":[],"mappings":"AAAA,OAAe,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK9D,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"directory-mode-node.d.ts","sourceRoot":"","sources":["../src/directory-mode-node.ts"],"names":[],"mappings":"AAAA,OAAe,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK9D,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE5G,wBAAgB,4BAA4B,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAO3F;AAED,2FAA2F;AAC3F,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,OAAO,CAAC,kBAAkB,CAAC,CAsE7B;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE;IAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAAC,KAAK,IAAI,IAAI,CAAA;CAAE,CAsFlE"}
|