@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,83 @@
1
+ /**
2
+ * @module Server configuration
3
+ *
4
+ * Loads how the server runs: `atlas.server.json`, then environment variables, then flags, each overriding the last, checked
5
+ * against `ServerConfigSchema` and resolved (paths made absolute, backends named, tokens read). Every mistake is a
6
+ * `ConfigError` that says what to change, and a server that would listen beyond this machine with no tokens refuses to
7
+ * start. `SERVER_SETTINGS` documents every setting once; the table in docs/server.md is generated from it and a test proves
8
+ * each row.
9
+ *
10
+ * @see docs/server.md#configuration
11
+ */
12
+ import { type ServerConfig, type ServerRole } from '../../schema/src/server-config.js';
13
+ type Env = Readonly<Record<string, string | undefined>>;
14
+ /** A token as the server holds it: the `name` it is shown under in logs, its `role`, and the secret `token`. */
15
+ export type ResolvedToken = {
16
+ readonly name: string;
17
+ readonly role: ServerRole;
18
+ readonly token: string;
19
+ };
20
+ /** A backend as configured: its `id`, the `label` people see, and the `url` (a folder, `s3://…`, or another server) to open. */
21
+ export type BackendSpec = {
22
+ readonly id: string;
23
+ readonly label: string;
24
+ readonly url: string;
25
+ };
26
+ /** The configuration the server runs with: the file, then the environment, then the flags, checked and resolved. */
27
+ export type ResolvedServerConfig = Omit<ServerConfig, 'backends' | 'auth' | 'write'> & {
28
+ /** The folder that is the default backend, absolute. */
29
+ readonly data: string;
30
+ /** Every backend, the data folder first. All are read; uploads go to `writeId`. */
31
+ readonly backends: readonly BackendSpec[];
32
+ readonly writeId: string;
33
+ readonly tokens: readonly ResolvedToken[];
34
+ readonly anonymousRead: boolean;
35
+ readonly insecure: boolean;
36
+ /** Where the config came from, for the first line of the log. */
37
+ readonly configFile: string | null;
38
+ };
39
+ /** A configuration that cannot be used. The message says what to change; `atlas serve` prints it and exits 2. */
40
+ export declare class ConfigError extends Error {
41
+ }
42
+ /** Every flag `atlas serve` accepts, with its `--`. */
43
+ export declare const SERVER_FLAGS: readonly string[];
44
+ /** One setting of the server as an operator meets it: the config key, the flag, the environment variables, and what it does. */
45
+ export type ServerSetting = {
46
+ /** What it sets, in words. */
47
+ readonly summary: string;
48
+ /** The key in `atlas.server.json` (dotted for nested keys). */
49
+ readonly key: string;
50
+ /** The command-line flag, with its value placeholder, or null when there is none. */
51
+ readonly flag: string | null;
52
+ /** The environment variables, the first that is set wins; empty when there are none. */
53
+ readonly env: readonly string[];
54
+ /** What it is when nothing sets it. */
55
+ readonly default: string;
56
+ /**
57
+ * How the documentation is held to the code: giving `value` through the environment variable (or the flag) must make
58
+ * `read` of the resolved configuration equal `expected`. A test runs every setting's example both ways.
59
+ */
60
+ readonly example: {
61
+ readonly value: string;
62
+ readonly read: (config: ResolvedServerConfig) => unknown;
63
+ readonly expected: unknown;
64
+ };
65
+ };
66
+ /**
67
+ * Every setting that can be given by flag or environment, documented once. `docs/server.md` renders its table from
68
+ * this list, and a test proves each row: its example, through the flag and through the environment, changes the
69
+ * configuration as the row says. A setting added to the loader without a row here is therefore not documented in the
70
+ * table; one documented here that the loader ignores fails the test.
71
+ */
72
+ export declare const SERVER_SETTINGS: readonly ServerSetting[];
73
+ /** The text `atlas serve --help` prints. Also the source of the `atlas serve` section of `docs/reference/cli.md`. */
74
+ export declare const SERVER_USAGE = "atlas serve: the Atlas server\n\n atlas serve [--config atlas.server.json] [--data <dir>] [--port 8080] [--host 127.0.0.1]\n [--store <url|id=url>]... [--write <id>] [--site <dir>]\n [--outline-branch <b>] [--branch <name>]... [--ignore-modified] [--refresh <seconds>]\n [--anonymous-read] [--insecure] [--no-log]\n\n One process serves the client and the API. Test results are uploaded to it from anywhere (laptops, CI) and it\n shows everyone the combined report, live. It keeps its data in --data (default ./atlas-data) and can read more\n backends (--store): S3, other servers, folders. Uploads go to the data folder unless --write names another.\n\n Tokens (ATLAS_TOKEN admin, ATLAS_PUBLISH_TOKEN, ATLAS_READ_TOKEN, or \"auth.tokens\" in the config) are required\n to listen beyond this machine unless --insecure. Every option has an environment variable (docs/deploying.md).\n";
75
+ /**
76
+ * Read the file named by `--config`/`ATLAS_CONFIG`, or `atlas.server.json` in the folder if there is one, lay the
77
+ * environment and then the flags over it, and check the result. Every problem is a {@link ConfigError} that says
78
+ * what to change.
79
+ */
80
+ export declare function loadServerConfig(argv: readonly string[], env: Env, cwd: string, read?: (file: string) => string): ResolvedServerConfig;
81
+ /** A server that anyone on the network can upload to must say who may. */
82
+ export declare function assertSafeToListen(config: Pick<ResolvedServerConfig, 'host' | 'tokens' | 'insecure'>): void;
83
+ export {};
@@ -0,0 +1,204 @@
1
+ /**
2
+ * @module Server configuration
3
+ *
4
+ * Loads how the server runs: `atlas.server.json`, then environment variables, then flags, each overriding the last, checked
5
+ * against `ServerConfigSchema` and resolved (paths made absolute, backends named, tokens read). Every mistake is a
6
+ * `ConfigError` that says what to change, and a server that would listen beyond this machine with no tokens refuses to
7
+ * start. `SERVER_SETTINGS` documents every setting once; the table in docs/server.md is generated from it and a test proves
8
+ * each row.
9
+ *
10
+ * @see docs/server.md#configuration
11
+ */
12
+ import { existsSync, readFileSync } from 'node:fs';
13
+ import path from 'node:path';
14
+ import { parseArgs } from 'node:util';
15
+ import { ServerConfigSchema } from '../../schema/src/server-config.js';
16
+ /** A configuration that cannot be used. The message says what to change; `atlas serve` prints it and exits 2. */
17
+ export class ConfigError extends Error {
18
+ }
19
+ const DEFAULT_FILE = 'atlas.server.json';
20
+ const MIN_TOKEN = 12;
21
+ const OPTIONS = {
22
+ config: { type: 'string' }, data: { type: 'string' }, store: { type: 'string', multiple: true }, write: { type: 'string' },
23
+ host: { type: 'string' }, port: { type: 'string' }, site: { type: 'string' }, 'outline-branch': { type: 'string' },
24
+ branch: { type: 'string', multiple: true }, 'ignore-modified': { type: 'boolean' }, refresh: { type: 'string' },
25
+ insecure: { type: 'boolean' }, 'no-log': { type: 'boolean' }, 'anonymous-read': { type: 'boolean' }
26
+ };
27
+ /** Every flag `atlas serve` accepts, with its `--`. */
28
+ export const SERVER_FLAGS = Object.keys(OPTIONS).map(name => `--${name}`);
29
+ /**
30
+ * Every setting that can be given by flag or environment, documented once. `docs/server.md` renders its table from
31
+ * this list, and a test proves each row: its example, through the flag and through the environment, changes the
32
+ * configuration as the row says. A setting added to the loader without a row here is therefore not documented in the
33
+ * table; one documented here that the loader ignores fails the test.
34
+ */
35
+ export const SERVER_SETTINGS = [
36
+ { summary: 'Address to listen on', key: 'host', flag: '--host <address>', env: ['ATLAS_HOST', 'HOST'], default: '`127.0.0.1`', example: { value: '0.0.0.0', read: c => c.host, expected: '0.0.0.0' } },
37
+ { summary: 'Port (`0` picks a free one)', key: 'port', flag: '--port <n>', env: ['ATLAS_PORT', 'PORT'], default: '`8080`', example: { value: '9191', read: c => c.port, expected: 9191 } },
38
+ { summary: 'The server\'s own folder: the default backend and write target', key: 'data', flag: '--data <dir>', env: ['ATLAS_DATA'], default: '`./atlas-data`', example: { value: 'results', read: c => path.basename(c.data), expected: 'results' } },
39
+ { summary: 'More backends, read together with the data folder', key: 'backends', flag: '--store <[id=]url>` (repeatable)', env: ['ATLAS_STORE'], default: 'none', example: { value: 'lab=https://lab.example.com', read: c => c.backends.map(backend => backend.id), expected: ['data', 'lab'] } },
40
+ { summary: 'The backend uploads go to', key: 'write', flag: '--write <id>', env: ['ATLAS_WRITE'], default: '`data`', example: { value: 'data', read: c => c.writeId, expected: 'data' } },
41
+ { summary: 'The built client', key: 'site', flag: '--site <dir>', env: ['ATLAS_SITE'], default: '`./atlas-site`, else built once', example: { value: 'client', read: c => path.basename(c.site ?? ''), expected: 'client' } },
42
+ { summary: 'Whose specs the report is laid over', key: 'outlineBranch', flag: '--outline-branch <branch>', env: ['ATLAS_OUTLINE_BRANCH'], default: '`main`, then `master`, then the newest', example: { value: 'trunk', read: c => c.outlineBranch, expected: 'trunk' } },
43
+ { summary: 'Only layers recorded on these branches take part', key: 'include.branches', flag: '--branch <name>` (repeatable)', env: ['ATLAS_ONLY_BRANCH'], default: 'all', example: { value: 'main', read: c => c.include.branches, expected: ['main'] } },
44
+ { summary: 'Leave out layers from modified checkouts and unknown origins', key: 'include.ignoreModified', flag: '--ignore-modified', env: ['ATLAS_IGNORE_MODIFIED'], default: 'no', example: { value: '1', read: c => c.include.ignoreModified, expected: true } },
45
+ { summary: 'How often (seconds) the backends are looked at for layers written by something else', key: 'refreshSeconds', flag: '--refresh <seconds>', env: ['ATLAS_REFRESH_SECONDS'], default: '`10`', example: { value: '30', read: c => c.refreshSeconds, expected: 30 } },
46
+ { summary: 'Let anyone look, not upload', key: 'auth.anonymousRead', flag: '--anonymous-read', env: ['ATLAS_ANONYMOUS_READ'], default: 'no', example: { value: '1', read: c => c.anonymousRead, expected: true } },
47
+ { summary: 'Listen publicly with no tokens (something in front signs people in)', key: 'auth.insecure', flag: '--insecure', env: ['ATLAS_INSECURE'], default: 'no', example: { value: '1', read: c => c.insecure, expected: true } },
48
+ { summary: 'The admin token (also `ATLAS_PUBLISH_TOKEN` and `ATLAS_READ_TOKEN` for the other roles, or `auth.tokens` in the file)', key: 'auth.tokens', flag: null, env: ['ATLAS_TOKEN'], default: 'none: the server is open and listens on this machine only', example: { value: 'admin-token-123456', read: c => c.tokens.map(token => token.role), expected: ['admin'] } },
49
+ { summary: 'The configuration file', key: '(the file itself)', flag: '--config <file>', env: ['ATLAS_CONFIG'], default: '`./atlas.server.json` when it exists', example: { value: 'custom.json', read: c => path.basename(c.configFile ?? ''), expected: 'custom.json' } },
50
+ { summary: 'Largest uploaded object in MiB', key: 'limits.maxObjectMiB', flag: null, env: ['ATLAS_MAX_OBJECT_MIB'], default: '`256`', example: { value: '64', read: c => c.limits.maxObjectMiB, expected: 64 } },
51
+ { summary: 'Largest JSON upload in MiB', key: 'limits.maxJsonMiB', flag: null, env: ['ATLAS_MAX_JSON_MIB'], default: '`32`', example: { value: '8', read: c => c.limits.maxJsonMiB, expected: 8 } },
52
+ { summary: 'Maximum simultaneous live viewers', key: 'limits.maxStreams', flag: null, env: ['ATLAS_MAX_STREAMS'], default: '`500`', example: { value: '100', read: c => c.limits.maxStreams, expected: 100 } },
53
+ { summary: 'Live connection heartbeat in seconds', key: 'heartbeatSeconds', flag: null, env: ['ATLAS_HEARTBEAT_SECONDS'], default: '`15`', example: { value: '20', read: c => c.heartbeatSeconds, expected: 20 } },
54
+ { summary: 'Live producer heartbeat timeout in seconds', key: 'runTimeoutSeconds', flag: null, env: ['ATLAS_RUN_TIMEOUT_SECONDS'], default: '`30`', example: { value: '60', read: c => c.runTimeoutSeconds, expected: 60 } },
55
+ { summary: 'Live run progress stall timeout in seconds (0 disables)', key: 'runStallSeconds', flag: null, env: ['ATLAS_RUN_STALL_SECONDS'], default: '`900`', example: { value: '0', read: c => c.runStallSeconds, expected: 0 } },
56
+ { summary: 'No request log', key: 'log', flag: '--no-log', env: ['ATLAS_LOG'], default: 'one JSON line per request', example: { value: 'false', read: c => c.log, expected: false } }
57
+ ];
58
+ /** The text `atlas serve --help` prints. Also the source of the `atlas serve` section of `docs/reference/cli.md`. */
59
+ export const SERVER_USAGE = `atlas serve: the Atlas server
60
+
61
+ atlas serve [--config atlas.server.json] [--data <dir>] [--port 8080] [--host 127.0.0.1]
62
+ [--store <url|id=url>]... [--write <id>] [--site <dir>]
63
+ [--outline-branch <b>] [--branch <name>]... [--ignore-modified] [--refresh <seconds>]
64
+ [--anonymous-read] [--insecure] [--no-log]
65
+
66
+ One process serves the client and the API. Test results are uploaded to it from anywhere (laptops, CI) and it
67
+ shows everyone the combined report, live. It keeps its data in --data (default ./atlas-data) and can read more
68
+ backends (--store): S3, other servers, folders. Uploads go to the data folder unless --write names another.
69
+
70
+ Tokens (ATLAS_TOKEN admin, ATLAS_PUBLISH_TOKEN, ATLAS_READ_TOKEN, or "auth.tokens" in the config) are required
71
+ to listen beyond this machine unless --insecure. Every option has an environment variable (docs/deploying.md).
72
+ `;
73
+ function parseFlags(argv) {
74
+ try {
75
+ return parseArgs({ args: [...argv], options: OPTIONS, allowPositionals: false, strict: true }).values;
76
+ }
77
+ catch (error) {
78
+ throw new ConfigError(error instanceof Error ? error.message : String(error));
79
+ }
80
+ }
81
+ const list = (text) => (text ?? '').split(/[\n,]+/).map(item => item.trim()).filter(Boolean);
82
+ const truthy = (value) => value === '1' || value === 'true';
83
+ /** `id=url`, or a bare url with an id made from it. */
84
+ function backendOf(spec, taken) {
85
+ const named = /^([a-z0-9-]+)=(.+)$/.exec(spec);
86
+ if (named)
87
+ return { id: named[1], url: named[2] };
88
+ const base = spec.replace(/^[a-z0-9+.-]+:\/\//i, '').replace(/[?#].*$/, '').replace(/[^a-z0-9]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase().slice(0, 40) || 'store';
89
+ let id = base;
90
+ for (let n = 2; taken.has(id); n++)
91
+ id = `${base}-${n}`;
92
+ return { id, url: spec };
93
+ }
94
+ /**
95
+ * Read the file named by `--config`/`ATLAS_CONFIG`, or `atlas.server.json` in the folder if there is one, lay the
96
+ * environment and then the flags over it, and check the result. Every problem is a {@link ConfigError} that says
97
+ * what to change.
98
+ */
99
+ export function loadServerConfig(argv, env, cwd, read = file => readFileSync(file, 'utf8')) {
100
+ const flags = parseFlags(argv);
101
+ const named = flags.config ?? env.ATLAS_CONFIG;
102
+ const file = named ? path.resolve(cwd, named) : existsSync(path.join(cwd, DEFAULT_FILE)) ? path.join(cwd, DEFAULT_FILE) : null;
103
+ let raw = {};
104
+ if (file) {
105
+ try {
106
+ raw = JSON.parse(read(file));
107
+ }
108
+ catch (error) {
109
+ throw new ConfigError(`Cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`);
110
+ }
111
+ }
112
+ const base = file ? path.dirname(file) : cwd;
113
+ const auth = { ...raw.auth };
114
+ const include = { ...raw.include };
115
+ const merged = { ...raw, auth, include };
116
+ const set = (key, value) => { if (value !== undefined)
117
+ merged[key] = value; };
118
+ // ATLAS_HOST and ATLAS_PORT first: HOST is often the machine's own name, and PORT belongs to whatever else is running here.
119
+ set('host', flags.host ?? env.ATLAS_HOST ?? env.HOST);
120
+ const port = env.ATLAS_PORT ?? env.PORT;
121
+ set('port', flags.port !== undefined ? Number(flags.port) : port !== undefined ? Number(port) : undefined);
122
+ set('data', flags.data ?? env.ATLAS_DATA);
123
+ set('write', flags.write ?? env.ATLAS_WRITE);
124
+ set('site', flags.site ?? env.ATLAS_SITE);
125
+ set('outlineBranch', flags['outline-branch'] ?? env.ATLAS_OUTLINE_BRANCH);
126
+ set('refreshSeconds', flags.refresh !== undefined ? Number(flags.refresh) : env.ATLAS_REFRESH_SECONDS !== undefined ? Number(env.ATLAS_REFRESH_SECONDS) : undefined);
127
+ if (env.ATLAS_LOG !== undefined)
128
+ merged.log = truthy(env.ATLAS_LOG);
129
+ if (flags['no-log'])
130
+ merged.log = false;
131
+ set('runTimeoutSeconds', env.ATLAS_RUN_TIMEOUT_SECONDS === undefined ? undefined : Number(env.ATLAS_RUN_TIMEOUT_SECONDS));
132
+ set('runStallSeconds', env.ATLAS_RUN_STALL_SECONDS === undefined ? undefined : Number(env.ATLAS_RUN_STALL_SECONDS));
133
+ set('heartbeatSeconds', env.ATLAS_HEARTBEAT_SECONDS === undefined ? undefined : Number(env.ATLAS_HEARTBEAT_SECONDS));
134
+ const limits = { ...raw.limits };
135
+ for (const [key, variable] of [['maxObjectMiB', 'ATLAS_MAX_OBJECT_MIB'], ['maxJsonMiB', 'ATLAS_MAX_JSON_MIB'], ['maxStreams', 'ATLAS_MAX_STREAMS']]) {
136
+ if (env[variable] !== undefined)
137
+ limits[key] = Number(env[variable]);
138
+ }
139
+ merged.limits = limits;
140
+ if (flags.insecure || truthy(env.ATLAS_INSECURE))
141
+ auth.insecure = true;
142
+ if (flags['anonymous-read'] || truthy(env.ATLAS_ANONYMOUS_READ))
143
+ auth.anonymousRead = true;
144
+ const branches = [...(flags.branch ?? []), ...list(env.ATLAS_ONLY_BRANCH)];
145
+ if (branches.length)
146
+ include.branches = branches;
147
+ if (flags['ignore-modified'] || truthy(env.ATLAS_IGNORE_MODIFIED))
148
+ include.ignoreModified = true;
149
+ const parsed = ServerConfigSchema.safeParse(merged);
150
+ if (!parsed.success)
151
+ throw new ConfigError(`${file ? `${file}: ` : 'Configuration: '}${parsed.error.issues.map(issue => `${issue.path.join('.') || '(config)'}: ${issue.message}`).join('; ')}`);
152
+ const config = parsed.data;
153
+ // Backends: the data folder, then those in the file, then --store and ATLAS_STORE.
154
+ const taken = new Set(['data']);
155
+ const backends = [{ id: 'data', label: 'Server data', url: path.resolve(base, config.data) }];
156
+ const extra = config.backends.map(item => ({ id: item.id, label: item.label ?? item.id, url: item.url, write: item.write === true }));
157
+ for (const item of extra) {
158
+ if (taken.has(item.id))
159
+ throw new ConfigError(`Two backends are called "${item.id}" ("data" is the data folder)`);
160
+ taken.add(item.id);
161
+ backends.push({ id: item.id, label: item.label, url: item.url });
162
+ }
163
+ for (const spec of [...(flags.store ?? []), ...(flags.store ? [] : list(env.ATLAS_STORE))]) {
164
+ const item = backendOf(spec, taken);
165
+ if (taken.has(item.id))
166
+ throw new ConfigError(`Two backends are called "${item.id}"`);
167
+ taken.add(item.id);
168
+ backends.push({ id: item.id, label: item.id, url: item.url });
169
+ }
170
+ const marked = extra.filter(item => item.write).map(item => item.id);
171
+ if (marked.length > 1)
172
+ throw new ConfigError(`Only one backend can take uploads; ${marked.join(', ')} are all marked write`);
173
+ const writeId = config.write ?? marked[0] ?? 'data';
174
+ if (!backends.some(item => item.id === writeId))
175
+ throw new ConfigError(`write names "${writeId}", which is not a backend (${backends.map(item => item.id).join(', ')})`);
176
+ // Tokens: the file's, then the environment's.
177
+ const tokens = [];
178
+ for (const item of config.auth.tokens) {
179
+ const token = item.token ?? env[item.tokenEnv];
180
+ if (!token)
181
+ throw new ConfigError(`Token "${item.name}" reads ${item.tokenEnv} and it is not set`);
182
+ tokens.push({ name: item.name, role: item.role, token });
183
+ }
184
+ for (const [variable, role, name] of [['ATLAS_TOKEN', 'admin', 'admin'], ['ATLAS_PUBLISH_TOKEN', 'publish', 'publish'], ['ATLAS_READ_TOKEN', 'read', 'read']]) {
185
+ if (env[variable])
186
+ tokens.push({ name, role, token: env[variable].trim() });
187
+ }
188
+ for (const item of tokens)
189
+ if (item.token.length < MIN_TOKEN)
190
+ throw new ConfigError(`Token "${item.name}" is shorter than ${MIN_TOKEN} characters: use a long random one (openssl rand -hex 24)`);
191
+ if (new Set(tokens.map(item => item.token)).size !== tokens.length)
192
+ throw new ConfigError('Two tokens are the same: each token must say who it is');
193
+ const { backends: _backends, auth: _auth, write: _write, ...rest } = config;
194
+ const resolved = { ...rest, data: backends[0].url, backends, writeId, tokens, anonymousRead: config.auth.anonymousRead, insecure: config.auth.insecure, configFile: file, ...(config.site ? { site: path.resolve(base, config.site) } : {}) };
195
+ assertSafeToListen(resolved);
196
+ return resolved;
197
+ }
198
+ const LOOPBACK = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);
199
+ /** A server that anyone on the network can upload to must say who may. */
200
+ export function assertSafeToListen(config) {
201
+ if (config.tokens.length || config.insecure || LOOPBACK.has(config.host))
202
+ return;
203
+ throw new ConfigError(`Listening on ${config.host} with no tokens would let anyone on the network upload results. Set ATLAS_TOKEN (and ATLAS_PUBLISH_TOKEN / ATLAS_READ_TOKEN), or listen on 127.0.0.1, or pass --insecure if something else in front of it does the signing in.`);
204
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @module `atlas dev`
3
+ *
4
+ * Runs the same server as `atlas serve` on a port of its own, with the client's Vite dev server in front of it. Vite proxies
5
+ * `/api` (server-sent events included) to the server and writes the same address book into the page, so the browser, the
6
+ * uploaders and the client all use one host, exactly as in production, and a change to the client shows at once.
7
+ */
8
+ import { type ServeIo } from './main.js';
9
+ /** The text `atlas dev --help` prints. Also the source of the `atlas dev` section of `docs/reference/cli.md`. */
10
+ export declare const DEV_USAGE = "atlas dev: the Atlas server and the client, for working on either\n\n atlas dev [--port 5180] [--host 127.0.0.1] [--data <dir>] [--store <url|id=url>]... [--config atlas.server.json]\n\n Starts the server (API only, on a port of its own) and the client's dev server, which proxies /api to it, so the\n browser talks to one host and a change to the client shows at once. In production `atlas serve` does both from\n one process.\n";
11
+ /** `atlas dev`. */
12
+ export declare function devMain(argv: readonly string[], io: ServeIo): Promise<number>;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @module `atlas dev`
3
+ *
4
+ * Runs the same server as `atlas serve` on a port of its own, with the client's Vite dev server in front of it. Vite proxies
5
+ * `/api` (server-sent events included) to the server and writes the same address book into the page, so the browser, the
6
+ * uploaders and the client all use one host, exactly as in production, and a change to the client shows at once.
7
+ */
8
+ import path from 'node:path';
9
+ import { openConfiguredBackends, serverOptionsOf } from './main.js';
10
+ import { createAtlasServer } from './app.js';
11
+ import { ConfigError, loadServerConfig, SERVER_USAGE } from './config.js';
12
+ import { listen } from './host.js';
13
+ import { fileServerSettings } from './settings.js';
14
+ import { visualizerRoot } from './site.js';
15
+ /** The text `atlas dev --help` prints. Also the source of the `atlas dev` section of `docs/reference/cli.md`. */
16
+ export const DEV_USAGE = `atlas dev: the Atlas server and the client, for working on either
17
+
18
+ atlas dev [--port 5180] [--host 127.0.0.1] [--data <dir>] [--store <url|id=url>]... [--config atlas.server.json]
19
+
20
+ Starts the server (API only, on a port of its own) and the client's dev server, which proxies /api to it, so the
21
+ browser talks to one host and a change to the client shows at once. In production \`atlas serve\` does both from
22
+ one process.
23
+ `;
24
+ /** `atlas dev`. */
25
+ export async function devMain(argv, io) {
26
+ const cwd = io.cwd ?? process.cwd();
27
+ const env = io.env ?? process.env;
28
+ if (argv.includes('--help') || argv.includes('-h')) {
29
+ io.out(DEV_USAGE);
30
+ return 0;
31
+ }
32
+ const root = visualizerRoot();
33
+ if (!root) {
34
+ io.out('atlas dev: the client is not installed alongside the server.\n');
35
+ return 2;
36
+ }
37
+ // --port is the one the browser uses (the client's); the API takes another that nobody needs to know.
38
+ const flagged = argv.findIndex(item => item === '--port');
39
+ const publicPort = flagged >= 0 ? Number(argv[flagged + 1]) : Number(env.ATLAS_PORT ?? env.PORT ?? 5180);
40
+ const forServer = argv.filter((_, i) => i !== flagged && i !== flagged + 1 || flagged < 0);
41
+ let config;
42
+ try {
43
+ // The API itself listens on a port of its own on this machine; what the user said about --host is for the client, the one door.
44
+ config = loadServerConfig([...forServer, '--port', '0'], { ...env, PORT: undefined, ATLAS_PORT: undefined }, cwd);
45
+ }
46
+ catch (error) {
47
+ if (error instanceof ConfigError) {
48
+ io.out(`atlas dev: ${error.message}\n${SERVER_USAGE.split('\n')[0]}\n`);
49
+ return 2;
50
+ }
51
+ throw error;
52
+ }
53
+ const backends = openConfiguredBackends(config, env, cwd);
54
+ const api = createAtlasServer({ ...serverOptionsOf(config, backends, line => io.out(`${JSON.stringify(line)}\n`)), settings: fileServerSettings(config, forServer, env, cwd) });
55
+ const running = await listen(api.fetch, { port: 0, host: '127.0.0.1', maxBodyBytes: Math.max(config.limits.maxObjectMiB, config.limits.maxJsonMiB) * 1024 * 1024 });
56
+ await api.ready;
57
+ // The client's config reads this to proxy /api, and to write the same page config a server would.
58
+ process.env.ATLAS_API_URL = running.url.replace('localhost', '127.0.0.1');
59
+ process.env.INIT_CWD = cwd;
60
+ const { createServer } = await import('vite');
61
+ const vite = await createServer({
62
+ configFile: path.join(root, 'vite.config.ts'),
63
+ configLoader: 'runner',
64
+ root,
65
+ server: { port: publicPort, host: config.host, strictPort: true }
66
+ });
67
+ try {
68
+ await vite.listen();
69
+ }
70
+ catch (error) {
71
+ api.close();
72
+ await vite.close();
73
+ await running.close(2_000);
74
+ throw error;
75
+ }
76
+ vite.printUrls();
77
+ io.out(`Data in ${path.relative(cwd, config.data) || config.data}. Publish with: ATLAS_STORE=${vite.resolvedUrls?.local[0] ?? `http://localhost:${publicPort}`} atlas report publish …\n`);
78
+ await new Promise(resolve => {
79
+ io.signal?.addEventListener('abort', () => resolve(), { once: true });
80
+ if (!io.signal) {
81
+ process.once('SIGTERM', () => resolve());
82
+ process.once('SIGINT', () => resolve());
83
+ }
84
+ });
85
+ api.close();
86
+ await vite.close();
87
+ await running.close(2_000);
88
+ return 0;
89
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @module Server-sent events
3
+ *
4
+ * What the server tells every connected viewer the moment it happens. A viewer that misses some (a dropped connection, a
5
+ * laptop that slept) loses nothing: report events announce changes, run events carry bounded progress snapshots, and a viewer reads authoritative state again
6
+ * when it reconnects. `EventHub` encodes each event once for all viewers, sends a named `ping` every heartbeat so proxies
7
+ * keep the connection and a quiet server is told apart from a dead one, refuses past a maximum number of viewers (they poll
8
+ * instead), and lets go of a viewer that has stopped reading rather than hold its backlog.
9
+ *
10
+ * @see docs/server.md#streaming
11
+ */
12
+ /** The events the server publishes: `hello` (sent first to a new viewer), `report` (layers arrived) `atlas:change` (specs or runs changed), `run` (a bounded live lease snapshot), and `projects` (project discovery changed). `ping` is the heartbeat, sent by the hub itself. */
13
+ export type EventName = 'hello' | 'report' | 'atlas:change' | 'run' | 'projects';
14
+ /** The viewers connected to `/api/events` and how to reach them all. One hub per server. */
15
+ export declare class EventHub {
16
+ #private;
17
+ constructor(options?: {
18
+ readonly maxSubscribers?: number;
19
+ readonly heartbeatMs?: number;
20
+ });
21
+ /** How many viewers are connected right now. */
22
+ get count(): number;
23
+ /** Tell every viewer. `data` is JSON. */
24
+ publish(name: EventName, data: unknown): void;
25
+ /** A stream for one viewer, starting with `hello` (what `greeting` returns). It ends when the viewer goes. */
26
+ stream(request: Request, greeting: () => unknown): Response;
27
+ /** End every stream, for shutting down. */
28
+ close(): void;
29
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @module Server-sent events
3
+ *
4
+ * What the server tells every connected viewer the moment it happens. A viewer that misses some (a dropped connection, a
5
+ * laptop that slept) loses nothing: report events announce changes, run events carry bounded progress snapshots, and a viewer reads authoritative state again
6
+ * when it reconnects. `EventHub` encodes each event once for all viewers, sends a named `ping` every heartbeat so proxies
7
+ * keep the connection and a quiet server is told apart from a dead one, refuses past a maximum number of viewers (they poll
8
+ * instead), and lets go of a viewer that has stopped reading rather than hold its backlog.
9
+ *
10
+ * @see docs/server.md#streaming
11
+ */
12
+ /** Events a viewer may be behind by before it is let go. */
13
+ const MAX_BACKLOG = 256;
14
+ /** The viewers connected to `/api/events` and how to reach them all. One hub per server. */
15
+ export class EventHub {
16
+ #subscribers = new Set();
17
+ #max;
18
+ #heartbeatMs;
19
+ #encoder = new TextEncoder();
20
+ #id = 0;
21
+ #timer = null;
22
+ constructor(options = {}) {
23
+ this.#max = options.maxSubscribers ?? 500;
24
+ this.#heartbeatMs = options.heartbeatMs ?? 15_000;
25
+ }
26
+ /** How many viewers are connected right now. */
27
+ get count() {
28
+ return this.#subscribers.size;
29
+ }
30
+ /** Tell every viewer. `data` is JSON. */
31
+ publish(name, data) {
32
+ this.#id += 1;
33
+ // Written once, however many are listening.
34
+ const frame = this.#encoder.encode(`id: ${this.#id}\nevent: ${name}\ndata: ${JSON.stringify(data)}\n\n`);
35
+ for (const subscriber of [...this.#subscribers])
36
+ subscriber.send(frame);
37
+ }
38
+ /** A stream for one viewer, starting with `hello` (what `greeting` returns). It ends when the viewer goes. */
39
+ stream(request, greeting) {
40
+ if (this.#subscribers.size >= this.#max)
41
+ return Response.json({ error: 'Too many viewers are connected for live updates; they will poll instead.' }, { status: 503, headers: { 'retry-after': '30' } });
42
+ let subscriber = null;
43
+ const body = new ReadableStream({
44
+ start: (controller) => {
45
+ const send = (chunk) => {
46
+ try {
47
+ // A viewer that has stopped reading (a laptop asleep, a dead connection) must not make the server hold its backlog: it reconnects and reads everything again.
48
+ if ((controller.desiredSize ?? 1) < -MAX_BACKLOG) {
49
+ drop();
50
+ controller.close();
51
+ return;
52
+ }
53
+ controller.enqueue(chunk);
54
+ }
55
+ catch {
56
+ drop();
57
+ }
58
+ };
59
+ const drop = () => { if (subscriber)
60
+ this.#subscribers.delete(subscriber); subscriber = null; if (!this.#subscribers.size)
61
+ this.#stopTimer(); };
62
+ subscriber = { send, close: () => { drop(); try {
63
+ controller.close();
64
+ }
65
+ catch { /* already closed */ } } };
66
+ this.#subscribers.add(subscriber);
67
+ this.#startTimer();
68
+ // `retry` is how long the browser waits before reconnecting by itself.
69
+ send(this.#encoder.encode(`retry: 3000\n\n`));
70
+ this.#id += 1;
71
+ send(this.#encoder.encode(`id: ${this.#id}\nevent: hello\ndata: ${JSON.stringify(greeting())}\n\n`));
72
+ request.signal.addEventListener('abort', drop, { once: true });
73
+ },
74
+ cancel: () => { if (subscriber)
75
+ this.#subscribers.delete(subscriber); subscriber = null; if (!this.#subscribers.size)
76
+ this.#stopTimer(); }
77
+ });
78
+ return new Response(body, { headers: { 'content-type': 'text/event-stream; charset=utf-8', 'cache-control': 'no-cache, no-transform', connection: 'keep-alive', 'x-accel-buffering': 'no' } });
79
+ }
80
+ #startTimer() {
81
+ if (this.#timer)
82
+ return;
83
+ // A named event, not a comment: proxies see traffic, and the viewer (which a browser does not tell about comments) can tell a quiet
84
+ // server from a dead connection.
85
+ const ping = this.#encoder.encode('event: ping\ndata: {}\n\n');
86
+ this.#timer = setInterval(() => { for (const subscriber of [...this.#subscribers])
87
+ subscriber.send(ping); }, this.#heartbeatMs);
88
+ this.#timer.unref?.();
89
+ }
90
+ #stopTimer() {
91
+ if (this.#timer)
92
+ clearInterval(this.#timer);
93
+ this.#timer = null;
94
+ }
95
+ /** End every stream, for shutting down. */
96
+ close() {
97
+ for (const subscriber of [...this.#subscribers])
98
+ subscriber.close();
99
+ this.#stopTimer();
100
+ }
101
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @module HTTP host for a fetch handler
3
+ *
4
+ * `listen` serves any `(Request) => Promise<Response>` over HTTP: with `Bun.serve` when running on Bun, with a `node:http`
5
+ * bridge (streaming bodies in and out, so server-sent events work) everywhere else. The server's behaviour is the same on
6
+ * both; `bun run release:smoke` proves it under each. Bodies over `maxBodyBytes` are refused by the runtime before the
7
+ * handler sees them.
8
+ */
9
+ /** A server that is listening: the `port` it got (useful when asked for `0`), its `url`, and `close`. */
10
+ export type RunningServer = {
11
+ readonly port: number;
12
+ readonly url: string;
13
+ /** Stop accepting, let requests in flight finish for up to `graceMs`, then end what is left. */
14
+ close(graceMs?: number): Promise<void>;
15
+ };
16
+ /** Where to listen (`port`, `host`) and the largest request body the runtime will accept. */
17
+ export type ListenOptions = {
18
+ readonly port: number;
19
+ readonly host: string;
20
+ /** The largest request body to read, in bytes. */
21
+ readonly maxBodyBytes: number;
22
+ };
23
+ type Handler = (request: Request) => Promise<Response> | Response;
24
+ /**
25
+ * Serve a fetch handler. On Bun, which is what Atlas is made for, that is `Bun.serve`: one native server for the
26
+ * API, the live streams and the page. On Node it is the same handler behind `node:http`, so the package still runs
27
+ * where Bun is not installed.
28
+ */
29
+ export declare function listen(handler: Handler, options: ListenOptions): Promise<RunningServer>;
30
+ export {};