@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,163 @@
1
+ /**
2
+ * `snapshotTree` — a comparable, content-addressed picture of a directory tree.
3
+ *
4
+ * The fault matrix asks one question of every injected run: did the tree end
5
+ * byte-equal to BEFORE or to GOLDEN, never a third state? Answering it needs the
6
+ * trees as values: a map from relative path to what is there (kind, mode, content
7
+ * hash, link target), and a diff that names every difference in words.
8
+ *
9
+ * ## What it will not do
10
+ *
11
+ * - It never follows a link: a link is `{ kind: 'link', target }` and nothing under it is
12
+ * walked, so a snapshot cannot wander out of its root or loop.
13
+ * - It never opens a special file (FIFO, socket, device): `lstat` decides the kind first and
14
+ * only a regular file is opened, `O_NONBLOCK`, so a FIFO that slipped in between cannot hang it.
15
+ * - It never swallows an unreadable entry. A directory the OS refuses is a loud error naming
16
+ * it, because a gap in the picture would read as "unchanged".
17
+ *
18
+ * ⛔ Framework-free, like everything under `testing/`: no `vitest` import, so the `./testing`
19
+ * subpath keeps the empty third-party set its purity pin asserts.
20
+ */
21
+ import { createHash } from 'node:crypto';
22
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readdirSync, readFileSync, readlinkSync } from 'node:fs';
23
+ import { isPathAbsentError } from '../errors/errno-table.js';
24
+ import { safePath } from '../path-core.js';
25
+ import { decodeTextContent } from '../text-content.js';
26
+ const NON_BLOCKING = constants.O_NONBLOCK ?? 0;
27
+ const PERMISSION_BITS = 0o7777;
28
+ const ROOT_KEY = '.';
29
+ function unreadable(path, error) {
30
+ return new Error(`snapshotTree: cannot read ${path}: ${error.message}`, { cause: error });
31
+ }
32
+ /** The file's content as it is hashed: byte rewrites first, then (decoded once) text rewrites. */
33
+ function rewritten(content, key, rewrites) {
34
+ const applicable = rewrites.filter((rule) => rule.applies(key));
35
+ let bytes = content;
36
+ for (const rule of applicable)
37
+ if ('rewriteBytes' in rule)
38
+ bytes = rule.rewriteBytes(bytes);
39
+ const textRules = applicable.filter((rule) => 'rewrite' in rule);
40
+ return textRules.length === 0 ? bytes : textRules.reduce((text, rule) => rule.rewrite(text), decodeTextContent(bytes).text);
41
+ }
42
+ function hashFile(path, key, rewrites) {
43
+ let fd;
44
+ try {
45
+ fd = openSync(path, constants.O_RDONLY | NON_BLOCKING);
46
+ }
47
+ catch (error) {
48
+ if (isPathAbsentError(error))
49
+ return undefined;
50
+ throw unreadable(path, error);
51
+ }
52
+ try {
53
+ const stats = fstatSync(fd);
54
+ if (!stats.isFile())
55
+ return undefined;
56
+ return { mode: stats.mode & PERMISSION_BITS, sha256: createHash('sha256').update(rewritten(readFileSync(fd), key, rewrites)).digest('hex') };
57
+ }
58
+ catch (error) {
59
+ throw unreadable(path, error);
60
+ }
61
+ finally {
62
+ closeSync(fd);
63
+ }
64
+ }
65
+ function linkTarget(path) {
66
+ try {
67
+ return readlinkSync(path);
68
+ }
69
+ catch (error) {
70
+ throw unreadable(path, error);
71
+ }
72
+ }
73
+ /** The entry at `path`, or undefined when it vanished between listing and looking. */
74
+ function entryAt(path, key, rewrites) {
75
+ let stats;
76
+ try {
77
+ stats = lstatSync(path);
78
+ }
79
+ catch (error) {
80
+ if (isPathAbsentError(error))
81
+ return undefined;
82
+ throw unreadable(path, error);
83
+ }
84
+ if (stats.isSymbolicLink())
85
+ return { kind: 'link', target: linkTarget(path) };
86
+ if (stats.isDirectory())
87
+ return { kind: 'dir', mode: stats.mode & PERMISSION_BITS };
88
+ if (!stats.isFile())
89
+ return { kind: 'special' };
90
+ const hashed = hashFile(path, key, rewrites);
91
+ return hashed === undefined ? { kind: 'special' } : { kind: 'file', ...hashed };
92
+ }
93
+ function walk(path, key, rewrites, out) {
94
+ const entry = entryAt(path, key, rewrites);
95
+ if (entry === undefined)
96
+ return;
97
+ out.set(key, entry);
98
+ if (entry.kind !== 'dir')
99
+ return;
100
+ let names;
101
+ try {
102
+ names = readdirSync(path);
103
+ }
104
+ catch (error) {
105
+ throw unreadable(path, error);
106
+ }
107
+ for (const name of names) {
108
+ walk(safePath.join(path, name), key === ROOT_KEY ? name : `${key}/${name}`, rewrites, out);
109
+ }
110
+ }
111
+ /**
112
+ * Snapshot the tree at `root`.
113
+ *
114
+ * @param root - A directory, a file or a link; absent yields the empty map
115
+ * @param options - Content rewrites applied before hashing; a key prefix for merging roots
116
+ * @returns Entries in walk order; compare with {@link diffSnapshots}
117
+ */
118
+ export function snapshotTree(root, options = {}) {
119
+ const out = new Map();
120
+ walk(root, options.keyPrefix ?? ROOT_KEY, options.rewrites ?? [], out);
121
+ return out;
122
+ }
123
+ function describeEntry(entry) {
124
+ switch (entry.kind) {
125
+ case 'file': return `file mode ${entry.mode.toString(8)} sha256 ${entry.sha256.slice(0, 12)}`;
126
+ case 'dir': return `dir mode ${entry.mode.toString(8)}`;
127
+ case 'link': return `link -> ${entry.target}`;
128
+ case 'special': return 'special file';
129
+ }
130
+ }
131
+ /** Everything an entry is compared by: what a diff line prints shows only the head of a file's hash. */
132
+ function stateOf(entry) {
133
+ return entry.kind === 'file' ? `${describeEntry(entry)} ${entry.sha256}` : describeEntry(entry);
134
+ }
135
+ function compareKeys(x, y) {
136
+ if (x === y)
137
+ return 0;
138
+ return x < y ? -1 : 1;
139
+ }
140
+ /**
141
+ * Every difference between two snapshots, in words: `+ key (…)` added, `- key (…)`
142
+ * removed, `~ key: … => …` changed. Sorted by key; empty means identical.
143
+ */
144
+ export function diffSnapshots(a, b) {
145
+ const lines = [];
146
+ for (const [key, before] of a) {
147
+ const after = b.get(key);
148
+ if (after === undefined)
149
+ lines.push({ key, line: `- ${key} (${describeEntry(before)})` });
150
+ else if (stateOf(before) !== stateOf(after))
151
+ lines.push({ key, line: `~ ${key}: ${describeEntry(before)} => ${describeEntry(after)}` });
152
+ }
153
+ for (const [key, after] of b) {
154
+ if (!a.has(key))
155
+ lines.push({ key, line: `+ ${key} (${describeEntry(after)})` });
156
+ }
157
+ return lines.toSorted((x, y) => compareKeys(x.key, y.key)).map((item) => item.line);
158
+ }
159
+ /** The entries at `prefix` and under it (keys unchanged); a sibling sharing the spelling is not under it. */
160
+ export function subtree(s, prefix) {
161
+ return new Map([...s].filter(([key]) => key === prefix || key.startsWith(`${prefix}/`)));
162
+ }
163
+ //# sourceMappingURL=tree-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-snapshot.js","sourceRoot":"","sources":["../../src/testing/tree-snapshot.ts"],"names":[],"mappings":"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"}
package/dist/testing.d.ts CHANGED
@@ -21,10 +21,16 @@
21
21
  import type { UnreadablePolicy } from './listing-refusal.js';
