@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,56 @@
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
+ /** Project namespace on a shared Atlas node. Omit for the legacy default project. */
15
+ readonly project?: string;
16
+ /** The Atlas server, such as `https://atlas.example.com`. A path on it (`/atlas`) is kept. */
17
+ readonly url: string;
18
+ /** A publish or admin token to write with, a read token to read with. Never put it in the URL. */
19
+ readonly token?: string;
20
+ readonly fetch?: typeof fetch;
21
+ /** Retries after the first attempt, for network errors and server errors. Default 4. */
22
+ readonly retries?: number;
23
+ /** Per attempt, to start answering, in milliseconds. Default 30 000. */
24
+ readonly timeoutMs?: number;
25
+ readonly sleep?: (ms: number) => Promise<void>;
26
+ };
27
+ /** What an Atlas server said no to, with what to do about it. */
28
+ export declare class AtlasHttpError extends Error {
29
+ readonly status: number | null;
30
+ readonly retryable: boolean;
31
+ constructor(message: string, status: number | null, retryable?: boolean);
32
+ }
33
+ /** 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). */
34
+ export type HttpStore = ObjectStore & PagedListing & {
35
+ /** The same authenticated node connection, scoped to another project. */
36
+ forProject(project: string): HttpStore;
37
+ /** `https://atlas.example.com`, with no secrets. */
38
+ readonly label: string;
39
+ readonly url: string;
40
+ /** Is the server there, and does it know this token? */
41
+ ping(): Promise<{
42
+ readonly ok: boolean;
43
+ readonly ms: number;
44
+ readonly role?: string;
45
+ readonly error?: string;
46
+ }>;
47
+ /** 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. */
48
+ status(query?: URLSearchParams): Promise<unknown>;
49
+ };
50
+ /**
51
+ * An {@link ObjectStore} on an Atlas server, over its store protocol (`/api/store`). The server holds the
52
+ * layers (on its own disk, or in whatever backends it was given), checks what is uploaded, and shows
53
+ * everyone the combined report at once. Anything that publishes to a store, `publishChecks`, spec sets,
54
+ * run uploads, `copyStore`, works against it unchanged.
55
+ */
56
+ export declare function httpStore(options: HttpStoreOptions): HttpStore;
@@ -0,0 +1,148 @@
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
+ import { ProjectSlugSchema } from '../../schema/src/project.js';
13
+ /** What an Atlas server said no to, with what to do about it. */
14
+ export class AtlasHttpError extends Error {
15
+ status;
16
+ retryable;
17
+ constructor(message, status, retryable = false) {
18
+ super(message);
19
+ this.status = status;
20
+ this.retryable = retryable;
21
+ this.name = 'AtlasHttpError';
22
+ }
23
+ }
24
+ const encode = (key) => key.split('/').map(encodeURIComponent).join('/');
25
+ function explain(status, base, message) {
26
+ const said = message ? `: ${message}` : '';
27
+ switch (status) {
28
+ case 401: return `${base} wants a token${said}. Set ATLAS_TOKEN (a publish token to upload, a read token to read).`;
29
+ case 403: return `${base} knows this token but it may not do that${said}. Uploading needs a publish or admin token.`;
30
+ case 409: return `${base} refused a change to something that already exists${said}`;
31
+ case 413: return `${base} refused an object that large${said}. Attach big files by uri, or raise the server's limits.maxObjectMiB.`;
32
+ case 422: return `${base} did not accept it${said}`;
33
+ default: return `${base} answered ${status}${said}`;
34
+ }
35
+ }
36
+ /**
37
+ * An {@link ObjectStore} on an Atlas server, over its store protocol (`/api/store`). The server holds the
38
+ * layers (on its own disk, or in whatever backends it was given), checks what is uploaded, and shows
39
+ * everyone the combined report at once. Anything that publishes to a store, `publishChecks`, spec sets,
40
+ * run uploads, `copyStore`, works against it unchanged.
41
+ */
42
+ export function httpStore(options) {
43
+ const base = options.url.replace(/\/+$/, '');
44
+ const api = options.project ? `${base}/api/projects/${ProjectSlugSchema.parse(options.project)}` : `${base}/api`;
45
+ const root = `${api}/store`;
46
+ const doFetch = options.fetch ?? fetch;
47
+ const retries = options.retries ?? 4;
48
+ const timeoutMs = options.timeoutMs ?? 30_000;
49
+ const sleep = options.sleep ?? ((ms) => new Promise(resolve => setTimeout(resolve, ms)));
50
+ const authorization = options.token ? { authorization: `Bearer ${options.token}` } : {};
51
+ async function request(method, url, init = {}) {
52
+ for (let attempt = 0;; attempt++) {
53
+ let failure;
54
+ try {
55
+ // The clock is for the server to start answering; a large body may take longer to arrive.
56
+ const controller = new AbortController();
57
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
58
+ let response;
59
+ try {
60
+ response = await doFetch(url, { method, headers: init.contentType ? { ...authorization, 'content-type': init.contentType } : authorization, ...(init.body ? { body: init.body } : {}), signal: controller.signal });
61
+ }
62
+ finally {
63
+ clearTimeout(timer);
64
+ }
65
+ if (response.ok)
66
+ return response;
67
+ if (response.status === 404 && init.allow404) {
68
+ await response.body?.cancel();
69
+ return null;
70
+ }
71
+ const said = await response.json().then((body) => body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null, () => null);
72
+ failure = new AtlasHttpError(explain(response.status, base, said), response.status, response.status >= 500 || response.status === 429 || response.status === 408);
73
+ }
74
+ catch (error) {
75
+ failure = new AtlasHttpError(`cannot reach ${base} (${error instanceof Error ? (error.name === 'AbortError' ? `no answer in ${timeoutMs} ms` : error.message) : String(error)})`, null, true);
76
+ }
77
+ if (!failure.retryable || attempt >= retries)
78
+ throw failure;
79
+ await sleep(Math.random() * Math.min(8_000, 100 * 2 ** attempt));
80
+ }
81
+ }
82
+ const store = {
83
+ forProject: project => httpStore({ ...options, project }),
84
+ label: base,
85
+ url: base,
86
+ async get(key) {
87
+ const response = await request('GET', `${root}/${encode(key)}`, { allow404: true });
88
+ if (!response)
89
+ return null;
90
+ return { body: response.body ?? new Blob([]).stream(), contentType: response.headers.get('content-type') ?? 'application/octet-stream', size: Number(response.headers.get('content-length') ?? 0) };
91
+ },
92
+ async put(key, bytes, contentType) {
93
+ await (await request('PUT', `${root}/${encode(key)}`, { body: bytes, contentType }))?.arrayBuffer();
94
+ },
95
+ async head(key) {
96
+ return (await request('HEAD', `${root}/${encode(key)}`, { allow404: true })) !== null;
97
+ },
98
+ async delete(key) {
99
+ await (await request('DELETE', `${root}/${encode(key)}`, { allow404: true }))?.arrayBuffer();
100
+ },
101
+ async deleteMany(keys) {
102
+ for (const key of keys)
103
+ await store.delete(key);
104
+ },
105
+ async getUri() {
106
+ return null;
107
+ },
108
+ async listPage(prefix = '', page = {}) {
109
+ const query = new URLSearchParams({ prefix, limit: String(page.limit ?? 1000) });
110
+ if (page.after !== undefined)
111
+ query.set('after', page.after);
112
+ const response = (await request('GET', `${root}?${query}`));
113
+ const body = await response.json();
114
+ if (!Array.isArray(body.keys))
115
+ throw new AtlasHttpError(`${base} did not answer a listing`, response.status);
116
+ return { keys: body.keys, next: body.next ?? null };
117
+ },
118
+ async listKeys(prefix = '') {
119
+ const keys = [];
120
+ for (let after;;) {
121
+ const page = await store.listPage(prefix, after === undefined ? {} : { after });
122
+ keys.push(...page.keys);
123
+ if (page.next === null)
124
+ return keys;
125
+ after = page.next;
126
+ }
127
+ },
128
+ withPrefix(prefix) {
129
+ return new ObjectStoreWithPrefix(store, prefix);
130
+ },
131
+ async status(query = new URLSearchParams()) {
132
+ const suffix = query.size ? `?${query}` : '';
133
+ return (await request('GET', `${api}/status${suffix}`)).json();
134
+ },
135
+ async ping() {
136
+ const started = performance.now();
137
+ try {
138
+ const response = (await request('GET', `${base}/api/server`));
139
+ const info = await response.json();
140
+ return { ok: true, ms: Math.round(performance.now() - started), ...(info.role ? { role: info.role } : {}) };
141
+ }
142
+ catch (error) {
143
+ return { ok: false, ms: Math.round(performance.now() - started), error: error instanceof Error ? error.message : String(error) };
144
+ }
145
+ }
146
+ };
147
+ return store;
148
+ }
@@ -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;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @module Importing checks from existing test reports
3
+ *
4
+ * Turns the output of tools that already exist (CTRF JSON, JUnit XML, an Atlas run `manifest.json`) into checks a
5
+ * layer can hold, so any test suite can publish without a custom producer. It owns the mapping of foreign
6
+ * statuses onto the four Atlas statuses (anything not clearly passed or failed is `not-run`, never a pass) and the
7
+ * rule for check ids: stable across runs, built from the file, suite and name, with a repeated id numbered
8
+ * (`#2`, `#3`) so no check is silently counted as another. It reads text it is given and touches no files.
9
+ *
10
+ * @see docs/report-contract.md (choosing a check id)
11
+ */
12
+ import path from 'node:path';
13
+ import { parseJunit } from './junit.js';
14
+ import { specPath } from './layer-merge.js';
15
+ function record(value) {
16
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {};
17
+ }
18
+ function text(value) {
19
+ return typeof value === 'string' ? value : '';
20
+ }
21
+ function finite(value) {
22
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
23
+ }
24
+ /** 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. */
25
+ export function uniqueChecks(checks) {
26
+ const seen = new Map();
27
+ return checks.map(check => {
28
+ const count = (seen.get(check.id) ?? 0) + 1;
29
+ seen.set(check.id, count);
30
+ return count === 1 ? check : { ...check, id: `${check.id} #${count}` };
31
+ });
32
+ }
33
+ const CTRF_STATUS = { passed: 'passed', failed: 'failed', skipped: 'not-run', pending: 'not-run', other: 'not-run' };
34
+ /**
35
+ * A [CTRF](https://ctrf.io) report (`results.tests`). A check's id is its file, its suites and its
36
+ * name, so the same test keeps the same id in every run. Skipped, pending and other results are
37
+ * `not-run`: never a pass; a status this function does not know is also `not-run`.
38
+ *
39
+ * Also carries what CTRF has beyond the outcome as evidence: tags, labels (browser, device, flaky, run environment
40
+ * and tool), steps, attachments (files by `path`, `url` as `uri`, inline screenshot, stdout/stderr, trace), retry
41
+ * attempts, a snippet annotation and `extra` under the `ctrf.extra` extension. `recordedAtMs` is `summary.stop`,
42
+ * else `summary.start`. Throws `Not a CTRF report: results.tests is missing` when there is no `results.tests` array.
43
+ *
44
+ * @param report The parsed CTRF JSON.
45
+ */
46
+ export function checksFromCtrf(report) {
47
+ const results = record(record(report).results);
48
+ const tests = Array.isArray(results.tests) ? results.tests : [];
49
+ if (!Array.isArray(results.tests))
50
+ throw new Error('Not a CTRF report: results.tests is missing');
51
+ const checks = tests.map((raw, index) => {
52
+ const test = record(raw);
53
+ const suite = Array.isArray(test.suite) ? test.suite.map(text) : text(test.suite) ? [text(test.suite)] : [];
54
+ const file = text(test.filePath);
55
+ const name = text(test.name) || `test ${index + 1}`;
56
+ const status = CTRF_STATUS[text(test.status)] ?? 'not-run';
57
+ const message = text(test.message) || text(test.trace);
58
+ const duration = finite(test.duration);
59
+ const tags = Array.isArray(test.tags) ? test.tags.map(text).filter(Boolean) : [];
60
+ const labels = {};
61
+ for (const key of ['browser', 'device', 'type', 'os', 'osPlatform'])
62
+ if (text(test[key]))
63
+ labels[key] = text(test[key]);
64
+ if (test.flaky === true)
65
+ labels.flaky = 'true';
66
+ const steps = Array.isArray(test.steps) ? test.steps.map(step => { const item = record(step); return { name: text(item.name) || 'step', ...(text(item.status) ? { status: text(item.status) } : {}) }; }) : [];
67
+ const attachments = [
68
+ ...(Array.isArray(test.attachments) ? test.attachments.flatMap(raw => { const item = record(raw); return text(item.name) || text(item.path) ? [{ name: text(item.name) || path.basename(text(item.path)), ...(text(item.contentType) ? { contentType: text(item.contentType) } : {}), ...(text(item.path) ? { path: text(item.path) } : {}), ...(text(item.url) ? { uri: text(item.url) } : {}) }] : []; }) : []),
69
+ ...(text(test.screenshot) ? [{ name: 'screenshot.png', role: 'screenshot', contentType: 'image/png', base64: text(test.screenshot) }] : []),
70
+ ...logOf(test.stdout, 'stdout'),
71
+ ...logOf(test.stderr, 'stderr'),
72
+ ...(text(test.trace) ? [{ name: 'stack trace', role: 'log', contentType: 'text/plain', text: text(test.trace) }] : [])
73
+ ];
74
+ const attempts = Array.isArray(test.retryAttempts) ? test.retryAttempts.map((raw, at) => { const item = record(raw); return { index: finite(item.attempt) !== null ? Math.max(0, finite(item.attempt) - 1) : at, status: (CTRF_STATUS[text(item.status)] ?? 'not-run'), ...(finite(item.duration) !== null ? { durationMs: finite(item.duration) } : {}), ...(text(item.message) ? { detail: text(item.message) } : {}) }; }) : [];
75
+ const annotations = text(test.snippet) ? [{ type: 'snippet', message: text(test.snippet), level: 'info', ...(file ? { source: { file, ...(finite(test.line) !== null ? { line: finite(test.line) } : {}) } } : {}) }] : [];
76
+ const extra = record(test.extra);
77
+ return {
78
+ id: [file, ...suite, name].filter(Boolean).join(' > '),
79
+ title: name,
80
+ status,
81
+ detail: status === 'failed' ? (message || 'failed') : test.flaky === true ? 'Passed on retry (flaky)' : message,
82
+ ...(file ? { specPath: specPath(file) } : {}),
83
+ measurements: [],
84
+ ...(duration === null ? {} : { durationMs: duration }),
85
+ ...(tags.length ? { tags } : {}),
86
+ ...(Object.keys(labels).length ? { labels } : {}),
87
+ ...(steps.length ? { steps } : {}),
88
+ ...(attachments.length ? { attachments } : {}),
89
+ ...(attempts.length ? { attempts } : {}),
90
+ ...(annotations.length ? { annotations } : {}),
91
+ ...(Object.keys(extra).length ? { extensions: { 'ctrf.extra': extra } } : {})
92
+ };
93
+ });
94
+ const summary = record(results.summary);
95
+ const hints = tests.map(raw => {
96
+ const test = record(raw);
97
+ return [...(Array.isArray(test.suite) ? test.suite.map(text) : [text(test.suite)]), text(test.filePath)].filter(Boolean);
98
+ });
99
+ const tool = record(results.tool);
100
+ const labels = {};
101
+ for (const [key, value] of Object.entries(record(results.environment)))
102
+ if (typeof value === 'string' || typeof value === 'number')
103
+ labels[key] = String(value);
104
+ if (text(tool.name))
105
+ labels.tool = text(tool.name);
106
+ if (text(tool.version))
107
+ labels['tool.version'] = text(tool.version);
108
+ return { checks: uniqueChecks(checks), hints, recordedAtMs: finite(summary.stop) ?? finite(summary.start), tool: text(tool.name) || null, ...(Object.keys(labels).length ? { labels } : {}) };
109
+ }
110
+ /** CTRF writes a stream as a list of lines. */
111
+ function logOf(value, name) {
112
+ const lines = Array.isArray(value) ? value.map(text).join('\n') : text(value);
113
+ return lines.trim() ? [{ name, role: 'log', contentType: 'text/plain', text: lines }] : [];
114
+ }
115
+ /** Many JUnit writers omit the offset. Read such a time as UTC, so it is the same wherever Atlas runs. */
116
+ function parseTimestamp(value) {
117
+ return Date.parse(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(value) ? `${value}Z` : value);
118
+ }
119
+ /**
120
+ * A JUnit XML report: one check per `<testcase>`, identified by its file or class and its name (`file or classname > name`).
121
+ *
122
+ * A case with a `<failure>` (an assertion that did not hold) or an `<error>` (an exception) is `failed`, one with
123
+ * `<skipped>` is `not-run`, any other is `passed`. Durations are seconds converted to milliseconds. Properties become labels, `<system-out>`/`<system-err>` become log
124
+ * attachments, and a Jenkins `[[ATTACHMENT|path]]` line in the output attaches that file by `path`. `recordedAtMs`
125
+ * is the newest `<testsuite timestamp>` (a timestamp with no offset is read as UTC). Throws
126
+ * `Not a JUnit report: no <testsuite> found` when the text has no test cases and no `<testsuite`.
127
+ */
128
+ export function checksFromJunit(xml) {
129
+ const cases = parseJunit(xml);
130
+ if (cases.length === 0 && !/<testsuite/.test(xml))
131
+ throw new Error('Not a JUnit report: no <testsuite> found');
132
+ const checks = cases.map((item) => {
133
+ const where = item.file || item.classname;
134
+ // Jenkins' convention: a line `[[ATTACHMENT|/path/to/file]]` in a test's output attaches that file.
135
+ const jenkins = [...`${item.stdout ?? ''}\n${item.stderr ?? ''}`.matchAll(/\[\[ATTACHMENT\|([^\]]+)\]\]/g)].map(match => ({ name: path.basename(match[1]), path: match[1] }));
136
+ const attachments = [
137
+ ...(item.stdout ? [{ name: 'stdout', role: 'log', contentType: 'text/plain', text: item.stdout }] : []),
138
+ ...(item.stderr ? [{ name: 'stderr', role: 'log', contentType: 'text/plain', text: item.stderr }] : []),
139
+ ...jenkins
140
+ ];
141
+ return {
142
+ id: [where, item.name].filter(Boolean).join(' > '),
143
+ title: item.name,
144
+ status: item.status === 'passed' ? 'passed' : item.status === 'failed' ? 'failed' : 'not-run',
145
+ detail: item.failure ?? '',
146
+ ...(item.file ? { specPath: specPath(item.file) } : {}),
147
+ measurements: [],
148
+ ...(item.time === null ? {} : { durationMs: Math.round(item.time * 1000) }),
149
+ ...(item.properties ? { labels: item.properties } : {}),
150
+ ...(attachments.length ? { attachments } : {})
151
+ };
152
+ });
153
+ const hints = cases.map(item => [item.classname, item.suite, item.file].filter(Boolean));
154
+ const stamps = [...xml.matchAll(/<testsuite\b[^>]*\stimestamp="([^"]+)"/g)].map(match => parseTimestamp(match[1])).filter(Number.isFinite);
155
+ return { checks: uniqueChecks(checks), hints, recordedAtMs: stamps.length ? Math.max(...stamps) : null, tool: null };
156
+ }
157
+ /**
158
+ * An Atlas run (`manifest.json`): one check per path. A path's key, which the same start and
159
+ * events give in every run, is its id. A flaky path passed. While the run is still going, a
160
+ * running path is `running` and a queued one `not-run`, so a snapshot never reads as finished.
161
+ *
162
+ * Every check's `specPath` is the run's root chart file under its spec directory. A run is still going when
163
+ * `run.finishedAt` is `null`. `recordedAtMs` is `run.finishedAt`, else `run.startedAt` plus `run.durationMs`.
164
+ * Throws `Not an Atlas manifest: run and paths are required` when `paths` or `run.id` is missing.
165
+ *
166
+ * @param manifest The parsed `manifest.json` of an Atlas run.
167
+ */
168
+ export function checksFromManifest(manifest) {
169
+ const root = record(manifest);
170
+ const run = record(root.run);
171
+ if (!Array.isArray(root.paths) || !run.id)
172
+ throw new Error('Not an Atlas manifest: run and paths are required');
173
+ const charts = Array.isArray(root.charts) ? root.charts.map(record) : [];
174
+ const chart = charts.find(item => item.parentState == null) ?? charts[0];
175
+ const directory = text(run.specDirectory).replace(/\/+$/, '');
176
+ const file = text(chart?.file);
177
+ const path = file ? specPath(directory ? `${directory}/${file}` : file) : null;
178
+ const live = run.finishedAt === null;
179
+ const checks = root.paths.map((raw) => {
180
+ const item = record(raw);
181
+ const status = text(item.status);
182
+ const attempts = Array.isArray(item.attempts) ? item.attempts.map(record) : [];
183
+ const progress = text(item.progress);
184
+ const mapped = live && progress === 'running' ? 'running' : live && progress === 'queued' ? 'not-run' : status === 'passed' || status === 'flaky' ? 'passed' : status === 'failed' ? 'failed' : 'not-run';
185
+ const duration = finite(item.durationMs);
186
+ const steps = Array.isArray(item.steps) ? item.steps.map(raw => {
187
+ const step = record(raw);
188
+ const state = text(step.status);
189
+ return { name: text(step.event) || 'step', kind: 'event', status: state === 'passed' || state === 'flaky' ? 'passed' : state === 'failed' ? 'failed' : 'not-reached', ...(text(step.reason) ? { detail: text(step.reason) } : {}), ...(text(step.transition) ? { extensions: { 'atlas.transition': text(step.transition) } } : {}) };
190
+ }) : [];
191
+ const journeys = Array.isArray(item.journeys) ? item.journeys.map(text).filter(Boolean) : [];
192
+ return {
193
+ id: text(item.key) || `${text(item.kind)}: ${text(item.name)}`,
194
+ title: text(item.name) || text(item.id),
195
+ status: mapped,
196
+ detail: mapped === 'failed' ? text(attempts.at(-1)?.error) || 'failed' : status === 'flaky' ? 'Passed on retry (flaky)' : '',
197
+ ...(path ? { specPath: path } : {}),
198
+ measurements: [],
199
+ ...(duration === null ? {} : { durationMs: duration }),
200
+ ...(steps.length ? { steps } : {}),
201
+ ...(text(item.kind) ? { labels: { kind: text(item.kind), ...(text(item.seed) ? { seed: text(item.seed) } : {}) } } : {}),
202
+ ...(journeys.length ? { tags: journeys } : {})
203
+ };
204
+ });
205
+ const startedAt = Date.parse(text(run.startedAt));
206
+ const finishedAt = Date.parse(text(run.finishedAt));
207
+ const duration = finite(run.durationMs) ?? 0;
208
+ const at = Number.isFinite(finishedAt) ? finishedAt : Number.isFinite(startedAt) ? startedAt + duration : null;
209
+ return { checks: uniqueChecks(checks), recordedAtMs: at, tool: 'atlas' };
210
+ }
@@ -0,0 +1,40 @@
1
+ import { InitPlanSchema } from '../../schema/src/cli-results.js';
2
+ import type { z } from 'zod/v4';
3
+ /** The document `atlas init` prints, and what {@link planInit} returns. */
4
+ export type InitPlan = z.infer<typeof InitPlanSchema>;
5
+ /** What `planInit` is told: where the project is, which server, and what to do with the plan. */
6
+ export type InitOptions = {
7
+ /** The project directory. Default: the current directory. */
8
+ readonly directory: string;
9
+ /** The Atlas server clients will send results to (`https://atlas.example.com`). Falls back to `ATLAS_STORE` when that is a server. */
10
+ readonly server?: string;
11
+ /** Write the files of the plan. Without it nothing is changed. */
12
+ readonly write?: boolean;
13
+ /** Ask the server who we are and whether this token may upload. */
14
+ readonly verify?: boolean;
15
+ readonly env?: Readonly<Record<string, string | undefined>>;
16
+ /** For tests: git as a function from arguments to output (or null). Default: the `git` command. */
17
+ readonly git?: (args: readonly string[], cwd: string) => string | null;
18
+ readonly fetch?: typeof fetch;
19
+ };
20
+ /**
21
+ * Detects the project in `options.directory` and returns the plan, writing its files when `options.write` is set.
22
+ * Never throws for a project it cannot understand: it says so in `warnings` and plans what it can (the `AGENTS.md`
23
+ * section with no suites, so the next agent knows to add them). Throws only for a directory that does not exist.
24
+ *
25
+ * @example
26
+ * const plan = await planInit({ directory: process.cwd(), server: 'https://atlas.example.com' });
27
+ * for (const suite of plan.suites) console.log(suite.run, '→', suite.publish);
28
+ */
29
+ export declare function planInit(options: InitOptions): Promise<InitPlan>;
30
+ /** The plan as the text a person reads: what was found, what is planned, and what to do next. */
31
+ export declare function formatPlan(plan: InitPlan): string;
32
+ /** The usage of `atlas init`, printed by `atlas init --help`. */
33
+ export declare const INIT_USAGE = "atlas init \u2014 integrate a project with an Atlas server\n\n atlas init [--dir <project>] [--server <url>] [--write] [--verify] [--format json]\n\n Looks at the project (package manager, test runners, spec files, CI) and prints a plan: for each suite the command\n that runs it and the command that sends its results, a CI workflow, and an AGENTS.md section that tells the next\n agent how to use Atlas here. Nothing is changed until --write; running it again changes nothing more.\n\n --dir <path> the project (default: the current directory)\n --server <url> the Atlas server results go to (default: an http(s) store in ATLAS_STORE)\n --write create or update the files of the plan (AGENTS.md between its atlas markers, .github/workflows/atlas.yml)\n --verify ask the server who this token is and whether it may upload (token: ATLAS_TOKEN)\n --format json print the plan as one JSON document (schema: init-plan.schema.json)\n\n Exit codes: 0 planned (and verified), 1 --verify failed, 2 the command was wrong.\n";
34
+ /** `atlas init`: parses the arguments, plans, prints the plan as text or JSON, and returns the exit code. */
35
+ export declare function initMain(argv: readonly string[], io: {
36
+ readonly out: (text: string) => void;
37
+ readonly env?: Readonly<Record<string, string | undefined>>;
38
+ readonly cwd?: string;
39
+ readonly fetch?: typeof fetch;
40
+ }): Promise<number>;