@crvouga/atlas 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +191 -611
  3. package/dist/bun/src/driver.d.ts +41 -7
  4. package/dist/bun/src/driver.js +47 -6
  5. package/dist/bun/src/index.d.ts +8 -0
  6. package/dist/bun/src/index.js +8 -0
  7. package/dist/core/src/cli.d.ts +14 -1
  8. package/dist/core/src/cli.js +35 -1
  9. package/dist/core/src/config.d.ts +77 -4
  10. package/dist/core/src/config.js +110 -17
  11. package/dist/core/src/events/cloudevents.d.ts +54 -9
  12. package/dist/core/src/events/cloudevents.js +60 -9
  13. package/dist/core/src/graph.d.ts +21 -0
  14. package/dist/core/src/graph.js +26 -0
  15. package/dist/core/src/index.d.ts +11 -0
  16. package/dist/core/src/index.js +11 -0
  17. package/dist/core/src/lint.d.ts +30 -0
  18. package/dist/core/src/lint.js +13 -0
  19. package/dist/core/src/plan.d.ts +39 -1
  20. package/dist/core/src/plan.js +28 -1
  21. package/dist/core/src/privacy.d.ts +29 -2
  22. package/dist/core/src/privacy.js +19 -2
  23. package/dist/core/src/report/formats.d.ts +34 -4
  24. package/dist/core/src/report/formats.js +24 -4
  25. package/dist/core/src/report/markdown.d.ts +15 -0
  26. package/dist/core/src/report/markdown.js +8 -0
  27. package/dist/core/src/run/clients.d.ts +44 -5
  28. package/dist/core/src/run/clients.js +46 -2
  29. package/dist/core/src/run/execute.d.ts +75 -16
  30. package/dist/core/src/run/execute.js +71 -18
  31. package/dist/core/src/run/frames.d.ts +13 -4
  32. package/dist/core/src/run/frames.js +19 -4
  33. package/dist/core/src/run/http.d.ts +34 -1
  34. package/dist/core/src/run/http.js +14 -1
  35. package/dist/core/src/run/types.d.ts +76 -3
  36. package/dist/core/src/run/types.js +19 -3
  37. package/dist/core/src/signals.d.ts +49 -5
  38. package/dist/core/src/signals.js +22 -5
  39. package/dist/core/src/spec/compose.d.ts +23 -1
  40. package/dist/core/src/spec/compose.js +10 -1
  41. package/dist/core/src/spec/index.d.ts +9 -1
  42. package/dist/core/src/spec/index.js +9 -1
  43. package/dist/core/src/spec/load.d.ts +22 -1
  44. package/dist/core/src/spec/load.js +31 -1
  45. package/dist/core/src/spec/parse.d.ts +16 -2
  46. package/dist/core/src/spec/parse.js +25 -2
  47. package/dist/core/src/spec/scxml.d.ts +25 -4
  48. package/dist/core/src/spec/scxml.js +36 -4
  49. package/dist/core/src/spec/types.d.ts +52 -1
  50. package/dist/core/src/spec/types.js +24 -0
  51. package/dist/detox/src/index.d.ts +54 -11
  52. package/dist/detox/src/index.js +45 -7
  53. package/dist/playwright/src/driver.d.ts +39 -3
  54. package/dist/playwright/src/driver.js +27 -3
  55. package/dist/playwright/src/index.d.ts +10 -0
  56. package/dist/playwright/src/index.js +10 -0
  57. package/dist/playwright/src/media.d.ts +50 -4
  58. package/dist/playwright/src/media.js +55 -4
  59. package/dist/playwright/src/signals.d.ts +44 -5
  60. package/dist/playwright/src/signals.js +29 -5
  61. package/dist/reports/src/attachment-files.d.ts +44 -0
  62. package/dist/reports/src/attachment-files.js +121 -0
  63. package/dist/reports/src/attachment-response.d.ts +31 -0
  64. package/dist/reports/src/attachment-response.js +55 -0
  65. package/dist/reports/src/aws-credentials.d.ts +48 -0
  66. package/dist/reports/src/aws-credentials.js +221 -0
  67. package/dist/reports/src/browser-navigation.d.ts +14 -0
  68. package/dist/reports/src/browser-navigation.js +7 -0
  69. package/dist/reports/src/bundle.d.ts +34 -2
  70. package/dist/reports/src/bundle.js +38 -2
  71. package/dist/reports/src/catalog.d.ts +34 -3
  72. package/dist/reports/src/catalog.js +17 -3
  73. package/dist/reports/src/cli-stores.d.ts +36 -0
  74. package/dist/reports/src/cli-stores.js +73 -0
  75. package/dist/reports/src/clock.d.ts +7 -2
  76. package/dist/reports/src/clock.js +5 -2
  77. package/dist/reports/src/completion-order.d.ts +18 -5
  78. package/dist/reports/src/completion-order.js +18 -5
  79. package/dist/reports/src/compose.d.ts +80 -0
  80. package/dist/reports/src/compose.js +117 -0
  81. package/dist/reports/src/copy-store.d.ts +29 -0
  82. package/dist/reports/src/copy-store.js +52 -0
  83. package/dist/reports/src/directory-outline.d.ts +38 -0
  84. package/dist/reports/src/directory-outline.js +103 -0
  85. package/dist/reports/src/directory-store.d.ts +20 -0
  86. package/dist/reports/src/directory-store.js +166 -0
  87. package/dist/reports/src/doctor.d.ts +29 -0
  88. package/dist/reports/src/doctor.js +85 -0
  89. package/dist/reports/src/effective-delta.d.ts +34 -0
  90. package/dist/reports/src/effective-delta.js +25 -0
  91. package/dist/reports/src/evidence-detail.d.ts +64 -0
  92. package/dist/reports/src/evidence-detail.js +92 -0
  93. package/dist/reports/src/evidence-live.d.ts +35 -5
  94. package/dist/reports/src/evidence-live.js +18 -2
  95. package/dist/reports/src/evidence-types.d.ts +68 -1
  96. package/dist/reports/src/evidence-types.js +5 -1
  97. package/dist/reports/src/evidence-view.d.ts +43 -4
  98. package/dist/reports/src/evidence-view.js +13 -2
  99. package/dist/reports/src/explorer-types.d.ts +40 -3
  100. package/dist/reports/src/explorer-types.js +8 -0
  101. package/dist/reports/src/explorer-versions.d.ts +9 -1
  102. package/dist/reports/src/explorer-versions.js +18 -1
  103. package/dist/reports/src/export-evidence.d.ts +16 -1
  104. package/dist/reports/src/export-evidence.js +52 -1
  105. package/dist/reports/src/fetcher.d.ts +27 -3
  106. package/dist/reports/src/fetcher.js +11 -3
  107. package/dist/reports/src/format-date.d.ts +6 -0
  108. package/dist/reports/src/format-date.js +6 -0
  109. package/dist/reports/src/format.d.ts +16 -0
  110. package/dist/reports/src/format.js +10 -0
  111. package/dist/reports/src/frames.d.ts +36 -3
  112. package/dist/reports/src/frames.js +22 -3
  113. package/dist/reports/src/hash.d.ts +18 -0
  114. package/dist/reports/src/hash.js +13 -0
  115. package/dist/reports/src/host.d.ts +29 -0
  116. package/dist/reports/src/host.js +64 -0
  117. package/dist/reports/src/html-frame.d.ts +5 -6
  118. package/dist/reports/src/html-frame.js +12 -1
  119. package/dist/reports/src/http-store.d.ts +56 -0
  120. package/dist/reports/src/http-store.js +148 -0
  121. package/dist/reports/src/http.d.ts +7 -0
  122. package/dist/reports/src/http.js +6 -0
  123. package/dist/reports/src/ingest.d.ts +54 -0
  124. package/dist/reports/src/ingest.js +210 -0
  125. package/dist/reports/src/init.d.ts +40 -0
  126. package/dist/reports/src/init.js +369 -0
  127. package/dist/reports/src/junit.d.ts +33 -6
  128. package/dist/reports/src/junit.js +87 -21
  129. package/dist/reports/src/keyboard.d.ts +20 -0
  130. package/dist/reports/src/keyboard.js +11 -0
  131. package/dist/reports/src/keys.d.ts +21 -3
  132. package/dist/reports/src/keys.js +21 -3
  133. package/dist/reports/src/layer-axes.d.ts +35 -5
  134. package/dist/reports/src/layer-axes.js +25 -5
  135. package/dist/reports/src/layer-catalog.d.ts +28 -1
  136. package/dist/reports/src/layer-catalog.js +18 -1
  137. package/dist/reports/src/layer-merge.d.ts +98 -9
  138. package/dist/reports/src/layer-merge.js +147 -31
  139. package/dist/reports/src/layer-source.d.ts +18 -0
  140. package/dist/reports/src/layer-source.js +11 -0
  141. package/dist/reports/src/layer-store.d.ts +42 -0
  142. package/dist/reports/src/layer-store.js +63 -5
  143. package/dist/reports/src/layer-types.d.ts +32 -1
  144. package/dist/reports/src/layers-route.d.ts +47 -4
  145. package/dist/reports/src/layers-route.js +86 -7
  146. package/dist/reports/src/live-reporter.d.ts +34 -0
  147. package/dist/reports/src/live-reporter.js +122 -0
  148. package/dist/reports/src/media-types.d.ts +11 -0
  149. package/dist/reports/src/media-types.js +22 -0
  150. package/dist/reports/src/namespace-guard.d.ts +10 -1
  151. package/dist/reports/src/namespace-guard.js +10 -1
  152. package/dist/reports/src/navigation.d.ts +12 -0
  153. package/dist/reports/src/navigation.js +7 -0
  154. package/dist/reports/src/object-io.d.ts +11 -4
  155. package/dist/reports/src/object-io.js +4 -4
  156. package/dist/reports/src/object-store-conformance.d.ts +27 -0
  157. package/dist/reports/src/object-store-conformance.js +87 -0
  158. package/dist/reports/src/object-store.d.ts +33 -24
  159. package/dist/reports/src/object-store.js +17 -24
  160. package/dist/reports/src/parse-with.d.ts +16 -0
  161. package/dist/reports/src/parse-with.js +14 -0
  162. package/dist/reports/src/performance-part.d.ts +25 -1
  163. package/dist/reports/src/performance-part.js +15 -1
  164. package/dist/reports/src/performance.d.ts +22 -2
  165. package/dist/reports/src/performance.js +12 -2
  166. package/dist/reports/src/persist.d.ts +12 -3
  167. package/dist/reports/src/persist.js +12 -3
  168. package/dist/reports/src/phone.d.ts +18 -1
  169. package/dist/reports/src/phone.js +18 -1
  170. package/dist/reports/src/protocol.d.ts +26 -4
  171. package/dist/reports/src/protocol.js +15 -4
  172. package/dist/reports/src/publication.d.ts +21 -1
  173. package/dist/reports/src/publication.js +21 -1
  174. package/dist/reports/src/publish-checks.d.ts +35 -0
  175. package/dist/reports/src/publish-checks.js +122 -0
  176. package/dist/reports/src/publish.d.ts +42 -9
  177. package/dist/reports/src/publish.js +36 -7
  178. package/dist/reports/src/read.d.ts +46 -16
  179. package/dist/reports/src/read.js +38 -14
  180. package/dist/reports/src/recording-bundle.d.ts +13 -2
  181. package/dist/reports/src/recording-bundle.js +4 -2
  182. package/dist/reports/src/recording-keys.d.ts +15 -1
  183. package/dist/reports/src/recording-keys.js +15 -1
  184. package/dist/reports/src/remote-report.d.ts +28 -7
  185. package/dist/reports/src/remote-report.js +15 -7
  186. package/dist/reports/src/report-cli.d.ts +51 -0
  187. package/dist/reports/src/report-cli.js +486 -0
  188. package/dist/reports/src/report-index.d.ts +99 -0
  189. package/dist/reports/src/report-index.js +337 -0
  190. package/dist/reports/src/report-loader.d.ts +10 -0
  191. package/dist/reports/src/report-loader.js +6 -0
  192. package/dist/reports/src/report-parts.d.ts +29 -3
  193. package/dist/reports/src/report-parts.js +37 -3
  194. package/dist/reports/src/report-status.d.ts +48 -0
  195. package/dist/reports/src/report-status.js +156 -0
  196. package/dist/reports/src/report-types.d.ts +73 -0
  197. package/dist/reports/src/report-types.js +3 -0
  198. package/dist/reports/src/reporter.d.ts +67 -0
  199. package/dist/reports/src/reporter.js +128 -0
  200. package/dist/reports/src/reports-explorer-route.d.ts +541 -0
  201. package/dist/reports/src/reports-explorer-route.js +11 -0
  202. package/dist/reports/src/reports-list-route.d.ts +9 -0
  203. package/dist/reports/src/reports-list-route.js +9 -0
  204. package/dist/reports/src/reports-route.d.ts +561 -22
  205. package/dist/reports/src/reports-route.js +11 -3
  206. package/dist/reports/src/reports-store.d.ts +19 -2
  207. package/dist/reports/src/reports-store.js +10 -2
  208. package/dist/reports/src/resilient-cache.d.ts +14 -1
  209. package/dist/reports/src/resilient-cache.js +8 -1
  210. package/dist/reports/src/retention.d.ts +17 -1
  211. package/dist/reports/src/retention.js +5 -1
  212. package/dist/reports/src/run-bundle.d.ts +63 -0
  213. package/dist/reports/src/run-bundle.js +159 -0
  214. package/dist/reports/src/s3-store.d.ts +79 -0
  215. package/dist/reports/src/s3-store.js +295 -0
  216. package/dist/reports/src/scenario.d.ts +14 -4
  217. package/dist/reports/src/scenario.js +5 -4
  218. package/dist/reports/src/sigv4.d.ts +72 -0
  219. package/dist/reports/src/sigv4.js +96 -0
  220. package/dist/reports/src/snapshot-bundle.d.ts +24 -1
  221. package/dist/reports/src/snapshot-bundle.js +27 -2
  222. package/dist/reports/src/snapshot-surface.d.ts +20 -9
  223. package/dist/reports/src/snapshot-surface.js +32 -19
  224. package/dist/reports/src/source-content.d.ts +23 -0
  225. package/dist/reports/src/source-content.js +30 -1
  226. package/dist/reports/src/source-entries.d.ts +13 -0
  227. package/dist/reports/src/source-entries.js +6 -0
  228. package/dist/reports/src/source-part.d.ts +15 -0
  229. package/dist/reports/src/source-part.js +8 -0
  230. package/dist/reports/src/source-reader.d.ts +11 -0
  231. package/dist/reports/src/source-reader.js +5 -0
  232. package/dist/reports/src/spec-evidence.d.ts +12 -2
  233. package/dist/reports/src/spec-evidence.js +6 -2
  234. package/dist/reports/src/spec-inventory.d.ts +15 -1
  235. package/dist/reports/src/spec-inventory.js +22 -1
  236. package/dist/reports/src/spec-link.d.ts +28 -0
  237. package/dist/reports/src/spec-link.js +70 -0
  238. package/dist/reports/src/spec-set.d.ts +63 -0
  239. package/dist/reports/src/spec-set.js +177 -0
  240. package/dist/reports/src/status.d.ts +10 -0
  241. package/dist/reports/src/status.js +5 -0
  242. package/dist/reports/src/store-prefix.d.ts +28 -0
  243. package/dist/reports/src/store-prefix.js +22 -0
  244. package/dist/reports/src/store-url.d.ts +89 -0
  245. package/dist/reports/src/store-url.js +160 -0
  246. package/dist/reports/src/test-catalog-entity.d.ts +11 -0
  247. package/dist/reports/src/test-catalog-entity.js +6 -0
  248. package/dist/reports/src/types.d.ts +6 -1
  249. package/dist/reports/src/use-report-nav.d.ts +35 -0
  250. package/dist/reports/src/use-report-nav.js +12 -0
  251. package/dist/runners/src/codecept.d.ts +14 -0
  252. package/dist/runners/src/codecept.js +16 -0
  253. package/dist/runners/src/jest.d.ts +50 -0
  254. package/dist/runners/src/jest.js +52 -0
  255. package/dist/runners/src/mocha.d.ts +40 -0
  256. package/dist/runners/src/mocha.js +39 -0
  257. package/dist/runners/src/node-test.d.ts +28 -0
  258. package/dist/runners/src/node-test.js +44 -0
  259. package/dist/runners/src/publish.d.ts +30 -0
  260. package/dist/runners/src/publish.js +68 -0
  261. package/dist/schema/src/api.d.ts +800 -0
  262. package/dist/schema/src/api.js +201 -0
  263. package/dist/schema/src/cli-results.d.ts +210 -0
  264. package/dist/schema/src/cli-results.js +124 -0
  265. package/dist/schema/src/common.d.ts +53 -0
  266. package/dist/schema/src/common.js +42 -0
  267. package/dist/schema/src/index.d.ts +16 -0
  268. package/dist/schema/src/index.js +16 -0
  269. package/dist/schema/src/layer.d.ts +2273 -0
  270. package/dist/schema/src/layer.js +462 -0
  271. package/dist/schema/src/live-run.d.ts +123 -0
  272. package/dist/schema/src/live-run.js +30 -0
  273. package/dist/schema/src/manifest.d.ts +97 -2
  274. package/dist/schema/src/manifest.js +84 -0
  275. package/dist/schema/src/project.d.ts +20 -0
  276. package/dist/schema/src/project.js +10 -0
  277. package/dist/schema/src/report-sources.d.ts +29 -0
  278. package/dist/schema/src/report-sources.js +27 -1
  279. package/dist/schema/src/runs-index.d.ts +17 -1
  280. package/dist/schema/src/runs-index.js +15 -0
  281. package/dist/schema/src/server-config.d.ts +177 -0
  282. package/dist/schema/src/server-config.js +85 -0
  283. package/dist/schema/src/spec.d.ts +69 -0
  284. package/dist/schema/src/spec.js +58 -0
  285. package/dist/server/src/app.d.ts +72 -0
  286. package/dist/server/src/app.js +557 -0
  287. package/dist/server/src/auth.d.ts +41 -0
  288. package/dist/server/src/auth.js +59 -0
  289. package/dist/server/src/config.d.ts +83 -0
  290. package/dist/server/src/config.js +204 -0
  291. package/dist/server/src/dev.d.ts +12 -0
  292. package/dist/server/src/dev.js +89 -0
  293. package/dist/server/src/events.d.ts +29 -0
  294. package/dist/server/src/events.js +101 -0
  295. package/dist/server/src/host.d.ts +30 -0
  296. package/dist/server/src/host.js +115 -0
  297. package/dist/server/src/index.d.ts +18 -0
  298. package/dist/server/src/index.js +18 -0
  299. package/dist/server/src/live-runs.d.ts +52 -0
  300. package/dist/server/src/live-runs.js +229 -0
  301. package/dist/server/src/llms.d.ts +19 -0
  302. package/dist/server/src/llms.js +63 -0
  303. package/dist/server/src/main.d.ts +30 -0
  304. package/dist/server/src/main.js +114 -0
  305. package/dist/server/src/openapi.d.ts +56 -0
  306. package/dist/server/src/openapi.js +438 -0
  307. package/dist/server/src/problem.d.ts +8 -0
  308. package/dist/server/src/problem.js +39 -0
  309. package/dist/server/src/projects.d.ts +3 -0
  310. package/dist/server/src/projects.js +154 -0
  311. package/dist/server/src/settings-page.d.ts +9 -0
  312. package/dist/server/src/settings-page.js +93 -0
  313. package/dist/server/src/settings.d.ts +23 -0
  314. package/dist/server/src/settings.js +72 -0
  315. package/dist/server/src/site.d.ts +15 -0
  316. package/dist/server/src/site.js +78 -0
  317. package/dist/server/src/store-api.d.ts +28 -0
  318. package/dist/server/src/store-api.js +180 -0
  319. package/dist/server/src/validate.d.ts +71 -0
  320. package/dist/server/src/validate.js +197 -0
  321. package/dist/vitest/src/index.d.ts +27 -5
  322. package/dist/vitest/src/index.js +18 -5
  323. package/dist/webdriver/src/driver.d.ts +63 -11
  324. package/dist/webdriver/src/driver.js +57 -6
  325. package/dist/webdriver/src/index.d.ts +8 -0
  326. package/dist/webdriver/src/index.js +1 -0
  327. package/dist/webdriver/src/protocol.d.ts +75 -6
  328. package/dist/webdriver/src/protocol.js +79 -6
  329. package/docs/agents.md +137 -0
  330. package/docs/deploying.md +345 -0
  331. package/docs/dogfooding.md +175 -0
  332. package/docs/extending.md +242 -0
  333. package/docs/integrating.md +485 -0
  334. package/docs/reference/bun.md +141 -0
  335. package/docs/reference/cli.md +148 -0
  336. package/docs/reference/core.md +2432 -0
  337. package/docs/reference/detox.md +150 -0
  338. package/docs/reference/http-api.md +799 -0
  339. package/docs/reference/playwright.md +303 -0
  340. package/docs/reference/reports.md +7152 -0
  341. package/docs/reference/runners.md +169 -0
  342. package/docs/reference/schema.md +1762 -0
  343. package/docs/reference/server.md +825 -0
  344. package/docs/reference/vitest.md +45 -0
  345. package/docs/reference/webdriver.md +330 -0
  346. package/docs/report-backends.md +144 -0
  347. package/docs/report-contract.md +400 -0
  348. package/docs/reporting-issues.md +160 -0
  349. package/docs/runners.md +69 -0
  350. package/docs/server.md +321 -0
  351. package/docs/statecharts.md +643 -0
  352. package/llms-full.txt +4114 -0
  353. package/llms.txt +49 -0
  354. package/package.json +61 -11
  355. package/packages/core/README.md +2 -1
  356. package/packages/reports/README.md +65 -0
  357. package/packages/reports/STORAGE.md +4 -0
  358. package/packages/runners/README.md +6 -0
  359. package/packages/runners/compat/codecept.cjs +2 -0
  360. package/packages/runners/compat/jest.cjs +2 -0
  361. package/packages/runners/compat/mocha.cjs +2 -0
  362. package/packages/schema/README.md +14 -0
  363. package/packages/schema/schemas/atlas-change.schema.json +4 -0
  364. package/packages/schema/schemas/check-detail.schema.json +1032 -0
  365. package/packages/schema/schemas/checks-result.schema.json +26 -0
  366. package/packages/schema/schemas/command-error.schema.json +35 -0
  367. package/packages/schema/schemas/doctor-result.schema.json +60 -0
  368. package/packages/schema/schemas/effective-delta.schema.json +490 -0
  369. package/packages/schema/schemas/effective-evidence.schema.json +471 -0
  370. package/packages/schema/schemas/evidence-page.schema.json +271 -0
  371. package/packages/schema/schemas/hello-event.schema.json +26 -0
  372. package/packages/schema/schemas/init-plan.schema.json +347 -0
  373. package/packages/schema/schemas/layer-rows.schema.json +199 -0
  374. package/packages/schema/schemas/layer.schema.json +2888 -0
  375. package/packages/schema/schemas/live-run-start.schema.json +42 -0
  376. package/packages/schema/schemas/live-run-update.schema.json +93 -0
  377. package/packages/schema/schemas/live-run.schema.json +225 -0
  378. package/packages/schema/schemas/merge-result.schema.json +40 -0
  379. package/packages/schema/schemas/outline.schema.json +62 -0
  380. package/packages/schema/schemas/problem.schema.json +37 -0
  381. package/packages/schema/schemas/project-slug.schema.json +8 -0
  382. package/packages/schema/schemas/project.schema.json +22 -0
  383. package/packages/schema/schemas/projects.schema.json +33 -0
  384. package/packages/schema/schemas/publish-checks.schema.json +1413 -0
  385. package/packages/schema/schemas/publish-result.schema.json +168 -0
  386. package/packages/schema/schemas/ready.schema.json +68 -0
  387. package/packages/schema/schemas/report-event.schema.json +100 -0
  388. package/packages/schema/schemas/report-status.schema.json +370 -0
  389. package/packages/schema/schemas/server-config.schema.json +208 -0
  390. package/packages/schema/schemas/server-editable-settings.schema.json +132 -0
  391. package/packages/schema/schemas/server-info.schema.json +114 -0
  392. package/packages/schema/schemas/server-settings-snapshot.schema.json +285 -0
  393. package/packages/schema/schemas/spec-set.schema.json +103 -0
  394. package/packages/schema/schemas/store-listing.schema.json +29 -0
  395. package/packages/schema/schemas/store-write-result.schema.json +28 -0
  396. package/packages/schema/schemas/stream-result.schema.json +59 -0
  397. package/packages/server/README.md +43 -0
  398. package/packages/server/openapi.json +11021 -0
  399. package/packages/visualizer/README.md +32 -11
  400. package/packages/visualizer/bin/atlas-client.mjs +176 -0
  401. package/packages/visualizer/bin/atlas-visualizer.mjs +2 -175
  402. package/packages/visualizer/src/app/AppShell.tsx +32 -16
  403. package/packages/visualizer/src/app/report-sources.tsx +7 -4
  404. package/packages/visualizer/src/app/router.tsx +3 -3
  405. package/packages/visualizer/src/components/ActivityIndicator.module.css +4 -0
  406. package/packages/visualizer/src/components/ActivityIndicator.tsx +6 -0
  407. package/packages/visualizer/src/components/Header.module.css +101 -192
  408. package/packages/visualizer/src/components/Header.tsx +88 -13
  409. package/packages/visualizer/src/components/ProjectPicker.tsx +64 -0
  410. package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
  411. package/packages/visualizer/src/data/adapters/index.ts +10 -1
  412. package/packages/visualizer/src/data/adapters/report-backend.test.ts +3 -2
  413. package/packages/visualizer/src/data/adapters/report-backend.ts +1 -1
  414. package/packages/visualizer/src/data/queries.ts +14 -7
  415. package/packages/visualizer/src/evidence/api.ts +16 -3
  416. package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
  417. package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
  418. package/packages/visualizer/src/evidence/check-detail.tsx +21 -4
  419. package/packages/visualizer/src/evidence/document-page.tsx +6 -2
  420. package/packages/visualizer/src/evidence/evidence-context.tsx +79 -12
  421. package/packages/visualizer/src/evidence/evidence.module.css +11 -1
  422. package/packages/visualizer/src/evidence/feature-page.tsx +7 -3
  423. package/packages/visualizer/src/evidence/model-page.tsx +6 -2
  424. package/packages/visualizer/src/evidence/project-home.tsx +45 -0
  425. package/packages/visualizer/src/evidence/recording-page.tsx +4 -1
  426. package/packages/visualizer/src/evidence/spec-index.tsx +2 -1
  427. package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
  428. package/packages/visualizer/src/evidence/test-filter.tsx +21 -0
  429. package/packages/visualizer/src/find/Finder.tsx +1 -7
  430. package/packages/visualizer/src/find/open.ts +4 -0
  431. package/packages/visualizer/src/live/live-query.ts +21 -0
  432. package/packages/visualizer/src/live/live-runs.tsx +69 -0
  433. package/packages/visualizer/src/live/live-stream.ts +225 -0
  434. package/packages/visualizer/src/live/report-progress.ts +62 -0
  435. package/packages/visualizer/src/live/test-progress.tsx +34 -0
  436. package/packages/visualizer/src/runtime.ts +35 -0
  437. package/packages/visualizer/src/state/ui-store.ts +3 -2
  438. package/packages/visualizer/src/views/ChartPage.tsx +2 -0
  439. package/packages/visualizer/src/views/ChartTests.tsx +43 -0
  440. package/packages/visualizer/src/views/SidePanel.tsx +2 -0
  441. package/packages/visualizer/vite/atlas-files.test.ts +4 -4
  442. package/packages/visualizer/vite/atlas-files.ts +1 -5
  443. package/packages/visualizer/vite/atlas-server-proxy.ts +37 -0
  444. package/packages/visualizer/vite.config.ts +11 -3
