@vibe-agent-toolkit/utils 0.2.0-rc.10 → 0.2.0-rc.12

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 (330) hide show
  1. package/README.md +112 -10
  2. package/dist/archive/archive.d.ts +129 -0
  3. package/dist/archive/archive.d.ts.map +1 -0
  4. package/dist/archive/archive.js +223 -0
  5. package/dist/archive/archive.js.map +1 -0
  6. package/dist/archive/entries.d.ts +132 -0
  7. package/dist/archive/entries.d.ts.map +1 -0
  8. package/dist/archive/entries.js +223 -0
  9. package/dist/archive/entries.js.map +1 -0
  10. package/dist/archive/limits.d.ts +39 -0
  11. package/dist/archive/limits.d.ts.map +1 -0
  12. package/dist/archive/limits.js +59 -0
  13. package/dist/archive/limits.js.map +1 -0
  14. package/dist/archive/records.d.ts +14 -0
  15. package/dist/archive/records.d.ts.map +1 -0
  16. package/dist/archive/records.js +3 -0
  17. package/dist/archive/records.js.map +1 -0
  18. package/dist/archive/tar-reader.d.ts +18 -0
  19. package/dist/archive/tar-reader.d.ts.map +1 -0
  20. package/dist/archive/tar-reader.js +173 -0
  21. package/dist/archive/tar-reader.js.map +1 -0
  22. package/dist/archive/zip-reader.d.ts +76 -0
  23. package/dist/archive/zip-reader.d.ts.map +1 -0
  24. package/dist/archive/zip-reader.js +376 -0
  25. package/dist/archive/zip-reader.js.map +1 -0
  26. package/dist/archive.d.ts +14 -0
  27. package/dist/archive.d.ts.map +1 -0
  28. package/dist/archive.js +12 -0
  29. package/dist/archive.js.map +1 -0
  30. package/dist/asset-reference.d.ts +17 -1
  31. package/dist/asset-reference.d.ts.map +1 -1
  32. package/dist/asset-reference.js +31 -7
  33. package/dist/asset-reference.js.map +1 -1
  34. package/dist/dirent-kind.d.ts +13 -40
  35. package/dist/dirent-kind.d.ts.map +1 -1
  36. package/dist/dirent-kind.js +21 -69
  37. package/dist/dirent-kind.js.map +1 -1
  38. package/dist/entrypoint.d.ts +2 -2
  39. package/dist/entrypoint.d.ts.map +1 -1
  40. package/dist/entrypoint.js +4 -3
  41. package/dist/entrypoint.js.map +1 -1
  42. package/dist/env-flag.d.ts +13 -5
  43. package/dist/env-flag.d.ts.map +1 -1
  44. package/dist/env-flag.js +13 -5
  45. package/dist/env-flag.js.map +1 -1
  46. package/dist/errors/confirmed-absent.d.ts +41 -0
  47. package/dist/errors/confirmed-absent.d.ts.map +1 -0
  48. package/dist/errors/confirmed-absent.js +43 -0
  49. package/dist/errors/confirmed-absent.js.map +1 -0
  50. package/dist/errors/errno-table.d.ts +177 -0
  51. package/dist/errors/errno-table.d.ts.map +1 -0
  52. package/dist/errors/errno-table.js +343 -0
  53. package/dist/errors/errno-table.js.map +1 -0
  54. package/dist/errors/fs-boundary.d.ts +36 -0
  55. package/dist/errors/fs-boundary.d.ts.map +1 -0
  56. package/dist/errors/fs-boundary.js +121 -0
  57. package/dist/errors/fs-boundary.js.map +1 -0
  58. package/dist/errors/fs-fault.d.ts +176 -0
  59. package/dist/errors/fs-fault.d.ts.map +1 -0
  60. package/dist/errors/fs-fault.js +215 -0
  61. package/dist/errors/fs-fault.js.map +1 -0
  62. package/dist/errors/git-errno.d.ts +13 -0
  63. package/dist/errors/git-errno.d.ts.map +1 -0
  64. package/dist/errors/git-errno.js +45 -0
  65. package/dist/errors/git-errno.js.map +1 -0
  66. package/dist/errors/path-present.d.ts +72 -0
  67. package/dist/errors/path-present.d.ts.map +1 -0
  68. package/dist/errors/path-present.js +110 -0
  69. package/dist/errors/path-present.js.map +1 -0
  70. package/dist/errors/rag-index-empty.d.ts +16 -0
  71. package/dist/errors/rag-index-empty.d.ts.map +1 -0
  72. package/dist/errors/rag-index-empty.js +16 -0
  73. package/dist/errors/rag-index-empty.js.map +1 -0
  74. package/dist/errors/suppressed-faults.d.ts +34 -0
  75. package/dist/errors/suppressed-faults.d.ts.map +1 -0
  76. package/dist/errors/suppressed-faults.js +66 -0
  77. package/dist/errors/suppressed-faults.js.map +1 -0
  78. package/dist/file-crawler.d.ts +13 -18
  79. package/dist/file-crawler.d.ts.map +1 -1
  80. package/dist/file-crawler.js +140 -24
  81. package/dist/file-crawler.js.map +1 -1
  82. package/dist/fs-utils.d.ts +75 -50
  83. package/dist/fs-utils.d.ts.map +1 -1
  84. package/dist/fs-utils.js +148 -158
  85. package/dist/fs-utils.js.map +1 -1
  86. package/dist/fs.d.ts +5 -4
  87. package/dist/fs.d.ts.map +1 -1
  88. package/dist/fs.js +5 -4
  89. package/dist/fs.js.map +1 -1
  90. package/dist/git-snapshot.d.ts +25 -17
  91. package/dist/git-snapshot.d.ts.map +1 -1
  92. package/dist/git-snapshot.js +65 -11
  93. package/dist/git-snapshot.js.map +1 -1
  94. package/dist/git-tracker.d.ts +11 -3
  95. package/dist/git-tracker.d.ts.map +1 -1
  96. package/dist/git-tracker.js +29 -6
  97. package/dist/git-tracker.js.map +1 -1
  98. package/dist/git-utils.d.ts.map +1 -1
  99. package/dist/git-utils.js +7 -35
  100. package/dist/git-utils.js.map +1 -1
  101. package/dist/git.d.ts +1 -1
  102. package/dist/git.d.ts.map +1 -1
  103. package/dist/git.js +1 -1
  104. package/dist/git.js.map +1 -1
  105. package/dist/gitignore-checker.d.ts.map +1 -1
  106. package/dist/gitignore-checker.js +5 -4
  107. package/dist/gitignore-checker.js.map +1 -1
  108. package/dist/in-order.d.ts +93 -0
  109. package/dist/in-order.d.ts.map +1 -0
  110. package/dist/in-order.js +165 -0
  111. package/dist/in-order.js.map +1 -0
  112. package/dist/index.d.ts +20 -4
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +30 -4
  115. package/dist/index.js.map +1 -1
  116. package/dist/kill-process-tree.d.ts +43 -0
  117. package/dist/kill-process-tree.d.ts.map +1 -0
  118. package/dist/kill-process-tree.js +48 -0
  119. package/dist/kill-process-tree.js.map +1 -0
  120. package/dist/link-following.d.ts +115 -0
  121. package/dist/link-following.d.ts.map +1 -0
  122. package/dist/link-following.js +283 -0
  123. package/dist/link-following.js.map +1 -0
  124. package/dist/listing-refusal.d.ts +44 -0
  125. package/dist/listing-refusal.d.ts.map +1 -1
  126. package/dist/listing-refusal.js +79 -2
  127. package/dist/listing-refusal.js.map +1 -1
  128. package/dist/name-fold.d.ts +25 -0
  129. package/dist/name-fold.d.ts.map +1 -0
  130. package/dist/name-fold.js +40 -0
  131. package/dist/name-fold.js.map +1 -0
  132. package/dist/path-containment.d.ts +17 -0
  133. package/dist/path-containment.d.ts.map +1 -1
  134. package/dist/path-containment.js +28 -5
  135. package/dist/path-containment.js.map +1 -1
  136. package/dist/path-utils.d.ts +14 -17
  137. package/dist/path-utils.d.ts.map +1 -1
  138. package/dist/path-utils.js +37 -55
  139. package/dist/path-utils.js.map +1 -1
  140. package/dist/process.d.ts +2 -0
  141. package/dist/process.d.ts.map +1 -1
  142. package/dist/process.js +2 -0
  143. package/dist/process.js.map +1 -1
  144. package/dist/project-utils.d.ts.map +1 -1
  145. package/dist/project-utils.js +8 -9
  146. package/dist/project-utils.js.map +1 -1
  147. package/dist/safe-exec.d.ts +6 -2
  148. package/dist/safe-exec.d.ts.map +1 -1
  149. package/dist/safe-exec.js +1 -1
  150. package/dist/safe-exec.js.map +1 -1
  151. package/dist/skill-targets.d.ts.map +1 -1
  152. package/dist/skill-targets.js +3 -1
  153. package/dist/skill-targets.js.map +1 -1
  154. package/dist/skill-test/spawn-claude.d.ts +0 -15
  155. package/dist/skill-test/spawn-claude.d.ts.map +1 -1
  156. package/dist/skill-test/spawn-claude.js +11 -50
  157. package/dist/skill-test/spawn-claude.js.map +1 -1
  158. package/dist/special-file.d.ts +18 -0
  159. package/dist/special-file.d.ts.map +1 -0
  160. package/dist/special-file.js +21 -0
  161. package/dist/special-file.js.map +1 -0
  162. package/dist/terminating-signals.d.ts +129 -0
  163. package/dist/terminating-signals.d.ts.map +1 -0
  164. package/dist/terminating-signals.js +210 -0
  165. package/dist/terminating-signals.js.map +1 -0
  166. package/dist/test-helpers.d.ts +9 -5
  167. package/dist/test-helpers.d.ts.map +1 -1
  168. package/dist/test-helpers.js +18 -21
  169. package/dist/test-helpers.js.map +1 -1
  170. package/dist/test-scratch-guard.d.ts +34 -0
  171. package/dist/test-scratch-guard.d.ts.map +1 -0
  172. package/dist/test-scratch-guard.js +45 -0
  173. package/dist/test-scratch-guard.js.map +1 -0
  174. package/dist/testing/archive-fixtures.d.ts +147 -0
  175. package/dist/testing/archive-fixtures.d.ts.map +1 -0
  176. package/dist/testing/archive-fixtures.js +449 -0
  177. package/dist/testing/archive-fixtures.js.map +1 -0
  178. package/dist/testing/executables.d.ts +6 -0
  179. package/dist/testing/executables.d.ts.map +1 -1
  180. package/dist/testing/executables.js +20 -9
  181. package/dist/testing/executables.js.map +1 -1
  182. package/dist/testing/fault-fs-preload.d.ts +16 -0
  183. package/dist/testing/fault-fs-preload.d.ts.map +1 -0
  184. package/dist/testing/fault-fs-preload.js +22 -0
  185. package/dist/testing/fault-fs-preload.js.map +1 -0
  186. package/dist/testing/fault-fs.d.ts +115 -0
  187. package/dist/testing/fault-fs.d.ts.map +1 -0
  188. package/dist/testing/fault-fs.js +411 -0
  189. package/dist/testing/fault-fs.js.map +1 -0
  190. package/dist/testing/fault-spec.d.ts +83 -0
  191. package/dist/testing/fault-spec.d.ts.map +1 -0
  192. package/dist/testing/fault-spec.js +132 -0
  193. package/dist/testing/fault-spec.js.map +1 -0
  194. package/dist/testing/hostile-tree.d.ts.map +1 -1
  195. package/dist/testing/hostile-tree.js +3 -4
  196. package/dist/testing/hostile-tree.js.map +1 -1
  197. package/dist/testing/platform-gates.d.ts +8 -0
  198. package/dist/testing/platform-gates.d.ts.map +1 -1
  199. package/dist/testing/platform-gates.js +24 -0
  200. package/dist/testing/platform-gates.js.map +1 -1
  201. package/dist/testing/temp-dir.d.ts +31 -0
  202. package/dist/testing/temp-dir.d.ts.map +1 -1
  203. package/dist/testing/temp-dir.js +63 -0
  204. package/dist/testing/temp-dir.js.map +1 -1
  205. package/dist/testing/tier-timeout.d.ts +34 -0
  206. package/dist/testing/tier-timeout.d.ts.map +1 -0
  207. package/dist/testing/tier-timeout.js +45 -0
  208. package/dist/testing/tier-timeout.js.map +1 -0
  209. package/dist/testing/tree-snapshot.d.ts +71 -0
  210. package/dist/testing/tree-snapshot.d.ts.map +1 -0
  211. package/dist/testing/tree-snapshot.js +163 -0
  212. package/dist/testing/tree-snapshot.js.map +1 -0
  213. package/dist/testing.d.ts +6 -0
  214. package/dist/testing.d.ts.map +1 -1
  215. package/dist/testing.js +8 -1
  216. package/dist/testing.js.map +1 -1
  217. package/dist/text-content.d.ts +29 -0
  218. package/dist/text-content.d.ts.map +1 -1
  219. package/dist/text-content.js +64 -0
  220. package/dist/text-content.js.map +1 -1
  221. package/dist/text-file.d.ts +58 -6
  222. package/dist/text-file.d.ts.map +1 -1
  223. package/dist/text-file.js +111 -11
  224. package/dist/text-file.js.map +1 -1
  225. package/dist/text.d.ts +1 -1
  226. package/dist/text.d.ts.map +1 -1
  227. package/dist/text.js +1 -1
  228. package/dist/text.js.map +1 -1
  229. package/dist/timing-dump.d.ts.map +1 -1
  230. package/dist/timing-dump.js +2 -10
  231. package/dist/timing-dump.js.map +1 -1
  232. package/dist/tree-change/apply-decisions.d.ts +101 -0
  233. package/dist/tree-change/apply-decisions.d.ts.map +1 -0
  234. package/dist/tree-change/apply-decisions.js +139 -0
  235. package/dist/tree-change/apply-decisions.js.map +1 -0
  236. package/dist/tree-change/apply.d.ts +98 -0
  237. package/dist/tree-change/apply.d.ts.map +1 -0
  238. package/dist/tree-change/apply.js +364 -0
  239. package/dist/tree-change/apply.js.map +1 -0
  240. package/dist/tree-change/copy-decisions.d.ts +58 -0
  241. package/dist/tree-change/copy-decisions.d.ts.map +1 -0
  242. package/dist/tree-change/copy-decisions.js +66 -0
  243. package/dist/tree-change/copy-decisions.js.map +1 -0
  244. package/dist/tree-change/copy-tree.d.ts +125 -0
  245. package/dist/tree-change/copy-tree.d.ts.map +1 -0
  246. package/dist/tree-change/copy-tree.js +279 -0
  247. package/dist/tree-change/copy-tree.js.map +1 -0
  248. package/dist/tree-change/files.d.ts +167 -0
  249. package/dist/tree-change/files.d.ts.map +1 -0
  250. package/dist/tree-change/files.js +423 -0
  251. package/dist/tree-change/files.js.map +1 -0
  252. package/dist/tree-change/followed-walk.d.ts +54 -0
  253. package/dist/tree-change/followed-walk.d.ts.map +1 -0
  254. package/dist/tree-change/followed-walk.js +81 -0
  255. package/dist/tree-change/followed-walk.js.map +1 -0
  256. package/dist/tree-change/host-chain.d.ts +48 -0
  257. package/dist/tree-change/host-chain.d.ts.map +1 -0
  258. package/dist/tree-change/host-chain.js +85 -0
  259. package/dist/tree-change/host-chain.js.map +1 -0
  260. package/dist/tree-change/identity-compare.d.ts +61 -0
  261. package/dist/tree-change/identity-compare.d.ts.map +1 -0
  262. package/dist/tree-change/identity-compare.js +115 -0
  263. package/dist/tree-change/identity-compare.js.map +1 -0
  264. package/dist/tree-change/identity.d.ts +62 -0
  265. package/dist/tree-change/identity.d.ts.map +1 -0
  266. package/dist/tree-change/identity.js +152 -0
  267. package/dist/tree-change/identity.js.map +1 -0
  268. package/dist/tree-change/index.d.ts +24 -0
  269. package/dist/tree-change/index.d.ts.map +1 -0
  270. package/dist/tree-change/index.js +14 -0
  271. package/dist/tree-change/index.js.map +1 -0
  272. package/dist/tree-change/link-chain.d.ts +180 -0
  273. package/dist/tree-change/link-chain.d.ts.map +1 -0
  274. package/dist/tree-change/link-chain.js +377 -0
  275. package/dist/tree-change/link-chain.js.map +1 -0
  276. package/dist/tree-change/link-containment.d.ts +105 -0
  277. package/dist/tree-change/link-containment.d.ts.map +1 -0
  278. package/dist/tree-change/link-containment.js +196 -0
  279. package/dist/tree-change/link-containment.js.map +1 -0
  280. package/dist/tree-change/link-walk.d.ts +49 -0
  281. package/dist/tree-change/link-walk.d.ts.map +1 -0
  282. package/dist/tree-change/link-walk.js +69 -0
  283. package/dist/tree-change/link-walk.js.map +1 -0
  284. package/dist/tree-change/plan.d.ts +206 -0
  285. package/dist/tree-change/plan.d.ts.map +1 -0
  286. package/dist/tree-change/plan.js +320 -0
  287. package/dist/tree-change/plan.js.map +1 -0
  288. package/dist/tree-change/readable-tree.d.ts +44 -0
  289. package/dist/tree-change/readable-tree.d.ts.map +1 -0
  290. package/dist/tree-change/readable-tree.js +52 -0
  291. package/dist/tree-change/readable-tree.js.map +1 -0
  292. package/dist/tree-change/rollback-error.d.ts +29 -0
  293. package/dist/tree-change/rollback-error.d.ts.map +1 -0
  294. package/dist/tree-change/rollback-error.js +31 -0
  295. package/dist/tree-change/rollback-error.js.map +1 -0
  296. package/dist/tree-change/staging-names.d.ts +27 -0
  297. package/dist/tree-change/staging-names.d.ts.map +1 -0
  298. package/dist/tree-change/staging-names.js +40 -0
  299. package/dist/tree-change/staging-names.js.map +1 -0
  300. package/dist/tree-change/tree-walk.d.ts +81 -0
  301. package/dist/tree-change/tree-walk.d.ts.map +1 -0
  302. package/dist/tree-change/tree-walk.js +212 -0
  303. package/dist/tree-change/tree-walk.js.map +1 -0
  304. package/dist/win32-namespace.d.ts +24 -0
  305. package/dist/win32-namespace.d.ts.map +1 -0
  306. package/dist/win32-namespace.js +31 -0
  307. package/dist/win32-namespace.js.map +1 -0
  308. package/dist/yaml/surgical-yaml.d.ts +12 -3
  309. package/dist/yaml/surgical-yaml.d.ts.map +1 -1
  310. package/dist/yaml/surgical-yaml.js +16 -6
  311. package/dist/yaml/surgical-yaml.js.map +1 -1
  312. package/eslint/README.md +12 -4
  313. package/eslint/rules/fs-binding-scan.cjs +278 -0
  314. package/eslint/rules/no-adhoc-errno.cjs +364 -0
  315. package/eslint/rules/no-bare-symlink-in-tests.cjs +2 -3
  316. package/eslint/rules/no-blind-catch.cjs +1 -1
  317. package/eslint/rules/no-destructive-fs.cjs +359 -0
  318. package/eslint/rules/no-existssync.cjs +70 -0
  319. package/eslint/rules/no-fragile-entrypoint-guard.cjs +3 -2
  320. package/eslint/rules/no-link-following-fs.cjs +68 -0
  321. package/eslint/rules/no-literal-process-exit.cjs +96 -8
  322. package/eslint/rules/no-literal-test-timeout.cjs +214 -0
  323. package/eslint/rules/no-raw-signal-listener.cjs +224 -0
  324. package/eslint/rules/no-stdout-outside-writer.cjs +135 -0
  325. package/package.json +10 -2
  326. package/dist/errors/errno.d.ts +0 -51
  327. package/dist/errors/errno.d.ts.map +0 -1
  328. package/dist/errors/errno.js +0 -112
  329. package/dist/errors/errno.js.map +0 -1
  330. package/eslint/rules/no-fs-promises-cp.cjs +0 -43
