@crvouga/atlas 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +178 -617
  3. package/dist/bun/src/driver.d.ts +41 -7
  4. package/dist/bun/src/driver.js +47 -6
  5. package/dist/bun/src/index.d.ts +8 -0
  6. package/dist/bun/src/index.js +8 -0
  7. package/dist/core/src/cli.d.ts +14 -1
  8. package/dist/core/src/cli.js +30 -1
  9. package/dist/core/src/config.d.ts +70 -4
  10. package/dist/core/src/config.js +62 -3
  11. package/dist/core/src/events/cloudevents.d.ts +54 -9
  12. package/dist/core/src/events/cloudevents.js +60 -9
  13. package/dist/core/src/graph.d.ts +21 -0
  14. package/dist/core/src/graph.js +26 -0
  15. package/dist/core/src/index.d.ts +11 -0
  16. package/dist/core/src/index.js +11 -0
  17. package/dist/core/src/lint.d.ts +30 -0
  18. package/dist/core/src/lint.js +13 -0
  19. package/dist/core/src/plan.d.ts +39 -1
  20. package/dist/core/src/plan.js +28 -1
  21. package/dist/core/src/privacy.d.ts +29 -2
  22. package/dist/core/src/privacy.js +19 -2
  23. package/dist/core/src/report/formats.d.ts +34 -4
  24. package/dist/core/src/report/formats.js +24 -4
  25. package/dist/core/src/report/markdown.d.ts +15 -0
  26. package/dist/core/src/report/markdown.js +8 -0
  27. package/dist/core/src/run/clients.d.ts +44 -5
  28. package/dist/core/src/run/clients.js +46 -2
  29. package/dist/core/src/run/execute.d.ts +69 -16
  30. package/dist/core/src/run/execute.js +67 -16
  31. package/dist/core/src/run/frames.d.ts +13 -4
  32. package/dist/core/src/run/frames.js +19 -4
  33. package/dist/core/src/run/http.d.ts +34 -1
  34. package/dist/core/src/run/http.js +14 -1
  35. package/dist/core/src/run/types.d.ts +76 -3
  36. package/dist/core/src/run/types.js +19 -3
  37. package/dist/core/src/signals.d.ts +49 -5
  38. package/dist/core/src/signals.js +22 -5
  39. package/dist/core/src/spec/compose.d.ts +23 -1
  40. package/dist/core/src/spec/compose.js +10 -1
  41. package/dist/core/src/spec/index.d.ts +9 -1
  42. package/dist/core/src/spec/index.js +9 -1
  43. package/dist/core/src/spec/load.d.ts +22 -1
  44. package/dist/core/src/spec/load.js +31 -1
  45. package/dist/core/src/spec/parse.d.ts +16 -2
  46. package/dist/core/src/spec/parse.js +25 -2
  47. package/dist/core/src/spec/scxml.d.ts +25 -4
  48. package/dist/core/src/spec/scxml.js +36 -4
  49. package/dist/core/src/spec/types.d.ts +52 -1
  50. package/dist/core/src/spec/types.js +24 -0
  51. package/dist/detox/src/index.d.ts +54 -11
  52. package/dist/detox/src/index.js +45 -7
  53. package/dist/playwright/src/driver.d.ts +39 -3
  54. package/dist/playwright/src/driver.js +27 -3
  55. package/dist/playwright/src/index.d.ts +10 -0
  56. package/dist/playwright/src/index.js +10 -0
  57. package/dist/playwright/src/media.d.ts +50 -4
  58. package/dist/playwright/src/media.js +55 -4
  59. package/dist/playwright/src/signals.d.ts +44 -5
  60. package/dist/playwright/src/signals.js +29 -5
  61. package/dist/reports/src/attachment-files.d.ts +44 -0
  62. package/dist/reports/src/attachment-files.js +121 -0
  63. package/dist/reports/src/attachment-response.d.ts +31 -0
  64. package/dist/reports/src/attachment-response.js +55 -0
  65. package/dist/reports/src/aws-credentials.d.ts +48 -0
  66. package/dist/reports/src/aws-credentials.js +221 -0
  67. package/dist/reports/src/browser-navigation.d.ts +14 -0
  68. package/dist/reports/src/browser-navigation.js +7 -0
  69. package/dist/reports/src/bundle.d.ts +34 -2
  70. package/dist/reports/src/bundle.js +38 -2
  71. package/dist/reports/src/catalog.d.ts +34 -3
  72. package/dist/reports/src/catalog.js +17 -3
  73. package/dist/reports/src/cli-stores.d.ts +36 -0
  74. package/dist/reports/src/cli-stores.js +73 -0
  75. package/dist/reports/src/clock.d.ts +7 -2
  76. package/dist/reports/src/clock.js +5 -2
  77. package/dist/reports/src/completion-order.d.ts +18 -5
  78. package/dist/reports/src/completion-order.js +18 -5
  79. package/dist/reports/src/compose.d.ts +80 -0
  80. package/dist/reports/src/compose.js +117 -0
  81. package/dist/reports/src/copy-store.d.ts +29 -0
  82. package/dist/reports/src/copy-store.js +52 -0
  83. package/dist/reports/src/directory-outline.d.ts +38 -0
  84. package/dist/reports/src/directory-outline.js +103 -0
  85. package/dist/reports/src/directory-store.d.ts +20 -0
  86. package/dist/reports/src/directory-store.js +166 -0
  87. package/dist/reports/src/doctor.d.ts +29 -0
  88. package/dist/reports/src/doctor.js +83 -0
  89. package/dist/reports/src/effective-delta.d.ts +34 -0
  90. package/dist/reports/src/effective-delta.js +25 -0
  91. package/dist/reports/src/evidence-detail.d.ts +64 -0
  92. package/dist/reports/src/evidence-detail.js +92 -0
  93. package/dist/reports/src/evidence-live.d.ts +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 +52 -0
  120. package/dist/reports/src/http-store.js +145 -0
  121. package/dist/reports/src/http.d.ts +7 -0
  122. package/dist/reports/src/http.js +6 -0
  123. package/dist/reports/src/ingest.d.ts +54 -0
  124. package/dist/reports/src/ingest.js +210 -0
  125. package/dist/reports/src/init.d.ts +40 -0
  126. package/dist/reports/src/init.js +364 -0
  127. package/dist/reports/src/junit.d.ts +33 -6
  128. package/dist/reports/src/junit.js +87 -21
  129. package/dist/reports/src/keyboard.d.ts +20 -0
  130. package/dist/reports/src/keyboard.js +11 -0
  131. package/dist/reports/src/keys.d.ts +21 -3
  132. package/dist/reports/src/keys.js +21 -3
  133. package/dist/reports/src/layer-axes.d.ts +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/media-types.d.ts +11 -0
  147. package/dist/reports/src/media-types.js +22 -0
  148. package/dist/reports/src/namespace-guard.d.ts +10 -1
  149. package/dist/reports/src/namespace-guard.js +10 -1
  150. package/dist/reports/src/navigation.d.ts +12 -0
  151. package/dist/reports/src/navigation.js +7 -0
  152. package/dist/reports/src/object-io.d.ts +11 -4
  153. package/dist/reports/src/object-io.js +4 -4
  154. package/dist/reports/src/object-store-conformance.d.ts +27 -0
  155. package/dist/reports/src/object-store-conformance.js +87 -0
  156. package/dist/reports/src/object-store.d.ts +33 -24
  157. package/dist/reports/src/object-store.js +17 -24
  158. package/dist/reports/src/parse-with.d.ts +16 -0
  159. package/dist/reports/src/parse-with.js +14 -0
  160. package/dist/reports/src/performance-part.d.ts +25 -1
  161. package/dist/reports/src/performance-part.js +15 -1
  162. package/dist/reports/src/performance.d.ts +22 -2
  163. package/dist/reports/src/performance.js +12 -2
  164. package/dist/reports/src/persist.d.ts +12 -3
  165. package/dist/reports/src/persist.js +12 -3
  166. package/dist/reports/src/phone.d.ts +18 -1
  167. package/dist/reports/src/phone.js +18 -1
  168. package/dist/reports/src/protocol.d.ts +26 -4
  169. package/dist/reports/src/protocol.js +15 -4
  170. package/dist/reports/src/publication.d.ts +21 -1
  171. package/dist/reports/src/publication.js +21 -1
  172. package/dist/reports/src/publish-checks.d.ts +35 -0
  173. package/dist/reports/src/publish-checks.js +122 -0
  174. package/dist/reports/src/publish.d.ts +42 -9
  175. package/dist/reports/src/publish.js +36 -7
  176. package/dist/reports/src/read.d.ts +46 -16
  177. package/dist/reports/src/read.js +38 -14
  178. package/dist/reports/src/recording-bundle.d.ts +13 -2
  179. package/dist/reports/src/recording-bundle.js +4 -2
  180. package/dist/reports/src/recording-keys.d.ts +15 -1
  181. package/dist/reports/src/recording-keys.js +15 -1
  182. package/dist/reports/src/remote-report.d.ts +28 -7
  183. package/dist/reports/src/remote-report.js +15 -7
  184. package/dist/reports/src/report-cli.d.ts +51 -0
  185. package/dist/reports/src/report-cli.js +485 -0
  186. package/dist/reports/src/report-index.d.ts +99 -0
  187. package/dist/reports/src/report-index.js +337 -0
  188. package/dist/reports/src/report-loader.d.ts +10 -0
  189. package/dist/reports/src/report-loader.js +6 -0
  190. package/dist/reports/src/report-parts.d.ts +29 -3
  191. package/dist/reports/src/report-parts.js +37 -3
  192. package/dist/reports/src/report-status.d.ts +48 -0
  193. package/dist/reports/src/report-status.js +156 -0
  194. package/dist/reports/src/report-types.d.ts +73 -0
  195. package/dist/reports/src/report-types.js +3 -0
  196. package/dist/reports/src/reporter.d.ts +67 -0
  197. package/dist/reports/src/reporter.js +128 -0
  198. package/dist/reports/src/reports-explorer-route.d.ts +541 -0
  199. package/dist/reports/src/reports-explorer-route.js +11 -0
  200. package/dist/reports/src/reports-list-route.d.ts +9 -0
  201. package/dist/reports/src/reports-list-route.js +9 -0
  202. package/dist/reports/src/reports-route.d.ts +561 -22
  203. package/dist/reports/src/reports-route.js +11 -3
  204. package/dist/reports/src/reports-store.d.ts +19 -2
  205. package/dist/reports/src/reports-store.js +10 -2
  206. package/dist/reports/src/resilient-cache.d.ts +14 -1
  207. package/dist/reports/src/resilient-cache.js +8 -1
  208. package/dist/reports/src/retention.d.ts +17 -1
  209. package/dist/reports/src/retention.js +5 -1
  210. package/dist/reports/src/run-bundle.d.ts +63 -0
  211. package/dist/reports/src/run-bundle.js +159 -0
  212. package/dist/reports/src/s3-store.d.ts +79 -0
  213. package/dist/reports/src/s3-store.js +295 -0
  214. package/dist/reports/src/scenario.d.ts +14 -4
  215. package/dist/reports/src/scenario.js +5 -4
  216. package/dist/reports/src/sigv4.d.ts +72 -0
  217. package/dist/reports/src/sigv4.js +96 -0
  218. package/dist/reports/src/snapshot-bundle.d.ts +24 -1
  219. package/dist/reports/src/snapshot-bundle.js +27 -2
  220. package/dist/reports/src/snapshot-surface.d.ts +20 -9
  221. package/dist/reports/src/snapshot-surface.js +32 -19
  222. package/dist/reports/src/source-content.d.ts +23 -0
  223. package/dist/reports/src/source-content.js +30 -1
  224. package/dist/reports/src/source-entries.d.ts +13 -0
  225. package/dist/reports/src/source-entries.js +6 -0
  226. package/dist/reports/src/source-part.d.ts +15 -0
  227. package/dist/reports/src/source-part.js +8 -0
  228. package/dist/reports/src/source-reader.d.ts +11 -0
  229. package/dist/reports/src/source-reader.js +5 -0
  230. package/dist/reports/src/spec-evidence.d.ts +12 -2
  231. package/dist/reports/src/spec-evidence.js +6 -2
  232. package/dist/reports/src/spec-inventory.d.ts +15 -1
  233. package/dist/reports/src/spec-inventory.js +22 -1
  234. package/dist/reports/src/spec-link.d.ts +28 -0
  235. package/dist/reports/src/spec-link.js +70 -0
  236. package/dist/reports/src/spec-set.d.ts +63 -0
  237. package/dist/reports/src/spec-set.js +177 -0
  238. package/dist/reports/src/status.d.ts +10 -0
  239. package/dist/reports/src/status.js +5 -0
  240. package/dist/reports/src/store-prefix.d.ts +28 -0
  241. package/dist/reports/src/store-prefix.js +22 -0
  242. package/dist/reports/src/store-url.d.ts +89 -0
  243. package/dist/reports/src/store-url.js +160 -0
  244. package/dist/reports/src/test-catalog-entity.d.ts +11 -0
  245. package/dist/reports/src/test-catalog-entity.js +6 -0
  246. package/dist/reports/src/types.d.ts +6 -1
  247. package/dist/reports/src/use-report-nav.d.ts +35 -0
  248. package/dist/reports/src/use-report-nav.js +12 -0
  249. package/dist/schema/src/api.d.ts +799 -0
  250. package/dist/schema/src/api.js +201 -0
  251. package/dist/schema/src/cli-results.d.ts +210 -0
  252. package/dist/schema/src/cli-results.js +124 -0
  253. package/dist/schema/src/common.d.ts +53 -0
  254. package/dist/schema/src/common.js +42 -0
  255. package/dist/schema/src/index.d.ts +14 -0
  256. package/dist/schema/src/index.js +14 -0
  257. package/dist/schema/src/layer.d.ts +2273 -0
  258. package/dist/schema/src/layer.js +462 -0
  259. package/dist/schema/src/manifest.d.ts +95 -0
  260. package/dist/schema/src/manifest.js +84 -0
  261. package/dist/schema/src/report-sources.d.ts +28 -0
  262. package/dist/schema/src/report-sources.js +25 -0
  263. package/dist/schema/src/runs-index.d.ts +16 -0
  264. package/dist/schema/src/runs-index.js +15 -0
  265. package/dist/schema/src/server-config.d.ts +93 -0
  266. package/dist/schema/src/server-config.js +73 -0
  267. package/dist/schema/src/spec.d.ts +69 -0
  268. package/dist/schema/src/spec.js +58 -0
  269. package/dist/server/src/app.d.ts +62 -0
  270. package/dist/server/src/app.js +419 -0
  271. package/dist/server/src/auth.d.ts +41 -0
  272. package/dist/server/src/auth.js +59 -0
  273. package/dist/server/src/config.d.ts +83 -0
  274. package/dist/server/src/config.js +187 -0
  275. package/dist/server/src/dev.d.ts +12 -0
  276. package/dist/server/src/dev.js +80 -0
  277. package/dist/server/src/events.d.ts +29 -0
  278. package/dist/server/src/events.js +101 -0
  279. package/dist/server/src/host.d.ts +30 -0
  280. package/dist/server/src/host.js +115 -0
  281. package/dist/server/src/index.d.ts +16 -0
  282. package/dist/server/src/index.js +16 -0
  283. package/dist/server/src/llms.d.ts +19 -0
  284. package/dist/server/src/llms.js +63 -0
  285. package/dist/server/src/main.d.ts +30 -0
  286. package/dist/server/src/main.js +112 -0
  287. package/dist/server/src/openapi.d.ts +59 -0
  288. package/dist/server/src/openapi.js +374 -0
  289. package/dist/server/src/problem.d.ts +8 -0
  290. package/dist/server/src/problem.js +39 -0
  291. package/dist/server/src/site.d.ts +13 -0
  292. package/dist/server/src/site.js +43 -0
  293. package/dist/server/src/store-api.d.ts +28 -0
  294. package/dist/server/src/store-api.js +159 -0
  295. package/dist/server/src/validate.d.ts +71 -0
  296. package/dist/server/src/validate.js +197 -0
  297. package/dist/vitest/src/index.d.ts +27 -5
  298. package/dist/vitest/src/index.js +18 -5
  299. package/dist/webdriver/src/driver.d.ts +63 -11
  300. package/dist/webdriver/src/driver.js +57 -6
  301. package/dist/webdriver/src/index.d.ts +8 -0
  302. package/dist/webdriver/src/index.js +1 -0
  303. package/dist/webdriver/src/protocol.d.ts +75 -6
  304. package/dist/webdriver/src/protocol.js +79 -6
  305. package/docs/agents.md +137 -0
  306. package/docs/deploying.md +345 -0
  307. package/docs/dogfooding.md +175 -0
  308. package/docs/extending.md +242 -0
  309. package/docs/integrating.md +485 -0
  310. package/docs/reference/bun.md +141 -0
  311. package/docs/reference/cli.md +146 -0
  312. package/docs/reference/core.md +2426 -0
  313. package/docs/reference/detox.md +150 -0
  314. package/docs/reference/http-api.md +303 -0
  315. package/docs/reference/playwright.md +303 -0
  316. package/docs/reference/reports.md +7098 -0
  317. package/docs/reference/schema.md +1651 -0
  318. package/docs/reference/server.md +685 -0
  319. package/docs/reference/vitest.md +45 -0
  320. package/docs/reference/webdriver.md +330 -0
  321. package/docs/report-backends.md +144 -0
  322. package/docs/report-contract.md +400 -0
  323. package/docs/reporting-issues.md +160 -0
  324. package/docs/server.md +285 -0
  325. package/docs/statecharts.md +641 -0
  326. package/llms-full.txt +3506 -0
  327. package/llms.txt +47 -0
  328. package/package.json +32 -4
  329. package/packages/core/README.md +2 -1
  330. package/packages/reports/README.md +65 -0
  331. package/packages/reports/STORAGE.md +4 -0
  332. package/packages/schema/README.md +14 -0
  333. package/packages/schema/schemas/check-detail.schema.json +1032 -0
  334. package/packages/schema/schemas/checks-result.schema.json +26 -0
  335. package/packages/schema/schemas/command-error.schema.json +35 -0
  336. package/packages/schema/schemas/doctor-result.schema.json +60 -0
  337. package/packages/schema/schemas/effective-delta.schema.json +490 -0
  338. package/packages/schema/schemas/effective-evidence.schema.json +471 -0
  339. package/packages/schema/schemas/evidence-page.schema.json +271 -0
  340. package/packages/schema/schemas/hello-event.schema.json +22 -0
  341. package/packages/schema/schemas/init-plan.schema.json +347 -0
  342. package/packages/schema/schemas/layer-rows.schema.json +199 -0
  343. package/packages/schema/schemas/layer.schema.json +2888 -0
  344. package/packages/schema/schemas/merge-result.schema.json +40 -0
  345. package/packages/schema/schemas/outline.schema.json +62 -0
  346. package/packages/schema/schemas/problem.schema.json +37 -0
  347. package/packages/schema/schemas/publish-checks.schema.json +1413 -0
  348. package/packages/schema/schemas/publish-result.schema.json +168 -0
  349. package/packages/schema/schemas/ready.schema.json +68 -0
  350. package/packages/schema/schemas/report-event.schema.json +100 -0
  351. package/packages/schema/schemas/report-status.schema.json +370 -0
  352. package/packages/schema/schemas/server-config.schema.json +194 -0
  353. package/packages/schema/schemas/server-info.schema.json +114 -0
  354. package/packages/schema/schemas/spec-set.schema.json +103 -0
  355. package/packages/schema/schemas/store-listing.schema.json +29 -0
  356. package/packages/schema/schemas/store-write-result.schema.json +28 -0
  357. package/packages/schema/schemas/stream-result.schema.json +59 -0
  358. package/packages/server/README.md +43 -0
  359. package/packages/server/openapi.json +6699 -0
  360. package/packages/visualizer/README.md +21 -0
  361. package/packages/visualizer/src/app/AppShell.tsx +9 -2
  362. package/packages/visualizer/src/app/report-sources.tsx +6 -4
  363. package/packages/visualizer/src/app/router.tsx +3 -3
  364. package/packages/visualizer/src/components/Header.module.css +35 -0
  365. package/packages/visualizer/src/components/Header.tsx +40 -3
  366. package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
  367. package/packages/visualizer/src/data/adapters/index.ts +10 -1
  368. package/packages/visualizer/src/evidence/api.ts +14 -2
  369. package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
  370. package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
  371. package/packages/visualizer/src/evidence/check-detail.tsx +3 -1
  372. package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
  373. package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
  374. package/packages/visualizer/src/find/Finder.tsx +1 -7
  375. package/packages/visualizer/src/find/open.ts +4 -0
  376. package/packages/visualizer/src/live/live-stream.ts +204 -0
  377. package/packages/visualizer/src/runtime.ts +19 -0
  378. package/packages/visualizer/vite/atlas-server-proxy.ts +36 -0
  379. package/packages/visualizer/vite.config.ts +11 -3
