@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
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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;
|