22
22
  export * from './test-helpers.js';
23
23
  export { __readCrawlTimingSnapshot, __setCrawlTimingForTest, __writeCrawlTimingDumpForTest, } from './crawl-timing.js';
24
+ export * from './testing/archive-fixtures.js';
24
25
  export * from './testing/executables.js';
26
+ export * from './testing/fault-fs.js';
27
+ export { faultFsSpecOf, faultRuleOf, INJECTED_ERRNOS, injectedErrnoError } from './testing/fault-spec.js';
28
+ export type { FaultFsSpec, FaultRule, FaultSpec, FsApi, FsOpFamily, InjectedErrno } from './testing/fault-spec.js';
25
29
  export * from './testing/hostile-tree.js';
26
30
  export * from './testing/platform-gates.js';
27
31
  export * from './testing/temp-dir.js';
32
+ export * from './testing/tier-timeout.js';
33
+ export * from './testing/tree-snapshot.js';
28
34
  /** A planted fixture tree and the means to remove it. */
29
35
  export interface TempCorpus {
30
36
  /** Absolute path to the tree's root. */
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAO7D,cAAc,mBAAmB,CAAC;AAIlC,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,mBAAmB,CAAC;AAG3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AAEtC,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,UAAU,CAMZ;AAED,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,wDAAwD;IACxD,IAAI,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,iBAAiB,CAyBnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAEtE"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAO7D,cAAc,mBAAmB,CAAC;AAIlC,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,mBAAmB,CAAC;AAG3B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC1G,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACnH,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAE3C,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,UAAU,CAMZ;AAED,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,wDAAwD;IACxD,IAAI,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,iBAAiB,CAyBnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAEtE"}
package/dist/testing.js CHANGED
@@ -31,10 +31,17 @@ export * from './test-helpers.js';
31
31
  export { __readCrawlTimingSnapshot, __setCrawlTimingForTest, __writeCrawlTimingDumpForTest, } from './crawl-timing.js';
