@openclaw/fs-safe 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -0
- package/README.md +15 -8
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +3 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +3 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -3
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +13 -9
- 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 +55 -46
- 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 +50 -17
- 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/bounded-read.js +2 -2
- 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/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -3
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +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 +12 -2
- 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-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +9 -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 +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +85 -23
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +10 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +217 -33
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +16 -5
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts +6 -0
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +67 -19
- package/dist/json-durable-queue-ownership.d.ts +8 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +134 -43
- 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 +6 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -0
- package/dist/json-durable-queue-read.js +61 -0
- package/dist/json-durable-queue.d.ts +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +83 -134
- 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 +21 -0
- package/dist/native-binding.d.ts.map +1 -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-parent-admission.d.ts +31 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +124 -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 +0 -3
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +309 -40
- 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 +14 -0
- package/dist/path-scope-lexical.d.ts.map +1 -0
- package/dist/path-scope-lexical.js +37 -0
- 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 +35 -6
- 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 +152 -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 +33 -29
- 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 +29 -0
- package/dist/root-boundary.d.ts.map +1 -0
- package/dist/root-boundary.js +182 -0
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +89 -16
- 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 +182 -33
- 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 +316 -325
- 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-move-preflight.d.ts +8 -0
- package/dist/root-move-preflight.d.ts.map +1 -0
- package/dist/root-move-preflight.js +16 -0
- package/dist/root-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 +2 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +63 -22
- 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 +13 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +263 -73
- 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 -29
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +89 -178
- 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-walk.d.ts.map +1 -1
- package/dist/root-walk.js +2 -1
- 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-mode.d.ts +2 -0
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +20 -7
- 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 +18 -7
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +3 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +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-windows.d.ts +10 -0
- package/dist/secure-file-windows.d.ts.map +1 -0
- package/dist/secure-file-windows.js +186 -0
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +98 -10
- 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 -1
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +181 -50
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -12
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +45 -15
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -3
- 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 +133 -4
- 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 +20 -16
- 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 +28 -1
- package/docs/archive.md +29 -2
- package/docs/atomic.md +27 -3
- package/docs/contributing.md +4 -1
- package/docs/copy.md +4 -1
- package/docs/durability.md +18 -2
- package/docs/errors.md +14 -6
- package/docs/file-store.md +30 -3
- package/docs/filename.md +21 -7
- package/docs/guest.md +5 -0
- package/docs/install-path.md +61 -15
- package/docs/install.md +12 -8
- 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 +28 -9
- package/docs/native.md +46 -10
- 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 +12 -1
- package/docs/permissions.md +26 -1
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +14 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +21 -4
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +21 -16
- package/docs/security-model.md +76 -9
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +27 -2
- package/docs/temp.md +262 -34
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import fs, {} from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
4
|
-
import { readBoundedAsync } from "./bounded-read.js";
|
|
5
3
|
import { syncDirectory } from "./directory-durability.js";
|
|
6
4
|
import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
|
|
7
|
-
import {
|
|
5
|
+
import { queueValidationRoot, queueValidationRoots, resolveQueueFilesystemPath, } from "./json-durable-queue-paths.js";
|
|
6
|
+
import { getErrorCode, acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, migrateDurableQueueEntry, moveDurableQueueEntryToFailed, validateDurableQueueEntryPaths, } from "./json-durable-queue-ownership.js";
|
|
7
|
+
import { withJsonDurableQueueEntry } from "./json-durable-queue-read.js";
|
|
8
8
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
9
|
-
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
10
9
|
import { realpathSync } from "./realpath.js";
|
|
11
10
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
12
11
|
import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
|
|
13
12
|
import { assertSafePathSegment } from "./safe-path-segment.js";
|
|
14
|
-
import {
|
|
15
|
-
|
|
13
|
+
import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
|
|
14
|
+
import { assertNoWindowsPathAlias, hasWindowsPathAlias, } from "./windows-path-alias.js";
|
|
15
|
+
export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
|
|
16
16
|
function assertSafeQueueEntryId(id) {
|
|
17
17
|
assertSafePathSegment(id, { label: "queue entry id" });
|
|
18
18
|
}
|
|
19
19
|
export async function unlinkBestEffort(filePath) {
|
|
20
|
+
if (hasWindowsPathAlias(filePath, "filesystem"))
|
|
21
|
+
return;
|
|
20
22
|
await fs.promises.unlink(filePath).catch(() => undefined);
|
|
21
23
|
}
|
|
22
24
|
export async function jsonDurableQueueEntryExists(filePath) {
|
|
25
|
+
assertNoWindowsPathAlias(filePath);
|
|
23
26
|
try {
|
|
24
27
|
const stat = fs.lstatSync(filePath);
|
|
25
28
|
return stat.isFile();
|
|
@@ -46,6 +49,7 @@ async function unlinkStaleTmpBestEffort(filePath, now, maxAgeMs) {
|
|
|
46
49
|
}
|
|
47
50
|
export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
|
|
48
51
|
assertSafeQueueEntryId(id);
|
|
52
|
+
assertNoWindowsPathAlias(queueDir);
|
|
49
53
|
return {
|
|
50
54
|
jsonPath: path.join(queueDir, `${id}.json`),
|
|
51
55
|
deliveredPath: path.join(queueDir, `${id}.delivered`),
|
|
@@ -53,9 +57,13 @@ export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
|
|
|
53
57
|
};
|
|
54
58
|
}
|
|
55
59
|
export async function ensureJsonDurableQueueDirs(params) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
const queueDir = params.queueDir;
|
|
61
|
+
const failedDir = params.failedDir;
|
|
62
|
+
assertNoWindowsPathAlias(queueDir);
|
|
63
|
+
assertNoWindowsPathAlias(failedDir);
|
|
64
|
+
const roots = await queueValidationRoots(queueDir, failedDir);
|
|
65
|
+
await ensureJsonDurableQueueDir(queueDir, roots.queueRoot);
|
|
66
|
+
await ensureJsonDurableQueueDir(failedDir, roots.failedRoot);
|
|
59
67
|
}
|
|
60
68
|
async function ensureJsonDurableQueueDir(dir, validationRoot) {
|
|
61
69
|
const root = validationRoot
|
|
@@ -73,41 +81,6 @@ async function assertJsonDurableQueueDir(dir, validationRoot) {
|
|
|
73
81
|
: queueValidationRoot(dir);
|
|
74
82
|
await assertNoSymlinkDirectorySegments(root, dir, false);
|
|
75
83
|
}
|
|
76
|
-
function commonPathAncestor(paths) {
|
|
77
|
-
const resolved = paths.map((entry) => path.resolve(entry));
|
|
78
|
-
const root = path.parse(resolved[0] ?? process.cwd()).root;
|
|
79
|
-
const parts = resolved.map((entry) => path.relative(root, entry).split(path.sep));
|
|
80
|
-
const common = [];
|
|
81
|
-
for (let index = 0; parts.every((part) => index < part.length); index++) {
|
|
82
|
-
const segment = parts[0]?.[index];
|
|
83
|
-
if (!segment || !parts.every((part) => part[index] === segment))
|
|
84
|
-
break;
|
|
85
|
-
common.push(segment);
|
|
86
|
-
}
|
|
87
|
-
return path.join(root, ...common);
|
|
88
|
-
}
|
|
89
|
-
function samePathRoot(paths) {
|
|
90
|
-
const roots = paths.map((entry) => path.parse(path.resolve(entry)).root);
|
|
91
|
-
const first = process.platform === "win32" ? roots[0]?.toLowerCase() : roots[0];
|
|
92
|
-
return roots.every((root) => (process.platform === "win32" ? root.toLowerCase() : root) === first);
|
|
93
|
-
}
|
|
94
|
-
async function queueValidationRoots(queueDir, failedDir) {
|
|
95
|
-
if (samePathRoot([queueDir, failedDir])) {
|
|
96
|
-
const common = commonPathAncestor([queueDir, failedDir]);
|
|
97
|
-
const root = queueValidationRoot(common);
|
|
98
|
-
return { failedRoot: root, queueRoot: root };
|
|
99
|
-
}
|
|
100
|
-
return {
|
|
101
|
-
failedRoot: queueValidationRoot(failedDir),
|
|
102
|
-
queueRoot: queueValidationRoot(queueDir),
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
function queueValidationRoot(dir) {
|
|
106
|
-
return {
|
|
107
|
-
path: path.parse(path.resolve(dir)).root,
|
|
108
|
-
allowSymlinkBase: process.platform === "darwin",
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
84
|
async function isDarwinSystemAlias(dir, stat) {
|
|
112
85
|
if (process.platform !== "darwin" || !stat.isSymbolicLink()) {
|
|
113
86
|
return false;
|
|
@@ -124,8 +97,8 @@ async function isDarwinSystemAlias(dir, stat) {
|
|
|
124
97
|
}
|
|
125
98
|
}
|
|
126
99
|
async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissing) {
|
|
127
|
-
let base =
|
|
128
|
-
let target =
|
|
100
|
+
let base = resolveQueueFilesystemPath(validationRoot.path);
|
|
101
|
+
let target = resolveQueueFilesystemPath(dir);
|
|
129
102
|
let current = base;
|
|
130
103
|
let baseStat = fs.lstatSync(base);
|
|
131
104
|
if (baseStat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
|
|
@@ -213,88 +186,51 @@ async function chmodQueueDirectory(dir) {
|
|
|
213
186
|
}
|
|
214
187
|
}
|
|
215
188
|
export async function writeJsonDurableQueueEntry(params) {
|
|
189
|
+
await writeQueueEntry(params);
|
|
190
|
+
}
|
|
191
|
+
async function writeQueueEntry(params, beforeRename) {
|
|
192
|
+
const filePath = admitStandalonePublicationPath(params.filePath);
|
|
193
|
+
const entry = params.entry;
|
|
194
|
+
const tempPrefix = params.tempPrefix;
|
|
216
195
|
await replaceFileAtomicWithDirectorySync({
|
|
217
|
-
filePath
|
|
218
|
-
content: stringifyJsonDocument(
|
|
196
|
+
filePath,
|
|
197
|
+
content: stringifyJsonDocument(entry, null, 2),
|
|
219
198
|
mode: 0o600,
|
|
220
|
-
tempPrefix
|
|
199
|
+
tempPrefix,
|
|
221
200
|
syncTempFile: true,
|
|
201
|
+
beforeRename,
|
|
222
202
|
}, syncDirectory);
|
|
223
203
|
}
|
|
224
|
-
async function inspectQueueEntry(inspect, maxBytes, expected) {
|
|
225
|
-
let inspectionFailed = false;
|
|
226
|
-
try {
|
|
227
|
-
return await inspectFileIdentity(async () => {
|
|
228
|
-
try {
|
|
229
|
-
const stat = inspect();
|
|
230
|
-
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
231
|
-
throw new Error("queue entry is not a regular file");
|
|
232
|
-
}
|
|
233
|
-
if (stat.nlink > 1n)
|
|
234
|
-
throw new Error("queue entry hardlinks are not allowed");
|
|
235
|
-
if (stat.size > maxBytes)
|
|
236
|
-
throw new Error(`queue entry exceeds ${maxBytes} bytes`);
|
|
237
|
-
return stat;
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
inspectionFailed = true;
|
|
241
|
-
throw error;
|
|
242
|
-
}
|
|
243
|
-
}, expected);
|
|
244
|
-
}
|
|
245
|
-
catch (error) {
|
|
246
|
-
// Translate only the identity helper's rejection, never inspection errors.
|
|
247
|
-
if (inspectionFailed)
|
|
248
|
-
throw error;
|
|
249
|
-
throw new Error("queue entry changed during read", { cause: error });
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
async function readBoundedUtf8File(params) {
|
|
253
|
-
const inspectPath = () => fs.lstatSync(params.filePath, { bigint: true });
|
|
254
|
-
const initialStat = await inspectQueueEntry(inspectPath, params.maxBytes);
|
|
255
|
-
const handle = await fs.promises.open(params.filePath, resolveReadOpenFlags());
|
|
256
|
-
try {
|
|
257
|
-
const openedStat = await inspectQueueEntry(() => fs.fstatSync(handle.fd, { bigint: true }), params.maxBytes, initialStat);
|
|
258
|
-
await inspectQueueEntry(inspectPath, params.maxBytes, openedStat);
|
|
259
|
-
const bytes = await readBoundedAsync(params.maxBytes, async (buffer, length) => (await handle.read(buffer, 0, length, null)).bytesRead, {
|
|
260
|
-
initialSize: openedStat.size >= 0n && Number.isSafeInteger(Number(openedStat.size))
|
|
261
|
-
? Number(openedStat.size) : undefined,
|
|
262
|
-
createLimitError: () => new Error(`queue entry exceeds ${params.maxBytes} bytes`),
|
|
263
|
-
});
|
|
264
|
-
return bytes.toString("utf8");
|
|
265
|
-
}
|
|
266
|
-
finally {
|
|
267
|
-
await handle.close();
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
204
|
export async function readJsonDurableQueueEntry(filePath, options = {}) {
|
|
271
|
-
return
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
})
|
|
276
|
-
})
|
|
205
|
+
return await withJsonDurableQueueEntry(filePath, options, async (entry) => entry);
|
|
206
|
+
}
|
|
207
|
+
async function migrateClaimedQueueEntry(params) {
|
|
208
|
+
await migrateDurableQueueEntry(params.paths, params.identity, params.releaseReadPin, async (filePath, beforePublish) => {
|
|
209
|
+
await writeQueueEntry({ filePath, entry: params.entry, tempPrefix: params.tempPrefix }, beforePublish);
|
|
210
|
+
});
|
|
277
211
|
}
|
|
278
212
|
export async function ackJsonDurableQueueEntry(paths) {
|
|
279
213
|
await acknowledgeDurableQueueEntry(paths);
|
|
280
214
|
}
|
|
281
215
|
export async function loadJsonDurableQueueEntry(params) {
|
|
282
216
|
try {
|
|
283
|
-
const
|
|
217
|
+
const pathsInput = params.paths;
|
|
218
|
+
const paths = validateDurableQueueEntryPaths(pathsInput);
|
|
219
|
+
const claimedPath = await claimDurableQueueEntry(paths);
|
|
284
220
|
if (!claimedPath)
|
|
285
221
|
return null;
|
|
286
|
-
|
|
222
|
+
return await withJsonDurableQueueEntry(claimedPath, {
|
|
287
223
|
maxBytes: params.maxBytes,
|
|
224
|
+
}, async (raw, identity, releaseReadPin) => {
|
|
225
|
+
const read = params.read;
|
|
226
|
+
const result = read ? await Reflect.apply(read, params, [raw, paths.jsonPath]) : { entry: raw };
|
|
227
|
+
if (result.migrated) {
|
|
228
|
+
await migrateClaimedQueueEntry({
|
|
229
|
+
paths, identity, releaseReadPin, entry: result.entry, tempPrefix: params.tempPrefix,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return result.entry;
|
|
288
233
|
});
|
|
289
|
-
const result = params.read ? await params.read(raw, params.paths.jsonPath) : { entry: raw };
|
|
290
|
-
if (result.migrated) {
|
|
291
|
-
await writeJsonDurableQueueEntry({
|
|
292
|
-
filePath: claimedPath,
|
|
293
|
-
entry: result.entry,
|
|
294
|
-
tempPrefix: params.tempPrefix,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
return result.entry;
|
|
298
234
|
}
|
|
299
235
|
catch (error) {
|
|
300
236
|
if (getErrorCode(error) === "ENOENT") {
|
|
@@ -304,9 +240,11 @@ export async function loadJsonDurableQueueEntry(params) {
|
|
|
304
240
|
}
|
|
305
241
|
}
|
|
306
242
|
export async function loadPendingJsonDurableQueueEntries(options) {
|
|
243
|
+
const queueDir = options.queueDir;
|
|
244
|
+
assertNoWindowsPathAlias(queueDir);
|
|
307
245
|
let files;
|
|
308
246
|
try {
|
|
309
|
-
files = await fs.promises.readdir(
|
|
247
|
+
files = await fs.promises.readdir(queueDir);
|
|
310
248
|
}
|
|
311
249
|
catch (error) {
|
|
312
250
|
if (getErrorCode(error) === "ENOENT") {
|
|
@@ -324,10 +262,11 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
324
262
|
catch {
|
|
325
263
|
continue;
|
|
326
264
|
}
|
|
327
|
-
|
|
265
|
+
const paths = validateDurableQueueEntryPaths(resolveJsonDurableQueueEntryPaths(queueDir, id));
|
|
266
|
+
await completeDeliveredQueueEntry(paths);
|
|
328
267
|
}
|
|
329
268
|
else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {
|
|
330
|
-
await unlinkStaleTmpBestEffort(path.join(
|
|
269
|
+
await unlinkStaleTmpBestEffort(path.join(queueDir, file), now, options.cleanupTmpMaxAgeMs);
|
|
331
270
|
}
|
|
332
271
|
}
|
|
333
272
|
const ids = [];
|
|
@@ -354,36 +293,46 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
354
293
|
}
|
|
355
294
|
const entries = [];
|
|
356
295
|
for (const id of ids) {
|
|
357
|
-
const paths = resolveJsonDurableQueueEntryPaths(
|
|
296
|
+
const paths = validateDurableQueueEntryPaths(resolveJsonDurableQueueEntryPaths(queueDir, id));
|
|
358
297
|
const claimedPath = await claimDurableQueueEntry(paths, { skipUnowned: true });
|
|
359
298
|
if (!claimedPath)
|
|
360
299
|
continue;
|
|
361
|
-
let
|
|
300
|
+
let migrationStarted = false;
|
|
362
301
|
try {
|
|
363
|
-
const
|
|
364
|
-
|
|
302
|
+
const entry = await withJsonDurableQueueEntry(claimedPath, { maxBytes: options.maxBytes }, async (raw, identity, releaseReadPin) => {
|
|
303
|
+
const read = options.read;
|
|
304
|
+
const result = read ? await Reflect.apply(read, options, [raw, paths.jsonPath]) : { entry: raw };
|
|
305
|
+
if (result.migrated) {
|
|
306
|
+
migrationStarted = true;
|
|
307
|
+
await migrateClaimedQueueEntry({
|
|
308
|
+
paths, identity, releaseReadPin, entry: result.entry, tempPrefix: options.tempPrefix,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return result.entry;
|
|
312
|
+
});
|
|
313
|
+
entries.push(entry);
|
|
365
314
|
}
|
|
366
|
-
catch {
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (migrationStarted)
|
|
317
|
+
throw error;
|
|
367
318
|
continue;
|
|
368
319
|
}
|
|
369
|
-
if (result.migrated) {
|
|
370
|
-
await writeJsonDurableQueueEntry({
|
|
371
|
-
filePath: claimedPath,
|
|
372
|
-
entry: result.entry,
|
|
373
|
-
tempPrefix: options.tempPrefix,
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
entries.push(result.entry);
|
|
377
320
|
}
|
|
378
321
|
return entries;
|
|
379
322
|
}
|
|
380
323
|
export async function moveJsonDurableQueueEntryToFailed(params) {
|
|
381
|
-
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
324
|
+
const id = params.id;
|
|
325
|
+
const queueDir = params.queueDir;
|
|
326
|
+
const failedDir = params.failedDir;
|
|
327
|
+
assertSafeQueueEntryId(id);
|
|
328
|
+
assertNoWindowsPathAlias(queueDir);
|
|
329
|
+
assertNoWindowsPathAlias(failedDir);
|
|
330
|
+
const roots = await queueValidationRoots(queueDir, failedDir);
|
|
331
|
+
await assertJsonDurableQueueDir(queueDir, roots.queueRoot);
|
|
332
|
+
await ensureJsonDurableQueueDir(failedDir, roots.failedRoot);
|
|
333
|
+
const paths = validateDurableQueueEntryPaths(resolveJsonDurableQueueEntryPaths(queueDir, id));
|
|
385
334
|
await moveDurableQueueEntryToFailed({
|
|
386
|
-
paths
|
|
387
|
-
failedPath: path.join(
|
|
335
|
+
paths,
|
|
336
|
+
failedPath: path.join(failedDir, `${id}.json`),
|
|
388
337
|
});
|
|
389
338
|
}
|
package/dist/json-store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-store.d.ts","sourceRoot":"","sources":["../src/json-store.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"json-store.d.ts","sourceRoot":"","sources":["../src/json-store.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAIlC,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,oBAAoB,GAAG;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,YAAY,EAAE,oBAAoB,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC;AAEtE,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAUvE"}
|
package/dist/json-store.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileStore } from "./file-store.js";
|
|
3
3
|
import {} from "./json-document-store.js";
|
|
4
|
+
import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
|
|
5
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
4
6
|
export function jsonStore(options) {
|
|
5
|
-
const
|
|
7
|
+
const filePathInput = admitStandalonePublicationPath(options.filePath);
|
|
8
|
+
const filePath = path.resolve(filePathInput);
|
|
9
|
+
assertNoWindowsPathAlias(filePath);
|
|
6
10
|
return fileStore({
|
|
7
11
|
rootDir: path.dirname(filePath),
|
|
8
12
|
private: true,
|
package/dist/json.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAO7B,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
|
package/dist/json.js
CHANGED
|
@@ -9,8 +9,10 @@ import { readRegularFile, readRegularFileSync, statRegularFile } from "./regular
|
|
|
9
9
|
import { openRootFileSync } from "./root-file.js";
|
|
10
10
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
11
11
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
12
|
+
import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
|
|
12
13
|
import { writeTextAtomic } from "./text-atomic.js";
|
|
13
14
|
import { sleep } from "./timing.js";
|
|
15
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
14
16
|
const READ_RETRY_MAX_ATTEMPTS = 5;
|
|
15
17
|
const READ_RETRY_BASE_DELAY_MS = 50;
|
|
16
18
|
function isRetryableReadError(err, options) {
|
|
@@ -167,16 +169,17 @@ export function tryReadJsonSync(pathname, options = {}) {
|
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
171
|
export function writeJsonSync(pathname, data) {
|
|
172
|
+
const filePath = admitStandalonePublicationPath(pathname);
|
|
170
173
|
// Keep literal parent segments so staging follows the same symlinks as the target.
|
|
171
|
-
const tmpPath = path.format({ ...path.parse(
|
|
174
|
+
const tmpPath = path.format({ ...path.parse(filePath), base: `.fs-safe-${randomUUID()}.tmp` });
|
|
172
175
|
const payload = `${stringifyJsonDocument(data, null, 2)}\n`;
|
|
173
|
-
fsSync.mkdirSync(recursiveMkdirPath(path.dirname(
|
|
176
|
+
fsSync.mkdirSync(recursiveMkdirPath(path.dirname(filePath)), { recursive: true, mode: JSON_DIR_MODE });
|
|
174
177
|
try {
|
|
175
178
|
const tempIdentity = writeTempJsonFile(tmpPath, payload);
|
|
176
179
|
trySetSecureMode(tmpPath, tempIdentity);
|
|
177
|
-
renameJsonFileWithFallback(tmpPath,
|
|
178
|
-
trySetSecureMode(
|
|
179
|
-
trySyncDirectory(
|
|
180
|
+
renameJsonFileWithFallback(tmpPath, filePath);
|
|
181
|
+
trySetSecureMode(filePath, tempIdentity);
|
|
182
|
+
trySyncDirectory(filePath);
|
|
180
183
|
}
|
|
181
184
|
finally {
|
|
182
185
|
try {
|
|
@@ -207,29 +210,59 @@ function resolveInvalidMessage(invalidMessage, relativePath) {
|
|
|
207
210
|
return invalidMessage ?? `${relativePath} has an unexpected shape`;
|
|
208
211
|
}
|
|
209
212
|
export function readRootStructuredFileSync(options) {
|
|
210
|
-
|
|
213
|
+
return readRootStructuredFileSyncInternal(options, options);
|
|
214
|
+
}
|
|
215
|
+
function readRootStructuredFileSyncInternal(options, parser) {
|
|
216
|
+
let absolutePath;
|
|
217
|
+
let relativePath;
|
|
218
|
+
let rootDir;
|
|
219
|
+
let rootRealPath;
|
|
220
|
+
try {
|
|
221
|
+
rootDir = options.rootDir;
|
|
222
|
+
relativePath = options.relativePath;
|
|
223
|
+
rootRealPath = options.rootRealPath;
|
|
224
|
+
assertNoWindowsPathAlias(rootDir);
|
|
225
|
+
assertNoWindowsPathAlias(relativePath, "relative");
|
|
226
|
+
if (rootRealPath !== undefined) {
|
|
227
|
+
assertNoWindowsPathAlias(rootRealPath);
|
|
228
|
+
}
|
|
229
|
+
absolutePath = path.resolve(rootDir, relativePath);
|
|
230
|
+
assertNoWindowsPathAlias(absolutePath);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
reason: "open",
|
|
236
|
+
failure: { ok: false, reason: "validation", error },
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const boundaryLabel = options.boundaryLabel;
|
|
240
|
+
const rejectHardlinks = options.rejectHardlinks;
|
|
241
|
+
const maxBytes = options.maxBytes;
|
|
211
242
|
const opened = openRootFileSync({
|
|
212
243
|
absolutePath,
|
|
213
|
-
rootPath:
|
|
214
|
-
|
|
215
|
-
boundaryLabel
|
|
216
|
-
rejectHardlinks
|
|
217
|
-
maxBytes
|
|
244
|
+
rootPath: rootDir,
|
|
245
|
+
rootRealPath,
|
|
246
|
+
boundaryLabel,
|
|
247
|
+
rejectHardlinks,
|
|
248
|
+
maxBytes,
|
|
218
249
|
allowedType: "file",
|
|
219
250
|
});
|
|
220
251
|
if (!opened.ok) {
|
|
221
252
|
return { ok: false, reason: "open", failure: opened };
|
|
222
253
|
}
|
|
223
254
|
try {
|
|
224
|
-
const raw =
|
|
255
|
+
const raw = maxBytes === undefined
|
|
225
256
|
? fsSync.readFileSync(opened.fd, "utf8")
|
|
226
|
-
: readFileDescriptorBoundedSync(opened.fd,
|
|
227
|
-
const
|
|
228
|
-
|
|
257
|
+
: readFileDescriptorBoundedSync(opened.fd, maxBytes).toString("utf8");
|
|
258
|
+
const parse = parser.parse;
|
|
259
|
+
const parsed = Reflect.apply(parse, parser, [raw]);
|
|
260
|
+
const validate = parser.validate;
|
|
261
|
+
if (validate && !Reflect.apply(validate, parser, [parsed])) {
|
|
229
262
|
return {
|
|
230
263
|
ok: false,
|
|
231
264
|
reason: "invalid",
|
|
232
|
-
error: resolveInvalidMessage(
|
|
265
|
+
error: resolveInvalidMessage(parser.invalidMessage, relativePath),
|
|
233
266
|
};
|
|
234
267
|
}
|
|
235
268
|
return {
|
|
@@ -244,7 +277,7 @@ export function readRootStructuredFileSync(options) {
|
|
|
244
277
|
return {
|
|
245
278
|
ok: false,
|
|
246
279
|
reason: "parse",
|
|
247
|
-
error: `failed to parse ${
|
|
280
|
+
error: `failed to parse ${relativePath}: ${String(error)}`,
|
|
248
281
|
};
|
|
249
282
|
}
|
|
250
283
|
finally {
|
|
@@ -252,14 +285,12 @@ export function readRootStructuredFileSync(options) {
|
|
|
252
285
|
}
|
|
253
286
|
}
|
|
254
287
|
export function readRootJsonSync(options) {
|
|
255
|
-
return
|
|
256
|
-
...options,
|
|
288
|
+
return readRootStructuredFileSyncInternal(options, {
|
|
257
289
|
parse: (raw) => JSON.parse(raw),
|
|
258
290
|
});
|
|
259
291
|
}
|
|
260
292
|
export function readRootJsonObjectSync(options) {
|
|
261
|
-
return
|
|
262
|
-
...options,
|
|
293
|
+
return readRootStructuredFileSyncInternal(options, {
|
|
263
294
|
parse: (raw) => JSON.parse(raw),
|
|
264
295
|
validate: isRecord,
|
|
265
296
|
invalidMessage: (relativePath) => `${relativePath} must contain a JSON object`,
|
|
@@ -334,8 +365,9 @@ export function readJsonSync(filePath, options = {}) {
|
|
|
334
365
|
}
|
|
335
366
|
}
|
|
336
367
|
export async function writeJson(filePath, value, options) {
|
|
368
|
+
const admittedPath = admitStandalonePublicationPath(filePath);
|
|
337
369
|
const text = stringifyJsonDocument(value, null, 2);
|
|
338
|
-
await writeTextAtomic(
|
|
370
|
+
await writeTextAtomic(admittedPath, text, {
|
|
339
371
|
mode: options?.mode,
|
|
340
372
|
dirMode: options?.dirMode,
|
|
341
373
|
trailingNewline: options?.trailingNewline,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"AAQA,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEhD;AAOD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAeT;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAET;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,IAAI,CAIjF;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,CAwBR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB3E"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath, URL } from "node:url";
|
|
3
3
|
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
4
|
+
import { hasWindowsPathAlias } from "./windows-path-alias.js";
|
|
4
5
|
const ENCODED_FILE_URL_SEPARATOR_RE = /%(?:2f|5c)/i;
|
|
5
6
|
const FILE_URL_PREFIX_RE = /^file:\/\//i;
|
|
6
7
|
export function isFileUrl(input) {
|
|
@@ -54,6 +55,9 @@ export function safeFileURLToPath(fileUrl, platform = process.platform) {
|
|
|
54
55
|
throw new Error(`file:// URLs cannot encode path separators: ${fileUrl}`);
|
|
55
56
|
}
|
|
56
57
|
const filePath = fileURLToPath(parsed, { windows: platform === "win32" });
|
|
58
|
+
if (hasWindowsPathAlias(filePath, "filesystem", platform)) {
|
|
59
|
+
throw new Error(`Local file URL cannot use Windows filesystem namespace aliases: ${filePath}`);
|
|
60
|
+
}
|
|
57
61
|
if (isWindowsNetworkPath(filePath, platform)) {
|
|
58
62
|
throw new Error(`Local file URL cannot use Windows network paths: ${filePath}`);
|
|
59
63
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BigIntStats, Stats } from "node:fs";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
import type { HardlinkPolicy } from "./root-options.js";
|
|
4
|
+
import type { SymlinkPolicy } from "./root-symlink-policy.js";
|
|
5
|
+
type OwnedLocalFile = {
|
|
6
|
+
handle: FileHandle;
|
|
7
|
+
stat: Stats;
|
|
8
|
+
identity: BigIntStats;
|
|
9
|
+
preOpenStat: BigIntStats | undefined;
|
|
10
|
+
};
|
|
11
|
+
export declare function openLocalFileDescriptor(filePath: string, options?: {
|
|
12
|
+
hardlinks?: HardlinkPolicy;
|
|
13
|
+
symlinks?: SymlinkPolicy;
|
|
14
|
+
readWrite?: true;
|
|
15
|
+
}): Promise<OwnedLocalFile>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=local-file-descriptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-file-descriptor.d.ts","sourceRoot":"","sources":["../src/local-file-descriptor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAYnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAO9D,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,KAAK,CAAC;IACZ,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;CACtC,CAAC;AAIF,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,GACA,OAAO,CAAC,cAAc,CAAC,CA0EzB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { recordFileOpenFailure, recordPreOpenFileChange } from "./opened-file-failure.js";
|
|
5
|
+
import { assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError, } from "./path.js";
|
|
6
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
7
|
+
import { fileNotFoundError, hardlinkedPathNotAllowedError } from "./root-errors.js";
|
|
8
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
9
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
10
|
+
const OPEN_READ_FLAGS = resolveReadOpenFlags();
|
|
11
|
+
const OPEN_READ_FOLLOW_FLAGS = resolveReadOpenFlags({ followSymlinks: true });
|
|
12
|
+
// Ownership transfers only after descriptor verification. Each admission caller
|
|
13
|
+
// must close on rejection and construct an OpenResult only after admission.
|
|
14
|
+
export async function openLocalFileDescriptor(filePath, options) {
|
|
15
|
+
assertNoUnsafeDeviceReadPath(filePath);
|
|
16
|
+
const fsSafeTestHooks = getFsSafeTestHooks();
|
|
17
|
+
let preOpenStat;
|
|
18
|
+
let observedBeforeOpen = false;
|
|
19
|
+
// Reject directories before opening so we never surface EISDIR to callers (e.g. tool
|
|
20
|
+
// results that get sent to messaging channels). See openclaw/openclaw#31186.
|
|
21
|
+
try {
|
|
22
|
+
preOpenStat = await inspectFileIdentity(() => {
|
|
23
|
+
const stat = fsSync.lstatSync(filePath, { bigint: true });
|
|
24
|
+
observedBeforeOpen = true;
|
|
25
|
+
if (stat.isSymbolicLink() && options?.symlinks !== "follow-within-root") {
|
|
26
|
+
throw new FsSafeError("symlink", "symlink not allowed");
|
|
27
|
+
}
|
|
28
|
+
if (!stat.isFile() && !stat.isSymbolicLink()) {
|
|
29
|
+
throw new FsSafeError("not-file", "not a file");
|
|
30
|
+
}
|
|
31
|
+
return stat;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
if (err instanceof FsSafeError || observedBeforeOpen) {
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
// Only an initial lookup failure falls through; a failed re-inspection aborts.
|
|
39
|
+
}
|
|
40
|
+
if (preOpenStat) {
|
|
41
|
+
await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
|
|
42
|
+
}
|
|
43
|
+
const openFlags = (options?.symlinks === "follow-within-root"
|
|
44
|
+
? OPEN_READ_FOLLOW_FLAGS
|
|
45
|
+
: OPEN_READ_FLAGS) | (options?.readWrite ? fsSync.constants.O_RDWR : 0);
|
|
46
|
+
await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
|
|
47
|
+
let handle;
|
|
48
|
+
try {
|
|
49
|
+
handle = await fs.open(filePath, openFlags).catch((error) => recordFileOpenFailure(isNotFoundPathError(error) ? fileNotFoundError() : error, filePath));
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (isSymlinkOpenError(err)) {
|
|
53
|
+
throw new FsSafeError("symlink", "symlink open blocked", { cause: err });
|
|
54
|
+
}
|
|
55
|
+
// Defensive: if open still throws EISDIR (e.g. race), sanitize so it never leaks.
|
|
56
|
+
if (hasNodeErrorCode(err, "EISDIR")) {
|
|
57
|
+
throw new FsSafeError("not-file", "not a file");
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
await fsSafeTestHooks?.afterOpen?.(filePath, handle);
|
|
63
|
+
const stat = fsSync.fstatSync(handle.fd);
|
|
64
|
+
if (!stat.isFile()) {
|
|
65
|
+
throw new FsSafeError("not-file", "not a file");
|
|
66
|
+
}
|
|
67
|
+
// Keep numeric Stats for the public receipt, never for identity verification.
|
|
68
|
+
let openedIdentity;
|
|
69
|
+
const identity = await inspectFileIdentity(() => (openedIdentity = fsSync.fstatSync(handle.fd, { bigint: true })), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined).catch(async (error) => {
|
|
70
|
+
if ([0, 1].includes(stat.nlink)) {
|
|
71
|
+
await recordPreOpenFileChange(error, handle, filePath, preOpenStat, openedIdentity);
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
});
|
|
75
|
+
if (options?.hardlinks === "reject" && stat.nlink > 1) {
|
|
76
|
+
throw hardlinkedPathNotAllowedError();
|
|
77
|
+
}
|
|
78
|
+
return { handle, stat, identity, preOpenStat };
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
await handle.close().catch(() => { });
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAQA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAQA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAQ3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAiEF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA2C7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAuCtC"}
|