@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
|
@@ -2,21 +2,27 @@ import fs, {} from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { syncDirectory } from "./directory-durability.js";
|
|
4
4
|
import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
|
|
5
|
-
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";
|
|
6
7
|
import { withJsonDurableQueueEntry } from "./json-durable-queue-read.js";
|
|
7
8
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
8
9
|
import { realpathSync } from "./realpath.js";
|
|
9
10
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
10
11
|
import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
|
|
11
12
|
import { assertSafePathSegment } from "./safe-path-segment.js";
|
|
13
|
+
import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
|
|
14
|
+
import { assertNoWindowsPathAlias, hasWindowsPathAlias, } from "./windows-path-alias.js";
|
|
12
15
|
export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
|
|
13
16
|
function assertSafeQueueEntryId(id) {
|
|
14
17
|
assertSafePathSegment(id, { label: "queue entry id" });
|
|
15
18
|
}
|
|
16
19
|
export async function unlinkBestEffort(filePath) {
|
|
20
|
+
if (hasWindowsPathAlias(filePath, "filesystem"))
|
|
21
|
+
return;
|
|
17
22
|
await fs.promises.unlink(filePath).catch(() => undefined);
|
|
18
23
|
}
|
|
19
24
|
export async function jsonDurableQueueEntryExists(filePath) {
|
|
25
|
+
assertNoWindowsPathAlias(filePath);
|
|
20
26
|
try {
|
|
21
27
|
const stat = fs.lstatSync(filePath);
|
|
22
28
|
return stat.isFile();
|
|
@@ -43,6 +49,7 @@ async function unlinkStaleTmpBestEffort(filePath, now, maxAgeMs) {
|
|
|
43
49
|
}
|
|
44
50
|
export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
|
|
45
51
|
assertSafeQueueEntryId(id);
|
|
52
|
+
assertNoWindowsPathAlias(queueDir);
|
|
46
53
|
return {
|
|
47
54
|
jsonPath: path.join(queueDir, `${id}.json`),
|
|
48
55
|
deliveredPath: path.join(queueDir, `${id}.delivered`),
|
|
@@ -50,9 +57,13 @@ export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
|
|
|
50
57
|
};
|
|
51
58
|
}
|
|
52
59
|
export async function ensureJsonDurableQueueDirs(params) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
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);
|
|
56
67
|
}
|
|
57
68
|
async function ensureJsonDurableQueueDir(dir, validationRoot) {
|
|
58
69
|
const root = validationRoot
|
|
@@ -70,41 +81,6 @@ async function assertJsonDurableQueueDir(dir, validationRoot) {
|
|
|
70
81
|
: queueValidationRoot(dir);
|
|
71
82
|
await assertNoSymlinkDirectorySegments(root, dir, false);
|
|
72
83
|
}
|
|
73
|
-
function commonPathAncestor(paths) {
|
|
74
|
-
const resolved = paths.map((entry) => path.resolve(entry));
|
|
75
|
-
const root = path.parse(resolved[0] ?? process.cwd()).root;
|
|
76
|
-
const parts = resolved.map((entry) => path.relative(root, entry).split(path.sep));
|
|
77
|
-
const common = [];
|
|
78
|
-
for (let index = 0; parts.every((part) => index < part.length); index++) {
|
|
79
|
-
const segment = parts[0]?.[index];
|
|
80
|
-
if (!segment || !parts.every((part) => part[index] === segment))
|
|
81
|
-
break;
|
|
82
|
-
common.push(segment);
|
|
83
|
-
}
|
|
84
|
-
return path.join(root, ...common);
|
|
85
|
-
}
|
|
86
|
-
function samePathRoot(paths) {
|
|
87
|
-
const roots = paths.map((entry) => path.parse(path.resolve(entry)).root);
|
|
88
|
-
const first = process.platform === "win32" ? roots[0]?.toLowerCase() : roots[0];
|
|
89
|
-
return roots.every((root) => (process.platform === "win32" ? root.toLowerCase() : root) === first);
|
|
90
|
-
}
|
|
91
|
-
async function queueValidationRoots(queueDir, failedDir) {
|
|
92
|
-
if (samePathRoot([queueDir, failedDir])) {
|
|
93
|
-
const common = commonPathAncestor([queueDir, failedDir]);
|
|
94
|
-
const root = queueValidationRoot(common);
|
|
95
|
-
return { failedRoot: root, queueRoot: root };
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
failedRoot: queueValidationRoot(failedDir),
|
|
99
|
-
queueRoot: queueValidationRoot(queueDir),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function queueValidationRoot(dir) {
|
|
103
|
-
return {
|
|
104
|
-
path: path.parse(path.resolve(dir)).root,
|
|
105
|
-
allowSymlinkBase: process.platform === "darwin",
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
84
|
async function isDarwinSystemAlias(dir, stat) {
|
|
109
85
|
if (process.platform !== "darwin" || !stat.isSymbolicLink()) {
|
|
110
86
|
return false;
|
|
@@ -121,8 +97,8 @@ async function isDarwinSystemAlias(dir, stat) {
|
|
|
121
97
|
}
|
|
122
98
|
}
|
|
123
99
|
async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissing) {
|
|
124
|
-
let base =
|
|
125
|
-
let target =
|
|
100
|
+
let base = resolveQueueFilesystemPath(validationRoot.path);
|
|
101
|
+
let target = resolveQueueFilesystemPath(dir);
|
|
126
102
|
let current = base;
|
|
127
103
|
let baseStat = fs.lstatSync(base);
|
|
128
104
|
if (baseStat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
|
|
@@ -213,11 +189,14 @@ export async function writeJsonDurableQueueEntry(params) {
|
|
|
213
189
|
await writeQueueEntry(params);
|
|
214
190
|
}
|
|
215
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,
|
|
222
201
|
beforeRename,
|
|
223
202
|
}, syncDirectory);
|
|
@@ -235,16 +214,19 @@ export async function ackJsonDurableQueueEntry(paths) {
|
|
|
235
214
|
}
|
|
236
215
|
export async function loadJsonDurableQueueEntry(params) {
|
|
237
216
|
try {
|
|
238
|
-
const
|
|
217
|
+
const pathsInput = params.paths;
|
|
218
|
+
const paths = validateDurableQueueEntryPaths(pathsInput);
|
|
219
|
+
const claimedPath = await claimDurableQueueEntry(paths);
|
|
239
220
|
if (!claimedPath)
|
|
240
221
|
return null;
|
|
241
222
|
return await withJsonDurableQueueEntry(claimedPath, {
|
|
242
223
|
maxBytes: params.maxBytes,
|
|
243
224
|
}, async (raw, identity, releaseReadPin) => {
|
|
244
|
-
const
|
|
225
|
+
const read = params.read;
|
|
226
|
+
const result = read ? await Reflect.apply(read, params, [raw, paths.jsonPath]) : { entry: raw };
|
|
245
227
|
if (result.migrated) {
|
|
246
228
|
await migrateClaimedQueueEntry({
|
|
247
|
-
paths
|
|
229
|
+
paths, identity, releaseReadPin, entry: result.entry, tempPrefix: params.tempPrefix,
|
|
248
230
|
});
|
|
249
231
|
}
|
|
250
232
|
return result.entry;
|
|
@@ -258,9 +240,11 @@ export async function loadJsonDurableQueueEntry(params) {
|
|
|
258
240
|
}
|
|
259
241
|
}
|
|
260
242
|
export async function loadPendingJsonDurableQueueEntries(options) {
|
|
243
|
+
const queueDir = options.queueDir;
|
|
244
|
+
assertNoWindowsPathAlias(queueDir);
|
|
261
245
|
let files;
|
|
262
246
|
try {
|
|
263
|
-
files = await fs.promises.readdir(
|
|
247
|
+
files = await fs.promises.readdir(queueDir);
|
|
264
248
|
}
|
|
265
249
|
catch (error) {
|
|
266
250
|
if (getErrorCode(error) === "ENOENT") {
|
|
@@ -278,10 +262,11 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
278
262
|
catch {
|
|
279
263
|
continue;
|
|
280
264
|
}
|
|
281
|
-
|
|
265
|
+
const paths = validateDurableQueueEntryPaths(resolveJsonDurableQueueEntryPaths(queueDir, id));
|
|
266
|
+
await completeDeliveredQueueEntry(paths);
|
|
282
267
|
}
|
|
283
268
|
else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {
|
|
284
|
-
await unlinkStaleTmpBestEffort(path.join(
|
|
269
|
+
await unlinkStaleTmpBestEffort(path.join(queueDir, file), now, options.cleanupTmpMaxAgeMs);
|
|
285
270
|
}
|
|
286
271
|
}
|
|
287
272
|
const ids = [];
|
|
@@ -308,14 +293,15 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
308
293
|
}
|
|
309
294
|
const entries = [];
|
|
310
295
|
for (const id of ids) {
|
|
311
|
-
const paths = resolveJsonDurableQueueEntryPaths(
|
|
296
|
+
const paths = validateDurableQueueEntryPaths(resolveJsonDurableQueueEntryPaths(queueDir, id));
|
|
312
297
|
const claimedPath = await claimDurableQueueEntry(paths, { skipUnowned: true });
|
|
313
298
|
if (!claimedPath)
|
|
314
299
|
continue;
|
|
315
300
|
let migrationStarted = false;
|
|
316
301
|
try {
|
|
317
302
|
const entry = await withJsonDurableQueueEntry(claimedPath, { maxBytes: options.maxBytes }, async (raw, identity, releaseReadPin) => {
|
|
318
|
-
const
|
|
303
|
+
const read = options.read;
|
|
304
|
+
const result = read ? await Reflect.apply(read, options, [raw, paths.jsonPath]) : { entry: raw };
|
|
319
305
|
if (result.migrated) {
|
|
320
306
|
migrationStarted = true;
|
|
321
307
|
await migrateClaimedQueueEntry({
|
|
@@ -335,12 +321,18 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
335
321
|
return entries;
|
|
336
322
|
}
|
|
337
323
|
export async function moveJsonDurableQueueEntryToFailed(params) {
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
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));
|
|
342
334
|
await moveDurableQueueEntryToFailed({
|
|
343
|
-
paths
|
|
344
|
-
failedPath: path.join(
|
|
335
|
+
paths,
|
|
336
|
+
failedPath: path.join(failedDir, `${id}.json`),
|
|
345
337
|
});
|
|
346
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"}
|
package/dist/local-roots.js
CHANGED
|
@@ -7,6 +7,7 @@ import { isFileUrl, safeFileURLToPath } from "./local-file-access.js";
|
|
|
7
7
|
import { realpathSync } from "./realpath.js";
|
|
8
8
|
import { ROOT_PATH_ALIAS_POLICIES, resolveRootPathSync } from "./root-path.js";
|
|
9
9
|
import { root } from "./root.js";
|
|
10
|
+
import { assertNoWindowsPathAlias, isWindowsPathAliasError, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
10
11
|
function resolveLocalPathInput(input, label) {
|
|
11
12
|
if (isFileUrl(input)) {
|
|
12
13
|
try {
|
|
@@ -20,14 +21,18 @@ function resolveLocalPathInput(input, label) {
|
|
|
20
21
|
if (input.includes("\0")) {
|
|
21
22
|
throw new FsSafeError("invalid-path", `${label} must not contain NUL bytes`);
|
|
22
23
|
}
|
|
24
|
+
assertNoWindowsPathAlias(input, "filesystem", `${label} uses a Windows filesystem namespace alias`);
|
|
23
25
|
const homePrefix = input === "~" || input.startsWith("~/") ||
|
|
24
26
|
(path.sep === "\\" && input.startsWith("~\\"));
|
|
25
27
|
const expanded = homePrefix ? `${resolveRequiredHomeDir()}${path.sep}${input.slice(2)}` : input;
|
|
28
|
+
assertNoWindowsPathAlias(expanded, "filesystem", `${label} uses a Windows filesystem namespace alias`);
|
|
26
29
|
if (path.isAbsolute(expanded))
|
|
27
30
|
return expanded;
|
|
28
31
|
const drive = path.parse(expanded).root;
|
|
29
32
|
const base = drive ? path.resolve(drive) : process.cwd();
|
|
30
|
-
|
|
33
|
+
const resolved = `${base}${path.sep}${expanded.slice(drive.length)}`;
|
|
34
|
+
assertNoWindowsPathAlias(resolved, "filesystem", `${label} uses a Windows filesystem namespace alias`);
|
|
35
|
+
return resolved;
|
|
31
36
|
}
|
|
32
37
|
function resolveLocalRootInput(input, label) {
|
|
33
38
|
const trimmed = input.trim();
|
|
@@ -40,29 +45,44 @@ function resolveLocalRootInput(input, label) {
|
|
|
40
45
|
if (resolved.includes("\0")) {
|
|
41
46
|
throw new FsSafeError("invalid-path", `${label} entry must not contain NUL bytes`);
|
|
42
47
|
}
|
|
48
|
+
assertNoWindowsPathAlias(resolved, "filesystem", `${label} entry uses a Windows filesystem namespace alias`);
|
|
43
49
|
if (!path.isAbsolute(resolved)) {
|
|
44
50
|
throw new FsSafeError("invalid-path", `${label} entries must be absolute paths: ${input}`);
|
|
45
51
|
}
|
|
46
|
-
|
|
52
|
+
const absolute = resolvePathPreservingWindowsRoot(resolved);
|
|
53
|
+
assertNoWindowsPathAlias(absolute, "filesystem", `${label} entry uses a Windows filesystem namespace alias`);
|
|
54
|
+
return absolute;
|
|
47
55
|
}
|
|
48
56
|
function resolveRootRealSync(rootDir) {
|
|
57
|
+
let realPath;
|
|
49
58
|
try {
|
|
50
59
|
// Configured roots may themselves be symlinks. Follow only this trusted
|
|
51
60
|
// root entry, then use its canonical directory for containment checks.
|
|
52
|
-
const
|
|
61
|
+
const operationPath = pathForWindowsFilesystem(rootDir);
|
|
62
|
+
const stat = fsSync.statSync(operationPath);
|
|
53
63
|
if (!stat.isDirectory()) {
|
|
54
64
|
return null;
|
|
55
65
|
}
|
|
56
|
-
|
|
66
|
+
realPath = realpathSync(operationPath);
|
|
57
67
|
}
|
|
58
68
|
catch {
|
|
59
69
|
return null;
|
|
60
70
|
}
|
|
71
|
+
assertNoWindowsPathAlias(realPath, "filesystem", "local root uses a Windows filesystem namespace alias");
|
|
72
|
+
return realPath;
|
|
61
73
|
}
|
|
62
74
|
export function resolveLocalPathFromRootsSync(options) {
|
|
63
75
|
const label = options.label ?? "local roots";
|
|
64
|
-
const requestedPath = resolveLocalPathInput(options.filePath, "file path");
|
|
65
76
|
const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
|
|
77
|
+
let requestedPath;
|
|
78
|
+
try {
|
|
79
|
+
requestedPath = resolveLocalPathInput(options.filePath, "file path");
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (isWindowsPathAliasError(error))
|
|
83
|
+
return null;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
66
86
|
for (const rootDir of rootDirs) {
|
|
67
87
|
const rootReal = resolveRootRealSync(rootDir);
|
|
68
88
|
if (!rootReal) {
|
|
@@ -89,7 +109,7 @@ export function resolveLocalPathFromRootsSync(options) {
|
|
|
89
109
|
continue;
|
|
90
110
|
if (candidate.exists && options.requireFile === true) {
|
|
91
111
|
try {
|
|
92
|
-
if (!fsSync.lstatSync(requestedPath).isFile())
|
|
112
|
+
if (!fsSync.lstatSync(pathForWindowsFilesystem(requestedPath)).isFile())
|
|
93
113
|
continue;
|
|
94
114
|
}
|
|
95
115
|
catch {
|
|
@@ -103,8 +123,16 @@ export function resolveLocalPathFromRootsSync(options) {
|
|
|
103
123
|
export async function readLocalFileFromRoots(options) {
|
|
104
124
|
const maxBytes = normalizeMaxBytes(options.maxBytes);
|
|
105
125
|
const label = options.label ?? "local roots";
|
|
106
|
-
const requestedPath = resolveLocalPathInput(options.filePath, "file path");
|
|
107
126
|
const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
|
|
127
|
+
let requestedPath;
|
|
128
|
+
try {
|
|
129
|
+
requestedPath = resolveLocalPathInput(options.filePath, "file path");
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (isWindowsPathAliasError(error))
|
|
133
|
+
return null;
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
108
136
|
for (const rootDir of rootDirs) {
|
|
109
137
|
let scopedRoot;
|
|
110
138
|
try {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
export type EntryIdentity = {
|
|
2
3
|
ctimeMs: number;
|
|
3
4
|
dev: number;
|
|
@@ -12,6 +13,7 @@ export type CopiedEntryManifest = (EntryIdentity & {
|
|
|
12
13
|
name: string;
|
|
13
14
|
manifest: CopiedEntryManifest;
|
|
14
15
|
}>;
|
|
16
|
+
directoryIdentity: Readonly<Pick<fsSync.BigIntStats, "dev" | "ino">>;
|
|
15
17
|
kind: "directory";
|
|
16
18
|
}) | (EntryIdentity & {
|
|
17
19
|
kind: "leaf";
|