@@ -0,0 +1,423 @@
1
+ /**
2
+ * The file-level half of the tree-change primitive: the staging names every
3
+ * change sits beside its destination under, the one rename (retried where Windows
4
+ * makes a rename race a scanner), the one removal (a read-only tree included), a
5
+ * whole-file replace that never truncates what it replaces, and a temp directory
6
+ * that is always disposed of.
7
+ */
8
+ import { rmSync } from 'node:fs';
9
+ import fs from 'node:fs/promises';
10
+ import { setTimeout as delay } from 'node:timers/promises';
11
+ import { requireConfirmedAbsent } from '../errors/confirmed-absent.js';
12
+ import { isAccessRefusedError, isAlreadyExistsError, isPathAbsentError, isRenameContentionError } from '../errors/errno-table.js';
13
+ import { classifyFsFault, everywhere, isFsFaultError, withFsFault } from '../errors/fs-fault.js';
14
+ import { recordSuppressedFault } from '../errors/suppressed-faults.js';
15
+ import { mapWithConcurrency } from '../in-order.js';
16
+ import { realpathNative, requireFollowedStat } from '../link-following.js';
17
+ import { isUnderRoot } from '../path-containment.js';
18
+ import { safePath, toForwardSlash } from '../path-core.js';
19
+ import { normalizedTmpdir } from '../path-utils.js';
20
+ import { entryInTheWayFault, renameRetryDelay, tempDirRefusal } from './apply-decisions.js';
21
+ import { stagingName } from './staging-names.js';
22
+ /** The owner's read, write and search bits: what a directory needs for its entries to be removed. */
23
+ const OWNER_RWX = 0o700;
24
+ export { TEMP_DIR_OUTSIDE_TMPDIR_CODE } from './apply-decisions.js';
25
+ function renameTrying(from, to, attempt) {
26
+ return fs.rename(from, to).catch(async (error) => {
27
+ const wait = renameRetryDelay(process.platform, attempt, isRenameContentionError(error));
28
+ if (wait === undefined)
29
+ throw error;
30
+ await delay(wait);
31
+ return renameTrying(from, to, attempt + 1);
32
+ });
33
+ }
34
+ /**
35
+ * Rename `from` to `to` — a file or a whole tree — in one step. Under win32 a
36
+ * rename the OS refuses with contention (`EPERM`, `EBUSY`, `EACCES`: a scanner,
37
+ * an indexer, a handle still closing) is retried, up to 6 tries with a backoff of
38
+ * 50·2ⁿ ms (1.55 s in all); any other errno, and any errno on another platform,
39
+ * is final.
40
+ *
41
+ * @param from - The entry to move
42
+ * @param to - Its new name, on the same filesystem
43
+ * @throws the rename's raw errno, for the caller to classify on the side it knows
44
+ */
45
+ export function renameFileAtomic(from, to) {
46
+ return renameTrying(from, to, 0);
47
+ }
48
+ /**
49
+ * Give `dir` the owner's rwx, then list the directories under it (links not followed). A
50
+ * directory already gone is nothing to grant: `rm`'s recursion removes siblings concurrently and
51
+ * goes on after it has rejected, so the walk can meet one it has just taken.
52
+ */
53
+ async function grantOwner(dir) {
54
+ try {
55
+ await fs.chmod(dir, OWNER_RWX);
56
+ const entries = await fs.readdir(dir, { withFileTypes: true });
57
+ return entries.filter((entry) => !entry.isSymbolicLink() && entry.isDirectory()).map((entry) => safePath.join(dir, entry.name));
58
+ }
59
+ catch (error) {
60
+ if (isPathAbsentError(error))
61
+ return [];
62
+ throw error;
63
+ }
64
+ }
65
+ /**
66
+ * Give every directory of `level` and below the owner's rwx, each before it is listed: one
67
+ * level at a time, at most `FS_CONCURRENCY` in flight however wide or deep the tree is.
68
+ */
69
+ async function grantOwnerWalk(level) {
70
+ if (level.length === 0)
71
+ return;
72
+ const next = await mapWithConcurrency(level, grantOwner);
73
+ await grantOwnerWalk(next.flat());
74
+ }
75
+ /**
76
+ * Remove `entry` — a tree, a file or a link (never what it points at) — whatever
77
+ * the modes under it: the root gets the owner's rwx first, and a removal still
78
+ * refused (`EACCES` / `EPERM`: a read-only directory deeper down refuses the
79
+ * removal of its entries) is retried once every directory under it has been given
80
+ * them too. Nothing there is not a failure. A root the OS will not chmod is still
81
+ * removed when the removal itself is allowed. Done means not there: a removal that
82
+ * resolved with the entry still present (`rm`'s `force` reading its own listing's
83
+ * ENOENT as "gone") is run once more, and a plain `rmdir` then throws why it stays.
84
+ *
85
+ * @throws the raw errno of the chmod, listing or removal that failed
86
+ */
87
+ export async function removeEntry(entry) {
88
+ if (!(await removeOnce(entry)))
89
+ return;
90
+ // `rm` with `force` takes an ENOENT from its own listing for "already gone" and resolves with the entry
91
+ // still there: done is what is not there. Once more; then the non-recursive removal names why it stays.
92
+ if (!(await present(entry)))
93
+ return;
94
+ await removeOnce(entry);
95
+ if (await present(entry))
96
+ await fs.rmdir(entry);
97
+ }
98
+ /**
99
+ * The `lstat` of `entry`, or `undefined` when nothing — a dangling link included — is there. "Nothing
100
+ * there" is believed only when the parent's listing agrees: an `lstat` answering ENOTDIR for a parked
101
+ * tree that WAS there read as "already gone", and the run exited 0 with the tree left behind.
102
+ */
103
+ async function examined(entry) {
104
+ try {
105
+ return await fs.lstat(entry);
106
+ }
107
+ catch (error) {
108
+ if (!isPathAbsentError(error))
109
+ throw error;
110
+ requireConfirmedAbsent(entry, error, { action: 'remove the entry', path: entry }, { follows: false, sideOf: everywhere('destination') });
111
+ return undefined;
112
+ }
113
+ }
114
+ /** Whether anything is at `entry` (see {@link examined}); a refusal is thrown. */
115
+ async function present(entry) {
116
+ return (await examined(entry)) !== undefined;
117
+ }
118
+ /** One removal pass (see {@link removeEntry}); `false` when nothing was there to remove. */
119
+ async function removeOnce(entry) {
120
+ const stats = await examined(entry);
121
+ if (stats === undefined)
122
+ return false;
123
+ const directory = stats.isDirectory();
124
+ // Best effort: a root the OS will not chmod may still be removable, and the walk below asks again.
125
+ if (directory)
126
+ await fs.chmod(entry, OWNER_RWX).catch((error_) => error_);
127
+ try {
128
+ await fs.rm(entry, { recursive: true, force: true });
129
+ }
130
+ catch (error) {
131
+ if (!directory || !isAccessRefusedError(error))
132
+ throw error;
133
+ await grantOwnerWalk([entry]);
134
+ await fs.rm(entry, { recursive: true, force: true });
135
+ }
136
+ return true;
137
+ }
138
+ /** Where a write to `dest` really lands: a link's target (so a dotfiles link stays a link), or `dest` itself. */
139
+ async function writeTarget(dest) {
140
+ try {
141
+ // The one native realpath rule (`realpathNative`): an unanswerable volume is asked again by the JS walk.
142
+ // `dest` is the path the verb itself names — the operator's own, so its own link is the edge.
143
+ const real = await realpathNative(dest, dest);
144
+ return { path: real, mode: (await requireFollowedStat(real, real)).mode & 0o7777 };
145
+ }
146
+ catch (error) {
147
+ if (isPathAbsentError(error))
148
+ return { path: dest, mode: undefined };
149
+ throw error;
150
+ }
151
+ }
152
+ /**
153
+ * Prove an existing file writable as it is, by opening it for writing — no truncation, no
154
+ * creation — and closing it. A rename over a file needs only its directory's permission, so
155
+ * without this a file its owner made read-only (or Windows marked read-only) would be replaced
156
+ * where a write in place is refused. An open, not `access`: on Windows `access` does not consult ACLs.
157
+ */
158
+ async function proveWritable(file) {
159
+ const handle = await fs.open(file, 'r+');
160
+ await handle.close();
161
+ }
162
+ /**
163
+ * Replace the file `dest` with `contents` in one step: the bytes go to a temp
164
+ * name beside it, which is then renamed over it ({@link renameFileAtomic}). A
165
+ * write that fails — a full disk mid-write — leaves `dest` byte-equal and the
166
+ * temp removed (a temp that will not go is recorded beside the thrown error:
167
+ * `suppressedFaultsOf`). An existing file keeps its mode, and must be writable as it is: one
168
+ * a write in place would be refused (a read-only file) is refused before anything is written,
169
+ * never replaced by the rename. A link is written through, so it stays a link to the replaced file.
170
+ *
171
+ * @param dest - The file to replace or create
172
+ * @param contents - Its new bytes
173
+ * @throws the raw errno, for the caller to classify on the side it knows
174
+ */
175
+ export async function replaceFile(dest, contents) {
176
+ const target = await writeTarget(dest);
177
+ if (target.mode !== undefined)
178
+ await proveWritable(target.path);
179
+ const temp = stagingName(target.path);
180
+ try {
181
+ const handle = await fs.open(temp, 'wx');
182
+ try {
183
+ await handle.writeFile(contents);
184
+ }
185
+ finally {
186
+ await handle.close();
187
+ }
188
+ if (target.mode !== undefined)
189
+ await fs.chmod(temp, target.mode);
190
+ await renameFileAtomic(temp, target.path);
191
+ }
192
+ catch (error) {
193
+ await fs.rm(temp, { force: true }).catch((error_) => {
194
+ recordSuppressedFault(error, classifyFsFault(error_, { side: 'destination', action: 'remove the temporary file', path: temp }));
195
+ });
196
+ throw error;
197
+ }
198
+ }
199
+ /** What is at `path`, never following a link; `undefined` when nothing is. A refusal is thrown raw. */
200
+ async function entryAt(path) {
201
+ try {
202
+ return await fs.lstat(path);
203
+ }
204
+ catch (error) {
205
+ if (isPathAbsentError(error))
206
+ return undefined;
207
+ throw error;
208
+ }
209
+ }
210
+ /**
211
+ * The directory `segments` name under `dir`, each component a REAL directory: made (a plain `mkdir`)
212
+ * when absent, refused when a link or a file stands there. One component at a time — each must be a
213
+ * directory before the next is looked at.
214
+ */
215
+ async function plainDirectoryUnder(dir, segments, writing) {
216
+ const [segment, ...rest] = segments;
217
+ if (segment === undefined)
218
+ return dir;
219
+ const next = safePath.join(dir, segment);
220
+ const there = await entryAt(next);
221
+ if (there === undefined)
222
+ await fs.mkdir(next);
223
+ else if (!there.isDirectory())
224
+ throw entryInTheWayFault(next, writing, undefined);
225
+ return plainDirectoryUnder(next, rest, writing);
226
+ }
227
+ /** The components of `relative`, a path under a tree's root. A `..` would leave the tree: a defect in the caller. */
228
+ function segmentsUnderRoot(relative) {
229
+ const segments = toForwardSlash(relative).split('/').filter((segment) => segment.length > 0 && segment !== '.');
230
+ if (segments.includes('..'))
231
+ throw new TypeError(`"${relative}" is not a path under the tree's root`);
232
+ return segments;
233
+ }
234
+ /**
235
+ * Make the directory `relative` under `root` — a tree VAT is building, which may hold whatever its
236
+ * source shipped, links included — without ever going through a link: every component must be a real
237
+ * directory (adopted when there, made by a plain `mkdir` when absent). What a recursive `mkdir`
238
+ * would do instead is follow a link standing where a component goes, out of the tree.
239
+ *
240
+ * @param root - The tree's root, which the caller made
241
+ * @param relative - The directory, relative to `root`, forward slashes
242
+ * @param writing - What the directory is for, for a refusal's message
243
+ * @returns The directory's path
244
+ * @throws FsFaultError side `source`, origin `content`, class `occupied`, naming the link or file
245
+ * standing where a directory goes; any other failure is the raw errno
246
+ */
247
+ export async function makeDirectoryUnder(root, relative, writing) {
248
+ return await plainDirectoryUnder(root, segmentsUnderRoot(relative), writing);
249
+ }
250
+ /**
251
+ * The ONE way VAT makes a file in a tree it is building — a staged tree that may hold whatever its
252
+ * source shipped, links included — without ever writing over or through what is already there.
253
+ *
254
+ * Every directory of `relative` under `root` must be a real directory (a link standing where one goes
255
+ * would carry the write outside the tree); a missing one is made by a plain `mkdir`. The file itself
256
+ * is made by `create`, which must be an EXCLUSIVE create (`wx`, which no link satisfies).
257
+ *
258
+ * @throws FsFaultError side `source`, origin `content`, class `occupied`, naming the entry in the
259
+ * way; any other failure is the raw errno, for the caller's boundary to classify
260
+ */
261
+ async function createUnder(root, relative, options, create) {
262
+ const segments = segmentsUnderRoot(relative);
263
+ const name = segments.at(-1);
264
+ if (name === undefined)
265
+ throw new TypeError(`"${relative}" names no file under the tree's root`);
266
+ const target = safePath.join(await plainDirectoryUnder(root, segments.slice(0, -1), options.writing), name);
267
+ if (options.existing === 'replace') {
268
+ const there = await entryAt(target);
269
+ if (there?.isFile() === true)
270
+ await fs.unlink(target);
271
+ }
272
+ try {
273
+ return await create(target);
274
+ }
275
+ catch (error) {
276
+ throw isAlreadyExistsError(error) ? entryInTheWayFault(target, options.writing, error) : error;
277
+ }
278
+ }
279
+ /**
280
+ * Write a file VAT makes into a tree it is building, never over or through what is already there
281
+ * (see {@link createUnder}: real directories only, an exclusive create, `existing` deciding the
282
+ * entry at the file's own name).
283
+ *
284
+ * @param root - The tree's root, which the caller made
285
+ * @param relative - The file, relative to `root`, forward slashes
286
+ * @param contents - Its bytes
287
+ * @param options - `existing`, and `writing`: what the file is, for a refusal's message
288
+ * @throws FsFaultError side `source`, origin `content`, class `occupied`, naming the entry in the
289
+ * way; any other failure is the raw errno, for the caller's boundary to classify
290
+ */
291
+ export async function writeFileUnder(root, relative, contents, options) {
292
+ await createUnder(root, relative, options, (target) => fs.writeFile(target, contents, { flag: 'wx' }));
293
+ }
294
+ /**
295
+ * {@link writeFileUnder} for bytes that are streamed: the file is made the same way and handed back
296
+ * OPEN, for the caller to fill and close. What the copy of one file into such a tree is built on.
297
+ *
298
+ * @returns The new file's handle, open for writing; the caller closes it
299
+ */
300
+ export function openFileUnder(root, relative, options) {
301
+ return createUnder(root, relative, options, (target) => fs.open(target, 'wx'));
302
+ }
303
+ /** The classified fault disposing of `dir` raised, or `undefined` once it is gone. A non-filesystem error is a defect and propagates. */
304
+ async function disposalFault(dir) {
305
+ try {
306
+ await removeEntry(dir);
307
+ return undefined;
308
+ }
309
+ catch (error) {
310
+ return classifiedDisposalFault(dir, error);
311
+ }
312
+ }
313
+ /** What a failed disposal of `dir` threw, classified; a non-filesystem error is a defect and is rethrown. */
314
+ function classifiedDisposalFault(dir, error) {
315
+ // The action names the directory: what is left is all of it, while the errno may name an entry deep inside.
316
+ const fault = classifyFsFault(error, { side: 'environment', action: `remove the temporary directory ${dir}`, path: dir });
317
+ if (!isFsFaultError(fault))
318
+ throw error;
319
+ return fault;
320
+ }
321
+ /**
322
+ * Remove a temporary directory, its modes notwithstanding — only one strictly
323
+ * under the temp directory (`normalizedTmpdir()`): this removal makes a read-only
324
+ * tree writable before it deletes it, so a wrong argument (an empty string, a
325
+ * project directory) must be refused before anything is touched. A directory
326
+ * elsewhere that VAT owns goes through a `remove` plan.
327
+ *
328
+ * @param dir - The directory
329
+ * @returns `undefined` when it is gone (or was never there); otherwise the leftover: the
330
+ * classified fault (an `FsFaultError`, side `environment`) whose message names the directory,
331
+ * for the caller to report as the one `TREE_CLEANUP_INCOMPLETE` warning beside its work
332
+ * @throws VatError {@link TEMP_DIR_OUTSIDE_TMPDIR_CODE} — a defect in the caller — for a
333
+ * directory not strictly under the temp directory, with nothing removed
334
+ */
335
+ export async function disposeTempDir(dir) {
336
+ refuseOutsideTmpdir(dir);
337
+ return await disposalFault(dir);
338
+ }
339
+ /** How many times {@link disposeTempDirSync} retries a transient refusal (`EBUSY`, `EPERM`, `ENOTEMPTY`, …). */
340
+ const SYNC_DISPOSAL_RETRIES = 4;
341
+ /** The first retry's delay; each further one waits this much longer (Node's linear backoff). */
342
+ const SYNC_DISPOSAL_RETRY_DELAY_MS = 40;
343
+ /**
344
+ * {@link disposeTempDir}, synchronously — for a process on its way OUT, where no promise will ever
345
+ * settle: a signal handler about to re-raise, an `exit` listener. Same refusal of any directory not
346
+ * strictly under the temp directory, same answer.
347
+ *
348
+ * ⚠️ No mode repair: it is for a directory the process filled with its own ordinary files. A
349
+ * SHORT bounded retry ({@link SYNC_DISPOSAL_RETRIES} × {@link SYNC_DISPOSAL_RETRY_DELAY_MS}, linear
350
+ * backoff, at most ~0.4 s) covers the one transient refusal a dying process meets: on Windows a
351
+ * child it has just terminated still holds a handle in the directory for a moment, and removal
352
+ * answers `EBUSY`/`EPERM` until the OS lets go. Anything still there after it is answered as the
353
+ * leftover fault.
354
+ *
355
+ * @param dir - The directory
356
+ * @returns `undefined` when it is gone (or was never there); otherwise the leftover fault
357
+ * @throws VatError {@link TEMP_DIR_OUTSIDE_TMPDIR_CODE} — a defect in the caller — for a
358
+ * directory not strictly under the temp directory, with nothing removed
359
+ */
360
+ export function disposeTempDirSync(dir) {
361
+ refuseOutsideTmpdir(dir);
362
+ try {
363
+ rmSync(dir, { recursive: true, force: true, maxRetries: SYNC_DISPOSAL_RETRIES, retryDelay: SYNC_DISPOSAL_RETRY_DELAY_MS });
364
+ return undefined;
365
+ }
366
+ catch (error) {
367
+ return classifiedDisposalFault(dir, error);
368
+ }
369
+ }
370
+ /** Refuse (a defect in the caller) a directory to dispose of that is not strictly under the temp directory. */
371
+ function refuseOutsideTmpdir(dir) {
372
+ const tmp = normalizedTmpdir();
373
+ const refusal = tempDirRefusal(dir, toForwardSlash(tmp), isUnderRoot(tmp, dir));
374
+ if (refusal !== undefined)
375
+ throw refusal;
376
+ }
377
+ /**
378
+ * Dispose of a temporary directory because the work that owned it failed — for a directory
379
+ * whose lifetime is not one call (one handed back to the caller on success, removed only on
380
+ * failure), where {@link withTempDir} cannot hold it. The failure stays the answer: a
381
+ * disposal fault is recorded beside it (`suppressedFaultsOf`), never thrown in its place.
382
+ *
383
+ * @param dir - The directory, strictly under the temp directory
384
+ * @param failure - What the work threw; the caller rethrows it
385
+ * @returns `undefined` when the directory went; otherwise the leftover fault, already recorded
386
+ * beside `failure` — for a caller that must also say so where no report will carry the failure
387
+ * @throws VatError {@link TEMP_DIR_OUTSIDE_TMPDIR_CODE} — a defect in the caller — for a
388
+ * directory not strictly under the temp directory, with nothing removed
389
+ */
390
+ export async function disposeTempDirAfterFailure(dir, failure) {
391
+ refuseOutsideTmpdir(dir);
392
+ const fault = await disposalFault(dir);
393
+ if (fault !== undefined)
394
+ recordSuppressedFault(failure, fault);
395
+ return fault;
396
+ }
397
+ /**
398
+ * Run `work` in a fresh directory under the temp dir (`normalizedTmpdir()`), and
399
+ * dispose of it after, whatever happened.
400
+ *
401
+ * The work's error always wins: rethrown unchanged — never wrapped, never given a
402
+ * cause, never mutated — with a disposal failure recorded beside it
403
+ * (`suppressedFaultsOf`), so a report still names the directory left behind.
404
+ * When the work succeeded, nothing is thrown: the value comes back with the
405
+ * disposal's `leftover` (see {@link TempDirOutcome}), for the caller to report.
406
+ *
407
+ * @param prefix - `mkdtemp` prefix, so a leaked directory names its owner
408
+ * @param work - Given the directory's forward-slash path
409
+ */
410
+ export async function withTempDir(prefix, work) {
411
+ const tmp = normalizedTmpdir();
412
+ const dir = toForwardSlash(await withFsFault({ side: 'environment', action: 'create a temporary directory', path: tmp }, () => fs.mkdtemp(safePath.join(tmp, prefix))));
413
+ let value;
414
+ try {
415
+ value = await work(dir);
416
+ }
417
+ catch (error) {
418
+ await disposeTempDirAfterFailure(dir, error);
419
+ throw error;
420
+ }
421
+ return { value, leftover: await disposalFault(dir) };
422
+ }
423
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/tree-change/files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The two refusals that bound a walk which FOLLOWS links: a link back into the
3
+ * walk ({@link FollowedWalk}, {@link DirectoryWalkRevisitedError}) and, for a walk
4
+ * contained to a root, a link out of it ({@link CopyLinkEscapesSourceError}).
5
+ * `proveTreeReadable` and `copyTree` under `links: 'follow-contained'` hold both;
6
+ * every other following walk holds a {@link FollowedWalk}.
7
+ */
8
+ import { type FsSide } from '../errors/fs-fault.js';
9
+ import { VatError } from '../errors/vat-error.js';
10
+ /** The `VatError` code of a {@link DirectoryWalkRevisitedError}. */
11
+ export declare const DIRECTORY_WALK_REVISITED_CODE = "DIRECTORY_WALK_REVISITED";
12
+ /** Thrown when a following walk is led back into a directory it has already entered. */
13
+ export declare class DirectoryWalkRevisitedError extends VatError {
14
+ constructor(dir: string, enteredAs: string);
15
+ }
16
+ /**
17
+ * The directories a FOLLOWING walk has entered, by realpath, so a link that
18
+ * leads back into the walk is refused instead of recursed until
19
+ * `ENAMETOOLONG`.
20
+ *
21
+ * A walk that follows links (`direntKindFollowing`) has no cycle guard by
22
+ * construction: `scripts/loop -> .` makes a copy create `dest/loop/loop/…`,
23
+ * writing every file at every level first, and a hashing walk do the same in
24
+ * memory. Every following walk holds one of these and calls {@link enter}
25
+ * on every directory it recurses into — the root included. A REVISIT is
26
+ * refused, not just a cycle: two links to one directory make the walk's
27
+ * output ambiguous (which spelling is the file's path?), and a refusal that
28
+ * names both spellings is the answer the author can act on.
29
+ */
30
+ export declare class FollowedWalk {
31
+ #private;
32
+ /**
33
+ * @param side - The side of the verb the walked tree is on: a refused realpath in {@link enter} is a fault there
34
+ * @param within - The tree the walk is in (the operator's own input): every link beneath it is read before the kernel resolves it
35
+ */
36
+ constructor(side: FsSide, within: string);
37
+ /**
38
+ * Record that the walk is entering `dir`. Synchronous on purpose — one
39
+ * realpath per directory entered is nothing beside the listing itself, and
40
+ * the sync and async walkers then share one guard.
41
+ *
42
+ * @param dir - A directory the walk is about to list, in any spelling
43
+ * @throws {DirectoryWalkRevisitedError} When its realpath was entered before
44
+ * @throws FsFaultError on the walk's side (origin `content`) when the OS refuses to resolve it
45
+ */
46
+ enter(dir: string): void;
47
+ }
48
+ /** The `VatError` code of a {@link CopyLinkEscapesSourceError}. */
49
+ export declare const COPY_LINK_ESCAPES_SOURCE_CODE = "COPY_LINK_ESCAPES_SOURCE";
50
+ /** Thrown when a link inside a tree walked with `links: 'follow-contained'` points outside it. */
51
+ export declare class CopyLinkEscapesSourceError extends VatError {
52
+ constructor(link: string, src: string);
53
+ }
54
+ //# sourceMappingURL=followed-walk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"followed-walk.d.ts","sourceRoot":"","sources":["../../src/tree-change/followed-walk.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAmB,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAIlD,oEAAoE;AACpE,eAAO,MAAM,6BAA6B,6BAA6B,CAAC;AAExE,wFAAwF;AACxF,qBAAa,2BAA4B,SAAQ,QAAQ;gBAC3C,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAM3C;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,YAAY;;IAKvB;;;OAGG;gBACS,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKxC;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAczB;AAED,mEAAmE;AACnE,eAAO,MAAM,6BAA6B,6BAA6B,CAAC;AAExE,kGAAkG;AAClG,qBAAa,0BAA2B,SAAQ,QAAQ;gBAC1C,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAQtC"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The two refusals that bound a walk which FOLLOWS links: a link back into the
3
+ * walk ({@link FollowedWalk}, {@link DirectoryWalkRevisitedError}) and, for a walk
4
+ * contained to a root, a link out of it ({@link CopyLinkEscapesSourceError}).
5
+ * `proveTreeReadable` and `copyTree` under `links: 'follow-contained'` hold both;
6
+ * every other following walk holds a {@link FollowedWalk}.
7
+ */
8
+ import { classifyFsFault } from '../errors/fs-fault.js';
9
+ import { VatError } from '../errors/vat-error.js';
10
+ import { toForwardSlash } from '../path-core.js';
11
+ import { normalizePath } from '../path-utils.js';
12
+ /** The `VatError` code of a {@link DirectoryWalkRevisitedError}. */
13
+ export const DIRECTORY_WALK_REVISITED_CODE = 'DIRECTORY_WALK_REVISITED';
14
+ /** Thrown when a following walk is led back into a directory it has already entered. */
15
+ export class DirectoryWalkRevisitedError extends VatError {
16
+ constructor(dir, enteredAs) {
17
+ super(DIRECTORY_WALK_REVISITED_CODE, `Refusing to enter ${dir}: it is the directory already walked as ${enteredAs} — a symlink leads the walk back into itself.`);
18
+ }
19
+ }
20
+ /**
21
+ * The directories a FOLLOWING walk has entered, by realpath, so a link that
22
+ * leads back into the walk is refused instead of recursed until
23
+ * `ENAMETOOLONG`.
24
+ *
25
+ * A walk that follows links (`direntKindFollowing`) has no cycle guard by
26
+ * construction: `scripts/loop -> .` makes a copy create `dest/loop/loop/…`,
27
+ * writing every file at every level first, and a hashing walk do the same in
28
+ * memory. Every following walk holds one of these and calls {@link enter}
29
+ * on every directory it recurses into — the root included. A REVISIT is
30
+ * refused, not just a cycle: two links to one directory make the walk's
31
+ * output ambiguous (which spelling is the file's path?), and a refusal that
32
+ * names both spellings is the answer the author can act on.
33
+ */
34
+ export class FollowedWalk {
35
+ #entered = new Map();
36
+ #side;
37
+ #within;
38
+ /**
39
+ * @param side - The side of the verb the walked tree is on: a refused realpath in {@link enter} is a fault there
40
+ * @param within - The tree the walk is in (the operator's own input): every link beneath it is read before the kernel resolves it
41
+ */
42
+ constructor(side, within) {
43
+ this.#side = side;
44
+ this.#within = within;
45
+ }
46
+ /**
47
+ * Record that the walk is entering `dir`. Synchronous on purpose — one
48
+ * realpath per directory entered is nothing beside the listing itself, and
49
+ * the sync and async walkers then share one guard.
50
+ *
51
+ * @param dir - A directory the walk is about to list, in any spelling
52
+ * @throws {DirectoryWalkRevisitedError} When its realpath was entered before
53
+ * @throws FsFaultError on the walk's side (origin `content`) when the OS refuses to resolve it
54
+ */
55
+ enter(dir) {
56
+ const real = toForwardSlash(this.#realpath(dir));
57
+ const before = this.#entered.get(real);
58
+ if (before !== undefined)
59
+ throw new DirectoryWalkRevisitedError(dir, before);
60
+ this.#entered.set(real, dir);
61
+ }
62
+ #realpath(dir) {
63
+ try {
64
+ return normalizePath(dir, this.#within);
65
+ }
66
+ catch (error) {
67
+ throw classifyFsFault(error, { side: this.#side, origin: 'content', action: `resolve ${dir}`, path: dir });
68
+ }
69
+ }
70
+ }
71
+ /** The `VatError` code of a {@link CopyLinkEscapesSourceError}. */
72
+ export const COPY_LINK_ESCAPES_SOURCE_CODE = 'COPY_LINK_ESCAPES_SOURCE';
73
+ /** Thrown when a link inside a tree walked with `links: 'follow-contained'` points outside it. */
74
+ export class CopyLinkEscapesSourceError extends VatError {
75
+ constructor(link, src) {
76
+ super(COPY_LINK_ESCAPES_SOURCE_CODE, `Refusing to copy ${link}: it is a symlink to a path outside ${src}. ` +
77
+ 'A copy follows links, so this would ship content the source tree does not own — ' +
78
+ 'replace the link with the files, or point it inside the tree.');
79
+ }
80
+ }
81
+ //# sourceMappingURL=followed-walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"followed-walk.js","sourceRoot":"","sources":["../../src/tree-change/followed-walk.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAe,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,oEAAoE;AACpE,MAAM,CAAC,MAAM,6BAA6B,GAAG,0BAA0B,CAAC;AAExE,wFAAwF;AACxF,MAAM,OAAO,2BAA4B,SAAQ,QAAQ;IACvD,YAAY,GAAW,EAAE,SAAiB;QACxC,KAAK,CACH,6BAA6B,EAC7B,qBAAqB,GAAG,2CAA2C,SAAS,+CAA+C,CAC5H,CAAC;IACJ,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,YAAY;IACd,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IACrC,KAAK,CAAS;IACd,OAAO,CAAS;IAEzB;;;OAGG;IACH,YAAY,IAAY,EAAE,MAAc;QACtC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAW;QACf,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,IAAI,2BAA2B,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC7E,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,SAAS,CAAC,GAAW;QACnB,IAAI,CAAC;YACH,OAAO,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,eAAe,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7G,CAAC;IACH,CAAC;CACF;AAED,mEAAmE;AACnE,MAAM,CAAC,MAAM,6BAA6B,GAAG,0BAA0B,CAAC;AAExE,kGAAkG;AAClG,MAAM,OAAO,0BAA2B,SAAQ,QAAQ;IACtD,YAAY,IAAY,EAAE,GAAW;QACnC,KAAK,CACH,6BAA6B,EAC7B,oBAAoB,IAAI,uCAAuC,GAAG,IAAI;YACpE,kFAAkF;YAClF,+DAA+D,CAClE,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The link-chain resolver ({@link walkLinkChain}) bound to this host's filesystem: `lstat` and
3
+ * `readlink`, and nothing that follows a link. Every call VAT makes that FOLLOWS a link — a
4
+ * `stat`, a `realpath` — reads the chain here first (`link-following.ts`, `assertLocalChain`), so
5
+ * no lane — a write lane, a read lane, an untrusted tree or the user's own — hands the kernel a
6
+ * chain whose every text has not been read.
7
+ */
8
+ import { VatError } from '../errors/vat-error.js';
9
+ import { type ChainRefusal, type ChainRule, type LinkChain, type PlaceKind } from './link-chain.js';
10
+ /**
11
+ * What stands at `path`, a link there not followed. A probe the OS refuses is thrown raw, for the
12
+ * caller to classify.
13
+ *
14
+ * ⚠️ "Not followed" is `lstat`'s word, and on Windows libuv keeps it only for what it calls a
15
+ * symbolic link: a junction to a volume (`\??\Volume{…}\`) and a reparse point of another tag
16
+ * (cloud files, dedup, WCI) are retried as a link-FOLLOWING `stat` and reported as the directory
17
+ * or file they lead to. Their targets are local — the kernel refuses a remote mount point — so no
18
+ * credential leaves the host, but the walk reads such a place as a plain directory where the
19
+ * kernel reparses it.
20
+ */
21
+ export declare function hostPlaceKind(path: string): PlaceKind;
22
+ /**
23
+ * Where `path` leads on this host's filesystem, under `rule` ({@link walkLinkChain}).
24
+ *
25
+ * @param path - Absolute, or relative to cwd; either separator
26
+ * @param rule - The flavor (bound from the host at the edge), the tree, and what the chain may do outside it
27
+ * @throws The raw errno of an `lstat` or `readlink` the OS refused, for the caller to classify
28
+ */
29
+ export declare function walkHostChain(path: string, rule: ChainRule): LinkChain;
30
+ /**
31
+ * A path VAT would not resolve: a link on the way to it leads to a network path, or the chain is
32
+ * longer than any kernel follows. A read lane records it as that one target's fault
33
+ * (`linkResolutionFault`); a lane that stops classifies it on its own side, like any refusal
34
+ * (`classifyFsFault`), so a destination's refused chain reads as the destination's.
35
+ */
36
+ export declare class LinkChainRefusedError extends VatError {
37
+ /** The path whose resolution was asked. */
38
+ readonly path: string;
39
+ /** Why, with the link whose text decided it. */
40
+ readonly refusal: ChainRefusal;
41
+ constructor(path: string, refusal: ChainRefusal);
42
+ }
43
+ /** Whether `error` is a {@link LinkChainRefusedError}, by its code (a `dist` copy never `instanceof`-matches a `src` one). */
44
+ export declare function isLinkChainRefusedError(error: unknown): error is VatError & {
45
+ readonly path: string;
46
+ readonly refusal: ChainRefusal;
47
+ };
48
+ //# sourceMappingURL=host-chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-chain.d.ts","sourceRoot":"","sources":["../../src/tree-change/host-chain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAc,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,EAAmB,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAiB,KAAK,SAAS,EAAiB,MAAM,iBAAiB,CAAC;AAEnJ;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CASrD;AAKD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,SAAS,CAEtE;AAkBD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,QAAQ;IACjD,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;gBACnB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY;CAKhD;AAED,8HAA8H;AAC9H,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAErI"}