@@ -0,0 +1,204 @@
1
+ import { AtlasChangeSchema, type AtlasChange } from '@crvouga/atlas/schema';
2
+ import { useSyncExternalStore } from 'react';
3
+
4
+ import { runtimeConfig, runtimeUrl } from '../runtime';
5
+
6
+ /** What the server tells a viewer as it happens. */
7
+ export type LiveEvent =
8
+ | { readonly type: 'open' }
9
+ | { readonly type: 'report'; readonly report: ReportAnnouncement }
10
+ | { readonly type: 'change'; readonly change: AtlasChange };
11
+
12
+ /** One announcement that results arrived: enough to say who sent them without asking again. */
13
+ export type ReportAnnouncement = {
14
+ readonly version: string;
15
+ readonly totalLayers: number;
16
+ readonly addedTotal: number;
17
+ readonly added: readonly {
18
+ readonly id: string;
19
+ readonly producer: string;
20
+ readonly environment?: string | null;
21
+ readonly branch?: string | null;
22
+ readonly counts?: Readonly<Record<string, number>>;
23
+ }[];
24
+ };
25
+
26
+ /** `connecting` before the first answer, `live` while the stream is open, `reconnecting` after it was lost. */
27
+ export type LiveStatus = 'off' | 'connecting' | 'live' | 'reconnecting';
28
+
29
+ type Listener = (event: LiveEvent) => void;
30
+
31
+ /** The server sends a comment every 15 seconds; this long without a word means the connection is dead. */
32
+ const SILENCE_MS = 45_000;
33
+ const BACKOFF_MS = [1_000, 2_000, 5_000, 10_000, 30_000] as const;
34
+
35
+ type Stream = {
36
+ readonly url: string;
37
+ readonly listeners: Set<Listener>;
38
+ source: EventSource | null;
39
+ status: LiveStatus;
40
+ attempts: number;
41
+ timer: ReturnType<typeof setTimeout> | null;
42
+ watchdog: ReturnType<typeof setTimeout> | null;
43
+ };
44
+
45
+ const streams = new Map<string, Stream>();
46
+ const statusListeners = new Set<() => void>();
47
+
48
+ function setStatus(stream: Stream, status: LiveStatus) {
49
+ if (stream.status === status) return;
50
+ stream.status = status;
51
+ for (const listener of statusListeners) listener();
52
+ }
53
+
54
+ function emit(stream: Stream, event: LiveEvent) {
55
+ for (const listener of [...stream.listeners]) {
56
+ try {
57
+ listener(event);
58
+ } catch {
59
+ // One listener's mistake must not stop the others hearing.
60
+ }
61
+ }
62
+ }
63
+
64
+ function parse(data: string): unknown {
65
+ try {
66
+ return JSON.parse(data) as unknown;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ function connect(stream: Stream) {
73
+ if (typeof EventSource === 'undefined') return;
74
+ stream.timer = null;
75
+ const source = new EventSource(stream.url);
76
+ stream.source = source;
77
+ const heard = () => {
78
+ if (stream.watchdog) clearTimeout(stream.watchdog);
79
+ stream.watchdog = setTimeout(() => lose(stream), SILENCE_MS);
80
+ };
81
+ source.addEventListener('open', () => {
82
+ heard();
83
+ stream.attempts = 0;
84
+ setStatus(stream, 'live');
85
+ // Anything may have happened while the stream was away, so a (re)connection reads everything again.
86
+ emit(stream, { type: 'open' });
87
+ });
88
+ source.addEventListener('hello', heard);
89
+ source.addEventListener('ping', heard);
90
+ source.addEventListener('report', (event) => {
91
+ heard();
92
+ const report = parse((event as MessageEvent<string>).data) as ReportAnnouncement | null;
93
+ if (report && typeof report.version === 'string') emit(stream, { type: 'report', report: { ...report, added: Array.isArray(report.added) ? report.added : [] } });
94
+ });
95
+ source.addEventListener('atlas:change', (event) => {
96
+ heard();
97
+ const change = AtlasChangeSchema.safeParse(parse((event as MessageEvent<string>).data));
98
+ if (change.success) emit(stream, { type: 'change', change: change.data });
99
+ });
100
+ // The server's heartbeat is a named `ping` event, so a quiet server is told apart from a dead connection.
101
+ source.addEventListener('error', () => {
102
+ setStatus(stream, stream.attempts === 0 && stream.status === 'connecting' ? 'connecting' : 'reconnecting');
103
+ // The browser retries a dropped stream by itself. A refused one (401, 503) it gives up on: retry that here, slowly.
104
+ if (source.readyState === EventSource.CLOSED) lose(stream);
105
+ });
106
+ heard();
107
+ }
108
+
109
+ function lose(stream: Stream) {
110
+ if (stream.watchdog) clearTimeout(stream.watchdog);
111
+ stream.watchdog = null;
112
+ stream.source?.close();
113
+ stream.source = null;
114
+ if (!stream.listeners.size) return;
115
+ setStatus(stream, 'reconnecting');
116
+ if (stream.timer) return;
117
+ const wait = BACKOFF_MS[Math.min(stream.attempts, BACKOFF_MS.length - 1)]!;
118
+ stream.attempts += 1;
119
+ stream.timer = setTimeout(() => connect(stream), wait);
120
+ }
121
+
122
+ function release(stream: Stream) {
123
+ if (stream.listeners.size) return;
124
+ if (stream.timer) clearTimeout(stream.timer);
125
+ if (stream.watchdog) clearTimeout(stream.watchdog);
126
+ stream.source?.close();
127
+ streams.delete(stream.url);
128
+ setStatus(stream, 'off');
129
+ }
130
+
131
+ /**
132
+ * Hear what the server at `url` announces. Everything in the page that listens to one address shares one
133
+ * connection, which is opened with the first listener and closed with the last.
134
+ */
135
+ export function listenLive(url: string, listener: Listener): () => void {
136
+ let stream = streams.get(url);
137
+ if (!stream) {
138
+ stream = { url, listeners: new Set(), source: null, status: 'connecting', attempts: 0, timer: null, watchdog: null };
139
+ streams.set(url, stream);
140
+ stream.listeners.add(listener);
141
+ connect(stream);
142
+ for (const notify of statusListeners) notify();
143
+ } else {
144
+ stream.listeners.add(listener);
145
+ }
146
+ const mine = stream;
147
+ return () => {
148
+ mine.listeners.delete(listener);
149
+ release(mine);
150
+ };
151
+ }
152
+
153
+ /** Changes to specs and runs, in the form the data layer already understands. */
154
+ export function listenChanges(url: string, onChange: (change: AtlasChange) => void): () => void {
155
+ return listenLive(url, (event) => {
156
+ if (event.type === 'change') onChange(event.change);
157
+ // A fresh connection may have missed changes while it was away.
158
+ if (event.type === 'open') onChange({ scope: 'runs', files: [], runIds: [] });
159
+ });
160
+ }
161
+
162
+ /** The address of this server's event stream, when the page has one. */
163
+ export const eventsAddress = () => runtimeUrl(runtimeConfig()?.eventsUrl);
164
+
165
+ function snapshot(): LiveStatus {
166
+ const url = eventsAddress();
167
+ return (url && streams.get(url)?.status) || (url ? 'connecting' : 'off');
168
+ }
169
+
170
+ function subscribeStatus(listener: () => void) {
171
+ statusListeners.add(listener);
172
+ return () => statusListeners.delete(listener);
173
+ }
174
+
175
+ /** Whether the report on screen is being kept up to date by the server. `off` for a static site. */
176
+ export function useLiveStatus(): LiveStatus {
177
+ return useSyncExternalStore(subscribeStatus, snapshot, () => 'off');
178
+ }
179
+
180
+ /** Test seam: forget every stream. */
181
+ export function resetLiveStreams() {
182
+ for (const stream of streams.values()) {
183
+ stream.listeners.clear();
184
+ release(stream);
185
+ }
186
+ streams.clear();
187
+ }
188
+
189
+ /**
190
+ * `act` at once, and then no more than once per `ms` however often it is called: a call made inside the wait
191
+ * runs when the wait ends. The last word is never lost.
192
+ */
193
+ export function throttled(act: () => void, ms: number): (() => void) & { cancel: () => void } {
194
+ let last = -Infinity;
195
+ let timer: ReturnType<typeof setTimeout> | null = null;
196
+ const call = () => {
197
+ if (timer) return;
198
+ const wait = last + ms - Date.now();
199
+ if (wait <= 0) { last = Date.now(); act(); return; }
200
+ timer = setTimeout(() => { timer = null; last = Date.now(); act(); }, wait);
201
+ };
202
+ call.cancel = () => { if (timer) clearTimeout(timer); timer = null; };
203
+ return call;
204
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The address book an Atlas server writes into the page it serves (`window.__ATLAS_CONFIG__`), so one build
3
+ * of the viewer works for every project and on every host. Under `atlas dev` the Vite dev server writes the
4
+ * same thing, so the viewer cannot tell the two apart: both are one host, with `/api` behind it.
5
+ */
6
+ export type RuntimeConfig = {
7
+ readonly api?: string;
8
+ readonly reportsUrl?: string;
9
+ readonly dataUrl?: string;
10
+ readonly eventsUrl?: string;
11
+ readonly effective?: boolean;
12
+ };
13
+
14
+ export const runtimeConfig = (): RuntimeConfig | undefined => (globalThis as { __ATLAS_CONFIG__?: RuntimeConfig }).__ATLAS_CONFIG__;
15
+
16
+ /** An address from the config, resolved against the page, as an absolute URL. */
17
+ export function runtimeUrl(value: string | undefined): string | undefined {
18
+ return value ? new URL(value, window.location.href).href : undefined;
19
+ }
@@ -0,0 +1,36 @@
1
+ import type { Plugin, ProxyOptions } from 'vite';
2
+
3
+ /** What the viewer is told about the server it is served with: the same words `atlas serve` writes into its page. */
4
+ export const serverPageConfig = { api: './api', reportsUrl: './api/reports', dataUrl: './api/data/', eventsUrl: './api/events', effective: true } as const;
5
+
6
+ /**
7
+ * Put the Atlas server behind the dev server. The browser talks to one host; `/api` goes to the server, so the
8
+ * page, its data and its event stream have one origin in development exactly as they do when deployed.
9
+ */
10
+ export function atlasServerProxy(target: string): { readonly proxy: Record<string, ProxyOptions> } {
11
+ return {
12
+ proxy: {
13
+ '/api': {
14
+ target,
15
+ changeOrigin: false,
16
+ ws: false,
17
+ // The event stream stays open for hours: no timeout, and nothing may hold its bytes back.
18
+ timeout: 0,
19
+ proxyTimeout: 0,
20
+ configure: (proxy) => {
21
+ proxy.on('proxyRes', (response) => {
22
+ if (String(response.headers['content-type'] ?? '').startsWith('text/event-stream')) response.headers['cache-control'] = 'no-cache, no-transform';
23
+ });
24
+ }
25
+ }
26
+ }
27
+ };
28
+ }
29
+
30
+ /** Write the server's address book into the page the dev server serves. */
31
+ export function atlasServerPage(): Plugin {
32
+ return {
33
+ name: 'atlas-server-page',
34
+ transformIndexHtml: () => [{ tag: 'script', children: `window.__ATLAS_CONFIG__=${JSON.stringify(serverPageConfig)}`, injectTo: 'head-prepend' }]
35
+ };
36
+ }
@@ -8,14 +8,22 @@ import { resolveRoots } from './vite/atlas-files';
8
8
  import { servesExampleReports } from './vite/example-reports';
9
9
  import { reportsDevPlugin } from './vite/reports-dev-plugin';
10
10
  import { atlasDevPlugin } from './vite/atlas-dev-plugin';
11
+ import { atlasServerPage, atlasServerProxy } from './vite/atlas-server-proxy';
11
12
 
12
13
  const appRoot = path.dirname(fileURLToPath(import.meta.url));
13
- const reportsModule = process.env.ATLAS_REPORTS_MODULE || (servesExampleReports(resolveRoots().specs) ? path.join(appRoot, 'vite/example-reports.ts') : '');
14
+
15
+ // `atlas dev` runs the Atlas server on a port of its own and tells this config where. The browser then talks to
16
+ // this dev server alone: `/api` is proxied to the server, and the page gets the same address book a served
17
+ // build gets, so the viewer cannot tell the two apart. Without a server, the viewer reads the working tree.
18
+ const serverUrl = process.env.ATLAS_API_URL || '';
19
+ const reportsModule = serverUrl ? '' : process.env.ATLAS_REPORTS_MODULE || (servesExampleReports(resolveRoots().specs) ? path.join(appRoot, 'vite/example-reports.ts') : '');
14
20
 
15
21
  export default defineConfig({
16
22
  base: './',
17
- plugins: [react(), atlasDevPlugin(), ...(reportsModule ? [reportsDevPlugin({ repoRoot: process.env.INIT_CWD || process.cwd(), appModule: reportsModule })] : [])],
23
+ plugins: serverUrl
24
+ ? [react(), atlasServerPage()]
25
+ : [react(), atlasDevPlugin(), ...(reportsModule ? [reportsDevPlugin({ repoRoot: process.env.INIT_CWD || process.cwd(), appModule: reportsModule })] : [])],
18
26
  worker: { format: 'es' },
19
- server: { port: Number(process.env.PORT ?? 5180), strictPort: false },
27
+ server: { port: Number(process.env.PORT ?? 5180), strictPort: false, ...(serverUrl ? atlasServerProxy(serverUrl) : {}) },
20
28
  build: { outDir: 'dist', emptyOutDir: true, chunkSizeWarningLimit: 2_500 }
21
29
  });