@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.
- package/CHANGELOG.md +13 -1
- package/README.md +191 -611
- package/dist/bun/src/driver.d.ts +41 -7
- package/dist/bun/src/driver.js +47 -6
- package/dist/bun/src/index.d.ts +8 -0
- package/dist/bun/src/index.js +8 -0
- package/dist/core/src/cli.d.ts +14 -1
- package/dist/core/src/cli.js +35 -1
- package/dist/core/src/config.d.ts +77 -4
- package/dist/core/src/config.js +110 -17
- package/dist/core/src/events/cloudevents.d.ts +54 -9
- package/dist/core/src/events/cloudevents.js +60 -9
- package/dist/core/src/graph.d.ts +21 -0
- package/dist/core/src/graph.js +26 -0
- package/dist/core/src/index.d.ts +11 -0
- package/dist/core/src/index.js +11 -0
- package/dist/core/src/lint.d.ts +30 -0
- package/dist/core/src/lint.js +13 -0
- package/dist/core/src/plan.d.ts +39 -1
- package/dist/core/src/plan.js +28 -1
- package/dist/core/src/privacy.d.ts +29 -2
- package/dist/core/src/privacy.js +19 -2
- package/dist/core/src/report/formats.d.ts +34 -4
- package/dist/core/src/report/formats.js +24 -4
- package/dist/core/src/report/markdown.d.ts +15 -0
- package/dist/core/src/report/markdown.js +8 -0
- package/dist/core/src/run/clients.d.ts +44 -5
- package/dist/core/src/run/clients.js +46 -2
- package/dist/core/src/run/execute.d.ts +75 -16
- package/dist/core/src/run/execute.js +71 -18
- package/dist/core/src/run/frames.d.ts +13 -4
- package/dist/core/src/run/frames.js +19 -4
- package/dist/core/src/run/http.d.ts +34 -1
- package/dist/core/src/run/http.js +14 -1
- package/dist/core/src/run/types.d.ts +76 -3
- package/dist/core/src/run/types.js +19 -3
- package/dist/core/src/signals.d.ts +49 -5
- package/dist/core/src/signals.js +22 -5
- package/dist/core/src/spec/compose.d.ts +23 -1
- package/dist/core/src/spec/compose.js +10 -1
- package/dist/core/src/spec/index.d.ts +9 -1
- package/dist/core/src/spec/index.js +9 -1
- package/dist/core/src/spec/load.d.ts +22 -1
- package/dist/core/src/spec/load.js +31 -1
- package/dist/core/src/spec/parse.d.ts +16 -2
- package/dist/core/src/spec/parse.js +25 -2
- package/dist/core/src/spec/scxml.d.ts +25 -4
- package/dist/core/src/spec/scxml.js +36 -4
- package/dist/core/src/spec/types.d.ts +52 -1
- package/dist/core/src/spec/types.js +24 -0
- package/dist/detox/src/index.d.ts +54 -11
- package/dist/detox/src/index.js +45 -7
- package/dist/playwright/src/driver.d.ts +39 -3
- package/dist/playwright/src/driver.js +27 -3
- package/dist/playwright/src/index.d.ts +10 -0
- package/dist/playwright/src/index.js +10 -0
- package/dist/playwright/src/media.d.ts +50 -4
- package/dist/playwright/src/media.js +55 -4
- package/dist/playwright/src/signals.d.ts +44 -5
- package/dist/playwright/src/signals.js +29 -5
- package/dist/reports/src/attachment-files.d.ts +44 -0
- package/dist/reports/src/attachment-files.js +121 -0
- package/dist/reports/src/attachment-response.d.ts +31 -0
- package/dist/reports/src/attachment-response.js +55 -0
- package/dist/reports/src/aws-credentials.d.ts +48 -0
- package/dist/reports/src/aws-credentials.js +221 -0
- package/dist/reports/src/browser-navigation.d.ts +14 -0
- package/dist/reports/src/browser-navigation.js +7 -0
- package/dist/reports/src/bundle.d.ts +34 -2
- package/dist/reports/src/bundle.js +38 -2
- package/dist/reports/src/catalog.d.ts +34 -3
- package/dist/reports/src/catalog.js +17 -3
- package/dist/reports/src/cli-stores.d.ts +36 -0
- package/dist/reports/src/cli-stores.js +73 -0
- package/dist/reports/src/clock.d.ts +7 -2
- package/dist/reports/src/clock.js +5 -2
- package/dist/reports/src/completion-order.d.ts +18 -5
- package/dist/reports/src/completion-order.js +18 -5
- package/dist/reports/src/compose.d.ts +80 -0
- package/dist/reports/src/compose.js +117 -0
- package/dist/reports/src/copy-store.d.ts +29 -0
- package/dist/reports/src/copy-store.js +52 -0
- package/dist/reports/src/directory-outline.d.ts +38 -0
- package/dist/reports/src/directory-outline.js +103 -0
- package/dist/reports/src/directory-store.d.ts +20 -0
- package/dist/reports/src/directory-store.js +166 -0
- package/dist/reports/src/doctor.d.ts +29 -0
- package/dist/reports/src/doctor.js +85 -0
- package/dist/reports/src/effective-delta.d.ts +34 -0
- package/dist/reports/src/effective-delta.js +25 -0
- package/dist/reports/src/evidence-detail.d.ts +64 -0
- package/dist/reports/src/evidence-detail.js +92 -0
- package/dist/reports/src/evidence-live.d.ts +35 -5
- package/dist/reports/src/evidence-live.js +18 -2
- package/dist/reports/src/evidence-types.d.ts +68 -1
- package/dist/reports/src/evidence-types.js +5 -1
- package/dist/reports/src/evidence-view.d.ts +43 -4
- package/dist/reports/src/evidence-view.js +13 -2
- package/dist/reports/src/explorer-types.d.ts +40 -3
- package/dist/reports/src/explorer-types.js +8 -0
- package/dist/reports/src/explorer-versions.d.ts +9 -1
- package/dist/reports/src/explorer-versions.js +18 -1
- package/dist/reports/src/export-evidence.d.ts +16 -1
- package/dist/reports/src/export-evidence.js +52 -1
- package/dist/reports/src/fetcher.d.ts +27 -3
- package/dist/reports/src/fetcher.js +11 -3
- package/dist/reports/src/format-date.d.ts +6 -0
- package/dist/reports/src/format-date.js +6 -0
- package/dist/reports/src/format.d.ts +16 -0
- package/dist/reports/src/format.js +10 -0
- package/dist/reports/src/frames.d.ts +36 -3
- package/dist/reports/src/frames.js +22 -3
- package/dist/reports/src/hash.d.ts +18 -0
- package/dist/reports/src/hash.js +13 -0
- package/dist/reports/src/host.d.ts +29 -0
- package/dist/reports/src/host.js +64 -0
- package/dist/reports/src/html-frame.d.ts +5 -6
- package/dist/reports/src/html-frame.js +12 -1
- package/dist/reports/src/http-store.d.ts +56 -0
- package/dist/reports/src/http-store.js +148 -0
- package/dist/reports/src/http.d.ts +7 -0
- package/dist/reports/src/http.js +6 -0
- package/dist/reports/src/ingest.d.ts +54 -0
- package/dist/reports/src/ingest.js +210 -0
- package/dist/reports/src/init.d.ts +40 -0
- package/dist/reports/src/init.js +369 -0
- package/dist/reports/src/junit.d.ts +33 -6
- package/dist/reports/src/junit.js +87 -21
- package/dist/reports/src/keyboard.d.ts +20 -0
- package/dist/reports/src/keyboard.js +11 -0
- package/dist/reports/src/keys.d.ts +21 -3
- package/dist/reports/src/keys.js +21 -3
- package/dist/reports/src/layer-axes.d.ts +35 -5
- package/dist/reports/src/layer-axes.js +25 -5
- package/dist/reports/src/layer-catalog.d.ts +28 -1
- package/dist/reports/src/layer-catalog.js +18 -1
- package/dist/reports/src/layer-merge.d.ts +98 -9
- package/dist/reports/src/layer-merge.js +147 -31
- package/dist/reports/src/layer-source.d.ts +18 -0
- package/dist/reports/src/layer-source.js +11 -0
- package/dist/reports/src/layer-store.d.ts +42 -0
- package/dist/reports/src/layer-store.js +63 -5
- package/dist/reports/src/layer-types.d.ts +32 -1
- package/dist/reports/src/layers-route.d.ts +47 -4
- package/dist/reports/src/layers-route.js +86 -7
- package/dist/reports/src/live-reporter.d.ts +34 -0
- package/dist/reports/src/live-reporter.js +122 -0
- package/dist/reports/src/media-types.d.ts +11 -0
- package/dist/reports/src/media-types.js +22 -0
- package/dist/reports/src/namespace-guard.d.ts +10 -1
- package/dist/reports/src/namespace-guard.js +10 -1
- package/dist/reports/src/navigation.d.ts +12 -0
- package/dist/reports/src/navigation.js +7 -0
- package/dist/reports/src/object-io.d.ts +11 -4
- package/dist/reports/src/object-io.js +4 -4
- package/dist/reports/src/object-store-conformance.d.ts +27 -0
- package/dist/reports/src/object-store-conformance.js +87 -0
- package/dist/reports/src/object-store.d.ts +33 -24
- package/dist/reports/src/object-store.js +17 -24
- package/dist/reports/src/parse-with.d.ts +16 -0
- package/dist/reports/src/parse-with.js +14 -0
- package/dist/reports/src/performance-part.d.ts +25 -1
- package/dist/reports/src/performance-part.js +15 -1
- package/dist/reports/src/performance.d.ts +22 -2
- package/dist/reports/src/performance.js +12 -2
- package/dist/reports/src/persist.d.ts +12 -3
- package/dist/reports/src/persist.js +12 -3
- package/dist/reports/src/phone.d.ts +18 -1
- package/dist/reports/src/phone.js +18 -1
- package/dist/reports/src/protocol.d.ts +26 -4
- package/dist/reports/src/protocol.js +15 -4
- package/dist/reports/src/publication.d.ts +21 -1
- package/dist/reports/src/publication.js +21 -1
- package/dist/reports/src/publish-checks.d.ts +35 -0
- package/dist/reports/src/publish-checks.js +122 -0
- package/dist/reports/src/publish.d.ts +42 -9
- package/dist/reports/src/publish.js +36 -7
- package/dist/reports/src/read.d.ts +46 -16
- package/dist/reports/src/read.js +38 -14
- package/dist/reports/src/recording-bundle.d.ts +13 -2
- package/dist/reports/src/recording-bundle.js +4 -2
- package/dist/reports/src/recording-keys.d.ts +15 -1
- package/dist/reports/src/recording-keys.js +15 -1
- package/dist/reports/src/remote-report.d.ts +28 -7
- package/dist/reports/src/remote-report.js +15 -7
- package/dist/reports/src/report-cli.d.ts +51 -0
- package/dist/reports/src/report-cli.js +486 -0
- package/dist/reports/src/report-index.d.ts +99 -0
- package/dist/reports/src/report-index.js +337 -0
- package/dist/reports/src/report-loader.d.ts +10 -0
- package/dist/reports/src/report-loader.js +6 -0
- package/dist/reports/src/report-parts.d.ts +29 -3
- package/dist/reports/src/report-parts.js +37 -3
- package/dist/reports/src/report-status.d.ts +48 -0
- package/dist/reports/src/report-status.js +156 -0
- package/dist/reports/src/report-types.d.ts +73 -0
- package/dist/reports/src/report-types.js +3 -0
- package/dist/reports/src/reporter.d.ts +67 -0
- package/dist/reports/src/reporter.js +128 -0
- package/dist/reports/src/reports-explorer-route.d.ts +541 -0
- package/dist/reports/src/reports-explorer-route.js +11 -0
- package/dist/reports/src/reports-list-route.d.ts +9 -0
- package/dist/reports/src/reports-list-route.js +9 -0
- package/dist/reports/src/reports-route.d.ts +561 -22
- package/dist/reports/src/reports-route.js +11 -3
- package/dist/reports/src/reports-store.d.ts +19 -2
- package/dist/reports/src/reports-store.js +10 -2
- package/dist/reports/src/resilient-cache.d.ts +14 -1
- package/dist/reports/src/resilient-cache.js +8 -1
- package/dist/reports/src/retention.d.ts +17 -1
- package/dist/reports/src/retention.js +5 -1
- package/dist/reports/src/run-bundle.d.ts +63 -0
- package/dist/reports/src/run-bundle.js +159 -0
- package/dist/reports/src/s3-store.d.ts +79 -0
- package/dist/reports/src/s3-store.js +295 -0
- package/dist/reports/src/scenario.d.ts +14 -4
- package/dist/reports/src/scenario.js +5 -4
- package/dist/reports/src/sigv4.d.ts +72 -0
- package/dist/reports/src/sigv4.js +96 -0
- package/dist/reports/src/snapshot-bundle.d.ts +24 -1
- package/dist/reports/src/snapshot-bundle.js +27 -2
- package/dist/reports/src/snapshot-surface.d.ts +20 -9
- package/dist/reports/src/snapshot-surface.js +32 -19
- package/dist/reports/src/source-content.d.ts +23 -0
- package/dist/reports/src/source-content.js +30 -1
- package/dist/reports/src/source-entries.d.ts +13 -0
- package/dist/reports/src/source-entries.js +6 -0
- package/dist/reports/src/source-part.d.ts +15 -0
- package/dist/reports/src/source-part.js +8 -0
- package/dist/reports/src/source-reader.d.ts +11 -0
- package/dist/reports/src/source-reader.js +5 -0
- package/dist/reports/src/spec-evidence.d.ts +12 -2
- package/dist/reports/src/spec-evidence.js +6 -2
- package/dist/reports/src/spec-inventory.d.ts +15 -1
- package/dist/reports/src/spec-inventory.js +22 -1
- package/dist/reports/src/spec-link.d.ts +28 -0
- package/dist/reports/src/spec-link.js +70 -0
- package/dist/reports/src/spec-set.d.ts +63 -0
- package/dist/reports/src/spec-set.js +177 -0
- package/dist/reports/src/status.d.ts +10 -0
- package/dist/reports/src/status.js +5 -0
- package/dist/reports/src/store-prefix.d.ts +28 -0
- package/dist/reports/src/store-prefix.js +22 -0
- package/dist/reports/src/store-url.d.ts +89 -0
- package/dist/reports/src/store-url.js +160 -0
- package/dist/reports/src/test-catalog-entity.d.ts +11 -0
- package/dist/reports/src/test-catalog-entity.js +6 -0
- package/dist/reports/src/types.d.ts +6 -1
- package/dist/reports/src/use-report-nav.d.ts +35 -0
- package/dist/reports/src/use-report-nav.js +12 -0
- package/dist/runners/src/codecept.d.ts +14 -0
- package/dist/runners/src/codecept.js +16 -0
- package/dist/runners/src/jest.d.ts +50 -0
- package/dist/runners/src/jest.js +52 -0
- package/dist/runners/src/mocha.d.ts +40 -0
- package/dist/runners/src/mocha.js +39 -0
- package/dist/runners/src/node-test.d.ts +28 -0
- package/dist/runners/src/node-test.js +44 -0
- package/dist/runners/src/publish.d.ts +30 -0
- package/dist/runners/src/publish.js +68 -0
- package/dist/schema/src/api.d.ts +800 -0
- package/dist/schema/src/api.js +201 -0
- package/dist/schema/src/cli-results.d.ts +210 -0
- package/dist/schema/src/cli-results.js +124 -0
- package/dist/schema/src/common.d.ts +53 -0
- package/dist/schema/src/common.js +42 -0
- package/dist/schema/src/index.d.ts +16 -0
- package/dist/schema/src/index.js +16 -0
- package/dist/schema/src/layer.d.ts +2273 -0
- package/dist/schema/src/layer.js +462 -0
- package/dist/schema/src/live-run.d.ts +123 -0
- package/dist/schema/src/live-run.js +30 -0
- package/dist/schema/src/manifest.d.ts +97 -2
- package/dist/schema/src/manifest.js +84 -0
- package/dist/schema/src/project.d.ts +20 -0
- package/dist/schema/src/project.js +10 -0
- package/dist/schema/src/report-sources.d.ts +29 -0
- package/dist/schema/src/report-sources.js +27 -1
- package/dist/schema/src/runs-index.d.ts +17 -1
- package/dist/schema/src/runs-index.js +15 -0
- package/dist/schema/src/server-config.d.ts +177 -0
- package/dist/schema/src/server-config.js +85 -0
- package/dist/schema/src/spec.d.ts +69 -0
- package/dist/schema/src/spec.js +58 -0
- package/dist/server/src/app.d.ts +72 -0
- package/dist/server/src/app.js +557 -0
- package/dist/server/src/auth.d.ts +41 -0
- package/dist/server/src/auth.js +59 -0
- package/dist/server/src/config.d.ts +83 -0
- package/dist/server/src/config.js +204 -0
- package/dist/server/src/dev.d.ts +12 -0
- package/dist/server/src/dev.js +89 -0
- package/dist/server/src/events.d.ts +29 -0
- package/dist/server/src/events.js +101 -0
- package/dist/server/src/host.d.ts +30 -0
- package/dist/server/src/host.js +115 -0
- package/dist/server/src/index.d.ts +18 -0
- package/dist/server/src/index.js +18 -0
- package/dist/server/src/live-runs.d.ts +52 -0
- package/dist/server/src/live-runs.js +229 -0
- package/dist/server/src/llms.d.ts +19 -0
- package/dist/server/src/llms.js +63 -0
- package/dist/server/src/main.d.ts +30 -0
- package/dist/server/src/main.js +114 -0
- package/dist/server/src/openapi.d.ts +56 -0
- package/dist/server/src/openapi.js +438 -0
- package/dist/server/src/problem.d.ts +8 -0
- package/dist/server/src/problem.js +39 -0
- package/dist/server/src/projects.d.ts +3 -0
- package/dist/server/src/projects.js +154 -0
- package/dist/server/src/settings-page.d.ts +9 -0
- package/dist/server/src/settings-page.js +93 -0
- package/dist/server/src/settings.d.ts +23 -0
- package/dist/server/src/settings.js +72 -0
- package/dist/server/src/site.d.ts +15 -0
- package/dist/server/src/site.js +78 -0
- package/dist/server/src/store-api.d.ts +28 -0
- package/dist/server/src/store-api.js +180 -0
- package/dist/server/src/validate.d.ts +71 -0
- package/dist/server/src/validate.js +197 -0
- package/dist/vitest/src/index.d.ts +27 -5
- package/dist/vitest/src/index.js +18 -5
- package/dist/webdriver/src/driver.d.ts +63 -11
- package/dist/webdriver/src/driver.js +57 -6
- package/dist/webdriver/src/index.d.ts +8 -0
- package/dist/webdriver/src/index.js +1 -0
- package/dist/webdriver/src/protocol.d.ts +75 -6
- package/dist/webdriver/src/protocol.js +79 -6
- package/docs/agents.md +137 -0
- package/docs/deploying.md +345 -0
- package/docs/dogfooding.md +175 -0
- package/docs/extending.md +242 -0
- package/docs/integrating.md +485 -0
- package/docs/reference/bun.md +141 -0
- package/docs/reference/cli.md +148 -0
- package/docs/reference/core.md +2432 -0
- package/docs/reference/detox.md +150 -0
- package/docs/reference/http-api.md +799 -0
- package/docs/reference/playwright.md +303 -0
- package/docs/reference/reports.md +7152 -0
- package/docs/reference/runners.md +169 -0
- package/docs/reference/schema.md +1762 -0
- package/docs/reference/server.md +825 -0
- package/docs/reference/vitest.md +45 -0
- package/docs/reference/webdriver.md +330 -0
- package/docs/report-backends.md +144 -0
- package/docs/report-contract.md +400 -0
- package/docs/reporting-issues.md +160 -0
- package/docs/runners.md +69 -0
- package/docs/server.md +321 -0
- package/docs/statecharts.md +643 -0
- package/llms-full.txt +4114 -0
- package/llms.txt +49 -0
- package/package.json +61 -11
- package/packages/core/README.md +2 -1
- package/packages/reports/README.md +65 -0
- package/packages/reports/STORAGE.md +4 -0
- package/packages/runners/README.md +6 -0
- package/packages/runners/compat/codecept.cjs +2 -0
- package/packages/runners/compat/jest.cjs +2 -0
- package/packages/runners/compat/mocha.cjs +2 -0
- package/packages/schema/README.md +14 -0
- package/packages/schema/schemas/atlas-change.schema.json +4 -0
- package/packages/schema/schemas/check-detail.schema.json +1032 -0
- package/packages/schema/schemas/checks-result.schema.json +26 -0
- package/packages/schema/schemas/command-error.schema.json +35 -0
- package/packages/schema/schemas/doctor-result.schema.json +60 -0
- package/packages/schema/schemas/effective-delta.schema.json +490 -0
- package/packages/schema/schemas/effective-evidence.schema.json +471 -0
- package/packages/schema/schemas/evidence-page.schema.json +271 -0
- package/packages/schema/schemas/hello-event.schema.json +26 -0
- package/packages/schema/schemas/init-plan.schema.json +347 -0
- package/packages/schema/schemas/layer-rows.schema.json +199 -0
- package/packages/schema/schemas/layer.schema.json +2888 -0
- package/packages/schema/schemas/live-run-start.schema.json +42 -0
- package/packages/schema/schemas/live-run-update.schema.json +93 -0
- package/packages/schema/schemas/live-run.schema.json +225 -0
- package/packages/schema/schemas/merge-result.schema.json +40 -0
- package/packages/schema/schemas/outline.schema.json +62 -0
- package/packages/schema/schemas/problem.schema.json +37 -0
- package/packages/schema/schemas/project-slug.schema.json +8 -0
- package/packages/schema/schemas/project.schema.json +22 -0
- package/packages/schema/schemas/projects.schema.json +33 -0
- package/packages/schema/schemas/publish-checks.schema.json +1413 -0
- package/packages/schema/schemas/publish-result.schema.json +168 -0
- package/packages/schema/schemas/ready.schema.json +68 -0
- package/packages/schema/schemas/report-event.schema.json +100 -0
- package/packages/schema/schemas/report-status.schema.json +370 -0
- package/packages/schema/schemas/server-config.schema.json +208 -0
- package/packages/schema/schemas/server-editable-settings.schema.json +132 -0
- package/packages/schema/schemas/server-info.schema.json +114 -0
- package/packages/schema/schemas/server-settings-snapshot.schema.json +285 -0
- package/packages/schema/schemas/spec-set.schema.json +103 -0
- package/packages/schema/schemas/store-listing.schema.json +29 -0
- package/packages/schema/schemas/store-write-result.schema.json +28 -0
- package/packages/schema/schemas/stream-result.schema.json +59 -0
- package/packages/server/README.md +43 -0
- package/packages/server/openapi.json +11021 -0
- package/packages/visualizer/README.md +32 -11
- package/packages/visualizer/bin/atlas-client.mjs +176 -0
- package/packages/visualizer/bin/atlas-visualizer.mjs +2 -175
- package/packages/visualizer/src/app/AppShell.tsx +32 -16
- package/packages/visualizer/src/app/report-sources.tsx +7 -4
- package/packages/visualizer/src/app/router.tsx +3 -3
- package/packages/visualizer/src/components/ActivityIndicator.module.css +4 -0
- package/packages/visualizer/src/components/ActivityIndicator.tsx +6 -0
- package/packages/visualizer/src/components/Header.module.css +101 -192
- package/packages/visualizer/src/components/Header.tsx +88 -13
- package/packages/visualizer/src/components/ProjectPicker.tsx +64 -0
- package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
- package/packages/visualizer/src/data/adapters/index.ts +10 -1
- package/packages/visualizer/src/data/adapters/report-backend.test.ts +3 -2
- package/packages/visualizer/src/data/adapters/report-backend.ts +1 -1
- package/packages/visualizer/src/data/queries.ts +14 -7
- package/packages/visualizer/src/evidence/api.ts +16 -3
- package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
- package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
- package/packages/visualizer/src/evidence/check-detail.tsx +21 -4
- package/packages/visualizer/src/evidence/document-page.tsx +6 -2
- package/packages/visualizer/src/evidence/evidence-context.tsx +79 -12
- package/packages/visualizer/src/evidence/evidence.module.css +11 -1
- package/packages/visualizer/src/evidence/feature-page.tsx +7 -3
- package/packages/visualizer/src/evidence/model-page.tsx +6 -2
- package/packages/visualizer/src/evidence/project-home.tsx +45 -0
- package/packages/visualizer/src/evidence/recording-page.tsx +4 -1
- package/packages/visualizer/src/evidence/spec-index.tsx +2 -1
- package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
- package/packages/visualizer/src/evidence/test-filter.tsx +21 -0
- package/packages/visualizer/src/find/Finder.tsx +1 -7
- package/packages/visualizer/src/find/open.ts +4 -0
- package/packages/visualizer/src/live/live-query.ts +21 -0
- package/packages/visualizer/src/live/live-runs.tsx +69 -0
- package/packages/visualizer/src/live/live-stream.ts +225 -0
- package/packages/visualizer/src/live/report-progress.ts +62 -0
- package/packages/visualizer/src/live/test-progress.tsx +34 -0
- package/packages/visualizer/src/runtime.ts +35 -0
- package/packages/visualizer/src/state/ui-store.ts +3 -2
- package/packages/visualizer/src/views/ChartPage.tsx +2 -0
- package/packages/visualizer/src/views/ChartTests.tsx +43 -0
- package/packages/visualizer/src/views/SidePanel.tsx +2 -0
- package/packages/visualizer/vite/atlas-files.test.ts +4 -4
- package/packages/visualizer/vite/atlas-files.ts +1 -5
- package/packages/visualizer/vite/atlas-server-proxy.ts +37 -0
- package/packages/visualizer/vite.config.ts +11 -3
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Incremental report index
|
|
3
|
+
*
|
|
4
|
+
* `ReportIndex` is the report, maintained as layers arrive. Each immutable layer is read once and folded into the winning
|
|
5
|
+
* result of every slot it fills, then dropped unless it still holds a winner, so memory follows the number of checks and not
|
|
6
|
+
* the age of the project. The winner rule is a total order, so folding is exact in any order and equals combining every layer
|
|
7
|
+
* from scratch (a test proves it over random partial runs). It also keeps what each layer sends (rows, JSON, a digest) until
|
|
8
|
+
* that layer changes, so taking in a result costs one layer, answers viewers a version hash, a cached JSON text and a delta,
|
|
9
|
+
* and tells listeners what arrived.
|
|
10
|
+
*
|
|
11
|
+
* @see docs/report-contract.md
|
|
12
|
+
* @see docs/server.md#performance
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { eachLimited } from './completion-order.js';
|
|
16
|
+
import { LAYER_INDEX, layerSummary, readLayer } from './layer-store.js';
|
|
17
|
+
import { layerEvidence, layerResultKeys, replaces } from './layer-merge.js';
|
|
18
|
+
import { readUtf8 } from './object-io.js';
|
|
19
|
+
import { isPaged } from './object-store.js';
|
|
20
|
+
const RETRY_FAILED_MS = 5 * 60_000;
|
|
21
|
+
/** Versions a viewer may still be holding, remembered so it can be sent only what changed. */
|
|
22
|
+
const REMEMBERED_VERSIONS = 256;
|
|
23
|
+
const REMEMBERED_REMOVALS = 4096;
|
|
24
|
+
/**
|
|
25
|
+
* The report, maintained as layers arrive. Each layer is read once and folded into the winning result
|
|
26
|
+
* of every slot it fills, then dropped unless it still holds a winner. Because the winner rule is a
|
|
27
|
+
* total order, folding is exact in any order, so this equals combining every layer from scratch, with
|
|
28
|
+
* memory proportional to the number of checks and not to how long the project has existed.
|
|
29
|
+
*
|
|
30
|
+
* It reads only immutable layers (`layers/index/…`), which is what makes "read once" correct.
|
|
31
|
+
*/
|
|
32
|
+
export class ReportIndex {
|
|
33
|
+
#backends;
|
|
34
|
+
#concurrency;
|
|
35
|
+
#now;
|
|
36
|
+
#winners = new Map();
|
|
37
|
+
#held = new Map();
|
|
38
|
+
/** The smallest backend id a layer was found in, so attribution does not depend on read order. */
|
|
39
|
+
#attribution = new Map();
|
|
40
|
+
#folded = new Set();
|
|
41
|
+
#failed = new Map();
|
|
42
|
+
#available = new Map();
|
|
43
|
+
/** Counts every change to what the report shows. A layer remembers the count at which it last changed. */
|
|
44
|
+
#rev = 0;
|
|
45
|
+
/** The counts at which versions were seen, oldest first. */
|
|
46
|
+
#versions = new Map();
|
|
47
|
+
/** Layers that stopped holding any winner, and when. Older removals are forgotten; `#forgotten` says how far. */
|
|
48
|
+
#removed = [];
|
|
49
|
+
#forgotten = 0;
|
|
50
|
+
#refreshing = null;
|
|
51
|
+
#timer = null;
|
|
52
|
+
#refreshedAt = 0;
|
|
53
|
+
#cached = null;
|
|
54
|
+
#include;
|
|
55
|
+
#fullListEvery;
|
|
56
|
+
#listeners = new Set();
|
|
57
|
+
#added = [];
|
|
58
|
+
#announced = '';
|
|
59
|
+
#passes = 0;
|
|
60
|
+
/** Layers handed over by an uploader, read one at a time in the order they came. */
|
|
61
|
+
#accepting = Promise.resolve();
|
|
62
|
+
constructor(backends, options = {}) {
|
|
63
|
+
this.#backends = backends;
|
|
64
|
+
this.#include = options.include ?? (() => true);
|
|
65
|
+
this.#fullListEvery = Math.max(1, options.fullListEvery ?? 30);
|
|
66
|
+
this.#concurrency = options.concurrency ?? 64;
|
|
67
|
+
this.#now = options.now ?? Date.now;
|
|
68
|
+
}
|
|
69
|
+
/** Read what is new in every backend and fold it in. Resolves true when the report changed. Calls overlap safely. */
|
|
70
|
+
refresh() {
|
|
71
|
+
this.#refreshing ??= this.#refresh().finally(() => { this.#refreshing = null; });
|
|
72
|
+
return this.#refreshing;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A refresh that surely starts after this call: one already under way may have looked before whatever
|
|
76
|
+
* made the caller ask. Used when something was just written.
|
|
77
|
+
*/
|
|
78
|
+
async refreshNow() {
|
|
79
|
+
const running = this.#refreshing;
|
|
80
|
+
const earlier = running ? await running.catch(() => false) : false;
|
|
81
|
+
return (await this.refresh()) || earlier;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Take in a layer an uploader has just written, from its catalog entry, without listing anything. Entries
|
|
85
|
+
* are read one after another; one already known is ignored. The regular refresh still reconciles the rest.
|
|
86
|
+
*/
|
|
87
|
+
accept(backendId, indexKey, known) {
|
|
88
|
+
const backend = this.#backends.find(item => item.id === backendId);
|
|
89
|
+
if (!backend)
|
|
90
|
+
return Promise.resolve();
|
|
91
|
+
this.#accepting = this.#accepting.then(async () => {
|
|
92
|
+
const mark = `${backend.id}|${indexKey}`;
|
|
93
|
+
if (this.#folded.has(mark))
|
|
94
|
+
return;
|
|
95
|
+
await this.#load(backend, indexKey, known);
|
|
96
|
+
this.#emit();
|
|
97
|
+
}).catch(() => { });
|
|
98
|
+
return this.#accepting;
|
|
99
|
+
}
|
|
100
|
+
/** Be told when the report changes. Returns how to stop. */
|
|
101
|
+
onChange(listener) {
|
|
102
|
+
this.#listeners.add(listener);
|
|
103
|
+
return () => { this.#listeners.delete(listener); };
|
|
104
|
+
}
|
|
105
|
+
#emit() {
|
|
106
|
+
if (!this.#listeners.size && !this.#added.length)
|
|
107
|
+
return;
|
|
108
|
+
const { version, totalLayers } = this.snapshot();
|
|
109
|
+
// Say it once per distinct report, and whenever layers arrived (they are news even if nothing they hold wins).
|
|
110
|
+
if (version === this.#announced && !this.#added.length)
|
|
111
|
+
return;
|
|
112
|
+
this.#announced = version;
|
|
113
|
+
const added = this.#added.sort((a, b) => b.recordedAtMs - a.recordedAtMs || (a.id < b.id ? -1 : 1));
|
|
114
|
+
this.#added = [];
|
|
115
|
+
for (const listener of this.#listeners) {
|
|
116
|
+
try {
|
|
117
|
+
listener({ version, added, totalLayers });
|
|
118
|
+
}
|
|
119
|
+
catch { /* a listener that fails does not stop the others */ }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Keep refreshing in the background. The first call returns once the first pass has finished. */
|
|
123
|
+
async start(intervalMs) {
|
|
124
|
+
await this.refresh();
|
|
125
|
+
this.#timer = setInterval(() => { void this.refresh().catch(() => { }); }, intervalMs);
|
|
126
|
+
this.#timer.unref?.();
|
|
127
|
+
}
|
|
128
|
+
/** Stops the background refresh started by `start`. Safe to call twice. */
|
|
129
|
+
stop() {
|
|
130
|
+
if (this.#timer)
|
|
131
|
+
clearInterval(this.#timer);
|
|
132
|
+
this.#timer = null;
|
|
133
|
+
}
|
|
134
|
+
/** True once a first pass over the backends has finished, so `snapshot` is a report and not an empty guess. */
|
|
135
|
+
get ready() {
|
|
136
|
+
return this.#refreshedAt > 0;
|
|
137
|
+
}
|
|
138
|
+
async #refresh() {
|
|
139
|
+
const before = this.#rev;
|
|
140
|
+
const fresh = [];
|
|
141
|
+
// The whole catalog on the first pass and now and then; otherwise only its newest end, where new layers sort.
|
|
142
|
+
const full = this.#passes++ % this.#fullListEvery === 0;
|
|
143
|
+
await Promise.all(this.#backends.map(async (backend) => {
|
|
144
|
+
try {
|
|
145
|
+
const consider = (key) => { if (!this.#folded.has(`${backend.id}|${key}`) && !this.#recentlyFailed(`${backend.id}|${key}`))
|
|
146
|
+
fresh.push({ backend, key }); };
|
|
147
|
+
if (!full && isPaged(backend.store)) {
|
|
148
|
+
// Newest first: pages of keys we already know mean nothing newer is behind them.
|
|
149
|
+
for (let after;;) {
|
|
150
|
+
const page = await backend.store.listPage(LAYER_INDEX, { ...(after === undefined ? {} : { after }), limit: 1000 });
|
|
151
|
+
let unknown = 0;
|
|
152
|
+
for (const key of page.keys) {
|
|
153
|
+
if (!this.#folded.has(`${backend.id}|${key}`))
|
|
154
|
+
unknown += 1;
|
|
155
|
+
consider(key);
|
|
156
|
+
}
|
|
157
|
+
if (page.next === null || unknown < page.keys.length)
|
|
158
|
+
break;
|
|
159
|
+
after = page.next;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
for (const key of await backend.store.listKeys(LAYER_INDEX))
|
|
164
|
+
consider(key);
|
|
165
|
+
}
|
|
166
|
+
this.#available.set(backend.id, true);
|
|
167
|
+
this.#failed.delete(`${backend.id}|`);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
this.#available.set(backend.id, false);
|
|
171
|
+
this.#failed.set(`${backend.id}|`, { at: this.#now(), message: `${backend.label ?? backend.id} is unavailable: ${error instanceof Error ? error.message : String(error)}` });
|
|
172
|
+
}
|
|
173
|
+
}));
|
|
174
|
+
// Oldest first or newest first gives the same report; newest first shows recent work soonest.
|
|
175
|
+
fresh.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : a.backend.id < b.backend.id ? -1 : 1));
|
|
176
|
+
await eachLimited(fresh, this.#concurrency, ({ backend, key }) => this.#load(backend, key));
|
|
177
|
+
this.#refreshedAt = this.#now();
|
|
178
|
+
this.#emit();
|
|
179
|
+
return this.#rev !== before;
|
|
180
|
+
}
|
|
181
|
+
async #load(backend, key, known) {
|
|
182
|
+
const mark = `${backend.id}|${key}`;
|
|
183
|
+
try {
|
|
184
|
+
let layer;
|
|
185
|
+
let id;
|
|
186
|
+
if (known && key.endsWith(`-${known.id}.json`)) {
|
|
187
|
+
// The caller has just written and read this layer: its entry names it, so there is nothing to look up.
|
|
188
|
+
id = known.id;
|
|
189
|
+
layer = known;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const raw = await readUtf8(backend.store, key);
|
|
193
|
+
id = raw ? JSON.parse(raw).id : undefined;
|
|
194
|
+
layer = id ? await readLayer(backend.store, id) : null;
|
|
195
|
+
}
|
|
196
|
+
if (!id || !layer)
|
|
197
|
+
throw new Error('an indexed layer is missing');
|
|
198
|
+
this.#fold(layerEvidence(layerSummary(layer), backend.id), backend.id);
|
|
199
|
+
this.#folded.add(mark);
|
|
200
|
+
this.#failed.delete(mark);
|
|
201
|
+
this.#refreshedAt ||= this.#now();
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
this.#failed.set(mark, { at: this.#now(), message: `${backend.label ?? backend.id}: a layer could not be read, so its outcome is unknown (${error instanceof Error ? error.message : String(error)})` });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
#recentlyFailed(mark) {
|
|
208
|
+
const failure = this.#failed.get(mark);
|
|
209
|
+
return failure !== undefined && this.#now() - failure.at < RETRY_FAILED_MS;
|
|
210
|
+
}
|
|
211
|
+
#fold(evidence, backend) {
|
|
212
|
+
const id = evidence.id;
|
|
213
|
+
const known = this.#attribution.get(id);
|
|
214
|
+
if (known !== undefined) {
|
|
215
|
+
// The same immutable layer in another backend: one layer, attributed to the backend that sorts first.
|
|
216
|
+
if (backend < known) {
|
|
217
|
+
this.#attribution.set(id, backend);
|
|
218
|
+
const held = this.#held.get(id);
|
|
219
|
+
if (held)
|
|
220
|
+
this.#touch(held);
|
|
221
|
+
else
|
|
222
|
+
this.#rev += 1;
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.#attribution.set(id, backend);
|
|
227
|
+
if (!this.#include(evidence.layer))
|
|
228
|
+
return;
|
|
229
|
+
this.#added.push({ ...evidence.layer, backend });
|
|
230
|
+
const wins = new Set();
|
|
231
|
+
const keyOf = layerResultKeys(evidence.layer);
|
|
232
|
+
for (const row of evidence.rows) {
|
|
233
|
+
const key = keyOf(row);
|
|
234
|
+
const result = { key, slot: '', layer: evidence.layer, row, parameters: evidence.layer.parameters, warning: null };
|
|
235
|
+
const prior = this.#winners.get(key);
|
|
236
|
+
if (prior && !replaces(result, prior))
|
|
237
|
+
continue;
|
|
238
|
+
if (prior)
|
|
239
|
+
this.#release(prior.layer.id, key);
|
|
240
|
+
this.#winners.set(key, result);
|
|
241
|
+
wins.add(key);
|
|
242
|
+
}
|
|
243
|
+
if (wins.size) {
|
|
244
|
+
this.#held.set(id, { evidence, wins, rev: ++this.#rev, entry: null, json: null, digest: null });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** What this layer sends has changed. */
|
|
248
|
+
#touch(held) {
|
|
249
|
+
held.rev = ++this.#rev;
|
|
250
|
+
held.entry = held.json = held.digest = null;
|
|
251
|
+
}
|
|
252
|
+
#release(layerId, key) {
|
|
253
|
+
const held = this.#held.get(layerId);
|
|
254
|
+
if (!held)
|
|
255
|
+
return;
|
|
256
|
+
held.wins.delete(key);
|
|
257
|
+
if (held.wins.size) {
|
|
258
|
+
this.#touch(held);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
this.#held.delete(layerId);
|
|
262
|
+
this.#removed.push({ id: layerId, rev: ++this.#rev });
|
|
263
|
+
if (this.#removed.length > REMEMBERED_REMOVALS)
|
|
264
|
+
this.#forgotten = this.#removed.splice(0, this.#removed.length - REMEMBERED_REMOVALS).at(-1).rev;
|
|
265
|
+
}
|
|
266
|
+
#entryOf(held) {
|
|
267
|
+
if (!held.entry) {
|
|
268
|
+
const id = held.evidence.id;
|
|
269
|
+
const rows = [...held.wins].map(key => this.#winners.get(key).row);
|
|
270
|
+
held.entry = { id, layer: { ...held.evidence.layer, backend: this.#attribution.get(id) ?? held.evidence.layer.backend }, rows, fingerprints: held.evidence.fingerprints };
|
|
271
|
+
}
|
|
272
|
+
return held.entry;
|
|
273
|
+
}
|
|
274
|
+
/** The current report, ready to send. Built once per change, and only the layers that changed are rebuilt. */
|
|
275
|
+
snapshot() {
|
|
276
|
+
return this.#current().value;
|
|
277
|
+
}
|
|
278
|
+
#current() {
|
|
279
|
+
const backends = this.#backends.map(backend => ({ id: backend.id, label: backend.label ?? backend.id, live: false, available: this.#available.get(backend.id) ?? false })).sort((a, b) => (a.id < b.id ? -1 : 1));
|
|
280
|
+
const notices = [...new Set([...this.#failed.values()].map(failure => failure.message))].sort();
|
|
281
|
+
const key = [this.#rev, this.#folded.size, backends.map(item => (item.available ? 1 : 0)).join(''), notices.join('\n')].join('|');
|
|
282
|
+
if (this.#cached?.key === key)
|
|
283
|
+
return this.#cached;
|
|
284
|
+
const held = [...this.#held.values()];
|
|
285
|
+
held.sort((a, b) => b.evidence.layer.recordedAtMs - a.evidence.layer.recordedAtMs || (a.evidence.id < b.evidence.id ? -1 : a.evidence.id > b.evidence.id ? 1 : 0));
|
|
286
|
+
const layers = held.map(item => this.#entryOf(item));
|
|
287
|
+
// A layer's rows are fixed by its id and which of its slots it holds, so a digest of those says when the report differs.
|
|
288
|
+
const hash = createHash('sha256');
|
|
289
|
+
for (const item of held) {
|
|
290
|
+
item.digest ??= `${item.evidence.id}|${this.#attribution.get(item.evidence.id) ?? ''}|${[...item.wins].join('\u0001')}`;
|
|
291
|
+
hash.update(item.digest).update('\n');
|
|
292
|
+
}
|
|
293
|
+
hash.update(JSON.stringify([backends, notices, this.#attribution.size]));
|
|
294
|
+
const version = hash.digest('hex').slice(0, 20);
|
|
295
|
+
if (!this.#versions.has(version)) {
|
|
296
|
+
this.#versions.set(version, this.#rev);
|
|
297
|
+
if (this.#versions.size > REMEMBERED_VERSIONS)
|
|
298
|
+
this.#versions.delete(this.#versions.keys().next().value);
|
|
299
|
+
}
|
|
300
|
+
this.#cached = { key, value: { version, layers, backends, notices, totalLayers: this.#attribution.size }, json: null, held };
|
|
301
|
+
return this.#cached;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The report as JSON text: exactly what `JSON.stringify({ ...snapshot(), active: [] })` writes, put together from
|
|
305
|
+
* the text of each layer, which is written once. `active` is what a store without live runs always sends: none.
|
|
306
|
+
*/
|
|
307
|
+
json() {
|
|
308
|
+
const current = this.#current();
|
|
309
|
+
if (current.json === null) {
|
|
310
|
+
const { value, held } = current;
|
|
311
|
+
current.json = `{"version":${JSON.stringify(value.version)},"layers":[${held.map(item => (item.json ??= JSON.stringify(this.#entryOf(item)))).join(',')}],"backends":${JSON.stringify(value.backends)},"notices":${JSON.stringify(value.notices)},"totalLayers":${value.totalLayers},"active":[]}`;
|
|
312
|
+
}
|
|
313
|
+
return { version: current.value.version, text: current.json };
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* What differs between the report a viewer holds (named by its version) and the current one: the layers that
|
|
317
|
+
* changed in full, and the ids of those that no longer hold a winner. Null when that version is not remembered,
|
|
318
|
+
* and the viewer should ask for the whole report. See `applyEffectiveDelta`.
|
|
319
|
+
*/
|
|
320
|
+
delta(since) {
|
|
321
|
+
const current = this.#current();
|
|
322
|
+
const base = this.#versions.get(since);
|
|
323
|
+
if (base === undefined || base < this.#forgotten)
|
|
324
|
+
return null;
|
|
325
|
+
const changed = current.held.filter(item => item.rev > base);
|
|
326
|
+
const gone = [...new Set(this.#removed.filter(item => item.rev > base && !this.#held.has(item.id)).map(item => item.id))];
|
|
327
|
+
const { value } = current;
|
|
328
|
+
return {
|
|
329
|
+
version: value.version,
|
|
330
|
+
text: `{"version":${JSON.stringify(value.version)},"base":${JSON.stringify(since)},"delta":true,"layers":[${changed.map(item => (item.json ??= JSON.stringify(this.#entryOf(item)))).join(',')}],"removed":${JSON.stringify(gone)},"backends":${JSON.stringify(value.backends)},"notices":${JSON.stringify(value.notices)},"totalLayers":${value.totalLayers},"active":[]}`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/** For `/readyz` and logs. */
|
|
334
|
+
stats() {
|
|
335
|
+
return { backends: this.#backends.length, layers: this.#attribution.size, slots: this.#winners.size, holding: this.#held.size, failed: this.#failed.size, refreshedAt: this.#refreshedAt };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Test loading state
|
|
3
|
+
*
|
|
4
|
+
* The state of loading one recorded test into the viewer, and the interface a report gives the viewer to load tests on
|
|
5
|
+
* demand. Types only.
|
|
6
|
+
*/
|
|
7
|
+
/** Where loading a test is: `ready`, `loading` (with how many frame files have arrived and how many there are, when known), `missing`, or `error` with a message to show. */
|
|
1
8
|
export type TestLoad = {
|
|
2
9
|
readonly kind: 'ready';
|
|
3
10
|
} | {
|
|
@@ -16,7 +23,10 @@ export type TestLoad = {
|
|
|
16
23
|
* (a standalone HTML file).
|
|
17
24
|
*/
|
|
18
25
|
export type ReportTestLoader = {
|
|
26
|
+
/** Starts loading a test's timeline if it has not started (or failed before). Returns at once. */
|
|
19
27
|
readonly load: (testId: string) => void;
|
|
28
|
+
/** Starts fetching one frame file of a test if it is not held or already being fetched. */
|
|
20
29
|
readonly ensureBlob?: (testId: string, file: string) => void;
|
|
30
|
+
/** The current load state of a test; reactive where the host is. */
|
|
21
31
|
readonly state: (testId: string) => TestLoad;
|
|
22
32
|
};
|
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
import type { ObjectStore } from './object-store.js';
|
|
2
2
|
import type { EvidenceStatus, ReportPart } from './explorer-types.js';
|
|
3
|
+
/** The status of a whole part: `failed` if any check failed, else `running` if any is running, else `not-run` if it has no checks or any is `not-run`, else `passed`. */
|
|
3
4
|
export declare function partStatus(part: ReportPart): EvidenceStatus;
|
|
5
|
+
/**
|
|
6
|
+
* Throws unless `part` is well formed: a 40-hex `sha`, an `id` usable as a key segment, a non-empty `kind` and
|
|
7
|
+
* `title`, a non-negative safe-integer `recordedAtMs`, string `parameters` values, and checks with a unique non-empty
|
|
8
|
+
* `id`, non-empty `title`, a string `detail`, one of the four statuses and finite-number measurements with a name and
|
|
9
|
+
* unit. The error is `Invalid report part`, `Invalid report parameters`, `Invalid evidence check` or `Invalid evidence
|
|
10
|
+
* measurement`; it does not say which field.
|
|
11
|
+
*/
|
|
4
12
|
export declare function validatePart(part: ReportPart): void;
|
|
5
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Stores a part and publishes it as a layer. Validates first (`validatePart`, which throws and writes nothing).
|
|
15
|
+
*
|
|
16
|
+
* Each producer owns a separate key, `commits/<sha>/parts/<id>/<recordedAtMs>-<sha256 of the JSON>.json`, independent
|
|
17
|
+
* of the spec-run attempt, so republishing identical content rewrites the same key. It then records the commit as an
|
|
18
|
+
* explorer version and publishes a layer (producer `part.id`, environment from the `environment` parameter, the
|
|
19
|
+
* commit's branch and run URL when a commit record exists, the commit's spec inventory) with no captured files.
|
|
20
|
+
*/
|
|
6
21
|
export declare function publishPart(store: ObjectStore, part: ReportPart): Promise<void>;
|
|
7
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Reads the latest part of every producer for a commit (see `latestParts`), sorted by kind then id.
|
|
24
|
+
*
|
|
25
|
+
* Throws `Invalid commit` for a sha that is not 40 hex, a validation error (see `validatePart`) or a `SyntaxError` for
|
|
26
|
+
* a part that is corrupt, `Report part disappeared` when a listed part vanishes, and `Report part belongs to another
|
|
27
|
+
* commit` when a part's `sha` or `id` does not match its key. Corrupt evidence fails visibly; it is never skipped.
|
|
28
|
+
*/
|
|
8
29
|
export declare function readParts(store: ObjectStore, sha: string): Promise<readonly ReportPart[]>;
|
|
9
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Keeps one part per producer id and sorts by kind then id. The newer `recordedAtMs` wins; at the same time the more
|
|
32
|
+
* severe status wins (`failed`, then `running`, then `not-run`, then `passed`); at the same status the greater JSON
|
|
33
|
+
* text wins, so the answer is independent of write and list order. This is not the layer winner rule: here a newer
|
|
34
|
+
* unfinished part replaces an older completed one.
|
|
35
|
+
*/
|
|
10
36
|
export declare function latestParts(parts: readonly ReportPart[]): readonly ReportPart[];
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Report parts of a commit
|
|
3
|
+
*
|
|
4
|
+
* Reads, writes and validates "parts": one producer's checks for one commit, stored under
|
|
5
|
+
* `commits/<sha>/parts/<producer>/` in the older commit-keyed layout. Publishing a part also publishes it as an
|
|
6
|
+
* immutable layer, so it composes with everything else; reading parts picks the latest per producer for the explorer.
|
|
7
|
+
* A part that is corrupt or belongs to another commit fails visibly instead of being skipped.
|
|
8
|
+
*/
|
|
1
9
|
import { normalizeEnvironment } from './layer-axes.js';
|
|
2
10
|
import { publishLayer } from './layer-store.js';
|
|
3
11
|
import { readCommit } from './read.js';
|
|
@@ -7,6 +15,7 @@ import { inCompletionOrder } from './completion-order.js';
|
|
|
7
15
|
import { recordExplorerVersion } from './explorer-versions.js';
|
|
8
16
|
import { isCommitSha, isKeySegment } from './keys.js';
|
|
9
17
|
const STATUSES = new Set(['passed', 'failed', 'running', 'not-run']);
|
|
18
|
+
/** The status of a whole part: `failed` if any check failed, else `running` if any is running, else `not-run` if it has no checks or any is `not-run`, else `passed`. */
|
|
10
19
|
export function partStatus(part) {
|
|
11
20
|
const statuses = part.checks.map((check) => check.status);
|
|
12
21
|
if (statuses.includes('failed'))
|
|
@@ -17,6 +26,13 @@ export function partStatus(part) {
|
|
|
17
26
|
return 'not-run';
|
|
18
27
|
return 'passed';
|
|
19
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Throws unless `part` is well formed: a 40-hex `sha`, an `id` usable as a key segment, a non-empty `kind` and
|
|
31
|
+
* `title`, a non-negative safe-integer `recordedAtMs`, string `parameters` values, and checks with a unique non-empty
|
|
32
|
+
* `id`, non-empty `title`, a string `detail`, one of the four statuses and finite-number measurements with a name and
|
|
33
|
+
* unit. The error is `Invalid report part`, `Invalid report parameters`, `Invalid evidence check` or `Invalid evidence
|
|
34
|
+
* measurement`; it does not say which field.
|
|
35
|
+
*/
|
|
20
36
|
export function validatePart(part) {
|
|
21
37
|
if (!isCommitSha(part.sha) ||
|
|
22
38
|
!isKeySegment(part.id) ||
|
|
@@ -58,7 +74,14 @@ function partsPrefix(sha) {
|
|
|
58
74
|
throw new Error('Invalid commit');
|
|
59
75
|
return `commits/${sha}/parts/`;
|
|
60
76
|
}
|
|
61
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Stores a part and publishes it as a layer. Validates first (`validatePart`, which throws and writes nothing).
|
|
79
|
+
*
|
|
80
|
+
* Each producer owns a separate key, `commits/<sha>/parts/<id>/<recordedAtMs>-<sha256 of the JSON>.json`, independent
|
|
81
|
+
* of the spec-run attempt, so republishing identical content rewrites the same key. It then records the commit as an
|
|
82
|
+
* explorer version and publishes a layer (producer `part.id`, environment from the `environment` parameter, the
|
|
83
|
+
* commit's branch and run URL when a commit record exists, the commit's spec inventory) with no captured files.
|
|
84
|
+
*/
|
|
62
85
|
export async function publishPart(store, part) {
|
|
63
86
|
validatePart(part);
|
|
64
87
|
const json = JSON.stringify(part);
|
|
@@ -76,7 +99,13 @@ export async function publishPart(store, part) {
|
|
|
76
99
|
parameters: part.parameters ?? {}, specs: (await readInventory(store, part.sha))?.entries ?? [], parts: [part], source: null, recording: null,
|
|
77
100
|
}, (async function* () { })());
|
|
78
101
|
}
|
|
79
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* Reads the latest part of every producer for a commit (see `latestParts`), sorted by kind then id.
|
|
104
|
+
*
|
|
105
|
+
* Throws `Invalid commit` for a sha that is not 40 hex, a validation error (see `validatePart`) or a `SyntaxError` for
|
|
106
|
+
* a part that is corrupt, `Report part disappeared` when a listed part vanishes, and `Report part belongs to another
|
|
107
|
+
* commit` when a part's `sha` or `id` does not match its key. Corrupt evidence fails visibly; it is never skipped.
|
|
108
|
+
*/
|
|
80
109
|
export async function readParts(store, sha) {
|
|
81
110
|
const keys = await store.listKeys(partsPrefix(sha));
|
|
82
111
|
const parts = [];
|
|
@@ -94,7 +123,12 @@ export async function readParts(store, sha) {
|
|
|
94
123
|
parts.push(part);
|
|
95
124
|
return latestParts(parts);
|
|
96
125
|
}
|
|
97
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Keeps one part per producer id and sorts by kind then id. The newer `recordedAtMs` wins; at the same time the more
|
|
128
|
+
* severe status wins (`failed`, then `running`, then `not-run`, then `passed`); at the same status the greater JSON
|
|
129
|
+
* text wins, so the answer is independent of write and list order. This is not the layer winner rule: here a newer
|
|
130
|
+
* unfinished part replaces an older completed one.
|
|
131
|
+
*/
|
|
98
132
|
export function latestParts(parts) {
|
|
99
133
|
const latest = new Map();
|
|
100
134
|
const rank = { passed: 0, 'not-run': 1, running: 2, failed: 3 };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module The report gate
|
|
3
|
+
*
|
|
4
|
+
* Reduces a combined report to a pass or fail with reasons (`reportStatus`) and renders that result as text or
|
|
5
|
+
* markdown. The JSON of `ReportStatus` is a stable contract (`schemaVersion` 1); the text and markdown are for people
|
|
6
|
+
* and are not. The gate fails closed: no checks, a failed or still-running check, an unreadable layer or backend
|
|
7
|
+
* (a notice), a missing required environment, or (when asked) a spec with no result each make it fail.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/report-contract.md#statuses-and-the-gate
|
|
10
|
+
*/
|
|
11
|
+
import { type ReportStatus } from '../../schema/src/layer.js';
|
|
12
|
+
import { type CombinedReport } from './compose.js';
|
|
13
|
+
/** Extra conditions for the gate beyond "all checks pass". */
|
|
14
|
+
export type StatusOptions = {
|
|
15
|
+
/** Environments that must have at least one check, such as `ios,android`. A missing one fails the gate. */
|
|
16
|
+
readonly requireEnvironments?: readonly string[];
|
|
17
|
+
/** Fail when any spec of the outline has no result at all. */
|
|
18
|
+
readonly requireAllSpecs?: boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The gate. A report passes only when it has checks, none failed, none is still running, every
|
|
22
|
+
* layer and backend could be read, and every required environment is present. Unknown never passes.
|
|
23
|
+
*
|
|
24
|
+
* Returns a `ReportStatus` (schemaVersion 1): `ok` is true exactly when `reasons` is empty. Reasons name, in order: no
|
|
25
|
+
* checks (or none completed), failed checks, running checks, specs without results (only with `requireAllSpecs`),
|
|
26
|
+
* each required environment with no checks (the name is normalised, so `iOS` means `ios`), then every notice of the
|
|
27
|
+
* report (an unreadable layer or backend). `failing` lists each failed or running winner, in a fixed order that does
|
|
28
|
+
* not depend on read order; diagnostic `performance` checks are left out unless nothing else was recorded.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const status = reportStatus(await combineBackends(backends), { requireEnvironments: ['ios'] });
|
|
32
|
+
* if (!status.ok) throw new Error(status.reasons.join('\n'));
|
|
33
|
+
*
|
|
34
|
+
* @see docs/report-contract.md#atlas-report-status
|
|
35
|
+
*/
|
|
36
|
+
export declare function reportStatus(report: CombinedReport, options?: StatusOptions): ReportStatus;
|
|
37
|
+
/**
|
|
38
|
+
* Renders a status as plain text for a terminal: a `PASS`/`FAIL` line with totals, one line per environment, each
|
|
39
|
+
* failing or running check with where it ran and the first line of its detail, notices, reasons and specs with no
|
|
40
|
+
* results. Ends with a newline. For people: the layout is not a contract; parse `--format json` instead.
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatStatusText(status: ReportStatus): string;
|
|
43
|
+
/**
|
|
44
|
+
* Renders a status as GitHub-flavoured markdown for a pull request comment or `$GITHUB_STEP_SUMMARY`: a heading with
|
|
45
|
+
* totals, a table per environment, a table of failing checks (`|` and newlines in cells are escaped) and the reasons.
|
|
46
|
+
* Ends with a newline. For people: the layout is not a contract.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatStatusMarkdown(status: ReportStatus): string;
|