@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
@@ -1,10 +1,23 @@
1
1
  /**
2
- * Run `tasks` at most `limit` at a time and yield each result the moment it
3
- * settles, so one slow read never holds back faster siblings. A rejection
4
- * is rethrown to the consumer once every started task has settled.
2
+ * @module Bounded concurrency
3
+ *
4
+ * Small helpers that run async work with a cap on how many tasks are in flight, used for reading and writing many
5
+ * store objects without flooding the backend. They differ from `Promise.all` in three ways: the cap, results in
6
+ * completion order (not input order), and failure handling: once one task rejects, no new task is started,
7
+ * those already started are allowed to finish, and then the first error is rethrown.
8
+ */
9
+ /**
10
+ * Runs `tasks` at most `limit` at a time (at least 1) and yields each result the moment it
11
+ * settles, so one slow read never holds back faster siblings. Results come in completion order, not input
12
+ * order. A rejection is rethrown to the consumer once every started task has settled; no results are yielded
13
+ * after the first failure.
5
14
  */
6
15
  export declare function inCompletionOrder<T>(tasks: readonly (() => Promise<T>)[], limit: number): AsyncGenerator<T>;
7
- /** Apply `work` to every item, at most `limit` at a time. */
16
+ /** Applies `work` to every item, at most `limit` at a time, and resolves when all are done; rejects with the first error as {@link inCompletionOrder} does. */
8
17
  export declare function eachLimited<T>(items: readonly T[], limit: number, work: (item: T) => Promise<void>): Promise<void>;
9
- /** Consume a lazy input with backpressure, draining started work before failure. */
18
+ /**
19
+ * Consumes a lazy (async) input with backpressure: it pulls the next item only while fewer than `limit` works are
20
+ * running, so a huge or generated input is never held in memory at once. After a failure it stops pulling,
21
+ * waits for the started work to settle, then rethrows the first error.
22
+ */
10
23
  export declare function eachLimitedStream<T>(items: AsyncIterable<T>, limit: number, work: (item: T) => Promise<void>): Promise<void>;
@@ -1,7 +1,16 @@
1
1
  /**
2
- * Run `tasks` at most `limit` at a time and yield each result the moment it
3
- * settles, so one slow read never holds back faster siblings. A rejection
4
- * is rethrown to the consumer once every started task has settled.
2
+ * @module Bounded concurrency
3
+ *
4
+ * Small helpers that run async work with a cap on how many tasks are in flight, used for reading and writing many
5
+ * store objects without flooding the backend. They differ from `Promise.all` in three ways: the cap, results in
6
+ * completion order (not input order), and failure handling: once one task rejects, no new task is started,
7
+ * those already started are allowed to finish, and then the first error is rethrown.
8
+ */
9
+ /**
10
+ * Runs `tasks` at most `limit` at a time (at least 1) and yields each result the moment it
11
+ * settles, so one slow read never holds back faster siblings. Results come in completion order, not input
12
+ * order. A rejection is rethrown to the consumer once every started task has settled; no results are yielded
13
+ * after the first failure.
5
14
  */
