@crvouga/atlas 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/README.md +178 -617
- 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 +30 -1
- package/dist/core/src/config.d.ts +70 -4
- package/dist/core/src/config.js +62 -3
- 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 +69 -16
- package/dist/core/src/run/execute.js +67 -16
- 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 +83 -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 +52 -0
- package/dist/reports/src/http-store.js +145 -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 +364 -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/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 +485 -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/schema/src/api.d.ts +799 -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 +14 -0
- package/dist/schema/src/index.js +14 -0
- package/dist/schema/src/layer.d.ts +2273 -0
- package/dist/schema/src/layer.js +462 -0
- package/dist/schema/src/manifest.d.ts +95 -0
- package/dist/schema/src/manifest.js +84 -0
- package/dist/schema/src/report-sources.d.ts +28 -0
- package/dist/schema/src/report-sources.js +25 -0
- package/dist/schema/src/runs-index.d.ts +16 -0
- package/dist/schema/src/runs-index.js +15 -0
- package/dist/schema/src/server-config.d.ts +93 -0
- package/dist/schema/src/server-config.js +73 -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 +62 -0
- package/dist/server/src/app.js +419 -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 +187 -0
- package/dist/server/src/dev.d.ts +12 -0
- package/dist/server/src/dev.js +80 -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 +16 -0
- package/dist/server/src/index.js +16 -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 +112 -0
- package/dist/server/src/openapi.d.ts +59 -0
- package/dist/server/src/openapi.js +374 -0
- package/dist/server/src/problem.d.ts +8 -0
- package/dist/server/src/problem.js +39 -0
- package/dist/server/src/site.d.ts +13 -0
- package/dist/server/src/site.js +43 -0
- package/dist/server/src/store-api.d.ts +28 -0
- package/dist/server/src/store-api.js +159 -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 +146 -0
- package/docs/reference/core.md +2426 -0
- package/docs/reference/detox.md +150 -0
- package/docs/reference/http-api.md +303 -0
- package/docs/reference/playwright.md +303 -0
- package/docs/reference/reports.md +7098 -0
- package/docs/reference/schema.md +1651 -0
- package/docs/reference/server.md +685 -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/server.md +285 -0
- package/docs/statecharts.md +641 -0
- package/llms-full.txt +3506 -0
- package/llms.txt +47 -0
- package/package.json +32 -4
- package/packages/core/README.md +2 -1
- package/packages/reports/README.md +65 -0
- package/packages/reports/STORAGE.md +4 -0
- package/packages/schema/README.md +14 -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 +22 -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/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/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 +194 -0
- package/packages/schema/schemas/server-info.schema.json +114 -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 +6699 -0
- package/packages/visualizer/README.md +21 -0
- package/packages/visualizer/src/app/AppShell.tsx +9 -2
- package/packages/visualizer/src/app/report-sources.tsx +6 -4
- package/packages/visualizer/src/app/router.tsx +3 -3
- package/packages/visualizer/src/components/Header.module.css +35 -0
- package/packages/visualizer/src/components/Header.tsx +40 -3
- package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
- package/packages/visualizer/src/data/adapters/index.ts +10 -1
- package/packages/visualizer/src/evidence/api.ts +14 -2
- 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 +3 -1
- package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
- package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
- package/packages/visualizer/src/find/Finder.tsx +1 -7
- package/packages/visualizer/src/find/open.ts +4 -0
- package/packages/visualizer/src/live/live-stream.ts +204 -0
- package/packages/visualizer/src/runtime.ts +19 -0
- package/packages/visualizer/vite/atlas-server-proxy.ts +36 -0
- package/packages/visualizer/vite.config.ts +11 -3
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Publishing checks as a layer
|
|
3
|
+
*
|
|
4
|
+
* `publishChecks` turns the checks of one producer into one immutable layer in any store: the whole integration for a suite
|
|
5
|
+
* that has no timeline to record. Attachments of any kind are stored by content (the same screenshot attached ten times is
|
|
6
|
+
* stored once). Nothing here reads a clock or the environment, so the same input is always the same layer and a retried CI
|
|
7
|
+
* step changes nothing.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/integrating.md
|
|
10
|
+
* @see docs/extending.md
|
|
11
|
+
*/
|
|
12
|
+
import { PublishChecksSchema } from '../../schema/src/layer.js';
|
|
13
|
+
import { writeBundle } from './bundle.js';
|
|
14
|
+
import { normalizeEnvironment } from './layer-axes.js';
|
|
15
|
+
import { publishLayerBundle } from './layer-store.js';
|
|
16
|
+
import { contentTypeOf } from './media-types.js';
|
|
17
|
+
import { parseWith } from './parse-with.js';
|
|
18
|
+
import { sha256Hex } from './sigv4.js';
|
|
19
|
+
/** A small text value stays in the layer's own document; anything longer is stored as a file. */
|
|
20
|
+
const INLINE_TEXT_BYTES = 8 * 1024;
|
|
21
|
+
const encoder = new TextEncoder();
|
|
22
|
+
function decodeBase64(text) {
|
|
23
|
+
const binary = atob(text);
|
|
24
|
+
return Uint8Array.from(binary, char => char.charCodeAt(0));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Move the bytes of every attachment, at every level, into the layer: a file by its content (so the same
|
|
28
|
+
* screenshot attached ten times is stored once), referred to by `bundleKey`. What a producer wrote as `data`,
|
|
29
|
+
* `base64` or a long `text` becomes a reference; a short `text` stays inline; a `uri` is left alone.
|
|
30
|
+
*/
|
|
31
|
+
async function materialize(holder, entries, limit) {
|
|
32
|
+
const out = { ...holder };
|
|
33
|
+
if (Array.isArray(out.attachments)) {
|
|
34
|
+
out.attachments = await Promise.all(out.attachments.map(async (given) => {
|
|
35
|
+
const { path: file, base64, data, ...kept } = given;
|
|
36
|
+
if (file !== undefined)
|
|
37
|
+
throw new Error(`publishChecks: attachment "${String(kept.name)}" names a file (path). Reading files is the command line's job: read it and pass its bytes as data`);
|
|
38
|
+
const text = typeof kept.text === 'string' ? kept.text : undefined;
|
|
39
|
+
let bytes = data ?? (base64 === undefined ? null : decodeBase64(base64));
|
|
40
|
+
if (!bytes && text !== undefined && encoder.encode(text).length > INLINE_TEXT_BYTES)
|
|
41
|
+
bytes = encoder.encode(text);
|
|
42
|
+
if (!bytes)
|
|
43
|
+
return { ...kept, ...(text !== undefined ? { bytes: encoder.encode(text).length } : {}) };
|
|
44
|
+
if (bytes.length > limit)
|
|
45
|
+
throw new Error(`publishChecks: attachment "${String(kept.name)}" is ${bytes.length} bytes, over the limit of ${limit}. Store it elsewhere and attach its uri`);
|
|
46
|
+
const sha256 = await sha256Hex(bytes);
|
|
47
|
+
const contentType = kept.contentType ?? contentTypeOf(String(kept.name));
|
|
48
|
+
entries.set(`attachments/${sha256}`, { key: `attachments/${sha256}`, bytes, contentType });
|
|
49
|
+
const { text: _inline, ...rest } = kept;
|
|
50
|
+
return { ...rest, contentType, bundleKey: `attachments/${sha256}`, bytes: bytes.length, sha256 };
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(out.steps))
|
|
54
|
+
out.steps = await Promise.all(out.steps.map(step => materialize(step, entries, limit)));
|
|
55
|
+
if (Array.isArray(out.attempts))
|
|
56
|
+
out.attempts = await Promise.all(out.attempts.map(attempt => materialize(attempt, entries, limit)));
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Publish the checks of one producer as one immutable layer. This is the whole integration for a
|
|
61
|
+
* suite that has no timelines to record: Gherkin scenarios, a Quint run, a DMN table test, an SLO
|
|
62
|
+
* evaluation, a linter, anything that ends in "this check passed or failed".
|
|
63
|
+
*
|
|
64
|
+
* A check may carry whatever evidence its framework has: attachments of any media type (screenshots,
|
|
65
|
+
* videos, traces, logs, DOM snapshots…), timeline steps, annotations, retries, tags, labels, links and
|
|
66
|
+
* namespaced `extensions`. All of it is optional (docs/extending.md).
|
|
67
|
+
*
|
|
68
|
+
* Nothing here reads a clock: `recordedAtMs` is when the checks ran, given by the caller. Publishing
|
|
69
|
+
* the same input twice therefore writes the same layer, and a retried CI step changes nothing.
|
|
70
|
+
* Each check's `id` is its identity across runs. It must be stable and unique within the layer.
|
|
71
|
+
*/
|
|
72
|
+
export async function publishChecks(store, input, options = {}) {
|
|
73
|
+
const args = parseWith(PublishChecksSchema, input, 'publishChecks');
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
for (const check of args.checks) {
|
|
76
|
+
if (seen.has(check.id))
|
|
77
|
+
throw new Error(`publishChecks: check id "${check.id}" appears twice; a check keeps one id across runs, so ids must be unique`);
|
|
78
|
+
seen.add(check.id);
|
|
79
|
+
}
|
|
80
|
+
const limit = options.maxAttachmentBytes ?? 512 * 1024 * 1024;
|
|
81
|
+
const entries = new Map();
|
|
82
|
+
const checks = await Promise.all(args.checks.map(check => materialize(check, entries, limit)));
|
|
83
|
+
const layerEvidence = await materialize({ attachments: args.attachments }, entries, limit);
|
|
84
|
+
const total = [...entries.values()].reduce((sum, entry) => sum + entry.bytes.length, 0);
|
|
85
|
+
const totalLimit = options.maxTotalAttachmentBytes ?? 2 * 1024 * 1024 * 1024;
|
|
86
|
+
if (total > totalLimit)
|
|
87
|
+
throw new Error(`publishChecks: attachments total ${total} bytes, over the limit of ${totalLimit}. Attach the largest by uri, or publish in several layers`);
|
|
88
|
+
const environment = normalizeEnvironment(args.environment ?? args.parameters.environment);
|
|
89
|
+
const { environment: _folded, ...parameters } = args.parameters;
|
|
90
|
+
const part = {
|
|
91
|
+
sha: args.sha ?? '',
|
|
92
|
+
id: args.producer,
|
|
93
|
+
kind: args.kind,
|
|
94
|
+
title: args.title ?? args.producer,
|
|
95
|
+
recordedAtMs: args.recordedAtMs,
|
|
96
|
+
checks: checks
|
|
97
|
+
};
|
|
98
|
+
// Key order is part of the layer's identity: build it the one way.
|
|
99
|
+
const summary = {
|
|
100
|
+
...(args.freshness ? { freshness: args.freshness } : {}),
|
|
101
|
+
producer: args.producer,
|
|
102
|
+
recordedAtMs: args.recordedAtMs,
|
|
103
|
+
sha: args.sha,
|
|
104
|
+
branch: args.branch,
|
|
105
|
+
runUrl: args.runUrl,
|
|
106
|
+
...(environment ? { environment } : {}),
|
|
107
|
+
parameters: Object.fromEntries(Object.entries(parameters).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))),
|
|
108
|
+
specs: args.specs,
|
|
109
|
+
parts: [part],
|
|
110
|
+
source: null,
|
|
111
|
+
recording: null,
|
|
112
|
+
...(args.tags?.length ? { tags: args.tags } : {}),
|
|
113
|
+
...(args.labels && Object.keys(args.labels).length ? { labels: Object.fromEntries(Object.entries(args.labels).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) } : {}),
|
|
114
|
+
...(args.links?.length ? { links: args.links } : {}),
|
|
115
|
+
...(layerEvidence.attachments?.length ? { attachments: layerEvidence.attachments } : {}),
|
|
116
|
+
...(args.annotations?.length ? { annotations: args.annotations } : {}),
|
|
117
|
+
...(args.extensions && Object.keys(args.extensions).length ? { extensions: args.extensions } : {})
|
|
118
|
+
};
|
|
119
|
+
const bundle = entries.size ? await writeBundle(store, (async function* () { for (const entry of [...entries.values()].sort((a, b) => (a.key < b.key ? -1 : 1)))
|
|
120
|
+
yield entry; })(), 'layers/blobs/') : {};
|
|
121
|
+
return publishLayerBundle(store, summary, bundle);
|
|
122
|
+
}
|
|
@@ -5,50 +5,83 @@ import type { CommitRecord, SourceRecord } from './protocol.js';
|
|
|
5
5
|
import type { SourceIdentity } from './source-content.js';
|
|
6
6
|
/** The run id every spec suite writes its bucket under. */
|
|
7
7
|
export declare const SPEC_RUN_ID = "latest";
|
|
8
|
+
/** What `openCommit` needs to start a commit's run. */
|
|
8
9
|
export type OpenCommitArgs = {
|
|
10
|
+
/** The full 40-hex commit sha. */
|
|
9
11
|
readonly sha: string;
|
|
10
12
|
readonly branch: string;
|
|
13
|
+
/** The CI run publishing this commit, or `null`. */
|
|
11
14
|
readonly runUrl: string | null;
|
|
15
|
+
/** When the run started, epoch milliseconds; it also orders the commit list (newest first). */
|
|
12
16
|
readonly startedAtMs: number;
|
|
13
17
|
/** Stylesheet the commit's HTML snapshots render with. */
|
|
14
18
|
readonly frameCss: string;
|
|
15
|
-
/** Concurrent CI attempts own independent commit-local files. */
|
|
19
|
+
/** Concurrent CI attempts own independent commit-local files. Letters, digits, `.`, `_`, `:` and `-`; see `publicationStore`. */
|
|
16
20
|
readonly publicationId?: string;
|
|
17
21
|
};
|
|
18
22
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
23
|
+
* Marks a commit's run as started and lists it (newest first), returning the `running` record.
|
|
24
|
+
*
|
|
25
|
+
* Writes the snapshot stylesheet first, so a test is viewable the moment it lands, then `commit.json`, then the
|
|
26
|
+
* `index/` entry that lists the commit, and, only when `publicationId` is given, last the pointer that tells readers
|
|
27
|
+
* to look under that attempt. With a `publicationId` the stylesheet and record are written in the attempt's scope, not
|
|
28
|
+
* the shared one. Calling it again for the same sha and attempt rewrites the same keys. Store errors reject.
|
|
21
29
|
*/
|
|
22
30
|
export declare function openCommit(store: ObjectStore, args: OpenCommitArgs): Promise<CommitRecord>;
|
|
31
|
+
/** What `publishSource` needs to publish one package bucket of recorded tests. */
|
|
23
32
|
export type PublishSourceArgs = {
|
|
24
|
-
/** Execution completion time, separate from a virtual timeline clock. */
|
|
33
|
+
/** Execution completion time, separate from a virtual timeline clock. Epoch milliseconds. Default: the latest test's `endedAtMs`. */
|
|
25
34
|
readonly recordedAtMs?: number;
|
|
35
|
+
/** Recorded on the layer: `modified` when the run was from a checkout with uncommitted changes, `unknown` when its origin is unknown. */
|
|
26
36
|
readonly freshness?: 'unknown' | 'modified';
|
|
27
37
|
/** The client this run checked: desktop web, mobile web, iOS, Android, or another recorded name. */
|
|
28
38
|
readonly environment?: string;
|
|
39
|
+
/** Choices other than the client that make this its own slot (see `LayerParameters`). */
|
|
29
40
|
readonly parameters?: LayerParameters;
|
|
41
|
+
/** The full 40-hex commit sha the run is for. */
|
|
30
42
|
readonly sha: string;
|
|
43
|
+
/** Repo-relative package dir; with `bucket` it forms the source id (`libs-chess--part-2`). */
|
|
31
44
|
readonly packageDir: string;
|
|
45
|
+
/** The bucket under the package's report dir (`part-2`, `all`). */
|
|
32
46
|
readonly bucket: string;
|
|
33
47
|
/** The package's local report bucket (`.cache/harness-report/<bucket>`). */
|
|
34
48
|
readonly local: ObjectStore;
|
|
49
|
+
/** The run id the tests were written under in `local`. Default: `latest` (`SPEC_RUN_ID`). */
|
|
35
50
|
readonly runId?: string;
|
|
36
51
|
/** Only a passing execution is reusable under its exact task hash. */
|
|
37
52
|
readonly identity?: SourceIdentity;
|
|
53
|
+
/** Forces the source's status to `failed`; any other value is ignored and the status is computed from the tests. */
|
|
38
54
|
readonly status?: ReportTestStatus;
|
|
39
55
|
};
|
|
40
56
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
57
|
+
* Copies one package bucket into the store: every test (runner outcomes from the bucket's `junit.xml` applied) and its
|
|
58
|
+
* frame files, then the source record last, so a reader that sees the record can read every test it names; finally it
|
|
59
|
+
* archives the source as an immutable layer. Returns the record, or `null` when the bucket recorded no test and
|
|
60
|
+
* neither `identity` nor `status` was given.
|
|
61
|
+
*
|
|
62
|
+
* The source's status is `failed` when `args.status` is `failed`, else `failed` if any test failed, `skipped` if all
|
|
63
|
+
* were skipped, else `passed`. A passing source with an `identity` is stored once, content-addressed by its task
|
|
64
|
+
* hash, and referenced from the commit; any other source gets its files in the commit's bundle. When the bucket has no
|
|
65
|
+
* `ui-quality` or `performance` part of its own, a `performance` part with a 50 ms allowance is derived from the
|
|
66
|
+
* tests. Throws on a source id that is not a valid key segment, a part that fails `validatePart`, or a store error;
|
|
67
|
+
* a failure part-way leaves unreferenced content, never a record that names missing content.
|
|
45
68
|
*/
|
|
46
69
|
export declare function publishSource(store: ObjectStore, args: PublishSourceArgs): Promise<SourceRecord | null>;
|
|
70
|
+
/** What `closeCommit` needs to finish a commit's run. */
|
|
47
71
|
export type CloseCommitArgs = {
|
|
72
|
+
/** The full 40-hex commit sha. */
|
|
48
73
|
readonly sha: string;
|
|
74
|
+
/** When the run ended, epoch milliseconds. */
|
|
49
75
|
readonly endedAtMs: number;
|
|
76
|
+
/** The attempt given to `openCommit`, when it was given one. */
|
|
50
77
|
readonly publicationId?: string;
|
|
78
|
+
/** `failed` forces the commit's status to `failed`; any other value is ignored. */
|
|
51
79
|
readonly status?: ReportTestStatus;
|
|
52
80
|
};
|
|
53
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Marks a commit's run `complete` and returns the record, with the worst status of its sources: `failed` if
|
|
83
|
+
* `args.status` is `failed` or any source failed, `skipped` when there are no sources or all were skipped, else
|
|
84
|
+
* `passed`. Throws `no open report for <sha>; openCommit runs first` when the commit was never opened. Calling it
|
|
85
|
+
* again rewrites the record with the sources present at that time.
|
|
86
|
+
*/
|
|
54
87
|
export declare function closeCommit(store: ObjectStore, args: CloseCommitArgs): Promise<CommitRecord>;
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Publishing commit-keyed recordings
|
|
3
|
+
*
|
|
4
|
+
* The write side of the older, commit-keyed shared store: what CI runs after a spec suite recorded timelines with
|
|
5
|
+
* screenshots. `openCommit` starts a commit, `publishSource` copies one package bucket's recorded tests into the store
|
|
6
|
+
* (and also archives it as an immutable layer, so it composes with every other layer), and `closeCommit` finishes the
|
|
7
|
+
* commit. Pure over two injected `ObjectStore`s, the local report bucket a test run wrote and the shared store, so it
|
|
8
|
+
* runs the same against a directory, S3 or memory. Writes go content first, then the record that names it, so a
|
|
9
|
+
* reader that sees a record can read everything it names. Plain checks from JUnit, CTRF or a checks file do not go
|
|
10
|
+
* through here: they use `publishChecks`.
|
|
11
|
+
*
|
|
12
|
+
* @see docs/report-backends.md
|
|
13
|
+
*/
|
|
1
14
|
import { archiveSourceLayer } from './layer-source.js';
|
|
2
15
|
import { normalizeEnvironment } from './layer-axes.js';
|
|
3
16
|
import { performancePart } from './performance-part.js';
|
|
@@ -21,8 +34,12 @@ export const SPEC_RUN_ID = 'latest';
|
|
|
21
34
|
const WRITE_WIDTH = 32;
|
|
22
35
|
const JSON_TYPE = 'application/json; charset=utf-8';
|
|
23
36
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
37
|
+
* Marks a commit's run as started and lists it (newest first), returning the `running` record.
|
|
38
|
+
*
|
|
39
|
+
* Writes the snapshot stylesheet first, so a test is viewable the moment it lands, then `commit.json`, then the
|
|
40
|
+
* `index/` entry that lists the commit, and, only when `publicationId` is given, last the pointer that tells readers
|
|
41
|
+
* to look under that attempt. With a `publicationId` the stylesheet and record are written in the attempt's scope, not
|
|
42
|
+
* the shared one. Calling it again for the same sha and attempt rewrites the same keys. Store errors reject.
|
|
26
43
|
*/
|
|
27
44
|
export async function openCommit(store, args) {
|
|
28
45
|
const { frameCss, publicationId, ...fields } = args;
|
|
@@ -83,10 +100,17 @@ async function publishSnapshots(store, args, tests, contentPrefix) {
|
|
|
83
100
|
});
|
|
84
101
|
}
|
|
85
102
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
103
|
+
* Copies one package bucket into the store: every test (runner outcomes from the bucket's `junit.xml` applied) and its
|
|
104
|
+
* frame files, then the source record last, so a reader that sees the record can read every test it names; finally it
|
|
105
|
+
* archives the source as an immutable layer. Returns the record, or `null` when the bucket recorded no test and
|
|
106
|
+
* neither `identity` nor `status` was given.
|
|
107
|
+
*
|
|
108
|
+
* The source's status is `failed` when `args.status` is `failed`, else `failed` if any test failed, `skipped` if all
|
|
109
|
+
* were skipped, else `passed`. A passing source with an `identity` is stored once, content-addressed by its task
|
|
110
|
+
* hash, and referenced from the commit; any other source gets its files in the commit's bundle. When the bucket has no
|
|
111
|
+
* `ui-quality` or `performance` part of its own, a `performance` part with a 50 ms allowance is derived from the
|
|
112
|
+
* tests. Throws on a source id that is not a valid key segment, a part that fails `validatePart`, or a store error;
|
|
113
|
+
* a failure part-way leaves unreferenced content, never a record that names missing content.
|
|
90
114
|
*/
|
|
91
115
|
export async function publishSource(store, args) {
|
|
92
116
|
const runId = args.runId ?? SPEC_RUN_ID;
|
|
@@ -152,7 +176,12 @@ async function sourceEvidence(local, sha, source, tests) {
|
|
|
152
176
|
validatePart({ ...part, sha, id: source, recordedAtMs: 0 });
|
|
153
177
|
return { evidence };
|
|
154
178
|
}
|
|
155
|
-
/**
|
|
179
|
+
/**
|
|
180
|
+
* Marks a commit's run `complete` and returns the record, with the worst status of its sources: `failed` if
|
|
181
|
+
* `args.status` is `failed` or any source failed, `skipped` when there are no sources or all were skipped, else
|
|
182
|
+
* `passed`. Throws `no open report for <sha>; openCommit runs first` when the commit was never opened. Calling it
|
|
183
|
+
* again rewrites the record with the sources present at that time.
|
|
184
|
+
*/
|
|
156
185
|
export async function closeCommit(store, args) {
|
|
157
186
|
if (args.publicationId !== undefined)
|
|
158
187
|
store = publicationStore(store, args.publicationId);
|
|
@@ -1,60 +1,90 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @module Reading commit-keyed recordings
|
|
3
|
+
*
|
|
4
|
+
* The read side of the older, commit-keyed shared store: what the reports API serves for a commit (its record, its
|
|
5
|
+
* sources, each test's timeline and frame files) and the event streams that deliver them. Pure over an injected
|
|
6
|
+
* `ObjectStore`, `Clock` and `Timers`; nothing here imports a runtime or an HTTP framework. Every read goes through
|
|
7
|
+
* `reportStore`, so a commit published by an attempt (`publicationId`) is read from that attempt's records.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/report-backends.md
|
|
5
10
|
*/
|
|
6
11
|
import type { Clock, Timers } from './clock.js';
|
|
7
12
|
import type { RunReport } from './report-types.js';
|
|
8
13
|
import type { ObjectStore, StoredObject } from './object-store.js';
|
|
9
14
|
import type { CommitEvent, CommitPage, CommitRecord, SourceRecord, TestEvent } from './protocol.js';
|
|
15
|
+
/** Reads a commit's record, or `null` when it is missing or not valid JSON. */
|
|
10
16
|
export declare function readCommit(store: ObjectStore, sha: string): Promise<CommitRecord | null>;
|
|
17
|
+
/** Reads the stylesheet a commit's snapshots render with, or `null` when the commit has none. */
|
|
11
18
|
export declare function readFrameCss(store: ObjectStore, sha: string): Promise<StoredObject | null>;
|
|
12
19
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
20
|
+
* Lists commits newest first, `limit` per page (at least 1).
|
|
21
|
+
*
|
|
22
|
+
* A commit published twice (a rerun) has two index entries; only the newer one is ever listed, on any page.
|
|
23
|
+
* `cursor` is the previous page's `cursor`; `null` starts at the newest. The returned `cursor` is `null` on the last
|
|
24
|
+
* page. An index entry whose commit record is missing or unreadable is left out, so a page may hold fewer than
|
|
25
|
+
* `limit` commits. Store errors reject.
|
|
15
26
|
*/
|
|
16
27
|
export declare function listCommits(store: ObjectStore, options: {
|
|
17
28
|
readonly limit: number;
|
|
18
29
|
readonly cursor: string | null;
|
|
19
30
|
}): Promise<CommitPage>;
|
|
31
|
+
/** How `commitEvents` tails a running commit. */
|
|
20
32
|
export type CommitEventsOptions = {
|
|
33
|
+
/** Time source for `tailMs`. */
|
|
21
34
|
readonly clock: Clock;
|
|
35
|
+
/** Timer functions used to wait `pollMs` between listings. */
|
|
22
36
|
readonly timers: Timers;
|
|
23
37
|
/** Sources the client already holds (a reconnect): never sent again. */
|
|
24
38
|
readonly have?: ReadonlySet<string>;
|
|
25
|
-
/** How often a running commit is re-listed for new sources. */
|
|
39
|
+
/** How often, in milliseconds, a running commit is re-listed for new sources. */
|
|
26
40
|
readonly pollMs: number;
|
|
27
|
-
/** A running commit's stream ends after this
|
|
41
|
+
/** A running commit's stream ends after this many milliseconds; the client reconnects. */
|
|
28
42
|
readonly tailMs: number;
|
|
29
43
|
};
|
|
30
44
|
/**
|
|
31
|
-
*
|
|
32
|
-
* commit is running, its sources are re-listed every `pollMs` and each new
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
45
|
+
* Streams a commit as events: the commit record, then every source in the order its read finishes. While the
|
|
46
|
+
* commit is running, its sources are re-listed every `pollMs` and each new one is sent as it lands, until the commit
|
|
47
|
+
* completes or `tailMs` passes. The state is read before each listing, so a listing after `complete` is the final
|
|
48
|
+
* one. A `commit` event is sent again when the commit's state changes.
|
|
49
|
+
*
|
|
50
|
+
* Ends with `{ type: 'end', state }`: `missing` when there is no commit record and no source, `complete` when the
|
|
51
|
+
* commit closed, `running` when `tailMs` passed first (reconnect with `have` to continue). A source is never sent
|
|
52
|
+
* twice in one stream, nor one listed in `options.have`. Trap: a source is counted as sent as soon as it is listed,
|
|
53
|
+
* so one whose record cannot be read at that moment is not retried in the same stream.
|
|
36
54
|
*/
|
|
37
55
|
export declare function commitEvents(store: ObjectStore, sha: string, options: CommitEventsOptions): AsyncGenerator<CommitEvent>;
|
|
38
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Reads one test's timeline (`RunReport`) without its frame files, or `null` when the source or test is not in the
|
|
58
|
+
* commit. A timeline stored as a reusable source is parsed leniently (`null` if not JSON); one in the commit's bundle
|
|
59
|
+
* throws if its bytes are not JSON.
|
|
60
|
+
*/
|
|
39
61
|
export declare function readTestReport(store: ObjectStore, args: {
|
|
40
62
|
readonly sha: string;
|
|
41
63
|
readonly source: string;
|
|
42
64
|
readonly testId: string;
|
|
43
65
|
}): Promise<RunReport | null>;
|
|
66
|
+
/** One frame file's content. */
|
|
44
67
|
export type FrameBody = {
|
|
68
|
+
/** The file's bytes. */
|
|
45
69
|
readonly bytes: Uint8Array;
|
|
70
|
+
/** The media type the file was stored with. */
|
|
46
71
|
readonly contentType: string;
|
|
47
72
|
};
|
|
48
|
-
/**
|
|
73
|
+
/** Reads one frame file of a test, or `null` when the test never wrote it. Throws `Invalid report frame` when `file` is not a safe relative frame path. */
|
|
49
74
|
export declare function readFrameBody(store: ObjectStore, args: {
|
|
50
75
|
readonly sha: string;
|
|
51
76
|
readonly source: string;
|
|
52
77
|
readonly testId: string;
|
|
53
78
|
readonly file: string;
|
|
54
79
|
}): Promise<FrameBody | null>;
|
|
55
|
-
/**
|
|
80
|
+
/** Lists every source record of a commit, in the order each read finishes. A source whose record cannot be read is left out. */
|
|
56
81
|
export declare function listSourceRecords(store: ObjectStore, sha: string): Promise<SourceRecord[]>;
|
|
57
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Streams a test as events: its report, then each frame file the report names as its read finishes, then
|
|
84
|
+
* `{ type: 'end', found: true }`. A test that is not in the commit yields only `{ type: 'end', found: false }`. A frame
|
|
85
|
+
* the report names but the store lacks is skipped. Text media (`html`, `json`, `text`) arrive as inline text, the rest
|
|
86
|
+
* as base64.
|
|
87
|
+
*/
|
|
58
88
|
export declare function testEvents(store: ObjectStore, args: {
|
|
59
89
|
readonly sha: string;
|
|
60
90
|
readonly source: string;
|
package/dist/reports/src/read.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @module Reading commit-keyed recordings
|
|
3
|
+
*
|
|
4
|
+
* The read side of the older, commit-keyed shared store: what the reports API serves for a commit (its record, its
|
|
5
|
+
* sources, each test's timeline and frame files) and the event streams that deliver them. Pure over an injected
|
|
6
|
+
* `ObjectStore`, `Clock` and `Timers`; nothing here imports a runtime or an HTTP framework. Every read goes through
|
|
7
|
+
* `reportStore`, so a commit published by an attempt (`publicationId`) is read from that attempt's records.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/report-backends.md
|
|
5
10
|
*/
|
|
6
11
|
import { readUtf8 } from './object-io.js';
|
|
7
12
|
import { inCompletionOrder } from './completion-order.js';
|
|
@@ -24,15 +29,21 @@ async function readJson(store, key) {
|
|
|
24
29
|
return null;
|
|
25
30
|
}
|
|
26
31
|
}
|
|
32
|
+
/** Reads a commit's record, or `null` when it is missing or not valid JSON. */
|
|
27
33
|
export async function readCommit(store, sha) {
|
|
28
34
|
return readJson(await reportStore(store, sha), commitKey(sha));
|
|
29
35
|
}
|
|
36
|
+
/** Reads the stylesheet a commit's snapshots render with, or `null` when the commit has none. */
|
|
30
37
|
export async function readFrameCss(store, sha) {
|
|
31
38
|
return (await reportStore(store, sha)).get(frameCssKey(sha));
|
|
32
39
|
}
|
|
33
40
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
41
|
+
* Lists commits newest first, `limit` per page (at least 1).
|
|
42
|
+
*
|
|
43
|
+
* A commit published twice (a rerun) has two index entries; only the newer one is ever listed, on any page.
|
|
44
|
+
* `cursor` is the previous page's `cursor`; `null` starts at the newest. The returned `cursor` is `null` on the last
|
|
45
|
+
* page. An index entry whose commit record is missing or unreadable is left out, so a page may hold fewer than
|
|
46
|
+
* `limit` commits. Store errors reject.
|
|
36
47
|
*/
|
|
37
48
|
export async function listCommits(store, options) {
|
|
38
49
|
const seen = new Set();
|
|
@@ -73,11 +84,15 @@ async function* newSources(store, sha, sent) {
|
|
|
73
84
|
}
|
|
74
85
|
}
|
|
75
86
|
/**
|
|
76
|
-
*
|
|
77
|
-
* commit is running, its sources are re-listed every `pollMs` and each new
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
87
|
+
* Streams a commit as events: the commit record, then every source in the order its read finishes. While the
|
|
88
|
+
* commit is running, its sources are re-listed every `pollMs` and each new one is sent as it lands, until the commit
|
|
89
|
+
* completes or `tailMs` passes. The state is read before each listing, so a listing after `complete` is the final
|
|
90
|
+
* one. A `commit` event is sent again when the commit's state changes.
|
|
91
|
+
*
|
|
92
|
+
* Ends with `{ type: 'end', state }`: `missing` when there is no commit record and no source, `complete` when the
|
|
93
|
+
* commit closed, `running` when `tailMs` passed first (reconnect with `have` to continue). A source is never sent
|
|
94
|
+
* twice in one stream, nor one listed in `options.have`. Trap: a source is counted as sent as soon as it is listed,
|
|
95
|
+
* so one whose record cannot be read at that moment is not retried in the same stream.
|
|
81
96
|
*/
|
|
82
97
|
export async function* commitEvents(store, sha, options) {
|
|
83
98
|
const startedAt = options.clock.now();
|
|
@@ -133,7 +148,11 @@ function frameBlob(obj, media) {
|
|
|
133
148
|
}
|
|
134
149
|
: { contentType: obj.contentType, base64: bytesToBase64(obj.bytes) };
|
|
135
150
|
}
|
|
136
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* Reads one test's timeline (`RunReport`) without its frame files, or `null` when the source or test is not in the
|
|
153
|
+
* commit. A timeline stored as a reusable source is parsed leniently (`null` if not JSON); one in the commit's bundle
|
|
154
|
+
* throws if its bytes are not JSON.
|
|
155
|
+
*/
|
|
137
156
|
export async function readTestReport(store, args) {
|
|
138
157
|
store = await reportStore(store, args.sha);
|
|
139
158
|
const prefix = await sourceTestPrefix(store, args);
|
|
@@ -147,7 +166,7 @@ export async function readTestReport(store, args) {
|
|
|
147
166
|
? null
|
|
148
167
|
: JSON.parse(new TextDecoder().decode(obj.bytes));
|
|
149
168
|
}
|
|
150
|
-
/**
|
|
169
|
+
/** Reads one frame file of a test, or `null` when the test never wrote it. Throws `Invalid report frame` when `file` is not a safe relative frame path. */
|
|
151
170
|
export async function readFrameBody(store, args) {
|
|
152
171
|
store = await reportStore(store, args.sha);
|
|
153
172
|
if (!isFramePath(args.file))
|
|
@@ -164,7 +183,7 @@ export async function readFrameBody(store, args) {
|
|
|
164
183
|
? null
|
|
165
184
|
: { bytes: obj.bytes, contentType: obj.contentType };
|
|
166
185
|
}
|
|
167
|
-
/**
|
|
186
|
+
/** Lists every source record of a commit, in the order each read finishes. A source whose record cannot be read is left out. */
|
|
168
187
|
export async function listSourceRecords(store, sha) {
|
|
169
188
|
const out = [];
|
|
170
189
|
for await (const source of newSources(store, sha, new Set())) {
|
|
@@ -172,7 +191,12 @@ export async function listSourceRecords(store, sha) {
|
|
|
172
191
|
}
|
|
173
192
|
return out;
|
|
174
193
|
}
|
|
175
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* Streams a test as events: its report, then each frame file the report names as its read finishes, then
|
|
196
|
+
* `{ type: 'end', found: true }`. A test that is not in the commit yields only `{ type: 'end', found: false }`. A frame
|
|
197
|
+
* the report names but the store lacks is skipped. Text media (`html`, `json`, `text`) arrive as inline text, the rest
|
|
198
|
+
* as base64.
|
|
199
|
+
*/
|
|
176
200
|
export async function* testEvents(store, args) {
|
|
177
201
|
store = await reportStore(store, args.sha);
|
|
178
202
|
const prefix = await sourceTestPrefix(store, args);
|
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @module In-memory recorded test
|
|
3
|
+
*
|
|
4
|
+
* Types for a recorded test with its frame files held in memory, which is what a viewer renders and what a publisher
|
|
5
|
+
* copies: text media inline, binary media as base64.
|
|
4
6
|
*/
|
|
5
7
|
import type { ReportTestStatus, RunReport } from './report-types.js';
|
|
6
8
|
/** One embedded frame file: text media inline, binary media as base64. */
|
|
7
9
|
export type HtmlReportBlob = {
|
|
10
|
+
/** The media type of the file. */
|
|
8
11
|
readonly contentType: string;
|
|
12
|
+
/** The file's content for text media. */
|
|
9
13
|
readonly text?: string;
|
|
14
|
+
/** The file's content for binary media, base64-encoded. */
|
|
10
15
|
readonly base64?: string;
|
|
11
16
|
};
|
|
17
|
+
/** One recorded test: its timeline and its frame files. */
|
|
12
18
|
export type HtmlReportTest = {
|
|
19
|
+
/** The test's timeline. */
|
|
13
20
|
readonly report: RunReport;
|
|
14
21
|
/** Keyed by the blob's `file` (relative to the test dir). */
|
|
15
22
|
readonly blobs: Readonly<Record<string, HtmlReportBlob>>;
|
|
16
23
|
};
|
|
24
|
+
/** The tests of one run held in memory. */
|
|
17
25
|
export type HtmlReportBundle = {
|
|
18
26
|
readonly runId: string;
|
|
27
|
+
/** The run's overall outcome. */
|
|
19
28
|
readonly status: ReportTestStatus;
|
|
29
|
+
/** When the run was last updated, epoch milliseconds. */
|
|
20
30
|
readonly updatedAtMs: number;
|
|
31
|
+
/** The run's tests. */
|
|
21
32
|
readonly tests: readonly HtmlReportTest[];
|
|
22
33
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @module In-memory recorded test
|
|
3
|
+
*
|
|
4
|
+
* Types for a recorded test with its frame files held in memory, which is what a viewer renders and what a publisher
|
|
5
|
+
* copies: text media inline, binary media as base64.
|
|
4
6
|
*/
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* @module Key layout of one report bucket
|
|
3
|
+
*
|
|
4
|
+
* Where a test run writes its recordings inside one local report bucket (an `ObjectStore`): `runs/index.json`,
|
|
5
|
+
* `runs/<runId>/manifest.json` and `runs/<runId>/tests/<testId>/report.json` plus the test's frame files beside it.
|
|
6
|
+
* Pure string functions. Neither id is validated here; callers sanitise them (`sanitizeRunId`, `sanitizeTestId`).
|
|
7
|
+
*/
|
|
8
|
+
/** The key of the index of the runs in a bucket. */
|
|
2
9
|
export declare const RUNS_INDEX_KEY = "runs/index.json";
|
|
10
|
+
/** The key prefix of one run, without a trailing slash: `runs/<runId>`. */
|
|
3
11
|
export declare function runPrefix(runId: string): string;
|
|
12
|
+
/** The key of a run's manifest: `runs/<runId>/manifest.json`. */
|
|
4
13
|
export declare function runManifestKey(runId: string): string;
|
|
14
|
+
/** The key prefix of one test, without a trailing slash: `runs/<runId>/tests/<testId>`. With an empty `testId` it ends in `/tests/`, which lists all tests of the run. */
|
|
5
15
|
export declare function testPrefix(runId: string, testId: string): string;
|
|
16
|
+
/** The key of a test's timeline: `runs/<runId>/tests/<testId>/report.json`. */
|
|
6
17
|
export declare function testReportKey(runId: string, testId: string): string;
|
|
18
|
+
/** The key of one frame file of a test: the test prefix and `frameFile` with any leading `/` removed. With an empty `frameFile` it is the test's prefix with a trailing slash. */
|
|
7
19
|
export declare function testFrameKey(runId: string, testId: string, frameFile: string): string;
|
|
20
|
+
/** The file extension for a snapshot media kind (`html`, `png`, `json`, `txt`, `bin`, `mp4`); an unknown kind is `bin`. */
|
|
8
21
|
export declare function mediaExtension(media: string): string;
|
|
22
|
+
/** The media type for a snapshot media kind (`html`, `png`, `json`, `text`, `bytes`, `video`); an unknown kind is `application/octet-stream`. */
|
|
9
23
|
export declare function contentTypeForMedia(media: string): string;
|
|
@@ -1,21 +1,34 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* @module Key layout of one report bucket
|
|
3
|
+
*
|
|
4
|
+
* Where a test run writes its recordings inside one local report bucket (an `ObjectStore`): `runs/index.json`,
|
|
5
|
+
* `runs/<runId>/manifest.json` and `runs/<runId>/tests/<testId>/report.json` plus the test's frame files beside it.
|
|
6
|
+
* Pure string functions. Neither id is validated here; callers sanitise them (`sanitizeRunId`, `sanitizeTestId`).
|
|
7
|
+
*/
|
|
8
|
+
/** The key of the index of the runs in a bucket. */
|
|
2
9
|
export const RUNS_INDEX_KEY = 'runs/index.json';
|
|
10
|
+
/** The key prefix of one run, without a trailing slash: `runs/<runId>`. */
|
|
3
11
|
export function runPrefix(runId) {
|
|
4
12
|
return `runs/${runId}`;
|
|
5
13
|
}
|
|
14
|
+
/** The key of a run's manifest: `runs/<runId>/manifest.json`. */
|
|
6
15
|
export function runManifestKey(runId) {
|
|
7
16
|
return `${runPrefix(runId)}/manifest.json`;
|
|
8
17
|
}
|
|
18
|
+
/** The key prefix of one test, without a trailing slash: `runs/<runId>/tests/<testId>`. With an empty `testId` it ends in `/tests/`, which lists all tests of the run. */
|
|
9
19
|
export function testPrefix(runId, testId) {
|
|
10
20
|
return `${runPrefix(runId)}/tests/${testId}`;
|
|
11
21
|
}
|
|
22
|
+
/** The key of a test's timeline: `runs/<runId>/tests/<testId>/report.json`. */
|
|
12
23
|
export function testReportKey(runId, testId) {
|
|
13
24
|
return `${testPrefix(runId, testId)}/report.json`;
|
|
14
25
|
}
|
|
26
|
+
/** The key of one frame file of a test: the test prefix and `frameFile` with any leading `/` removed. With an empty `frameFile` it is the test's prefix with a trailing slash. */
|
|
15
27
|
export function testFrameKey(runId, testId, frameFile) {
|
|
16
28
|
const rel = frameFile.replace(/^\/+/, '');
|
|
17
29
|
return `${testPrefix(runId, testId)}/${rel}`;
|
|
18
30
|
}
|
|
31
|
+
/** The file extension for a snapshot media kind (`html`, `png`, `json`, `txt`, `bin`, `mp4`); an unknown kind is `bin`. */
|
|
19
32
|
export function mediaExtension(media) {
|
|
20
33
|
switch (media) {
|
|
21
34
|
case 'html':
|
|
@@ -34,6 +47,7 @@ export function mediaExtension(media) {
|
|
|
34
47
|
return 'bin';
|
|
35
48
|
}
|
|
36
49
|
}
|
|
50
|
+
/** The media type for a snapshot media kind (`html`, `png`, `json`, `text`, `bytes`, `video`); an unknown kind is `application/octet-stream`. */
|
|
37
51
|
export function contentTypeForMedia(media) {
|
|
38
52
|
switch (media) {
|
|
39
53
|
case 'html':
|