@crvouga/atlas 0.3.0 → 0.5.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 (429) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +178 -604
  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 +30 -1
  9. package/dist/core/src/config.d.ts +70 -4
  10. package/dist/core/src/config.js +62 -3
  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 +69 -16
  30. package/dist/core/src/run/execute.js +67 -16
  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 +83 -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 +40 -4
  94. package/dist/reports/src/evidence-live.js +52 -4
  95. package/dist/reports/src/evidence-types.d.ts +70 -1
  96. package/dist/reports/src/evidence-types.js +5 -1
  97. package/dist/reports/src/evidence-view.d.ts +58 -6
  98. package/dist/reports/src/evidence-view.js +44 -5
  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 +52 -0
  120. package/dist/reports/src/http-store.js +145 -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 +364 -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 +48 -0
  134. package/dist/reports/src/layer-axes.js +71 -0
  135. package/dist/reports/src/layer-catalog.d.ts +28 -1
  136. package/dist/reports/src/layer-catalog.js +19 -2
  137. package/dist/reports/src/layer-merge.d.ts +98 -9
  138. package/dist/reports/src/layer-merge.js +154 -33
  139. package/dist/reports/src/layer-source.d.ts +18 -0
  140. package/dist/reports/src/layer-source.js +14 -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 +34 -1
  144. package/dist/reports/src/layers-route.d.ts +47 -4
  145. package/dist/reports/src/layers-route.js +88 -12
  146. package/dist/reports/src/media-types.d.ts +11 -0
  147. package/dist/reports/src/media-types.js +22 -0
  148. package/dist/reports/src/namespace-guard.d.ts +10 -1
  149. package/dist/reports/src/namespace-guard.js +10 -1
  150. package/dist/reports/src/navigation.d.ts +12 -0
  151. package/dist/reports/src/navigation.js +7 -0
  152. package/dist/reports/src/object-io.d.ts +11 -4
  153. package/dist/reports/src/object-io.js +4 -4
  154. package/dist/reports/src/object-store-conformance.d.ts +27 -0
  155. package/dist/reports/src/object-store-conformance.js +87 -0
  156. package/dist/reports/src/object-store.d.ts +33 -24
  157. package/dist/reports/src/object-store.js +17 -24
  158. package/dist/reports/src/parse-with.d.ts +16 -0
  159. package/dist/reports/src/parse-with.js +14 -0
  160. package/dist/reports/src/performance-part.d.ts +25 -1
  161. package/dist/reports/src/performance-part.js +15 -1
  162. package/dist/reports/src/performance.d.ts +22 -2
  163. package/dist/reports/src/performance.js +12 -2
  164. package/dist/reports/src/persist.d.ts +12 -3
  165. package/dist/reports/src/persist.js +12 -3
  166. package/dist/reports/src/phone.d.ts +18 -1
  167. package/dist/reports/src/phone.js +18 -1
  168. package/dist/reports/src/protocol.d.ts +30 -4
  169. package/dist/reports/src/protocol.js +15 -4
  170. package/dist/reports/src/publication.d.ts +21 -1
  171. package/dist/reports/src/publication.js +21 -1
  172. package/dist/reports/src/publish-checks.d.ts +35 -0
  173. package/dist/reports/src/publish-checks.js +122 -0
  174. package/dist/reports/src/publish.d.ts +44 -9
  175. package/dist/reports/src/publish.js +40 -7
  176. package/dist/reports/src/read.d.ts +46 -16
  177. package/dist/reports/src/read.js +38 -14
  178. package/dist/reports/src/recording-bundle.d.ts +13 -2
  179. package/dist/reports/src/recording-bundle.js +4 -2
  180. package/dist/reports/src/recording-keys.d.ts +15 -1
  181. package/dist/reports/src/recording-keys.js +15 -1
  182. package/dist/reports/src/remote-report.d.ts +28 -7
  183. package/dist/reports/src/remote-report.js +15 -7
  184. package/dist/reports/src/report-cli.d.ts +51 -0
  185. package/dist/reports/src/report-cli.js +485 -0
  186. package/dist/reports/src/report-index.d.ts +99 -0
  187. package/dist/reports/src/report-index.js +337 -0
  188. package/dist/reports/src/report-loader.d.ts +10 -0
  189. package/dist/reports/src/report-loader.js +6 -0
  190. package/dist/reports/src/report-parts.d.ts +29 -3
  191. package/dist/reports/src/report-parts.js +40 -3
  192. package/dist/reports/src/report-status.d.ts +48 -0
  193. package/dist/reports/src/report-status.js +156 -0
  194. package/dist/reports/src/report-types.d.ts +73 -0
  195. package/dist/reports/src/report-types.js +3 -0
  196. package/dist/reports/src/reporter.d.ts +67 -0
  197. package/dist/reports/src/reporter.js +128 -0
  198. package/dist/reports/src/reports-explorer-route.d.ts +541 -0
  199. package/dist/reports/src/reports-explorer-route.js +11 -0
  200. package/dist/reports/src/reports-list-route.d.ts +9 -0
  201. package/dist/reports/src/reports-list-route.js +9 -0
  202. package/dist/reports/src/reports-route.d.ts +561 -22
  203. package/dist/reports/src/reports-route.js +11 -3
  204. package/dist/reports/src/reports-store.d.ts +19 -2
  205. package/dist/reports/src/reports-store.js +10 -2
  206. package/dist/reports/src/resilient-cache.d.ts +14 -1
  207. package/dist/reports/src/resilient-cache.js +8 -1
  208. package/dist/reports/src/retention.d.ts +17 -1
  209. package/dist/reports/src/retention.js +5 -1
  210. package/dist/reports/src/run-bundle.d.ts +63 -0
  211. package/dist/reports/src/run-bundle.js +159 -0
  212. package/dist/reports/src/s3-store.d.ts +79 -0
  213. package/dist/reports/src/s3-store.js +295 -0
  214. package/dist/reports/src/scenario.d.ts +14 -4
  215. package/dist/reports/src/scenario.js +5 -4
  216. package/dist/reports/src/sigv4.d.ts +72 -0
  217. package/dist/reports/src/sigv4.js +96 -0
  218. package/dist/reports/src/snapshot-bundle.d.ts +24 -1
  219. package/dist/reports/src/snapshot-bundle.js +27 -2
  220. package/dist/reports/src/snapshot-surface.d.ts +20 -9
  221. package/dist/reports/src/snapshot-surface.js +32 -19
  222. package/dist/reports/src/source-content.d.ts +23 -0
  223. package/dist/reports/src/source-content.js +30 -1
  224. package/dist/reports/src/source-entries.d.ts +13 -0
  225. package/dist/reports/src/source-entries.js +6 -0
  226. package/dist/reports/src/source-part.d.ts +15 -0
  227. package/dist/reports/src/source-part.js +8 -0
  228. package/dist/reports/src/source-reader.d.ts +11 -0
  229. package/dist/reports/src/source-reader.js +5 -0
  230. package/dist/reports/src/spec-evidence.d.ts +12 -2
  231. package/dist/reports/src/spec-evidence.js +6 -2
  232. package/dist/reports/src/spec-inventory.d.ts +15 -1
  233. package/dist/reports/src/spec-inventory.js +22 -1
  234. package/dist/reports/src/spec-link.d.ts +28 -0
  235. package/dist/reports/src/spec-link.js +70 -0
  236. package/dist/reports/src/spec-set.d.ts +63 -0
  237. package/dist/reports/src/spec-set.js +177 -0
  238. package/dist/reports/src/status.d.ts +10 -0
  239. package/dist/reports/src/status.js +5 -0
  240. package/dist/reports/src/store-prefix.d.ts +28 -0
  241. package/dist/reports/src/store-prefix.js +22 -0
  242. package/dist/reports/src/store-url.d.ts +89 -0
  243. package/dist/reports/src/store-url.js +160 -0
  244. package/dist/reports/src/test-catalog-entity.d.ts +11 -0
  245. package/dist/reports/src/test-catalog-entity.js +6 -0
  246. package/dist/reports/src/types.d.ts +6 -1
  247. package/dist/reports/src/use-report-nav.d.ts +35 -0
  248. package/dist/reports/src/use-report-nav.js +12 -0
  249. package/dist/schema/src/api.d.ts +799 -0
  250. package/dist/schema/src/api.js +201 -0
  251. package/dist/schema/src/cli-results.d.ts +210 -0
  252. package/dist/schema/src/cli-results.js +124 -0
  253. package/dist/schema/src/common.d.ts +53 -0
  254. package/dist/schema/src/common.js +42 -0
  255. package/dist/schema/src/index.d.ts +14 -0
  256. package/dist/schema/src/index.js +14 -0
  257. package/dist/schema/src/layer.d.ts +2273 -0
  258. package/dist/schema/src/layer.js +462 -0
  259. package/dist/schema/src/manifest.d.ts +95 -0
  260. package/dist/schema/src/manifest.js +84 -0
  261. package/dist/schema/src/report-sources.d.ts +28 -0
  262. package/dist/schema/src/report-sources.js +25 -0
  263. package/dist/schema/src/runs-index.d.ts +16 -0
  264. package/dist/schema/src/runs-index.js +15 -0
  265. package/dist/schema/src/server-config.d.ts +93 -0
  266. package/dist/schema/src/server-config.js +73 -0
  267. package/dist/schema/src/spec.d.ts +69 -0
  268. package/dist/schema/src/spec.js +58 -0
  269. package/dist/server/src/app.d.ts +62 -0
  270. package/dist/server/src/app.js +419 -0
  271. package/dist/server/src/auth.d.ts +41 -0
  272. package/dist/server/src/auth.js +59 -0
  273. package/dist/server/src/config.d.ts +83 -0
  274. package/dist/server/src/config.js +187 -0
  275. package/dist/server/src/dev.d.ts +12 -0
  276. package/dist/server/src/dev.js +80 -0
  277. package/dist/server/src/events.d.ts +29 -0
  278. package/dist/server/src/events.js +101 -0
  279. package/dist/server/src/host.d.ts +30 -0
  280. package/dist/server/src/host.js +115 -0
  281. package/dist/server/src/index.d.ts +16 -0
  282. package/dist/server/src/index.js +16 -0
  283. package/dist/server/src/llms.d.ts +19 -0
  284. package/dist/server/src/llms.js +63 -0
  285. package/dist/server/src/main.d.ts +30 -0
  286. package/dist/server/src/main.js +112 -0
  287. package/dist/server/src/openapi.d.ts +59 -0
  288. package/dist/server/src/openapi.js +374 -0
  289. package/dist/server/src/problem.d.ts +8 -0
  290. package/dist/server/src/problem.js +39 -0
  291. package/dist/server/src/site.d.ts +13 -0
  292. package/dist/server/src/site.js +43 -0
  293. package/dist/server/src/store-api.d.ts +28 -0
  294. package/dist/server/src/store-api.js +159 -0
  295. package/dist/server/src/validate.d.ts +71 -0
  296. package/dist/server/src/validate.js +197 -0
  297. package/dist/vitest/src/index.d.ts +27 -5
  298. package/dist/vitest/src/index.js +18 -5
  299. package/dist/webdriver/src/driver.d.ts +63 -11
  300. package/dist/webdriver/src/driver.js +57 -6
  301. package/dist/webdriver/src/index.d.ts +8 -0
  302. package/dist/webdriver/src/index.js +1 -0
  303. package/dist/webdriver/src/protocol.d.ts +75 -6
  304. package/dist/webdriver/src/protocol.js +79 -6
  305. package/docs/agents.md +137 -0
  306. package/docs/deploying.md +345 -0
  307. package/docs/dogfooding.md +175 -0
  308. package/docs/extending.md +242 -0
  309. package/docs/integrating.md +485 -0
  310. package/docs/reference/bun.md +141 -0
  311. package/docs/reference/cli.md +146 -0
  312. package/docs/reference/core.md +2426 -0
  313. package/docs/reference/detox.md +150 -0
  314. package/docs/reference/http-api.md +303 -0
  315. package/docs/reference/playwright.md +303 -0
  316. package/docs/reference/reports.md +7098 -0
  317. package/docs/reference/schema.md +1651 -0
  318. package/docs/reference/server.md +685 -0
  319. package/docs/reference/vitest.md +45 -0
  320. package/docs/reference/webdriver.md +330 -0
  321. package/docs/report-backends.md +144 -0
  322. package/docs/report-contract.md +400 -0
  323. package/docs/reporting-issues.md +160 -0
  324. package/docs/server.md +285 -0
  325. package/docs/statecharts.md +641 -0
  326. package/llms-full.txt +3506 -0
  327. package/llms.txt +47 -0
  328. package/package.json +33 -5
  329. package/packages/core/README.md +2 -1
  330. package/packages/reports/README.md +69 -2
  331. package/packages/reports/STORAGE.md +5 -1
  332. package/packages/schema/README.md +14 -0
  333. package/packages/schema/schemas/check-detail.schema.json +1032 -0
  334. package/packages/schema/schemas/checks-result.schema.json +26 -0
  335. package/packages/schema/schemas/command-error.schema.json +35 -0
  336. package/packages/schema/schemas/doctor-result.schema.json +60 -0
  337. package/packages/schema/schemas/effective-delta.schema.json +490 -0
  338. package/packages/schema/schemas/effective-evidence.schema.json +471 -0
  339. package/packages/schema/schemas/evidence-page.schema.json +271 -0
  340. package/packages/schema/schemas/hello-event.schema.json +22 -0
  341. package/packages/schema/schemas/init-plan.schema.json +347 -0
  342. package/packages/schema/schemas/layer-rows.schema.json +199 -0
  343. package/packages/schema/schemas/layer.schema.json +2888 -0
  344. package/packages/schema/schemas/merge-result.schema.json +40 -0
  345. package/packages/schema/schemas/outline.schema.json +62 -0
  346. package/packages/schema/schemas/problem.schema.json +37 -0
  347. package/packages/schema/schemas/publish-checks.schema.json +1413 -0
  348. package/packages/schema/schemas/publish-result.schema.json +168 -0
  349. package/packages/schema/schemas/ready.schema.json +68 -0
  350. package/packages/schema/schemas/report-event.schema.json +100 -0
  351. package/packages/schema/schemas/report-status.schema.json +370 -0
  352. package/packages/schema/schemas/server-config.schema.json +194 -0
  353. package/packages/schema/schemas/server-info.schema.json +114 -0
  354. package/packages/schema/schemas/spec-set.schema.json +103 -0
  355. package/packages/schema/schemas/store-listing.schema.json +29 -0
  356. package/packages/schema/schemas/store-write-result.schema.json +28 -0
  357. package/packages/schema/schemas/stream-result.schema.json +59 -0
  358. package/packages/server/README.md +43 -0
  359. package/packages/server/openapi.json +6699 -0
  360. package/packages/visualizer/README.md +27 -5
  361. package/packages/visualizer/example/specs/coffee/ordering/cart.qnt +7 -0
  362. package/packages/visualizer/example/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  363. package/packages/visualizer/example/specs/coffee/ordering/drink-price.dmn +24 -0
  364. package/packages/visualizer/example/specs/coffee/ordering/loyalty.feature +7 -0
  365. package/packages/visualizer/example/specs/coffee/ordering/ordering.feature +36 -0
  366. package/packages/visualizer/example/specs/coffee/ordering/payment.als +4 -0
  367. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/cart.qnt +7 -0
  368. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  369. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/drink-price.dmn +24 -0
  370. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/loyalty.feature +7 -0
  371. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/ordering.feature +36 -0
  372. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/payment.als +4 -0
  373. package/packages/visualizer/fixtures/full/specs/coffee/ordering/cart.qnt +7 -0
  374. package/packages/visualizer/fixtures/full/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  375. package/packages/visualizer/fixtures/full/specs/coffee/ordering/drink-price.dmn +24 -0
  376. package/packages/visualizer/fixtures/full/specs/coffee/ordering/loyalty.feature +7 -0
  377. package/packages/visualizer/fixtures/full/specs/coffee/ordering/ordering.feature +36 -0
  378. package/packages/visualizer/fixtures/full/specs/coffee/ordering/payment.als +4 -0
  379. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/cart.qnt +7 -0
  380. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  381. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/drink-price.dmn +24 -0
  382. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/loyalty.feature +7 -0
  383. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/ordering.feature +36 -0
  384. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/payment.als +4 -0
  385. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/cart.qnt +7 -0
  386. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  387. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/drink-price.dmn +24 -0
  388. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/loyalty.feature +7 -0
  389. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/ordering.feature +36 -0
  390. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/payment.als +4 -0
  391. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/cart.qnt +7 -0
  392. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  393. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/drink-price.dmn +24 -0
  394. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/loyalty.feature +7 -0
  395. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/ordering.feature +36 -0
  396. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/payment.als +4 -0
  397. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/cart.qnt +7 -0
  398. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  399. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/drink-price.dmn +24 -0
  400. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/loyalty.feature +7 -0
  401. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/ordering.feature +36 -0
  402. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/payment.als +4 -0
  403. package/packages/visualizer/scripts/make-fixtures.ts +2 -0
  404. package/packages/visualizer/scripts/publish-data.ts +1 -2
  405. package/packages/visualizer/src/app/AppShell.tsx +9 -2
  406. package/packages/visualizer/src/app/report-sources.tsx +6 -4
  407. package/packages/visualizer/src/app/router.tsx +3 -3
  408. package/packages/visualizer/src/components/Header.module.css +35 -0
  409. package/packages/visualizer/src/components/Header.tsx +41 -4
  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/evidence/api.ts +14 -2
  413. package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
  414. package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
  415. package/packages/visualizer/src/evidence/check-detail.tsx +38 -4
  416. package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
  417. package/packages/visualizer/src/evidence/evidence.module.css +20 -0
  418. package/packages/visualizer/src/evidence/marks.tsx +2 -0
  419. package/packages/visualizer/src/evidence/overview.tsx +3 -3
  420. package/packages/visualizer/src/evidence/sources-panel.tsx +88 -34
  421. package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
  422. package/packages/visualizer/src/find/Finder.tsx +1 -7
  423. package/packages/visualizer/src/find/open.ts +4 -0
  424. package/packages/visualizer/src/live/live-stream.ts +204 -0
  425. package/packages/visualizer/src/runtime.ts +19 -0
  426. package/packages/visualizer/src/state/ui-store.ts +6 -2
  427. package/packages/visualizer/vite/atlas-server-proxy.ts +36 -0
  428. package/packages/visualizer/vite/example-reports.ts +475 -0
  429. package/packages/visualizer/vite.config.ts +18 -2
