@openclaw/fs-safe 0.13.1 → 0.15.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 +64 -1
- package/README.md +10 -4
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +4 -4
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.js +6 -5
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/directory-mode-owner.js +5 -5
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-directory.d.ts.map +1 -1
- package/dist/file-store-sync-directory.js +14 -7
- package/dist/file-store-sync-write.js +3 -3
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guest-dispatch-python.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/native-binding.d.ts +8 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-parent-admission.d.ts +3 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +24 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +7 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +202 -172
- package/dist/native-policy-directory-observation.d.ts +12 -0
- package/dist/native-policy-directory-observation.d.ts.map +1 -0
- package/dist/native-policy-directory-observation.js +61 -0
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +200 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +42 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +23 -29
- package/dist/pinned-mutation-observation.d.ts +7 -2
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +81 -18
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-types.d.ts +2 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -2
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +65 -30
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +6 -0
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +5 -1
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +5 -4
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +20 -11
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +2 -2
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +12 -37
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-compatibility.d.ts +1 -2
- package/dist/root-write-compatibility.d.ts.map +1 -1
- package/dist/root-write-compatibility.js +17 -68
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +10 -24
- package/dist/root-write-lock-binding.d.ts +15 -0
- package/dist/root-write-lock-binding.d.ts.map +1 -0
- package/dist/root-write-lock-binding.js +162 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/staged-directory.d.ts +5 -2
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +37 -1
- package/dist/temp-workspace-admission.d.ts.map +1 -1
- package/dist/temp-workspace-admission.js +28 -4
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -2
- package/docs/advanced.md +1 -1
- package/docs/archive.md +36 -9
- package/docs/atomic.md +85 -8
- package/docs/copy.md +35 -0
- package/docs/file-store.md +19 -0
- package/docs/guest.md +1 -1
- package/docs/json-store.md +5 -0
- package/docs/native-helper.md +10 -3
- package/docs/native.md +8 -0
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +13 -0
- package/docs/public-api.md +3 -2
- package/docs/root.md +22 -3
- package/docs/sidecar-lock.md +109 -4
- package/docs/staged-file.md +7 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +11 -1
- package/package.json +10 -10
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
|
@@ -84,10 +84,9 @@ export function rawPathRelativeToCanonicalRoot(candidate, rootCanonicalPath, opt
|
|
|
84
84
|
function isFilesystemRoot(candidate) {
|
|
85
85
|
return path.parse(candidate).root === candidate;
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
function pathExists(targetPath) {
|
|
88
88
|
try {
|
|
89
|
-
fs.lstatSync(pathForWindowsFilesystem(targetPath));
|
|
90
|
-
return true;
|
|
89
|
+
return fs.lstatSync(pathForWindowsFilesystem(targetPath), { throwIfNoEntry: false }) !== undefined;
|
|
91
90
|
}
|
|
92
91
|
catch (error) {
|
|
93
92
|
if (isNotFoundPathError(error)) {
|
|
@@ -97,46 +96,22 @@ async function pathExists(targetPath) {
|
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
98
|
export async function resolvePathViaExistingAncestor(targetPath) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const missingSuffix = [];
|
|
105
|
-
while (!isFilesystemRoot(cursor) && !(await pathExists(cursor))) {
|
|
106
|
-
missingSuffix.unshift(path.basename(cursor));
|
|
107
|
-
const parent = path.dirname(cursor);
|
|
108
|
-
if (parent === cursor) {
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
cursor = parent;
|
|
112
|
-
}
|
|
113
|
-
if (!(await pathExists(cursor))) {
|
|
114
|
-
return normalized;
|
|
115
|
-
}
|
|
116
|
-
let rawResolvedAncestor;
|
|
117
|
-
try {
|
|
118
|
-
rawResolvedAncestor = realpathSync.native(pathForWindowsFilesystem(cursor));
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
return normalized;
|
|
122
|
-
}
|
|
123
|
-
assertNoWindowsPathAlias(rawResolvedAncestor);
|
|
124
|
-
const resolvedAncestor = resolvePathPreservingWindowsRoot(rawResolvedAncestor);
|
|
125
|
-
assertNoWindowsPathAlias(resolvedAncestor);
|
|
126
|
-
const resolved = missingSuffix.length === 0
|
|
127
|
-
? resolvedAncestor
|
|
128
|
-
: path.resolve(resolvedAncestor, ...missingSuffix);
|
|
129
|
-
assertNoWindowsPathAlias(resolved);
|
|
130
|
-
return resolved;
|
|
99
|
+
return resolveExistingAncestor(targetPath, pathExists, realpathSync.native);
|
|
100
|
+
}
|
|
101
|
+
function pathExistsSync(targetPath) {
|
|
102
|
+
return fs.existsSync(pathForWindowsFilesystem(targetPath));
|
|
131
103
|
}
|
|
132
104
|
export function resolvePathViaExistingAncestorSync(targetPath) {
|
|
105
|
+
return resolveExistingAncestor(targetPath, pathExistsSync, realpathSync);
|
|
106
|
+
}
|
|
107
|
+
function resolveExistingAncestor(targetPath, exists, canonicalize) {
|
|
133
108
|
assertNoWindowsPathAlias(targetPath);
|
|
134
109
|
const normalized = resolvePathPreservingWindowsRoot(targetPath);
|
|
135
110
|
assertNoWindowsPathAlias(normalized);
|
|
136
111
|
let cursor = normalized;
|
|
137
112
|
const missingSuffix = [];
|
|
138
113
|
while (!isFilesystemRoot(cursor) &&
|
|
139
|
-
!
|
|
114
|
+
!exists(cursor)) {
|
|
140
115
|
missingSuffix.unshift(path.basename(cursor));
|
|
141
116
|
const parent = path.dirname(cursor);
|
|
142
117
|
if (parent === cursor) {
|
|
@@ -144,12 +119,12 @@ export function resolvePathViaExistingAncestorSync(targetPath) {
|
|
|
144
119
|
}
|
|
145
120
|
cursor = parent;
|
|
146
121
|
}
|
|
147
|
-
if (!
|
|
122
|
+
if (!exists(cursor)) {
|
|
148
123
|
return normalized;
|
|
149
124
|
}
|
|
150
125
|
let rawResolvedAncestor;
|
|
151
126
|
try {
|
|
152
|
-
rawResolvedAncestor =
|
|
127
|
+
rawResolvedAncestor = canonicalize(pathForWindowsFilesystem(cursor));
|
|
153
128
|
}
|
|
154
129
|
catch {
|
|
155
130
|
return normalized;
|
package/dist/root-path.js
CHANGED
|
@@ -519,19 +519,7 @@ function buildResolvedRootPath(params) {
|
|
|
519
519
|
};
|
|
520
520
|
}
|
|
521
521
|
async function getPathKind(absolutePath, preserveFinalSymlink) {
|
|
522
|
-
|
|
523
|
-
const operationPath = pathForWindowsFilesystem(absolutePath);
|
|
524
|
-
const stat = preserveFinalSymlink
|
|
525
|
-
? fs.lstatSync(operationPath)
|
|
526
|
-
: fs.statSync(operationPath);
|
|
527
|
-
return { exists: true, kind: toResolvedKind(stat) };
|
|
528
|
-
}
|
|
529
|
-
catch (error) {
|
|
530
|
-
if (isNotFoundPathError(error)) {
|
|
531
|
-
return { exists: false, kind: "missing" };
|
|
532
|
-
}
|
|
533
|
-
throw error;
|
|
534
|
-
}
|
|
522
|
+
return getPathKindSync(absolutePath, preserveFinalSymlink);
|
|
535
523
|
}
|
|
536
524
|
function getPathKindSync(absolutePath, preserveFinalSymlink) {
|
|
537
525
|
try {
|
package/dist/root-remove.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type RootContext } from "./root-context.js";
|
|
2
2
|
import type { RootRemoveOptions } from "./root-options.js";
|
|
3
3
|
import type { RemovalPathReceipts } from "./root-remove-receipt.js";
|
|
4
|
+
export declare const nonrecursiveRemovalKind: unique symbol;
|
|
4
5
|
export declare function validateRemoveOptions(options: RootRemoveOptions): void;
|
|
5
6
|
export declare function removePathInRootFallback(root: RootContext, targetPath: string, options: RootRemoveOptions, receipts?: RemovalPathReceipts): Promise<void>;
|
|
6
7
|
//# sourceMappingURL=root-remove.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AASpE,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;AAyOD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA6Lf"}
|
package/dist/root-remove.js
CHANGED
|
@@ -16,6 +16,7 @@ import { createSuppressedError } from "./suppressed-error.js";
|
|
|
16
16
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
17
17
|
const DEFAULT_MAX_ENTRIES = 100_000;
|
|
18
18
|
const DEFAULT_MAX_DEPTH = 64;
|
|
19
|
+
export const nonrecursiveRemovalKind = Symbol("nonrecursive removal kind");
|
|
19
20
|
export function validateRemoveOptions(options) {
|
|
20
21
|
if (options.order !== undefined && options.order !== "filesystem" && options.order !== "sorted") {
|
|
21
22
|
throw new TypeError("remove order must be filesystem or sorted");
|
|
@@ -220,6 +221,9 @@ async function removeOne(root, targetPath, options, receipts) {
|
|
|
220
221
|
}
|
|
221
222
|
try {
|
|
222
223
|
const isDirectory = fsSync.lstatSync(targetPath).isDirectory();
|
|
224
|
+
if (!isDirectory && options[nonrecursiveRemovalKind] === "directory") {
|
|
225
|
+
throw new FsSafeError("path-mismatch", "store entry is no longer a directory");
|
|
226
|
+
}
|
|
223
227
|
assertFinalSymlinkRejected(targetPath, options.mutationSymlinks !== undefined);
|
|
224
228
|
assertNotAborted(options.signal);
|
|
225
229
|
options.assertBeforeMutation?.();
|
package/dist/root-walk.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type RootWalkEntry = RootWalkDataEntry | {
|
|
|
21
21
|
size: 0;
|
|
22
22
|
error: unknown;
|
|
23
23
|
};
|
|
24
|
-
export type RootWalkEntryFilter = (entry: RootWalkDataEntry) => RootWalkEntryFilterResult
|
|
24
|
+
export type RootWalkEntryFilter = (entry: RootWalkDataEntry) => RootWalkEntryFilterResult | Promise<RootWalkEntryFilterResult>;
|
|
25
25
|
export type RootWalkOptions = {
|
|
26
26
|
maxDepth?: number;
|
|
27
27
|
maxEntries?: number;
|
package/dist/root-walk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,
|
|
1
|
+
{"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAChC,KAAK,EAAE,iBAAiB,KACrB,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IAChC,aAAa,EAAE,qBAAqB,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CACF,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC,CAAC;AAqBF,wBAAuB,QAAQ,CAC7B,IAAI,EAAE,kBAAkB,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,aAAa,CAAC,CAmL/B"}
|
package/dist/root-walk.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
|
-
import { resolveRootPath } from "./root-path.js";
|
|
3
|
+
import { resolveRootPath, ROOT_PATH_ALIAS_POLICIES } from "./root-path.js";
|
|
4
4
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
5
5
|
function validateBudget(name, value) {
|
|
6
6
|
if (value === undefined)
|
|
@@ -65,12 +65,16 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
65
65
|
options.signal?.throwIfAborted();
|
|
66
66
|
let listing;
|
|
67
67
|
try {
|
|
68
|
+
const skipChildSymlinks = depth > 0 && options.symlinkPolicy === "skip";
|
|
68
69
|
const resolvedDirectory = await resolveRootPath({
|
|
69
70
|
absolutePath: path.resolve(root.rootReal, directory),
|
|
70
71
|
rootPath: root.rootReal,
|
|
71
72
|
rootCanonicalPath: root.rootReal,
|
|
72
73
|
boundaryLabel: "root walk",
|
|
74
|
+
policy: skipChildSymlinks ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
|
|
73
75
|
});
|
|
76
|
+
if (skipChildSymlinks && resolvedDirectory.kind === "symlink")
|
|
77
|
+
return;
|
|
74
78
|
if (!resolvedDirectory.exists || resolvedDirectory.kind !== "directory") {
|
|
75
79
|
throw new FsSafeError("not-file", `root walk path is not a directory: ${directory || "."}`);
|
|
76
80
|
}
|
|
@@ -140,7 +144,18 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
140
144
|
size = target.size;
|
|
141
145
|
}
|
|
142
146
|
const walkEntry = { relativePath: child, kind, size };
|
|
143
|
-
|
|
147
|
+
let filterResult = options.entryFilter?.(walkEntry) ?? "include";
|
|
148
|
+
if (typeof filterResult !== "string") {
|
|
149
|
+
filterResult = (await filterResult) ?? "include";
|
|
150
|
+
options.signal?.throwIfAborted();
|
|
151
|
+
try {
|
|
152
|
+
await listing.assertCurrent();
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
yield onDirectoryError(directory, error);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
144
159
|
if (!["include", "skip", "skip-subtree"].includes(filterResult)) {
|
|
145
160
|
throw new TypeError(`invalid root walk entryFilter result: ${String(filterResult)}`);
|
|
146
161
|
}
|
|
@@ -34,9 +34,7 @@ export type RootWritePathSelection = Readonly<{
|
|
|
34
34
|
export type RetainedRootWriteSelection = RootWritePathSelection & Readonly<{
|
|
35
35
|
identity: Readonly<Pick<BigIntStats, "dev" | "ino">>;
|
|
36
36
|
}>;
|
|
37
|
-
export declare function retainRootWriteSelection(owner: object, selection: RetainedRootWriteSelection): void;
|
|
38
37
|
export declare function createRootWriteSelectionForFd(selection: RootWritePathSelection, fd: number): RetainedRootWriteSelection;
|
|
39
|
-
export declare function takeRootWriteSelection(owner: object): RetainedRootWriteSelection | undefined;
|
|
40
38
|
export declare function assertRootWritePathSelectionSync(root: RootContext, selection: RootWritePathSelection): void;
|
|
41
39
|
export declare function assertRootWriteSelectionSync(root: RootContext, selection: RetainedRootWriteSelection, verifyBinding: boolean, fd?: number): void;
|
|
42
40
|
export declare function prepareRootWritePathSelection(params: Omit<RootWritePathSelection, "parentGuard"> & Readonly<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-write-admission.d.ts","sourceRoot":"","sources":["../src/root-write-admission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAA2B,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,sBAAsB,CAAC;AAQ9B,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,iCAAiC,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;CAClD,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC;AAE7E,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,SAAS,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,YAAY,EAAE,gBAAgB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,uBAAuB,CAAC,EAAE,gCAAgC,CAAC;CAC5D,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,iBAAiB,EAAE,4BAA4B,CAAC;IAChD,uBAAuB,EAAE,gCAAgC,CAAC;IAC1D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,sBAAsB,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG,sBAAsB,GAAG,QAAQ,CAAC;IACzE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;CACtD,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"root-write-admission.d.ts","sourceRoot":"","sources":["../src/root-write-admission.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import { FsSafeError } from "./errors.js";
|
|
|
6
6
|
import { isNotFoundPathError } from "./path.js";
|
|
7
7
|
import { assertNoPathAliasEscape } from "./path-policy.js";
|
|
8
8
|
import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy, } from "./pinned-mutation-admission.js";
|
|
9
|
-
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
9
|
+
import { admitPathInsideRoot, sameNormalizedPathSpelling } from "./root-boundary.js";
|
|
10
10
|
import { resolvePathInRoot } from "./root-context.js";
|
|
11
11
|
import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
|
|
12
12
|
import { inheritWriteTargetMode } from "./root-write-mode.js";
|
|
@@ -15,10 +15,6 @@ import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
|
15
15
|
import { realpathSync } from "./realpath.js";
|
|
16
16
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
17
17
|
import { assertPreparedRootWriteParentCurrent, } from "./root-write-complete-parent.js";
|
|
18
|
-
const retainedRootWriteSelections = new WeakMap();
|
|
19
|
-
export function retainRootWriteSelection(owner, selection) {
|
|
20
|
-
retainedRootWriteSelections.set(owner, selection);
|
|
21
|
-
}
|
|
22
18
|
export function createRootWriteSelectionForFd(selection, fd) {
|
|
23
19
|
const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
|
|
24
20
|
return Object.freeze({
|
|
@@ -26,11 +22,6 @@ export function createRootWriteSelectionForFd(selection, fd) {
|
|
|
26
22
|
identity: Object.freeze({ dev: stat.dev, ino: stat.ino }),
|
|
27
23
|
});
|
|
28
24
|
}
|
|
29
|
-
export function takeRootWriteSelection(owner) {
|
|
30
|
-
const selection = retainedRootWriteSelections.get(owner);
|
|
31
|
-
retainedRootWriteSelections.delete(owner);
|
|
32
|
-
return selection;
|
|
33
|
-
}
|
|
34
25
|
function writeSelectionChanged(cause) {
|
|
35
26
|
return new FsSafeError("path-mismatch", "write target changed during operation", {
|
|
36
27
|
cause: cause instanceof Error ? cause : undefined,
|
|
@@ -55,11 +46,6 @@ function inspectRegularSelectionPath(pathname, expected, follow) {
|
|
|
55
46
|
throw hardlinkedPathNotAllowedError();
|
|
56
47
|
return observed;
|
|
57
48
|
}
|
|
58
|
-
function sameNormalizedPathSpelling(left, right) {
|
|
59
|
-
// Windows can expose case-sensitive directories, so normalize separators
|
|
60
|
-
// and roots without using path.relative's case-folding comparison.
|
|
61
|
-
return path.resolve(left) === path.resolve(right);
|
|
62
|
-
}
|
|
63
49
|
function inspectSelectionBindingSync(root, selection, fd) {
|
|
64
50
|
inspectRegularSelectionPath(selection.selectedPath, selection.identity, false);
|
|
65
51
|
if (!sameNormalizedPathSpelling(selection.operationTargetPath, selection.selectedPath)) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function assertRootFallbackWritePath(expected: string | undefined, actual: string): void;
|
|
1
|
+
import type { RootBoundaryIdentity } from "./root-boundary.js";
|
|
3
2
|
export declare function withRootFallbackCompatibilityLock<T>(params: {
|
|
4
3
|
rootPath: string;
|
|
5
4
|
targetPath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-write-compatibility.d.ts","sourceRoot":"","sources":["../src/root-write-compatibility.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"root-write-compatibility.d.ts","sourceRoot":"","sources":["../src/root-write-compatibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAK/D,wBAAsB,iCAAiC,CAAC,CAAC,EACvD,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,EACxH,GAAG,EAAE,CAAC,OAAO,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,IAAI,CAAA;CAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3G,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
|
|
@@ -1,74 +1,23 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { FsSafeError } from "./errors.js";
|
|
4
|
-
import { isNotFoundPathError } from "./path.js";
|
|
5
|
-
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
6
1
|
import { withPinnedWriteRenameIdentityLock } from "./pinned-write.js";
|
|
7
|
-
import {
|
|
2
|
+
import { createRootWriteLockBinding } from "./root-write-lock-binding.js";
|
|
8
3
|
import { serializePathWrite } from "./write-queue.js";
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
//
|
|
16
|
-
function effectiveDestination(rootPath, targetPath, rootIdentity) {
|
|
17
|
-
let effective;
|
|
4
|
+
export async function withRootFallbackCompatibilityLock(params, run) {
|
|
5
|
+
const binding = createRootWriteLockBinding(params);
|
|
6
|
+
const callerAssertion = params.assertBeforeMutation;
|
|
7
|
+
const assertBeforeMutation = callerAssertion === undefined
|
|
8
|
+
? binding.assertCurrent
|
|
9
|
+
: () => { callerAssertion.call(params); binding.assertCurrent(); };
|
|
10
|
+
// Keep the alias queue and historical lock hash before admitting the writer.
|
|
18
11
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
for (;;) {
|
|
27
|
-
try {
|
|
28
|
-
fs.lstatSync(cursor);
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
if (!isNotFoundPathError(error) || cursor === rootPath)
|
|
33
|
-
throw error;
|
|
34
|
-
const parent = path.dirname(cursor);
|
|
35
|
-
if (parent === cursor)
|
|
36
|
-
throw error;
|
|
37
|
-
missing.unshift(path.basename(cursor));
|
|
38
|
-
cursor = parent;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// An existing dangling link must fail resolution, not become a missing
|
|
42
|
-
// lexical component. Keep the fallback bounded by the retained Root.
|
|
43
|
-
effective = path.join(realpathSync.native(cursor), ...missing);
|
|
12
|
+
return await serializePathWrite(binding.targetPath, async () => await withPinnedWriteRenameIdentityLock({
|
|
13
|
+
rootPath: params.rootPath, targetPath: binding.targetPath, relativeTargetPath: binding.relativeLockPath,
|
|
14
|
+
}, async () => await run({
|
|
15
|
+
targetPath: binding.targetPath,
|
|
16
|
+
relativePath: binding.relativePath,
|
|
17
|
+
assertBeforeMutation,
|
|
18
|
+
})));
|
|
44
19
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
throw unsupportedSpelling();
|
|
48
|
-
const relative = admitted.relativePath;
|
|
49
|
-
if (!relative || !relative.split(path.sep).every(component => /^[a-z0-9._-]+$/.test(component) && !component.endsWith(".")))
|
|
50
|
-
throw unsupportedSpelling();
|
|
51
|
-
return admitted.path;
|
|
52
|
-
}
|
|
53
|
-
export function assertRootFallbackWritePath(expected, actual) {
|
|
54
|
-
if (expected !== undefined && actual !== expected) {
|
|
55
|
-
throw new FsSafeError("path-mismatch", "compatibility write destination changed after lock selection");
|
|
20
|
+
finally {
|
|
21
|
+
binding.dispose();
|
|
56
22
|
}
|
|
57
23
|
}
|
|
58
|
-
export async function withRootFallbackCompatibilityLock(params, run) {
|
|
59
|
-
const targetPath = effectiveDestination(params.rootPath, params.targetPath, params.rootIdentity);
|
|
60
|
-
const relativePath = path.relative(params.rootPath, targetPath).split(path.sep).join("/");
|
|
61
|
-
const assertCurrent = () => assertRootFallbackWritePath(targetPath, effectiveDestination(params.rootPath, targetPath, params.rootIdentity));
|
|
62
|
-
// Sidecars are reentrant within one manager; aliases also need the same local
|
|
63
|
-
// queue before acquisition. The outer Root queue uses the caller's spelling.
|
|
64
|
-
return await serializePathWrite(targetPath, async () => await withPinnedWriteRenameIdentityLock({
|
|
65
|
-
rootPath: params.rootPath, targetPath, relativeTargetPath: relativePath,
|
|
66
|
-
}, async () => {
|
|
67
|
-
// The guarded open resolves again under the lock; each mutation rechecks
|
|
68
|
-
// below. Lock selection is never reused as post-lock filesystem authority.
|
|
69
|
-
return await run({
|
|
70
|
-
targetPath, relativePath,
|
|
71
|
-
assertBeforeMutation: () => { params.assertBeforeMutation?.(); assertCurrent(); },
|
|
72
|
-
});
|
|
73
|
-
}));
|
|
74
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-write-complete-parent.d.ts","sourceRoot":"","sources":["../src/root-write-complete-parent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAKxE,KAAK,8BAA8B,GAAG,QAAQ,CAC1C;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CACtC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,8BAA8B,CAAC;CACxC,CAAC,CAAC;AAEH,KAAK,2BAA2B,GAAG,QAAQ,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,sBAAsB,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,uBAAuB,CAAC;CAC1C,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"root-write-complete-parent.d.ts","sourceRoot":"","sources":["../src/root-write-complete-parent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAKxE,KAAK,8BAA8B,GAAG,QAAQ,CAC1C;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CACtC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,8BAA8B,CAAC;CACxC,CAAC,CAAC;AAEH,KAAK,2BAA2B,GAAG,QAAQ,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,sBAAsB,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,uBAAuB,CAAC;CAC1C,CAAC,CAAC;AA2FH,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,uBAAuB,EACjC,YAAY,UAAQ,GACnB,IAAI,CAqBN;AAwCD,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAsBhC"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { assertDirectoryIdentitySync, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
4
|
-
import { WINDOWS_RESERVED_DEVICE_NAMES } from "./device-path.js";
|
|
5
4
|
import { FsSafeError } from "./errors.js";
|
|
6
5
|
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
7
6
|
import { hasNodeErrorCode } from "./path.js";
|
|
8
7
|
import { realpathSync } from "./realpath.js";
|
|
9
|
-
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
8
|
+
import { admitPathInsideRoot, sameNormalizedPathSpelling } from "./root-boundary.js";
|
|
9
|
+
import { ordinaryWindowsSegments } from "./pinned-mutation-shared-route.js";
|
|
10
10
|
import { prepareRootWriteTarget } from "./root-directory-creation.js";
|
|
11
|
-
import {
|
|
11
|
+
import { canReuseParentWithMutationAssertion } from "./root-write-lock-binding.js";
|
|
12
12
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
13
13
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
14
14
|
function writeSelectionChanged(cause) {
|
|
@@ -16,30 +16,15 @@ function writeSelectionChanged(cause) {
|
|
|
16
16
|
cause: cause instanceof Error ? cause : undefined,
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
function sameNormalizedPathSpelling(left, right) {
|
|
20
|
-
// Windows can expose case-sensitive directories, so normalize separators
|
|
21
|
-
// and roots without using path.relative's case-folding comparison.
|
|
22
|
-
return path.resolve(left) === path.resolve(right);
|
|
23
|
-
}
|
|
24
|
-
function windowsReservedDeviceSegment(segment) {
|
|
25
|
-
const extension = segment.indexOf(".");
|
|
26
|
-
const stem = segment.slice(0, extension < 0 ? segment.length : extension).toUpperCase();
|
|
27
|
-
return WINDOWS_RESERVED_DEVICE_NAMES.has(stem);
|
|
28
|
-
}
|
|
29
19
|
function ordinarySharedWriteRoute(root, relativePath, operationTargetPath) {
|
|
30
20
|
if (process.versions.bun || relativePath === "" || relativePath.startsWith("~") ||
|
|
31
21
|
relativePath.includes("\0") || path.isAbsolute(relativePath))
|
|
32
22
|
return false;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (segments.some((segment) => segment === "" || segment === "." || segment === ".." ||
|
|
39
|
-
(process.platform === "win32" &&
|
|
40
|
-
(!isSafePathSegment(segment, { allowDotPrefix: true }) ||
|
|
41
|
-
segment.endsWith(".") || segment.endsWith(" ") ||
|
|
42
|
-
windowsReservedDeviceSegment(segment)))))
|
|
23
|
+
if (process.platform === "win32") {
|
|
24
|
+
if (!ordinaryWindowsSegments(relativePath))
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
else if (relativePath.includes("\\") || relativePath.split("/").some(segment => segment === "" || segment === "." || segment === ".."))
|
|
43
28
|
return false;
|
|
44
29
|
const routed = path.resolve(root.rootReal, relativePath);
|
|
45
30
|
const admitted = admitPathInsideRoot({
|
|
@@ -185,7 +170,8 @@ export async function prepareSharedRootWriteTarget(root, params) {
|
|
|
185
170
|
if (mutationAdmission)
|
|
186
171
|
await beforeParentAdmission?.(resolvedPath);
|
|
187
172
|
const preparedParent = params.mkdir !== false &&
|
|
188
|
-
params.assertBeforeMutation
|
|
173
|
+
canReuseParentWithMutationAssertion(params.assertBeforeMutation, root.rootReal, guardedTarget.targetPath) &&
|
|
174
|
+
beforeParentAdmission === undefined
|
|
189
175
|
? await prepareCompleteRootWriteParent(root, guardedTarget, params.relativePath)
|
|
190
176
|
: undefined;
|
|
191
177
|
const targetPath = params.mkdir === false
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type RootBoundaryIdentity } from "./root-boundary.js";
|
|
2
|
+
export declare function canReuseParentWithMutationAssertion(assertion: (() => void) | undefined, rootPath: string, targetPath: string): boolean;
|
|
3
|
+
export declare function assertRootFallbackWritePath(expected: string | undefined, actual: string): void;
|
|
4
|
+
export declare function createRootWriteLockBinding(params: Readonly<{
|
|
5
|
+
rootPath: string;
|
|
6
|
+
targetPath: string;
|
|
7
|
+
rootIdentity?: RootBoundaryIdentity;
|
|
8
|
+
}>): Readonly<{
|
|
9
|
+
targetPath: string;
|
|
10
|
+
relativePath: string;
|
|
11
|
+
relativeLockPath: string;
|
|
12
|
+
assertCurrent: () => void;
|
|
13
|
+
dispose(): void;
|
|
14
|
+
}>;
|
|
15
|
+
//# sourceMappingURL=root-write-lock-binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-write-lock-binding.d.ts","sourceRoot":"","sources":["../src/root-write-lock-binding.ts"],"names":[],"mappings":"AAKA,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAKpF,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,EACnC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAIT;AA6GD,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAI9F;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC,CAAC;;;;;;GAqCD"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
5
|
+
import { hasNodeErrorCode, isNotFoundPathError } from "./path.js";
|
|
6
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
7
|
+
import { realpathSync } from "./realpath.js";
|
|
8
|
+
const lockObservations = new WeakMap();
|
|
9
|
+
export function canReuseParentWithMutationAssertion(assertion, rootPath, targetPath) {
|
|
10
|
+
if (assertion === undefined)
|
|
11
|
+
return true;
|
|
12
|
+
const binding = lockObservations.get(assertion);
|
|
13
|
+
return binding?.rootPath === rootPath && binding.targetPath === targetPath;
|
|
14
|
+
}
|
|
15
|
+
function unsupportedSpelling() {
|
|
16
|
+
return new FsSafeError("path-alias", "Windows compatibility writes require lower-case ASCII destination components");
|
|
17
|
+
}
|
|
18
|
+
// Missing names have no portable case/Unicode lock key. Keep this admission
|
|
19
|
+
// narrower than general Root paths and never change keys after acquisition.
|
|
20
|
+
function effectiveDestination(rootPath, targetPath, rootIdentity) {
|
|
21
|
+
let effective;
|
|
22
|
+
let missingHint;
|
|
23
|
+
try {
|
|
24
|
+
effective = realpathSync.native(targetPath);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (!isNotFoundPathError(error))
|
|
28
|
+
throw error;
|
|
29
|
+
let cursor = targetPath;
|
|
30
|
+
const missing = [];
|
|
31
|
+
for (;;) {
|
|
32
|
+
try {
|
|
33
|
+
fs.lstatSync(cursor);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (!isNotFoundPathError(error) || cursor === rootPath)
|
|
38
|
+
throw error;
|
|
39
|
+
const parent = path.dirname(cursor);
|
|
40
|
+
if (parent === cursor)
|
|
41
|
+
throw error;
|
|
42
|
+
missing.unshift(path.basename(cursor));
|
|
43
|
+
cursor = parent;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Existing dangling links must fail resolution, not become missing names.
|
|
47
|
+
const canonicalParent = realpathSync.native(cursor);
|
|
48
|
+
effective = path.join(canonicalParent, ...missing);
|
|
49
|
+
// The hint must describe the selected route exactly. The full resolver can
|
|
50
|
+
// normalize a missing raw `..` component out of the eventual lock path.
|
|
51
|
+
if (missing.length > 1 && path.resolve(canonicalParent) === canonicalParent &&
|
|
52
|
+
missing.every(component => /^[a-z0-9._-]+$/.test(component) &&
|
|
53
|
+
component !== "." && component !== ".." && !component.endsWith("."))) {
|
|
54
|
+
missingHint = Object.freeze({ parentPath: canonicalParent, parts: Object.freeze(missing), nextIndex: 0 });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const admitted = admitPathInsideRoot({ rootPath, candidatePath: effective, rootIdentity });
|
|
58
|
+
if (!admitted)
|
|
59
|
+
throw unsupportedSpelling();
|
|
60
|
+
const relative = admitted.relativePath;
|
|
61
|
+
if (!relative || !relative.split(path.sep).every(component => /^[a-z0-9._-]+$/.test(component) && !component.endsWith(".")))
|
|
62
|
+
throw unsupportedSpelling();
|
|
63
|
+
return {
|
|
64
|
+
path: admitted.path,
|
|
65
|
+
// Case-folded or rebased root spellings keep the full resolver and its
|
|
66
|
+
// existing root-identity admission at every mutation.
|
|
67
|
+
missing: admitted.admission === "exact" && admitted.path === effective ? missingHint : undefined,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// The hint stores only a location. Each call freshly binds the nearest existing
|
|
71
|
+
// prefix and its first absent child; no identity or authorization crosses calls.
|
|
72
|
+
function observeMissingDestination(hint) {
|
|
73
|
+
// A complete parent needs only the ordinary leaf check, not a prefix fence.
|
|
74
|
+
if (hint.parts.length - hint.nextIndex < 2)
|
|
75
|
+
return undefined;
|
|
76
|
+
try {
|
|
77
|
+
let parentPath = hint.parentPath;
|
|
78
|
+
let parent = inspectDirectoryIdentitySync(parentPath);
|
|
79
|
+
for (let nextIndex = hint.nextIndex; nextIndex < hint.parts.length; nextIndex++) {
|
|
80
|
+
const childPath = path.join(parentPath, hint.parts[nextIndex]);
|
|
81
|
+
let child;
|
|
82
|
+
try {
|
|
83
|
+
child = fs.lstatSync(childPath, { bigint: true });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (!hasNodeErrorCode(error, "ENOENT"))
|
|
87
|
+
return undefined;
|
|
88
|
+
if (realpathSync.native(parentPath) !== parentPath)
|
|
89
|
+
return undefined;
|
|
90
|
+
inspectDirectoryIdentitySync(parentPath, parent);
|
|
91
|
+
try {
|
|
92
|
+
fs.lstatSync(childPath);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (!hasNodeErrorCode(error, "ENOENT"))
|
|
96
|
+
return undefined;
|
|
97
|
+
// Absence can follow a renamed ancestor through a new alias while
|
|
98
|
+
// preserving the directory's identity. Finish at its current name.
|
|
99
|
+
if (realpathSync.native(parentPath) !== parentPath)
|
|
100
|
+
return undefined;
|
|
101
|
+
return Object.freeze({ parentPath, parts: hint.parts, nextIndex });
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
// An existing leaf always receives the historical full resolution,
|
|
106
|
+
// including final links, collisions and unsupported canonical spellings.
|
|
107
|
+
if (nextIndex === hint.parts.length - 1)
|
|
108
|
+
return undefined;
|
|
109
|
+
parent = inspectDirectoryIdentitySync(childPath, undefined, child);
|
|
110
|
+
parentPath = childPath;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Removal, redirection and uncertain observations only discard the hint.
|
|
115
|
+
// The full resolver below still determines the destination and error.
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
export function assertRootFallbackWritePath(expected, actual) {
|
|
120
|
+
if (expected !== undefined && actual !== expected) {
|
|
121
|
+
throw new FsSafeError("path-mismatch", "compatibility write destination changed after lock selection");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function createRootWriteLockBinding(params) {
|
|
125
|
+
const { rootPath } = params;
|
|
126
|
+
const rootIdentity = params.rootIdentity && Object.freeze({
|
|
127
|
+
dev: params.rootIdentity.dev, ino: params.rootIdentity.ino,
|
|
128
|
+
});
|
|
129
|
+
const selected = effectiveDestination(rootPath, params.targetPath, rootIdentity);
|
|
130
|
+
const targetPath = selected.path;
|
|
131
|
+
let missingHint = selected.missing;
|
|
132
|
+
const relativePath = path.relative(rootPath, targetPath);
|
|
133
|
+
let active = true;
|
|
134
|
+
const assertCurrent = () => {
|
|
135
|
+
if (!active)
|
|
136
|
+
throw new FsSafeError("path-mismatch", "compatibility lock observation expired");
|
|
137
|
+
const observed = missingHint && observeMissingDestination(missingHint);
|
|
138
|
+
if (observed) {
|
|
139
|
+
missingHint = observed;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
missingHint = undefined;
|
|
143
|
+
const current = effectiveDestination(rootPath, targetPath, rootIdentity);
|
|
144
|
+
assertRootFallbackWritePath(targetPath, current.path);
|
|
145
|
+
missingHint = current.missing;
|
|
146
|
+
};
|
|
147
|
+
// Only this library-owned, observation-only closure permits parent reuse.
|
|
148
|
+
// Callers cannot obtain that status by setting properties on their callback.
|
|
149
|
+
lockObservations.set(assertCurrent, Object.freeze({ rootPath, targetPath }));
|
|
150
|
+
return Object.freeze({
|
|
151
|
+
targetPath,
|
|
152
|
+
relativePath,
|
|
153
|
+
// The existing lock protocol uses slashes; the writer needs native spelling.
|
|
154
|
+
relativeLockPath: relativePath.split(path.sep).join("/"),
|
|
155
|
+
assertCurrent,
|
|
156
|
+
dispose() {
|
|
157
|
+
active = false;
|
|
158
|
+
missingHint = undefined;
|
|
159
|
+
lockObservations.delete(assertCurrent);
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|