@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.
Files changed (256) hide show
  1. package/CHANGELOG.md +64 -1
  2. package/README.md +10 -4
  3. package/dist/advanced.d.ts +1 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -1
  6. package/dist/archive-merge.d.ts.map +1 -1
  7. package/dist/archive-merge.js +113 -46
  8. package/dist/archive-read.d.ts.map +1 -1
  9. package/dist/archive-read.js +4 -4
  10. package/dist/archive-zip-directory.d.ts +4 -0
  11. package/dist/archive-zip-directory.d.ts.map +1 -1
  12. package/dist/archive-zip-directory.js +2 -0
  13. package/dist/archive-zip-entry.d.ts +6 -2
  14. package/dist/archive-zip-entry.d.ts.map +1 -1
  15. package/dist/archive-zip-entry.js +23 -8
  16. package/dist/archive-zip-integrity.d.ts.map +1 -1
  17. package/dist/archive-zip-integrity.js +3 -4
  18. package/dist/archive-zip-loader.d.ts.map +1 -1
  19. package/dist/archive-zip-loader.js +107 -31
  20. package/dist/archive-zip-names.d.ts +1 -0
  21. package/dist/archive-zip-names.d.ts.map +1 -1
  22. package/dist/archive-zip-names.js +6 -0
  23. package/dist/archive.js +6 -5
  24. package/dist/bounded-read-stream.d.ts +0 -1
  25. package/dist/bounded-read-stream.d.ts.map +1 -1
  26. package/dist/bounded-read-stream.js +0 -6
  27. package/dist/copy-publication.d.ts +6 -0
  28. package/dist/copy-publication.d.ts.map +1 -1
  29. package/dist/copy-publication.js +3 -0
  30. package/dist/copy-tree-portable.d.ts.map +1 -1
  31. package/dist/copy-tree-portable.js +44 -24
  32. package/dist/copy.d.ts.map +1 -1
  33. package/dist/copy.js +29 -11
  34. package/dist/directory-mode-owner.js +5 -5
  35. package/dist/file-handle-transfer.d.ts +2 -0
  36. package/dist/file-handle-transfer.d.ts.map +1 -1
  37. package/dist/file-handle-transfer.js +57 -2
  38. package/dist/file-identity.d.ts.map +1 -1
  39. package/dist/file-identity.js +18 -4
  40. package/dist/file-lock-sync-admission.d.ts +19 -0
  41. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  42. package/dist/file-lock-sync-admission.js +93 -0
  43. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  44. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  45. package/dist/file-lock-sync-root-acquire.js +370 -0
  46. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  47. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  48. package/dist/file-lock-sync-root-arbitration.js +66 -0
  49. package/dist/file-lock-sync-root-held.d.ts +34 -0
  50. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  51. package/dist/file-lock-sync-root-held.js +393 -0
  52. package/dist/file-lock-sync-root-io.d.ts +44 -0
  53. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  54. package/dist/file-lock-sync-root-io.js +209 -0
  55. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  56. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  57. package/dist/file-lock-sync-root-mutation.js +277 -0
  58. package/dist/file-lock-sync-root-options.d.ts +20 -0
  59. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  60. package/dist/file-lock-sync-root-options.js +58 -0
  61. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  62. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  63. package/dist/file-lock-sync-root-registration.js +90 -0
  64. package/dist/file-lock-sync-root.d.ts +36 -0
  65. package/dist/file-lock-sync-root.d.ts.map +1 -0
  66. package/dist/file-lock-sync-root.js +361 -0
  67. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  68. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  69. package/dist/file-lock-sync-stale-admission.js +205 -0
  70. package/dist/file-lock-sync.d.ts.map +1 -1
  71. package/dist/file-lock-sync.js +245 -205
  72. package/dist/file-store-prune.d.ts.map +1 -1
  73. package/dist/file-store-prune.js +5 -1
  74. package/dist/file-store-sync-directory.d.ts.map +1 -1
  75. package/dist/file-store-sync-directory.js +14 -7
  76. package/dist/file-store-sync-write.js +3 -3
  77. package/dist/file-store.d.ts.map +1 -1
  78. package/dist/file-store.js +47 -12
  79. package/dist/guest-dispatch-python.js +1 -1
  80. package/dist/json-document-store.d.ts.map +1 -1
  81. package/dist/json-document-store.js +22 -15
  82. package/dist/json-durable-queue-ownership.d.ts +0 -1
  83. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  84. package/dist/json-durable-queue-ownership.js +0 -6
  85. package/dist/native-binding.d.ts +8 -0
  86. package/dist/native-binding.d.ts.map +1 -1
  87. package/dist/native-parent-admission.d.ts +3 -2
  88. package/dist/native-parent-admission.d.ts.map +1 -1
  89. package/dist/native-parent-admission.js +24 -5
  90. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  91. package/dist/native-pinned-write-windows.js +7 -7
  92. package/dist/native-pinned-write.d.ts.map +1 -1
  93. package/dist/native-pinned-write.js +202 -172
  94. package/dist/native-policy-directory-observation.d.ts +12 -0
  95. package/dist/native-policy-directory-observation.d.ts.map +1 -0
  96. package/dist/native-policy-directory-observation.js +61 -0
  97. package/dist/native-policy-parent-windows.d.ts +14 -0
  98. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  99. package/dist/native-policy-parent-windows.js +200 -0
  100. package/dist/native-rename-outcome.d.ts +4 -0
  101. package/dist/native-rename-outcome.d.ts.map +1 -0
  102. package/dist/native-rename-outcome.js +8 -0
  103. package/dist/native-staged-file.d.ts +2 -2
  104. package/dist/native-staged-file.d.ts.map +1 -1
  105. package/dist/native-staged-file.js +42 -40
  106. package/dist/output.d.ts.map +1 -1
  107. package/dist/output.js +12 -8
  108. package/dist/path-prefix.d.ts.map +1 -1
  109. package/dist/path-prefix.js +30 -8
  110. package/dist/path-suffix-aliases.d.ts +2 -0
  111. package/dist/path-suffix-aliases.d.ts.map +1 -1
  112. package/dist/path-suffix-aliases.js +25 -17
  113. package/dist/permission-exec.d.ts +2 -0
  114. package/dist/permission-exec.d.ts.map +1 -1
  115. package/dist/permission-exec.js +150 -21
  116. package/dist/permissions-windows.js +1 -1
  117. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  118. package/dist/pinned-mutation-admission.js +23 -29
  119. package/dist/pinned-mutation-observation.d.ts +7 -2
  120. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  121. package/dist/pinned-mutation-observation.js +81 -18
  122. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  123. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  124. package/dist/pinned-mutation-shared-route.js +1 -1
  125. package/dist/pinned-write-types.d.ts +2 -0
  126. package/dist/pinned-write-types.d.ts.map +1 -1
  127. package/dist/private-temp-workspace.d.ts.map +1 -1
  128. package/dist/private-temp-workspace.js +75 -121
  129. package/dist/regular-file.d.ts.map +1 -1
  130. package/dist/regular-file.js +1 -15
  131. package/dist/replace-directory.d.ts.map +1 -1
  132. package/dist/replace-directory.js +256 -18
  133. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  134. package/dist/replace-file-copy-fallback.js +62 -70
  135. package/dist/replace-file-copy-source.d.ts.map +1 -1
  136. package/dist/replace-file-copy-source.js +10 -12
  137. package/dist/replace-file-temp-owner.d.ts +5 -2
  138. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  139. package/dist/replace-file-temp-owner.js +65 -30
  140. package/dist/replace-file.js +6 -6
  141. package/dist/retained-directory-replacement.d.ts +26 -0
  142. package/dist/retained-directory-replacement.d.ts.map +1 -0
  143. package/dist/retained-directory-replacement.js +193 -0
  144. package/dist/root-boundary.d.ts +1 -0
  145. package/dist/root-boundary.d.ts.map +1 -1
  146. package/dist/root-boundary.js +4 -0
  147. package/dist/root-context.d.ts +0 -8
  148. package/dist/root-context.d.ts.map +1 -1
  149. package/dist/root-context.js +0 -3
  150. package/dist/root-create-input.d.ts +6 -0
  151. package/dist/root-create-input.d.ts.map +1 -1
  152. package/dist/root-create-input.js +5 -1
  153. package/dist/root-directory-creation.d.ts.map +1 -1
  154. package/dist/root-directory-creation.js +5 -4
  155. package/dist/root-directory-list.d.ts +1 -0
  156. package/dist/root-directory-list.d.ts.map +1 -1
  157. package/dist/root-directory-list.js +1 -0
  158. package/dist/root-impl.d.ts.map +1 -1
  159. package/dist/root-impl.js +20 -11
  160. package/dist/root-move-noreplace.d.ts.map +1 -1
  161. package/dist/root-move-noreplace.js +2 -2
  162. package/dist/root-path-errors.d.ts +1 -0
  163. package/dist/root-path-errors.d.ts.map +1 -1
  164. package/dist/root-path-errors.js +11 -2
  165. package/dist/root-path-existing.d.ts.map +1 -1
  166. package/dist/root-path-existing.js +12 -37
  167. package/dist/root-path.js +1 -13
  168. package/dist/root-remove.d.ts +1 -0
  169. package/dist/root-remove.d.ts.map +1 -1
  170. package/dist/root-remove.js +4 -0
  171. package/dist/root-walk.d.ts +1 -1
  172. package/dist/root-walk.d.ts.map +1 -1
  173. package/dist/root-walk.js +17 -2
  174. package/dist/root-write-admission.d.ts +0 -2
  175. package/dist/root-write-admission.d.ts.map +1 -1
  176. package/dist/root-write-admission.js +1 -15
  177. package/dist/root-write-compatibility.d.ts +1 -2
  178. package/dist/root-write-compatibility.d.ts.map +1 -1
  179. package/dist/root-write-compatibility.js +17 -68
  180. package/dist/root-write-complete-parent.d.ts.map +1 -1
  181. package/dist/root-write-complete-parent.js +10 -24
  182. package/dist/root-write-lock-binding.d.ts +15 -0
  183. package/dist/root-write-lock-binding.d.ts.map +1 -0
  184. package/dist/root-write-lock-binding.js +162 -0
  185. package/dist/root-write-verification.d.ts.map +1 -1
  186. package/dist/root-write-verification.js +29 -42
  187. package/dist/secret-file.d.ts.map +1 -1
  188. package/dist/secret-file.js +2 -24
  189. package/dist/secret-read-async.d.ts.map +1 -1
  190. package/dist/secret-read-async.js +3 -24
  191. package/dist/secret-read-policy.d.ts +6 -2
  192. package/dist/secret-read-policy.d.ts.map +1 -1
  193. package/dist/secret-read-policy.js +26 -2
  194. package/dist/sibling-temp.d.ts.map +1 -1
  195. package/dist/sibling-temp.js +23 -12
  196. package/dist/sidecar-lock-acquire.d.ts +2 -28
  197. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  198. package/dist/sidecar-lock-acquire.js +288 -199
  199. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  200. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  201. package/dist/sidecar-lock-admission-context.js +60 -0
  202. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  203. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  204. package/dist/sidecar-lock-admission-parser.js +113 -0
  205. package/dist/sidecar-lock-admission.d.ts +35 -0
  206. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  207. package/dist/sidecar-lock-admission.js +7 -0
  208. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  209. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  210. package/dist/sidecar-lock-reclaim.js +80 -25
  211. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  212. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  213. package/dist/sidecar-lock-stale-admission.js +232 -0
  214. package/dist/sidecar-lock-target.d.ts +8 -0
  215. package/dist/sidecar-lock-target.d.ts.map +1 -0
  216. package/dist/sidecar-lock-target.js +55 -0
  217. package/dist/sidecar-lock.d.ts.map +1 -1
  218. package/dist/sidecar-lock.js +100 -16
  219. package/dist/staged-directory.d.ts +5 -2
  220. package/dist/staged-directory.d.ts.map +1 -1
  221. package/dist/staged-directory.js +37 -1
  222. package/dist/temp-workspace-admission.d.ts.map +1 -1
  223. package/dist/temp-workspace-admission.js +28 -4
  224. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  225. package/dist/temp-workspace-descriptor.js +9 -27
  226. package/dist/temp-workspace-owner.d.ts.map +1 -1
  227. package/dist/temp-workspace-owner.js +8 -8
  228. package/dist/walk.d.ts +5 -1
  229. package/dist/walk.d.ts.map +1 -1
  230. package/dist/walk.js +19 -6
  231. package/dist/windows-owner.d.ts.map +1 -1
  232. package/dist/windows-owner.js +2 -2
  233. package/docs/advanced.md +1 -1
  234. package/docs/archive.md +36 -9
  235. package/docs/atomic.md +85 -8
  236. package/docs/copy.md +35 -0
  237. package/docs/file-store.md +19 -0
  238. package/docs/guest.md +1 -1
  239. package/docs/json-store.md +5 -0
  240. package/docs/native-helper.md +10 -3
  241. package/docs/native.md +8 -0
  242. package/docs/output.md +6 -0
  243. package/docs/path-prefix.md +10 -0
  244. package/docs/path-suffix-aliases.md +51 -6
  245. package/docs/permissions.md +13 -0
  246. package/docs/public-api.md +3 -2
  247. package/docs/root.md +22 -3
  248. package/docs/sidecar-lock.md +109 -4
  249. package/docs/staged-file.md +7 -3
  250. package/docs/temp.md +20 -3
  251. package/docs/walk.md +67 -1
  252. package/docs/writing.md +11 -1
  253. package/package.json +10 -10
  254. package/dist/darwin-acl.d.ts +0 -4
  255. package/dist/darwin-acl.d.ts.map +0 -1
  256. 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