@@ -20,6 +20,7 @@ function carriedUiBlobs(test) {
20
20
  carryCache.set(test.report.steps, out);
21
21
  return out;
22
22
  }
23
+ /** Flattens the frames of a step tree depth first (a step's own frames, then its children's), in step order, not capture-time order. Appends to `out`. */
23
24
  export function flattenFrames(steps, out = [], depth = 0) {
24
25
  for (const step of steps) {
25
26
  for (const frame of step.frames) {
@@ -29,6 +30,7 @@ export function flattenFrames(steps, out = [], depth = 0) {
29
30
  }
30
31
  return out;
31
32
  }
33
+ /** Flattens a step tree depth first into rows that know their depth and parent. Appends to `out`. */
32
34
  export function flattenSteps(steps, out = [], depth = 0, parent = null) {
33
35
  for (const step of steps) {
34
36
  const row = { step, depth, parent, index: out.length };
@@ -37,6 +39,7 @@ export function flattenSteps(steps, out = [], depth = 0, parent = null) {
37
39
  }
38
40
  return out;
39
41
  }
42
+ /** The first frame of `step`, or failing that of its first descendant that has one; null when none of them has a frame. */
40
43
  export function firstFrameFor(step, frames) {
41
44
  for (const f of frames) {
42
45
  if (f.step === step)
@@ -49,6 +52,7 @@ export function firstFrameFor(step, frames) {
49
52
  }
50
53
  return null;
51
54
  }
55
+ /** The last frame (in list order, which should be capture-time order) captured at or before the step ended; null when none was. */
52
56
  export function lastFrameBefore(step, frames) {
53
57
  let best = null;
54
58
  for (const f of frames) {
@@ -57,6 +61,7 @@ export function lastFrameBefore(step, frames) {
57
61
  }
58
62
  return best;
59
63
  }
64
+ /** All frames of a test in capture-time order (ties keep step order), each numbered by its position. Cached per report. */
60
65
  export function testFrames(test) {
61
66
  const cached = frameCache.get(test.report.steps);
62
67
  if (cached)
@@ -98,7 +103,12 @@ function allUiActorIds(test) {
98
103
  actorOrderCache.set(test.report.steps, ordered);
99
104
  return ordered;
100
105
  }
101
- /** Return UI snapshots in one stable actor order, including empty slots. */
106
+ /**
107
+ * Returns a frame's UI snapshots (html or png) in one stable actor order, including empty slots, so each client
108
+ * stays in the same column from frame to frame. The order is the report's `meta.actorIds`, then any other client in
109
+ * the order first seen. A client with no snapshot at this frame carries its latest earlier one (`stale: true`), or
110
+ * `null` blob if it has none yet. Snapshots with no `nodeId` follow, named `snapshot-N`.
111
+ */
102
112
  export function orderedUiBlobs(frame, test) {
103
113
  const blobs = uiBlobs(frame);
104
114
  const byNode = new Map();
@@ -126,6 +136,7 @@ export function orderedUiBlobs(frame, test) {
126
136
  }
127
137
  return ordered;
128
138
  }
139
+ /** The frame a route selects: the 1-based `frame` param when in range, otherwise the first frame of a failed step, otherwise the last frame; null with no frames. */
129
140
  export function selectedFrame(frames, route) {
130
141
  const n = Number.parseInt(route.params['frame'] || '', 10);
131
142
  if (Number.isFinite(n) && n >= 1 && n <= frames.length) {
@@ -137,7 +148,7 @@ export function selectedFrame(frames, route) {
137
148
  }
138
149
  return frames[frames.length - 1] ?? null;
139
150
  }
140
- /** Every file a report names: step snapshots, and files attached to the test as a whole. */
151
+ /** Every distinct file a report names, relative to the test's folder: step snapshots, and files attached to the test as a whole (artifacts with their poster and captions). Used to know what to copy or prefetch. */
141
152
  export function capturedFiles(report) {
142
153
  const files = new Set();
143
154
  const visit = (steps) => {
@@ -158,15 +169,19 @@ export function capturedFiles(report) {
158
169
  }
159
170
  return [...files];
160
171
  }
172
+ /** Whether a snapshot is a rendered user interface (`html` or `png`), as opposed to data such as JSON or text. */
161
173
  export function isUi(b) {
162
174
  return b.media === 'html' || b.media === 'png';
163
175
  }
176
+ /** The UI snapshots (html or png) of a frame; empty for no frame. */
164
177
  export function uiBlobs(frame) {
165
178
  return frame ? frame.frame.snapshots.filter(isUi) : [];
166
179
  }
180
+ /** All snapshots of a frame of any media; empty for no frame. */
167
181
  export function stageBlobs(frame) {
168
182
  return frame ? [...frame.frame.snapshots] : [];
169
183
  }
184
+ /** Whether a frame has at least one UI snapshot. */
170
185
  export function frameHasUi(frame) {
171
186
  return uiBlobs(frame).length > 0;
172
187
  }
@@ -176,7 +191,7 @@ const PREFETCH_MEDIA = new Set([
176
191
  'json',
177
192
  'text',
178
193
  ]);
179
- /** Frame files around `index` (the visible one, plus neighbors) to fetch ahead. */
194
+ /** The distinct html, png, json and text files of the frames within `radius` of `index` (the visible one plus neighbors, clamped to the list), to fetch ahead. */
180
195
  export function frameFilesNear(frames, index, radius) {
181
196
  const files = [];
182
197
  const seen = new Set();
@@ -192,6 +207,7 @@ export function frameFilesNear(frames, index, radius) {
192
207
  }
193
208
  return files;
194
209
  }
210
+ /** The clients (actors) to show for a frame: those with a UI snapshot, else those with any snapshot, else the report's declared `meta.actorIds`. */
195
211
  export function actorIds(frame, test) {
196
212
  const ids = [];
197
213
  const seen = {};
@@ -213,6 +229,7 @@ export function actorIds(frame, test) {
213
229
  }
214
230
  return ids;
215
231
  }
232
+ /** The client a route selects: the `node` param when it is one of the frame's actors, else the frame's step actor, else the first actor; null with none. */
216
233
  export function selectedNode(frame, route, test) {
217
234
  const ids = actorIds(frame, test);
218
235
  const param = route.params['node'];
@@ -223,10 +240,12 @@ export function selectedNode(frame, route, test) {
223
240
  }
224
241
  return ids[0] ?? null;
225
242
  }
243
+ /** The text of a blob the test holds as text, or null when it is missing or held as bytes. */
226
244
  export function blobText(test, blob) {
227
245
  const b = test.blobs[blob.file];
228
246
  return b && typeof b.text === 'string' ? b.text : null;
229
247
  }
248
+ /** A `data:` URL for a blob the test holds as base64 bytes, or null when it is missing or held as text. */
230
249
  export function blobDataUrl(test, blob) {
231
250
  const b = test.blobs[blob.file];
232
251
  if (!b?.base64)
@@ -1,17 +1,35 @@
1
+ /**
2
+ * @module URL hash routes of the reports viewer
3
+ *
4
+ * The explorer keeps its state in the URL hash (`#/test/<id>?view=single&zoom=fit`) so a link reproduces a view.
5
+ * This module parses a hash into a {@link Route} and builds it back; it is pure and never throws on a missing
6
+ * value, but `parseHash` throws `URIError` on a malformed percent-escape. Unknown params are carried untouched.
7
+ */
8
+ /** The query part of a hash: decoded keys and values. Empty values are dropped when a hash is built. */
1
9
  export type RouteParams = Readonly<Record<string, string>>;
10
+ /** The list of reports or tests (`#/`). */
2
11
  export type ListRoute = {
3
12
  readonly view: 'list';
4
13
  readonly params: RouteParams;
5
14
  };
15
+ /** One recorded test (`#/test/<id>`). */
6
16
  export type TestRoute = {
7
17
  readonly view: 'test';
18
+ /** The test's id, decoded; it may contain slashes (`libs/chess/monkey`). */
8
19
  readonly testId: string;
9
20
  readonly params: RouteParams;
10
21
  };
22
+ /** Where the viewer is: the list, or one test. Anything that is not `#/test/<id>` is the list. */
11
23
  export type Route = ListRoute | TestRoute;
24
+ /** Parses a `location.hash` (with or without the leading `#` and `/`) into a {@link Route}; an unrecognized path is the list. */
12
25
  export declare function parseHash(hash: string): Route;
26
+ /** Builds the hash for a route (`#/` or `#/test/<encoded id>`) with its non-empty params encoded; the inverse of {@link parseHash}. */
13
27
  export declare function buildHash(route: Route): string;
28
+ /** `single` when the `view` param says so, otherwise `multi`. */
14
29
  export declare function viewMode(route: Route): 'single' | 'multi';
30
+ /** The `zoom` param, or `fit` when it is absent. */
15
31
  export declare function zoomMode(route: Route): string;
32
+ /** Whether the side panel is shown: true unless `aside=0`. */
16
33
  export declare function showAside(route: Route): boolean;
34
+ /** Whether press targets are drawn on frames: true unless `press=0`. */
17
35
  export declare function showPress(route: Route): boolean;
@@ -1,3 +1,11 @@
1
+ /**
2
+ * @module URL hash routes of the reports viewer
3
+ *
4
+ * The explorer keeps its state in the URL hash (`#/test/<id>?view=single&zoom=fit`) so a link reproduces a view.
5
+ * This module parses a hash into a {@link Route} and builds it back; it is pure and never throws on a missing
6
+ * value, but `parseHash` throws `URIError` on a malformed percent-escape. Unknown params are carried untouched.
7
+ */
8
+ /** Parses a `location.hash` (with or without the leading `#` and `/`) into a {@link Route}; an unrecognized path is the list. */
1
9
  export function parseHash(hash) {
2
10
  const raw = hash.replace(/^#\/?/, '');
3
11
  const q = raw.split('?');
@@ -18,6 +26,7 @@ export function parseHash(hash) {
18
26
  }
19
27
  return { view: 'list', params };
20
28
  }
29
+ /** Builds the hash for a route (`#/` or `#/test/<encoded id>`) with its non-empty params encoded; the inverse of {@link parseHash}. */
21
30
  export function buildHash(route) {
22
31
  const qs = Object.keys(route.params)
23
32
  .filter((k) => {
@@ -29,15 +38,19 @@ export function buildHash(route) {
29
38
  const path = route.view === 'test' ? `/test/${encodeURIComponent(route.testId)}` : '/';
30
39
  return `#${path}${qs ? `?${qs}` : ''}`;
31
40
  }
41
+ /** `single` when the `view` param says so, otherwise `multi`. */
32
42
  export function viewMode(route) {
33
43
  return route.params['view'] === 'single' ? 'single' : 'multi';
34
44
  }
45
+ /** The `zoom` param, or `fit` when it is absent. */
35
46
  export function zoomMode(route) {
36
47
  return route.params['zoom'] || 'fit';
37
48
  }
49
+ /** Whether the side panel is shown: true unless `aside=0`. */
38
50
  export function showAside(route) {
39
51
  return route.params['aside'] !== '0';
40
52
  }
53
+ /** Whether press targets are drawn on frames: true unless `press=0`. */
41
54
  export function showPress(route) {
42
55
  return route.params['press'] !== '0';
43
56
  }
@@ -0,0 +1,29 @@
1
+ import type { BackendInput } from './compose.js';
2
+ /** Options of {@link reportsHost}. */
3
+ export type ReportsHostOptions = {
4
+ /** The specs directory, relative to the project. Its files are the outline every layer fills. */
5
+ readonly specs: string;
6
+ /**
7
+ * Where layers are: a directory, an `s3://` URL, or any backend. Several stores are one report: layers
8
+ * from all of them are laid over the same specs. Default: `ATLAS_STORE`.
9
+ */
10
+ readonly stores?: readonly (string | BackendInput)[];
11
+ /** Groups the specs in the viewer. Default: the repository folder's name, or `product`. */
12
+ readonly domain?: string;
13
+ };
14
+ /**
15
+ * The reports half of the viewer, for a project's own stores. Export it from the module that
16
+ * `ATLAS_REPORTS_MODULE` names:
17
+ *
18
+ * ```ts
19
+ * import { reportsHost } from '@crvouga/atlas/reports/host';
20
+ * export const reportsDevFetch = reportsHost({ specs: './specs', stores: ['./atlas-reports'] });
21
+ * ```
22
+ *
23
+ * The returned function takes the incoming `Request` and the repository root (relative store paths and `specs`
24
+ * resolve against it); a path starting `/api/reports` has that prefix removed before routing. The app is built
25
+ * on first use and rebuilt if the root changes. A store given as a string is opened with `openStore` and gets an
26
+ * id derived from its location; a failure while answering (for example a wrong `specs` path) is a 500 whose JSON
27
+ * body names the `specs` directory and the store ids.
28
+ */
29
+ export declare function reportsHost(options: ReportsHostOptions): (request: Request, repoRoot: string) => Promise<Response>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @module Reports host for a project's own stores
3
+ *
4
+ * Mounts the reports API (outline of a specs directory, layers from one or more stores) as a plain
5
+ * `(request, repoRoot) => Response` function, for a dev server or a project's own server to call at
6
+ * `/api/reports`. It owns opening the stores and lazily building the app once per repository root; the report
7
+ * logic itself is in `reports-route.ts` and `compose.ts`. Node/Bun only. It forwards only the method, URL and
8
+ * headers of a request (not the body), so it serves reads.
9
+ *
10
+ * @see docs/dogfooding.md
11
+ */
12
+ import path from 'node:path';
13
+ import { openStore, storeId, storeSpecs } from './store-url.js';
14
+ import { loadDirectoryOutline } from './directory-outline.js';
15
+ import { createReportsApp } from './reports-route.js';
16
+ const PREFIX = '/api/reports';
17
+ /**
18
+ * The reports half of the viewer, for a project's own stores. Export it from the module that
19
+ * `ATLAS_REPORTS_MODULE` names:
20
+ *
21
+ * ```ts
22
+ * import { reportsHost } from '@crvouga/atlas/reports/host';
23
+ * export const reportsDevFetch = reportsHost({ specs: './specs', stores: ['./atlas-reports'] });
24
+ * ```
25
+ *
26
+ * The returned function takes the incoming `Request` and the repository root (relative store paths and `specs`
27
+ * resolve against it); a path starting `/api/reports` has that prefix removed before routing. The app is built
28
+ * on first use and rebuilt if the root changes. A store given as a string is opened with `openStore` and gets an
29
+ * id derived from its location; a failure while answering (for example a wrong `specs` path) is a 500 whose JSON
30
+ * body names the `specs` directory and the store ids.
31
+ */
32
+ export function reportsHost(options) {
33
+ let app;
34
+ let root = '';
35
+ return async (request, repoRoot) => {
36
+ if (!app || root !== repoRoot) {
37
+ root = repoRoot;
38
+ // Ids given by the caller are kept; the ones derived from the store step around them.
39
+ const given = options.stores ?? storeSpecs([], process.env);
40
+ const used = new Set(given.flatMap((store) => (typeof store === 'string' ? [] : [store.id])));
41
+ const backends = given.map((store) => {
42
+ if (typeof store !== 'string')
43
+ return store;
44
+ const opened = openStore(store, { base: repoRoot, env: process.env });
45
+ const id = storeId(opened.location, used);
46
+ used.add(id);
47
+ return { id, label: opened.location.kind === 'directory' ? path.basename(opened.location.directory) : opened.label, store: opened.store };
48
+ });
49
+ const specs = path.resolve(repoRoot, options.specs);
50
+ app = createReportsApp({
51
+ store: null,
52
+ backends: backends.map(backend => ({ ...backend, label: backend.label ?? backend.id })),
53
+ clock: { now: Date.now },
54
+ timers: { setTimeout: (fn, ms) => setTimeout(fn, ms), clearTimeout: handle => clearTimeout(handle), setInterval: (fn, ms) => setInterval(fn, ms), clearInterval: handle => clearInterval(handle) },
55
+ outline: () => loadDirectoryOutline(specs, { repoRoot, domain: options.domain ?? (path.basename(repoRoot) || 'product') })
56
+ });
57
+ // A wrong path says which one, instead of a bare "Internal Server Error".
58
+ app.onError((error, c) => c.json({ error: error instanceof Error ? error.message : String(error), specs, stores: backends.map(backend => backend.id) }, 500));
59
+ }
60
+ const url = new URL(request.url);
61
+ const pathname = url.pathname === PREFIX || url.pathname.startsWith(`${PREFIX}/`) ? url.pathname.slice(PREFIX.length) || '/' : url.pathname;
62
+ return app.request(new Request(`http://reports${pathname}${url.search}`, { method: request.method, headers: request.headers }));
63
+ };
64
+ }
@@ -1,13 +1,12 @@
1
1
  import type { FlatFrame } from './frames.js';
2
- /**
3
- * A self-contained HTML document for a frame that has no UI snapshot: a
4
- * Quint step's model state, or any spec step that only recorded text.
5
- * Painted in the same phone surface as a real snapshot.
6
- */
7
2
  type Section = {
8
3
  readonly heading: string;
9
4
  readonly body: string;
10
5
  };
11
- /** HTML document of one frame's model or recorded state. */
6
+ /**
7
+ * The HTML document of one frame's model or recorded state: the step's kind and timings, its title, its error,
8
+ * its `detail` (shown as a name/value table when every line reads `name = value`, otherwise as preformatted text)
9
+ * and then one block per extra `sections` entry. Everything from the recording is escaped.
10
+ */
12
11
  export declare function stateFrameHtml(frame: FlatFrame, sections: readonly Section[]): string;
13
12
  export {};
@@ -1,3 +1,10 @@
1
+ /**
2
+ * @module HTML cards for frames without a UI snapshot
3
+ *
4
+ * Builds a self-contained HTML document for a frame that has no UI snapshot: a Quint step's model state, or any
5
+ * spec step that only recorded text. It is painted in the same phone surface as a real snapshot. All text taken
6
+ * from the recording is HTML-escaped and the document carries only its own inline style, no script.
7
+ */
1
8
  import { fmtMs } from './format.js';
2
9
  const CARD_CSS = [
3
10
  'html,body{margin:0;background:#f4f1ea;color:#1c1915;',
@@ -53,7 +60,11 @@ function sectionBlocks(sections) {
53
60
  .map((section) => `<h2>${escapeHtml(section.heading)}</h2><pre>${escapeHtml(section.body)}</pre>`)
54
61
  .join('');
55
62
  }
56
- /** HTML document of one frame's model or recorded state. */
63
+ /**
64
+ * The HTML document of one frame's model or recorded state: the step's kind and timings, its title, its error,
65
+ * its `detail` (shown as a name/value table when every line reads `name = value`, otherwise as preformatted text)
66
+ * and then one block per extra `sections` entry. Everything from the recording is escaped.
67
+ */
57
68
  export function stateFrameHtml(frame, sections) {
58
69
  const step = frame.step;
59
70
  const error = step.error === undefined
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @module Atlas server as an ObjectStore
3
+ *
4
+ * `httpStore` speaks the server's store protocol (`/api/store`) as an `ObjectStore`, so everything that publishes to a store
5
+ * (`publishChecks`, spec sets, run uploads, `copyStore`, `createReporter`) publishes to a server unchanged. It retries what a
6
+ * retry can fix (network errors, 5xx, 429, 408) with jittered backoff, never what it cannot (401, 403, 409, 413, 422), and
7
+ * every refusal says what to do about it.
8
+ *
9
+ * @see docs/server.md#the-store-protocol
10
+ */
11
+ import type { ObjectStore, PagedListing } from './object-store.js';
12
+ /** How to reach a server: its `url`, the `token` to send (never in the URL), and, for tests and special runtimes, `fetch`, the retry count, the per-attempt timeout and `sleep`. */
13
+ export type HttpStoreOptions = {
14
+ /** The Atlas server, such as `https://atlas.example.com`. A path on it (`/atlas`) is kept. */
15
+ readonly url: string;
16
+ /** A publish or admin token to write with, a read token to read with. Never put it in the URL. */
17
+ readonly token?: string;
18
+ readonly fetch?: typeof fetch;
19
+ /** Retries after the first attempt, for network errors and server errors. Default 4. */
20
+ readonly retries?: number;
21
+ /** Per attempt, to start answering, in milliseconds. Default 30 000. */
22
+ readonly timeoutMs?: number;
23
+ readonly sleep?: (ms: number) => Promise<void>;
24
+ };
25
+ /** What an Atlas server said no to, with what to do about it. */
26
+ export declare class AtlasHttpError extends Error {
27
+ readonly status: number | null;
28
+ readonly retryable: boolean;
29
+ constructor(message: string, status: number | null, retryable?: boolean);
30
+ }
31
+ /** An `ObjectStore` (with paged listing) over an Atlas server, plus what only a server can answer: `ping` (is it there, and what may this token do) and `status` (the gate, worked out by the server). */
32
+ export type HttpStore = ObjectStore & PagedListing & {
33
+ /** `https://atlas.example.com`, with no secrets. */
34
+ readonly label: string;
35
+ readonly url: string;
36
+ /** Is the server there, and does it know this token? */
37
+ ping(): Promise<{
38
+ readonly ok: boolean;
39
+ readonly ms: number;
40
+ readonly role?: string;
41
+ readonly error?: string;
42
+ }>;
43
+ /** The gate, worked out by the server from the layers it holds: the `atlas report status --format json` document. Needs a token that may read. */
44
+ status(query?: URLSearchParams): Promise<unknown>;
45
+ };
46
+ /**
47
+ * An {@link ObjectStore} on an Atlas server, over its store protocol (`/api/store`). The server holds the
48
+ * layers (on its own disk, or in whatever backends it was given), checks what is uploaded, and shows
49
+ * everyone the combined report at once. Anything that publishes to a store, `publishChecks`, spec sets,
50
+ * run uploads, `copyStore`, works against it unchanged.
51
+ */
52
+ export declare function httpStore(options: HttpStoreOptions): HttpStore;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * @module Atlas server as an ObjectStore
3
+ *
4
+ * `httpStore` speaks the server's store protocol (`/api/store`) as an `ObjectStore`, so everything that publishes to a store
5
+ * (`publishChecks`, spec sets, run uploads, `copyStore`, `createReporter`) publishes to a server unchanged. It retries what a
6
+ * retry can fix (network errors, 5xx, 429, 408) with jittered backoff, never what it cannot (401, 403, 409, 413, 422), and
7
+ * every refusal says what to do about it.
8
+ *
9
+ * @see docs/server.md#the-store-protocol
10
+ */
11
+ import { ObjectStoreWithPrefix } from './store-prefix.js';
12
+ /** What an Atlas server said no to, with what to do about it. */
13
+ export class AtlasHttpError extends Error {
14
+ status;
15
+ retryable;
16
+ constructor(message, status, retryable = false) {
17
+ super(message);
18
+ this.status = status;
19
+ this.retryable = retryable;
20
+ this.name = 'AtlasHttpError';
21
+ }
22
+ }
23
+ const encode = (key) => key.split('/').map(encodeURIComponent).join('/');
24
+ function explain(status, base, message) {
25
+ const said = message ? `: ${message}` : '';
26
+ switch (status) {
27
+ case 401: return `${base} wants a token${said}. Set ATLAS_TOKEN (a publish token to upload, a read token to read).`;
28
+ case 403: return `${base} knows this token but it may not do that${said}. Uploading needs a publish or admin token.`;
29
+ case 409: return `${base} refused a change to something that already exists${said}`;
30
+ case 413: return `${base} refused an object that large${said}. Attach big files by uri, or raise the server's limits.maxObjectMiB.`;
31
+ case 422: return `${base} did not accept it${said}`;
32
+ default: return `${base} answered ${status}${said}`;
33
+ }
34
+ }
35
+ /**
36
+ * An {@link ObjectStore} on an Atlas server, over its store protocol (`/api/store`). The server holds the
37
+ * layers (on its own disk, or in whatever backends it was given), checks what is uploaded, and shows
38
+ * everyone the combined report at once. Anything that publishes to a store, `publishChecks`, spec sets,
39
+ * run uploads, `copyStore`, works against it unchanged.
40
+ */
41
+ export function httpStore(options) {
42
+ const base = options.url.replace(/\/+$/, '');
43
+ const root = `${base}/api/store`;
44
+ const doFetch = options.fetch ?? fetch;
45
+ const retries = options.retries ?? 4;
46
+ const timeoutMs = options.timeoutMs ?? 30_000;
47
+ const sleep = options.sleep ?? ((ms) => new Promise(resolve => setTimeout(resolve, ms)));
48
+ const authorization = options.token ? { authorization: `Bearer ${options.token}` } : {};
49
+ async function request(method, url, init = {}) {
50
+ for (let attempt = 0;; attempt++) {
51
+ let failure;
52
+ try {
53
+ // The clock is for the server to start answering; a large body may take longer to arrive.
54
+ const controller = new AbortController();
55
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
56
+ let response;
57
+ try {
58
+ response = await doFetch(url, { method, headers: init.contentType ? { ...authorization, 'content-type': init.contentType } : authorization, ...(init.body ? { body: init.body } : {}), signal: controller.signal });
59
+ }
60
+ finally {
61
+ clearTimeout(timer);
62
+ }
63
+ if (response.ok)
64
+ return response;
65
+ if (response.status === 404 && init.allow404) {
66
+ await response.body?.cancel();
67
+ return null;
68
+ }
69
+ const said = await response.json().then((body) => body.error ?? null, () => null);
70
+ failure = new AtlasHttpError(explain(response.status, base, said), response.status, response.status >= 500 || response.status === 429 || response.status === 408);
71
+ }
72
+ catch (error) {
73
+ failure = new AtlasHttpError(`cannot reach ${base} (${error instanceof Error ? (error.name === 'AbortError' ? `no answer in ${timeoutMs} ms` : error.message) : String(error)})`, null, true);
74
+ }
75
+ if (!failure.retryable || attempt >= retries)
76
+ throw failure;
77
+ await sleep(Math.random() * Math.min(8_000, 100 * 2 ** attempt));
78
+ }
79
+ }
80
+ const store = {
81
+ label: base,
82
+ url: base,
83
+ async get(key) {
84
+ const response = await request('GET', `${root}/${encode(key)}`, { allow404: true });
85
+ if (!response)
86
+ return null;
87
+ return { body: response.body ?? new Blob([]).stream(), contentType: response.headers.get('content-type') ?? 'application/octet-stream', size: Number(response.headers.get('content-length') ?? 0) };
88
+ },
89
+ async put(key, bytes, contentType) {
90
+ await (await request('PUT', `${root}/${encode(key)}`, { body: bytes, contentType }))?.arrayBuffer();
91
+ },
92
+ async head(key) {
93
+ return (await request('HEAD', `${root}/${encode(key)}`, { allow404: true })) !== null;
94
+ },
95
+ async delete(key) {
96
+ await (await request('DELETE', `${root}/${encode(key)}`, { allow404: true }))?.arrayBuffer();
97
+ },
98
+ async deleteMany(keys) {
99
+ for (const key of keys)
100
+ await store.delete(key);
101
+ },
102
+ async getUri() {
103
+ return null;
104
+ },
105
+ async listPage(prefix = '', page = {}) {
106
+ const query = new URLSearchParams({ prefix, limit: String(page.limit ?? 1000) });
107
+ if (page.after !== undefined)
108
+ query.set('after', page.after);
109
+ const response = (await request('GET', `${root}?${query}`));
110
+ const body = await response.json();
111
+ if (!Array.isArray(body.keys))
112
+ throw new AtlasHttpError(`${base} did not answer a listing`, response.status);
113
+ return { keys: body.keys, next: body.next ?? null };
114
+ },
115
+ async listKeys(prefix = '') {
116
+ const keys = [];
117
+ for (let after;;) {
118
+ const page = await store.listPage(prefix, after === undefined ? {} : { after });
119
+ keys.push(...page.keys);
120
+ if (page.next === null)
121
+ return keys;
122
+ after = page.next;
123
+ }
124
+ },
125
+ withPrefix(prefix) {
126
+ return new ObjectStoreWithPrefix(store, prefix);
127
+ },
128
+ async status(query = new URLSearchParams()) {
129
+ const suffix = query.size ? `?${query}` : '';
130
+ return (await request('GET', `${base}/api/status${suffix}`)).json();
131
+ },
132
+ async ping() {
133
+ const started = performance.now();
134
+ try {
135
+ const response = (await request('GET', `${base}/api/server`));
136
+ const info = await response.json();
137
+ return { ok: true, ms: Math.round(performance.now() - started), ...(info.role ? { role: info.role } : {}) };
138
+ }
139
+ catch (error) {
140
+ return { ok: false, ms: Math.round(performance.now() - started), error: error instanceof Error ? error.message : String(error) };
141
+ }
142
+ }
143
+ };
144
+ return store;
145
+ }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * @module HTTP client port
3
+ *
4
+ * The one thing the reports readers need from the network, as a type, so a host can supply `fetch`, an
5
+ * authenticating wrapper or a test double.
6
+ */
7
+ /** Anything with a `fetch` of the standard signature. The readers only call it; they do not retry or add credentials. */
1
8
  export type HttpClient = {
2
9
  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
3
10
  };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @module HTTP client port
3
+ *
4
+ * The one thing the reports readers need from the network, as a type, so a host can supply `fetch`, an
5
+ * authenticating wrapper or a test double.
6
+ */
@@ -0,0 +1,54 @@
1
+ import type { EvidenceCheck } from './explorer-types.js';
2
+ /** The checks an existing test report holds, and when it was recorded when the report says. */
3
+ export type IngestedChecks = {
4
+ /** One per test, with ids made unique. Attachments that name a file carry a `path` for the publisher to read; see `attachment-files.ts`. */
5
+ readonly checks: readonly EvidenceCheck[];
6
+ /**
7
+ * For each check, in order, the names the report gave its container (suite, class, file), which
8
+ * can name the spec it checks when the report has no path for it.
9
+ */
10
+ readonly hints?: readonly (readonly string[])[];
11
+ /** When the report says the run ended (or started), epoch milliseconds; null when it does not say. */
12
+ readonly recordedAtMs: number | null;
13
+ /** The tool that wrote the report, when it says. */
14
+ readonly tool: string | null;
15
+ /** What the report says about the run as a whole: the tool, the build, the platform. */
16
+ readonly labels?: Readonly<Record<string, string>>;
17
+ };
18
+ /** Never silently count two checks as one: the first check with an id keeps it and a repeat becomes `<id> #2`, `<id> #3`, ... in input order. */
19
+ export declare function uniqueChecks(checks: readonly EvidenceCheck[]): EvidenceCheck[];
20
+ /**
21
+ * A [CTRF](https://ctrf.io) report (`results.tests`). A check's id is its file, its suites and its
22
+ * name, so the same test keeps the same id in every run. Skipped, pending and other results are
23
+ * `not-run`: never a pass; a status this function does not know is also `not-run`.
24
+ *
25
+ * Also carries what CTRF has beyond the outcome as evidence: tags, labels (browser, device, flaky, run environment
26
+ * and tool), steps, attachments (files by `path`, `url` as `uri`, inline screenshot, stdout/stderr, trace), retry
27
+ * attempts, a snippet annotation and `extra` under the `ctrf.extra` extension. `recordedAtMs` is `summary.stop`,
28
+ * else `summary.start`. Throws `Not a CTRF report: results.tests is missing` when there is no `results.tests` array.
29
+ *
30
+ * @param report The parsed CTRF JSON.
31
+ */
32
+ export declare function checksFromCtrf(report: unknown): IngestedChecks;
33
+ /**
34
+ * A JUnit XML report: one check per `<testcase>`, identified by its file or class and its name (`file or classname > name`).
35
+ *
36
+ * A case with a `<failure>` (an assertion that did not hold) or an `<error>` (an exception) is `failed`, one with
37
+ * `<skipped>` is `not-run`, any other is `passed`. Durations are seconds converted to milliseconds. Properties become labels, `<system-out>`/`<system-err>` become log
38
+ * attachments, and a Jenkins `[[ATTACHMENT|path]]` line in the output attaches that file by `path`. `recordedAtMs`
39
+ * is the newest `<testsuite timestamp>` (a timestamp with no offset is read as UTC). Throws
40
+ * `Not a JUnit report: no <testsuite> found` when the text has no test cases and no `<testsuite`.
41
+ */
42
+ export declare function checksFromJunit(xml: string): IngestedChecks;
43
+ /**
44
+ * An Atlas run (`manifest.json`): one check per path. A path's key, which the same start and
45
+ * events give in every run, is its id. A flaky path passed. While the run is still going, a
46
+ * running path is `running` and a queued one `not-run`, so a snapshot never reads as finished.
47
+ *
48
+ * Every check's `specPath` is the run's root chart file under its spec directory. A run is still going when
49
+ * `run.finishedAt` is `null`. `recordedAtMs` is `run.finishedAt`, else `run.startedAt` plus `run.durationMs`.
50
+ * Throws `Not an Atlas manifest: run and paths are required` when `paths` or `run.id` is missing.
51
+ *
52
+ * @param manifest The parsed `manifest.json` of an Atlas run.
53
+ */
54
+ export declare function checksFromManifest(manifest: unknown): IngestedChecks;