32
32
  // The three fixture modules the sinks' suites share: the ONE temp-dir family,
33
33
  // the host gates, and the hostile tree every sink is tested against.
34
+ export * from './testing/archive-fixtures.js';
34
35
  export * from './testing/executables.js';
36
+ export * from './testing/fault-fs.js';
37
+ // By name: the harness's vocabulary, not its op table (`OPS`, `OpSpec`, `PathShape` are `fault-fs.ts`'s
38
+ // own wiring, and an `export *` here made them this subpath's API). Pinned by `module-subpaths.test.ts`.
39
+ export { faultFsSpecOf, faultRuleOf, INJECTED_ERRNOS, injectedErrnoError } from './testing/fault-spec.js';
35
40
  export * from './testing/hostile-tree.js';
36
41
  export * from './testing/platform-gates.js';
37
42
  export * from './testing/temp-dir.js';
43
+ export * from './testing/tier-timeout.js';
44
+ export * from './testing/tree-snapshot.js';
38
45
  /**
39
46
  * Write a literal corpus into a fresh temp directory.
40
47
  *
@@ -119,6 +126,6 @@ export function replantableCorpus(prefix, corpus) {
119
126
  * @returns A `refuse` policy for `crawlDirectory` and the git listings
120
127
  */
121
128
  export function refuseUnreadableFixture(root) {
122
- return { refuse: { root, remedy: 'A test fixture must be listable in full; fix the fixture, not the walk.' } };
129
+ return { refuse: { root, remedy: 'A test fixture must be listable in full; fix the fixture, not the walk.', side: 'source' } };
123
130
  }
