@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/root-remove.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type RootContext } from "./root-context.js";
|
|
2
2
|
import type { RootRemoveOptions } from "./root-options.js";
|
|
3
|
+
import type { RemovalPathReceipts } from "./root-remove-receipt.js";
|
|
3
4
|
export declare function validateRemoveOptions(options: RootRemoveOptions): void;
|
|
4
|
-
export declare function removePathInRootFallback(root: RootContext, targetPath: string, options: RootRemoveOptions): Promise<void>;
|
|
5
|
+
export declare function removePathInRootFallback(root: RootContext, targetPath: string, options: RootRemoveOptions, receipts?: RemovalPathReceipts): Promise<void>;
|
|
5
6
|
//# sourceMappingURL=root-remove.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AASpE,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;AAsOD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA6Lf"}
|
package/dist/root-remove.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
5
5
|
import { assertMutationNotDenied } from "./deny-mutations.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
@@ -9,6 +9,7 @@ import { MutationAuthorityError } from "./mutation-authority.js";
|
|
|
9
9
|
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
10
10
|
import { assertRootIdentityCurrent } from "./root-context.js";
|
|
11
11
|
import { normalizeRemoveGuardError, normalizeRemovePathError, rootPathChangedError } from "./root-errors.js";
|
|
12
|
+
import { assertRemovalDirectoryCurrent, createRemovalDirectoryAssertion, } from "./root-remove-identity.js";
|
|
12
13
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
13
14
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
14
15
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
@@ -41,22 +42,178 @@ function assertNotAborted(signal) {
|
|
|
41
42
|
throw new MutationAuthorityError(error);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
function removalAncestorChanged(error) {
|
|
46
|
+
return new FsSafeError("path-mismatch", "removal ancestor changed during operation", {
|
|
47
|
+
cause: error instanceof Error ? error : undefined,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function sameCanonicalDirectory(left, right) {
|
|
51
|
+
return left === right || (isPathInside(left, right) && isPathInside(right, left));
|
|
52
|
+
}
|
|
53
|
+
function assertExactRemovalReceiptPrefixCurrent(rootGuard, intermediates) {
|
|
54
|
+
try {
|
|
55
|
+
assertSyncDirectoryGuard(rootGuard);
|
|
56
|
+
for (const receipt of intermediates) {
|
|
57
|
+
inspectDirectoryIdentitySync(receipt.path, receipt);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// force tolerates a vanished leaf, never a lost traversal boundary.
|
|
62
|
+
throw removalAncestorChanged(error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function assertMissingRemovalPrefixCurrent(rootGuard, intermediates) {
|
|
66
|
+
if (intermediates.length === 0) {
|
|
67
|
+
assertExactRemovalReceiptPrefixCurrent(rootGuard, intermediates);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const deepest = intermediates[intermediates.length - 1];
|
|
71
|
+
assertExactRemovalReceiptPrefixCurrent(rootGuard, intermediates.slice(0, -1));
|
|
72
|
+
let deepestGuard;
|
|
73
|
+
try {
|
|
74
|
+
deepestGuard = await createAsyncDirectoryGuard(deepest.path, { bigint: true });
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw removalAncestorChanged(error);
|
|
78
|
+
}
|
|
79
|
+
if (!sameFileIdentityForCleanup(deepestGuard.stat, deepest) ||
|
|
80
|
+
!sameCanonicalDirectory(deepestGuard.realPath, deepest.path)) {
|
|
81
|
+
throw removalAncestorChanged();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function captureNonrecursiveRemovalAdmission(root, targetPath, options, receipts) {
|
|
85
|
+
const parentPath = path.dirname(targetPath);
|
|
86
|
+
if (!isPathInside(root.rootReal, parentPath)) {
|
|
87
|
+
throw new FsSafeError("path-mismatch", "removal parent is outside the retained root");
|
|
88
|
+
}
|
|
89
|
+
const retained = receipts?.complete(root.rootReal, targetPath);
|
|
90
|
+
let rootGuard;
|
|
46
91
|
try {
|
|
47
|
-
|
|
92
|
+
// A complete, exactly spelled parent canonicalization below also proves
|
|
93
|
+
// Root's canonical route. Retain Root's earlier exact identity either way.
|
|
94
|
+
rootGuard = retained && parentPath !== root.rootReal
|
|
95
|
+
? { dir: root.rootReal, realPath: root.rootReal, stat: retained.rootStat }
|
|
96
|
+
: await createAsyncDirectoryGuard(root.rootReal, { bigint: true, initial: retained?.rootStat });
|
|
48
97
|
}
|
|
49
98
|
catch (error) {
|
|
50
|
-
|
|
99
|
+
// force may tolerate a missing target or parent, never a missing Root.
|
|
100
|
+
if (options.force && isNotFoundPathError(error))
|
|
51
101
|
await assertRootIdentityCurrent(root);
|
|
102
|
+
throw normalizeRemoveGuardError(error);
|
|
103
|
+
}
|
|
104
|
+
if (!sameFileIdentityForCleanup(rootGuard.stat, root.rootIdentity))
|
|
105
|
+
throw rootPathChangedError();
|
|
106
|
+
if (parentPath === root.rootReal) {
|
|
107
|
+
const rootAssertion = createRemovalDirectoryAssertion(rootGuard.dir, rootGuard.stat, rootGuard.realPath);
|
|
108
|
+
return Object.freeze({
|
|
109
|
+
assertAfterMutation() {
|
|
110
|
+
// Here Root is also the immediate parent, so retain its established
|
|
111
|
+
// post-dispatch error classification while checking it only once.
|
|
112
|
+
assertRemovalDirectoryCurrent(rootAssertion);
|
|
113
|
+
},
|
|
114
|
+
assertCurrent() {
|
|
115
|
+
try {
|
|
116
|
+
assertRemovalDirectoryCurrent(rootAssertion);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
throw removalAncestorChanged(error);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const intermediates = [];
|
|
125
|
+
const retainedDirectories = retained?.directories;
|
|
126
|
+
const freshSegments = retainedDirectories
|
|
127
|
+
? undefined
|
|
128
|
+
: path.relative(root.rootReal, parentPath).split(path.sep).filter(Boolean).slice(0, -1);
|
|
129
|
+
const intermediateCount = retainedDirectories ? Math.max(0, retainedDirectories.length - 1) : freshSegments.length;
|
|
130
|
+
let freshAncestor = root.rootReal;
|
|
131
|
+
for (let index = 0; index < intermediateCount; index += 1) {
|
|
132
|
+
const retainedDirectory = retainedDirectories?.[index];
|
|
133
|
+
const ancestor = retainedDirectory?.path ?? (freshAncestor = path.join(freshAncestor, freshSegments[index]));
|
|
134
|
+
try {
|
|
135
|
+
const stat = inspectDirectoryIdentitySync(ancestor, undefined, retainedDirectory?.stat);
|
|
136
|
+
intermediates.push(createRemovalDirectoryAssertion(ancestor, stat));
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (isNotFoundPathError(error)) {
|
|
140
|
+
await assertMissingRemovalPrefixCurrent(rootGuard, intermediates);
|
|
141
|
+
assertNotAborted(options.signal);
|
|
142
|
+
if (options.force)
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
throw normalizeRemoveGuardError(error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
let parentGuard;
|
|
149
|
+
try {
|
|
150
|
+
parentGuard = await createAsyncDirectoryGuard(retained?.parent?.path ?? parentPath, {
|
|
151
|
+
bigint: true,
|
|
152
|
+
initial: retained?.parent?.stat,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (isNotFoundPathError(error)) {
|
|
157
|
+
await assertMissingRemovalPrefixCurrent(rootGuard, intermediates);
|
|
52
158
|
assertNotAborted(options.signal);
|
|
53
|
-
|
|
159
|
+
if (options.force)
|
|
160
|
+
return undefined;
|
|
54
161
|
}
|
|
55
162
|
throw normalizeRemoveGuardError(error);
|
|
56
163
|
}
|
|
164
|
+
if (!sameCanonicalDirectory(parentGuard.realPath, parentPath)) {
|
|
165
|
+
throw removalAncestorChanged();
|
|
166
|
+
}
|
|
167
|
+
const canonicalParentCoversRoot = retained !== undefined && parentGuard.realPath === parentPath;
|
|
168
|
+
if (retained && !canonicalParentCoversRoot) {
|
|
169
|
+
try {
|
|
170
|
+
rootGuard = await createAsyncDirectoryGuard(root.rootReal, { bigint: true, initial: retained.rootStat });
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (options.force && isNotFoundPathError(error))
|
|
174
|
+
await assertRootIdentityCurrent(root);
|
|
175
|
+
throw normalizeRemoveGuardError(error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const rootAssertion = createRemovalDirectoryAssertion(rootGuard.dir, rootGuard.stat, canonicalParentCoversRoot ? undefined : rootGuard.realPath);
|
|
179
|
+
const parentAssertion = createRemovalDirectoryAssertion(parentGuard.dir, parentGuard.stat, parentGuard.realPath);
|
|
180
|
+
const assertPrefixCurrent = () => {
|
|
181
|
+
try {
|
|
182
|
+
assertRemovalDirectoryCurrent(rootAssertion);
|
|
183
|
+
for (const assertion of intermediates)
|
|
184
|
+
assertRemovalDirectoryCurrent(assertion);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
throw removalAncestorChanged(error);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return Object.freeze({
|
|
191
|
+
assertAfterMutation() {
|
|
192
|
+
assertPrefixCurrent();
|
|
193
|
+
// Preserve the established nonrecursive post-dispatch mapping for the
|
|
194
|
+
// immediate parent: disappearance is `not-found`, while replacements
|
|
195
|
+
// retain their directory-guard classification. Earlier lost ancestry
|
|
196
|
+
// remains a fail-closed `path-mismatch`.
|
|
197
|
+
assertRemovalDirectoryCurrent(parentAssertion);
|
|
198
|
+
},
|
|
199
|
+
assertCurrent() {
|
|
200
|
+
assertPrefixCurrent();
|
|
201
|
+
try {
|
|
202
|
+
assertRemovalDirectoryCurrent(parentAssertion);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
throw removalAncestorChanged(error);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
async function removeOne(root, targetPath, options, receipts) {
|
|
211
|
+
const admission = await captureNonrecursiveRemovalAdmission(root, targetPath, options, receipts);
|
|
212
|
+
if (!admission)
|
|
213
|
+
return;
|
|
57
214
|
try {
|
|
58
215
|
await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("remove", targetPath);
|
|
59
|
-
|
|
216
|
+
admission.assertCurrent();
|
|
60
217
|
}
|
|
61
218
|
catch (error) {
|
|
62
219
|
throw normalizeRemoveGuardError(error);
|
|
@@ -72,13 +229,18 @@ async function removeOne(root, targetPath, options) {
|
|
|
72
229
|
if (!(options.force && isNotFoundPathError(error)))
|
|
73
230
|
throw normalizeRemovePathError(error);
|
|
74
231
|
}
|
|
75
|
-
|
|
232
|
+
try {
|
|
233
|
+
admission.assertAfterMutation();
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
throw normalizeRemoveGuardError(error);
|
|
237
|
+
}
|
|
76
238
|
assertNotAborted(options.signal);
|
|
77
239
|
}
|
|
78
|
-
export async function removePathInRootFallback(root, targetPath, options) {
|
|
240
|
+
export async function removePathInRootFallback(root, targetPath, options, receipts) {
|
|
79
241
|
assertNotAborted(options.signal);
|
|
80
242
|
if (!options.recursive) {
|
|
81
|
-
await removeOne(root, targetPath, options);
|
|
243
|
+
await removeOne(root, targetPath, options, receipts);
|
|
82
244
|
return;
|
|
83
245
|
}
|
|
84
246
|
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { BigIntStats } from "node:fs";
|
|
2
|
+
import { type DenyMutationPolicy } from "./deny-mutations.js";
|
|
3
|
+
import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
|
+
import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
|
|
5
|
+
import type { RootContext } from "./root-context.js";
|
|
6
|
+
import { resolvePathInRoot } from "./root-context.js";
|
|
7
|
+
import { type MutationSymlinkPolicy } from "./root-symlink-policy.js";
|
|
8
|
+
import { type PreparedRootWriteParent } from "./root-write-complete-parent.js";
|
|
9
|
+
export type PinnedWriteTarget = Readonly<{
|
|
10
|
+
rootReal: string;
|
|
11
|
+
targetPath: string;
|
|
12
|
+
relativeParentPath: string;
|
|
13
|
+
basename: string;
|
|
14
|
+
mode: number;
|
|
15
|
+
mutationAdmission?: PinnedWriteMutationAdmission;
|
|
16
|
+
}>;
|
|
17
|
+
export type GuardedWritePath = Awaited<ReturnType<typeof resolvePathInRoot>>;
|
|
18
|
+
export type SelectedRootWriteTargetAdmission = Readonly<{
|
|
19
|
+
authorize(selectedTargetPath: string): Promise<void>;
|
|
20
|
+
}>;
|
|
21
|
+
export type GuardedRootWriteTarget = Readonly<{
|
|
22
|
+
resolvedPath: GuardedWritePath;
|
|
23
|
+
targetPath: string;
|
|
24
|
+
mutationAdmission?: PinnedWriteMutationAdmission;
|
|
25
|
+
selectedTargetAdmission?: SelectedRootWriteTargetAdmission;
|
|
26
|
+
}>;
|
|
27
|
+
export type RootWritePathSelection = Readonly<{
|
|
28
|
+
mutationAdmission: PinnedWriteMutationAdmission;
|
|
29
|
+
selectedTargetAdmission: SelectedRootWriteTargetAdmission;
|
|
30
|
+
operationTargetPath: string;
|
|
31
|
+
selectedPath: string;
|
|
32
|
+
parentGuard: AnyAsyncDirectoryGuard;
|
|
33
|
+
}>;
|
|
34
|
+
export type RetainedRootWriteSelection = RootWritePathSelection & Readonly<{
|
|
35
|
+
identity: Readonly<Pick<BigIntStats, "dev" | "ino">>;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function retainRootWriteSelection(owner: object, selection: RetainedRootWriteSelection): void;
|
|
38
|
+
export declare function createRootWriteSelectionForFd(selection: RootWritePathSelection, fd: number): RetainedRootWriteSelection;
|
|
39
|
+
export declare function takeRootWriteSelection(owner: object): RetainedRootWriteSelection | undefined;
|
|
40
|
+
export declare function assertRootWritePathSelectionSync(root: RootContext, selection: RootWritePathSelection): void;
|
|
41
|
+
export declare function assertRootWriteSelectionSync(root: RootContext, selection: RetainedRootWriteSelection, verifyBinding: boolean, fd?: number): void;
|
|
42
|
+
export declare function prepareRootWritePathSelection(params: Omit<RootWritePathSelection, "parentGuard"> & Readonly<{
|
|
43
|
+
preparedParent?: PreparedRootWriteParent;
|
|
44
|
+
}>): Promise<RootWritePathSelection>;
|
|
45
|
+
export declare function prepareGuardedRootWritePathSelection(guarded: {
|
|
46
|
+
mutationAdmission?: PinnedWriteMutationAdmission;
|
|
47
|
+
selectedTargetAdmission?: SelectedRootWriteTargetAdmission;
|
|
48
|
+
} | undefined, selectedPath: string, operationTargetPath?: string, preparedParent?: PreparedRootWriteParent): Promise<RootWritePathSelection | undefined>;
|
|
49
|
+
export declare function refreshRootWritePathSelection(selection: RootWritePathSelection): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Re-authorize a retained shared-JavaScript destination without recapturing
|
|
52
|
+
* either its pathname binding or its exact parent fence.
|
|
53
|
+
*/
|
|
54
|
+
export declare function refreshRootWriteSelection(root: RootContext, selection: RetainedRootWriteSelection, verifyBinding: boolean, fd?: number): Promise<void>;
|
|
55
|
+
export declare function refreshRetainedRootWriteAdmission(root: RootContext, selection: RetainedRootWriteSelection, verifyBinding: boolean, fd?: number): Promise<void>;
|
|
56
|
+
type GuardedWritePathOptions = {
|
|
57
|
+
relativePath: string;
|
|
58
|
+
denyMutations?: DenyMutationPolicy;
|
|
59
|
+
mutationSymlinks?: MutationSymlinkPolicy;
|
|
60
|
+
allowFinalSymlink?: boolean;
|
|
61
|
+
protectDeniedAncestors?: boolean;
|
|
62
|
+
shouldAssertNoPathAlias?: (resolved: GuardedWritePath) => Promise<boolean> | boolean;
|
|
63
|
+
};
|
|
64
|
+
export declare function resolveGuardedWritePathInRoot(root: RootContext, params: GuardedWritePathOptions): Promise<GuardedWritePath>;
|
|
65
|
+
export declare function resolveGuardedWriteTargetInRoot(root: RootContext, params: GuardedWritePathOptions): Promise<GuardedRootWriteTarget>;
|
|
66
|
+
export declare function resolvePinnedWriteTargetInRoot(root: RootContext, relativePath: string, requestedMode?: number, denyMutations?: DenyMutationPolicy, overwrite?: boolean, mutationSymlinks?: MutationSymlinkPolicy): Promise<PinnedWriteTarget>;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=root-write-admission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-write-admission.d.ts","sourceRoot":"","sources":["../src/root-write-admission.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { assertMutationNotDenied } from "./deny-mutations.js";
|
|
4
|
+
import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
|
|
5
|
+
import { FsSafeError } from "./errors.js";
|
|
6
|
+
import { isNotFoundPathError } from "./path.js";
|
|
7
|
+
import { assertNoPathAliasEscape } from "./path-policy.js";
|
|
8
|
+
import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy, } from "./pinned-mutation-admission.js";
|
|
9
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
10
|
+
import { resolvePathInRoot } from "./root-context.js";
|
|
11
|
+
import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
|
|
12
|
+
import { inheritWriteTargetMode } from "./root-write-mode.js";
|
|
13
|
+
import { mutationSymlinkResolution, } from "./root-symlink-policy.js";
|
|
14
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
15
|
+
import { realpathSync } from "./realpath.js";
|
|
16
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
17
|
+
import { assertPreparedRootWriteParentCurrent, } from "./root-write-complete-parent.js";
|
|
18
|
+
const retainedRootWriteSelections = new WeakMap();
|
|
19
|
+
export function retainRootWriteSelection(owner, selection) {
|
|
20
|
+
retainedRootWriteSelections.set(owner, selection);
|
|
21
|
+
}
|
|
22
|
+
export function createRootWriteSelectionForFd(selection, fd) {
|
|
23
|
+
const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
...selection,
|
|
26
|
+
identity: Object.freeze({ dev: stat.dev, ino: stat.ino }),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function takeRootWriteSelection(owner) {
|
|
30
|
+
const selection = retainedRootWriteSelections.get(owner);
|
|
31
|
+
retainedRootWriteSelections.delete(owner);
|
|
32
|
+
return selection;
|
|
33
|
+
}
|
|
34
|
+
function writeSelectionChanged(cause) {
|
|
35
|
+
return new FsSafeError("path-mismatch", "write target changed during operation", {
|
|
36
|
+
cause: cause instanceof Error ? cause : undefined,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function inspectRegularSelectionPath(pathname, expected, follow) {
|
|
40
|
+
let observed;
|
|
41
|
+
try {
|
|
42
|
+
observed = inspectFileIdentitySync(() => follow
|
|
43
|
+
? fsSync.statSync(pathname, { bigint: true })
|
|
44
|
+
: fsSync.lstatSync(pathname, { bigint: true }), expected);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof FsSafeError && error.code === "path-mismatch")
|
|
48
|
+
throw error;
|
|
49
|
+
throw writeSelectionChanged(error);
|
|
50
|
+
}
|
|
51
|
+
if ((!follow && observed.isSymbolicLink()) || !observed.isFile()) {
|
|
52
|
+
throw writeSelectionChanged();
|
|
53
|
+
}
|
|
54
|
+
if (observed.nlink > 1n)
|
|
55
|
+
throw hardlinkedPathNotAllowedError();
|
|
56
|
+
return observed;
|
|
57
|
+
}
|
|
58
|
+
function sameNormalizedPathSpelling(left, right) {
|
|
59
|
+
// Windows can expose case-sensitive directories, so normalize separators
|
|
60
|
+
// and roots without using path.relative's case-folding comparison.
|
|
61
|
+
return path.resolve(left) === path.resolve(right);
|
|
62
|
+
}
|
|
63
|
+
function inspectSelectionBindingSync(root, selection, fd) {
|
|
64
|
+
inspectRegularSelectionPath(selection.selectedPath, selection.identity, false);
|
|
65
|
+
if (!sameNormalizedPathSpelling(selection.operationTargetPath, selection.selectedPath)) {
|
|
66
|
+
let operationRealPath;
|
|
67
|
+
try {
|
|
68
|
+
operationRealPath = realpathSync.native(selection.operationTargetPath);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
throw writeSelectionChanged(error);
|
|
72
|
+
}
|
|
73
|
+
const admitted = admitPathInsideRoot({
|
|
74
|
+
rootPath: root.rootReal,
|
|
75
|
+
candidatePath: operationRealPath,
|
|
76
|
+
rootIdentity: root.rootIdentity,
|
|
77
|
+
});
|
|
78
|
+
if (!admitted)
|
|
79
|
+
throw outsideWorkspaceError();
|
|
80
|
+
inspectRegularSelectionPath(admitted.path, selection.identity, true);
|
|
81
|
+
}
|
|
82
|
+
if (fd !== undefined) {
|
|
83
|
+
try {
|
|
84
|
+
inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), selection.identity);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof FsSafeError && error.code === "path-mismatch")
|
|
88
|
+
throw error;
|
|
89
|
+
throw writeSelectionChanged(error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function assertRootWritePathSelectionSync(root, selection) {
|
|
94
|
+
if (!sameNormalizedPathSpelling(selection.operationTargetPath, selection.selectedPath)) {
|
|
95
|
+
let operationRealPath;
|
|
96
|
+
try {
|
|
97
|
+
operationRealPath = realpathSync.native(selection.operationTargetPath);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw writeSelectionChanged(error);
|
|
101
|
+
}
|
|
102
|
+
const admitted = admitPathInsideRoot({
|
|
103
|
+
rootPath: root.rootReal,
|
|
104
|
+
candidatePath: operationRealPath,
|
|
105
|
+
rootIdentity: root.rootIdentity,
|
|
106
|
+
});
|
|
107
|
+
if (!admitted)
|
|
108
|
+
throw outsideWorkspaceError();
|
|
109
|
+
const selected = inspectRegularSelectionPath(selection.selectedPath, undefined, false);
|
|
110
|
+
inspectRegularSelectionPath(admitted.path, selected, true);
|
|
111
|
+
}
|
|
112
|
+
assertSyncDirectoryGuard(selection.parentGuard);
|
|
113
|
+
}
|
|
114
|
+
export function assertRootWriteSelectionSync(root, selection, verifyBinding, fd) {
|
|
115
|
+
if (verifyBinding)
|
|
116
|
+
inspectSelectionBindingSync(root, selection, fd);
|
|
117
|
+
assertSyncDirectoryGuard(selection.parentGuard);
|
|
118
|
+
}
|
|
119
|
+
async function authorizeRootWritePathSelection(selection, afterAwait) {
|
|
120
|
+
const selectedAdmissionPending = !sameNormalizedPathSpelling(selection.operationTargetPath, selection.selectedPath);
|
|
121
|
+
await selection.mutationAdmission.authorize(Object.freeze({
|
|
122
|
+
targetPath: selection.operationTargetPath,
|
|
123
|
+
mutationPath: selection.operationTargetPath,
|
|
124
|
+
phase: "parent",
|
|
125
|
+
}));
|
|
126
|
+
afterAwait?.(selectedAdmissionPending);
|
|
127
|
+
if (selectedAdmissionPending) {
|
|
128
|
+
await selection.selectedTargetAdmission.authorize(selection.selectedPath);
|
|
129
|
+
afterAwait?.(false);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export async function prepareRootWritePathSelection(params) {
|
|
133
|
+
const { preparedParent, ...selectionParams } = params;
|
|
134
|
+
if (preparedParent && (!sameNormalizedPathSpelling(preparedParent.operationTargetPath, params.operationTargetPath) ||
|
|
135
|
+
!sameNormalizedPathSpelling(preparedParent.parentGuard.dir, path.dirname(params.operationTargetPath))))
|
|
136
|
+
throw writeSelectionChanged();
|
|
137
|
+
const selection = Object.freeze({
|
|
138
|
+
...selectionParams,
|
|
139
|
+
parentGuard: preparedParent?.parentGuard ??
|
|
140
|
+
await createAsyncDirectoryGuard(path.dirname(params.selectedPath), { bigint: true }),
|
|
141
|
+
});
|
|
142
|
+
await authorizeRootWritePathSelection(selection, preparedParent
|
|
143
|
+
? (selectedAdmissionPending) => assertPreparedRootWriteParentCurrent(preparedParent, !selectedAdmissionPending)
|
|
144
|
+
: undefined);
|
|
145
|
+
if (preparedParent) {
|
|
146
|
+
if (!sameNormalizedPathSpelling(preparedParent.selectedTargetPath, params.selectedPath)) {
|
|
147
|
+
throw writeSelectionChanged();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
await assertAsyncDirectoryGuard(selection.parentGuard);
|
|
152
|
+
}
|
|
153
|
+
return selection;
|
|
154
|
+
}
|
|
155
|
+
export async function prepareGuardedRootWritePathSelection(guarded, selectedPath, operationTargetPath = selectedPath, preparedParent) {
|
|
156
|
+
if (!guarded?.mutationAdmission)
|
|
157
|
+
return undefined;
|
|
158
|
+
if (!guarded.selectedTargetAdmission)
|
|
159
|
+
throw writeSelectionChanged();
|
|
160
|
+
return await prepareRootWritePathSelection({
|
|
161
|
+
mutationAdmission: guarded.mutationAdmission,
|
|
162
|
+
selectedTargetAdmission: guarded.selectedTargetAdmission,
|
|
163
|
+
operationTargetPath,
|
|
164
|
+
selectedPath,
|
|
165
|
+
preparedParent,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
export async function refreshRootWritePathSelection(selection) {
|
|
169
|
+
await authorizeRootWritePathSelection(selection);
|
|
170
|
+
await assertAsyncDirectoryGuard(selection.parentGuard);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Re-authorize a retained shared-JavaScript destination without recapturing
|
|
174
|
+
* either its pathname binding or its exact parent fence.
|
|
175
|
+
*/
|
|
176
|
+
export async function refreshRootWriteSelection(root, selection, verifyBinding, fd) {
|
|
177
|
+
await authorizeRootWritePathSelection(selection);
|
|
178
|
+
if (verifyBinding)
|
|
179
|
+
inspectSelectionBindingSync(root, selection, fd);
|
|
180
|
+
await assertAsyncDirectoryGuard(selection.parentGuard);
|
|
181
|
+
}
|
|
182
|
+
export async function refreshRetainedRootWriteAdmission(root, selection, verifyBinding, fd) {
|
|
183
|
+
await getFsSafeTestHooks()?.beforePinnedWriteParentAdmission?.(selection.operationTargetPath);
|
|
184
|
+
await refreshRootWriteSelection(root, selection, verifyBinding, fd);
|
|
185
|
+
}
|
|
186
|
+
export async function resolveGuardedWritePathInRoot(root, params) {
|
|
187
|
+
const resolvedPath = await resolvePathInRoot(root, params.relativePath, {
|
|
188
|
+
aliasErrorCode: "path-alias",
|
|
189
|
+
rejectAmbiguousParents: true,
|
|
190
|
+
allowFinalSymlink: params.allowFinalSymlink,
|
|
191
|
+
...mutationSymlinkResolution(params.mutationSymlinks),
|
|
192
|
+
});
|
|
193
|
+
await assertMutationNotDenied(resolvedPath.resolved, params.denyMutations, params.protectDeniedAncestors ? { protectAncestors: true } : undefined);
|
|
194
|
+
if (await (params.shouldAssertNoPathAlias?.(resolvedPath) ?? true)) {
|
|
195
|
+
try {
|
|
196
|
+
await assertNoPathAliasEscape({
|
|
197
|
+
absolutePath: resolvedPath.resolved,
|
|
198
|
+
rootPath: resolvedPath.rootReal,
|
|
199
|
+
boundaryLabel: "root",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
throw new FsSafeError("path-alias", "path alias escape blocked", {
|
|
204
|
+
cause: error instanceof Error ? error : undefined,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return resolvedPath;
|
|
209
|
+
}
|
|
210
|
+
export async function resolveGuardedWriteTargetInRoot(root, params) {
|
|
211
|
+
const policy = params.denyMutations === undefined && params.mutationSymlinks === undefined
|
|
212
|
+
? undefined
|
|
213
|
+
: snapshotPinnedMutationPolicy(params.denyMutations, params.mutationSymlinks);
|
|
214
|
+
const resolveCurrent = async () => await resolveGuardedWritePathInRoot(root, {
|
|
215
|
+
...params,
|
|
216
|
+
denyMutations: policy?.denyMutations,
|
|
217
|
+
mutationSymlinks: policy?.mutationSymlinks,
|
|
218
|
+
});
|
|
219
|
+
const resolvedPath = await resolveCurrent();
|
|
220
|
+
if (!policy)
|
|
221
|
+
return { resolvedPath, targetPath: resolvedPath.resolved };
|
|
222
|
+
const relativeParent = path.relative(resolvedPath.rootReal, path.dirname(resolvedPath.resolved));
|
|
223
|
+
const prepared = await preparePinnedWriteMutationAdmission({
|
|
224
|
+
rootReal: resolvedPath.rootReal,
|
|
225
|
+
rootWithSep: resolvedPath.rootWithSep,
|
|
226
|
+
rootIdentity: root.rootIdentity,
|
|
227
|
+
resolvedTargetPath: resolvedPath.resolved,
|
|
228
|
+
originalPath: params.relativePath,
|
|
229
|
+
defaultRelativeParentPath: relativeParent
|
|
230
|
+
? relativeParent.split(path.sep).join(path.posix.sep)
|
|
231
|
+
: "",
|
|
232
|
+
policy,
|
|
233
|
+
resolveCurrent,
|
|
234
|
+
});
|
|
235
|
+
const selectedTargetAdmission = Object.freeze({
|
|
236
|
+
async authorize(selectedTargetPath) {
|
|
237
|
+
const admitted = admitPathInsideRoot({
|
|
238
|
+
rootPath: root.rootReal,
|
|
239
|
+
candidatePath: selectedTargetPath,
|
|
240
|
+
rootIdentity: root.rootIdentity,
|
|
241
|
+
});
|
|
242
|
+
if (!admitted)
|
|
243
|
+
throw outsideWorkspaceError();
|
|
244
|
+
await resolveGuardedWritePathInRoot(root, {
|
|
245
|
+
relativePath: admitted.relativePath,
|
|
246
|
+
denyMutations: policy.denyMutations,
|
|
247
|
+
mutationSymlinks: policy.mutationSymlinks,
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
const targetPath = path.join(resolvedPath.rootReal, ...prepared.relativeParentPath.split("/").filter(Boolean), path.basename(resolvedPath.resolved));
|
|
252
|
+
return {
|
|
253
|
+
resolvedPath,
|
|
254
|
+
targetPath,
|
|
255
|
+
mutationAdmission: prepared.mutationAdmission,
|
|
256
|
+
selectedTargetAdmission,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations, overwrite = true, mutationSymlinks) {
|
|
260
|
+
// Snapshot mutable caller-owned policy before preflight so every admission
|
|
261
|
+
// in this operation authorizes the same rules.
|
|
262
|
+
const policy = denyMutations === undefined && mutationSymlinks === undefined
|
|
263
|
+
? undefined
|
|
264
|
+
: snapshotPinnedMutationPolicy(denyMutations, mutationSymlinks);
|
|
265
|
+
const resolveCurrent = policy
|
|
266
|
+
? async () => await resolveGuardedWritePathInRoot(root, {
|
|
267
|
+
relativePath,
|
|
268
|
+
denyMutations: policy.denyMutations,
|
|
269
|
+
mutationSymlinks: policy.mutationSymlinks,
|
|
270
|
+
})
|
|
271
|
+
: undefined;
|
|
272
|
+
const { rootReal, rootWithSep, resolved } = resolveCurrent
|
|
273
|
+
? await resolveCurrent()
|
|
274
|
+
: await resolveGuardedWritePathInRoot(root, { relativePath });
|
|
275
|
+
const relativeResolved = path.relative(rootReal, resolved);
|
|
276
|
+
if (path.isAbsolute(relativeResolved))
|
|
277
|
+
throw outsideWorkspaceError();
|
|
278
|
+
const relativePosix = relativeResolved
|
|
279
|
+
? relativeResolved.split(path.sep).join(path.posix.sep)
|
|
280
|
+
: "";
|
|
281
|
+
const basename = path.posix.basename(relativePosix);
|
|
282
|
+
if (!basename || basename === "." || basename === "/") {
|
|
283
|
+
throw new FsSafeError("invalid-path", "invalid target path");
|
|
284
|
+
}
|
|
285
|
+
if (!overwrite) {
|
|
286
|
+
try {
|
|
287
|
+
const existing = fsSync.statSync(resolved);
|
|
288
|
+
if (!existing.isFile())
|
|
289
|
+
throw new FsSafeError("not-file", "not a file");
|
|
290
|
+
if (existing.nlink > 1)
|
|
291
|
+
throw hardlinkedPathNotAllowedError();
|
|
292
|
+
throw new FsSafeError("already-exists", "file already exists");
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
if (!isNotFoundPathError(error))
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const mode = overwrite
|
|
300
|
+
? await inheritWriteTargetMode({
|
|
301
|
+
targetPath: resolved,
|
|
302
|
+
rootWithSep,
|
|
303
|
+
rootIdentity: root.rootIdentity,
|
|
304
|
+
requestedMode,
|
|
305
|
+
})
|
|
306
|
+
: requestedMode ?? 0o600;
|
|
307
|
+
let relativeParentPath = path.posix.dirname(relativePosix) === "." ? "" : path.posix.dirname(relativePosix);
|
|
308
|
+
let mutationAdmission;
|
|
309
|
+
if (policy) {
|
|
310
|
+
({ relativeParentPath, mutationAdmission } = await preparePinnedWriteMutationAdmission({
|
|
311
|
+
rootReal,
|
|
312
|
+
rootWithSep,
|
|
313
|
+
rootIdentity: root.rootIdentity,
|
|
314
|
+
resolvedTargetPath: resolved,
|
|
315
|
+
originalPath: relativePath,
|
|
316
|
+
defaultRelativeParentPath: relativeParentPath,
|
|
317
|
+
policy,
|
|
318
|
+
resolveCurrent: resolveCurrent,
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
return { rootReal, targetPath: resolved, relativeParentPath, basename, mode, mutationAdmission };
|
|
322
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type RootBoundaryIdentity } from "./root-boundary.js";
|
|
2
|
+
export declare function assertRootFallbackWritePath(expected: string | undefined, actual: string): void;
|
|
3
|
+
export declare function withRootFallbackCompatibilityLock<T>(params: {
|
|
4
|
+
rootPath: string;
|
|
5
|
+
targetPath: string;
|
|
6
|
+
rootIdentity?: RootBoundaryIdentity;
|
|
7
|
+
assertBeforeMutation?: () => void;
|
|
8
|
+
}, run: (binding: {
|
|
9
|
+
targetPath: string;
|
|
10
|
+
relativePath: string;
|
|
11
|
+
assertBeforeMutation: () => void;
|
|
12
|
+
}) => Promise<T>): Promise<T>;
|
|
13
|
+
//# sourceMappingURL=root-write-compatibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-write-compatibility.d.ts","sourceRoot":"","sources":["../src/root-write-compatibility.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AA6CpF,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAI9F;AAED,wBAAsB,iCAAiC,CAAC,CAAC,EACvD,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,EACxH,GAAG,EAAE,CAAC,OAAO,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,IAAI,CAAA;CAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3G,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
|