@@ -1,8 +1,19 @@
1
+ /**
2
+ * @module Playwright screenshots, clips and paced actions
3
+ *
4
+ * The media side of the Playwright driver: `UserActions` taps and types like a person (pausing, and drawing a touch
5
+ * indicator in showcase videos) while logging each action to the step's timeline; `captureScreen` takes a settled,
6
+ * deterministic screenshot at device resolution plus a WebP thumbnail; `ScreencastRecorder` records an H.264 clip at
7
+ * device resolution; `ffmpeg` runs the `ffmpeg` binary, which must be on the PATH for thumbnails and clips.
8
+ *
9
+ * @see docs/statecharts.md
10
+ */
1
11
  import { spawn } from 'node:child_process';
2
12
  import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
13
  import path from 'node:path';
4
14
  import { locate } from './signals.js';
5
15
  const isLocator = (target) => 'waitFor' in target;
16
+ /** The default pacing of showcase mode: a visible glide and pauses, so a person can follow the clip. */
6
17
  export const SHOWCASE_PACING = {
7
18
  moveMs: 600,
8
19
  pauseBeforeTapMs: 300,
@@ -10,6 +21,7 @@ export const SHOWCASE_PACING = {
10
21
  holdAfterMs: 1000,
11
22
  holdBeforeMs: 500
12
23
  };
24
+ /** No pauses at all; the pacing of fast mode. */
13
25
  export const FAST_PACING = {
14
26
  moveMs: 0,
15
27
  pauseBeforeTapMs: 0,
@@ -17,6 +29,10 @@ export const FAST_PACING = {
17
29
  holdAfterMs: 0,
18
30
  holdBeforeMs: 0
19
31
  };
32
+ /**
33
+ * The default `device` of `playwrightDriver`: a 390 by 844 CSS pixel viewport at device scale factor 3 (screenshots
34
+ * are 1170 by 2532 pixels), a mobile user agent and touch.
35
+ */
20
36
  export const PHONE = {
21
37
  viewport: { width: 390, height: 844 },
22
38
  deviceScaleFactor: 3,
@@ -66,7 +82,10 @@ const TOUCH_OVERLAY = `(() => {
66
82
  };
67
83
  if (document.documentElement) install(); else document.addEventListener('DOMContentLoaded', install);
68
84
  })();`;
69
- /** Taps and typing a person would do, paced for showcase videos and logged to the step timeline. */
85
+ /**
86
+ * Taps and typing a person would do, paced for showcase videos and logged to the step timeline. Provided to event
87
+ * implementations as `user` on the `PlaywrightContext`. `showTouches` turns on the touch indicator (showcase mode).
88
+ */
70
89
  export class UserActions {
71
90
  page;
72
91
  pacing;
@@ -78,6 +97,10 @@ export class UserActions {
78
97
  this.timeline = timeline;
79
98
  this.showTouches = showTouches;
80
99
  }
100
+ /**
101
+ * Registers the touch indicator overlay to be added to every page load, so it exists before the app's scripts run.
102
+ * Call it once per page, before navigating.
103
+ */
81
104
  static async install(page) {
82
105
  await page.addInitScript(TOUCH_OVERLAY);
83
106
  }
@@ -91,7 +114,13 @@ export class UserActions {
91
114
  }, { fn, args })
92
115
  .catch(() => undefined);
93
116
  }
94
- /** Tap a locator or a signal (resolved with `locate`). */
117
+ /**
118
+ * Taps the centre of a locator or a signal (resolved with `locate`). Waits up to 15 s for it to be visible, scrolls
119
+ * it
120
+ * into view, logs a `tap` entry labelled `label` to the timeline, then clicks (10 s timeout). In showcase mode the
121
+ * touch
122
+ * indicator glides there first. Throws when the target has no position on screen.
123
+ */
95
124
  async tap(target, label) {
96
125
  const locator = isLocator(target) ? target : locate(this.page, target);
97
126
  await locator.waitFor({ state: 'visible', timeout: 15_000 });
@@ -113,6 +142,10 @@ export class UserActions {
113
142
  await this.overlay('press', false);
114
143
  }
115
144
  }
145
+ /**
146
+ * Taps `target`, clears it, then types `text` one character at a time, `pacing.typeDelayMs` apart. Logs a `tap` and
147
+ * a `type` entry labelled `label` to the timeline.
148
+ */
116
149
  async type(target, text, label) {
117
150
  const locator = isLocator(target) ? target : locate(this.page, target);
118
151
  await this.tap(locator, label);
@@ -120,18 +153,26 @@ export class UserActions {
120
153
  await locator.fill('');
121
154
  await locator.pressSequentially(text, { delay: this.pacing.typeDelayMs });
122
155
  }
156
+ /** Waits `ms` milliseconds; does nothing when `ms` is zero or less. */
123
157
  async hold(ms) {
124
158
  if (ms > 0)
125
159
  await this.page.waitForTimeout(ms);
126
160
  }
161
+ /** Hides the touch indicator, so a screenshot does not show it. */
127
162
  async hideTouches() {
128
163
  await this.overlay('visible', false);
129
164
  }
165
+ /** Shows the touch indicator again after `hideTouches`. */
130
166
  async showTouchLayer() {
131
167
  await this.overlay('visible', true);
132
168
  }
133
169
  }
134
- /** A settled, deterministic capture at device resolution, with the touch layer hidden. */
170
+ /**
171
+ * Takes a settled, deterministic screenshot at device resolution, with the touch layer hidden: waits for the network to
172
+ * go quiet (up to 5 s) and for fonts, disables animations and hides the caret. Writes the PNG to `file` (creating its
173
+ * folder) and a 400 px wide WebP thumbnail beside it with FFmpeg. `mask` lists locators to paint over. Rejects when
174
+ * FFmpeg is missing or fails.
175
+ */
135
176
  export async function captureScreen(page, driver, file, mask = []) {
136
177
  mkdirSync(path.dirname(file), { recursive: true });
137
178
  await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => undefined);
@@ -160,6 +201,10 @@ export async function captureScreen(page, driver, file, mask = []) {
160
201
  ]);
161
202
  return { png: file, webp };
162
203
  }