124
131
  //# sourceMappingURL=testing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7D,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE7D,6EAA6E;AAC7E,2EAA2E;AAC3E,+EAA+E;AAC/E,sEAAsE;AACtE,cAAc,mBAAmB,CAAC;AAClC,2EAA2E;AAC3E,8EAA8E;AAC9E,gEAAgE;AAChE,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,mBAAmB,CAAC;AAC3B,8EAA8E;AAC9E,qEAAqE;AACrE,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AAUtC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAc,EACd,MAAwC;IAExC,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IACpE,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AACjF,CAAC;AAYD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,MAAwC;IAExC,IAAI,OAA+B,CAAC;IACpC,OAAO;QACL,KAAK,EAAE,GAAG,EAAE;YACV,0EAA0E;YAC1E,uEAAuE;YACvE,wEAAwE;YACxE,wEAAwE;YACxE,iEAAiE;YACjE,OAAO,EAAE,OAAO,EAAE,CAAC;YACnB,OAAO,GAAG,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;QACD,KAAK,EAAE,GAAG,EAAE;YACV,OAAO,EAAE,OAAO,EAAE,CAAC;YACnB,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;QACD,IAAI,EAAE,GAAG,EAAE;YACT,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CACb,sBAAsB,MAAM,iEAAiE,CAC9F,CAAC;YACJ,CAAC;YACD,OAAO,OAAO,CAAC,IAAI,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,yEAAyE,EAAE,EAAE,CAAC;AACjH,CAAC"}
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7D,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE7D,6EAA6E;AAC7E,2EAA2E;AAC3E,+EAA+E;AAC/E,sEAAsE;AACtE,cAAc,mBAAmB,CAAC;AAClC,2EAA2E;AAC3E,8EAA8E;AAC9E,gEAAgE;AAChE,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,mBAAmB,CAAC;AAC3B,8EAA8E;AAC9E,qEAAqE;AACrE,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,wGAAwG;AACxG,yGAAyG;AACzG,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE1G,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAU3C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAc,EACd,MAAwC;IAExC,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IACpE,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AACjF,CAAC;AAYD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,MAAwC;IAExC,IAAI,OAA+B,CAAC;IACpC,OAAO;QACL,KAAK,EAAE,GAAG,EAAE;YACV,0EAA0E;YAC1E,uEAAuE;YACvE,wEAAwE;YACxE,wEAAwE;YACxE,iEAAiE;YACjE,OAAO,EAAE,OAAO,EAAE,CAAC;YACnB,OAAO,GAAG,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;QACD,KAAK,EAAE,GAAG,EAAE;YACV,OAAO,EAAE,OAAO,EAAE,CAAC;YACnB,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;QACD,IAAI,EAAE,GAAG,EAAE;YACT,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CACb,sBAAsB,MAAM,iEAAiE,CAC9F,CAAC;YACJ,CAAC;YACD,OAAO,OAAO,CAAC,IAAI,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,yEAAyE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;AACjI,CAAC"}
@@ -130,6 +130,34 @@
130
130
  * NOT shift is the content key, which is computed over the raw bytes; see
131
131
  * `resources`' `content-key.ts`.
132
132
  */
133
+ import { VatError } from './errors/vat-error.js';
134
+ /**
135
+ * Thrown when bytes are too many to become one JS string — the engine's refusal,
136
+ * made a coded error so a caller can treat it as a fact about a file.
137
+ *
138
+ * The limit is the engine's, not VAT's: V8 caps a string at
139
+ * `buffer.constants.MAX_STRING_LENGTH` (0x1fffffe8) UTF-16 code units, and
140
+ * decoding past it throws. One near-gigabyte CSV in a corpus used to abort a
141
+ * whole projection population on that throw.
142
+ *
143
+ * Two places raise it, and they agree by construction rather than by a shared
144
+ * literal. The `./fs` read path (`readDecodableBytes` in `text-file.ts`) refuses
145
+ * by `stat` BEFORE reading, against `MAX_DECODABLE_BYTES`, so half a gigabyte is
146
+ * never pulled into memory only to be refused. {@link decodeTextContent} — pure,
147
+ * so it cannot ask `node:buffer` for the limit (`subpath-purity.test.ts`) —
148
+ * translates the engine's own refusal for bytes that arrive by any other route.
149
+ * Dispatch with `isVatError(error, TextTooLargeError.code)`, never on the message.
150
+ */
151
+ export declare class TextTooLargeError extends VatError {
152
+ readonly byteLength: number;
153
+ /** The code every instance carries, for `isVatError(error, TextTooLargeError.code)`. */
154
+ static readonly code = "TEXT_TOO_LARGE";
155
+ /**
156
+ * @param byteLength - How many bytes the refused content holds
157
+ * @param options - `cause`, when an engine refusal is being translated
158
+ */
159
+ constructor(byteLength: number, options?: ErrorOptions);
160
+ }
133
161
  /** An encoding this module can decode. */
134
162
  export type TextEncoding = 'utf-8' | 'utf-16le' | 'utf-16be' | 'utf-32le' | 'utf-32be';
135
163
  /**
@@ -184,6 +212,7 @@ export interface DecodedText extends TextProvenance {
184
212
  * @param bytes - The exact bytes read from disk
185
213
  * @returns The decoded text, the encoding used, whether that was a fact, and how
186
214
  * many characters the decode had to replace
215
+ * @throws {@link TextTooLargeError} when the engine refuses a string that long
187
216
  *
188
217
  * @example
189
218
  * ```typescript
@@ -1 +1 @@
1
- {"version":3,"file":"text-content.d.ts","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmIG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkLD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAiChE"}
1
+ {"version":3,"file":"text-content.d.ts","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAkB,SAAQ,QAAQ;IAS3C,QAAQ,CAAC,UAAU,EAAE,MAAM;IAR7B,wFAAwF;IACxF,MAAM,CAAC,QAAQ,CAAC,IAAI,oBAAoB;IAExC;;;OAGG;gBAEQ,UAAU,EAAE,MAAM,EAC3B,OAAO,CAAC,EAAE,YAAY;CAQzB;AAkBD,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkLD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAOhE"}
@@ -130,6 +130,51 @@
130
130
  * NOT shift is the content key, which is computed over the raw bytes; see
131
131
  * `resources`' `content-key.ts`.
132
132
  */
