@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
@@ -0,0 +1,221 @@
1
+ /**
2
+ * @module AWS credential lookup
3
+ *
4
+ * Finds S3 credentials the way an AWS SDK does, without depending on one: environment variables, web identity
5
+ * (EKS), ECS container credentials, the shared credentials file, the AWS CLI (SSO, assumed roles) and EC2 instance
6
+ * metadata. It owns the lookup order, the caching and the early refresh before expiry; it does not sign requests
7
+ * (that is `sigv4.ts`) or know about buckets (`s3-store.ts`). Credentials are never put in a store URL: they
8
+ * come from here. Node-only (reads files, spawns `aws`). Nothing runs at import time.
9
+ *
10
+ * @see docs/report-contract.md (backends)
11
+ */
12
+ import { spawn } from 'node:child_process';
13
+ import { readFile } from 'node:fs/promises';
14
+ import os from 'node:os';
15
+ import path from 'node:path';
16
+ import { XMLParser } from 'fast-xml-parser';
17
+ let parser;
18
+ /** Built on first use, so importing this module does no work. */
19
+ const xml = { parse: (text) => (parser ??= new XMLParser({ ignoreAttributes: true, parseTagValue: false })).parse(text) };
20
+ async function runCommand(command, args) {
21
+ return new Promise(resolve => {
22
+ try {
23
+ const child = spawn(command, [...args], { stdio: ['ignore', 'pipe', 'ignore'] });
24
+ let out = '';
25
+ const timer = setTimeout(() => { child.kill(); resolve(null); }, 20_000);
26
+ child.stdout.on('data', chunk => { out += String(chunk); });
27
+ child.on('error', () => { clearTimeout(timer); resolve(null); });
28
+ child.on('close', code => { clearTimeout(timer); resolve(code === 0 ? out : null); });
29
+ }
30
+ catch {
31
+ resolve(null);
32
+ }
33
+ });
34
+ }
35
+ function parseIni(text) {
36
+ const sections = {};
37
+ let current = null;
38
+ for (const raw of text.split(/\r?\n/)) {
39
+ const line = raw.trim();
40
+ if (!line || line.startsWith('#') || line.startsWith(';'))
41
+ continue;
42
+ const header = /^\[(?:profile\s+)?([^\]]+)\]$/.exec(line);
43
+ if (header) {
44
+ current = sections[header[1].trim()] ??= {};
45
+ continue;
46
+ }
47
+ const at = line.indexOf('=');
48
+ if (current && at > 0)
49
+ current[line.slice(0, at).trim().toLowerCase()] = line.slice(at + 1).trim();
50
+ }
51
+ return sections;
52
+ }
53
+ function expiry(value) {
54
+ const time = typeof value === 'string' ? Date.parse(value) : NaN;
55
+ return Number.isFinite(time) ? time : undefined;
56
+ }
57
+ /**
58
+ * Creates a provider for the credentials an AWS SDK would find, in the same order, with no SDK: environment, web
59
+ * identity (EKS), container (ECS), shared credentials file, the AWS CLI (which covers SSO and assumed roles),
60
+ * and EC2 instance metadata. `ATLAS_S3_ACCESS_KEY_ID` / `ATLAS_S3_SECRET_ACCESS_KEY` (and `ATLAS_S3_SESSION_TOKEN`)
61
+ * come first so one store can use keys that are not the machine's AWS keys, such as Cloudflare R2 tokens.
62
+ *
63
+ * Nothing is looked up until the provider is first called. The first source that yields credentials wins and
64
+ * is cached; a source that throws (for example an STS error) is skipped and reported if no later source works.
65
+ * When none works the provider rejects with an error that lists every source tried and how to fix it. Set
66
+ * `ATLAS_NO_AWS_CLI` to skip the `aws` CLI step and `AWS_EC2_METADATA_DISABLED=true` to skip instance metadata.
67
+ *
68
+ * @example
69
+ * const credentials = await credentialChain()();
70
+ * credentials.accessKeyId;
71
+ */
72
+ export function credentialChain(options = {}) {
73
+ const env = options.env ?? process.env;
74
+ const doFetch = options.fetch ?? fetch;
75
+ const read = options.readFile ?? ((file) => readFile(file, 'utf8'));
76
+ const run = options.run ?? runCommand;
77
+ const now = options.now ?? Date.now;
78
+ const skew = options.skewMs ?? 5 * 60_000;
79
+ const home = options.home ?? env.HOME ?? os.homedir();
80
+ const profile = env.AWS_PROFILE || env.AWS_DEFAULT_PROFILE || 'default';
81
+ let cached = null;
82
+ let pending = null;
83
+ const sources = [
84
+ {
85
+ name: 'environment (ATLAS_S3_*)',
86
+ load: async () => env.ATLAS_S3_ACCESS_KEY_ID && env.ATLAS_S3_SECRET_ACCESS_KEY
87
+ ? { accessKeyId: env.ATLAS_S3_ACCESS_KEY_ID, secretAccessKey: env.ATLAS_S3_SECRET_ACCESS_KEY, ...(env.ATLAS_S3_SESSION_TOKEN ? { sessionToken: env.ATLAS_S3_SESSION_TOKEN } : {}) }
88
+ : null
89
+ },
90
+ {
91
+ name: 'environment (AWS_ACCESS_KEY_ID)',
92
+ load: async () => env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY
93
+ ? { accessKeyId: env.AWS_ACCESS_KEY_ID, secretAccessKey: env.AWS_SECRET_ACCESS_KEY, ...(env.AWS_SESSION_TOKEN ? { sessionToken: env.AWS_SESSION_TOKEN } : {}) }
94
+ : null
95
+ },
96
+ {
97
+ name: 'web identity (IRSA)',
98
+ load: async () => {
99
+ if (!env.AWS_WEB_IDENTITY_TOKEN_FILE || !env.AWS_ROLE_ARN)
100
+ return null;
101
+ const region = env.AWS_REGION || env.AWS_DEFAULT_REGION || 'us-east-1';
102
+ const token = (await read(env.AWS_WEB_IDENTITY_TOKEN_FILE)).trim();
103
+ const url = new URL(env.AWS_STS_ENDPOINT || `https://sts.${region}.amazonaws.com/`);
104
+ url.search = new URLSearchParams({ Action: 'AssumeRoleWithWebIdentity', Version: '2011-06-15', RoleArn: env.AWS_ROLE_ARN, RoleSessionName: env.AWS_ROLE_SESSION_NAME || 'atlas', WebIdentityToken: token }).toString();
105
+ const response = await doFetch(url, { method: 'GET', headers: { accept: 'application/xml' } });
106
+ const body = await response.text();
107
+ if (!response.ok)
108
+ throw new Error(`STS AssumeRoleWithWebIdentity failed (${response.status}): ${body.slice(0, 300)}`);
109
+ const found = xml.parse(body)?.AssumeRoleWithWebIdentityResponse?.AssumeRoleWithWebIdentityResult?.Credentials;
110
+ if (!found?.AccessKeyId)
111
+ throw new Error('STS returned no credentials');
112
+ return { accessKeyId: found.AccessKeyId, secretAccessKey: found.SecretAccessKey, sessionToken: found.SessionToken, ...(expiry(found.Expiration) ? { expiresAtMs: expiry(found.Expiration) } : {}) };
113
+ }
114
+ },
115
+ {
116
+ name: 'container credentials (ECS)',
117
+ load: async () => {
118
+ const url = env.AWS_CONTAINER_CREDENTIALS_FULL_URI || (env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ? `http://169.254.170.2${env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}` : null);
119
+ if (!url)
120
+ return null;
121
+ const token = env.AWS_CONTAINER_AUTHORIZATION_TOKEN || (env.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE ? (await read(env.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE)).trim() : null);
122
+ const response = await doFetch(url, { headers: token ? { authorization: token } : {} });
123
+ if (!response.ok)
124
+ throw new Error(`The container credentials endpoint answered ${response.status}`);
125
+ const found = await response.json();
126
+ return { accessKeyId: found.AccessKeyId, secretAccessKey: found.SecretAccessKey, sessionToken: found.Token, ...(expiry(found.Expiration) ? { expiresAtMs: expiry(found.Expiration) } : {}) };
127
+ }
128
+ },
129
+ {
130
+ name: `shared credentials file (profile ${profile})`,
131
+ load: async () => {
132
+ const file = env.AWS_SHARED_CREDENTIALS_FILE || path.join(home, '.aws', 'credentials');
133
+ const text = await read(file).catch(() => null);
134
+ const section = text ? parseIni(text)[profile] : undefined;
135
+ return section?.aws_access_key_id && section.aws_secret_access_key
136
+ ? { accessKeyId: section.aws_access_key_id, secretAccessKey: section.aws_secret_access_key, ...(section.aws_session_token ? { sessionToken: section.aws_session_token } : {}) }
137
+ : null;
138
+ }
139
+ },
140
+ {
141
+ name: `AWS CLI (profile ${profile})`,
142
+ load: async () => {
143
+ // Covers SSO, assumed roles and credential_process: whatever `aws` itself can sign in with.
144
+ if (env.ATLAS_NO_AWS_CLI)
145
+ return null;
146
+ const out = await run('aws', ['configure', 'export-credentials', '--format', 'process', ...(env.AWS_PROFILE ? ['--profile', env.AWS_PROFILE] : [])]);
147
+ if (!out)
148
+ return null;
149
+ try {
150
+ const found = JSON.parse(out);
151
+ return found.AccessKeyId && found.SecretAccessKey ? { accessKeyId: found.AccessKeyId, secretAccessKey: found.SecretAccessKey, ...(found.SessionToken ? { sessionToken: found.SessionToken } : {}), ...(expiry(found.Expiration) ? { expiresAtMs: expiry(found.Expiration) } : {}) } : null;
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ },
158
+ {
159
+ name: 'EC2 instance metadata',
160
+ load: async () => {
161
+ if (env.AWS_EC2_METADATA_DISABLED === 'true')
162
+ return null;
163
+ const base = env.AWS_EC2_METADATA_SERVICE_ENDPOINT || 'http://169.254.169.254';
164
+ const signal = () => AbortSignal.timeout(1_000);
165
+ try {
166
+ const token = await doFetch(`${base}/latest/api/token`, { method: 'PUT', headers: { 'x-aws-ec2-metadata-token-ttl-seconds': '21600' }, signal: signal() });
167
+ if (!token.ok)
168
+ return null;
169
+ const headers = { 'x-aws-ec2-metadata-token': await token.text() };
170
+ const roles = await doFetch(`${base}/latest/meta-data/iam/security-credentials/`, { headers, signal: signal() });
171
+ const role = (await roles.text()).split('\n')[0]?.trim();
172
+ if (!roles.ok || !role)
173
+ return null;
174
+ const found = await (await doFetch(`${base}/latest/meta-data/iam/security-credentials/${role}`, { headers, signal: signal() })).json();
175
+ return { accessKeyId: found.AccessKeyId, secretAccessKey: found.SecretAccessKey, sessionToken: found.Token, ...(expiry(found.Expiration) ? { expiresAtMs: expiry(found.Expiration) } : {}) };
176
+ }
177
+ catch {
178
+ return null;
179
+ }
180
+ }
181
+ }
182
+ ];
183
+ async function resolve() {
184
+ const failures = [];
185
+ for (const source of sources) {
186
+ try {
187
+ const credentials = await source.load();
188
+ if (credentials) {
189
+ cached = { credentials, source: source.name };
190
+ return credentials;
191
+ }
192
+ }
193
+ catch (error) {
194
+ failures.push(`${source.name}: ${error instanceof Error ? error.message : String(error)}`);
195
+ }
196
+ }
197
+ throw new Error([
198
+ 'No AWS credentials found for the object store.',
199
+ 'Atlas looked, in order, at: ATLAS_S3_ACCESS_KEY_ID / ATLAS_S3_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY, EKS web identity, ECS container credentials, ~/.aws/credentials, the AWS CLI (aws sso login), and EC2 instance metadata.',
200
+ ...failures.map(failure => ` - ${failure}`),
201
+ 'Fix: export AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, or run `aws sso login` / `aws configure`, or attach a role to the machine.'
202
+ ].join('\n'));
203
+ }
204
+ const provider = (async () => {
205
+ if (cached && (cached.credentials.expiresAtMs === undefined || cached.credentials.expiresAtMs - skew > now()))
206
+ return cached.credentials;
207
+ // One lookup at a time, however many requests are waiting for credentials.
208
+ pending ??= resolve().finally(() => { pending = null; });
209
+ return pending;
210
+ });
211
+ provider.invalidate = () => { cached = null; };
212
+ provider.source = () => cached?.source ?? null;
213
+ return provider;
214
+ }
215
+ /** Wraps keys that are already known in a {@link CredentialProvider} that never refreshes (its `invalidate` does nothing). */
216
+ export function staticCredentials(credentials) {
217
+ const provider = (async () => credentials);
218
+ provider.invalidate = () => { };
219
+ provider.source = () => 'given to the store';
220
+ return provider;
221
+ }
@@ -1,2 +1,16 @@
1
+ /**
2
+ * @module Browser navigation for the reports viewer
3
+ *
4
+ * The one implementation of {@link ReportNavigation} that touches `window` and `document`: the explorer's selection
5
+ * lives in the URL hash, keys come from `keydown`, and the tab title is `document.title`. Browser-only; nothing
6
+ * here runs until {@link browserReportNavigation} is called, so importing it is safe in Node.
7
+ */
1
8
  import type { ReportNavigation } from './navigation.js';
9
+ /**
10
+ * Creates a {@link ReportNavigation} backed by `window.location.hash`, `history`, `keydown` and `document.title`.
11
+ *
12
+ * `writeHash` does nothing when the hash is already the given one; otherwise it replaces or pushes a history entry
13
+ * (no `hashchange` is fired by `replaceState`/`pushState`, so the caller owns re-reading). `subscribeHash` listens to both
14
+ * `hashchange` and `popstate`. Every `subscribe*` returns its unsubscribe function. Must be called in a browser.
15
+ */
2
16
  export declare function browserReportNavigation(): ReportNavigation;
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Creates a {@link ReportNavigation} backed by `window.location.hash`, `history`, `keydown` and `document.title`.
3
+ *
4
+ * `writeHash` does nothing when the hash is already the given one; otherwise it replaces or pushes a history entry
5
+ * (no `hashchange` is fired by `replaceState`/`pushState`, so the caller owns re-reading). `subscribeHash` listens to both
6
+ * `hashchange` and `popstate`. Every `subscribe*` returns its unsubscribe function. Must be called in a browser.
7
+ */
1
8
  export function browserReportNavigation() {
2
9
  return {
3
10
  readHash: () => window.location.hash,
@@ -1,24 +1,56 @@
1
1
  import type { ObjectStore } from './object-store.js';
2
+ /** Uncompressed size of one chunk, in bytes (4 MiB). A file larger than this is split across several chunks. */
2
3
  export declare const BUNDLE_BYTES: number;
3
4
  type Location = {
4
5
  blob: string;
5
6
  offset: number;
6
7
  size: number;
7
8
  };
9
+ /**
10
+ * Where each file of a bundle is, keyed by the file's key (for a layer, `attachments/<sha256>` and the like).
11
+ * `parts` lists, in order, the byte ranges (`offset`, `size` within the decompressed blob `blob`, a store key) whose
12
+ * concatenation is the file.
13
+ */
8
14
  export type BundleIndex = Record<string, {
9
15
  contentType: string;
10
16
  parts: Location[];
11
17
  }>;
18
+ /** One file to bundle, or one file read back. */
12
19
  export type BundleEntry = {
20
+ /** The file's name inside the bundle; the key in the {@link BundleIndex}. A repeated key appends more parts to the same file. */
13
21
  key: string;
14
22
  bytes: Uint8Array;
23
+ /** The media type to serve the bytes with. The first entry with a key decides it. */
15
24
  contentType: string;
16
25
  };
26
+ /** The store key of a commit's bundle index for one source: `commits/<sha>/bundles/<source>.json`. This is the older, per-commit layout; layers hold their index in the layer document instead. */
17
27
  export declare function bundleIndexKey(sha: string, source: string): string;
18
- /** Four bounded compressed chunks in flight, with one small index published last. */
28
+ /**
29
+ * Writes `entries` as blobs next to `key` and then the index at `key`: up to four compressed chunks in flight,
30
+ * with the one small index published last, so a reader never finds an index whose blobs have not arrived.
31
+ *
32
+ * Trap: blobs go to `<key up to "/bundles/">/blobs/`, so `key` must contain `/bundles/` (as {@link bundleIndexKey}
33
+ * gives); otherwise the prefix is computed from a wrong slice.
34
+ */
19
35
  export declare function publishBundle(store: ObjectStore, key: string, entries: AsyncIterable<BundleEntry>): Promise<void>;
36
+ /**
37
+ * Packs `entries` into gzip blobs under `prefix` (for layers, `layers/blobs/`) and returns the index that locates
38
+ * every file; the caller stores the index (a layer keeps it as its `bundle`).
39
+ *
40
+ * The entries are consumed lazily with up to four chunks in flight. A blob that already exists in the store is not
41
+ * written again (one `head` per distinct blob). The gzip header's timestamp is zeroed, and the blob name is the
42
+ * SHA-256 of the uncompressed chunk, so the same files in the same order always produce the same blobs. Order of
43
+ * entries matters for that: chunking follows input order. A store error rejects after started writes settle.
44
+ */
20
45
  export declare function writeBundle(store: ObjectStore, entries: AsyncIterable<BundleEntry>, prefix: string): Promise<BundleIndex>;
21
- /** Cache only immutable blobs; mutable indexes are read for each request. */
46
+ /**
47
+ * Creates a reader that returns one file of a bundle by key, or `null` when the key is not in `index` or one of
48
+ * its blobs is missing from the store.
49
+ *
50
+ * Only immutable blobs are cached (the last four decompressed blobs, oldest evicted first, including a cached
51
+ * "missing" answer); mutable indexes are read for each request. Transport errors from the store reject.
52
+ */
22
53
  export declare function createBundleReader(store: ObjectStore, index: BundleIndex): (key: string) => Promise<BundleEntry | null>;
54
+ /** Reads and parses the bundle index at `key`; an index that is not there is an empty bundle (`{}`). Invalid JSON throws. */
23
55
  export declare function readBundleIndex(store: ObjectStore, key: string): Promise<BundleIndex>;
24
56
  export {};
@@ -1,6 +1,20 @@
1
+ /**
2
+ * @module Bundles of captured files
3
+ *
4
+ * Packs many small captured files (screenshots, frames, attachments) into a few gzip-compressed, content-addressed
5
+ * blobs in an {@link ObjectStore}, and reads single files back out. The bytes of every entry are concatenated into
6
+ * chunks of at most {@link BUNDLE_BYTES} uncompressed bytes; each chunk is gzipped and stored once at
7
+ * `<prefix><sha256 of the uncompressed chunk>.gz`, so identical chunks and re-publishes write nothing new. A
8
+ * {@link BundleIndex} says, per file key, which byte ranges of which blobs hold it. A layer document keeps that
9
+ * index in its `bundle` field. Blobs are immutable; the index is the only mutable-looking part and is written last.
10
+ *
11
+ * @see docs/report-contract.md (where bytes go)
12
+ */
1
13
  import { readObjectBytes, readUtf8, writeUtf8, } from './object-io.js';
2
14
  import { eachLimitedStream } from './completion-order.js';
15
+ /** Uncompressed size of one chunk, in bytes (4 MiB). A file larger than this is split across several chunks. */
3
16
  export const BUNDLE_BYTES = 4 * 1024 * 1024;
17
+ /** The store key of a commit's bundle index for one source: `commits/<sha>/bundles/<source>.json`. This is the older, per-commit layout; layers hold their index in the layer document instead. */
4
18
  export function bundleIndexKey(sha, source) {
5
19
  return `commits/${sha}/bundles/${source}.json`;
6
20
  }
@@ -67,18 +81,39 @@ async function* chunks(entries) {
67
81
  if (pending.length > 0)
68
82
  yield pending;
69
83
  }
70
- /** Four bounded compressed chunks in flight, with one small index published last. */
84
+ /**
85
+ * Writes `entries` as blobs next to `key` and then the index at `key`: up to four compressed chunks in flight,
86
+ * with the one small index published last, so a reader never finds an index whose blobs have not arrived.
87
+ *
88
+ * Trap: blobs go to `<key up to "/bundles/">/blobs/`, so `key` must contain `/bundles/` (as {@link bundleIndexKey}
89
+ * gives); otherwise the prefix is computed from a wrong slice.
90
+ */
71
91
  export async function publishBundle(store, key, entries) {
72
92
  const index = await writeBundle(store, entries, `${key.slice(0, key.indexOf('/bundles/'))}/blobs/`);
73
93
  await writeUtf8(store, key, JSON.stringify(index), 'application/json');
74
94
  }
95
+ /**
96
+ * Packs `entries` into gzip blobs under `prefix` (for layers, `layers/blobs/`) and returns the index that locates
97
+ * every file; the caller stores the index (a layer keeps it as its `bundle`).
98
+ *
99
+ * The entries are consumed lazily with up to four chunks in flight. A blob that already exists in the store is not
100
+ * written again (one `head` per distinct blob). The gzip header's timestamp is zeroed, and the blob name is the
101
+ * SHA-256 of the uncompressed chunk, so the same files in the same order always produce the same blobs. Order of
102
+ * entries matters for that: chunking follows input order. A store error rejects after started writes settle.
103
+ */
75
104
  export async function writeBundle(store, entries, prefix) {
76
105
  const index = {};
77
106
  const writes = { prefix, pending: new Map() };
78
107
  await eachLimitedStream(chunks(entries), 4, chunk => flush(store, chunk, index, writes));
79
108
  return index;
80
109
  }
81
- /** Cache only immutable blobs; mutable indexes are read for each request. */
110
+ /**
111
+ * Creates a reader that returns one file of a bundle by key, or `null` when the key is not in `index` or one of
112
+ * its blobs is missing from the store.
113
+ *
114
+ * Only immutable blobs are cached (the last four decompressed blobs, oldest evicted first, including a cached
115
+ * "missing" answer); mutable indexes are read for each request. Transport errors from the store reject.
116
+ */
82
117
  export function createBundleReader(store, index) {
83
118
  const cache = new Map();
84
119
  return async (key) => {
@@ -108,6 +143,7 @@ export function createBundleReader(store, index) {
108
143
  return { key, bytes, contentType: location.contentType };
109
144
  };
110
145
  }
146
+ /** Reads and parses the bundle index at `key`; an index that is not there is an empty bundle (`{}`). Invalid JSON throws. */
111
147
  export async function readBundleIndex(store, key) {
112
148
  const text = await readUtf8(store, key);
113
149
  return text === null ? {} : JSON.parse(text);
@@ -1,37 +1,68 @@
1
- /** Shared search, filter, group and sort for reports and the tests inside one. */
1
+ /**
2
+ * @module Catalog search, filter, group and sort
3
+ *
4
+ * The pure view logic behind the viewer's list of reports (and of the tests inside one): given entities and a
5
+ * query (text, status, grouping, sort), it returns the groups to draw. It owns no data and does no I/O; the
6
+ * caller maps its own records to {@link CatalogEntity}. Input is never mutated.
7
+ */
8
+ /** A status filter: one of the four result statuses as the list names them (`skipped` is a check that was `not-run`), or `all`. */
2
9
  export type CatalogStatus = 'all' | 'passed' | 'failed' | 'skipped' | 'running';
10
+ /** What the list is grouped by; `none` gives one group named `All`. */
3
11
  export type CatalogGroup = 'none' | 'file' | 'status' | 'package' | 'runtime';
12
+ /** How entities are ordered inside a group: `recent` keeps the input order, `name` is by title, `duration` is longest first, `status` is failed first then by title. */
4
13
  export type CatalogSort = 'recent' | 'name' | 'duration' | 'status';
14
+ /** How the list is drawn. */
5
15
  export type CatalogLayout = 'list' | 'grid';
16
+ /** The user's current choices, mirrored in the URL (see {@link catalogParams}). */
6
17
  export type CatalogQuery = {
18
+ /** Free text; matched case-insensitively as a substring of each entity's `search` text. Blank matches everything. */
7
19
  readonly q: string;
8
20
  readonly status: CatalogStatus;
9
21
  readonly group: CatalogGroup;
10
22
  readonly sort: CatalogSort;
11
23
  readonly layout: CatalogLayout;
12
24
  };
25
+ /** One row of the list: a report or a test, flattened to what the list needs. */
13
26
  export type CatalogEntity = {
27
+ /** Stable identifier of the row. */
14
28
  readonly id: string;
15
29
  readonly title: string;
30
+ /** A status string; only `passed`, `failed`, `skipped` and `running` are counted and ranked, any other sorts after them. */
16
31
  readonly status: string;
32
+ /** Duration in milliseconds, or null when unknown (sorted last by `duration`, counted as 0 in a group's total). */
17
33
  readonly durationMs: number | null;
18
34
  /** Group label for `file` (a source file, or a branch). */
19
35
  readonly file: string;
36
+ /** Group label for `package`; empty groups under `Other`. */
20
37
  readonly packageName: string;
38
+ /** Group label for `runtime`; empty groups under `Other`. */
21
39
  readonly runtime: string;
22
40
  /** One muted line under the title: a failure, or a date. */
23
41
  readonly detail: string;
42
+ /** Everything the text query may match (title, ids, paths, ...), built by the caller. */
24
43
  readonly search: string;
44
+ /** Where selecting the row navigates, or null when it is not a link. */
25
45
  readonly href: string | null;
26
46
  };
47
+ /** One drawn group of the list. */
27
48
  export type CatalogGroupView = {
28
49
  readonly name: string;
50
+ /** Sum of the members' durations in milliseconds; unknown durations count as 0. */
29
51
  readonly durationMs: number;
52
+ /** How many members have status `failed`. */
30
53
  readonly failed: number;
31
54
  readonly entities: readonly CatalogEntity[];
32
55
  };
56
+ /** Counts entities per status filter (`all` is the total); a status the list does not know is counted only in `all`. */
33
57
  export declare function catalogCounts(entities: readonly CatalogEntity[]): Record<CatalogStatus, number>;
34
- /** The list's search params, dropped when a test route carries frame and zoom. */
58
+ /** Keeps only the list's own URL search params (`q`, `status`, `group`, `sort`, `layout`, `spec`, `tab`, `partq`) and drops empty ones, so a test route's frame and zoom params do not leak into a link back to the list. */
35
59
  export declare function catalogParams(params: Readonly<Record<string, string>>): Record<string, string>;
36
- /** Filter, sort, then one group per chosen key. `recent` keeps input order. */
60
+ /**
61
+ * Filters entities by the query's status and text, sorts them, then splits them into one group per value of the
62
+ * chosen key. `recent` keeps input order.
63
+ *
64
+ * Group order: for `status`, failed, running, skipped, passed; otherwise alphabetical by name with `Other` (the
65
+ * label for an empty file, package or runtime) last. With `none` there is a single group named `All`. When
66
+ * nothing matches, no group is returned.
67
+ */
37
68
  export declare function arrangeCatalog(entities: readonly CatalogEntity[], query: CatalogQuery): readonly CatalogGroupView[];
@@ -1,10 +1,17 @@
1
- /** Shared search, filter, group and sort for reports and the tests inside one. */
1
+ /**
2
+ * @module Catalog search, filter, group and sort
3
+ *
4
+ * The pure view logic behind the viewer's list of reports (and of the tests inside one): given entities and a
5
+ * query (text, status, grouping, sort), it returns the groups to draw. It owns no data and does no I/O; the
6
+ * caller maps its own records to {@link CatalogEntity}. Input is never mutated.
7
+ */
2
8
  const STATUS_RANK = {
3
9
  failed: 0,
4
10
  running: 1,
5
11
  skipped: 2,
6
12
  passed: 3,
7
13
  };
14
+ /** Counts entities per status filter (`all` is the total); a status the list does not know is counted only in `all`. */
8
15
  export function catalogCounts(entities) {
9
16
  const counts = {
10
17
  all: entities.length,
@@ -83,7 +90,7 @@ const LIST_KEYS = [
83
90
  'tab',
84
91
  'partq',
85
92
  ];
86
- /** The list's search params, dropped when a test route carries frame and zoom. */
93
+ /** Keeps only the list's own URL search params (`q`, `status`, `group`, `sort`, `layout`, `spec`, `tab`, `partq`) and drops empty ones, so a test route's frame and zoom params do not leak into a link back to the list. */
87
94
  export function catalogParams(params) {
88
95
  const out = {};
89
96
  for (const key of LIST_KEYS) {
@@ -93,7 +100,14 @@ export function catalogParams(params) {
93
100
  }
94
101
  return out;
95
102
  }
96
- /** Filter, sort, then one group per chosen key. `recent` keeps input order. */
103
+ /**
104
+ * Filters entities by the query's status and text, sorts them, then splits them into one group per value of the
105
+ * chosen key. `recent` keeps input order.
106
+ *
107
+ * Group order: for `status`, failed, running, skipped, passed; otherwise alphabetical by name with `Other` (the
108
+ * label for an empty file, package or runtime) last. With `none` there is a single group named `All`. When
109
+ * nothing matches, no group is returned.
110
+ */
97
111
  export function arrangeCatalog(entities, query) {
98
112
  const shown = entities.filter((entity) => matches(entity, query));
99
113
  const sorted = query.sort === 'recent'
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @module Stores named on a command line
3
+ *
4
+ * Turns `--store`, `--backends` and `ATLAS_STORE` into opened backends for the `atlas report` commands, and reads the
5
+ * small flag grammar those commands share (`--name value`, repeatable). A mistyped directory is an error here, never
6
+ * an empty store: a gate that read nothing must not read as a pass.
7
+ *
8
+ * @see docs/deploying.md
9
+ */
10
+ import type { BackendInput } from './compose.js';
11
+ import { type OpenedStore } from './store-url.js';
12
+ type Env = Readonly<Record<string, string | undefined>>;
13
+ /** A backend of a command: what `combineBackends` reads, plus, when it came from a URL, how it was opened (`location`, `server`, `s3`). */
14
+ export type CliBackend = BackendInput & Partial<OpenedStore>;
15
+ /** Every value given for `--<name>`, in order. `--name` with nothing after it is ignored. */
16
+ export declare function values(argv: readonly string[], name: string): string[];
17
+ /** The first value given for `--<name>`, or `undefined`. */
18
+ export declare function value(argv: readonly string[], name: string): string | undefined;
19
+ /**
20
+ * Every store a command reads: `--store` (a directory or `s3://…`, optionally `id=url`), then
21
+ * `--backends <module>`, or `ATLAS_STORE` when neither is given. A directory that is not there is an
22
+ * error: a mistyped path must never read as an empty store.
23
+ */
24
+ export declare function backendsOf(argv: readonly string[], base: string, env: Env): Promise<CliBackend[]>;
25
+ /** The one store a command writes to. A directory is created; a bucket must already exist. */
26
+ export declare function writeStoreOf(flag: string | undefined, base: string, env: Env, name?: string): OpenedStore;
27
+ /**
28
+ * Which layers take part, from `--branch <name>` (repeatable) and `--ignore-modified`. By default all do:
29
+ * a report is whatever the newest results are, wherever they were recorded. A gate that must speak for
30
+ * a branch narrows it; the narrowing is yours to choose, never Atlas's.
31
+ */
32
+ export declare function layerFilter(argv: readonly string[], env: Env): ((layer: {
33
+ readonly branch: string | null;
34
+ readonly freshness?: 'unknown' | 'modified';
35
+ }) => boolean) | undefined;
36
+ export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @module Stores named on a command line
3
+ *
4
+ * Turns `--store`, `--backends` and `ATLAS_STORE` into opened backends for the `atlas report` commands, and reads the
5
+ * small flag grammar those commands share (`--name value`, repeatable). A mistyped directory is an error here, never
6
+ * an empty store: a gate that read nothing must not read as a pass.
7
+ *
8
+ * @see docs/deploying.md
9
+ */
10
+ import { existsSync, mkdirSync, statSync } from 'node:fs';
11
+ import path from 'node:path';
12
+ import { pathToFileURL } from 'node:url';
13
+ import { openBackends, openStore, storeSpecs } from './store-url.js';
14
+ /** Every value given for `--<name>`, in order. `--name` with nothing after it is ignored. */
15
+ export function values(argv, name) {
16
+ return argv.flatMap((token, i) => (token === `--${name}` && argv[i + 1] !== undefined ? [argv[i + 1]] : []));
17
+ }
18
+ /** The first value given for `--<name>`, or `undefined`. */
19
+ export function value(argv, name) {
20
+ return values(argv, name)[0];
21
+ }
22
+ /**
23
+ * Every store a command reads: `--store` (a directory or `s3://…`, optionally `id=url`), then
24
+ * `--backends <module>`, or `ATLAS_STORE` when neither is given. A directory that is not there is an
25
+ * error: a mistyped path must never read as an empty store.
26
+ */
27
+ export async function backendsOf(argv, base, env) {
28
+ const specs = storeSpecs(values(argv, 'store'), env);
29
+ const backends = openBackends(specs, { base, env });
30
+ backends.forEach((backend, i) => {
31
+ const location = backend.location;
32
+ if (location?.kind === 'directory' && (!existsSync(location.directory) || !statSync(location.directory).isDirectory())) {
33
+ throw new Error(`Store directory not found: ${specs[i].replace(/^[a-z0-9-]+=/, '')}. If a node failed to publish or upload, fix that first`);
34
+ }
35
+ });
36
+ const used = new Set(backends.map(backend => backend.id));
37
+ for (const file of values(argv, 'backends')) {
38
+ const mod = (await import(pathToFileURL(path.resolve(base, file)).href));
39
+ if (!mod.default)
40
+ throw new Error(`${file} has no default export: a list of { id, label?, store }`);
41
+ for (const backend of typeof mod.default === 'function' ? await mod.default() : mod.default) {
42
+ if (used.has(backend.id))
43
+ throw new Error(`Two stores are named "${backend.id}"`);
44
+ used.add(backend.id);
45
+ backends.push(backend);
46
+ }
47
+ }
48
+ if (!backends.length)
49
+ throw new Error('Give at least one --store <dir or s3://bucket/prefix> (or --backends <module>), or set ATLAS_STORE');
50
+ return backends;
51
+ }
52
+ /** The one store a command writes to. A directory is created; a bucket must already exist. */
53
+ export function writeStoreOf(flag, base, env, name = 'store') {
54
+ const specs = flag ? [flag] : storeSpecs([], env);
55
+ if (specs.length !== 1)
56
+ throw new Error(specs.length ? `Give one --${name} to write to (ATLAS_STORE names ${specs.length})` : `Give --${name} <dir or s3://bucket/prefix>, or set ATLAS_STORE`);
57
+ const opened = openStore(/^([a-z0-9-]+)=(.+)$/.exec(specs[0])?.[2] ?? specs[0], { base, env });
58
+ if (opened.location.kind === 'directory')
59
+ mkdirSync(opened.location.directory, { recursive: true });
60
+ return opened;
61
+ }
62
+ /**
63
+ * Which layers take part, from `--branch <name>` (repeatable) and `--ignore-modified`. By default all do:
64
+ * a report is whatever the newest results are, wherever they were recorded. A gate that must speak for
65
+ * a branch narrows it; the narrowing is yours to choose, never Atlas's.
66
+ */
67
+ export function layerFilter(argv, env) {
68
+ const branches = [...values(argv, 'branch'), ...(env.ATLAS_ONLY_BRANCH ?? '').split(/[\s,]+/).filter(Boolean)];
69
+ const ignoreModified = argv.includes('--ignore-modified') || env.ATLAS_IGNORE_MODIFIED === '1' || env.ATLAS_IGNORE_MODIFIED === 'true';
70
+ if (!branches.length && !ignoreModified)
71
+ return undefined;
72
+ return layer => (!branches.length || (layer.branch !== null && branches.includes(layer.branch))) && (!ignoreModified || layer.freshness === undefined);
73
+ }
@@ -1,11 +1,16 @@
1
1
  /**
2
- * Injectable wall / virtual time for deterministic simulation.
3
- * Production uses {@link wallClock} / {@link wallTimers}.
2
+ * @module Injectable time
3
+ *
4
+ * The two time dependencies of the reports code, as types, so tests and simulations can supply virtual time.
5
+ * Production hosts pass `{ now: Date.now }` and wrappers over the global timers (see `host.ts`). There is no
6
+ * default implementation in this module.
4
7
  */
8
+ /** The clock a reader or route uses to decide when polling or tailing has run long enough. */
5
9
  export type Clock = {
6
10
  /** Epoch milliseconds (virtual or wall). */
7
11
  readonly now: () => number;
8
12
  };
13
+ /** The timer functions the reports code schedules work with; each handle is opaque and only passed back to the matching `clear*`. */
9
14
  export type Timers = {
10
15
  readonly setTimeout: (fn: () => void, ms: number) => unknown;
11
16
  readonly clearTimeout: (handle: unknown) => void;
@@ -1,4 +1,7 @@
1
1
  /**
2
- * Injectable wall / virtual time for deterministic simulation.
3
- * Production uses {@link wallClock} / {@link wallTimers}.
2
+ * @module Injectable time
3
+ *
4
+ * The two time dependencies of the reports code, as types, so tests and simulations can supply virtual time.
5
+ * Production hosts pass `{ now: Date.now }` and wrappers over the global timers (see `host.ts`). There is no
6
+ * default implementation in this module.
4
7
  */