- async function pathExists(targetPath) {
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
- assertNoWindowsPathAlias(targetPath);
101
- const normalized = resolvePathPreservingWindowsRoot(targetPath);
102
- assertNoWindowsPathAlias(normalized);
103
- let cursor = normalized;
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
- !fs.existsSync(pathForWindowsFilesystem(cursor))) {
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 (!fs.existsSync(pathForWindowsFilesystem(cursor))) {
122
+ if (!exists(cursor)) {
148
123
  return normalized;
149
124
  }
150
125
  let rawResolvedAncestor;
151
126
  try {
152
- rawResolvedAncestor = realpathSync(pathForWindowsFilesystem(cursor));
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
- try {
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 {
@@ -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;AAsOD,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"}
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"}
@@ -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?.();
@@ -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;
@@ -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,CAAC,KAAK,EAAE,iBAAiB,KAAK,yBAAyB,CAAC;AAE1F,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,CAsK/B"}
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
- const filterResult = options.entryFilter?.(walkEntry) ?? "include";
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":"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"}
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 { type RootBoundaryIdentity } from "./root-boundary.js";
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":"AAIA,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AA6CpF,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAI9F;AAED,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
+ {"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 { realpathSync } from "./realpath.js";
2
+ import { createRootWriteLockBinding } from "./root-write-lock-binding.js";
8
3
  import { serializePathWrite } from "./write-queue.js";
9
- function unsupportedSpelling() {
10
- return new FsSafeError("path-alias", "Windows compatibility writes require lower-case ASCII destination components");
11
- }
12
- // The caller first applies the Root's ordinary path and mutation policies. This
13
- // extra admission rule is deliberately narrower than general Windows filenames:
14
- // the existing lock protocol has no portable case/Unicode equivalence key for a
15
- // missing name. Do not guess one, or switch keys when a placeholder appears.
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
- effective = realpathSync.native(targetPath);
20
- }
21
- catch (error) {
22
- if (!isNotFoundPathError(error))
23
- throw error;
24
- let cursor = targetPath;
25
- const missing = [];
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
- const admitted = admitPathInsideRoot({ rootPath, candidatePath: effective, rootIdentity });
46
- if (!admitted)
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;AA2GH,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,CAqBhC"}
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 { isSafePathSegment } from "./safe-path-segment.js";
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
- const separator = process.platform === "win32" ? "\\" : "/";
34
- const alternateSeparator = process.platform === "win32" ? "/" : "\\";
35
- if (relativePath.includes(alternateSeparator))
36
- return false;
37
- const segments = relativePath.split(separator);
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 === undefined && beforeParentAdmission === undefined
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
+ }