133
+ import { VatError } from './errors/vat-error.js';
134
+ /**
135
+ * Thrown when bytes are too many to become one JS string — the engine's refusal,
136
+ * made a coded error so a caller can treat it as a fact about a file.
137
+ *
138
+ * The limit is the engine's, not VAT's: V8 caps a string at
139
+ * `buffer.constants.MAX_STRING_LENGTH` (0x1fffffe8) UTF-16 code units, and
140
+ * decoding past it throws. One near-gigabyte CSV in a corpus used to abort a
141
+ * whole projection population on that throw.
142
+ *
143
+ * Two places raise it, and they agree by construction rather than by a shared
144
+ * literal. The `./fs` read path (`readDecodableBytes` in `text-file.ts`) refuses
145
+ * by `stat` BEFORE reading, against `MAX_DECODABLE_BYTES`, so half a gigabyte is
146
+ * never pulled into memory only to be refused. {@link decodeTextContent} — pure,
147
+ * so it cannot ask `node:buffer` for the limit (`subpath-purity.test.ts`) —
148
+ * translates the engine's own refusal for bytes that arrive by any other route.
149
+ * Dispatch with `isVatError(error, TextTooLargeError.code)`, never on the message.
150
+ */
151
+ export class TextTooLargeError extends VatError {
152
+ byteLength;
153
+ /** The code every instance carries, for `isVatError(error, TextTooLargeError.code)`. */
154
+ static code = 'TEXT_TOO_LARGE';
155
+ /**
156
+ * @param byteLength - How many bytes the refused content holds
157
+ * @param options - `cause`, when an engine refusal is being translated
158
+ */
159
+ constructor(byteLength, options) {
160
+ super(TextTooLargeError.code, `${byteLength} bytes is too large to decode into a JavaScript string`, options);
161
+ this.byteLength = byteLength;
162
+ }
163
+ }
164
+ /**
165
+ * The code Node's `TextDecoder` attaches when the decoded string would pass the
166
+ * engine's string-length limit. Matched as a `code`, never as its message.
167
+ */
168
+ const ENGINE_STRING_TOO_LONG = 'ERR_STRING_TOO_LONG';
169
+ /**
170
+ * Whether `error` is the engine refusing to build a string that long.
171
+ *
172
+ * @param error - What a decode threw
173
+ * @returns True for Node's `ERR_STRING_TOO_LONG`
174
+ */
175
+ function isEngineStringTooLong(error) {
176
+ return error instanceof Error && error.code === ENGINE_STRING_TOO_LONG;
177
+ }
133
178
  /**
134
179
  * Byte-order marks, **longest first**.
135
180
  *
@@ -308,6 +353,7 @@ function countReplacementCharacters(text) {
308
353
  * @param bytes - The exact bytes read from disk
309
354
  * @returns The decoded text, the encoding used, whether that was a fact, and how
310
355
  * many characters the decode had to replace
356
+ * @throws {@link TextTooLargeError} when the engine refuses a string that long
311
357
  *
312
358
  * @example
313
359
  * ```typescript
@@ -318,6 +364,24 @@ function countReplacementCharacters(text) {
318
364
  * ```
319
365
  */
