@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
|
@@ -13,8 +13,16 @@ import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransient
|
|
|
13
13
|
import { readSidecarLockRawSnapshot, parseSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
|
|
14
14
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
15
15
|
import { sleep } from "./timing.js";
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
import { anchorWindowsDriveRelativePath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
17
|
+
function isCwdIndependentAbsolutePath(filePath) {
|
|
18
|
+
if (!path.isAbsolute(filePath))
|
|
19
|
+
return false;
|
|
20
|
+
// A leading separator on Windows is rooted on the process's current drive.
|
|
21
|
+
// Drive-qualified, UNC, and namespace roots are longer and need no cwd state.
|
|
22
|
+
return process.platform !== "win32" || path.parse(filePath).root.length > 1;
|
|
23
|
+
}
|
|
24
|
+
async function resolveNormalizedTargetPath(resolved, lockRoot) {
|
|
25
|
+
assertNoWindowsPathAlias(resolved);
|
|
18
26
|
const dir = path.dirname(resolved);
|
|
19
27
|
if (lockRoot) {
|
|
20
28
|
// The target is an arbitration key, not necessarily inside the lock Root.
|
|
@@ -22,15 +30,23 @@ async function resolveNormalizedTargetPath(targetPath, lockRoot) {
|
|
|
22
30
|
await lockRoot.resolve(".");
|
|
23
31
|
const parent = await canonicalPathFromExistingAncestor(dir);
|
|
24
32
|
await lockRoot.resolve(".");
|
|
25
|
-
|
|
33
|
+
assertNoWindowsPathAlias(parent);
|
|
34
|
+
const normalized = path.join(parent, path.basename(resolved));
|
|
35
|
+
assertNoWindowsPathAlias(normalized);
|
|
36
|
+
return normalized;
|
|
26
37
|
}
|
|
27
38
|
await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
|
|
39
|
+
let parent;
|
|
28
40
|
try {
|
|
29
|
-
|
|
41
|
+
parent = realpathSync.native(dir);
|
|
30
42
|
}
|
|
31
43
|
catch {
|
|
32
44
|
return resolved;
|
|
33
45
|
}
|
|
46
|
+
assertNoWindowsPathAlias(parent);
|
|
47
|
+
const normalized = path.join(parent, path.basename(resolved));
|
|
48
|
+
assertNoWindowsPathAlias(normalized);
|
|
49
|
+
return normalized;
|
|
34
50
|
}
|
|
35
51
|
export async function acquireSidecarLock(options, context) {
|
|
36
52
|
const retry = options.retry ?? {};
|
|
@@ -38,9 +54,24 @@ export async function acquireSidecarLock(options, context) {
|
|
|
38
54
|
validateSidecarLockTimeoutMs(options.timeoutMs);
|
|
39
55
|
validateSidecarLockStaleMs(options.staleMs);
|
|
40
56
|
validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
|
|
57
|
+
const targetPath = options.targetPath;
|
|
58
|
+
const explicitLockPath = options.lockPath;
|
|
59
|
+
const anchoredTargetPath = anchorWindowsDriveRelativePath(targetPath);
|
|
60
|
+
assertNoWindowsPathAlias(anchoredTargetPath);
|
|
61
|
+
const resolvedTargetPath = path.resolve(anchoredTargetPath);
|
|
62
|
+
let resolvedLockPath;
|
|
63
|
+
if (explicitLockPath !== undefined) {
|
|
64
|
+
const anchoredLockPath = anchorWindowsDriveRelativePath(explicitLockPath);
|
|
65
|
+
assertNoWindowsPathAlias(anchoredLockPath);
|
|
66
|
+
resolvedLockPath = isCwdIndependentAbsolutePath(explicitLockPath)
|
|
67
|
+
? explicitLockPath
|
|
68
|
+
: path.resolve(anchoredLockPath);
|
|
69
|
+
}
|
|
41
70
|
context.ensureExitCleanupRegistered();
|
|
42
|
-
const
|
|
43
|
-
const
|
|
71
|
+
const lockRoot = options.lockRoot;
|
|
72
|
+
const normalizedTargetPath = await resolveNormalizedTargetPath(resolvedTargetPath, lockRoot);
|
|
73
|
+
const lockPath = resolvedLockPath ?? `${normalizedTargetPath}.lock`;
|
|
74
|
+
assertNoWindowsPathAlias(lockPath);
|
|
44
75
|
let held = context.held.get(normalizedTargetPath);
|
|
45
76
|
if (held &&
|
|
46
77
|
options.reentrantOwner !== undefined &&
|
|
@@ -118,8 +149,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
118
149
|
const payload = await options.payload();
|
|
119
150
|
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
120
151
|
try {
|
|
121
|
-
if (
|
|
122
|
-
const lockRoot = options.lockRoot;
|
|
152
|
+
if (lockRoot) {
|
|
123
153
|
const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
|
|
124
154
|
const observation = fileObservation();
|
|
125
155
|
try {
|
|
@@ -180,7 +210,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
180
210
|
snapshot,
|
|
181
211
|
acquiredAt: Date.now(),
|
|
182
212
|
metadata: options.metadata ?? {},
|
|
183
|
-
lockRoot
|
|
213
|
+
lockRoot,
|
|
184
214
|
retainOnExit: options.retainOnExit,
|
|
185
215
|
parsePayload: options.parsePayload,
|
|
186
216
|
};
|
|
@@ -239,13 +269,13 @@ export async function acquireSidecarLock(options, context) {
|
|
|
239
269
|
// Root-created records retain the creator's byte/token receipt;
|
|
240
270
|
// partial direct writes use the exclusive descriptor's identity.
|
|
241
271
|
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
|
|
242
|
-
lockRoot
|
|
272
|
+
lockRoot,
|
|
243
273
|
parsePayload: options.parsePayload,
|
|
244
274
|
});
|
|
245
275
|
}
|
|
246
276
|
else if (createdSnapshot) {
|
|
247
277
|
await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
|
|
248
|
-
lockRoot
|
|
278
|
+
lockRoot,
|
|
249
279
|
parsePayload: options.parsePayload,
|
|
250
280
|
});
|
|
251
281
|
}
|
|
@@ -271,7 +301,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
271
301
|
let lockFileOpenDenied = false;
|
|
272
302
|
try {
|
|
273
303
|
rawSnapshot = await readSidecarLockRawSnapshot(lockPath, {
|
|
274
|
-
lockRoot
|
|
304
|
+
lockRoot,
|
|
275
305
|
rejectNonFile: true,
|
|
276
306
|
discardObservation: "changed",
|
|
277
307
|
onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
|
|
@@ -302,7 +332,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
302
332
|
heldByThisProcess: context.held.has(normalizedTargetPath),
|
|
303
333
|
})) {
|
|
304
334
|
if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
|
|
305
|
-
lockRoot
|
|
335
|
+
lockRoot,
|
|
306
336
|
parsePayload: options.parsePayload,
|
|
307
337
|
}))) {
|
|
308
338
|
await waitForRetry();
|
|
@@ -320,7 +350,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
320
350
|
normalizedTargetPath,
|
|
321
351
|
snapshot,
|
|
322
352
|
shouldRemoveStaleLock: options.shouldRemoveStaleLock,
|
|
323
|
-
lockRoot
|
|
353
|
+
lockRoot,
|
|
324
354
|
parsePayload: options.parsePayload,
|
|
325
355
|
});
|
|
326
356
|
if (removal === "removed" || removal === "changed") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAyD5B"}
|
|
@@ -53,13 +53,15 @@ export async function openSidecarRoot(lockRoot, relative, discardObservation, on
|
|
|
53
53
|
if (!discardObservation || (!missing && (!completeParents || !discardable)))
|
|
54
54
|
throw error;
|
|
55
55
|
try {
|
|
56
|
+
const probe = fileObservation();
|
|
56
57
|
try {
|
|
57
|
-
const current = await lockRoot.stat(relative);
|
|
58
|
+
const current = await probe.run(() => lockRoot.stat(relative));
|
|
58
59
|
if (!current.isFile || current.isSymbolicLink || current.nlink !== 1)
|
|
59
60
|
throw error;
|
|
60
61
|
}
|
|
61
62
|
catch (probeError) {
|
|
62
|
-
if (!(probeError instanceof FsSafeError && probeError.code === "not-found")
|
|
63
|
+
if (!(probeError instanceof FsSafeError && probeError.code === "not-found") &&
|
|
64
|
+
!probe.has(probeError, `stat-leaf-missing:${expectedRealPath}`))
|
|
63
65
|
throw probeError;
|
|
64
66
|
}
|
|
65
67
|
for (const { dir, stat } of parents)
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
1
2
|
import type { DirectoryReceipt } from "./directory-durability.js";
|
|
2
3
|
import type { FileIdentityStat } from "./file-identity.js";
|
|
4
|
+
import { type MutationDirectoryObservation } from "./pinned-mutation-observation.js";
|
|
3
5
|
import type { StagedFileReceipt } from "./staged-file-types.js";
|
|
4
|
-
type
|
|
6
|
+
export type StagedDirectorySnapshot = StagedFileReceipt["directory"];
|
|
7
|
+
export type PolicyStagedDirectory = Readonly<{
|
|
8
|
+
directory: StagedDirectorySnapshot;
|
|
9
|
+
observation: MutationDirectoryObservation;
|
|
10
|
+
stat: BigIntStats;
|
|
11
|
+
}>;
|
|
5
12
|
export declare function exactIdentityMatches(expected: FileIdentityStat, actual: Readonly<{
|
|
6
13
|
dev: bigint;
|
|
7
14
|
ino: bigint;
|
|
8
15
|
}>): boolean;
|
|
9
|
-
export declare function describeStagedDirectory(fd: number, pathname: string):
|
|
10
|
-
export declare function assertStagedDirectoryCurrent(receipt:
|
|
16
|
+
export declare function describeStagedDirectory(fd: number, pathname: string): StagedDirectorySnapshot;
|
|
17
|
+
export declare function assertStagedDirectoryCurrent(receipt: StagedDirectorySnapshot): BigIntStats;
|
|
18
|
+
export declare function describePolicyStagedDirectory(fd: number, pathname: string): PolicyStagedDirectory;
|
|
19
|
+
export declare function assertPolicyStagedDirectoryCurrent(captured: PolicyStagedDirectory): BigIntStats;
|
|
20
|
+
export declare function refreshPolicyStagedDirectoryObservation(captured: PolicyStagedDirectory): MutationDirectoryObservation;
|
|
11
21
|
export declare function openStagedDirectory(directory: string | DirectoryReceipt): {
|
|
12
22
|
fd: number;
|
|
13
|
-
receipt:
|
|
23
|
+
receipt: StagedDirectorySnapshot;
|
|
14
24
|
};
|
|
15
|
-
export {};
|
|
16
25
|
//# sourceMappingURL=staged-directory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,uBAAuB,CAAC;IACnC,WAAW,EAAE,4BAA4B,CAAC;IAC1C,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC,CAAC;AAEH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,uBAAuB,CAY7F;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,uBAAuB,GAAG,WAAW,CAS1F;AASD,wBAAgB,6BAA6B,CAC3C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACf,qBAAqB,CAwBvB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,qBAAqB,GAC9B,WAAW,CAOb;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,qBAAqB,GAC9B,4BAA4B,CAU9B;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,uBAAuB,CAAC;CAClC,CAiCA"}
|
package/dist/staged-directory.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
5
|
+
import { checkedMutationDirectory, } from "./pinned-mutation-observation.js";
|
|
4
6
|
import { realpathSync } from "./realpath.js";
|
|
7
|
+
import { resolvePathPreservingWindowsRoot } from "./windows-path-alias.js";
|
|
5
8
|
export function exactIdentityMatches(expected, actual) {
|
|
6
9
|
return ["dev", "ino"].every((key) => {
|
|
7
10
|
const value = expected[key];
|
|
@@ -14,7 +17,7 @@ export function describeStagedDirectory(fd, pathname) {
|
|
|
14
17
|
throw new FsSafeError("not-file", "staging parent must be a directory");
|
|
15
18
|
}
|
|
16
19
|
const receipt = Object.freeze({
|
|
17
|
-
path:
|
|
20
|
+
path: resolvePathPreservingWindowsRoot(pathname),
|
|
18
21
|
realPath: realpathSync(pathname),
|
|
19
22
|
identity: Object.freeze({ dev: identity.dev, ino: identity.ino }),
|
|
20
23
|
});
|
|
@@ -27,10 +30,58 @@ export function assertStagedDirectoryCurrent(receipt) {
|
|
|
27
30
|
realpathSync(receipt.path) !== receipt.realPath) {
|
|
28
31
|
throw new FsSafeError("path-mismatch", "staging directory pathname changed");
|
|
29
32
|
}
|
|
33
|
+
return current;
|
|
34
|
+
}
|
|
35
|
+
function sameDirectoryMetadata(left, right) {
|
|
36
|
+
return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode &&
|
|
37
|
+
left.nlink === right.nlink;
|
|
38
|
+
}
|
|
39
|
+
// The policy hot path is Node/POSIX-only. It deliberately uses native
|
|
40
|
+
// canonicalization so a successful capture contains no JS realpath work.
|
|
41
|
+
export function describePolicyStagedDirectory(fd, pathname) {
|
|
42
|
+
const resolved = path.resolve(pathname);
|
|
43
|
+
const descriptor = fs.fstatSync(fd, { bigint: true });
|
|
44
|
+
if (!descriptor.isDirectory()) {
|
|
45
|
+
throw new FsSafeError("not-file", "staging parent must be a directory");
|
|
46
|
+
}
|
|
47
|
+
const before = inspectDirectoryIdentitySync(resolved);
|
|
48
|
+
const canonicalPath = path.resolve(realpathSync.native(resolved));
|
|
49
|
+
const after = inspectDirectoryIdentitySync(resolved, descriptor);
|
|
50
|
+
if (canonicalPath !== resolved || !sameDirectoryMetadata(descriptor, before) ||
|
|
51
|
+
!sameDirectoryMetadata(descriptor, after) ||
|
|
52
|
+
path.resolve(realpathSync.native(resolved)) !== canonicalPath) {
|
|
53
|
+
throw new FsSafeError("path-mismatch", "staging directory pathname changed");
|
|
54
|
+
}
|
|
55
|
+
const directory = Object.freeze({
|
|
56
|
+
path: resolved,
|
|
57
|
+
realPath: canonicalPath,
|
|
58
|
+
identity: Object.freeze({ dev: descriptor.dev, ino: descriptor.ino }),
|
|
59
|
+
});
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
directory,
|
|
62
|
+
observation: checkedMutationDirectory(resolved, canonicalPath, after),
|
|
63
|
+
stat: after,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function assertPolicyStagedDirectoryCurrent(captured) {
|
|
67
|
+
const current = inspectDirectoryIdentitySync(captured.directory.path, captured.directory.identity);
|
|
68
|
+
if (!sameDirectoryMetadata(captured.stat, current) ||
|
|
69
|
+
path.resolve(realpathSync.native(captured.directory.path)) !== captured.directory.realPath) {
|
|
70
|
+
throw new FsSafeError("path-mismatch", "staging directory pathname changed");
|
|
71
|
+
}
|
|
72
|
+
return current;
|
|
73
|
+
}
|
|
74
|
+
export function refreshPolicyStagedDirectoryObservation(captured) {
|
|
75
|
+
const current = inspectDirectoryIdentitySync(captured.directory.path, captured.directory.identity);
|
|
76
|
+
const canonicalPath = path.resolve(realpathSync.native(captured.directory.path));
|
|
77
|
+
if (canonicalPath !== captured.directory.realPath) {
|
|
78
|
+
throw new FsSafeError("path-mismatch", "staging directory pathname changed");
|
|
79
|
+
}
|
|
80
|
+
return checkedMutationDirectory(captured.directory.path, canonicalPath, current);
|
|
30
81
|
}
|
|
31
82
|
export function openStagedDirectory(directory) {
|
|
32
83
|
// Copy supplied facts before any asynchronous work; receipts are not authority.
|
|
33
|
-
const pathname =
|
|
84
|
+
const pathname = resolvePathPreservingWindowsRoot(typeof directory === "string" ? directory : directory.path);
|
|
34
85
|
const expected = typeof directory === "string" ? undefined : {
|
|
35
86
|
realPath: directory.realPath, dev: directory.identity.dev, ino: directory.identity.ino,
|
|
36
87
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standalone-publication-path.d.ts","sourceRoot":"","sources":["../src/standalone-publication-path.ts"],"names":[],"mappings":"AAKA,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAItF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { anchorWindowsDriveRelativePath, assertNoWindowsPathAlias, } from "./windows-path-alias.js";
|
|
2
|
+
export function admitStandalonePublicationPath(value, message) {
|
|
3
|
+
const admittedPath = anchorWindowsDriveRelativePath(value);
|
|
4
|
+
assertNoWindowsPathAlias(admittedPath, "filesystem", message);
|
|
5
|
+
return admittedPath;
|
|
6
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BigIntStats, Stats } from "node:fs";
|
|
2
|
+
export type ExactStatIdentity = Pick<BigIntStats, "dev" | "ino">;
|
|
3
|
+
export type StatObservationReceipt = {
|
|
4
|
+
stat: Stats | BigIntStats;
|
|
5
|
+
identity: ExactStatIdentity;
|
|
6
|
+
};
|
|
7
|
+
/** Operation-local metadata plus exact identity; never use projected PathStat IDs. */
|
|
8
|
+
export declare function inspectStatObservationSync(inspect: (bigint: boolean) => Stats | BigIntStats, expected?: ExactStatIdentity, initial?: Stats | BigIntStats, platform?: NodeJS.Platform): StatObservationReceipt;
|
|
9
|
+
/** Revalidate an exact identity without allocating a receipt that the caller will discard. */
|
|
10
|
+
export declare function assertStatObservationSync(inspect: (bigint: boolean) => Stats | BigIntStats, expected: ExactStatIdentity, initial?: Stats | BigIntStats, platform?: NodeJS.Platform): Stats | BigIntStats;
|
|
11
|
+
//# sourceMappingURL=stat-observation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stat-observation.d.ts","sourceRoot":"","sources":["../src/stat-observation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAKlD,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AACjE,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,KAAK,GAAG,WAAW,CAAC;IAC1B,QAAQ,EAAE,iBAAiB,CAAC;CAC7B,CAAC;AAqEF,sFAAsF;AACtF,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,GAAG,WAAW,EACjD,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,OAAO,CAAC,EAAE,KAAK,GAAG,WAAW,EAC7B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,CAExB;AAED,8FAA8F;AAC9F,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,GAAG,WAAW,EACjD,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,CAAC,EAAE,KAAK,GAAG,WAAW,EAC7B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,KAAK,GAAG,WAAW,CAErB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { recordFileObservationFailure } from "./file-observation.js";
|
|
3
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
4
|
+
function identityMismatch() {
|
|
5
|
+
const error = new FsSafeError("path-mismatch", "file identity changed or could not be verified");
|
|
6
|
+
recordFileObservationFailure(error, "identity");
|
|
7
|
+
throw error;
|
|
8
|
+
}
|
|
9
|
+
function safeNumber(value) {
|
|
10
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
11
|
+
}
|
|
12
|
+
function safeExpected(identity) {
|
|
13
|
+
return typeof identity.dev === "bigint" && typeof identity.ino === "bigint" &&
|
|
14
|
+
identity.dev >= 0n && identity.ino >= 0n &&
|
|
15
|
+
identity.dev <= 9007199254740991n && identity.ino <= 9007199254740991n;
|
|
16
|
+
}
|
|
17
|
+
function observeStatSync(inspect, expected, initial, platform = process.platform, captureIdentity) {
|
|
18
|
+
if (platform === "win32" || (!initial && expected && !safeExpected(expected))) {
|
|
19
|
+
// Preserve the strict Windows unknown-ID retry, including known components
|
|
20
|
+
// and validation of the expected identity before the first observation.
|
|
21
|
+
const stat = inspectFileIdentitySync(() => {
|
|
22
|
+
const current = initial;
|
|
23
|
+
initial = undefined;
|
|
24
|
+
return (current ?? inspect(true));
|
|
25
|
+
}, expected, platform);
|
|
26
|
+
return (captureIdentity
|
|
27
|
+
? { stat, identity: expected ?? { dev: stat.dev, ino: stat.ino } }
|
|
28
|
+
: stat);
|
|
29
|
+
}
|
|
30
|
+
const stat = initial ?? inspect(false);
|
|
31
|
+
const { dev: rawDev, ino: rawIno } = stat;
|
|
32
|
+
const numericDev = safeNumber(rawDev);
|
|
33
|
+
const numericIno = safeNumber(rawIno);
|
|
34
|
+
const dev = numericDev ? BigInt(rawDev) : typeof rawDev === "bigint" ? rawDev : undefined;
|
|
35
|
+
const ino = numericIno ? BigInt(rawIno) : typeof rawIno === "bigint" ? rawIno : undefined;
|
|
36
|
+
if (expected && ((dev !== undefined && dev !== expected.dev) ||
|
|
37
|
+
(ino !== undefined && ino !== expected.ino)))
|
|
38
|
+
identityMismatch();
|
|
39
|
+
if (numericDev && numericIno) {
|
|
40
|
+
return (captureIdentity
|
|
41
|
+
? { stat, identity: expected ?? { dev: dev, ino: ino } }
|
|
42
|
+
: stat);
|
|
43
|
+
}
|
|
44
|
+
// An unsafe numeric component is unknown, not a rounded identity. Retain
|
|
45
|
+
// every exact component from this observation while obtaining the full ID.
|
|
46
|
+
const exact = inspectFileIdentitySync(() => {
|
|
47
|
+
const current = inspect(true);
|
|
48
|
+
if ((dev !== undefined && current.dev !== dev) ||
|
|
49
|
+
(ino !== undefined && current.ino !== ino))
|
|
50
|
+
identityMismatch();
|
|
51
|
+
return current;
|
|
52
|
+
}, expected, platform);
|
|
53
|
+
return (captureIdentity
|
|
54
|
+
? { stat: exact, identity: expected ?? { dev: exact.dev, ino: exact.ino } }
|
|
55
|
+
: exact);
|
|
56
|
+
}
|
|
57
|
+
/** Operation-local metadata plus exact identity; never use projected PathStat IDs. */
|
|
58
|
+
export function inspectStatObservationSync(inspect, expected, initial, platform = process.platform) {
|
|
59
|
+
return observeStatSync(inspect, expected, initial, platform, true);
|
|
60
|
+
}
|
|
61
|
+
/** Revalidate an exact identity without allocating a receipt that the caller will discard. */
|
|
62
|
+
export function assertStatObservationSync(inspect, expected, initial, platform = process.platform) {
|
|
63
|
+
return observeStatSync(inspect, expected, initial, platform, false);
|
|
64
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strict-file-identity.d.ts","sourceRoot":"","sources":["../src/strict-file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,KAAK,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AA+B1D,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EACnE,OAAO,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC7B,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,iBAAiB,EACjE,OAAO,EAAE,MAAM,CAAC,EAChB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,CAAC,
|
|
1
|
+
{"version":3,"file":"strict-file-identity.d.ts","sourceRoot":"","sources":["../src/strict-file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,KAAK,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AA+B1D,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EACnE,OAAO,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC7B,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,iBAAiB,EACjE,OAAO,EAAE,MAAM,CAAC,EAChB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,CAAC,CAmCH"}
|
|
@@ -41,10 +41,47 @@ export async function inspectFileIdentity(inspect, expected, platform = process.
|
|
|
41
41
|
throw identityMismatch();
|
|
42
42
|
}
|
|
43
43
|
export function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
|
|
44
|
-
|
|
44
|
+
let knownDev;
|
|
45
|
+
let knownIno;
|
|
46
|
+
if (expected) {
|
|
47
|
+
knownDev = expected.dev;
|
|
48
|
+
if (typeof knownDev !== "bigint")
|
|
49
|
+
throw identityMismatch();
|
|
50
|
+
knownIno = expected.ino;
|
|
51
|
+
if (typeof knownIno !== "bigint")
|
|
52
|
+
throw identityMismatch();
|
|
53
|
+
// An unknown expected device must not bypass observation of its inode.
|
|
54
|
+
if (platform === "win32" && (knownDev === 0n || knownIno === 0n))
|
|
55
|
+
throw identityMismatch();
|
|
56
|
+
}
|
|
45
57
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
46
58
|
const stat = inspect();
|
|
47
|
-
|
|
59
|
+
let complete = true;
|
|
60
|
+
// Keep dev-before-ino access and early mismatch errors without allocating
|
|
61
|
+
// a checker closure, known-component object, or field array per inspection.
|
|
62
|
+
const dev = stat.dev;
|
|
63
|
+
if (typeof dev !== "bigint")
|
|
64
|
+
throw identityMismatch();
|
|
65
|
+
if (platform === "win32" && dev === 0n) {
|
|
66
|
+
complete = false;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (knownDev !== undefined && knownDev !== dev)
|
|
70
|
+
throw identityMismatch();
|
|
71
|
+
knownDev = dev;
|
|
72
|
+
}
|
|
73
|
+
const ino = stat.ino;
|
|
74
|
+
if (typeof ino !== "bigint")
|
|
75
|
+
throw identityMismatch();
|
|
76
|
+
if (platform === "win32" && ino === 0n) {
|
|
77
|
+
complete = false;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
if (knownIno !== undefined && knownIno !== ino)
|
|
81
|
+
throw identityMismatch();
|
|
82
|
+
knownIno = ino;
|
|
83
|
+
}
|
|
84
|
+
if (complete)
|
|
48
85
|
return stat;
|
|
49
86
|
}
|
|
50
87
|
throw identityMismatch();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symlink-parents.d.ts","sourceRoot":"","sources":["../src/symlink-parents.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"symlink-parents.d.ts","sourceRoot":"","sources":["../src/symlink-parents.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAqCF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,6BAA6B,GACpC,IAAI,CA6BN"}
|
package/dist/symlink-parents.js
CHANGED
|
@@ -2,9 +2,14 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { hasNodeErrorCode, isPathRelativeEscape } from "./path.js";
|
|
5
|
+
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
5
6
|
function resolvePathWalk(params) {
|
|
6
|
-
const
|
|
7
|
-
|
|
7
|
+
const rawRootDir = params.rootDir;
|
|
8
|
+
assertNoWindowsPathAlias(rawRootDir, "filesystem", "root dir uses a Windows filesystem namespace alias");
|
|
9
|
+
const rawTargetPath = params.targetPath;
|
|
10
|
+
assertNoWindowsPathAlias(rawTargetPath, "filesystem", "target path uses a Windows filesystem namespace alias");
|
|
11
|
+
const root = resolvePathPreservingWindowsRoot(rawRootDir);
|
|
12
|
+
const target = resolvePathPreservingWindowsRoot(rawTargetPath);
|
|
8
13
|
const relative = path.relative(root, target);
|
|
9
14
|
if (isPathRelativeEscape(relative)) {
|
|
10
15
|
if (params.allowOutsideRoot) {
|
package/dist/temp-target.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
|
+
import type { TempWorkspaceCleanupSafety } from "./temp-workspace-owner.js";
|
|
2
3
|
export type TempFile = {
|
|
3
4
|
dir: string;
|
|
4
5
|
path: string;
|
|
@@ -11,6 +12,7 @@ type TempFileOptions = {
|
|
|
11
12
|
prefix: string;
|
|
12
13
|
fileName?: string;
|
|
13
14
|
onCleanupError?: (error: unknown) => void;
|
|
15
|
+
cleanupSafety?: TempWorkspaceCleanupSafety;
|
|
14
16
|
};
|
|
15
17
|
export declare function sanitizeTempFileName(fileName: string): string;
|
|
16
18
|
export declare function buildRandomTempFilePath(params: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAa7B,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,aAAa,CAAC,EAAE,0BAA0B,CAAC;CAC5C,CAAC;AA8DF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAeT;AAwDD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAC1E,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;CAC7D,CAAC,CAmID;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEzE;AAED,wBAAsB,YAAY,CAAC,CAAC,EAClC,MAAM,EAAE,eAAe,EACvB,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
|
package/dist/temp-target.js
CHANGED
|
@@ -7,6 +7,7 @@ import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
|
7
7
|
import { assertSafePathSegment, sanitizeSafePathSegment, trimHyphenEdges } from "./safe-path-segment.js";
|
|
8
8
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
9
9
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
10
|
+
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
10
11
|
const HYPHEN_CHAR_CODE = 0x2d;
|
|
11
12
|
const DOT_CHAR_CODE = 0x2e;
|
|
12
13
|
const NUMBER_ZERO_CHAR_CODE = 0x30;
|
|
@@ -74,7 +75,9 @@ export function buildRandomTempFilePath(params) {
|
|
|
74
75
|
const uuid = params.uuid
|
|
75
76
|
? assertSafePathSegment(params.uuid.trim(), { label: "temp uuid" })
|
|
76
77
|
: crypto.randomUUID();
|
|
77
|
-
|
|
78
|
+
const filePath = path.join(rootDir, `${prefix}-${now}-${uuid}${extension}`);
|
|
79
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "temp file path uses a Windows filesystem namespace alias");
|
|
80
|
+
return filePath;
|
|
78
81
|
}
|
|
79
82
|
function isNodeErrorWithCode(err, code) {
|
|
80
83
|
return (typeof err === "object" &&
|
|
@@ -106,17 +109,141 @@ async function cleanupTempDir(dir, identity, onCleanupError) {
|
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
function resolveTempRoot(rootDir) {
|
|
109
|
-
|
|
112
|
+
if (rootDir !== undefined) {
|
|
113
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "temp root uses a Windows filesystem namespace alias");
|
|
114
|
+
}
|
|
115
|
+
const selectedRoot = rootDir ?? resolveSecureTempRoot({ fallbackPrefix: "fs-safe" });
|
|
116
|
+
assertNoWindowsPathAlias(selectedRoot, "filesystem", "temp root uses a Windows filesystem namespace alias");
|
|
117
|
+
const resolvedRoot = resolvePathPreservingWindowsRoot(selectedRoot);
|
|
118
|
+
assertNoWindowsPathAlias(resolvedRoot, "filesystem", "temp root uses a Windows filesystem namespace alias");
|
|
119
|
+
return resolvedRoot;
|
|
120
|
+
}
|
|
121
|
+
function resolveTempFileCleanupSafety(value) {
|
|
122
|
+
if (value === undefined || value === "compatible")
|
|
123
|
+
return "compatible";
|
|
124
|
+
if (value === "require-bounded")
|
|
125
|
+
return value;
|
|
126
|
+
throw new TypeError("cleanupSafety must be compatible or require-bounded");
|
|
110
127
|
}
|
|
111
128
|
export async function createOwnedTempFile(params) {
|
|
129
|
+
const cleanupSafety = resolveTempFileCleanupSafety(params.cleanupSafety);
|
|
112
130
|
const rootDir = resolveTempRoot(params.rootDir);
|
|
113
131
|
const prefix = `${sanitizePrefix(params.prefix)}-`;
|
|
132
|
+
if (cleanupSafety === "require-bounded") {
|
|
133
|
+
const initialFileName = sanitizeTempFileName(params.fileName ?? "download.bin");
|
|
134
|
+
const [{ TempWorkspaceCleanupCapability, TempWorkspaceCleanupOwner }, { admitExistingTempWorkspaceRoot }, { TempWorkspaceRetainedChild }, { validateInitialTempWorkspaceChild, admitRetainedTempWorkspaceChild }, { inspectDirectoryIdentitySync },] = await Promise.all([
|
|
135
|
+
import("./temp-workspace-owner.js"),
|
|
136
|
+
import("./temp-workspace-admission.js"),
|
|
137
|
+
import("./temp-workspace-descriptor.js"),
|
|
138
|
+
import("./temp-workspace-child-admission.js"),
|
|
139
|
+
import("./directory-guard.js"),
|
|
140
|
+
]);
|
|
141
|
+
const admission = admitExistingTempWorkspaceRoot(rootDir);
|
|
142
|
+
const childPrefix = path.join(admission.dir, prefix);
|
|
143
|
+
assertNoWindowsPathAlias(childPrefix, "filesystem", "temp directory uses a Windows filesystem namespace alias");
|
|
144
|
+
const capability = new TempWorkspaceCleanupCapability(admission.dir, cleanupSafety, admission, 0o700);
|
|
145
|
+
let dir;
|
|
146
|
+
let initialPath;
|
|
147
|
+
let identity;
|
|
148
|
+
let retainedChild;
|
|
149
|
+
let cleanupOwner;
|
|
150
|
+
let unregisterTempDir;
|
|
151
|
+
try {
|
|
152
|
+
capability.prepareChildCreation();
|
|
153
|
+
dir = await fs.mkdtemp(childPrefix);
|
|
154
|
+
assertNoWindowsPathAlias(dir, "filesystem", "temp directory uses a Windows filesystem namespace alias");
|
|
155
|
+
initialPath = path.join(dir, initialFileName);
|
|
156
|
+
assertNoWindowsPathAlias(initialPath, "filesystem", "temp file path uses a Windows filesystem namespace alias");
|
|
157
|
+
capability.assertCurrent();
|
|
158
|
+
const initial = inspectDirectoryIdentitySync(dir);
|
|
159
|
+
identity = Object.freeze({ dev: initial.dev, ino: initial.ino });
|
|
160
|
+
const needsModeInitialization = validateInitialTempWorkspaceChild(initial, admission.ownerUid, 0o700);
|
|
161
|
+
retainedChild = TempWorkspaceRetainedChild.retain(dir, identity);
|
|
162
|
+
const modeInitialization = admitRetainedTempWorkspaceChild(retainedChild, needsModeInitialization, admission, 0o700);
|
|
163
|
+
if (modeInitialization)
|
|
164
|
+
await modeInitialization;
|
|
165
|
+
const retainDescriptor = capability.admitChildDescriptor(retainedChild.ensureReadable());
|
|
166
|
+
capability.assertAncestryCurrent();
|
|
167
|
+
retainedChild.finalizeAdmission(admission.ownerUid, 0o700);
|
|
168
|
+
cleanupOwner = new TempWorkspaceCleanupOwner(retainedChild, capability, retainDescriptor);
|
|
169
|
+
retainedChild = undefined;
|
|
170
|
+
unregisterTempDir = registerTempPathForExit(dir, {
|
|
171
|
+
cleanupSync: () => cleanupOwner.cleanupSync(),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
try {
|
|
176
|
+
if (cleanupOwner)
|
|
177
|
+
cleanupOwner.cleanupSync();
|
|
178
|
+
else {
|
|
179
|
+
const closeErrors = [];
|
|
180
|
+
try {
|
|
181
|
+
retainedChild?.close();
|
|
182
|
+
}
|
|
183
|
+
catch (closeError) {
|
|
184
|
+
closeErrors.push(closeError);
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
capability.close();
|
|
188
|
+
}
|
|
189
|
+
catch (closeError) {
|
|
190
|
+
closeErrors.push(closeError);
|
|
191
|
+
}
|
|
192
|
+
if (closeErrors.length === 1)
|
|
193
|
+
throw closeErrors[0];
|
|
194
|
+
if (closeErrors.length > 1) {
|
|
195
|
+
throw new AggregateError(closeErrors, "temp file admission descriptor close failed");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (cleanupError) {
|
|
200
|
+
throw new AggregateError([error, cleanupError], "temp file creation and cleanup both failed");
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
const owner = cleanupOwner;
|
|
205
|
+
const file = (fileName) => {
|
|
206
|
+
const filePath = path.join(dir, sanitizeTempFileName(fileName ?? params.fileName ?? "download.bin"));
|
|
207
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "temp file path uses a Windows filesystem namespace alias");
|
|
208
|
+
return filePath;
|
|
209
|
+
};
|
|
210
|
+
const cleanup = async () => {
|
|
211
|
+
try {
|
|
212
|
+
try {
|
|
213
|
+
await owner.cleanup();
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
if (!isNodeErrorWithCode(err, "ENOENT")) {
|
|
217
|
+
params.onCleanupError?.(err);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
unregisterTempDir();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
return {
|
|
226
|
+
target: {
|
|
227
|
+
dir,
|
|
228
|
+
path: initialPath,
|
|
229
|
+
file,
|
|
230
|
+
cleanup,
|
|
231
|
+
[Symbol.asyncDispose]: cleanup,
|
|
232
|
+
},
|
|
233
|
+
identity,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
114
236
|
const dir = await fs.mkdtemp(path.join(rootDir, prefix));
|
|
237
|
+
assertNoWindowsPathAlias(dir, "filesystem", "temp directory uses a Windows filesystem namespace alias");
|
|
115
238
|
// Windows file indexes can exceed Number.MAX_SAFE_INTEGER. Cleanup receipts
|
|
116
239
|
// must retain the exact identity or adjacent directories can compare equal.
|
|
117
240
|
const identity = fsSync.lstatSync(dir, { bigint: true });
|
|
118
241
|
const unregisterTempDir = registerTempPathForExit(dir, { recursive: true, identity });
|
|
119
|
-
const file = (fileName) =>
|
|
242
|
+
const file = (fileName) => {
|
|
243
|
+
const filePath = path.join(dir, sanitizeTempFileName(fileName ?? params.fileName ?? "download.bin"));
|
|
244
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "temp file path uses a Windows filesystem namespace alias");
|
|
245
|
+
return filePath;
|
|
246
|
+
};
|
|
120
247
|
const cleanup = async () => {
|
|
121
248
|
try {
|
|
122
249
|
await cleanupTempDir(dir, identity, params.onCleanupError);
|