@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,105 @@
1
+ /**
2
+ * The rule for a tree whose links are KEPT: none of them may lead outside it.
3
+ *
4
+ * A copy that preserves links carries each link's text verbatim, so a link out of the tree
5
+ * arrives pointing at whatever stands at that path beside the COPY — the installing user's own
6
+ * files — and every reader of the copy that follows links reads them as the tree's content. So a
7
+ * tree that came from somewhere VAT does not own (a cloned repository, a directory an operator
8
+ * installs from) is checked here before anything reads it.
9
+ */
10
+ import { type FsSide } from '../errors/fs-fault.js';
11
+ import { type LinkChain, type PathFlavor } from './link-chain.js';
12
+ /** What {@link refuseEscapingLinks} checks, and how its refusal names it. */
13
+ export interface LinkContainmentCheck {
14
+ /** The tree no link may lead out of. */
15
+ readonly root: string;
16
+ /** Where in it to look; the whole of `root` when omitted. */
17
+ readonly subtree?: string;
18
+ /** A directory under `root` that is never entered (a clone's `.git`). */
19
+ readonly skip?: string;
20
+ /**
21
+ * Whether the tree is read where it stands, or will be RELOCATED (an install copies it, each
22
+ * link's text verbatim).
23
+ *
24
+ * - `in-place`: a link is judged by where it leads NOW. Its chain is read first, link by link,
25
+ * by the one resolver ({@link treeChain}), so nothing is resolved until every text on the way
26
+ * is: a network, absolute or drive-relative text anywhere on the way, a climb above the root,
27
+ * or a chain no kernel finishes, is refused. Then its real path must be inside. One that leads
28
+ * to nothing (a dangle, a cycle) reads nothing, so it is not refused.
29
+ * - `relocated`: where a link leads now proves nothing about where its text leads once the tree
30
+ * is somewhere else, so the text itself is walked ({@link linkTextRefusal}): relative, never
31
+ * above the root, through no other link, and ending at something that exists. Then the chain
32
+ * from it is read as in place, and only then is the kernel asked ({@link kernelRefusal}).
33
+ */
34
+ readonly placement: 'in-place' | 'relocated';
35
+ /** Which side the tree is on: every probe of it the OS refuses — a listing, an `lstat`, a `readlink`, a link's target — is that side's fault. */
36
+ readonly side: FsSide;
37
+ /** Whose kernel reads the tree: bound from `process.platform` at the edge (`HOST_PATH_FLAVOR`). */
38
+ readonly flavor: PathFlavor;
39
+ /** What the tree is, for the refusal: `the cloned repository`. */
40
+ readonly tree: string;
41
+ /** What a link must stay inside, for the refusal: `the clone`. */
42
+ readonly within: string;
43
+ /** What keeping the link would do, for the refusal — a sentence without its full stop. */
44
+ readonly consequence: string;
45
+ }
46
+ /**
47
+ * Where `candidate` stands relative to `root`, on real paths. The root itself counts as inside
48
+ * (`isUnderRoot` calls a root `outside` itself, which is right for a sink and wrong here).
49
+ *
50
+ * @param root - The tree
51
+ * @param candidate - A path in any spelling, existing or not
52
+ */
53
+ export declare function treeContainment(root: string, candidate: string): 'inside' | 'outside' | 'absent';
54
+ /**
55
+ * Where `path` leads in an UNTRUSTED tree, read link by link by the one resolver — `lstat` and
56
+ * `readlink` alone — before anything resolves it: an absolute, drive-relative or network text on
57
+ * the way (under either flavor: the tree may be read on the other host), a climb above `root`,
58
+ * or a chain longer than the hop limit that is not a cycle, is refused; a cycle leads to
59
+ * nothing, as a dangling link does; nothing outside `root` is probed.
60
+ *
61
+ * @param root - The tree, spelled as `path` spells it (its own components are never probed)
62
+ * @param path - A path below it (a link, or a subpath the operator named)
63
+ * @param flavor - Whose kernel will resolve it: bound from the host at the edge
64
+ * @throws The raw errno of an `lstat` or `readlink` the OS refused, for the caller to classify
65
+ */
66
+ export declare function treeChain(root: string, path: string, flavor: PathFlavor): LinkChain;
67
+ /**
68
+ * The last lock on a link of a tree that will be relocated: the KERNEL's resolution of it, at
69
+ * the source, must exist and be inside the tree. Asked only once the chain from the link has been
70
+ * read and shown local ({@link treeChain}). The walk of the text ({@link linkTextRefusal}) shows
71
+ * the text stays inside wherever the tree goes; this shows the walk's reading of the text is the
72
+ * kernel's. A probe that failed because the link resolves to nothing
73
+ * (`isUnresolvableLinkError`: it dangles, it is part of a cycle, Windows cannot follow its reparse
74
+ * data) is a refusal of the link on every host: nothing can be shown about where it leads. Every
75
+ * other failure — the OS declining (`EACCES`, `EPERM`), running short (`EMFILE`), held (`EBUSY`) or
76
+ * unable to look (`EIO`, `ETIMEDOUT`, Windows' ambiguous `UNKNOWN`) — says nothing about the link:
77
+ * a fault on the tree, thrown classified by the one write-lane catch body
78
+ * (`assertUnresolvableLink`, on the side the caller names in `check.side`, found as content).
79
+ *
80
+ * Exported apart from the walk so the lock can be shown to hold ALONE: with a correct walk, a
81
+ * link the kernel resolves outside never reaches it from a real tree.
82
+ *
83
+ * @param root - The tree, as its canonical path
84
+ * @param link - The link, spelled by its real directory
85
+ * @param text - The link's own text, for the refusal
86
+ * @returns Why the link is refused, after "is a symbolic link ", or `undefined` when the kernel agrees
87
+ * @throws FsFaultError on `check.side` for a probe that failed for any other reason; an error that carries no errno as it was
88
+ */
89
+ export declare function kernelRefusal(check: LinkContainmentCheck, root: string, link: string, text: string): string | undefined;
90
+ /**
91
+ * Refuse a tree holding a symbolic link that may not be kept: one leading outside it, and — for a
92
+ * tree that will be relocated — one whose text cannot be shown to stay inside wherever the tree
93
+ * goes ({@link LinkContainmentCheck.placement}). Every directory is walked at its REAL path, once: a
94
+ * link that stays inside is followed to the directory it names, so a link into a sibling cannot
95
+ * smuggle out a second hop, a link cycle ends, and a directory first reached through an alias still
96
+ * has its own links judged where they live.
97
+ *
98
+ * @param check - The tree, where in it to look, and how the refusal names it
99
+ * @throws VatError `COPY_LINK_ESCAPES_SOURCE` — the tree's own link, so the input's refusal —
100
+ * naming the first link found leading out; FsFaultError on `check.side`, found as content, for
101
+ * every probe of the tree the OS refused (a listing, an `lstat`, a `readlink`, a relocated link's
102
+ * target)
103
+ */
104
+ export declare function refuseEscapingLinks(check: LinkContainmentCheck): void;
105
+ //# sourceMappingURL=link-containment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-containment.d.ts","sourceRoot":"","sources":["../../src/tree-change/link-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAA0B,KAAK,MAAM,EAAmB,MAAM,uBAAuB,CAAC;AAQ7F,OAAO,EAAqB,KAAK,SAAS,EAAiB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGpG,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,SAAS,EAAE,UAAU,GAAG,WAAW,CAAC;IAC7C,iJAAiJ;IACjJ,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mGAAmG;IACnG,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAGhG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAEnF;AAyCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAavH;AAqCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAgBrE"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * The rule for a tree whose links are KEPT: none of them may lead outside it.
3
+ *
4
+ * A copy that preserves links carries each link's text verbatim, so a link out of the tree
5
+ * arrives pointing at whatever stands at that path beside the COPY — the installing user's own
6
+ * files — and every reader of the copy that follows links reads them as the tree's content. So a
7
+ * tree that came from somewhere VAT does not own (a cloned repository, a directory an operator
8
+ * installs from) is checked here before anything reads it.
9
+ */
10
+ import { readdirSync, readlinkSync } from 'node:fs';
11
+ import { dirname } from 'node:path';
12
+ import { fsFaultOf } from '../errors/errno-table.js';
13
+ import { assertUnresolvableLink, withFsFaultSync } from '../errors/fs-fault.js';
14
+ import { VatError } from '../errors/vat-error.js';
15
+ import { requireFollowedStatSync } from '../link-following.js';
16
+ import { canonicalPath, isUnderRoot } from '../path-containment.js';
17
+ import { safePath, toForwardSlash } from '../path-core.js';
18
+ import { COPY_LINK_ESCAPES_SOURCE_CODE } from './followed-walk.js';
19
+ import { hostPlaceKind, walkHostChain } from './host-chain.js';
20
+ import { MAX_LINK_HOPS } from './link-chain.js';
21
+ import { linkTextRefusal } from './link-walk.js';
22
+ /**
23
+ * Where `candidate` stands relative to `root`, on real paths. The root itself counts as inside
24
+ * (`isUnderRoot` calls a root `outside` itself, which is right for a sink and wrong here).
25
+ *
26
+ * @param root - The tree
27
+ * @param candidate - A path in any spelling, existing or not
28
+ */
29
+ export function treeContainment(root, candidate) {
30
+ if (canonicalPath(candidate, root) === canonicalPath(root, root))
31
+ return 'inside';
32
+ return isUnderRoot(root, candidate);
33
+ }
34
+ /**
35
+ * Where `path` leads in an UNTRUSTED tree, read link by link by the one resolver — `lstat` and
36
+ * `readlink` alone — before anything resolves it: an absolute, drive-relative or network text on
37
+ * the way (under either flavor: the tree may be read on the other host), a climb above `root`,
38
+ * or a chain longer than the hop limit that is not a cycle, is refused; a cycle leads to
39
+ * nothing, as a dangling link does; nothing outside `root` is probed.
40
+ *
41
+ * @param root - The tree, spelled as `path` spells it (its own components are never probed)
42
+ * @param path - A path below it (a link, or a subpath the operator named)
43
+ * @param flavor - Whose kernel will resolve it: bound from the host at the edge
44
+ * @throws The raw errno of an `lstat` or `readlink` the OS refused, for the caller to classify
45
+ */
46
+ export function treeChain(root, path, flavor) {
47
+ return walkHostChain(path, { flavor, root: safePath.resolve(root), outside: 'refuse' });
48
+ }
49
+ /** The refusal of the link at `link`, named relative to the tree as the filesystem spells it. */
50
+ function linkRefusal(check, link, what) {
51
+ return new VatError(COPY_LINK_ESCAPES_SOURCE_CODE, `Refusing ${check.tree}: ${safePath.relative(canonicalPath(check.root, check.root), link)} is a symbolic link ${what}. ` +
52
+ `${check.consequence} — replace the link with the files, or point it inside with a relative target.`);
53
+ }
54
+ /** How a refused chain reads, after "is a symbolic link "; `text` is the link's own. */
55
+ function describedChainRefusal(check, text, refusal) {
56
+ switch (refusal.why) {
57
+ case 'network':
58
+ case 'absolute':
59
+ case 'drive-relative':
60
+ return `whose target leads through an absolute path (${refusal.text}), which names a place outside ${check.within}`;
61
+ case 'climbs-out':
62
+ return `to a path outside ${check.within} (${text})`;
63
+ case 'hop-limit':
64
+ return `whose target starts a chain of more than ${MAX_LINK_HOPS.toString()} symbolic links that is not a cycle, so where it ends cannot be shown`;
65
+ case 'unportable':
66
+ return `whose target names a component no Windows host can spell (${refusal.text}): a NUL, or a name ending in a dot or a space that Windows strips`;
67
+ }
68
+ }
69
+ /** How each refusal of a link's text reads, after "is a symbolic link ". */
70
+ function describedRefusal(check, text, refusal) {
71
+ switch (refusal.why) {
72
+ case 'absolute':
73
+ return `with an absolute target (${text}), which would still point there once ${check.within} is copied`;
74
+ case 'climbs-out':
75
+ return `to a path outside ${check.within} (${text})`;
76
+ case 'through-link':
77
+ return `whose target passes through another symbolic link (${refusal.at}), so where ${text} leads depends on where ${check.within} is copied`;
78
+ case 'dangling':
79
+ return `to nothing (${refusal.at} does not exist), so where it would lead once ${check.within} is copied cannot be shown`;
80
+ }
81
+ }
82
+ /**
83
+ * The last lock on a link of a tree that will be relocated: the KERNEL's resolution of it, at
84
+ * the source, must exist and be inside the tree. Asked only once the chain from the link has been
85
+ * read and shown local ({@link treeChain}). The walk of the text ({@link linkTextRefusal}) shows
86
+ * the text stays inside wherever the tree goes; this shows the walk's reading of the text is the
87
+ * kernel's. A probe that failed because the link resolves to nothing
88
+ * (`isUnresolvableLinkError`: it dangles, it is part of a cycle, Windows cannot follow its reparse
89
+ * data) is a refusal of the link on every host: nothing can be shown about where it leads. Every
90
+ * other failure — the OS declining (`EACCES`, `EPERM`), running short (`EMFILE`), held (`EBUSY`) or
91
+ * unable to look (`EIO`, `ETIMEDOUT`, Windows' ambiguous `UNKNOWN`) — says nothing about the link:
92
+ * a fault on the tree, thrown classified by the one write-lane catch body
93
+ * (`assertUnresolvableLink`, on the side the caller names in `check.side`, found as content).
94
+ *
95
+ * Exported apart from the walk so the lock can be shown to hold ALONE: with a correct walk, a
96
+ * link the kernel resolves outside never reaches it from a real tree.
97
+ *
98
+ * @param root - The tree, as its canonical path
99
+ * @param link - The link, spelled by its real directory
100
+ * @param text - The link's own text, for the refusal
101
+ * @returns Why the link is refused, after "is a symbolic link ", or `undefined` when the kernel agrees
102
+ * @throws FsFaultError on `check.side` for a probe that failed for any other reason; an error that carries no errno as it was
103
+ */
104
+ export function kernelRefusal(check, root, link, text) {
105
+ try {
106
+ // Followed to the end, as a reader of the link would: this fails where the kernel finds nothing.
107
+ requireFollowedStatSync(link, root);
108
+ }
109
+ catch (error) {
110
+ const fault = fsFaultOf(error);
111
+ if (fault === undefined)
112
+ throw error;
113
+ // The action names the link: a refusal on its chain names the place refused, which may be its target.
114
+ assertUnresolvableLink(error, { side: check.side, origin: 'content', action: `resolve the symbolic link ${link}`, path: link });
115
+ return `to nothing (${text} does not resolve: ${fault.errno}), so where it would lead once ${check.within} is copied cannot be shown`;
116
+ }
117
+ // It resolves, so its canonical path is its real one.
118
+ return treeContainment(root, link) === 'inside' ? undefined : `to a path outside ${check.within} (${text} resolves outside it)`;
119
+ }
120
+ /**
121
+ * Judge the link at `link` under `check.placement` — spelled by its REAL directory, so its text is
122
+ * read from where the link is, never from an alias of that directory, which reads a climbing text
123
+ * as staying inside.
124
+ *
125
+ * Either way the CHAIN is read first ({@link treeChain}) and the kernel last, so nothing the text
126
+ * names is resolved until every text on the way has been read. Relocated, the text is walked
127
+ * through no link before that ({@link linkTextRefusal}) and the kernel is asked after it
128
+ * ({@link kernelRefusal}); in place, the kernel's real path must be inside.
129
+ */
130
+ function judgeLink(check, root, link) {
131
+ const text = readlinkSync(link);
132
+ if (check.placement === 'relocated') {
133
+ const below = safePath.relative(root, dirname(link));
134
+ const textRefusal = linkTextRefusal(below === '' ? [] : toForwardSlash(below).split('/'), text, (place) => hostPlaceKind(safePath.join(root, ...place)), check.flavor);
135
+ if (textRefusal !== undefined)
136
+ return { refusal: describedRefusal(check, text, textRefusal) };
137
+ }
138
+ const chain = treeChain(root, link, check.flavor);
139
+ if (chain.kind === 'refused')
140
+ return { refusal: describedChainRefusal(check, text, chain.refusal) };
141
+ if (check.placement === 'relocated') {
142
+ // Every text on the way was read and is local: the kernel may now be asked, as the last lock.
143
+ const refusal = kernelRefusal(check, root, link, text);
144
+ if (refusal !== undefined)
145
+ return { refusal };
146
+ }
147
+ else if (chain.kind === 'nothing') {
148
+ // A dangle or a cycle, every text read: in place it reads nothing.
149
+ return { enter: undefined };
150
+ }
151
+ else if (treeContainment(root, link) === 'outside') {
152
+ return { refusal: `to a path outside ${check.within}` };
153
+ }
154
+ return { enter: chain.kind === 'resolved' && chain.final === 'directory' ? canonicalPath(link, root) : undefined };
155
+ }
156
+ /**
157
+ * Refuse a tree holding a symbolic link that may not be kept: one leading outside it, and — for a
158
+ * tree that will be relocated — one whose text cannot be shown to stay inside wherever the tree
159
+ * goes ({@link LinkContainmentCheck.placement}). Every directory is walked at its REAL path, once: a
160
+ * link that stays inside is followed to the directory it names, so a link into a sibling cannot
161
+ * smuggle out a second hop, a link cycle ends, and a directory first reached through an alias still
162
+ * has its own links judged where they live.
163
+ *
164
+ * @param check - The tree, where in it to look, and how the refusal names it
165
+ * @throws VatError `COPY_LINK_ESCAPES_SOURCE` — the tree's own link, so the input's refusal —
166
+ * naming the first link found leading out; FsFaultError on `check.side`, found as content, for
167
+ * every probe of the tree the OS refused (a listing, an `lstat`, a `readlink`, a relocated link's
168
+ * target)
169
+ */
170
+ export function refuseEscapingLinks(check) {
171
+ withFsFaultSync({ side: check.side, origin: 'content', action: `examine the links of ${check.tree}`, path: check.subtree ?? check.root }, () => {
172
+ const skipped = check.skip === undefined ? undefined : canonicalPath(check.skip, check.root);
173
+ const visited = new Set();
174
+ const root = canonicalPath(check.root, check.root);
175
+ const pending = [canonicalPath(check.subtree ?? check.root, check.root)];
176
+ for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
177
+ if (dir === skipped || visited.has(dir))
178
+ continue;
179
+ visited.add(dir);
180
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
181
+ const entryPath = safePath.join(dir, entry.name);
182
+ const next = entry.isSymbolicLink() ? enterOrThrow(check, root, entryPath) : entry.isDirectory() && entryPath;
183
+ if (typeof next === 'string')
184
+ pending.push(next);
185
+ }
186
+ }
187
+ });
188
+ }
189
+ /** The directory a kept link leads to, for the walk to enter; throws the link's refusal. */
190
+ function enterOrThrow(check, root, link) {
191
+ const verdict = judgeLink(check, root, link);
192
+ if ('refusal' in verdict)
193
+ throw linkRefusal(check, link, verdict.refusal);
194
+ return verdict.enter;
195
+ }
196
+ //# sourceMappingURL=link-containment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-containment.js","sourceRoot":"","sources":["../../src/tree-change/link-containment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Where a symbolic link's text leads, decided by WALKING it — the rule for a link in a tree that
3
+ * will be RELOCATED (an install copies the tree, each link's text verbatim). Pure: the caller says
4
+ * what stands at each place.
5
+ *
6
+ * A lexical resolve cancels `x/..` without asking what `x` is, so `l -> up/../../../secret` beside
7
+ * `up -> ../..` reads as inside the tree while the kernel follows `up` first and lands three
8
+ * levels above it. And where such a link dangles at the source, its real path proves nothing: it
9
+ * resolves once the tree is copied somewhere those levels exist. So nothing is assumed:
10
+ *
11
+ * - the text is relative — an absolute one points at the same place wherever the copy lands;
12
+ * - walked one segment at a time from the link's own directory, the place never rises above the
13
+ * tree's root — not even to come back in by the tree's own name, which the copy may not keep;
14
+ * - no component the walk passes THROUGH is a link: a link may point at something, never through
15
+ * another link, whose own text decides where the rest of the walk goes;
16
+ * - the place it ends at exists. A link that dangles is refused, because nothing can be proved
17
+ * about where it lands after the copy.
18
+ *
19
+ * The walk is a MODEL of the kernel's resolution, and a model can be wrong (it once split a text
20
+ * on a backslash the POSIX kernel reads as part of a name). So its caller holds two more locks
21
+ * beside it — the one link-chain resolver (`link-chain.ts`), which reads the chain from the link to
22
+ * its end before anything follows it, and then the kernel's own resolution of the link at the
23
+ * source — and a link is kept only when all three agree.
24
+ */
25
+ import type { PathFlavor, PlaceKind } from './link-chain.js';
26
+ /** Why a link's text is refused, with the place (root-relative, `/`-joined) the walk stopped at. */
27
+ export type LinkTextRefusal = {
28
+ readonly why: 'absolute';
29
+ } | {
30
+ readonly why: 'climbs-out';
31
+ } | {
32
+ readonly why: 'through-link';
33
+ readonly at: string;
34
+ } | {
35
+ readonly why: 'dangling';
36
+ readonly at: string;
37
+ };
38
+ /**
39
+ * Walk a link's text from the directory the link is in.
40
+ *
41
+ * @param linkDir - The link's REAL directory, as segments below the tree's root (`[]`: the root itself)
42
+ * @param text - The link's text, verbatim
43
+ * @param kindAt - What stands at a place below the root, never following a link there
44
+ * @param flavor - Whose kernel resolves the link: its separators split the text
45
+ * @returns `undefined` when the text leads to something that exists inside the tree, by a walk
46
+ * that passes through no link; otherwise why it is refused
47
+ */
48
+ export declare function linkTextRefusal(linkDir: readonly string[], text: string, kindAt: (place: readonly string[]) => PlaceKind, flavor: PathFlavor): LinkTextRefusal | undefined;
49
+ //# sourceMappingURL=link-walk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-walk.d.ts","sourceRoot":"","sources":["../../src/tree-change/link-walk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE7D,oGAAoG;AACpG,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAgBtD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,eAAe,GAAG,SAAS,CAiB1K"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Where a symbolic link's text leads, decided by WALKING it — the rule for a link in a tree that
3
+ * will be RELOCATED (an install copies the tree, each link's text verbatim). Pure: the caller says
4
+ * what stands at each place.
5
+ *
6
+ * A lexical resolve cancels `x/..` without asking what `x` is, so `l -> up/../../../secret` beside
7
+ * `up -> ../..` reads as inside the tree while the kernel follows `up` first and lands three
8
+ * levels above it. And where such a link dangles at the source, its real path proves nothing: it
9
+ * resolves once the tree is copied somewhere those levels exist. So nothing is assumed:
10
+ *
11
+ * - the text is relative — an absolute one points at the same place wherever the copy lands;
12
+ * - walked one segment at a time from the link's own directory, the place never rises above the
13
+ * tree's root — not even to come back in by the tree's own name, which the copy may not keep;
14
+ * - no component the walk passes THROUGH is a link: a link may point at something, never through
15
+ * another link, whose own text decides where the rest of the walk goes;
16
+ * - the place it ends at exists. A link that dangles is refused, because nothing can be proved
17
+ * about where it lands after the copy.
18
+ *
19
+ * The walk is a MODEL of the kernel's resolution, and a model can be wrong (it once split a text
20
+ * on a backslash the POSIX kernel reads as part of a name). So its caller holds two more locks
21
+ * beside it — the one link-chain resolver (`link-chain.ts`), which reads the chain from the link to
22
+ * its end before anything follows it, and then the kernel's own resolution of the link at the
23
+ * source — and a link is kept only when all three agree.
24
+ */
25
+ /**
26
+ * What separates the components of a link's text is the answer of the kernel that resolves it,
27
+ * not VAT's. On POSIX only `/` does — a backslash is an ordinary character of a name, so `a\b` is
28
+ * one component, and reading it as two would count a `..` the kernel never takes.
29
+ */
30
+ const SEPARATOR = { posix: /\//, win32: /[/\\]/ };
31
+ /** A POSIX root, a UNC root or a drive prefix: absolute, or drive-relative, on some host. */
32
+ const ABSOLUTE = /^([/\\]|[A-Za-z]:)/;
33
+ /** The components of a link's text, as `flavor` splits it: `.` and empty ones dropped. */
34
+ function segmentsOf(text, flavor) {
35
+ return text.split(SEPARATOR[flavor]).filter((segment) => segment !== '' && segment !== '.');
36
+ }
37
+ /**
38
+ * Walk a link's text from the directory the link is in.
39
+ *
40
+ * @param linkDir - The link's REAL directory, as segments below the tree's root (`[]`: the root itself)
41
+ * @param text - The link's text, verbatim
42
+ * @param kindAt - What stands at a place below the root, never following a link there
43
+ * @param flavor - Whose kernel resolves the link: its separators split the text
44
+ * @returns `undefined` when the text leads to something that exists inside the tree, by a walk
45
+ * that passes through no link; otherwise why it is refused
46
+ */
47
+ export function linkTextRefusal(linkDir, text, kindAt, flavor) {
48
+ if (ABSOLUTE.test(text))
49
+ return { why: 'absolute' };
50
+ const segments = segmentsOf(text, flavor);
51
+ const place = [...linkDir];
52
+ for (const [index, segment] of segments.entries()) {
53
+ if (segment === '..') {
54
+ // Every place the walk stands at is a real directory of the tree, so its parent is `place` less one.
55
+ if (place.pop() === undefined)
56
+ return { why: 'climbs-out' };
57
+ continue;
58
+ }
59
+ place.push(segment);
60
+ const kind = kindAt(place);
61
+ const last = index === segments.length - 1;
62
+ if (kind === 'absent' || (!last && kind === 'file'))
63
+ return { why: 'dangling', at: place.join('/') };
64
+ if (!last && kind === 'link')
65
+ return { why: 'through-link', at: place.join('/') };
66
+ }
67
+ return undefined;
68
+ }
69
+ //# sourceMappingURL=link-walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-walk.js","sourceRoot":"","sources":["../../src/tree-change/link-walk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAWH;;;;GAIG;AACH,MAAM,SAAS,GAAyC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACxF,6FAA6F;AAC7F,MAAM,QAAQ,GAAG,oBAAoB,CAAC;AAEtC,0FAA0F;AAC1F,SAAS,UAAU,CAAC,IAAY,EAAE,MAAkB;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,CAAC,CAAC;AAC9F,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,OAA0B,EAAE,IAAY,EAAE,MAA+C,EAAE,MAAkB;IAC3I,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC;IACpD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;IAC3B,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QAClD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,qGAAqG;YACrG,IAAI,KAAK,CAAC,GAAG,EAAE,KAAK,SAAS;gBAAE,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC;YAC5D,SAAS;QACX,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAC3C,IAAI,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC;YAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACrG,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACpF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}