320
366
  export function decodeTextContent(bytes) {
367
+ try {
368
+ return decodeWithinEngineLimits(bytes);
369
+ }
370
+ catch (error) {
371
+ if (isEngineStringTooLong(error))
372
+ throw new TextTooLargeError(bytes.byteLength, { cause: error });
373
+ throw error;
374
+ }
375
+ }
376
+ /**
377
+ * {@link decodeTextContent} without the translation of the engine's
378
+ * string-length refusal — split out so the translation wraps BOTH decodes, the
379
+ * fatal first pass and the lenient retry, either of which can reach the limit.
380
+ *
381
+ * @param bytes - The exact bytes read from disk
382
+ * @returns The decoded text and its provenance
383
+ */
384
+ function decodeWithinEngineLimits(bytes) {
321
385
  const bom = bomAt(bytes);
322
386
  const encoding = bom?.encoding ?? 'utf-8';
323
387
  const body = bom === null ? bytes : bytes.subarray(bom.length);
@@ -1 +1 @@
1
- {"version":3,"file":"text-content.js","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"text-content.js","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"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"}
@@ -14,18 +14,20 @@
14
14
  * it worse rather than more consistent.
15
15
  *
16
16
  * A caller that needs the RAW bytes as well as the text — to hash them, key
17
- * them, or report `stat().size` — must not use these: read the bytes once itself
18
- * and call `decodeTextContent` on them, so the digest and the characters come
19
- * from the same read. `readContentWithKey` in `@vibe-agent-toolkit/resources` is
20
- * that caller and is shaped exactly that way.
17
+ * them, or report `stat().size` — must not use these: read the bytes once with
18
+ * {@link readDecodableBytes} and call `decodeTextContent` on them, so the digest
19
+ * and the characters come from the same read. `readContentWithKey` in
20
+ * `@vibe-agent-toolkit/resources` is that caller and is shaped exactly that way.
21
21
  */
22
+ import { type FileHandle } from 'node:fs/promises';
22
23
  import { type DecodedText } from './text-content.js';
23
24
  /**
24
25
  * Read a file and decode it through {@link decodeTextContent}.
25
26
  *
26
27
  * @param filePath - Path to read
27
28
  * @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
28
- * @throws Whatever `readFile` throws — callers decide whether that is fatal
29
+ * @throws Whatever `readFile` throws — callers decide whether that is fatal — and
30
+ * {@link TextTooLargeError} for a file too large to decode, before reading it
29
31
  *
30
32
  * @example
31
33
  * ```typescript
@@ -39,7 +41,57 @@ export declare function readTextContent(filePath: string): Promise<DecodedText>;
39
41
  *
40
42
  * @param filePath - Path to read
41
43
  * @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
42
- * @throws Whatever `readFileSync` throws
44
+ * @throws Whatever `readFileSync` throws, and {@link TextTooLargeError} as {@link readDecodableBytes} does
43
45
  */
44
46
  export declare function readTextContentSync(filePath: string): DecodedText;
47
+ /**
48
+ * The most bytes that can be decoded into one JS string: the engine's own
49
+ * string-length limit, read from `node:buffer` rather than written down.
50
+ *
51
+ * A bound on BYTES stated in UTF-16 code units, which is exact-or-safe for UTF-8
52
+ * (a UTF-8 byte never yields more than one code unit) and conservative for
53
+ * UTF-16/UTF-32, whose bytes yield half or a quarter as many.
54
+ */
55
+ export declare const MAX_DECODABLE_BYTES: number;
56
+ /**
57
+ * Whether content of this many bytes is past what can be decoded to a string.
58
+ *
59
+ * @param byteLength - The content's size in bytes, as `stat` reports it
60
+ * @returns True when decoding it would exceed {@link MAX_DECODABLE_BYTES}
61
+ */
62
+ export declare function exceedsDecodableLength(byteLength: number): boolean;
63
+ /**
64
+ * Open `filePath` for reading without ever blocking on it: a named pipe, socket
65
+ * or device is refused (`EFTYPE`) and its handle closed, so only a regular file
66
+ * (or a directory, which the read refuses itself) comes back. The open a caller
67
+ * uses to PROVE a source readable before copying it — a bare `open(path, 'r')`
68
+ * of a pipe waits for a writer forever.
69
+ *
70
+ * @param filePath - Path to open
71
+ * @returns The open handle; the caller closes it
72
+ * @throws an `EFTYPE` errno for a special file, and whatever `open` throws otherwise
73
+ */
74
+ export declare function openForReading(filePath: string): Promise<FileHandle>;
75
+ /**
76
+ * Read a file's raw bytes for decoding — refusing, by `stat` and before reading
77
+ * a byte, one too large to decode at all.
78
+ *
79
+ * The one read path for "bytes that are about to go through
80
+ * {@link decodeTextContent}": {@link readTextContent} uses it, and so does
81
+ * `readContentWithKey` in `@vibe-agent-toolkit/resources`, which needs the raw
82
+ * bytes as well to key them. Without the stat, a 966 MB file is read whole and
83
+ * only then refused by the decoder, and past 2 GiB `readFile` fails first with
84
+ * its own `ERR_FS_FILE_TOO_LARGE` — two different errors for one fact.
85
+ *
86
+ * The size comes from `fstat` on the handle that is then read, not from a
87
+ * separate `stat` of the path, so a rename between the two cannot substitute
88
+ * another file. A file that GROWS past the limit after the `fstat` still reaches
89
+ * the decoder, which raises the same {@link TextTooLargeError}.
90
+ *
91
+ * @param filePath - Path to read
92
+ * @returns The file's bytes
93
+ * @throws {@link TextTooLargeError} when the file is past {@link MAX_DECODABLE_BYTES},
94
+ * an `EFTYPE` errno when it is not a regular file, and whatever `open`/`readFile` throw otherwise
95
+ */
96
+ export declare function readDecodableBytes(filePath: string): Promise<Buffer>;
45
97
  //# sourceMappingURL=text-file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-file.d.ts","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAExE;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE5E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAEjE"}
1
+ {"version":3,"file":"text-file.d.ts","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGzD,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE3F;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE5E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAUjE;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAoC,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAElE;AAoBD;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAS1E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ1E"}
package/dist/text-file.js CHANGED
@@ -14,20 +14,23 @@
14
14
  * it worse rather than more consistent.
15
15
  *
16
16
  * A caller that needs the RAW bytes as well as the text — to hash them, key
17
- * them, or report `stat().size` — must not use these: read the bytes once itself
18
- * and call `decodeTextContent` on them, so the digest and the characters come
19
- * from the same read. `readContentWithKey` in `@vibe-agent-toolkit/resources` is
20
- * that caller and is shaped exactly that way.
17
+ * them, or report `stat().size` — must not use these: read the bytes once with
18
+ * {@link readDecodableBytes} and call `decodeTextContent` on them, so the digest
19
+ * and the characters come from the same read. `readContentWithKey` in
20
+ * `@vibe-agent-toolkit/resources` is that caller and is shaped exactly that way.
21
21
  */
22
- import { readFileSync } from 'node:fs';
23
- import { readFile } from 'node:fs/promises';
24
- import { decodeTextContent } from './text-content.js';
22
+ import { constants } from 'node:buffer';
23
+ import { closeSync, constants as fsConstants, fstatSync, openSync, readFileSync } from 'node:fs';
24
+ import { open } from 'node:fs/promises';
25
+ import { refuseSpecialFile } from './special-file.js';
26
+ import { decodeTextContent, TextTooLargeError } from './text-content.js';
25
27
  /**
26
28
  * Read a file and decode it through {@link decodeTextContent}.
27
29
  *
28
30
  * @param filePath - Path to read
29
31
  * @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
30
- * @throws Whatever `readFile` throws — callers decide whether that is fatal
32
+ * @throws Whatever `readFile` throws — callers decide whether that is fatal — and
33
+ * {@link TextTooLargeError} for a file too large to decode, before reading it
31
34
  *
32
35
  * @example
33
36
  * ```typescript
@@ -36,16 +39,113 @@ import { decodeTextContent } from './text-content.js';
36
39
  * ```
37
40
  */
38
41
  export async function readTextContent(filePath) {
39
- return decodeTextContent(await readFile(filePath));
42
+ return decodeTextContent(await readDecodableBytes(filePath));
40
43
  }
41
44
  /**
42
45
  * {@link readTextContent}, synchronously.
43
46
  *
44
47
  * @param filePath - Path to read
45
48
  * @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
46
- * @throws Whatever `readFileSync` throws
49
+ * @throws Whatever `readFileSync` throws, and {@link TextTooLargeError} as {@link readDecodableBytes} does
47
50
  */
48
51
  export function readTextContentSync(filePath) {
49
- return decodeTextContent(readFileSync(filePath));
52
+ const handle = openSync(filePath, readWithoutBlocking());
53
+ try {
54
+ const stats = fstatSync(handle);
55
+ refuseSpecialFile(stats, filePath);
56
+ refuseUndecodableSize(stats.size);
57
+ return decodeTextContent(readFileSync(handle));
58
+ }
59
+ finally {
60
+ closeSync(handle);
61
+ }
62
+ }
63
+ /**
64
+ * The most bytes that can be decoded into one JS string: the engine's own
65
+ * string-length limit, read from `node:buffer` rather than written down.
66
+ *
67
+ * A bound on BYTES stated in UTF-16 code units, which is exact-or-safe for UTF-8
68
+ * (a UTF-8 byte never yields more than one code unit) and conservative for
69
+ * UTF-16/UTF-32, whose bytes yield half or a quarter as many.
70
+ */
71
+ export const MAX_DECODABLE_BYTES = constants.MAX_STRING_LENGTH;
72
+ /**
73
+ * Whether content of this many bytes is past what can be decoded to a string.
74
+ *
75
+ * @param byteLength - The content's size in bytes, as `stat` reports it
76
+ * @returns True when decoding it would exceed {@link MAX_DECODABLE_BYTES}
77
+ */
78
+ export function exceedsDecodableLength(byteLength) {
79
+ return byteLength > MAX_DECODABLE_BYTES;
80
+ }
81
+ /**
82
+ * The flags to open for reading without waiting on a writer. Opening a named
83
+ * pipe blocks until one appears — a FIFO reached by a parse lane hung the whole
84
+ * run — and `O_NONBLOCK` returns at once so the handle's `fstat` can refuse it.
85
+ * It changes nothing for a regular file. Windows defines no `O_NONBLOCK` (and no
86
+ * FIFOs). Read at the call, not at import, so a test's partial `node:fs` mock
87
+ * that never reads a file does not have to supply `constants`.
88
+ */
89
+ function readWithoutBlocking() {
90
+ const nonBlocking = fsConstants.O_NONBLOCK;
91
+ return fsConstants.O_RDONLY | (nonBlocking ?? 0);
92
+ }
93
+ /** Throw {@link TextTooLargeError} for a size no decode can hold. */
94
+ function refuseUndecodableSize(byteLength) {
95
+ if (exceedsDecodableLength(byteLength))
96
+ throw new TextTooLargeError(byteLength);
97
+ }
98
+ /**
99
+ * Open `filePath` for reading without ever blocking on it: a named pipe, socket
100
+ * or device is refused (`EFTYPE`) and its handle closed, so only a regular file
101
+ * (or a directory, which the read refuses itself) comes back. The open a caller
102
+ * uses to PROVE a source readable before copying it — a bare `open(path, 'r')`
103
+ * of a pipe waits for a writer forever.
104
+ *
105
+ * @param filePath - Path to open
106
+ * @returns The open handle; the caller closes it
107
+ * @throws an `EFTYPE` errno for a special file, and whatever `open` throws otherwise
108
+ */
109
+ export async function openForReading(filePath) {
110
+ const handle = await open(filePath, readWithoutBlocking());
111
+ try {
112
+ refuseSpecialFile(await handle.stat(), filePath);
113
+ return handle;
114
+ }
115
+ catch (error) {
116
+ await handle.close();
117
+ throw error;
118
+ }
119
+ }
120
+ /**
121
+ * Read a file's raw bytes for decoding — refusing, by `stat` and before reading
122
+ * a byte, one too large to decode at all.
123
+ *
124
+ * The one read path for "bytes that are about to go through
125
+ * {@link decodeTextContent}": {@link readTextContent} uses it, and so does
126
+ * `readContentWithKey` in `@vibe-agent-toolkit/resources`, which needs the raw
127
+ * bytes as well to key them. Without the stat, a 966 MB file is read whole and
128
+ * only then refused by the decoder, and past 2 GiB `readFile` fails first with
129
+ * its own `ERR_FS_FILE_TOO_LARGE` — two different errors for one fact.
130
+ *
131
+ * The size comes from `fstat` on the handle that is then read, not from a
132
+ * separate `stat` of the path, so a rename between the two cannot substitute
133
+ * another file. A file that GROWS past the limit after the `fstat` still reaches
134
+ * the decoder, which raises the same {@link TextTooLargeError}.
135
+ *
136
+ * @param filePath - Path to read
137
+ * @returns The file's bytes
138
+ * @throws {@link TextTooLargeError} when the file is past {@link MAX_DECODABLE_BYTES},
139
+ * an `EFTYPE` errno when it is not a regular file, and whatever `open`/`readFile` throw otherwise
140
+ */
141
+ export async function readDecodableBytes(filePath) {
142
+ const handle = await openForReading(filePath);
143
+ try {
144
+ refuseUndecodableSize((await handle.stat()).size);
145
+ return await handle.readFile();
146
+ }
147
+ finally {
148
+ await handle.close();
149
+ }
50
150
  }
51
151
  //# sourceMappingURL=text-file.js.map