6
15
  export async function* inCompletionOrder(tasks, limit) {
7
16
  const width = Math.max(1, Math.floor(limit));
@@ -42,14 +51,18 @@ export async function* inCompletionOrder(tasks, limit) {
42
51
  if (outcome.failure !== null)
43
52
  throw outcome.failure.error;
44
53
  }
45
- /** Apply `work` to every item, at most `limit` at a time. */
54
+ /** Applies `work` to every item, at most `limit` at a time, and resolves when all are done; rejects with the first error as {@link inCompletionOrder} does. */
46
55
  export async function eachLimited(items, limit, work) {
47
56
  const tasks = items.map((item) => () => work(item));
48
57
  for await (const _ of inCompletionOrder(tasks, limit)) {
49
58
  // Results are void; draining is the point.
50
59
  }
51
60
  }
52
- /** Consume a lazy input with backpressure, draining started work before failure. */
61
+ /**
62
+ * Consumes a lazy (async) input with backpressure: it pulls the next item only while fewer than `limit` works are
63
+ * running, so a huge or generated input is never held in memory at once. After a failure it stops pulling,
64
+ * waits for the started work to settle, then rethrows the first error.
65
+ */
53
66
  export async function eachLimitedStream(items, limit, work) {
54
67
  const width = Math.max(1, Math.floor(limit));
55
68
  const running = new Set();
@@ -0,0 +1,80 @@
1
+ import type { BackendInfo, LayerHeader, StatusCounts } from './evidence-types.js';
2
+ import { type CombinedEvidence, type EvidenceResult } from './layer-merge.js';
3
+ import type { ReportOutline } from './layer-types.js';
4
+ import type { ObjectStore } from './object-store.js';
5
+ /** A store that contributes layers. Any {@link ObjectStore} is one: a directory, a bucket, a device lab. */
6
+ export type BackendInput = {
7
+ /** Stable and unique: lowercase letters, digits and hyphens. It names where a result came from. */
8
+ readonly id: string;
9
+ /** What a person sees in notices and in the viewer. Defaults to `id`. */
10
+ readonly label?: string;
11
+ /** Where the layers are. Only read from. */
12
+ readonly store: ObjectStore;
13
+ };
14
+ /** Options of {@link combineBackends}. */
15
+ export type CombineOptions = {
16
+ /** The specs the report is laid out against. When omitted, the specs the layers recorded. */
17
+ readonly outline?: ReportOutline;
18
+ /** Layers read at once, across all backends. Default 16. */
19
+ readonly concurrency?: number;
20
+ /**
21
+ * Leave layers out, to see what earlier ones said (a layer for which this returns false takes no part: not in the
22
+ * results, the layer list, the counts, or the recorded specs). Layers are still read; this filters after reading.
23
+ */
24
+ readonly include?: (layer: LayerHeader) => boolean;
25
+ };
26
+ /** What a report says in numbers. Timings that only qualify a check are not counted as checks. */
27
+ export type ReportSummary = {
28
+ /** Counts of the winning results by status, and their total. */
29
+ readonly checks: StatusCounts & {
30
+ readonly total: number;
31
+ };
32
+ /** The same counts per environment slug, keys sorted; results with no environment are under `unspecified`. */
33
+ readonly byEnvironment: Readonly<Record<string, StatusCounts & {
34
+ readonly total: number;
35
+ }>>;
36
+ /** Specs in the outline with no result at all. */
37
+ readonly specsWithoutEvidence: readonly string[];
38
+ };
39
+ /** A report: the results laid over the specs, with what was read and what could not be. */
40
+ export type CombinedReport = {
41
+ /** Every spec in the outline with the winning result of each of its slots, plus results that belong to no spec in the outline. */
42
+ readonly combined: CombinedEvidence;
43
+ /** Every distinct layer that was read, newest first. A layer several backends hold is one layer. */
44
+ readonly layers: readonly LayerHeader[];
45
+ /** Every backend given, sorted by id, with whether it could be read. */
46
+ readonly backends: readonly BackendInfo[];
47
+ /** What could not be read. A gate treats any of these as unknown, never as a pass. */
48
+ readonly notices: readonly string[];
49
+ /** The numbers of `combined`, computed once for the gate and the viewer. */
50
+ readonly summary: ReportSummary;
51
+ };
52
+ /**
53
+ * Counts results by status. Diagnostic results (parts of kind `performance`, which only qualify a check) are left out
54
+ * whenever any other result is present; if only diagnostics exist, they are counted so the report is not empty.
55
+ */
56
+ export declare function countResults(results: readonly EvidenceResult[]): StatusCounts & {
57
+ total: number;
58
+ };
59
+ /** Counts a combined report's winning results in total and per environment, and lists the specs that have no result at all. */
60
+ export declare function summarize(combined: CombinedEvidence): ReportSummary;
61
+ /**
62
+ * Reads every layer of every backend and lays them over the specs. Layers are the only input:
63
+ * a report is a function of the set of layers, so the order of backends and layers, and how they
64
+ * were split across machines, branches and runs, does not change it.
65
+ *
66
+ * A backend that cannot be read, or a layer that cannot be understood, is named in `notices` and
67
+ * leaves everything else usable; a gate must treat any notice as unknown, never as a pass. A layer held by
68
+ * several backends is one layer. Only an invalid backend `id` (not lowercase letters, digits and hyphens, or
69
+ * repeated) throws, before anything is read. Backends are only read from.
70
+ *
71
+ * When `options.outline` is omitted, the specs are the ones the included layers recorded (the newest
72
+ * recording of each spec path wins), with no commit sha.
73
+ *
74
+ * @example
75
+ * const outline = await loadDirectoryOutline('./specs'); // optional: without it the layers' own specs are used
76
+ * const report = await combineBackends([{ id: 'ci', store: directoryStore('./atlas-reports') }], { outline });
77
+ * report.summary.checks.failed; // number of slots whose winner failed
78
+ * report.notices; // non-empty means something could not be read
79
+ */
80
+ export declare function combineBackends(backends: readonly BackendInput[], options?: CombineOptions): Promise<CombinedReport>;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @module Combining backends into one report
3
+ *
4
+ * The entry point that turns stores of layers into a report: it reads every layer from every {@link BackendInput},
5
+ * lays them over the specs, and counts the outcome. It owns reading and failure reporting (an unreadable backend or
6
+ * layer becomes a notice, never a pass); the rule for which result wins a slot lives in `layer-merge.ts`. A report
7
+ * is computed, never stored, and is a function of the set of layers: the order and split of backends does not
8
+ * change it.
9
+ *
10
+ * @see docs/report-contract.md (how layers compose, guarantees)
11
+ */
12
+ import { eachLimited } from './completion-order.js';
13
+ import { catalogEntries, catalogLayers } from './layer-catalog.js';
14
+ import { combineEvidence, distinctHeaders, emptyCounts, isDiagnosticKind, layerEvidence, specPath } from './layer-merge.js';
15
+ const BACKEND_ID = /^[a-z0-9-]+$/;
16
+ /**
17
+ * Counts results by status. Diagnostic results (parts of kind `performance`, which only qualify a check) are left out
18
+ * whenever any other result is present; if only diagnostics exist, they are counted so the report is not empty.
19
+ */
20
+ export function countResults(results) {
21
+ const outcomes = results.some(result => !isDiagnosticKind(result.row.kind)) ? results.filter(result => !isDiagnosticKind(result.row.kind)) : results;
22
+ const counts = emptyCounts();
23
+ for (const result of outcomes)
24
+ counts[result.row.status] += 1;
25
+ return { ...counts, total: outcomes.length };
26
+ }
27
+ /** Counts a combined report's winning results in total and per environment, and lists the specs that have no result at all. */
28
+ export function summarize(combined) {
29
+ const results = [...combined.specs.flatMap(entry => entry.results), ...combined.unassigned];
30
+ const environments = new Map();
31
+ for (const result of results) {
32
+ const key = result.layer.environment ?? 'unspecified';
33
+ const group = environments.get(key);
34
+ if (group)
35
+ group.push(result);
36
+ else
37
+ environments.set(key, [result]);
38
+ }
39
+ return {
40
+ checks: countResults(results),
41
+ byEnvironment: Object.fromEntries([...environments].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([key, value]) => [key, countResults(value)])),
42
+ specsWithoutEvidence: combined.specs.filter(entry => entry.results.length === 0).map(entry => entry.spec.path)
43
+ };
44
+ }
45
+ /**
46
+ * Reads every layer of every backend and lays them over the specs. Layers are the only input:
47
+ * a report is a function of the set of layers, so the order of backends and layers, and how they
48
+ * were split across machines, branches and runs, does not change it.
49
+ *
50
+ * A backend that cannot be read, or a layer that cannot be understood, is named in `notices` and
51
+ * leaves everything else usable; a gate must treat any notice as unknown, never as a pass. A layer held by
52
+ * several backends is one layer. Only an invalid backend `id` (not lowercase letters, digits and hyphens, or
53
+ * repeated) throws, before anything is read. Backends are only read from.
54
+ *
55
+ * When `options.outline` is omitted, the specs are the ones the included layers recorded (the newest
56
+ * recording of each spec path wins), with no commit sha.
57
+ *
58
+ * @example
59
+ * const outline = await loadDirectoryOutline('./specs'); // optional: without it the layers' own specs are used
60
+ * const report = await combineBackends([{ id: 'ci', store: directoryStore('./atlas-reports') }], { outline });
61
+ * report.summary.checks.failed; // number of slots whose winner failed
62
+ * report.notices; // non-empty means something could not be read
63
+ */
64
+ export async function combineBackends(backends, options = {}) {
65
+ const seen = new Set();
66
+ for (const backend of backends) {
67
+ if (!BACKEND_ID.test(backend.id) || seen.has(backend.id))
68
+ throw new Error(`Invalid report backend id: "${backend.id}". Use unique lowercase letters, digits and hyphens`);
69
+ seen.add(backend.id);
70
+ }
71
+ const notices = [];
72
+ const info = [];
73
+ const entries = [];
74
+ await Promise.all(backends.map(async (backend) => {
75
+ try {
76
+ for (const entry of await catalogEntries(backend.store))
77
+ entries.push({ backend, entry });
78
+ info.push({ id: backend.id, label: backend.label ?? backend.id, live: false, available: true });
79
+ }
80
+ catch (error) {
81
+ info.push({ id: backend.id, label: backend.label ?? backend.id, live: false, available: false });
82
+ notices.push(`${backend.label ?? backend.id} is unavailable: ${error instanceof Error ? error.message : String(error)}`);
83
+ }
84
+ }));
85
+ entries.sort((a, b) => (a.backend.id < b.backend.id ? -1 : a.backend.id > b.backend.id ? 1 : a.entry < b.entry ? -1 : a.entry > b.entry ? 1 : 0));
86
+ const loaded = [];
87
+ await eachLimited(entries, options.concurrency ?? 16, async ({ backend, entry }) => {
88
+ try {
89
+ for (const summary of await catalogLayers(backend.store, entry)) {
90
+ loaded.push({ evidence: layerEvidence(summary, backend.id), specs: summary.specs });
91
+ }
92
+ }
93
+ catch (error) {
94
+ notices.push(`${backend.label ?? backend.id}: a layer could not be read, so its outcome is unknown (${error instanceof Error ? error.message : String(error)})`);
95
+ }
96
+ });
97
+ const include = options.include;
98
+ const kept = include ? loaded.filter(item => include(item.evidence.layer)) : loaded;
99
+ const layers = kept.map(item => item.evidence);
100
+ // The specs a report is laid over come from the layers that are in it, not from ones left out.
101
+ const recorded = new Map();
102
+ if (!options.outline) {
103
+ for (const { evidence, specs } of kept) {
104
+ for (const spec of specs) {
105
+ const key = specPath(spec.path);
106
+ const prior = recorded.get(key);
107
+ const at = evidence.layer.recordedAtMs;
108
+ if (!prior || at > prior.at || (at === prior.at && (spec.fingerprint ?? '') > (prior.entry.fingerprint ?? '')))
109
+ recorded.set(key, { entry: { ...spec, path: key }, at });
110
+ }
111
+ }
112
+ }
113
+ const outline = options.outline ?? { specs: [...recorded.values()].map(item => item.entry).sort((a, b) => (a.path < b.path ? -1 : 1)), sha: null, changedFiles: [] };
114
+ const combined = combineEvidence(outline, layers);
115
+ const headers = distinctHeaders(layers.map(evidence => evidence.layer)).sort((a, b) => b.recordedAtMs - a.recordedAtMs || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
116
+ return { combined, layers: headers, backends: info.sort((a, b) => (a.id < b.id ? -1 : 1)), notices: [...new Set(notices)].sort(), summary: summarize(combined) };
117
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @module Copying layers between stores
3
+ *
4
+ * `copyStore` copies the layers one store has and another lacks: how partial reports meet (each CI node publishes to its own
5
+ * store; copying them together is the aggregate). It is idempotent, commutative and safe to repeat, because layers are
6
+ * immutable and named by their content, and it writes in dependency order (files, then documents, then catalog entries) so a
7
+ * reader of the destination, or a server that checks, never sees something before what it names.
8
+ *
9
+ * @see docs/report-contract.md
10
+ */
11
+ import type { ObjectStore } from './object-store.js';
12
+ /**
13
+ * Copy every layer object (`layers/…`) of `from` that `to` does not have. Only layers: they are
14
+ * immutable, so an object that exists is already right. Commit-keyed legacy objects are mutable and
15
+ * are left where they are. This is how partial reports meet: each CI
16
+ * node publishes into its own store, and copying them together is the aggregate, because layers are
17
+ * immutable, addressed by content, and never share a key with different bytes. It is idempotent,
18
+ * commutative and safe to repeat after a failure.
19
+ *
20
+ * A layer's catalog entry is copied last, and its manifest after its files, so a reader of `to` never sees an
21
+ * entry for a layer whose content has not arrived.
22
+ */
23
+ export declare function copyStore(from: ObjectStore, to: ObjectStore, options?: {
24
+ readonly concurrency?: number;
25
+ }): Promise<{
26
+ readonly copied: number;
27
+ readonly skipped: number;
28
+ readonly ignored: number;
29
+ }>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @module Copying layers between stores
3
+ *
4
+ * `copyStore` copies the layers one store has and another lacks: how partial reports meet (each CI node publishes to its own
5
+ * store; copying them together is the aggregate). It is idempotent, commutative and safe to repeat, because layers are
6
+ * immutable and named by their content, and it writes in dependency order (files, then documents, then catalog entries) so a
7
+ * reader of the destination, or a server that checks, never sees something before what it names.
8
+ *
9
+ * @see docs/report-contract.md
10
+ */
11
+ import { eachLimited } from './completion-order.js';
12
+ import { readBytes } from './object-io.js';
13
+ /**
14
+ * What is copied after what it names, so a reader (or a server that checks) never sees one without its content:
15
+ * files first, then the documents that name files (a layer's manifest, a spec set, a run's manifest), then the
16
+ * catalog entries that make a document visible.
17
+ */
18
+ const isCatalog = (key) => key.includes('/index/');
19
+ const isDocument = (key) => /^layers\/(manifests\/|specs\/sets\/|runs\/[^/]+\/manifest\.json$)/.test(key);
20
+ /**
21
+ * Copy every layer object (`layers/…`) of `from` that `to` does not have. Only layers: they are
22
+ * immutable, so an object that exists is already right. Commit-keyed legacy objects are mutable and
23
+ * are left where they are. This is how partial reports meet: each CI
24
+ * node publishes into its own store, and copying them together is the aggregate, because layers are
25
+ * immutable, addressed by content, and never share a key with different bytes. It is idempotent,
26
+ * commutative and safe to repeat after a failure.
27
+ *
28
+ * A layer's catalog entry is copied last, and its manifest after its files, so a reader of `to` never sees an
29
+ * entry for a layer whose content has not arrived.
30
+ */
31
+ export async function copyStore(from, to, options = {}) {
32
+ const everything = await from.listKeys();
33
+ const keys = everything.filter(key => key.startsWith('layers/'));
34
+ const width = options.concurrency ?? 16;
35
+ let copied = 0, skipped = 0;
36
+ const copy = async (key) => {
37
+ if (await to.head(key)) {
38
+ skipped += 1;
39
+ return;
40
+ }
41
+ const stored = await from.get(key);
42
+ if (!stored)
43
+ return;
44
+ await to.put(key, await readBytes(stored.body), stored.contentType);
45
+ copied += 1;
46
+ };
47
+ await eachLimited(keys.filter(key => !isCatalog(key) && !isDocument(key)), width, copy);
48
+ await eachLimited(keys.filter(key => !isCatalog(key) && isDocument(key)), width, copy);
49
+ await eachLimited(keys.filter(isCatalog), width, copy);
50
+ // Said, not silent: whatever is not a layer was left where it is.
51
+ return { copied, skipped, ignored: everything.length - keys.length };
52
+ }
@@ -0,0 +1,38 @@
1
+ import type { ReportOutline } from './layer-types.js';
2
+ /** Every file the viewer reads from a specs directory: charts, journeys, notations and notes. */
3
+ export declare function isSpecFile(file: string): boolean;
4
+ /** The notation of a spec file. Anything else is named by its extension. */
5
+ export declare function specKindOf(file: string): string;
6
+ /** The title of a spec: the `Feature:` line of a `.feature`, the `module` name of a `.qnt`, otherwise the file's base name. */
7
+ export declare function specTitleOf(file: string, text: string): string;
8
+ /** One spec file: its `specs/<relative path>` name and its UTF-8 text. */
9
+ export type SpecFileText = {
10
+ readonly path: string;
11
+ readonly text: string;
12
+ };
13
+ /**
14
+ * The spec files of a directory, named `specs/<path inside it>`: the one definition of what a spec is,
15
+ * shared by the outline a report is laid over and by the specs that are published. Hidden files and
16
+ * folders, `node_modules`, `dist`, files over 2 MiB and files of other types (pictures, build output) are
17
+ * left out. Sorted by path. Rejects if `directory` does not exist.
18
+ */
19
+ export declare function collectSpecFiles(directory: string): Promise<SpecFileText[]>;
20
+ /**
21
+ * The specs of a directory as the outline a report is laid over. Each spec is named
22
+ * `specs/<its path inside the directory>`: that is the `specPath` a check uses to say which spec it
23
+ * belongs to. `domain` groups specs in the viewer; it defaults to the project's folder name.
24
+ *
25
+ * `repoRoot` is where `git rev-parse HEAD` is asked for the outline's `sha` (default: `directory`); the `sha` is
26
+ * null outside git. `changedFiles` is always empty. The returned `readSpec(specPath)` answers the text read here,
27
+ * or null for a path that is not in the outline.
28
+ *
29
+ * @example
30
+ * const outline = await loadDirectoryOutline('./specs', { repoRoot: process.cwd() });
31
+ * outline.specs.map(spec => spec.path); // ['specs/join.feature', ...]
32
+ */
33
+ export declare function loadDirectoryOutline(directory: string, options?: {
34
+ readonly domain?: string;
35
+ readonly repoRoot?: string;
36
+ }): Promise<ReportOutline & {
37
+ readSpec: (specPath: string) => Promise<string | null>;
38
+ }>;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @module Spec outline from a directory
3
+ *
4
+ * Defines what a spec file is and reads a specs directory into the outline a report is laid over: one entry per
5
+ * spec, named `specs/<path inside the directory>`, with its notation, title and a fingerprint of its text. The same
6
+ * definition feeds the specs that are published, so the outline and the published spec set agree. Node-only
7
+ * (reads the file system and asks `git` for the commit); nothing is written.
8
+ *
9
+ * @see docs/report-contract.md (the layer document's `specs`, and `specPath`)
10
+ */
11
+ import { execFile } from 'node:child_process';
12
+ import { readdir, readFile, stat } from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ import { promisify } from 'node:util';
15
+ import { isStatechartFile } from '../../schema/src/spec.js';
16
+ import { textFingerprint } from './layer-merge.js';
17
+ const exec = promisify(execFile);
18
+ const SKIPPED = new Set(['node_modules', 'dist', '.git', '.cache']);
19
+ const MAX_BYTES = 2 * 1024 * 1024;
20
+ /** Every file the viewer reads from a specs directory: charts, journeys, notations and notes. */
21
+ export function isSpecFile(file) {
22
+ return /\.scxml$/i.test(file) || /(^|\/)([\w.-]+\.)?machine\.(ya?ml|json)$/.test(file) || isStatechartFile(file) || /\.(feature|qnt|dmn|als|md|ya?ml|json|xml|csv|txt)$/i.test(file);
23
+ }
24
+ /** The notation of a spec file. Anything else is named by its extension. */
25
+ export function specKindOf(file) {
26
+ if (/\.feature$/i.test(file))
27
+ return 'gherkin';
28
+ if (/\.qnt$/i.test(file))
29
+ return 'quint';
30
+ if (/\.dmn$/i.test(file))
31
+ return 'dmn';
32
+ if (/\.als$/i.test(file))
33
+ return 'alloy';
34
+ if (/\.openslo\.ya?ml$/i.test(file))
35
+ return 'openslo';
36
+ const extension = file.split('.').at(-1);
37
+ return extension && extension !== file ? extension.toLowerCase() : 'document';
38
+ }
39
+ /** The title of a spec: the `Feature:` line of a `.feature`, the `module` name of a `.qnt`, otherwise the file's base name. */
40
+ export function specTitleOf(file, text) {
41
+ if (/\.feature$/i.test(file))
42
+ return /^\s*Feature:\s*(.+)$/m.exec(text)?.[1]?.trim() || path.posix.basename(file);
43
+ if (/\.qnt$/i.test(file))
44
+ return /^\s*module\s+(\w+)/m.exec(text)?.[1] || path.posix.basename(file);
45
+ return path.posix.basename(file);
46
+ }
47
+ /**
48
+ * The spec files of a directory, named `specs/<path inside it>`: the one definition of what a spec is,
49
+ * shared by the outline a report is laid over and by the specs that are published. Hidden files and
50
+ * folders, `node_modules`, `dist`, files over 2 MiB and files of other types (pictures, build output) are
51
+ * left out. Sorted by path. Rejects if `directory` does not exist.
52
+ */
53
+ export async function collectSpecFiles(directory) {
54
+ const root = path.resolve(directory);
55
+ const found = [];
56
+ async function walk(dir) {
57
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
58
+ if (entry.name.startsWith('.') || SKIPPED.has(entry.name))
59
+ continue;
60
+ const full = path.join(dir, entry.name);
61
+ if (entry.isDirectory())
62
+ await walk(full);
63
+ else if (entry.isFile()) {
64
+ const relative = path.relative(root, full).split(path.sep).join('/');
65
+ if (isSpecFile(relative) && (await stat(full)).size <= MAX_BYTES)
66
+ found.push({ path: `specs/${relative}`, text: await readFile(full, 'utf8') });
67
+ }
68
+ }
69
+ }
70
+ await walk(root);
71
+ return found.sort((a, b) => (a.path < b.path ? -1 : 1));
72
+ }
73
+ async function git(cwd, args) {
74
+ try {
75
+ return (await exec('git', args, { cwd })).stdout.trim() || null;
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ /**
82
+ * The specs of a directory as the outline a report is laid over. Each spec is named
83
+ * `specs/<its path inside the directory>`: that is the `specPath` a check uses to say which spec it
84
+ * belongs to. `domain` groups specs in the viewer; it defaults to the project's folder name.
85
+ *
86
+ * `repoRoot` is where `git rev-parse HEAD` is asked for the outline's `sha` (default: `directory`); the `sha` is
87
+ * null outside git. `changedFiles` is always empty. The returned `readSpec(specPath)` answers the text read here,
88
+ * or null for a path that is not in the outline.
89
+ *
90
+ * @example
91
+ * const outline = await loadDirectoryOutline('./specs', { repoRoot: process.cwd() });
92
+ * outline.specs.map(spec => spec.path); // ['specs/join.feature', ...]
93
+ */
94
+ export async function loadDirectoryOutline(directory, options = {}) {
95
+ const root = path.resolve(directory);
96
+ const files = await collectSpecFiles(root);
97
+ const texts = new Map(files.map(file => [file.path, file.text]));
98
+ const domain = options.domain ?? (path.basename(options.repoRoot ?? root) || 'product');
99
+ const specs = files.map(file => ({ path: file.path, domain, kind: specKindOf(file.path), title: specTitleOf(file.path, file.text), fingerprint: textFingerprint(file.text) }));
100
+ const cwd = options.repoRoot ?? root;
101
+ const sha = await git(cwd, ['rev-parse', 'HEAD']);
102
+ return { specs, sha: sha && /^[0-9a-f]{40}$/.test(sha) ? sha : null, changedFiles: [], readSpec: async (specPath) => texts.get(specPath) ?? null };
103
+ }
@@ -0,0 +1,20 @@
1
+ import type { ObjectStore, PagedListing } from './object-store.js';
2
+ /**
3
+ * Creates an {@link ObjectStore} on a directory: one file per key, its content type in a sidecar file. The
4
+ * backend for a CI workspace, a downloaded artifact or a laptop. Writes are atomic, listing reads
5
+ * only the directories a prefix can reach, and two stores over copies of the same layers can be
6
+ * merged with `cp -R` because layers are immutable and addressed by content.
7
+ *
8
+ * The directory need not exist yet: `put` creates it, and `get`/`head`/`listKeys` on a missing one answer
9
+ * null/false/`[]`. A missing sidecar reads as `application/octet-stream`. `put` writes the content type before
10
+ * the bytes, so an object is never visible without its type. `listKeys` returns keys in code-unit order, skips
11
+ * temporary and sidecar files, and throws on a prefix that is absolute, contains a backslash or `..`. `getUri`
12
+ * is a `file://` URL. Also implements `listPage` (`after` is exclusive, `limit` defaults to 1000, `next` is the
13
+ * last returned key while more remain).
14
+ *
15
+ * @example
16
+ * const store = directoryStore('./atlas-reports');
17
+ * await store.put('layers/blobs/x', new TextEncoder().encode('hi'), 'text/plain');
18
+ * await store.listKeys('layers/'); // ['layers/blobs/x']
19
+ */
20
+ export declare function directoryStore(root: string): ObjectStore & PagedListing;