204
+ /**
205
+ * Runs `ffmpeg` with `args` and resolves when it exits with code 0. Rejects with the spawn error when `ffmpeg` is not
206
+ * on the PATH, or with `ffmpeg <code>: <stderr>` when it fails.
207
+ */
163
208
  export function ffmpeg(args) {
164
209
  return new Promise((resolve, reject) => {
165
210
  const child = spawn('ffmpeg', args, { stdio: ['ignore', 'ignore', 'pipe'] });
@@ -170,7 +215,7 @@ export function ffmpeg(args) {
170
215
  });
171
216
  }
172
217
  /**
173
- * Records a clip at the phone's physical resolution (1170×2532). Chrome's screencast, and
218
+ * Records a clip of a page at its physical resolution (1170×2532 for the default `PHONE`). Chrome's screencast, and
174
219
  * Playwright's built-in video that rides on it, only deliver CSS-pixel frames (390×844) and
175
220
  * upscale them, so text goes soft. Instead this loops device-scale JPEG captures (about 20 fps
176
221
  * here), keeps each frame's own timestamp, and encodes H.264 at 30 fps, holding each frame for as
@@ -189,6 +234,7 @@ export class ScreencastRecorder {
189
234
  this.page = page;
190
235
  this.scale = scale;
191
236
  }
237
+ /** Begins capturing frames of the page into `workDirectory`, which is emptied first. Call `stop` to encode them. */
192
238
  async start(workDirectory) {
193
239
  this.directory = workDirectory;
194
240
  rmSync(workDirectory, { recursive: true, force: true });
@@ -220,6 +266,11 @@ export class ScreencastRecorder {
220
266
  }
221
267
  })();
