@openclaw/fs-safe 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +36 -7
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +2 -8
- package/dist/advanced.d.ts +3 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +22 -23
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-merge.d.ts +1 -0
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +4 -4
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +1 -0
- package/dist/archive-options.d.ts +2 -0
- package/dist/archive-options.d.ts.map +1 -1
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +6 -7
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive-zip-count.d.ts.map +1 -1
- package/dist/archive-zip-count.js +21 -1
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +23 -1
- package/dist/archive-zip-loader.d.ts +2 -0
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +7 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +7 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +14 -9
- package/dist/byte-view.d.ts +3 -0
- package/dist/byte-view.d.ts.map +1 -0
- package/dist/byte-view.js +13 -0
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +6 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +70 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +7 -7
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +123 -0
- package/dist/file-cleanup.d.ts +20 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +81 -0
- package/dist/file-contents.d.ts +6 -0
- package/dist/file-contents.d.ts.map +1 -0
- package/dist/file-contents.js +40 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +16 -4
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
- package/dist/file-lock-sync-root-acquire.js +3 -0
- package/dist/file-lock-sync-root-held.d.ts +1 -2
- package/dist/file-lock-sync-root-held.d.ts.map +1 -1
- package/dist/file-lock-sync-root-held.js +7 -5
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
- package/dist/file-lock-sync-stale-admission.js +3 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -11
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +2 -5
- package/dist/file-store.js +3 -3
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +27 -19
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +2 -5
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +2 -6
- package/dist/json-durable-queue-paths.d.ts.map +1 -1
- package/dist/json-durable-queue-paths.js +2 -24
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +10 -9
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +32 -75
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +19 -21
- package/dist/move-path-cleanup.d.ts +5 -19
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +57 -21
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +63 -40
- package/dist/native-binding.d.ts +11 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +2 -0
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +3 -2
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +173 -28
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +19 -3
- package/dist/native-policy-parent-windows.d.ts.map +1 -1
- package/dist/native-policy-parent-windows.js +15 -6
- package/dist/native-staged-file.d.ts +5 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +90 -40
- package/dist/native.js +2 -2
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +11 -2
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +2 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -17
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +35 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +31 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +186 -0
- package/dist/pinned-write-types.d.ts +3 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +41 -147
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts +2 -2
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +58 -98
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +35 -44
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +28 -26
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +13 -22
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +10 -16
- package/dist/replace-file-temp-owner.d.ts +0 -7
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +9 -60
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +9 -13
- package/dist/root-create-input.d.ts +2 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +13 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +20 -3
- package/dist/root-file-final-admission.d.ts +1 -1
- package/dist/root-file-final-admission.d.ts.map +1 -1
- package/dist/root-file-final-admission.js +5 -2
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +3 -2
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +78 -27
- package/dist/root-move-noreplace.d.ts +2 -0
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +22 -13
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-read-admission.d.ts.map +1 -1
- package/dist/root-read-admission.js +7 -2
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +15 -1
- package/dist/root-write-publication.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +4 -6
- package/dist/sidecar-lock-handle.d.ts +3 -0
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +6 -0
- package/dist/sidecar-lock-reclaim.d.ts +1 -1
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +11 -8
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +3 -5
- package/dist/staged-directory.d.ts +2 -2
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/strict-file-identity.d.ts +1 -1
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +9 -9
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +2 -27
- package/dist/temp-workspace-owner.js +4 -4
- package/dist/unicode-path.d.ts.map +1 -1
- package/dist/unicode-path.js +3 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +4 -2
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -1
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/dist/write-file-handle.d.ts +7 -0
- package/dist/write-file-handle.d.ts.map +1 -1
- package/dist/write-file-handle.js +23 -0
- package/dist/write-open-flags.d.ts.map +1 -1
- package/dist/write-open-flags.js +1 -8
- package/dist/write-queue.d.ts.map +1 -1
- package/dist/write-queue.js +1 -4
- package/docs/advanced.md +71 -2
- package/docs/archive.md +102 -39
- package/docs/atomic.md +29 -5
- package/docs/config.md +6 -2
- package/docs/contributing.md +48 -4
- package/docs/copy.md +2 -0
- package/docs/creation.md +132 -0
- package/docs/durability.md +59 -0
- package/docs/file-contents.md +68 -0
- package/docs/install.md +31 -7
- package/docs/json.md +5 -4
- package/docs/local-roots.md +2 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/mutation-policy-proof.md +5 -3
- package/docs/native-helper.md +22 -9
- package/docs/native.md +47 -15
- package/docs/path.md +4 -4
- package/docs/permissions.md +37 -14
- package/docs/public-api.md +5 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +2 -2
- package/docs/regular-file.md +3 -0
- package/docs/root.md +43 -0
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +14 -5
- package/docs/staged-file.md +9 -3
- package/docs/store.md +3 -1
- package/docs/temp.md +4 -1
- package/docs/types.md +18 -2
- package/docs/walk.md +7 -0
- package/docs/writing.md +80 -7
- package/package.json +18 -15
package/dist/root-impl.js
CHANGED
|
@@ -56,6 +56,7 @@ import fs from "node:fs/promises";
|
|
|
56
56
|
import path from "node:path";
|
|
57
57
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
58
58
|
import { assertCopySourceCurrent, resolveFileCopyCloneMode } from "./copy-file-input.js";
|
|
59
|
+
import { assertPrivateFileCreationAvailable, resolveCreationPermissions } from "./creation-permissions.js";
|
|
59
60
|
import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, createNearestExistingDirectoryGuard } from "./directory-guard.js";
|
|
60
61
|
import { FsSafeError } from "./errors.js";
|
|
61
62
|
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
@@ -71,18 +72,19 @@ import { validatePinnedOperationPayload } from "./pinned-operation.js";
|
|
|
71
72
|
import { PATH_ALIAS_POLICIES } from "./path-policy.js";
|
|
72
73
|
import { assertNoNulPathInput, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError, } from "./path.js";
|
|
73
74
|
import { readOpenedFileSafely } from "./read-opened-file.js";
|
|
74
|
-
import { cleanupPinnedFilePath
|
|
75
|
+
import { cleanupPinnedFilePath } from "./file-cleanup.js";
|
|
76
|
+
import { removePathIfIdentityUnchanged } from "./replace-file-temp-owner.js";
|
|
75
77
|
import { realpathSync } from "./realpath.js";
|
|
76
78
|
import { mkdirPathFallback, prepareRootWriteTarget, tryMkdirAtExactParent } from "./root-directory-creation.js";
|
|
77
79
|
import { isNonRegularWriteOpenError, resolveNonblockingWriteFlag } from "./write-open-flags.js";
|
|
78
|
-
import { resolveRootPath, resolveRootPathForRemoval } from "./root-path.js";
|
|
80
|
+
import { resolveRootPath, resolveRootPathSync, resolveRootPathForRemoval } from "./root-path.js";
|
|
79
81
|
import { RemovalPathReceipts } from "./root-remove-receipt.js";
|
|
80
82
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
81
83
|
import { listDirectoryPath, openRootDirectoryListing } from "./root-directory-list.js";
|
|
82
84
|
import { statResolvedPathInRoot } from "./root-path-stat.js";
|
|
83
85
|
import { entriesInRoot } from "./root-entries.js";
|
|
84
86
|
import { assertMoveMutationAllowed } from "./root-move-preflight.js";
|
|
85
|
-
import { assertRootIdentityCurrent, assertValidRootDestinationPath, assertValidRootRelativePath, ensureTrailingSep, expandRelativePathWithHome, resolvePathInRoot, resolveRootContext, rootRelativeReadPath, } from "./root-context.js";
|
|
87
|
+
import { assertRootIdentityCurrent, assertRootIdentityCurrentSync, assertValidRootDestinationPath, assertValidRootRelativePath, ensureTrailingSep, expandRelativePathWithHome, resolvePathInRoot, resolveRootContext, rootRelativeReadPath, } from "./root-context.js";
|
|
86
88
|
import { fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, normalizePinnedWriteError, outsideWorkspaceError, } from "./root-errors.js";
|
|
87
89
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
88
90
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
@@ -97,7 +99,7 @@ import { finishRootFallbackWrite } from "./root-write-publication.js";
|
|
|
97
99
|
import { withRootFallbackCompatibilityLock } from "./root-write-compatibility.js";
|
|
98
100
|
import { assertRootFallbackWritePath } from "./root-write-lock-binding.js";
|
|
99
101
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
100
|
-
import { movePathNoReplaceNative } from "./root-move-noreplace.js";
|
|
102
|
+
import { admitMoveSourceStat, movePathNoReplaceNative } from "./root-move-noreplace.js";
|
|
101
103
|
import { admitRootReadHandle, inspectOpenedPathIdentitySync } from "./root-read-admission.js";
|
|
102
104
|
import { createCopyPublicationObserver, onCopyPublication, onCopySourceAdmission } from "./copy-publication.js";
|
|
103
105
|
import { writeAllToFile } from "./write-file-handle.js";
|
|
@@ -315,13 +317,15 @@ export class RootHandle {
|
|
|
315
317
|
}
|
|
316
318
|
async create(relativePath, data, options = {}) {
|
|
317
319
|
assertValidRootDestinationPath(relativePath);
|
|
320
|
+
const durable = options.durable ?? this.defaults.durable ?? true;
|
|
318
321
|
await writeFileInRoot(this.context, {
|
|
319
322
|
relativePath,
|
|
320
323
|
data,
|
|
321
324
|
mkdir: this.defaults.mkdir,
|
|
322
325
|
mode: this.defaults.mode,
|
|
323
326
|
...this.mutationOptions(createInputOptions(data, options, this.defaults.maxBytes)),
|
|
324
|
-
durable:
|
|
327
|
+
durable: durable !== false,
|
|
328
|
+
strictFileSync: durable === "file",
|
|
325
329
|
overwrite: false,
|
|
326
330
|
}).catch(rethrowMutationAuthorityError).catch(rethrowCreateInputError);
|
|
327
331
|
}
|
|
@@ -377,7 +381,9 @@ export class RootHandle {
|
|
|
377
381
|
assertValidRootRelativePath(fromRelative);
|
|
378
382
|
assertValidRootDestinationPath(toRelative);
|
|
379
383
|
validatePinnedOperationPayload({ from: fromRelative, to: toRelative });
|
|
380
|
-
const
|
|
384
|
+
const mutationOptions = this.mutationOptions(options);
|
|
385
|
+
const { assertBeforeMutation } = mutationOptions;
|
|
386
|
+
const { denyMutations, mutationSymlinks } = snapshotPinnedMutationPolicy(mutationOptions.denyMutations, mutationOptions.mutationSymlinks) ?? {};
|
|
381
387
|
const overwrite = options.overwrite ?? false;
|
|
382
388
|
await assertMoveMutationAllowed(this.context, {
|
|
383
389
|
fromRelative,
|
|
@@ -717,8 +723,9 @@ async function appendFileInRoot(root, params) {
|
|
|
717
723
|
if (params.prependNewlineIfNeeded === true &&
|
|
718
724
|
!target.createdForWrite &&
|
|
719
725
|
target.stat.size > 0 &&
|
|
726
|
+
params.data.length > 0 &&
|
|
720
727
|
((typeof params.data === "string" && !params.data.startsWith("\n")) ||
|
|
721
|
-
(Buffer.isBuffer(params.data) && params.data
|
|
728
|
+
(Buffer.isBuffer(params.data) && params.data[0] !== 0x0a))) {
|
|
722
729
|
const newline = Buffer.from("\n", typeof params.data === "string" ? params.encoding : "utf8");
|
|
723
730
|
const tail = Buffer.alloc(newline.length);
|
|
724
731
|
const { bytesRead } = await target.handle.read(tail, 0, tail.length, Math.max(0, target.stat.size - tail.length));
|
|
@@ -778,6 +785,7 @@ async function removePathInRoot(root, params) {
|
|
|
778
785
|
}
|
|
779
786
|
}
|
|
780
787
|
async function mkdirPathInRoot(root, params) {
|
|
788
|
+
const privateMode = resolveCreationPermissions(params, true).private;
|
|
781
789
|
validatePinnedOperationPayload({ relativePath: params.relativePath });
|
|
782
790
|
const policy = params.denyMutations === undefined && params.mutationSymlinks === undefined
|
|
783
791
|
? undefined
|
|
@@ -810,19 +818,23 @@ async function mkdirPathInRoot(root, params) {
|
|
|
810
818
|
await getFsSafeTestHooks()?.beforePinnedWriteParentAdmission?.(resolved.resolved);
|
|
811
819
|
}
|
|
812
820
|
try {
|
|
813
|
-
if (prepared?.mutationAdmission && params.assertBeforeMutation === undefined &&
|
|
821
|
+
if (!privateMode && prepared?.mutationAdmission && params.assertBeforeMutation === undefined &&
|
|
814
822
|
await tryMkdirAtExactParent(root, resolved.resolved, prepared.mutationAdmission))
|
|
815
823
|
return;
|
|
816
|
-
await mkdirPathFallback(root, resolved, params.assertBeforeMutation, policy?.mutationSymlinks !== undefined, prepared?.mutationAdmission);
|
|
824
|
+
await mkdirPathFallback(root, resolved, params.assertBeforeMutation, policy?.mutationSymlinks !== undefined, prepared?.mutationAdmission, privateMode);
|
|
817
825
|
}
|
|
818
826
|
catch (error) {
|
|
819
827
|
throw normalizePinnedPathError(error);
|
|
820
828
|
}
|
|
821
829
|
}
|
|
822
830
|
async function writeFileInRoot(root, params) {
|
|
831
|
+
if (params.private !== undefined) {
|
|
832
|
+
const permissions = resolveCreationPermissions(params, false);
|
|
833
|
+
params = { ...params, private: permissions.private, mode: permissions.mode };
|
|
834
|
+
}
|
|
823
835
|
const input = rootWriteInput(params);
|
|
824
836
|
await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
|
|
825
|
-
if (input.kind === "buffer" && process.platform === "win32" &&
|
|
837
|
+
if (!params.private && input.kind === "buffer" && !input.stageBeforePublish && process.platform === "win32" &&
|
|
826
838
|
(params.renameIdentity === "verify-content-with-lock" || !getNativeBinding())) {
|
|
827
839
|
await writeFileFallback(root, { ...params, data: input.data });
|
|
828
840
|
return;
|
|
@@ -836,15 +848,25 @@ async function writeFileInRoot(root, params) {
|
|
|
836
848
|
async function commitPinnedWriteInRoot(root, pinned, params, input) {
|
|
837
849
|
let verifyingPublication = false;
|
|
838
850
|
try {
|
|
851
|
+
if (params.private)
|
|
852
|
+
assertPrivateFileCreationAvailable();
|
|
853
|
+
if (params.private && params.mkdir !== false) {
|
|
854
|
+
const target = await prepareRootWriteTarget(root, pinned.targetPath, params.assertBeforeMutation, pinned.mutationAdmission, true);
|
|
855
|
+
if (target !== pinned.targetPath) {
|
|
856
|
+
throw new FsSafeError("path-mismatch", "private creation parent changed during admission");
|
|
857
|
+
}
|
|
858
|
+
}
|
|
839
859
|
await runPinnedWriteWithRenamePolicy({
|
|
840
860
|
rootPath: pinned.rootReal,
|
|
841
861
|
relativeParentPath: pinned.relativeParentPath,
|
|
842
862
|
basename: pinned.basename,
|
|
843
863
|
targetPath: pinned.targetPath,
|
|
844
864
|
renameIdentity: params.renameIdentity,
|
|
845
|
-
mkdir: params.mkdir !== false,
|
|
865
|
+
mkdir: !params.private && params.mkdir !== false,
|
|
866
|
+
private: params.private,
|
|
846
867
|
mode: params.mode ?? pinned.mode,
|
|
847
868
|
sync: params.durable !== false,
|
|
869
|
+
strictFileSync: params.strictFileSync,
|
|
848
870
|
overwrite: params.overwrite,
|
|
849
871
|
rejectFinalSymlink: params.mutationSymlinks !== undefined,
|
|
850
872
|
input,
|
|
@@ -1064,6 +1086,8 @@ async function listPathFallback(root, relativePath, withFileTypes) {
|
|
|
1064
1086
|
return await listDirectoryPath(root, resolved.resolved, withFileTypes, resolved.receipt);
|
|
1065
1087
|
}
|
|
1066
1088
|
async function movePathFallback(root, params) {
|
|
1089
|
+
const originalRoutes = params.overwrite && params.assertBeforeMutation
|
|
1090
|
+
? await Promise.all([params.fromRelative, params.toRelative].map(expandRelativePathWithHome)) : undefined;
|
|
1067
1091
|
const source = await resolvePathInRoot(root, params.fromRelative, {
|
|
1068
1092
|
aliasErrorCode: "path-alias",
|
|
1069
1093
|
allowFinalSymlink: true,
|
|
@@ -1099,9 +1123,14 @@ async function movePathFallback(root, params) {
|
|
|
1099
1123
|
targetStat?.isSymbolicLink() === true);
|
|
1100
1124
|
},
|
|
1101
1125
|
});
|
|
1102
|
-
let
|
|
1126
|
+
let sourceIdentity;
|
|
1103
1127
|
try {
|
|
1104
|
-
|
|
1128
|
+
if (params.overwrite && params.assertBeforeMutation) {
|
|
1129
|
+
sourceIdentity = inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(source.resolved, { bigint: true }), true));
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
admitMoveSourceStat(fsSync.lstatSync(source.resolved), params.overwrite);
|
|
1133
|
+
}
|
|
1105
1134
|
}
|
|
1106
1135
|
catch (error) {
|
|
1107
1136
|
if (isNotFoundPathError(error)) {
|
|
@@ -1109,19 +1138,10 @@ async function movePathFallback(root, params) {
|
|
|
1109
1138
|
}
|
|
1110
1139
|
throw error;
|
|
1111
1140
|
}
|
|
1112
|
-
if (
|
|
1113
|
-
throw new FsSafeError("
|
|
1114
|
-
}
|
|
1115
|
-
if (sourceStat.isFile() && sourceStat.nlink > 1) {
|
|
1116
|
-
throw hardlinkedPathNotAllowedError();
|
|
1117
|
-
}
|
|
1118
|
-
if (!params.overwrite && sourceStat.isDirectory()) {
|
|
1119
|
-
throw new FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
1141
|
+
if (!pinnedTarget) {
|
|
1142
|
+
throw new FsSafeError("path-mismatch", "destination admission was not completed");
|
|
1120
1143
|
}
|
|
1121
1144
|
if (!params.overwrite) {
|
|
1122
|
-
if (!pinnedTarget) {
|
|
1123
|
-
throw new FsSafeError("path-mismatch", "destination admission was not completed");
|
|
1124
|
-
}
|
|
1125
1145
|
await movePathNoReplaceNative(root, params, {
|
|
1126
1146
|
sourcePath: source.resolved,
|
|
1127
1147
|
sourceParentPath: path.dirname(pinnedSource.canonicalPath),
|
|
@@ -1130,8 +1150,9 @@ async function movePathFallback(root, params) {
|
|
|
1130
1150
|
});
|
|
1131
1151
|
return;
|
|
1132
1152
|
}
|
|
1133
|
-
const
|
|
1134
|
-
const
|
|
1153
|
+
const guardOptions = { bigint: sourceIdentity !== undefined };
|
|
1154
|
+
const sourceParentGuard = await createAsyncDirectoryGuard(path.dirname(source.resolved), guardOptions);
|
|
1155
|
+
const targetParentGuard = await createNearestExistingDirectoryGuard(target.rootReal, path.dirname(target.resolved), guardOptions);
|
|
1135
1156
|
await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("move", target.resolved);
|
|
1136
1157
|
await assertAsyncDirectoryGuard(sourceParentGuard);
|
|
1137
1158
|
await assertAsyncDirectoryGuard(targetParentGuard);
|
|
@@ -1139,6 +1160,31 @@ async function movePathFallback(root, params) {
|
|
|
1139
1160
|
assertFinalSymlinkRejected(source.resolved, params.mutationSymlinks !== undefined);
|
|
1140
1161
|
assertFinalSymlinkRejected(target.resolved, params.mutationSymlinks !== undefined);
|
|
1141
1162
|
params.assertBeforeMutation?.();
|
|
1163
|
+
if (sourceIdentity) {
|
|
1164
|
+
assertRootIdentityCurrentSync(root);
|
|
1165
|
+
assertSyncDirectoryGuard(sourceParentGuard);
|
|
1166
|
+
assertSyncDirectoryGuard(targetParentGuard);
|
|
1167
|
+
for (const [route, selected, expected, policy] of [
|
|
1168
|
+
[originalRoutes[0], source.resolved, pinnedSource.canonicalPath, PATH_ALIAS_POLICIES.strict],
|
|
1169
|
+
[originalRoutes[1], target.resolved, pinnedTarget.canonicalPath, PATH_ALIAS_POLICIES.unlinkTarget],
|
|
1170
|
+
]) {
|
|
1171
|
+
for (const absolutePath of new Set([path.isAbsolute(route) ? route : `${root.rootWithSep}${route}`, selected])) {
|
|
1172
|
+
try {
|
|
1173
|
+
const current = resolveRootPathSync({
|
|
1174
|
+
absolutePath, rootPath: root.rootReal, rootCanonicalPath: root.rootReal,
|
|
1175
|
+
rootIdentity: root.rootIdentity, boundaryLabel: "root", policy,
|
|
1176
|
+
...mutationSymlinkResolution(params.mutationSymlinks),
|
|
1177
|
+
});
|
|
1178
|
+
if (current.canonicalPath !== expected)
|
|
1179
|
+
throw new FsSafeError("path-mismatch", "move route changed during authorization");
|
|
1180
|
+
}
|
|
1181
|
+
catch (error) {
|
|
1182
|
+
throw normalizePinnedPathError(error);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(source.resolved, { bigint: true }), true), sourceIdentity);
|
|
1187
|
+
}
|
|
1142
1188
|
await fs.rename(source.resolved, target.resolved);
|
|
1143
1189
|
}
|
|
1144
1190
|
catch (error) {
|
|
@@ -1303,6 +1349,7 @@ async function writeMissingFileFallback(root, params, expectedWritePath) {
|
|
|
1303
1349
|
const parentGuard = pathSelection?.parentGuard ??
|
|
1304
1350
|
await createAsyncDirectoryGuard(path.dirname(targetPath), { bigint: true });
|
|
1305
1351
|
let created = false;
|
|
1352
|
+
let completed = false;
|
|
1306
1353
|
let createdIdentity;
|
|
1307
1354
|
let writtenHandle;
|
|
1308
1355
|
let verifyingPublication = false;
|
|
@@ -1346,6 +1393,7 @@ async function writeMissingFileFallback(root, params, expectedWritePath) {
|
|
|
1346
1393
|
});
|
|
1347
1394
|
if (params.durable !== false)
|
|
1348
1395
|
await syncDirectoryBestEffort(path.dirname(targetPath));
|
|
1396
|
+
completed = true;
|
|
1349
1397
|
}
|
|
1350
1398
|
catch (err) {
|
|
1351
1399
|
if (verifyingPublication)
|
|
@@ -1363,6 +1411,9 @@ async function writeMissingFileFallback(root, params, expectedWritePath) {
|
|
|
1363
1411
|
pathname: targetPath, handle: writtenHandle, identity: createdIdentity, parentGuard,
|
|
1364
1412
|
});
|
|
1365
1413
|
}
|
|
1366
|
-
|
|
1414
|
+
if (completed)
|
|
1415
|
+
await writtenHandle?.close();
|
|
1416
|
+
else
|
|
1417
|
+
await writtenHandle?.close().catch(() => undefined);
|
|
1367
1418
|
}
|
|
1368
1419
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { type BigIntStats, type Stats } from "node:fs";
|
|
1
2
|
import { type DenyMutationPolicy } from "./deny-mutations.js";
|
|
2
3
|
import { type RootContext } from "./root-context.js";
|
|
3
4
|
import { type MutationSymlinkPolicy } from "./root-symlink-policy.js";
|
|
5
|
+
export declare function admitMoveSourceStat<T extends Stats | BigIntStats>(stat: T, overwrite?: boolean): T;
|
|
4
6
|
export declare function movePathNoReplaceNative(root: RootContext, params: {
|
|
5
7
|
assertBeforeMutation?: () => void;
|
|
6
8
|
denyMutations?: DenyMutationPolicy;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-move-noreplace.d.ts","sourceRoot":"","sources":["../src/root-move-noreplace.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"root-move-noreplace.d.ts","sourceRoot":"","sources":["../src/root-move-noreplace.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAI/D,OAAO,EAA2B,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAQvF,OAAO,EAA4D,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAU/G,OAAO,EAA8B,KAAK,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAsDlG,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,KAAK,GAAG,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,UAAQ,GAAG,CAAC,CAWhG;AAED,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE;IACN,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;CAC1C,EACD,KAAK,EAAE;IACL,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,IAAI,CAAC,CAsIf"}
|
|
@@ -12,6 +12,7 @@ import { admitPathInsideRoot } from "./root-boundary.js";
|
|
|
12
12
|
import { fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, outsideWorkspaceError, } from "./root-errors.js";
|
|
13
13
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
14
14
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
15
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
15
16
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
16
17
|
function nativeParentRelativePath(rootReal, parentPath) {
|
|
17
18
|
const relative = path.relative(rootReal, parentPath);
|
|
@@ -57,6 +58,18 @@ function normalizeRenameNoReplaceError(error) {
|
|
|
57
58
|
}
|
|
58
59
|
return normalizeMoveError(error);
|
|
59
60
|
}
|
|
61
|
+
export function admitMoveSourceStat(stat, overwrite = false) {
|
|
62
|
+
if (stat.isSymbolicLink()) {
|
|
63
|
+
throw new FsSafeError("symlink", "symlink not allowed");
|
|
64
|
+
}
|
|
65
|
+
if (stat.isFile() && stat.nlink > 1) {
|
|
66
|
+
throw hardlinkedPathNotAllowedError();
|
|
67
|
+
}
|
|
68
|
+
if (!overwrite && stat.isDirectory()) {
|
|
69
|
+
throw new FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
70
|
+
}
|
|
71
|
+
return stat;
|
|
72
|
+
}
|
|
60
73
|
export async function movePathNoReplaceNative(root, params, paths) {
|
|
61
74
|
try {
|
|
62
75
|
fsSync.lstatSync(paths.targetPath);
|
|
@@ -104,21 +117,13 @@ export async function movePathNoReplaceNative(root, params, paths) {
|
|
|
104
117
|
await assertRootIdentityCurrent(root);
|
|
105
118
|
for (const admission of parentAdmissions)
|
|
106
119
|
assertSyncDirectoryGuard(admission.guard);
|
|
120
|
+
// Capture an exact receipt only when admission must survive a callback.
|
|
107
121
|
let sourceStat;
|
|
108
|
-
|
|
109
|
-
sourceStat = fsSync.lstatSync(admittedSourcePath);
|
|
122
|
+
if (params.assertBeforeMutation) {
|
|
123
|
+
sourceStat = inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true })));
|
|
110
124
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
if (sourceStat.isSymbolicLink()) {
|
|
115
|
-
throw new FsSafeError("symlink", "symlink not allowed");
|
|
116
|
-
}
|
|
117
|
-
if (sourceStat.isFile() && sourceStat.nlink > 1) {
|
|
118
|
-
throw hardlinkedPathNotAllowedError();
|
|
119
|
-
}
|
|
120
|
-
if (sourceStat.isDirectory()) {
|
|
121
|
-
throw new FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
125
|
+
else {
|
|
126
|
+
admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath));
|
|
122
127
|
}
|
|
123
128
|
assertFinalSymlinkRejected(admittedTargetPath, params.mutationSymlinks !== undefined);
|
|
124
129
|
params.assertBeforeMutation?.();
|
|
@@ -143,6 +148,10 @@ export async function movePathNoReplaceNative(root, params, paths) {
|
|
|
143
148
|
for (const admission of parentAdmissions)
|
|
144
149
|
assertSyncDirectoryGuard(admission.guard);
|
|
145
150
|
}
|
|
151
|
+
// The callback can change a leaf without replacing its admitted parent.
|
|
152
|
+
if (sourceStat !== undefined) {
|
|
153
|
+
inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true })), sourceStat);
|
|
154
|
+
}
|
|
146
155
|
try {
|
|
147
156
|
binding.renameNoReplace(sourceParent.fd, path.basename(paths.sourcePath), targetParent.fd, path.basename(paths.targetPath));
|
|
148
157
|
}
|
package/dist/root-options.d.ts
CHANGED
|
@@ -50,12 +50,18 @@ export type RootWriteJsonOptions = RootWriteOptions & {
|
|
|
50
50
|
space?: Parameters<typeof JSON.stringify>[2];
|
|
51
51
|
trailingNewline?: boolean;
|
|
52
52
|
};
|
|
53
|
-
export type RootCreateOptions = Omit<RootWriteOptions, "overwrite"
|
|
54
|
-
|
|
53
|
+
export type RootCreateOptions = Omit<RootWriteOptions, "overwrite" | "durable"> & {
|
|
54
|
+
/** Publish complete content without replacing an existing entry, including without native support. */
|
|
55
|
+
atomic?: boolean;
|
|
56
|
+
private?: boolean;
|
|
57
|
+
/** "file" requires file synchronization; directory synchronization remains best effort. */
|
|
58
|
+
durable?: boolean | "file";
|
|
59
|
+
};
|
|
60
|
+
export type RootCreateStreamOptions = Omit<RootCreateOptions, "atomic" | "encoding" | "renameIdentity"> & {
|
|
55
61
|
maxBytes?: number;
|
|
56
62
|
signal?: AbortSignal;
|
|
57
63
|
};
|
|
58
|
-
export type RootCreateJsonOptions = Omit<RootWriteJsonOptions, "overwrite">;
|
|
64
|
+
export type RootCreateJsonOptions = Omit<RootWriteJsonOptions, "overwrite" | "durable"> & Pick<RootCreateOptions, "atomic" | "durable" | "private">;
|
|
59
65
|
export type RootAppendOptions = RootWriteOptions & {
|
|
60
66
|
prependNewlineIfNeeded?: boolean;
|
|
61
67
|
};
|
|
@@ -70,7 +76,9 @@ export type RootRemoveOptions = Pick<RootDefaults, "assertBeforeMutation" | "den
|
|
|
70
76
|
maxDepth?: number;
|
|
71
77
|
signal?: AbortSignal;
|
|
72
78
|
};
|
|
73
|
-
export type RootMkdirOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"
|
|
79
|
+
export type RootMkdirOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
|
|
80
|
+
private?: boolean;
|
|
81
|
+
};
|
|
74
82
|
export type RootReadParams = Omit<RootReadOptions, "nonBlockingRead">;
|
|
75
83
|
export declare function readDefaults(defaults: RootDefaults): RootReadParams;
|
|
76
84
|
export declare function mergeReadOptions(defaults: RootDefaults, options: RootReadOptions): RootReadParams;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-options.d.ts","sourceRoot":"","sources":["../src/root-options.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"root-options.d.ts","sourceRoot":"","sources":["../src/root-options.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-path-stat.d.ts","sourceRoot":"","sources":["../src/root-path-stat.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAIjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"root-path-stat.d.ts","sourceRoot":"","sources":["../src/root-path-stat.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAIjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA2D3C,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,0BAA0B,GACnC,OAAO,CAAC,QAAQ,CAAC,CAUnB"}
|
package/dist/root-path-stat.js
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
-
import { recordFileObservationFailure } from "./file-observation.js";
|
|
4
|
+
import { fileObservation, recordFileObservationFailure } from "./file-observation.js";
|
|
5
5
|
import { hasNodeErrorCode, isNotFoundPathError } from "./path.js";
|
|
6
6
|
import { assertRootDirectoryObservationGuard, assertRootPathObservationReceiptCurrent, createRootDirectoryObservationGuard, pathStatFromStats, } from "./root-directory-list.js";
|
|
7
7
|
import { fileNotFoundError } from "./root-errors.js";
|
|
8
8
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
9
9
|
import { assertStatObservationSync } from "./stat-observation.js";
|
|
10
10
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
11
|
-
|
|
11
|
+
function singleLinkFileIdentity(stat) {
|
|
12
|
+
if (!stat?.isFile() || stat.isSymbolicLink() || (stat.nlink !== 1 && stat.nlink !== 1n))
|
|
13
|
+
return;
|
|
14
|
+
const exact = (value) => typeof value === "bigint" ? value : Number.isSafeInteger(value) ? BigInt(value) : undefined;
|
|
15
|
+
const dev = exact(stat.dev), ino = exact(stat.ino);
|
|
16
|
+
if (dev === undefined || ino === undefined || dev < 0n || ino < 0n ||
|
|
17
|
+
(process.platform === "win32" && (dev === 0n || ino === 0n)))
|
|
18
|
+
return;
|
|
19
|
+
return { dev, ino };
|
|
20
|
+
}
|
|
21
|
+
async function captureChangedObservedFile(error, pathname, before, observed, assertParents, proof) {
|
|
22
|
+
try {
|
|
23
|
+
const previous = singleLinkFileIdentity(before), current = singleLinkFileIdentity(observed);
|
|
24
|
+
if (!previous || !current || (previous.dev === current.dev && previous.ino === current.ino))
|
|
25
|
+
return;
|
|
26
|
+
await assertParents();
|
|
27
|
+
// A changed metadata probe grants only a fresh acquisition attempt, never ownership.
|
|
28
|
+
proof.failure = { error, kind: `stat-leaf-changed:${pathname}` };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Ambiguous file or ancestry observations retain the original public error.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function missingObservedFileError(cause, pathname, before, assertParents, proof) {
|
|
12
35
|
const failure = new FsSafeError("path-mismatch", "file changed during operation", {
|
|
13
36
|
cause: cause instanceof Error ? cause : undefined,
|
|
14
37
|
});
|
|
@@ -17,7 +40,7 @@ async function missingObservedFileError(cause, pathname, before, assertParents)
|
|
|
17
40
|
try {
|
|
18
41
|
await assertParents();
|
|
19
42
|
// Metadata loss permits discarding a probe, never reading or owning a file.
|
|
20
|
-
|
|
43
|
+
proof.failure = { error: failure, kind: `stat-leaf-missing:${pathname}` };
|
|
21
44
|
}
|
|
22
45
|
catch {
|
|
23
46
|
// Keep the public mismatch and withhold provenance when ancestry changed.
|
|
@@ -26,6 +49,19 @@ async function missingObservedFileError(cause, pathname, before, assertParents)
|
|
|
26
49
|
return failure;
|
|
27
50
|
}
|
|
28
51
|
export async function statResolvedPathInRoot(root, resolvedPath, receipt) {
|
|
52
|
+
const proof = {};
|
|
53
|
+
try {
|
|
54
|
+
// Nested observations in a hook cannot grant this invocation discard authority.
|
|
55
|
+
return await fileObservation().run(() => statObservedPathInRoot(root, resolvedPath, proof, receipt));
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const failure = proof.failure;
|
|
59
|
+
if (failure && failure.error === error)
|
|
60
|
+
recordFileObservationFailure(error, failure.kind);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function statObservedPathInRoot(root, resolvedPath, proof, receipt) {
|
|
29
65
|
let admittedTarget = false;
|
|
30
66
|
try {
|
|
31
67
|
if (receipt) {
|
|
@@ -37,13 +73,21 @@ export async function statResolvedPathInRoot(root, resolvedPath, receipt) {
|
|
|
37
73
|
if (beforeObservation)
|
|
38
74
|
await beforeObservation(resolvedPath);
|
|
39
75
|
let observed;
|
|
76
|
+
let sample, samples = 0;
|
|
77
|
+
const comparison = fileObservation();
|
|
40
78
|
try {
|
|
41
|
-
observed =
|
|
79
|
+
observed = comparison.run(() => assertStatObservationSync(bigint => {
|
|
80
|
+
sample = bigint ? fsSync.lstatSync(resolvedPath, { bigint: true }) : fsSync.lstatSync(resolvedPath);
|
|
81
|
+
samples += 1;
|
|
82
|
+
return sample;
|
|
83
|
+
}, receipt.target.identity));
|
|
42
84
|
}
|
|
43
85
|
catch (error) {
|
|
44
86
|
if (isNotFoundPathError(error)) {
|
|
45
|
-
throw await missingObservedFileError(error, resolvedPath, "stat" in receipt.target ? receipt.target.stat : undefined, () => assertRootPathObservationReceiptCurrent(root, receipt));
|
|
87
|
+
throw await missingObservedFileError(error, resolvedPath, "stat" in receipt.target ? receipt.target.stat : undefined, () => assertRootPathObservationReceiptCurrent(root, receipt), proof);
|
|
46
88
|
}
|
|
89
|
+
if (comparison.has(error, "identity"))
|
|
90
|
+
await captureChangedObservedFile(error, resolvedPath, "stat" in receipt.target ? receipt.target.stat : undefined, samples === 1 ? sample : undefined, () => assertRootPathObservationReceiptCurrent(root, receipt), proof);
|
|
47
91
|
throw error;
|
|
48
92
|
}
|
|
49
93
|
if (observed.isSymbolicLink()) {
|
|
@@ -77,13 +121,21 @@ export async function statResolvedPathInRoot(root, resolvedPath, receipt) {
|
|
|
77
121
|
if (beforeObservation)
|
|
78
122
|
await beforeObservation(resolvedPath);
|
|
79
123
|
let observed;
|
|
124
|
+
let sample, samples = 0;
|
|
125
|
+
const comparison = fileObservation();
|
|
80
126
|
try {
|
|
81
|
-
observed =
|
|
127
|
+
observed = comparison.run(() => inspectFileIdentitySync(() => {
|
|
128
|
+
sample = fsSync.lstatSync(resolvedPath, { bigint: true });
|
|
129
|
+
samples += 1;
|
|
130
|
+
return sample;
|
|
131
|
+
}, expected));
|
|
82
132
|
}
|
|
83
133
|
catch (error) {
|
|
84
134
|
if (isNotFoundPathError(error)) {
|
|
85
|
-
throw await missingObservedFileError(error, resolvedPath, expected, () => assertRootDirectoryObservationGuard(root, parentGuard));
|
|
135
|
+
throw await missingObservedFileError(error, resolvedPath, expected, () => assertRootDirectoryObservationGuard(root, parentGuard), proof);
|
|
86
136
|
}
|
|
137
|
+
if (comparison.has(error, "identity"))
|
|
138
|
+
await captureChangedObservedFile(error, resolvedPath, expected, samples === 1 ? sample : undefined, () => assertRootDirectoryObservationGuard(root, parentGuard), proof);
|
|
87
139
|
throw error;
|
|
88
140
|
}
|
|
89
141
|
if (observed.isSymbolicLink()) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-read-admission.d.ts","sourceRoot":"","sources":["../src/root-read-admission.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,OAAO,EAAiC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAmB9D,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,aAAa,GAAG,SAAS,GAClC,WAAW,CAQb;
|
|
1
|
+
{"version":3,"file":"root-read-admission.d.ts","sourceRoot":"","sources":["../src/root-read-admission.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,OAAO,EAAiC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAmB9D,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,aAAa,GAAG,SAAS,GAClC,WAAW,CAQb;AAgDD,KAAK,mBAAmB,GAAG;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACzB,CAAC;AAEF,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,mBAAmB,EAAE,MAAM,EAAE;IAC/E,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;IACV,QAAQ,EAAE,WAAW,CAAC;IACtB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClF,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CACzE,GAAG,OAAO,CAAC,CAAC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BpC"}
|
|
@@ -35,7 +35,9 @@ function assertRootReadAdmissionCurrent(params) {
|
|
|
35
35
|
assertRootIdentityCurrentSync(params.root);
|
|
36
36
|
const admittedPath = (() => {
|
|
37
37
|
try {
|
|
38
|
-
inspectFileIdentitySync(() => inspectOpenedPathIdentitySync(params.filePath, params.symlinks), params.identity);
|
|
38
|
+
const current = inspectFileIdentitySync(() => inspectOpenedPathIdentitySync(params.filePath, params.symlinks), params.identity);
|
|
39
|
+
if (params.hardlinks !== "allow" && current.nlink > 1n)
|
|
40
|
+
throw hardlinkedPathNotAllowedError();
|
|
39
41
|
const canonicalPath = resolveFinalOpenedRealPath(params.filePath);
|
|
40
42
|
assertNoWindowsPathAlias(canonicalPath, "filesystem", "resolved file path uses a Windows filesystem namespace alias");
|
|
41
43
|
const admittedRealPath = admitPathInsideRoot({
|
|
@@ -48,7 +50,9 @@ function assertRootReadAdmissionCurrent(params) {
|
|
|
48
50
|
}
|
|
49
51
|
// This is a fresh no-follow observation even when canonicalPath and
|
|
50
52
|
// filePath have the same spelling; realpath may have raced a replacement.
|
|
51
|
-
inspectFileIdentitySync(() => inspectOpenedPathIdentitySync(canonicalPath, undefined), params.identity);
|
|
53
|
+
const canonical = inspectFileIdentitySync(() => inspectOpenedPathIdentitySync(canonicalPath, undefined), params.identity);
|
|
54
|
+
if (params.hardlinks !== "allow" && canonical.nlink > 1n)
|
|
55
|
+
throw hardlinkedPathNotAllowedError();
|
|
52
56
|
return admittedRealPath.path;
|
|
53
57
|
}
|
|
54
58
|
catch (error) {
|
|
@@ -75,6 +79,7 @@ export async function admitRootReadHandle(params) {
|
|
|
75
79
|
filePath: params.filePath,
|
|
76
80
|
handle: params.opened.handle,
|
|
77
81
|
identity: params.identity,
|
|
82
|
+
hardlinks: params.hardlinks,
|
|
78
83
|
symlinks: params.symlinks,
|
|
79
84
|
afterPathIdentityCheck: params.afterPathIdentityCheck,
|
|
80
85
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AASpE,eAAO,MAAM,uBAAuB,EAAE,OAAO,MAA4C,CAAC;AAK1F,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;
|
|
1
|
+
{"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AASpE,eAAO,MAAM,uBAAuB,EAAE,OAAO,MAA4C,CAAC;AAK1F,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;AAiPD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAkMf"}
|
package/dist/root-remove.js
CHANGED
|
@@ -220,13 +220,21 @@ async function removeOne(root, targetPath, options, receipts) {
|
|
|
220
220
|
throw normalizeRemoveGuardError(error);
|
|
221
221
|
}
|
|
222
222
|
try {
|
|
223
|
-
const
|
|
223
|
+
const identity = options.assertBeforeMutation
|
|
224
|
+
? inspectFileIdentitySync(() => fsSync.lstatSync(targetPath, { bigint: true })) : undefined;
|
|
225
|
+
const isDirectory = (identity ?? fsSync.lstatSync(targetPath)).isDirectory();
|
|
224
226
|
if (!isDirectory && options[nonrecursiveRemovalKind] === "directory") {
|
|
225
227
|
throw new FsSafeError("path-mismatch", "store entry is no longer a directory");
|
|
226
228
|
}
|
|
227
229
|
assertFinalSymlinkRejected(targetPath, options.mutationSymlinks !== undefined);
|
|
228
230
|
assertNotAborted(options.signal);
|
|
229
231
|
options.assertBeforeMutation?.();
|
|
232
|
+
if (identity) {
|
|
233
|
+
assertNotAborted(options.signal);
|
|
234
|
+
admission.assertCurrent();
|
|
235
|
+
inspectFileIdentitySync(() => fsSync.lstatSync(targetPath, { bigint: true }), identity);
|
|
236
|
+
assertFinalSymlinkRejected(targetPath, options.mutationSymlinks !== undefined);
|
|
237
|
+
}
|
|
230
238
|
await (isDirectory ? fs.rmdir(targetPath) : fs.unlink(targetPath));
|
|
231
239
|
}
|
|
232
240
|
catch (error) {
|
|
@@ -436,6 +444,12 @@ export async function removePathInRootFallback(root, targetPath, options, receip
|
|
|
436
444
|
assertFinalSymlinkRejected(target, options.mutationSymlinks !== undefined, details(target, "remove"));
|
|
437
445
|
assertNotAborted(options.signal);
|
|
438
446
|
options.assertBeforeMutation?.();
|
|
447
|
+
if (options.assertBeforeMutation) {
|
|
448
|
+
assertNotAborted(options.signal);
|
|
449
|
+
if (!inspect(target, initial))
|
|
450
|
+
return;
|
|
451
|
+
assertFinalSymlinkRejected(target, options.mutationSymlinks !== undefined, details(target, "remove"));
|
|
452
|
+
}
|
|
439
453
|
try {
|
|
440
454
|
await (initial.isDirectory() ? fs.rmdir(target) : fs.unlink(target));
|
|
441
455
|
}
|
|
@@ -4,7 +4,7 @@ import { readFileHandleBounded } from "./bounded-read.js";
|
|
|
4
4
|
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
|
|
7
|
-
import { cleanupPinnedFilePath } from "./
|
|
7
|
+
import { cleanupPinnedFilePath } from "./file-cleanup.js";
|
|
8
8
|
import { verifyAtomicWriteResult } from "./root-write-verification.js";
|
|
9
9
|
// Compatibility callers hold the lock throughout this operation. The caller
|
|
10
10
|
// owns the original write handle; this helper owns a content-verified reopen.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAShJ,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAQjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CA2CR;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AA+BF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAgCD;
|
|
1
|
+
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAShJ,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAQjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CA2CR;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AA+BF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAgCD;AAyDD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBzF"}
|
package/dist/secret-file.js
CHANGED
|
@@ -291,6 +291,7 @@ async function materializeSecretFileAtomic(params, createOnly) {
|
|
|
291
291
|
basename: fileName,
|
|
292
292
|
mkdir: false,
|
|
293
293
|
mode,
|
|
294
|
+
verifyPosixMode: true,
|
|
294
295
|
sync: params.durable !== false,
|
|
295
296
|
overwrite: !createOnly,
|
|
296
297
|
input: { kind: "buffer", data: typeof params.content === "string" ? params.content : Buffer.from(params.content) },
|
|
@@ -6,5 +6,11 @@ export declare function inspectSecureWindowsDescriptor(params: {
|
|
|
6
6
|
identity: ExactIdentity;
|
|
7
7
|
stat: Stats;
|
|
8
8
|
}): PermissionCheck;
|
|
9
|
+
/** Both mechanisms inspect the same borrowed handle; the caller owns its lifetime. */
|
|
10
|
+
export declare function inspectSecureWindowsFile(params: {
|
|
11
|
+
fd: number;
|
|
12
|
+
identity: ExactIdentity;
|
|
13
|
+
stat: Stats;
|
|
14
|
+
}): Promise<PermissionCheck>;
|
|
9
15
|
export {};
|
|
10
16
|
//# sourceMappingURL=secure-file-windows.d.ts.map
|