222
268
  }
269
+ /**
270
+ * Stops capturing and encodes the frames to the MP4 file `output` (the poster is written beside it, with `.mp4`
271
+ * replaced by `.poster.webp`), then deletes the work directory. Resolves to `{ video, poster, durationMs }`, or
272
+ * `null` when no frame was captured.
273
+ */
223
274
  async stop(output) {
224
275
  this.stoppedAt = Date.now();
225
276
  this.running = false;
@@ -1,24 +1,63 @@
1
+ /**
2
+ * @module Playwright signal helpers
3
+ *
4
+ * Maps the driver-neutral `Signal` values (test ID, role, label, text, CSS) to Playwright locators, and builds state
5
+ * recognisers from them: `recognize` evaluates a UI signature, `checkSignals` verifies quoted spec text is on screen,
6
+ * and `screen` wraps both into a `StateImplementation` for any context that exposes `page`. Also re-exports the
7
+ * signal constructors so an implementation imports everything from `@crvouga/atlas/playwright`. Visibility always
8
+ * means the first visible match.
9
+ *
10
+ * @see docs/statecharts.md
11
+ */
1
12
  import type { CheckResult, RecognizerResult, StateImplementation } from '../../core/src/index.js';
2
13
  import type { Signal } from '../../core/src/signals.js';
3
14
  import type { Locator, Page } from 'playwright';
4
15
  export { css, describeSignal, label, role, testId, text, type Signal } from '../../core/src/signals.js';
5
- /** A signal as a Playwright locator: the first visible match, with Playwright's own role and text rules. */
16
+ /**
17
+ * Resolves a `Signal` to a Playwright locator for the first visible match, using Playwright's own role, label and
18
+ * text rules (text matches are substrings unless `exact`).
19
+ */
6
20
  export declare function locate(page: Page, signal: Signal): Locator;
21
+ /**
22
+ * A state's UI signature: every `all` signal visible, at least one `any` signal visible (when `any` is given), and no
23
+ * `none` signal visible.
24
+ */
7
25
  export type SignatureSpec = {
8
26
  all?: Signal[];
9
27
  any?: Signal[];
10
28
  none?: Signal[];
11
29
  };
12
- /** Recognise a state from its UI signature: every `all` visible, one `any` visible, no `none`. */
30
+ /**
31
+ * Recognises a state from its UI signature on `page`: every `all` signal visible, one `any` signal visible (when `any`
32
+ * is given), no `none` signal visible. Returns `matched` plus one entry per signal with what was expected and whether
33
+ * it
34
+ * was visible, which the report shows when the state is not recognised. A signal that cannot be evaluated counts as not
35
+ * visible.
36
+ */
13
37
  export declare function recognize(page: Page, spec: SignatureSpec): Promise<RecognizerResult>;
14
- /** Checks that a quoted piece of the spec is on screen; records expected and actual on failure. */
38
+ /**
39
+ * Checks that each quoted piece of the spec (`check`) is visible on screen; returns one result per check, with
40
+ * `expected` and `actual` filled in.
41
+ */
15
42
  export declare function checkSignals(page: Page, checks: {
16
43
  check: string;
17
44
  signal: Signal;
18
45
  }[]): Promise<CheckResult[]>;
19
46
  /**
20
- * A state implementation from a UI signature, for a context that exposes `page`. Extra fields
21
- * (`lookIn`, `settle`, `transient`, `sameAs`, `checks`) pass through.
47
+ * Builds a state implementation from a UI signature, for a context that exposes `page` (the `PlaywrightContext`). The
48
+ * state is recognised as `recognize` describes; `checks` become the state's `meta.checks` results. The other fields
49
+ * (`lookIn`, `settle`, `transient`, `sameAs`) pass through to the `StateImplementation`.
50
+ *
51
+ * @see docs/statecharts.md
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { role, screen, text } from '@crvouga/atlas/playwright';
56
+ *
57
+ * const states = {
58
+ * 'No todos': screen({ all: [text('Nothing to do yet')], none: [role('listitem')] })
59
+ * };
60
+ * ```
22
61
  */
23
62
  export declare function screen<C extends {
24
63
  page: Page;
@@ -1,6 +1,9 @@
1
1
  import { describeSignal } from '../../core/src/signals.js';
2
2
  export { css, describeSignal, label, role, testId, text } from '../../core/src/signals.js';
3
- /** A signal as a Playwright locator: the first visible match, with Playwright's own role and text rules. */
3
+ /**
4
+ * Resolves a `Signal` to a Playwright locator for the first visible match, using Playwright's own role, label and
5
+ * text rules (text matches are substrings unless `exact`).
6
+ */
4
7
  export function locate(page, signal) {
5
8
  if ('testId' in signal)
6
9
  return page.getByTestId(signal.testId).filter({ visible: true }).first();
@@ -15,7 +18,13 @@ export function locate(page, signal) {
15
18
  return page.getByText(signal.text, signal.exact ? { exact: true } : {}).filter({ visible: true }).first();
16
19
  }
17
20
  const isVisible = (page, signal) => locate(page, signal).isVisible().catch(() => false);
18
- /** Recognise a state from its UI signature: every `all` visible, one `any` visible, no `none`. */
21
+ /**
22
+ * Recognises a state from its UI signature on `page`: every `all` signal visible, one `any` signal visible (when `any`
23
+ * is given), no `none` signal visible. Returns `matched` plus one entry per signal with what was expected and whether
24
+ * it
25
+ * was visible, which the report shows when the state is not recognised. A signal that cannot be evaluated counts as not
26
+ * visible.
27
+ */
19
28
  export async function recognize(page, spec) {
20
29
  const signals = [];
21
30
  for (const s of spec.all ?? [])
@@ -31,7 +40,10 @@ export async function recognize(page, spec) {
31
40
  signals.push({ signal: describeSignal(s), expected: 'hidden', visible: await isVisible(page, s) });
32
41
  return { matched: signals.every((x) => (x.expected === 'visible' ? x.visible : !x.visible)), signals };
33
42
  }
34
- /** Checks that a quoted piece of the spec is on screen; records expected and actual on failure. */
43
+ /**
44
+ * Checks that each quoted piece of the spec (`check`) is visible on screen; returns one result per check, with
45
+ * `expected` and `actual` filled in.
46
+ */
35
47
  export async function checkSignals(page, checks) {
36
48
  const results = [];
37
49
  for (const { check, signal } of checks) {
@@ -41,8 +53,20 @@ export async function checkSignals(page, checks) {
41
53
  return results;
42
54
  }
43
55
  /**
44
- * A state implementation from a UI signature, for a context that exposes `page`. Extra fields
45
- * (`lookIn`, `settle`, `transient`, `sameAs`, `checks`) pass through.
56
+ * Builds a state implementation from a UI signature, for a context that exposes `page` (the `PlaywrightContext`). The
57
+ * state is recognised as `recognize` describes; `checks` become the state's `meta.checks` results. The other fields
58
+ * (`lookIn`, `settle`, `transient`, `sameAs`) pass through to the `StateImplementation`.
59
+ *
60
+ * @see docs/statecharts.md
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * import { role, screen, text } from '@crvouga/atlas/playwright';
65
+ *
66
+ * const states = {
67
+ * 'No todos': screen({ all: [text('Nothing to do yet')], none: [role('listitem')] })
68
+ * };
69
+ * ```
46
70
  */
47
71
  export function screen(spec) {
48
72
  return {
@@ -0,0 +1,44 @@
1
+ type Loose = Record<string, unknown>;
2
+ /** What {@link readAttachmentFiles} found. */
3
+ export type AttachmentFileResult = {
4
+ /** A copy of the holder in which every readable `path` attachment became a `data` attachment (and `path` is gone). The input is not mutated. */
5
+ readonly value: Loose;
6
+ /** How many files were read. */
7
+ readonly attached: number;
8
+ /** The total size of those files, in bytes. */
9
+ readonly bytes: number;
10
+ /** One line per attachment that was left out (missing, not allowed, too large, over budget), prefixed with its name. */
11
+ readonly warnings: readonly string[];
12
+ };
13
+ /** A budget shared by every call of one publish, so many files cannot add up to more than can be held. */
14
+ export type AttachmentBudget = {
15
+ /** Bytes still available; each file read subtracts its size. A file larger than this is left out, not truncated. */
16
+ remaining: number;
17
+ };
18
+ /** Options of {@link readAttachmentFiles}. */
19
+ export type ReadOptions = {
20
+ /** Largest single file, in bytes. Default 512 MiB. */
21
+ readonly maxBytes?: number;
22
+ /** Shared total-size budget; mutated as files are read. Omit for no total limit. */
23
+ readonly budget?: AttachmentBudget;
24
+ /**
25
+ * A path the person running the command typed (`--attach`) is theirs to choose. A path inside a *report* is
26
+ * data from a test run, which is not: it may name only files inside `roots`, never a hidden file, and never a
27
+ * link that leads out.
28
+ */
29
+ readonly trusted?: boolean;
30
+ };
31
+ /**
32
+ * Reads the files that attachments name (`path`), at every level (the holder, its `steps` and its `attempts`,
33
+ * recursively), and hands their bytes over as `data` with a `contentType` guessed from the file name when the
34
+ * attachment gave none.
35
+ *
36
+ * A file that is missing, not allowed or too large is left out with a warning: evidence that could not be
37
+ * found never stops the results from being published. Attachments without a `path` are kept untouched.
38
+ *
39
+ * @param holder A check, step, attempt or any object that may have `attachments`, `steps` and `attempts`.
40
+ * @param roots Folders a relative path is resolved against (in order), and the only folders an untrusted path may name.
41
+ * @returns The rewritten copy, counts and warnings; never throws for a bad file.
42
+ */
43
+ export declare function readAttachmentFiles(holder: Loose, roots: readonly string[], options?: ReadOptions): Promise<AttachmentFileResult>;
44
+ export {};
@@ -0,0 +1,121 @@
1
+ /**
2
+ * @module Reading the files attachments name
3
+ *
4
+ * Turns an attachment that names a file on disk (`path`) into one that carries its bytes (`data`), just before a
5
+ * layer is published. It owns the safety rules for doing so: a path that came from a test report may only name
6
+ * files inside the allowed roots, never a hidden file, and never a link that leads out; a path the person running
7
+ * the command typed is trusted. It does not upload anything. Node-only (reads the file system).
8
+ *
9
+ * @see docs/report-contract.md (evidence rule 7: paths inside a report are data from a test run)
10
+ */
11
+ import { readFile, realpath, stat } from 'node:fs/promises';
12
+ import path from 'node:path';
13
+ import { contentTypeOf } from './media-types.js';
14
+ /**
15
+ * Reads the files that attachments name (`path`), at every level (the holder, its `steps` and its `attempts`,
16
+ * recursively), and hands their bytes over as `data` with a `contentType` guessed from the file name when the
17
+ * attachment gave none.
18
+ *
19
+ * A file that is missing, not allowed or too large is left out with a warning: evidence that could not be
20
+ * found never stops the results from being published. Attachments without a `path` are kept untouched.
21
+ *
22
+ * @param holder A check, step, attempt or any object that may have `attachments`, `steps` and `attempts`.
23
+ * @param roots Folders a relative path is resolved against (in order), and the only folders an untrusted path may name.
24
+ * @returns The rewritten copy, counts and warnings; never throws for a bad file.
25
+ */
26
+ export async function readAttachmentFiles(holder, roots, options = {}) {
27
+ const warnings = [];
28
+ const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
29
+ let attached = 0, bytes = 0;
30
+ async function walk(value) {
31
+ const out = { ...value };
32
+ if (Array.isArray(out.attachments)) {
33
+ const kept = [];
34
+ for (const given of out.attachments) {
35
+ const file = typeof given.path === 'string' ? given.path : undefined;
36
+ if (file === undefined) {
37
+ kept.push(given);
38
+ continue;
39
+ }
40
+ const name = String(given.name ?? file);
41
+ const { path: _path, ...rest } = given;
42
+ const found = await locate(file, roots, options.trusted === true);
43
+ if (found.problem) {
44
+ warnings.push(`${name}: ${found.problem}`);
45
+ continue;
46
+ }
47
+ const size = (await stat(found.file)).size;
48
+ if (size > maxBytes) {
49
+ warnings.push(`${name}: ${(size / 1024 / 1024).toFixed(0)} MiB is over the limit of ${Math.round(maxBytes / 1024 / 1024)} MiB`);
50
+ continue;
51
+ }
52
+ if (options.budget && size > options.budget.remaining) {
53
+ warnings.push(`${name}: the total size of attachments would pass the budget (raise --max-total-attachment-mb, or attach a uri)`);
54
+ continue;
55
+ }
56
+ if (options.budget)
57
+ options.budget.remaining -= size;
58
+ attached += 1;
59
+ bytes += size;
60
+ kept.push({ ...rest, data: new Uint8Array(await readFile(found.file)), ...(rest.contentType ? {} : { contentType: contentTypeOf(found.file) }) });
61
+ }
62
+ out.attachments = kept;
63
+ }
64
+ if (Array.isArray(out.steps)) {
65
+ const steps = [];
66
+ for (const step of out.steps)
67
+ steps.push(await walk(step));
68
+ out.steps = steps;
69
+ }
70
+ if (Array.isArray(out.attempts)) {
71
+ const attempts = [];
72
+ for (const attempt of out.attempts)
73
+ attempts.push(await walk(attempt));
74
+ out.attempts = attempts;
75
+ }
76
+ return out;
77
+ }
78
+ return { value: await walk(holder), attached, bytes, warnings };
79
+ }
80
+ /** Is `file` inside `root`, once both are real paths (so a link cannot lead out)? */
81
+ function inside(file, root) {
82
+ const relative = path.relative(root, file);
83
+ return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);
84
+ }
85
+ // Resolves a named file to a real path, or says why not. A trusted path may also be relative to the working
86
+ // directory; an untrusted one must land, after resolving links, inside a root and under no dot-named part.
87
+ async function locate(file, roots, trusted) {
88
+ const candidates = path.isAbsolute(file) ? [file] : [...roots.map(root => path.resolve(root, file)), ...(trusted ? [path.resolve(file)] : [])];
89
+ let outside = false;
90
+ for (const candidate of candidates) {
91
+ let real;
92
+ try {
93
+ real = await realpath(candidate);
94
+ if (!(await stat(real)).isFile())
95
+ continue;
96
+ }
97
+ catch {
98
+ continue;
99
+ }
100
+ if (trusted)
101
+ return { file: real };
102
+ const root = await firstRootHolding(real, roots);
103
+ // A hidden file or folder (.env, .aws, .git, .ssh) is never attached on a report's say-so.
104
+ if (!root || path.relative(root, real).split(path.sep).some(part => part.startsWith('.'))) {
105
+ outside = true;
106
+ continue;
107
+ }
108
+ return { file: real };
109
+ }
110
+ return { problem: outside ? `${file} is outside the folders reports may attach from (the report's own folder and each --attach-root), or is a hidden file: pass --attach-root <dir> to allow a folder` : `file not found (${file})` };
111
+ }
112
+ async function firstRootHolding(real, roots) {
113
+ for (const root of roots) {
114
+ try {
115
+ if (inside(real, await realpath(root)))
116
+ return await realpath(root);
117
+ }
118
+ catch { /* a root that is not there holds nothing */ }
119
+ }
120
+ return null;
121
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @module Serving attachment bytes safely
3
+ *
4
+ * Builds the HTTP response for an attachment's bytes. A producer chose the media type, and the bytes come from a
5
+ * test run, so they are treated as untrusted: never sniffed, never allowed to run scripts, never embedded by
6
+ * another site. Videos can be sought (HTTP range requests). Every route that serves attachment bytes must go
7
+ * through here (or add {@link UNTRUSTED_HEADERS}); it never reads from a store itself.
8
+ *
9
+ * @see docs/report-contract.md (evidence rule 8)
10
+ */
11
+ /**
12
+ * Headers for bytes that came from a test run: never sniffed (`nosniff`), a sandboxing content-security policy so a
13
+ * page navigated to at this address runs locked down, and `cross-origin-resource-policy: same-origin` so
14
+ * another site cannot embed it. Spread these into every response that serves attachment or recorded bytes.
15
+ */
16
+ export declare const UNTRUSTED_HEADERS: Readonly<Record<string, string>>;
17
+ /**
18
+ * Answers a request for one attachment's bytes with the untrusted-bytes headers, long-lived immutable caching
19
+ * and range support.
20
+ *
21
+ * The producer's `contentType` is used only when it is a well-formed media type (`type/subtype` with simple
22
+ * parameters); anything else is served as `application/octet-stream`. A matching `if-none-match` answers 304.
23
+ * A single `bytes=a-b`, `bytes=a-` or `bytes=-n` range answers 206, an unsatisfiable one 416; multiple ranges are
24
+ * not understood and get the whole body (200).
25
+ *
26
+ * @param etag A strong validator the caller derives from content identity (it is sent as given, so include the quotes).
27
+ */
28
+ export declare function attachmentResponse(request: Request, entry: {
29
+ readonly bytes: Uint8Array;
30
+ readonly contentType: string;
31
+ }, etag: string): Response;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @module Serving attachment bytes safely
3
+ *
4
+ * Builds the HTTP response for an attachment's bytes. A producer chose the media type, and the bytes come from a
5
+ * test run, so they are treated as untrusted: never sniffed, never allowed to run scripts, never embedded by
6
+ * another site. Videos can be sought (HTTP range requests). Every route that serves attachment bytes must go
7
+ * through here (or add {@link UNTRUSTED_HEADERS}); it never reads from a store itself.
8
+ *
9
+ * @see docs/report-contract.md (evidence rule 8)
10
+ */
11
+ /**
12
+ * Headers for bytes that came from a test run: never sniffed (`nosniff`), a sandboxing content-security policy so a
13
+ * page navigated to at this address runs locked down, and `cross-origin-resource-policy: same-origin` so
14
+ * another site cannot embed it. Spread these into every response that serves attachment or recorded bytes.
15
+ */
16
+ export const UNTRUSTED_HEADERS = {
17
+ 'x-content-type-options': 'nosniff',
18
+ // If a page is ever navigated to or framed from this address, it runs as a locked-down document.
19
+ 'content-security-policy': "sandbox; default-src 'none'; img-src data:; media-src 'self'; style-src 'unsafe-inline'",
20
+ 'cross-origin-resource-policy': 'same-origin'
21
+ };
22
+ /**
23
+ * Answers a request for one attachment's bytes with the untrusted-bytes headers, long-lived immutable caching
24
+ * and range support.
25
+ *
26
+ * The producer's `contentType` is used only when it is a well-formed media type (`type/subtype` with simple
27
+ * parameters); anything else is served as `application/octet-stream`. A matching `if-none-match` answers 304.
28
+ * A single `bytes=a-b`, `bytes=a-` or `bytes=-n` range answers 206, an unsatisfiable one 416; multiple ranges are
29
+ * not understood and get the whole body (200).
30
+ *
31
+ * @param etag A strong validator the caller derives from content identity (it is sent as given, so include the quotes).
32
+ */
33
+ export function attachmentResponse(request, entry, etag) {
34
+ // A media type is a short token list; anything with line breaks or odd characters is not one.
35
+ const type = /^[\w.+-]+\/[\w.+-]+(?:\s*;\s*[\w.-]+=[\w.\-"]+)*$/.test(entry.contentType) ? entry.contentType : 'application/octet-stream';
36
+ const headers = {
37
+ 'content-type': type,
38
+ ...UNTRUSTED_HEADERS,
39
+ 'cache-control': 'public, max-age=31536000, immutable',
40
+ 'accept-ranges': 'bytes',
41
+ etag
42
+ };
43
+ if (request.headers.get('if-none-match') === etag)
44
+ return new Response(null, { status: 304, headers });
45
+ const size = entry.bytes.length;
46
+ const range = /^bytes=(\d*)-(\d*)$/.exec(request.headers.get('range') ?? '');
47
+ if (!range || (range[1] === '' && range[2] === ''))
48
+ return new Response(entry.bytes, { headers: { ...headers, 'content-length': String(size) } });
49
+ // `bytes=-500` is the last 500 bytes; `bytes=500-` is from 500 on.
50
+ const start = range[1] === '' ? Math.max(0, size - Number(range[2])) : Number(range[1]);
51
+ const end = range[1] === '' || range[2] === '' ? size - 1 : Math.min(Number(range[2]), size - 1);
52
+ if (start >= size || start > end)
53
+ return new Response(null, { status: 416, headers: { ...headers, 'content-range': `bytes */${size}` } });
54
+ return new Response(entry.bytes.slice(start, end + 1), { status: 206, headers: { ...headers, 'content-range': `bytes ${start}-${end}/${size}`, 'content-length': String(end - start + 1) } });
55
+ }
@@ -0,0 +1,48 @@
1
+ import type { Credentials } from './sigv4.js';
2
+ type Env = Readonly<Record<string, string | undefined>>;
3
+ /**
4
+ * An async function returning currently valid credentials, with cache control. Concurrent calls share one lookup;
5
+ * cached credentials are reused until shortly before they expire (never, when they carry no expiry).
6
+ */
7
+ export type CredentialProvider = (() => Promise<Credentials>) & {
8
+ /** Drop what is cached, so the next call asks again. Used when a store says the credentials expired. */
9
+ invalidate(): void;
10
+ /** Where the credentials came from, once known: "environment", "web identity (IRSA)", "profile prod", … */
11
+ source(): string | null;
12
+ };
13
+ /** Options of {@link credentialChain}; every one exists so a test can replace a piece of the machine. */
14
+ export type ChainOptions = {
15
+ /** The environment variables to read. Default `process.env`. */
16
+ readonly env?: Env;
17
+ /** The HTTP client for STS, container and instance-metadata calls. Default global `fetch`. */
18
+ readonly fetch?: typeof fetch;
19
+ /** Reads a UTF-8 file. Default `fs.readFile`. */
20
+ readonly readFile?: (file: string) => Promise<string>;
21
+ /** Run a command and return its stdout, or null when it is not installed or fails. Default spawns it with a 20 s timeout. */
22
+ readonly run?: (command: string, args: readonly string[]) => Promise<string | null>;
23
+ /** The clock, epoch milliseconds. Default `Date.now`. */
24
+ readonly now?: () => number;
25
+ /** The home directory that holds `.aws/credentials`. Default `$HOME`, then the OS home. */
26
+ readonly home?: string;
27
+ /** Refresh this long before credentials expire, in milliseconds. Default 5 minutes. */
28
+ readonly skewMs?: number;
29
+ };
30
+ /**
31
+ * Creates a provider for the credentials an AWS SDK would find, in the same order, with no SDK: environment, web
32
+ * identity (EKS), container (ECS), shared credentials file, the AWS CLI (which covers SSO and assumed roles),
33
+ * and EC2 instance metadata. `ATLAS_S3_ACCESS_KEY_ID` / `ATLAS_S3_SECRET_ACCESS_KEY` (and `ATLAS_S3_SESSION_TOKEN`)
34
+ * come first so one store can use keys that are not the machine's AWS keys, such as Cloudflare R2 tokens.
35
+ *
36
+ * Nothing is looked up until the provider is first called. The first source that yields credentials wins and
37
+ * is cached; a source that throws (for example an STS error) is skipped and reported if no later source works.
38
+ * When none works the provider rejects with an error that lists every source tried and how to fix it. Set
39
+ * `ATLAS_NO_AWS_CLI` to skip the `aws` CLI step and `AWS_EC2_METADATA_DISABLED=true` to skip instance metadata.
40
+ *
41
+ * @example
42
+ * const credentials = await credentialChain()();
43
+ * credentials.accessKeyId;
44
+ */
45
+ export declare function credentialChain(options?: ChainOptions): CredentialProvider;
46
+ /** Wraps keys that are already known in a {@link CredentialProvider} that never refreshes (its `invalidate` does nothing). */
47
+ export declare function staticCredentials(credentials: Credentials): CredentialProvider;
48
+ export {};