@storyshelf/core 0.6.2 → 0.7.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/README.md +49 -32
- package/dist/adapter/capture-queue.d.mts +44 -2
- package/dist/adapter/capture-runner.d.mts +127 -10
- package/dist/adapter/capture-runner.mjs +5 -12
- package/dist/adapter/database.d.mts +27 -8
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +85 -0
- package/dist/adapter/design.mjs +28 -0
- package/dist/adapter/design.mjs.map +1 -0
- package/dist/adapter/metadata.d.mts +3 -10
- package/dist/adapter/notifier.d.mts +6 -3
- package/dist/adapter/notifier.mjs +5 -1
- package/dist/adapter/oauth.d.mts +52 -0
- package/dist/adapter/setup.d.mts +4 -5
- package/dist/adapter/setup.mjs +2 -1
- package/dist/adapter/setup.mjs.map +1 -1
- package/dist/adapter/webhook-events.mjs +1 -1
- package/dist/adapters/capture-target-offers.d.mts +28 -0
- package/dist/adapters/capture-target-offers.mjs +58 -0
- package/dist/adapters/capture-target-offers.mjs.map +1 -0
- package/dist/adapters/capture-target.d.mts +71 -0
- package/dist/adapters/capture-target.mjs +76 -0
- package/dist/adapters/capture-target.mjs.map +1 -0
- package/dist/adapters/notifier/build-comment.d.mts +24 -0
- package/dist/adapters/notifier/build-comment.mjs +24 -0
- package/dist/adapters/notifier/build-comment.mjs.map +1 -0
- package/dist/adapters/notifier/build-state.d.mts +99 -0
- package/dist/adapters/notifier/build-state.mjs +218 -0
- package/dist/adapters/notifier/build-state.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +4 -2
- package/dist/adapters/notifier/events.mjs +31 -18
- package/dist/adapters/notifier/events.mjs.map +1 -1
- package/dist/adapters/notifier/provider.d.mts +53 -7
- package/dist/adapters/notifier/provider.mjs +12 -0
- package/dist/adapters/notifier/provider.mjs.map +1 -0
- package/dist/adapters/notifier/state.d.mts +24 -0
- package/dist/adapters/notifier/state.mjs +66 -0
- package/dist/adapters/notifier/state.mjs.map +1 -0
- package/dist/adapters/notifier/types.d.mts +51 -2
- package/dist/adapters/remote-browser.d.mts +87 -0
- package/dist/adapters/remote-browser.mjs +103 -0
- package/dist/adapters/remote-browser.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +25 -0
- package/dist/adapters/render-runs.mjs +41 -0
- package/dist/adapters/render-runs.mjs.map +1 -0
- package/dist/adapters/setup-runner.mjs +2 -4
- package/dist/adapters/setup-runner.mjs.map +1 -1
- package/dist/adapters/sources.d.mts +12 -0
- package/dist/adapters/sources.mjs +20 -0
- package/dist/adapters/sources.mjs.map +1 -0
- package/dist/adapters/validate.mjs +25 -18
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +105 -0
- package/dist/ai.mjs +19 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/capture/a11y/axe-source.d.mts +18 -0
- package/dist/capture/a11y/axe-source.mjs +37 -0
- package/dist/capture/a11y/axe-source.mjs.map +1 -0
- package/dist/capture/a11y/in-page.d.mts +27 -0
- package/dist/capture/a11y/in-page.mjs +60 -0
- package/dist/capture/a11y/in-page.mjs.map +1 -0
- package/dist/capture/a11y/map.d.mts +24 -0
- package/dist/capture/a11y/map.mjs +48 -0
- package/dist/capture/a11y/map.mjs.map +1 -0
- package/dist/capture/a11y/options.d.mts +23 -0
- package/dist/capture/a11y/options.mjs +75 -0
- package/dist/capture/a11y/options.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +41 -3
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +2 -0
- package/dist/capture/affected.mjs +13 -4
- package/dist/capture/affected.mjs.map +1 -1
- package/dist/capture/attempt-logger.mjs +84 -0
- package/dist/capture/attempt-logger.mjs.map +1 -0
- package/dist/capture/browsers.d.mts +35 -0
- package/dist/capture/browsers.mjs +67 -0
- package/dist/capture/browsers.mjs.map +1 -0
- package/dist/capture/build-state/deliver.d.mts +32 -0
- package/dist/capture/build-state/deliver.mjs +118 -0
- package/dist/capture/build-state/deliver.mjs.map +1 -0
- package/dist/capture/build-state/index.d.mts +8 -0
- package/dist/capture/build-state/notify-job.d.mts +56 -0
- package/dist/capture/build-state/notify-job.mjs +133 -0
- package/dist/capture/build-state/notify-job.mjs.map +1 -0
- package/dist/capture/build-state/publisher.d.mts +23 -0
- package/dist/capture/build-state/publisher.mjs +107 -0
- package/dist/capture/build-state/publisher.mjs.map +1 -0
- package/dist/capture/build-state/resume.d.mts +9 -0
- package/dist/capture/build-state/resume.mjs +47 -0
- package/dist/capture/build-state/resume.mjs.map +1 -0
- package/dist/capture/build-state/types.d.mts +49 -0
- package/dist/capture/catalog/catalogue.d.mts +22 -0
- package/dist/capture/catalog/catalogue.mjs +53 -0
- package/dist/capture/catalog/catalogue.mjs.map +1 -0
- package/dist/capture/catalog/components.d.mts +23 -0
- package/dist/capture/catalog/components.mjs +55 -0
- package/dist/capture/catalog/components.mjs.map +1 -0
- package/dist/capture/catalog/history.d.mts +30 -0
- package/dist/capture/catalog/history.mjs +51 -0
- package/dist/capture/catalog/history.mjs.map +1 -0
- package/dist/capture/catalog/hook.d.mts +14 -0
- package/dist/capture/catalog/hook.mjs +19 -0
- package/dist/capture/catalog/hook.mjs.map +1 -0
- package/dist/capture/catalog/index.d.mts +11 -0
- package/dist/capture/catalog/load.d.mts +26 -0
- package/dist/capture/catalog/load.mjs +49 -0
- package/dist/capture/catalog/load.mjs.map +1 -0
- package/dist/capture/catalog/parse.d.mts +15 -0
- package/dist/capture/catalog/parse.mjs +56 -0
- package/dist/capture/catalog/parse.mjs.map +1 -0
- package/dist/capture/catalog/read.d.mts +12 -0
- package/dist/capture/catalog/read.mjs +33 -0
- package/dist/capture/catalog/read.mjs.map +1 -0
- package/dist/capture/catalog/types.d.mts +33 -0
- package/dist/capture/consumer/capture-handler.d.mts +58 -0
- package/dist/capture/consumer/capture-handler.mjs +118 -0
- package/dist/capture/consumer/capture-handler.mjs.map +1 -0
- package/dist/capture/consumer/idle.d.mts +17 -0
- package/dist/capture/consumer/idle.mjs +25 -0
- package/dist/capture/consumer/idle.mjs.map +1 -0
- package/dist/capture/consumer/index.d.mts +13 -0
- package/dist/capture/consumer/index.mjs +139 -0
- package/dist/capture/consumer/index.mjs.map +1 -0
- package/dist/capture/consumer/loop.mjs +110 -0
- package/dist/capture/consumer/loop.mjs.map +1 -0
- package/dist/capture/consumer/metrics.d.mts +41 -0
- package/dist/capture/consumer/metrics.mjs +38 -0
- package/dist/capture/consumer/metrics.mjs.map +1 -0
- package/dist/capture/consumer/retry.d.mts +33 -0
- package/dist/capture/consumer/retry.mjs +34 -0
- package/dist/capture/consumer/retry.mjs.map +1 -0
- package/dist/capture/consumer/run-job.mjs +195 -0
- package/dist/capture/consumer/run-job.mjs.map +1 -0
- package/dist/capture/consumer/startup.mjs +38 -0
- package/dist/capture/consumer/startup.mjs.map +1 -0
- package/dist/capture/consumer/types.d.mts +97 -0
- package/dist/capture/crash-failure.d.mts +25 -0
- package/dist/capture/crash-failure.mjs +70 -0
- package/dist/capture/crash-failure.mjs.map +1 -0
- package/dist/capture/db-queue/cadence.d.mts +32 -0
- package/dist/capture/db-queue/cadence.mjs +38 -0
- package/dist/capture/db-queue/cadence.mjs.map +1 -0
- package/dist/capture/db-queue/claim.d.mts +9 -0
- package/dist/capture/db-queue/claim.mjs +132 -0
- package/dist/capture/db-queue/claim.mjs.map +1 -0
- package/dist/capture/db-queue/clock.mjs +12 -0
- package/dist/capture/db-queue/clock.mjs.map +1 -0
- package/dist/capture/db-queue/codec.mjs +76 -0
- package/dist/capture/db-queue/codec.mjs.map +1 -0
- package/dist/capture/db-queue/index.d.mts +20 -0
- package/dist/capture/db-queue/index.mjs +89 -0
- package/dist/capture/db-queue/index.mjs.map +1 -0
- package/dist/capture/db-queue/keeper.d.mts +36 -0
- package/dist/capture/db-queue/keeper.mjs +92 -0
- package/dist/capture/db-queue/keeper.mjs.map +1 -0
- package/dist/capture/db-queue/lease.mjs +46 -0
- package/dist/capture/db-queue/lease.mjs.map +1 -0
- package/dist/capture/db-queue/lifecycle.mjs +36 -0
- package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
- package/dist/capture/db-queue/operations.mjs +94 -0
- package/dist/capture/db-queue/operations.mjs.map +1 -0
- package/dist/capture/db-queue/poll-loop.d.mts +30 -0
- package/dist/capture/db-queue/poll-loop.mjs +89 -0
- package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
- package/dist/capture/db-queue/poll.d.mts +10 -0
- package/dist/capture/db-queue/poll.mjs +69 -0
- package/dist/capture/db-queue/poll.mjs.map +1 -0
- package/dist/capture/db-queue/timers.d.mts +12 -0
- package/dist/capture/db-queue/timers.mjs +37 -0
- package/dist/capture/db-queue/timers.mjs.map +1 -0
- package/dist/capture/db-queue/types.d.mts +83 -0
- package/dist/capture/db-queue/types.mjs +12 -0
- package/dist/capture/db-queue/types.mjs.map +1 -0
- package/dist/capture/db-queue/wake.mjs +48 -0
- package/dist/capture/db-queue/wake.mjs.map +1 -0
- package/dist/capture/design.mjs +39 -0
- package/dist/capture/design.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +23 -3
- package/dist/capture/diff-record.mjs.map +1 -1
- package/dist/capture/dispatch.d.mts +12 -7
- package/dist/capture/dispatch.mjs +86 -107
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +60 -0
- package/dist/capture/enrich.mjs.map +1 -0
- package/dist/capture/failure-sets.mjs +22 -0
- package/dist/capture/failure-sets.mjs.map +1 -0
- package/dist/capture/fingerprint.d.mts +42 -0
- package/dist/capture/fingerprint.mjs +83 -0
- package/dist/capture/fingerprint.mjs.map +1 -0
- package/dist/capture/forked/channel.d.mts +25 -0
- package/dist/capture/forked/child.d.mts +34 -0
- package/dist/capture/forked/child.mjs +202 -0
- package/dist/capture/forked/child.mjs.map +1 -0
- package/dist/capture/forked/crash-tracker.mjs +23 -0
- package/dist/capture/forked/crash-tracker.mjs.map +1 -0
- package/dist/capture/forked/decision.d.mts +26 -0
- package/dist/capture/forked/decision.mjs +27 -0
- package/dist/capture/forked/decision.mjs.map +1 -0
- package/dist/capture/forked/exit.mjs +12 -0
- package/dist/capture/forked/exit.mjs.map +1 -0
- package/dist/capture/forked/fork.d.mts +31 -0
- package/dist/capture/forked/fork.mjs +93 -0
- package/dist/capture/forked/fork.mjs.map +1 -0
- package/dist/capture/forked/forked-capture.d.mts +40 -0
- package/dist/capture/forked/forked-capture.mjs +240 -0
- package/dist/capture/forked/forked-capture.mjs.map +1 -0
- package/dist/capture/forked/lease-book.mjs +46 -0
- package/dist/capture/forked/lease-book.mjs.map +1 -0
- package/dist/capture/forked/protocol.d.mts +38 -0
- package/dist/capture/forked/protocol.mjs +25 -0
- package/dist/capture/forked/protocol.mjs.map +1 -0
- package/dist/capture/forked/runs.mjs +75 -0
- package/dist/capture/forked/runs.mjs.map +1 -0
- package/dist/capture/isolate.mjs +141 -0
- package/dist/capture/isolate.mjs.map +1 -0
- package/dist/capture/job-lease.d.mts +28 -0
- package/dist/capture/job-lease.mjs +57 -0
- package/dist/capture/job-lease.mjs.map +1 -0
- package/dist/capture/matrix.mjs +298 -0
- package/dist/capture/matrix.mjs.map +1 -0
- package/dist/capture/modes.d.mts +25 -0
- package/dist/capture/modes.mjs +107 -0
- package/dist/capture/modes.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +37 -5
- package/dist/capture/orchestrator.mjs +118 -107
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +9 -2
- package/dist/capture/pipeline.mjs +250 -45
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +11 -0
- package/dist/capture/progress.mjs +28 -0
- package/dist/capture/progress.mjs.map +1 -0
- package/dist/capture/publish/accept.d.mts +39 -0
- package/dist/capture/publish/accept.mjs +28 -0
- package/dist/capture/publish/accept.mjs.map +1 -0
- package/dist/capture/publish/complete.d.mts +46 -0
- package/dist/capture/publish/complete.mjs +64 -0
- package/dist/capture/publish/complete.mjs.map +1 -0
- package/dist/capture/publish/extract.d.mts +20 -0
- package/dist/capture/publish/extract.mjs +119 -0
- package/dist/capture/publish/extract.mjs.map +1 -0
- package/dist/capture/publish/handler.d.mts +28 -0
- package/dist/capture/publish/handler.mjs +62 -0
- package/dist/capture/publish/handler.mjs.map +1 -0
- package/dist/capture/publish/index.d.mts +10 -0
- package/dist/capture/publish/limits.d.mts +37 -0
- package/dist/capture/publish/limits.mjs +39 -0
- package/dist/capture/publish/limits.mjs.map +1 -0
- package/dist/capture/publish/published.d.mts +15 -0
- package/dist/capture/publish/published.mjs +18 -0
- package/dist/capture/publish/published.mjs.map +1 -0
- package/dist/capture/publish/run.d.mts +33 -0
- package/dist/capture/publish/run.mjs +30 -0
- package/dist/capture/publish/run.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +1 -1
- package/dist/capture/rediff.mjs +4 -3
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +53 -0
- package/dist/capture/render.mjs.map +1 -0
- package/dist/capture/scratch.d.mts +44 -0
- package/dist/capture/scratch.mjs +138 -0
- package/dist/capture/scratch.mjs.map +1 -0
- package/dist/capture/shard-plan.mjs +128 -0
- package/dist/capture/shard-plan.mjs.map +1 -0
- package/dist/capture/shards/context.mjs +51 -0
- package/dist/capture/shards/context.mjs.map +1 -0
- package/dist/capture/shards/execute.mjs +174 -0
- package/dist/capture/shards/execute.mjs.map +1 -0
- package/dist/capture/shards/fan-out.mjs +126 -0
- package/dist/capture/shards/fan-out.mjs.map +1 -0
- package/dist/capture/shards/history.mjs +35 -0
- package/dist/capture/shards/history.mjs.map +1 -0
- package/dist/capture/shards/reduce.mjs +97 -0
- package/dist/capture/shards/reduce.mjs.map +1 -0
- package/dist/capture/sizing.mjs +1 -11
- package/dist/capture/sizing.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +2 -65
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +7 -8
- package/dist/capture/statics.mjs +25 -71
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +40 -0
- package/dist/capture/storybook-urls.mjs +41 -0
- package/dist/capture/storybook-urls.mjs.map +1 -0
- package/dist/capture/storybook.d.mts +12 -4
- package/dist/capture/storybook.mjs +56 -16
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +21 -0
- package/dist/capture/target.mjs.map +1 -0
- package/dist/capture/targets.d.mts +25 -0
- package/dist/capture/targets.mjs +32 -0
- package/dist/capture/targets.mjs.map +1 -0
- package/dist/capture/tests.d.mts +37 -0
- package/dist/capture/tests.mjs +126 -0
- package/dist/capture/tests.mjs.map +1 -0
- package/dist/capture/token-chunks.mjs +74 -0
- package/dist/capture/token-chunks.mjs.map +1 -0
- package/dist/capture.d.mts +60 -7
- package/dist/capture.mjs +48 -3
- package/dist/config.d.mts +121 -12
- package/dist/config.mjs +9 -6
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +9 -3
- package/dist/diff.mjs +16 -3
- package/dist/diff.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/insights/budget.d.mts +20 -0
- package/dist/insights/budget.mjs +33 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/envelope.d.mts +59 -0
- package/dist/insights/envelope.mjs +60 -0
- package/dist/insights/envelope.mjs.map +1 -0
- package/dist/insights/evidence-health.d.mts +32 -0
- package/dist/insights/evidence-health.mjs +28 -0
- package/dist/insights/evidence-health.mjs.map +1 -0
- package/dist/insights/evidence.d.mts +55 -0
- package/dist/insights/evidence.mjs +64 -0
- package/dist/insights/evidence.mjs.map +1 -0
- package/dist/insights/hash.d.mts +18 -0
- package/dist/insights/hash.mjs +21 -0
- package/dist/insights/hash.mjs.map +1 -0
- package/dist/insights/limits.d.mts +24 -0
- package/dist/insights/limits.mjs +24 -0
- package/dist/insights/limits.mjs.map +1 -0
- package/dist/insights/prompts/v1.d.mts +13 -0
- package/dist/insights/prompts/v1.mjs +34 -0
- package/dist/insights/prompts/v1.mjs.map +1 -0
- package/dist/insights/redact.d.mts +9 -0
- package/dist/insights/redact.mjs +24 -0
- package/dist/insights/redact.mjs.map +1 -0
- package/dist/insights/text.mjs +44 -0
- package/dist/insights/text.mjs.map +1 -0
- package/dist/insights.d.mts +11 -0
- package/dist/insights.mjs +12 -0
- package/dist/logger.mjs +17 -1
- package/dist/logger.mjs.map +1 -1
- package/dist/models/ai-budget-alert.d.mts +23 -0
- package/dist/models/ai-budget-alert.mjs +38 -0
- package/dist/models/ai-budget-alert.mjs.map +1 -0
- package/dist/models/ai-usage.d.mts +40 -0
- package/dist/models/ai-usage.mjs +61 -0
- package/dist/models/ai-usage.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +84 -0
- package/dist/models/baseline-archive.mjs +106 -0
- package/dist/models/baseline-archive.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +19 -0
- package/dist/models/baseline-export.mjs +136 -0
- package/dist/models/baseline-export.mjs.map +1 -0
- package/dist/models/baseline-import.mjs +118 -0
- package/dist/models/baseline-import.mjs.map +1 -0
- package/dist/models/baseline-transfer.d.mts +49 -0
- package/dist/models/baseline-transfer.mjs +121 -0
- package/dist/models/baseline-transfer.mjs.map +1 -0
- package/dist/models/baseline.d.mts +32 -5
- package/dist/models/baseline.mjs +65 -20
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +28 -0
- package/dist/models/build-component.mjs +55 -0
- package/dist/models/build-component.mjs.map +1 -0
- package/dist/models/build-review-status.d.mts +29 -0
- package/dist/models/build-review-status.mjs +37 -0
- package/dist/models/build-review-status.mjs.map +1 -0
- package/dist/models/build-review.d.mts +28 -0
- package/dist/models/build-review.mjs +54 -0
- package/dist/models/build-review.mjs.map +1 -0
- package/dist/models/build.d.mts +25 -3
- package/dist/models/build.mjs +50 -11
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +7 -1
- package/dist/models/capture-attempt.mjs +29 -4
- package/dist/models/capture-attempt.mjs.map +1 -1
- package/dist/models/capture-job.d.mts +106 -0
- package/dist/models/capture-job.mjs +288 -0
- package/dist/models/capture-job.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +1 -1
- package/dist/models/capture-log.mjs +4 -3
- package/dist/models/capture-log.mjs.map +1 -1
- package/dist/models/capture-shard.d.mts +62 -0
- package/dist/models/capture-shard.mjs +190 -0
- package/dist/models/capture-shard.mjs.map +1 -0
- package/dist/models/changeset.d.mts +48 -0
- package/dist/models/changeset.mjs +81 -0
- package/dist/models/changeset.mjs.map +1 -0
- package/dist/models/columns.mjs +19 -0
- package/dist/models/columns.mjs.map +1 -0
- package/dist/models/comment.d.mts +4 -1
- package/dist/models/comment.mjs +7 -4
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/component.d.mts +43 -0
- package/dist/models/component.mjs +60 -0
- package/dist/models/component.mjs.map +1 -0
- package/dist/models/connections.d.mts +41 -0
- package/dist/models/connections.mjs +112 -0
- package/dist/models/connections.mjs.map +1 -0
- package/dist/models/content-ref.d.mts +1 -1
- package/dist/models/content-ref.mjs +1 -1
- package/dist/models/content-ref.mjs.map +1 -1
- package/dist/models/credentials.mjs +12 -2
- package/dist/models/credentials.mjs.map +1 -1
- package/dist/models/design-credentials.d.mts +41 -0
- package/dist/models/design-credentials.mjs +86 -0
- package/dist/models/design-credentials.mjs.map +1 -0
- package/dist/models/design-health.d.mts +16 -0
- package/dist/models/design-health.mjs +25 -0
- package/dist/models/design-health.mjs.map +1 -0
- package/dist/models/design-nodes.d.mts +83 -0
- package/dist/models/design-nodes.mjs +222 -0
- package/dist/models/design-nodes.mjs.map +1 -0
- package/dist/models/design-renders.mjs +74 -0
- package/dist/models/design-renders.mjs.map +1 -0
- package/dist/models/design-resolve.d.mts +29 -0
- package/dist/models/design-resolve.mjs +41 -0
- package/dist/models/design-resolve.mjs.map +1 -0
- package/dist/models/design.d.mts +39 -0
- package/dist/models/design.mjs +90 -0
- package/dist/models/design.mjs.map +1 -0
- package/dist/models/inherited-ratio.d.mts +21 -0
- package/dist/models/inherited-ratio.mjs +22 -0
- package/dist/models/inherited-ratio.mjs.map +1 -0
- package/dist/models/insight.d.mts +70 -0
- package/dist/models/insight.mjs +162 -0
- package/dist/models/insight.mjs.map +1 -0
- package/dist/models/label.d.mts +4 -2
- package/dist/models/label.mjs +25 -10
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +1 -1
- package/dist/models/member.mjs +5 -4
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +37 -3
- package/dist/models/notification-channel.mjs +88 -9
- package/dist/models/notification-channel.mjs.map +1 -1
- package/dist/models/notification-subscription.d.mts +1 -2
- package/dist/models/notification-subscription.mjs +7 -9
- package/dist/models/notification-subscription.mjs.map +1 -1
- package/dist/models/project-group-mapping.d.mts +6 -1
- package/dist/models/project-group-mapping.mjs +14 -4
- package/dist/models/project-group-mapping.mjs.map +1 -1
- package/dist/models/project.d.mts +9 -1
- package/dist/models/project.mjs +9 -4
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/public-origin.d.mts +63 -0
- package/dist/models/public-origin.mjs +114 -0
- package/dist/models/public-origin.mjs.map +1 -0
- package/dist/models/review-switch-off.d.mts +14 -0
- package/dist/models/review-switch-off.mjs +26 -0
- package/dist/models/review-switch-off.mjs.map +1 -0
- package/dist/models/site-settings.d.mts +23 -0
- package/dist/models/site-settings.mjs +48 -0
- package/dist/models/site-settings.mjs.map +1 -0
- package/dist/models/snapshot.d.mts +36 -1
- package/dist/models/snapshot.mjs +50 -3
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +13 -1
- package/dist/models/token.mjs +23 -5
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/usage.d.mts +76 -0
- package/dist/models/usage.mjs +149 -0
- package/dist/models/usage.mjs.map +1 -0
- package/dist/models/user.d.mts +1 -1
- package/dist/models/user.mjs +4 -3
- package/dist/models/user.mjs.map +1 -1
- package/dist/models/webhook.d.mts +15 -1
- package/dist/models/webhook.mjs +33 -4
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +26 -3
- package/dist/models.mjs +27 -5
- package/dist/options.d.mts +18 -0
- package/dist/options.mjs +90 -0
- package/dist/options.mjs.map +1 -0
- package/dist/paths.d.mts +19 -4
- package/dist/paths.mjs +51 -10
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +18 -0
- package/dist/retention/ai.mjs +23 -0
- package/dist/retention/ai.mjs.map +1 -0
- package/dist/retention/pending.mjs +47 -0
- package/dist/retention/pending.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +51 -0
- package/dist/retention/screenshot-sweep.mjs +128 -0
- package/dist/retention/screenshot-sweep.mjs.map +1 -0
- package/dist/retention.d.mts +16 -2
- package/dist/retention.mjs +71 -14
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +6 -0
- package/dist/schema/build-component.d.mts +25 -0
- package/dist/schema/build-review.d.mts +17 -0
- package/dist/schema/build.d.mts +7 -0
- package/dist/schema/capture-job.d.mts +60 -0
- package/dist/schema/capture-shard.d.mts +35 -0
- package/dist/schema/comment.d.mts +2 -0
- package/dist/schema/connection.d.mts +24 -0
- package/dist/schema/design.d.mts +58 -0
- package/dist/schema/insight.d.mts +54 -0
- package/dist/schema/notification.d.mts +4 -0
- package/dist/schema/project.d.mts +21 -0
- package/dist/schema/site-setting.d.mts +21 -0
- package/dist/schema/snapshot.d.mts +12 -0
- package/dist/schema/usage.d.mts +30 -0
- package/dist/schema.d.mts +11 -3
- package/dist/serverless.d.mts +21 -0
- package/dist/serverless.mjs +37 -0
- package/dist/serverless.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +1 -1
- package/dist/test-helpers/deferred.d.mts +39 -0
- package/dist/test-helpers/deferred.mjs +61 -0
- package/dist/test-helpers/deferred.mjs.map +1 -0
- package/dist/test-helpers/design-contract.d.mts +21 -0
- package/dist/test-helpers/design-contract.mjs +40 -0
- package/dist/test-helpers/design-contract.mjs.map +1 -0
- package/dist/test-helpers/fake-database.mjs +14 -2
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +43 -0
- package/dist/test-helpers/fake-design.mjs +114 -0
- package/dist/test-helpers/fake-design.mjs.map +1 -0
- package/dist/test-helpers/fake-notifier.d.mts +21 -0
- package/dist/test-helpers/fake-notifier.mjs +44 -0
- package/dist/test-helpers/fake-notifier.mjs.map +1 -0
- package/dist/test-helpers/fake-tables.mjs +255 -67
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +27 -0
- package/dist/test-helpers/manual-clock.mjs +56 -0
- package/dist/test-helpers/manual-clock.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +7 -1
- package/dist/test-helpers/notifier-contract.mjs +32 -3
- package/dist/test-helpers/notifier-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-contract.d.mts +16 -2
- package/dist/test-helpers/queue-contract.mjs +195 -1
- package/dist/test-helpers/queue-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-multi-instance.d.mts +20 -0
- package/dist/test-helpers/queue-multi-instance.mjs +206 -0
- package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.mjs +11 -0
- package/dist/test-helpers/runner-contract.mjs.map +1 -1
- package/dist/test-helpers/sql-chunks.mjs +23 -0
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.mjs +1 -1
- package/dist/test-helpers.d.mts +9 -4
- package/dist/test-helpers.mjs +8 -3
- package/dist/types.d.mts +14 -3
- package/dist/types.mjs +21 -4
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +4 -0
- package/dist/urls.mjs +11 -2
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/adaptive-concurrency.d.mts +66 -0
- package/dist/utils/adaptive-concurrency.mjs +149 -0
- package/dist/utils/adaptive-concurrency.mjs.map +1 -0
- package/dist/utils/connect-retry.d.mts +26 -0
- package/dist/utils/connect-retry.mjs +43 -0
- package/dist/utils/connect-retry.mjs.map +1 -0
- package/dist/utils/environment.d.mts +19 -0
- package/dist/utils/environment.mjs +59 -0
- package/dist/utils/environment.mjs.map +1 -0
- package/dist/utils/http.d.mts +17 -1
- package/dist/utils/http.mjs +22 -3
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +15 -0
- package/dist/utils/map-adaptive.mjs +72 -0
- package/dist/utils/map-adaptive.mjs.map +1 -0
- package/dist/utils/map-limit.d.mts +10 -0
- package/dist/utils/map-limit.mjs +32 -0
- package/dist/utils/map-limit.mjs.map +1 -0
- package/dist/utils/page-limiter.d.mts +37 -0
- package/dist/utils/page-limiter.mjs +56 -0
- package/dist/utils/page-limiter.mjs.map +1 -0
- package/dist/utils/public-origin.d.mts +20 -0
- package/dist/utils/public-origin.mjs +75 -0
- package/dist/utils/public-origin.mjs.map +1 -0
- package/dist/utils/redact.mjs +3 -0
- package/dist/utils/redact.mjs.map +1 -1
- package/dist/utils/remote-environment.d.mts +39 -0
- package/dist/utils/remote-environment.mjs +114 -0
- package/dist/utils/remote-environment.mjs.map +1 -0
- package/dist/utils/resources.d.mts +64 -0
- package/dist/utils/resources.mjs +122 -0
- package/dist/utils/resources.mjs.map +1 -0
- package/dist/utils/screenshot-store.d.mts +15 -0
- package/dist/utils/screenshot-store.mjs +41 -0
- package/dist/utils/screenshot-store.mjs.map +1 -0
- package/dist/utils/session-gate.d.mts +22 -0
- package/dist/utils/session-gate.mjs +53 -0
- package/dist/utils/session-gate.mjs.map +1 -0
- package/dist/utils/storybook-token.d.mts +81 -0
- package/dist/utils/storybook-token.mjs +134 -0
- package/dist/utils/storybook-token.mjs.map +1 -0
- package/dist/utils/storybook-url.d.mts +15 -0
- package/dist/utils/storybook-url.mjs +21 -0
- package/dist/utils/storybook-url.mjs.map +1 -0
- package/dist/utils/timing.mjs +1 -1
- package/dist/utils/timing.mjs.map +1 -1
- package/dist/utils/zip-read.mjs +129 -0
- package/dist/utils/zip-read.mjs.map +1 -0
- package/dist/utils/zip-write.d.mts +18 -0
- package/dist/utils/zip-write.mjs +188 -0
- package/dist/utils/zip-write.mjs.map +1 -0
- package/dist/utils.d.mts +17 -3
- package/dist/utils.mjs +18 -4
- package/package.json +32 -23
- package/dist/adapter/capture-runner.mjs.map +0 -1
- package/dist/adapter/git-host/comments.d.mts +0 -41
- package/dist/adapter/git-host/comments.mjs +0 -54
- package/dist/adapter/git-host/comments.mjs.map +0 -1
- package/dist/adapter/git-host/helpers.d.mts +0 -13
- package/dist/adapter/git-host/helpers.mjs +0 -30
- package/dist/adapter/git-host/helpers.mjs.map +0 -1
- package/dist/adapter/git-host.d.mts +0 -42
- package/dist/capture/queue.d.mts +0 -38
- package/dist/capture/queue.mjs +0 -98
- package/dist/capture/queue.mjs.map +0 -1
- package/dist/capture/recovery.d.mts +0 -21
- package/dist/capture/recovery.mjs +0 -83
- package/dist/capture/recovery.mjs.map +0 -1
- package/dist/capture/status-fanout.mjs +0 -72
- package/dist/capture/status-fanout.mjs.map +0 -1
- package/dist/diff/engine.d.mts +0 -16
- package/dist/diff/engine.mjs +0 -87
- package/dist/diff/engine.mjs.map +0 -1
- package/dist/diff/options.d.mts +0 -42
- package/dist/diff/options.mjs +0 -15
- package/dist/diff/options.mjs.map +0 -1
- package/dist/models/status-config.d.mts +0 -38
- package/dist/models/status-config.mjs +0 -63
- package/dist/models/status-config.mjs.map +0 -1
- package/dist/schema/status-config.d.mts +0 -16
- package/dist/test-helpers/git-contract.d.mts +0 -11
- package/dist/test-helpers/git-contract.mjs +0 -34
- package/dist/test-helpers/git-contract.mjs.map +0 -1
- /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import {
|
|
4
|
+
import { DEFAULT_BROWSER } from "../adapters/capture-target.mjs";
|
|
5
5
|
import { emitAttemptLog } from "../models/capture-attempt.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { BuildModel } from "../models/build.mjs";
|
|
7
|
+
import { diffPath } from "../paths.mjs";
|
|
8
|
+
import { storeScreenshot } from "../utils/screenshot-store.mjs";
|
|
7
9
|
import { BaselineModel } from "../models/baseline.mjs";
|
|
10
|
+
import { assertScreenshotBuffer } from "./guards.mjs";
|
|
8
11
|
import { NO_BASELINE } from "../models/baseline-status.mjs";
|
|
9
12
|
import { SnapshotModel } from "../models/snapshot.mjs";
|
|
13
|
+
import { mapLimit } from "../utils/map-limit.mjs";
|
|
10
14
|
import { diffStoredScreenshots, writeDiffOverlay } from "./diff-record.mjs";
|
|
11
|
-
import {
|
|
12
|
-
import { SIZING_DEFAULTS, infraHashFor } from "./sizing.mjs";
|
|
13
|
-
//#region src/capture/pipeline.ts
|
|
15
|
+
import { buildFingerprint, changedFields, fingerprintHash, mergeFingerprint, parseFingerprint } from "./fingerprint.mjs";
|
|
14
16
|
/**
|
|
15
17
|
* Persist a completed capture run: write screenshots, diff against the branch
|
|
16
18
|
* baseline, create snapshots, and finalize the build.
|
|
@@ -24,13 +26,24 @@ import { SIZING_DEFAULTS, infraHashFor } from "./sizing.mjs";
|
|
|
24
26
|
* @param flakyFailedStoryIds - Story ids that failed but are marked flaky
|
|
25
27
|
* (non-blocking). They are logged as warnings and do not block the build.
|
|
26
28
|
*/
|
|
27
|
-
async function persistCapture(
|
|
29
|
+
async function persistCapture(input, renderFailedStoryIds = /* @__PURE__ */ new Set(), flakyFailedStoryIds = /* @__PURE__ */ new Set(), a11yFailedStoryIds = /* @__PURE__ */ new Set()) {
|
|
30
|
+
const sets = await persistRendered(input, renderFailedStoryIds, flakyFailedStoryIds, a11yFailedStoryIds);
|
|
31
|
+
for (const id of await persistInherited(input)) sets.failed.add(id);
|
|
32
|
+
await finalizeBuild(input, allStoryIds(input), sets.failed, sets.flaky, sets.a11y);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Persist the rendered captures only: screenshots, diffs and snapshot rows, without the
|
|
36
|
+
* inherited snapshots or the build finalize. A sharded capture runs this once per shard.
|
|
37
|
+
*/
|
|
38
|
+
async function persistRendered(input, renderFailedStoryIds = /* @__PURE__ */ new Set(), flakyFailedStoryIds = /* @__PURE__ */ new Set(), a11yFailedStoryIds = /* @__PURE__ */ new Set()) {
|
|
39
|
+
const ctx = await withFreshBrowsers(input);
|
|
28
40
|
const failedStoryIds = new Set(renderFailedStoryIds);
|
|
29
41
|
const flakyIds = new Set(flakyFailedStoryIds);
|
|
30
42
|
const a11yIds = new Set(a11yFailedStoryIds);
|
|
31
|
-
|
|
43
|
+
for (const group of capturesByPhase(ctx)) await mapLimit(group, 4, async (capture) => {
|
|
32
44
|
try {
|
|
33
45
|
await persistSnapshot(ctx, capture);
|
|
46
|
+
ctx.progress?.();
|
|
34
47
|
} catch (error) {
|
|
35
48
|
if (flakyIds.has(capture.story.id) || a11yIds.has(capture.story.id)) {
|
|
36
49
|
if (flakyIds.has(capture.story.id)) flakyIds.add(capture.story.id);
|
|
@@ -55,7 +68,7 @@ async function persistCapture(ctx, renderFailedStoryIds = /* @__PURE__ */ new Se
|
|
|
55
68
|
viewport: capture.viewportName
|
|
56
69
|
});
|
|
57
70
|
}
|
|
58
|
-
})
|
|
71
|
+
});
|
|
59
72
|
for (const id of flakyIds) if (!failedStoryIds.has(id)) {
|
|
60
73
|
ctx.logger?.warn({ storyId: id }, "flaky story failed (non-blocking)");
|
|
61
74
|
await emitAttemptLog(ctx.recordLog, ctx.logger, "warn", "flaky story failed (non-blocking)", { storyId: id });
|
|
@@ -64,8 +77,62 @@ async function persistCapture(ctx, renderFailedStoryIds = /* @__PURE__ */ new Se
|
|
|
64
77
|
ctx.logger?.warn({ storyId: id }, "a11y violations found (non-blocking)");
|
|
65
78
|
await emitAttemptLog(ctx.recordLog, ctx.logger, "warn", "a11y violations found (non-blocking)", { storyId: id });
|
|
66
79
|
}
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
return {
|
|
81
|
+
failed: failedStoryIds,
|
|
82
|
+
flaky: flakyIds,
|
|
83
|
+
a11y: a11yIds
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Engines of this run, primary (first) first. */
|
|
87
|
+
function runBrowsers(ctx) {
|
|
88
|
+
const listed = ctx.browsers && ctx.browsers.length > 0 ? ctx.browsers : [DEFAULT_BROWSER];
|
|
89
|
+
return [...new Set(listed)];
|
|
90
|
+
}
|
|
91
|
+
function primaryBrowser(ctx) {
|
|
92
|
+
return runBrowsers(ctx)[0] ?? "chromium";
|
|
93
|
+
}
|
|
94
|
+
/** The capture's axe violation texts as the JSON stored on the snapshot; null when there are none. */
|
|
95
|
+
function a11yJsonOf(capture) {
|
|
96
|
+
return capture.a11y && capture.a11y.length > 0 ? JSON.stringify(capture.a11y) : null;
|
|
97
|
+
}
|
|
98
|
+
function browserOf(capture) {
|
|
99
|
+
return capture.browser ?? "chromium";
|
|
100
|
+
}
|
|
101
|
+
/** Captures split into the primary engine's, then everyone else's. */
|
|
102
|
+
function capturesByPhase(ctx) {
|
|
103
|
+
const primary = primaryBrowser(ctx);
|
|
104
|
+
const first = ctx.captures.filter((capture) => browserOf(capture) === primary);
|
|
105
|
+
const rest = ctx.captures.filter((capture) => browserOf(capture) !== primary);
|
|
106
|
+
return rest.length > 0 ? [first, rest] : [first];
|
|
107
|
+
}
|
|
108
|
+
/** Secondary engines with no baseline yet (their first snapshots bootstrap from the primary). */
|
|
109
|
+
async function freshBrowsersOf(ctx) {
|
|
110
|
+
const primary = primaryBrowser(ctx);
|
|
111
|
+
const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);
|
|
112
|
+
const secondary = runBrowsers(ctx).filter((browser) => browser !== primary);
|
|
113
|
+
const checks = await Promise.all(secondary.map(async (browser) => ({
|
|
114
|
+
browser,
|
|
115
|
+
fresh: !await baselines.hasAny(ctx.project.id, browser)
|
|
116
|
+
})));
|
|
117
|
+
return new Set(checks.filter((check) => check.fresh).map((check) => check.browser));
|
|
118
|
+
}
|
|
119
|
+
/** Mark secondary engines with no baseline yet: their first snapshots bootstrap. */
|
|
120
|
+
async function withFreshBrowsers(ctx) {
|
|
121
|
+
return ctx.freshBrowsers ? ctx : {
|
|
122
|
+
...ctx,
|
|
123
|
+
freshBrowsers: await freshBrowsersOf(ctx)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A newly enabled engine accepts a story/mode's first snapshot when the
|
|
128
|
+
* primary engine's snapshot of it is clean (Chromatic's "auto-accepted where
|
|
129
|
+
* the primary browser was accepted"); anything else goes to review as `new`.
|
|
130
|
+
*/
|
|
131
|
+
async function bootstrapAccepts(ctx, capture) {
|
|
132
|
+
const browser = browserOf(capture);
|
|
133
|
+
if (!ctx.freshBrowsers?.has(browser)) return false;
|
|
134
|
+
const primary = primaryBrowser(ctx);
|
|
135
|
+
return (await new SnapshotModel(ctx.db, ctx.tables).listByBuild(ctx.build.id)).some((snapshot) => snapshot.storyId === capture.story.id && snapshot.viewportName === capture.viewportName && (snapshot.browser ?? "chromium") === primary && (snapshot.status === "approved" || snapshot.status === "unchanged"));
|
|
69
136
|
}
|
|
70
137
|
/** Persist one inherited snapshot; returns the story id on failure. */
|
|
71
138
|
async function tryPersistInherited(ctx, inherited) {
|
|
@@ -86,24 +153,33 @@ async function tryPersistInherited(ctx, inherited) {
|
|
|
86
153
|
}
|
|
87
154
|
}
|
|
88
155
|
/** Persist inherited snapshots; a failure marks the story failed. */
|
|
156
|
+
/** Persist the inherited snapshots (stories reusing their baseline); returns the story ids that failed. */
|
|
89
157
|
async function persistInherited(ctx) {
|
|
90
|
-
const outcomes = await
|
|
158
|
+
const outcomes = await mapLimit(ctx.inherited ?? [], 4, async (inherited) => {
|
|
159
|
+
const failed = await tryPersistInherited(ctx, inherited);
|
|
160
|
+
ctx.progress?.();
|
|
161
|
+
return failed;
|
|
162
|
+
});
|
|
91
163
|
return new Set(outcomes.filter((id) => id !== null));
|
|
92
164
|
}
|
|
93
165
|
/** Create an unchanged snapshot reusing the baseline screenshot. */
|
|
94
166
|
async function persistInheritedSnapshot(ctx, inherited) {
|
|
95
|
-
const
|
|
167
|
+
const browser = browserOf(inherited);
|
|
168
|
+
const infraHash = inherited.baseline.infraHash;
|
|
96
169
|
const snapshots = new SnapshotModel(ctx.db, ctx.tables);
|
|
97
170
|
const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
|
|
171
|
+
browser,
|
|
98
172
|
storyId: inherited.story.id,
|
|
99
173
|
storyName: inherited.story.name,
|
|
100
174
|
storyTitle: inherited.story.title,
|
|
101
175
|
storyImportPath: inherited.story.importPath ?? "",
|
|
176
|
+
designUrl: inherited.story.designUrl ?? null,
|
|
102
177
|
viewportName: inherited.viewport.name,
|
|
103
178
|
viewportWidth: inherited.viewport.width,
|
|
104
179
|
viewportHeight: inherited.viewport.height,
|
|
105
180
|
screenshotPath: inherited.baseline.screenshotPath,
|
|
106
181
|
infraHash,
|
|
182
|
+
mode: modeJsonOf(inherited.viewport),
|
|
107
183
|
inherited: true,
|
|
108
184
|
baselineId: inherited.baseline.id,
|
|
109
185
|
baselineVersion: inherited.baseline.updatedAt
|
|
@@ -123,6 +199,10 @@ function allStoryIds(ctx) {
|
|
|
123
199
|
for (const inherited of ctx.inherited ?? []) ids.add(inherited.story.id);
|
|
124
200
|
return ids;
|
|
125
201
|
}
|
|
202
|
+
/** JSON for the snapshot `mode` column: the globals a mode applied (null for plain viewports). */
|
|
203
|
+
function modeJsonOf(viewport) {
|
|
204
|
+
return viewport?.globals ? JSON.stringify(viewport.globals) : null;
|
|
205
|
+
}
|
|
126
206
|
function viewportByName(ctx, name) {
|
|
127
207
|
return ctx.viewports.find((v) => v.name === name) ?? {
|
|
128
208
|
name,
|
|
@@ -130,77 +210,194 @@ function viewportByName(ctx, name) {
|
|
|
130
210
|
height: 0
|
|
131
211
|
};
|
|
132
212
|
}
|
|
133
|
-
|
|
213
|
+
/** Where the capture's screenshot lives: already stored by the orchestrator, or stored now. */
|
|
214
|
+
async function screenshotLocation(ctx, capture) {
|
|
215
|
+
if (capture.storedPath) return capture.storedPath;
|
|
134
216
|
assertScreenshotBuffer(capture.screenshot);
|
|
217
|
+
return await storeScreenshot(ctx.storage, ctx.project.id, capture.screenshot);
|
|
218
|
+
}
|
|
219
|
+
async function persistSnapshot(ctx, capture) {
|
|
135
220
|
const viewport = capture.viewport ?? viewportByName(ctx, capture.viewportName);
|
|
136
|
-
const
|
|
137
|
-
await ctx
|
|
138
|
-
const baseline = await resolveBaseline(ctx, capture.story.id, capture.viewportName);
|
|
221
|
+
const browser = browserOf(capture);
|
|
222
|
+
const screenshot = await screenshotLocation(ctx, capture);
|
|
223
|
+
const baseline = await resolveBaseline(ctx, capture.story.id, capture.viewportName, browser);
|
|
139
224
|
if (!baseline) {
|
|
140
225
|
await createWithoutBaseline(ctx, capture, viewport, screenshot);
|
|
141
226
|
return;
|
|
142
227
|
}
|
|
143
228
|
await createWithBaseline(ctx, capture, viewport, screenshot, baseline);
|
|
144
229
|
}
|
|
145
|
-
async function resolveBaseline(ctx, storyId, viewport) {
|
|
146
|
-
return await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).resolve(ctx.project.id, storyId, viewport, ctx.build.gitBranch, ctx.project.gitDefaultBranch);
|
|
230
|
+
async function resolveBaseline(ctx, storyId, viewport, browser) {
|
|
231
|
+
return await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).resolve(ctx.project.id, storyId, viewport, ctx.build.gitBranch, ctx.project.gitDefaultBranch, browser);
|
|
147
232
|
}
|
|
148
233
|
async function createWithoutBaseline(ctx, capture, viewport, screenshot) {
|
|
149
234
|
const snapshots = new SnapshotModel(ctx.db, ctx.tables);
|
|
150
|
-
const
|
|
151
|
-
const
|
|
235
|
+
const browser = browserOf(capture);
|
|
236
|
+
const accepted = ctx.build.isDefault || await bootstrapAccepts(ctx, capture);
|
|
237
|
+
const status = accepted ? "approved" : "new";
|
|
238
|
+
const now = fingerprintOf(capture, viewport);
|
|
152
239
|
const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
|
|
240
|
+
browser,
|
|
241
|
+
durationMs: capture.durationMs ?? null,
|
|
242
|
+
a11yViolations: a11yJsonOf(capture),
|
|
153
243
|
storyId: capture.story.id,
|
|
154
244
|
storyName: capture.story.name,
|
|
155
245
|
storyTitle: capture.story.title,
|
|
156
246
|
storyImportPath: capture.story.importPath ?? "",
|
|
247
|
+
designUrl: capture.story.designUrl ?? null,
|
|
157
248
|
viewportName: capture.viewportName,
|
|
158
249
|
viewportWidth: viewport.width,
|
|
159
250
|
viewportHeight: viewport.height,
|
|
160
251
|
screenshotPath: screenshot,
|
|
161
|
-
infraHash,
|
|
252
|
+
infraHash: now.infraHash,
|
|
253
|
+
mode: modeJsonOf(viewport),
|
|
162
254
|
baselineVersion: NO_BASELINE
|
|
163
255
|
});
|
|
164
256
|
await snapshots.setStatus(snapshot.id, status);
|
|
165
|
-
if (
|
|
257
|
+
if (accepted) await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).upsert(ctx.project.id, capture.story.id, capture.viewportName, ctx.build.gitBranch, snapshot.id, screenshot, now.infraHash, {
|
|
258
|
+
mode: modeJsonOf(viewport),
|
|
259
|
+
browser,
|
|
260
|
+
fingerprint: now.json
|
|
261
|
+
});
|
|
166
262
|
}
|
|
263
|
+
function fingerprintOf(capture, viewport) {
|
|
264
|
+
return captureFingerprintOf(buildFingerprint(browserOf(capture), viewport, capture.environment));
|
|
265
|
+
}
|
|
266
|
+
/** A fingerprint as stored: its hash (the `infraHash`) and JSON. */
|
|
267
|
+
function captureFingerprintOf(fingerprint) {
|
|
268
|
+
return {
|
|
269
|
+
fingerprint,
|
|
270
|
+
infraHash: fingerprintHash(fingerprint),
|
|
271
|
+
json: JSON.stringify(fingerprint)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* What changed in the capture environment since the baseline was recorded, or null when nothing
|
|
276
|
+
* did. Compared component by component, only where both sides report a value: a capture with no
|
|
277
|
+
* environment (spooled before a browser crash) is unknown, not a change. A baseline from before
|
|
278
|
+
* fingerprints cannot be compared, so it is never reported as changed; it is backfilled instead.
|
|
279
|
+
*/
|
|
280
|
+
function environmentChange(baseline, now) {
|
|
281
|
+
const from = parseFingerprint(baseline.fingerprint);
|
|
282
|
+
if (!from) return null;
|
|
283
|
+
const changed = changedFields(from, now.fingerprint);
|
|
284
|
+
return changed.length > 0 ? {
|
|
285
|
+
changed,
|
|
286
|
+
from,
|
|
287
|
+
to: now.fingerprint
|
|
288
|
+
} : null;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* A changed environment migrates the baseline without a diff only on the default branch, which is
|
|
292
|
+
* authoritative and auto-approves its own changes anyway. A feature branch still diffs: a real
|
|
293
|
+
* change is reported as `changed` with the environment change attached, never auto-accepted.
|
|
294
|
+
*/
|
|
167
295
|
async function createWithBaseline(ctx, capture, viewport, screenshot, baseline) {
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
296
|
+
const now = fingerprintOf(capture, viewport);
|
|
297
|
+
const change = environmentChange(baseline, now);
|
|
298
|
+
const input = {
|
|
299
|
+
ctx,
|
|
300
|
+
capture,
|
|
301
|
+
viewport,
|
|
302
|
+
screenshot,
|
|
303
|
+
baseline,
|
|
304
|
+
now,
|
|
305
|
+
change
|
|
306
|
+
};
|
|
307
|
+
if (change && ctx.build.isDefault) await persistMigration(input, change);
|
|
308
|
+
else await persistDiff(input);
|
|
309
|
+
}
|
|
310
|
+
/** The fingerprint to write on the baseline: the capture's, keeping components it did not report. */
|
|
311
|
+
function baselineFingerprintOf(input) {
|
|
312
|
+
const earlier = parseFingerprint(input.baseline.fingerprint);
|
|
313
|
+
return captureFingerprintOf(mergeFingerprint(earlier, input.now.fingerprint));
|
|
314
|
+
}
|
|
315
|
+
function snapshotInput(input) {
|
|
316
|
+
const { capture, viewport, screenshot, baseline, now } = input;
|
|
317
|
+
return {
|
|
318
|
+
browser: browserOf(capture),
|
|
319
|
+
durationMs: capture.durationMs ?? null,
|
|
320
|
+
a11yViolations: a11yJsonOf(capture),
|
|
172
321
|
storyId: capture.story.id,
|
|
173
322
|
storyName: capture.story.name,
|
|
174
323
|
storyTitle: capture.story.title,
|
|
175
324
|
storyImportPath: capture.story.importPath ?? "",
|
|
325
|
+
designUrl: capture.story.designUrl ?? null,
|
|
176
326
|
viewportName: capture.viewportName,
|
|
177
327
|
viewportWidth: viewport.width,
|
|
178
328
|
viewportHeight: viewport.height,
|
|
179
329
|
screenshotPath: screenshot,
|
|
180
|
-
infraHash,
|
|
330
|
+
infraHash: now.infraHash,
|
|
331
|
+
mode: modeJsonOf(viewport),
|
|
181
332
|
baselineId: baseline.id,
|
|
182
333
|
baselineVersion: baseline.updatedAt
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* The default branch's capture environment changed (browser build, fonts, platform, scale): pixels
|
|
338
|
+
* would differ with no code change, so no diff is computed. The snapshot is accepted, records what
|
|
339
|
+
* changed, and the new screenshot becomes the baseline.
|
|
340
|
+
*/
|
|
341
|
+
async function persistMigration(input, change) {
|
|
342
|
+
const { ctx, capture, viewport, screenshot, now } = input;
|
|
343
|
+
const snapshots = new SnapshotModel(ctx.db, ctx.tables);
|
|
344
|
+
const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
|
|
345
|
+
...snapshotInput(input),
|
|
346
|
+
envChange: JSON.stringify(change)
|
|
183
347
|
});
|
|
184
|
-
const shouldAutomigrate = result.sizeChanged && !result.passed && ctx.project.automigrate && baseline.infraHash !== null && baseline.infraHash !== infraHash;
|
|
185
|
-
const status = shouldAutomigrate ? "unchanged" : diffStatus(result.passed, ctx.build.isDefault);
|
|
186
|
-
const diff = diffPath(ctx.project.id, ctx.build.id, capture.story.id, capture.viewportName);
|
|
187
|
-
const passed = shouldAutomigrate ? true : result.passed;
|
|
188
|
-
const pixels = shouldAutomigrate ? 0 : result.diffPixels;
|
|
189
|
-
const ratio = shouldAutomigrate ? 0 : result.diffRatio;
|
|
190
|
-
const overlay = await writeDiffOverlay(ctx.storage, diff, result, passed);
|
|
191
348
|
await snapshots.update(snapshot.id, {
|
|
192
|
-
status,
|
|
349
|
+
status: "unchanged",
|
|
350
|
+
diffPath: null,
|
|
351
|
+
diffPixels: 0,
|
|
352
|
+
diffRatio: 0,
|
|
353
|
+
diffPassed: true,
|
|
354
|
+
infraHash: now.infraHash
|
|
355
|
+
});
|
|
356
|
+
await upsertDefaultBaseline(ctx, snapshot.id, capture, screenshot, baselineFingerprintOf(input));
|
|
357
|
+
ctx.logger?.debug({
|
|
358
|
+
storyId: capture.story.id,
|
|
359
|
+
viewport: viewport.name,
|
|
360
|
+
changed: change.changed
|
|
361
|
+
}, "capture environment changed; accepted without a diff");
|
|
362
|
+
}
|
|
363
|
+
/** Diff against the baseline; a feature-branch environment change rides along as `envChange`. */
|
|
364
|
+
async function persistDiff(input) {
|
|
365
|
+
const { ctx, capture, screenshot, baseline, now, change } = input;
|
|
366
|
+
const result = await diffStoredScreenshots(ctx.storage, ctx.project, baseline.screenshotPath, screenshot, capture.story.parameters?.diffThreshold);
|
|
367
|
+
const snapshots = new SnapshotModel(ctx.db, ctx.tables);
|
|
368
|
+
const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
|
|
369
|
+
...snapshotInput(input),
|
|
370
|
+
...change ? { envChange: JSON.stringify(change) } : {}
|
|
371
|
+
});
|
|
372
|
+
const diff = diffPath(ctx.project.id, ctx.build.id, capture.story.id, capture.viewportName, browserOf(capture));
|
|
373
|
+
const overlay = await writeDiffOverlay(ctx.storage, diff, result, result.passed);
|
|
374
|
+
await snapshots.update(snapshot.id, {
|
|
375
|
+
status: diffStatus(result.passed, ctx.build.isDefault),
|
|
193
376
|
diffPath: overlay,
|
|
194
|
-
diffPixels:
|
|
195
|
-
diffRatio:
|
|
196
|
-
diffPassed: passed,
|
|
197
|
-
infraHash
|
|
377
|
+
diffPixels: result.diffPixels,
|
|
378
|
+
diffRatio: result.diffRatio,
|
|
379
|
+
diffPassed: result.passed,
|
|
380
|
+
infraHash: now.infraHash
|
|
198
381
|
});
|
|
199
|
-
if (ctx.build.isDefault
|
|
382
|
+
if (ctx.build.isDefault) await refreshDefaultBaseline(input, snapshot.id, result.passed);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* After a default-branch diff: a failed diff moves the baseline to the new screenshot; a passing
|
|
386
|
+
* one backfills the fingerprint when it was missing or had components the capture now reports.
|
|
387
|
+
*/
|
|
388
|
+
async function refreshDefaultBaseline(input, snapshotId, passed) {
|
|
389
|
+
const { ctx, capture, screenshot, baseline } = input;
|
|
390
|
+
const target = baselineFingerprintOf(input);
|
|
391
|
+
if (!passed) await upsertDefaultBaseline(ctx, snapshotId, capture, screenshot, target);
|
|
392
|
+
else if (!baseline.fingerprint || baseline.infraHash !== target.infraHash) await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).backfillFingerprint(baseline, target.infraHash, target.json);
|
|
200
393
|
}
|
|
201
394
|
/** Default-branch builds are authoritative: the captured screenshot becomes the baseline. */
|
|
202
|
-
async function upsertDefaultBaseline(ctx, snapshotId, capture, screenshot,
|
|
203
|
-
await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).upsert(ctx.project.id, capture.story.id, capture.viewportName, ctx.build.gitBranch, snapshotId, screenshot, infraHash
|
|
395
|
+
async function upsertDefaultBaseline(ctx, snapshotId, capture, screenshot, now) {
|
|
396
|
+
await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).upsert(ctx.project.id, capture.story.id, capture.viewportName, ctx.build.gitBranch, snapshotId, screenshot, now.infraHash, {
|
|
397
|
+
mode: modeJsonOf(capture.viewport),
|
|
398
|
+
browser: browserOf(capture),
|
|
399
|
+
fingerprint: now.json
|
|
400
|
+
});
|
|
204
401
|
}
|
|
205
402
|
/** Snapshot status after diffing: default-branch changes are auto-approved, not queued for review. */
|
|
206
403
|
function diffStatus(passed, isDefault) {
|
|
@@ -213,8 +410,16 @@ function finalBuildStatus(isDefault, failedCount, hasCaptures, build) {
|
|
|
213
410
|
if (isDefault || hasCaptures && build.changedCount === 0) return "approved";
|
|
214
411
|
return "reviewing";
|
|
215
412
|
}
|
|
216
|
-
|
|
217
|
-
|
|
413
|
+
/**
|
|
414
|
+
* Close out a capture: roll the snapshot counts up, set the build's final status from the failed
|
|
415
|
+
* story ids, and (on the default branch) drop baselines of stories that no longer exist.
|
|
416
|
+
*/
|
|
417
|
+
async function finalizeBuild(ctx, storyIds, failedStoryIds, flakyFailedStoryIds = /* @__PURE__ */ new Set(), a11yFailedStoryIds = /* @__PURE__ */ new Set()) {
|
|
418
|
+
const builds = new BuildModel(ctx.db, ctx.tables, ctx.logger);
|
|
419
|
+
if ((await builds.get(ctx.build.id))?.status === "cancelled") {
|
|
420
|
+
ctx.logger?.debug({ buildId: ctx.build.id }, "build is cancelled; finalize skipped");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
218
423
|
const build = await builds.updateCounts(ctx.build.id);
|
|
219
424
|
const hasCaptures = storyIds.size > 0;
|
|
220
425
|
const status = finalBuildStatus(ctx.build.isDefault, failedStoryIds.size, hasCaptures, build);
|
|
@@ -230,6 +435,6 @@ async function finalize(ctx, storyIds, failedStoryIds, flakyFailedStoryIds = /*
|
|
|
230
435
|
if (ctx.build.isDefault && hasCaptures) await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).removeOrphans(ctx.project.id, new Set(storyIds));
|
|
231
436
|
}
|
|
232
437
|
//#endregion
|
|
233
|
-
export { diffStatus, persistCapture };
|
|
438
|
+
export { diffStatus, finalizeBuild, freshBrowsersOf, persistCapture, persistInherited, persistRendered };
|
|
234
439
|
|
|
235
440
|
//# sourceMappingURL=pipeline.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.mjs","names":[],"sources":["../../src/capture/pipeline.ts"],"sourcesContent":["import type { Logger } from \"pino\";\nimport type { RenderedSnapshot } from \"../adapters/capture-runner.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { NO_BASELINE } from \"../models/baseline-status.ts\";\nimport { BaselineModel, type BaselineTables } from \"../models/baseline.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { emitAttemptLog, type AttemptLogRecorder } from \"../models/capture-attempt.ts\";\nimport { SnapshotModel, type SnapshotTables } from \"../models/snapshot.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport type { BuildStatus, SnapshotStatus } from \"../types.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { diffPath, screenshotPath } from \"../utils/paths.ts\";\nimport type { Viewport } from \"./adapter.ts\";\nimport type { InheritedStory } from \"./affected.ts\";\nimport { diffStoredScreenshots, writeDiffOverlay } from \"./diff-record.ts\";\nimport { assertScreenshotBuffer } from \"./guards.ts\";\nimport { infraHashFor, SIZING_DEFAULTS } from \"./sizing.ts\";\n\n/**\n * Persist a completed capture run: write screenshots, diff against the branch\n * baseline, create snapshots, and finalize the build.\n *\n * Rendering is performed by a pure `CaptureRunner`; this function owns only\n * storage writes and record keeping, so it can run against any renderer.\n *\n * @param ctx - Capture context.\n * @param renderFailedStoryIds - Story ids that the renderer could not capture\n * (blocking failures). They force the build to `failed`.\n * @param flakyFailedStoryIds - Story ids that failed but are marked flaky\n * (non-blocking). They are logged as warnings and do not block the build.\n */\nexport async function persistCapture(\n ctx: CaptureContext,\n renderFailedStoryIds: ReadonlySet<string> = new Set(),\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<void> {\n const failedStoryIds = new Set(renderFailedStoryIds);\n const flakyIds = new Set(flakyFailedStoryIds);\n const a11yIds = new Set(a11yFailedStoryIds);\n await Promise.all(\n ctx.captures.map(async (capture) => {\n try {\n await persistSnapshot(ctx, capture);\n } catch (error) {\n // A failure in one story/viewport must not abort the other persists.\n // Flaky and a11y stories remain non-blocking even on persist failure.\n if (flakyIds.has(capture.story.id) || a11yIds.has(capture.story.id)) {\n if (flakyIds.has(capture.story.id)) flakyIds.add(capture.story.id);\n if (a11yIds.has(capture.story.id)) a11yIds.add(capture.story.id);\n ctx.logger?.warn(\n { storyId: capture.story.id, viewport: capture.viewportName, err: error },\n \"capture failed for flaky/a11y story (non-blocking)\",\n );\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"capture failed for flaky/a11y story (non-blocking)\",\n {\n storyId: capture.story.id,\n viewport: capture.viewportName,\n },\n );\n } else {\n failedStoryIds.add(capture.story.id);\n }\n ctx.logger?.error(\n { storyId: capture.story.id, viewport: capture.viewportName, err: error },\n \"capture failed for story\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"error\", \"capture failed for story\", {\n storyId: capture.story.id,\n viewport: capture.viewportName,\n });\n }\n }),\n );\n // Also log flaky and a11y render failures as warnings\n for (const id of flakyIds) {\n if (!failedStoryIds.has(id)) {\n ctx.logger?.warn({ storyId: id }, \"flaky story failed (non-blocking)\");\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"warn\", \"flaky story failed (non-blocking)\", {\n storyId: id,\n });\n }\n }\n for (const id of a11yIds) {\n if (!failedStoryIds.has(id) && !flakyIds.has(id)) {\n ctx.logger?.warn({ storyId: id }, \"a11y violations found (non-blocking)\");\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"a11y violations found (non-blocking)\",\n {\n storyId: id,\n },\n );\n }\n }\n for (const id of await persistInherited(ctx)) {\n failedStoryIds.add(id);\n }\n await finalize(ctx, allStoryIds(ctx), failedStoryIds, flakyIds, a11yIds);\n}\n\n/** Persist one inherited snapshot; returns the story id on failure. */\nasync function tryPersistInherited(\n ctx: CaptureContext,\n inherited: InheritedStory,\n): Promise<string | null> {\n try {\n await persistInheritedSnapshot(ctx, inherited);\n return null;\n } catch (error) {\n ctx.logger?.error(\n { storyId: inherited.story.id, viewport: inherited.viewport.name, err: error },\n \"inherit failed for story\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"error\", \"inherit failed for story\", {\n storyId: inherited.story.id,\n viewport: inherited.viewport.name,\n });\n return inherited.story.id;\n }\n}\n\n/** Persist inherited snapshots; a failure marks the story failed. */\nasync function persistInherited(ctx: CaptureContext): Promise<ReadonlySet<string>> {\n const outcomes = await Promise.all(\n (ctx.inherited ?? []).map(async (inherited) => await tryPersistInherited(ctx, inherited)),\n );\n return new Set(outcomes.filter((id): id is string => id !== null));\n}\n\n/** Create an unchanged snapshot reusing the baseline screenshot. */\nasync function persistInheritedSnapshot(\n ctx: CaptureContext,\n inherited: InheritedStory,\n): Promise<void> {\n const infraHash = infraHashFor(ctx.project.browser ?? \"chromium\", ctx.viewports, SIZING_DEFAULTS);\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n storyId: inherited.story.id,\n storyName: inherited.story.name,\n storyTitle: inherited.story.title,\n storyImportPath: inherited.story.importPath ?? \"\",\n viewportName: inherited.viewport.name,\n viewportWidth: inherited.viewport.width,\n viewportHeight: inherited.viewport.height,\n screenshotPath: inherited.baseline.screenshotPath,\n infraHash,\n inherited: true,\n baselineId: inherited.baseline.id,\n baselineVersion: inherited.baseline.updatedAt,\n });\n await snapshots.update(snapshot.id, {\n status: \"unchanged\",\n diffPixels: 0,\n diffRatio: 0,\n diffPassed: true,\n diffPath: null,\n infraHash,\n });\n}\n\n/** Every story covered by a capture run, rendered or inherited. */\nfunction allStoryIds(ctx: CaptureContext): ReadonlySet<string> {\n const ids = new Set(ctx.captures.map((capture) => capture.story.id));\n for (const inherited of ctx.inherited ?? []) {\n ids.add(inherited.story.id);\n }\n return ids;\n}\n\n/** Table handles required by the capture pipeline. */\nexport type PipelineTables = BuildTables & BaselineTables & SnapshotTables;\n\n/** Persistence inputs for a completed capture run. */\nexport interface CaptureContext {\n /** Database adapter. */\n db: DatabaseAdapter;\n /** Table handles. */\n tables: PipelineTables;\n /** Storage adapter. */\n storage: StorageAdapter;\n /** The project being captured. */\n project: Project;\n /** The build being captured. */\n build: Build;\n /** Viewports at which stories were captured. */\n viewports: Viewport[];\n /** Screenshot buffers produced by the capture renderer. */\n captures: RenderedSnapshot[];\n /** Stories inheriting their baseline without rendering (affected capture). */\n inherited?: InheritedStory[];\n /** Optional logger for capture diagnostics. */\n logger?: Logger;\n /** Optional per-attempt log recorder (mirrors logger lines into the DB). */\n recordLog?: AttemptLogRecorder;\n /** Server secret for decrypting webhook secrets at send time. */\n secret?: SecretInput;\n}\n\nfunction viewportByName(ctx: CaptureContext, name: string): Viewport {\n return ctx.viewports.find((v) => v.name === name) ?? { name, width: 0, height: 0 };\n}\n\nasync function persistSnapshot(ctx: CaptureContext, capture: RenderedSnapshot): Promise<void> {\n assertScreenshotBuffer(capture.screenshot);\n const viewport = capture.viewport ?? viewportByName(ctx, capture.viewportName);\n const screenshot = screenshotPath(\n ctx.project.id,\n ctx.build.id,\n capture.story.id,\n capture.viewportName,\n );\n await ctx.storage.write(screenshot, capture.screenshot);\n\n const baseline = await resolveBaseline(ctx, capture.story.id, capture.viewportName);\n if (!baseline) {\n await createWithoutBaseline(ctx, capture, viewport, screenshot);\n return;\n }\n await createWithBaseline(ctx, capture, viewport, screenshot, baseline);\n}\n\nasync function resolveBaseline(\n ctx: CaptureContext,\n storyId: string,\n viewport: string,\n): Promise<Baseline | null> {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n return await baselines.resolve(\n ctx.project.id,\n storyId,\n viewport,\n ctx.build.gitBranch,\n ctx.project.gitDefaultBranch,\n );\n}\n\nasync function createWithoutBaseline(\n ctx: CaptureContext,\n capture: RenderedSnapshot,\n viewport: Viewport,\n screenshot: string,\n): Promise<void> {\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const status = ctx.build.isDefault ? \"approved\" : \"new\";\n const infraHash = infraHashFor(ctx.project.browser ?? \"chromium\", ctx.viewports, SIZING_DEFAULTS);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n storyId: capture.story.id,\n storyName: capture.story.name,\n storyTitle: capture.story.title,\n storyImportPath: capture.story.importPath ?? \"\",\n viewportName: capture.viewportName,\n viewportWidth: viewport.width,\n viewportHeight: viewport.height,\n screenshotPath: screenshot,\n infraHash,\n baselineVersion: NO_BASELINE,\n });\n await snapshots.setStatus(snapshot.id, status);\n\n if (ctx.build.isDefault) {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.upsert(\n ctx.project.id,\n capture.story.id,\n capture.viewportName,\n ctx.build.gitBranch,\n snapshot.id,\n screenshot,\n infraHash,\n );\n }\n}\n\nasync function createWithBaseline(\n ctx: CaptureContext,\n capture: RenderedSnapshot,\n viewport: Viewport,\n screenshot: string,\n baseline: Baseline,\n): Promise<void> {\n const result = await diffStoredScreenshots(\n ctx.storage,\n ctx.project,\n baseline.screenshotPath,\n screenshot,\n capture.story.parameters?.diffThreshold,\n );\n\n const infraHash = infraHashFor(ctx.project.browser ?? \"chromium\", ctx.viewports, SIZING_DEFAULTS);\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n storyId: capture.story.id,\n storyName: capture.story.name,\n storyTitle: capture.story.title,\n storyImportPath: capture.story.importPath ?? \"\",\n viewportName: capture.viewportName,\n viewportWidth: viewport.width,\n viewportHeight: viewport.height,\n screenshotPath: screenshot,\n infraHash,\n baselineId: baseline.id,\n baselineVersion: baseline.updatedAt,\n });\n\n // Automigrate: infra-induced size change with project.automigrate enabled\n const shouldAutomigrate =\n result.sizeChanged &&\n !result.passed &&\n (ctx.project as unknown as { automigrate?: boolean }).automigrate &&\n baseline.infraHash !== null &&\n baseline.infraHash !== infraHash;\n\n const status = shouldAutomigrate ? \"unchanged\" : diffStatus(result.passed, ctx.build.isDefault);\n const diff = diffPath(ctx.project.id, ctx.build.id, capture.story.id, capture.viewportName);\n const passed = shouldAutomigrate ? true : result.passed;\n const pixels = shouldAutomigrate ? 0 : result.diffPixels;\n const ratio = shouldAutomigrate ? 0 : result.diffRatio;\n const overlay = await writeDiffOverlay(ctx.storage, diff, result, passed);\n await snapshots.update(snapshot.id, {\n status,\n diffPath: overlay,\n diffPixels: pixels,\n diffRatio: ratio,\n diffPassed: passed,\n infraHash,\n });\n if (ctx.build.isDefault && (shouldAutomigrate || !passed)) {\n await upsertDefaultBaseline(ctx, snapshot.id, capture, screenshot, infraHash);\n }\n}\n\n/** Default-branch builds are authoritative: the captured screenshot becomes the baseline. */\nasync function upsertDefaultBaseline(\n ctx: CaptureContext,\n snapshotId: string,\n capture: RenderedSnapshot,\n screenshot: string,\n infraHash: string,\n): Promise<void> {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.upsert(\n ctx.project.id,\n capture.story.id,\n capture.viewportName,\n ctx.build.gitBranch,\n snapshotId,\n screenshot,\n infraHash,\n );\n}\n\n/** Snapshot status after diffing: default-branch changes are auto-approved, not queued for review. */\nexport function diffStatus(passed: boolean, isDefault: boolean): SnapshotStatus {\n if (passed) {\n return \"unchanged\";\n }\n return isDefault ? \"approved\" : \"changed\";\n}\n\n/** A failed capture fails the build; default-branch builds otherwise auto-approve. */\nfunction finalBuildStatus(\n isDefault: boolean,\n failedCount: number,\n hasCaptures: boolean,\n build: { changedCount: number },\n): BuildStatus {\n if (failedCount > 0) {\n return \"failed\";\n }\n if (isDefault || (hasCaptures && build.changedCount === 0)) {\n return \"approved\";\n }\n return \"reviewing\";\n}\n\nasync function finalize(\n ctx: CaptureContext,\n storyIds: ReadonlySet<string>,\n failedStoryIds: ReadonlySet<string>,\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<void> {\n const builds = new BuildModel(ctx.db, ctx.tables);\n const build = await builds.updateCounts(ctx.build.id);\n const hasCaptures = storyIds.size > 0;\n const status = finalBuildStatus(ctx.build.isDefault, failedStoryIds.size, hasCaptures, build);\n // Flaky and a11y failures do not block: log warning if any failed non-blocking\n if (flakyFailedStoryIds.size > 0 && failedStoryIds.size === 0) {\n ctx.logger?.warn(\n { flakyStoryIds: [...flakyFailedStoryIds] },\n \"flaky stories failed (non-blocking)\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"warn\", \"flaky stories failed (non-blocking)\", {\n flakyStoryIds: [...flakyFailedStoryIds],\n });\n }\n if (a11yFailedStoryIds.size > 0 && failedStoryIds.size === 0) {\n ctx.logger?.warn(\n { a11yStoryIds: [...a11yFailedStoryIds] },\n \"a11y violations found (non-blocking)\",\n );\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"a11y violations found (non-blocking)\",\n {\n a11yStoryIds: [...a11yFailedStoryIds],\n },\n );\n }\n await builds.setStatus(ctx.build.id, status);\n\n if (ctx.build.isDefault && hasCaptures) {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.removeOrphans(ctx.project.id, new Set(storyIds));\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pipeline.mjs","names":[],"sources":["../../src/capture/pipeline.ts"],"sourcesContent":["import type { Logger } from \"pino\";\nimport { DEFAULT_BROWSER, type RenderedSnapshot } from \"../adapters/capture-runner.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { NO_BASELINE } from \"../models/baseline-status.ts\";\nimport { BaselineModel, type BaselineTables } from \"../models/baseline.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { emitAttemptLog, type AttemptLogRecorder } from \"../models/capture-attempt.ts\";\nimport { SnapshotModel, type SnapshotTables } from \"../models/snapshot.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport type { BuildStatus, SnapshotStatus } from \"../types.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { mapLimit } from \"../utils/map-limit.ts\";\nimport { diffPath } from \"../utils/paths.ts\";\nimport { storeScreenshot } from \"../utils/screenshot-store.ts\";\nimport type { Viewport } from \"./adapter.ts\";\nimport type { InheritedStory } from \"./affected.ts\";\nimport { diffStoredScreenshots, writeDiffOverlay } from \"./diff-record.ts\";\nimport {\n buildFingerprint,\n changedFields,\n fingerprintHash,\n mergeFingerprint,\n parseFingerprint,\n type EnvironmentChange,\n type Fingerprint,\n} from \"./fingerprint.ts\";\nimport { assertScreenshotBuffer } from \"./guards.ts\";\nimport type { CaptureProgress } from \"./progress.ts\";\n\n/** Snapshots persisted at once: each decodes two PNGs for the diff, so unbounded fan-out spikes memory. */\nexport const PERSIST_CONCURRENCY = 4;\n\n/**\n * Persist a completed capture run: write screenshots, diff against the branch\n * baseline, create snapshots, and finalize the build.\n *\n * Rendering is performed by a pure `CaptureRunner`; this function owns only\n * storage writes and record keeping, so it can run against any renderer.\n *\n * @param ctx - Capture context.\n * @param renderFailedStoryIds - Story ids that the renderer could not capture\n * (blocking failures). They force the build to `failed`.\n * @param flakyFailedStoryIds - Story ids that failed but are marked flaky\n * (non-blocking). They are logged as warnings and do not block the build.\n */\nexport async function persistCapture(\n input: CaptureContext,\n renderFailedStoryIds: ReadonlySet<string> = new Set(),\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<void> {\n const sets = await persistRendered(\n input,\n renderFailedStoryIds,\n flakyFailedStoryIds,\n a11yFailedStoryIds,\n );\n for (const id of await persistInherited(input)) {\n sets.failed.add(id);\n }\n await finalizeBuild(input, allStoryIds(input), sets.failed, sets.flaky, sets.a11y);\n}\n\n/** Blocking, flaky and a11y story ids a persist run ended with. */\nexport interface PersistedSets {\n failed: Set<string>;\n flaky: Set<string>;\n a11y: Set<string>;\n}\n\n/**\n * Persist the rendered captures only: screenshots, diffs and snapshot rows, without the\n * inherited snapshots or the build finalize. A sharded capture runs this once per shard.\n */\nexport async function persistRendered(\n input: CaptureContext,\n renderFailedStoryIds: ReadonlySet<string> = new Set(),\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<PersistedSets> {\n const ctx = await withFreshBrowsers(input);\n const failedStoryIds = new Set(renderFailedStoryIds);\n const flakyIds = new Set(flakyFailedStoryIds);\n const a11yIds = new Set(a11yFailedStoryIds);\n // Primary engine first: a newly enabled engine bootstraps from its counterpart.\n for (const group of capturesByPhase(ctx)) {\n // oxlint-disable-next-line no-await-in-loop -- phases must run in order\n await mapLimit(group, PERSIST_CONCURRENCY, async (capture) => {\n try {\n await persistSnapshot(ctx, capture);\n // Each diff is progress: a long, healthy persist phase must not read as a hung job.\n ctx.progress?.();\n } catch (error) {\n // A failure in one story/viewport must not abort the other persists.\n // Flaky and a11y stories remain non-blocking even on persist failure.\n if (flakyIds.has(capture.story.id) || a11yIds.has(capture.story.id)) {\n if (flakyIds.has(capture.story.id)) flakyIds.add(capture.story.id);\n if (a11yIds.has(capture.story.id)) a11yIds.add(capture.story.id);\n ctx.logger?.warn(\n { storyId: capture.story.id, viewport: capture.viewportName, err: error },\n \"capture failed for flaky/a11y story (non-blocking)\",\n );\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"capture failed for flaky/a11y story (non-blocking)\",\n {\n storyId: capture.story.id,\n viewport: capture.viewportName,\n },\n );\n } else {\n failedStoryIds.add(capture.story.id);\n }\n ctx.logger?.error(\n { storyId: capture.story.id, viewport: capture.viewportName, err: error },\n \"capture failed for story\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"error\", \"capture failed for story\", {\n storyId: capture.story.id,\n viewport: capture.viewportName,\n });\n }\n });\n }\n // Also log flaky and a11y render failures as warnings\n for (const id of flakyIds) {\n if (!failedStoryIds.has(id)) {\n ctx.logger?.warn({ storyId: id }, \"flaky story failed (non-blocking)\");\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"warn\", \"flaky story failed (non-blocking)\", {\n storyId: id,\n });\n }\n }\n for (const id of a11yIds) {\n if (!failedStoryIds.has(id) && !flakyIds.has(id)) {\n ctx.logger?.warn({ storyId: id }, \"a11y violations found (non-blocking)\");\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"a11y violations found (non-blocking)\",\n {\n storyId: id,\n },\n );\n }\n }\n return { failed: failedStoryIds, flaky: flakyIds, a11y: a11yIds };\n}\n\n/** Engines of this run, primary (first) first. */\nfunction runBrowsers(ctx: CaptureContext): string[] {\n const listed = ctx.browsers && ctx.browsers.length > 0 ? ctx.browsers : [DEFAULT_BROWSER];\n return [...new Set(listed)];\n}\n\nfunction primaryBrowser(ctx: CaptureContext): string {\n return runBrowsers(ctx)[0] ?? DEFAULT_BROWSER;\n}\n\n/** The capture's axe violation texts as the JSON stored on the snapshot; null when there are none. */\nfunction a11yJsonOf(capture: { a11y?: string[] }): string | null {\n return capture.a11y && capture.a11y.length > 0 ? JSON.stringify(capture.a11y) : null;\n}\n\nfunction browserOf(capture: { browser?: string }): string {\n return capture.browser ?? DEFAULT_BROWSER;\n}\n\n/** Captures split into the primary engine's, then everyone else's. */\nfunction capturesByPhase(ctx: CaptureContext): RenderedSnapshot[][] {\n const primary = primaryBrowser(ctx);\n const first = ctx.captures.filter((capture) => browserOf(capture) === primary);\n const rest = ctx.captures.filter((capture) => browserOf(capture) !== primary);\n return rest.length > 0 ? [first, rest] : [first];\n}\n\n/** Secondary engines with no baseline yet (their first snapshots bootstrap from the primary). */\nexport async function freshBrowsersOf(ctx: CaptureContext): Promise<Set<string>> {\n const primary = primaryBrowser(ctx);\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n const secondary = runBrowsers(ctx).filter((browser) => browser !== primary);\n const checks = await Promise.all(\n secondary.map(async (browser) => ({\n browser,\n fresh: !(await baselines.hasAny(ctx.project.id, browser)),\n })),\n );\n return new Set(checks.filter((check) => check.fresh).map((check) => check.browser));\n}\n\n/** Mark secondary engines with no baseline yet: their first snapshots bootstrap. */\nasync function withFreshBrowsers(ctx: CaptureContext): Promise<CaptureContext> {\n return ctx.freshBrowsers ? ctx : { ...ctx, freshBrowsers: await freshBrowsersOf(ctx) };\n}\n\n/**\n * A newly enabled engine accepts a story/mode's first snapshot when the\n * primary engine's snapshot of it is clean (Chromatic's \"auto-accepted where\n * the primary browser was accepted\"); anything else goes to review as `new`.\n */\nasync function bootstrapAccepts(ctx: CaptureContext, capture: RenderedSnapshot): Promise<boolean> {\n const browser = browserOf(capture);\n if (!ctx.freshBrowsers?.has(browser)) return false;\n const primary = primaryBrowser(ctx);\n const siblings = await new SnapshotModel(ctx.db, ctx.tables).listByBuild(ctx.build.id);\n return siblings.some(\n (snapshot) =>\n snapshot.storyId === capture.story.id &&\n snapshot.viewportName === capture.viewportName &&\n (snapshot.browser ?? DEFAULT_BROWSER) === primary &&\n (snapshot.status === \"approved\" || snapshot.status === \"unchanged\"),\n );\n}\n\n/** Persist one inherited snapshot; returns the story id on failure. */\nasync function tryPersistInherited(\n ctx: CaptureContext,\n inherited: InheritedStory,\n): Promise<string | null> {\n try {\n await persistInheritedSnapshot(ctx, inherited);\n return null;\n } catch (error) {\n ctx.logger?.error(\n { storyId: inherited.story.id, viewport: inherited.viewport.name, err: error },\n \"inherit failed for story\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"error\", \"inherit failed for story\", {\n storyId: inherited.story.id,\n viewport: inherited.viewport.name,\n });\n return inherited.story.id;\n }\n}\n\n/** Persist inherited snapshots; a failure marks the story failed. */\n/** Persist the inherited snapshots (stories reusing their baseline); returns the story ids that failed. */\nexport async function persistInherited(ctx: CaptureContext): Promise<ReadonlySet<string>> {\n const outcomes = await mapLimit(ctx.inherited ?? [], PERSIST_CONCURRENCY, async (inherited) => {\n const failed = await tryPersistInherited(ctx, inherited);\n ctx.progress?.();\n return failed;\n });\n return new Set(outcomes.filter((id): id is string => id !== null));\n}\n\n/** Create an unchanged snapshot reusing the baseline screenshot. */\nasync function persistInheritedSnapshot(\n ctx: CaptureContext,\n inherited: InheritedStory,\n): Promise<void> {\n const browser = browserOf(inherited);\n // Nothing rendered, so there is no new fingerprint: the snapshot carries the baseline's.\n const infraHash = inherited.baseline.infraHash;\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n browser,\n storyId: inherited.story.id,\n storyName: inherited.story.name,\n storyTitle: inherited.story.title,\n storyImportPath: inherited.story.importPath ?? \"\",\n designUrl: inherited.story.designUrl ?? null,\n viewportName: inherited.viewport.name,\n viewportWidth: inherited.viewport.width,\n viewportHeight: inherited.viewport.height,\n screenshotPath: inherited.baseline.screenshotPath,\n infraHash,\n mode: modeJsonOf(inherited.viewport),\n inherited: true,\n baselineId: inherited.baseline.id,\n baselineVersion: inherited.baseline.updatedAt,\n });\n await snapshots.update(snapshot.id, {\n status: \"unchanged\",\n diffPixels: 0,\n diffRatio: 0,\n diffPassed: true,\n diffPath: null,\n infraHash,\n });\n}\n\n/** Every story covered by a capture run, rendered or inherited. */\nfunction allStoryIds(ctx: CaptureContext): ReadonlySet<string> {\n const ids = new Set(ctx.captures.map((capture) => capture.story.id));\n for (const inherited of ctx.inherited ?? []) {\n ids.add(inherited.story.id);\n }\n return ids;\n}\n\n/** Table handles required by the capture pipeline. */\nexport type PipelineTables = BuildTables & BaselineTables & SnapshotTables;\n\n/** Persistence inputs for a completed capture run. */\nexport interface CaptureContext {\n /** Database adapter. */\n db: DatabaseAdapter;\n /** Table handles. */\n tables: PipelineTables;\n /** Storage adapter. */\n storage: StorageAdapter;\n /** The project being captured. */\n project: Project;\n /** The build being captured. */\n build: Build;\n /** Viewports at which stories were captured. */\n viewports: Viewport[];\n /** Engines captured this run, primary first (default: chromium only). */\n browsers?: string[];\n /** Secondary engines with no baselines yet; worked out by `persistCapture`, or fixed up front by a shard plan. */\n freshBrowsers?: ReadonlySet<string>;\n /** Screenshot buffers produced by the capture renderer. */\n captures: RenderedSnapshot[];\n /** Stories inheriting their baseline without rendering (affected capture). */\n inherited?: InheritedStory[];\n /** Optional logger for capture diagnostics. */\n logger?: Logger;\n /** Optional per-attempt log recorder (mirrors logger lines into the DB). */\n recordLog?: AttemptLogRecorder;\n /** Called per snapshot persisted, so the job's lease sees a long diff phase making progress. */\n progress?: CaptureProgress | undefined;\n /** Server secret for decrypting webhook secrets at send time. */\n secret?: SecretInput;\n}\n\n/** JSON for the snapshot `mode` column: the globals a mode applied (null for plain viewports). */\nfunction modeJsonOf(viewport: Viewport | undefined): string | null {\n return viewport?.globals ? JSON.stringify(viewport.globals) : null;\n}\n\nfunction viewportByName(ctx: CaptureContext, name: string): Viewport {\n return ctx.viewports.find((v) => v.name === name) ?? { name, width: 0, height: 0 };\n}\n\n/** Where the capture's screenshot lives: already stored by the orchestrator, or stored now. */\nasync function screenshotLocation(ctx: CaptureContext, capture: RenderedSnapshot): Promise<string> {\n if (capture.storedPath) {\n return capture.storedPath;\n }\n assertScreenshotBuffer(capture.screenshot);\n return await storeScreenshot(ctx.storage, ctx.project.id, capture.screenshot);\n}\n\nasync function persistSnapshot(ctx: CaptureContext, capture: RenderedSnapshot): Promise<void> {\n const viewport = capture.viewport ?? viewportByName(ctx, capture.viewportName);\n const browser = browserOf(capture);\n const screenshot = await screenshotLocation(ctx, capture);\n\n const baseline = await resolveBaseline(ctx, capture.story.id, capture.viewportName, browser);\n if (!baseline) {\n await createWithoutBaseline(ctx, capture, viewport, screenshot);\n return;\n }\n await createWithBaseline(ctx, capture, viewport, screenshot, baseline);\n}\n\nasync function resolveBaseline(\n ctx: CaptureContext,\n storyId: string,\n viewport: string,\n browser: string,\n): Promise<Baseline | null> {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n return await baselines.resolve(\n ctx.project.id,\n storyId,\n viewport,\n ctx.build.gitBranch,\n ctx.project.gitDefaultBranch,\n browser,\n );\n}\n\nasync function createWithoutBaseline(\n ctx: CaptureContext,\n capture: RenderedSnapshot,\n viewport: Viewport,\n screenshot: string,\n): Promise<void> {\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const browser = browserOf(capture);\n const accepted = ctx.build.isDefault || (await bootstrapAccepts(ctx, capture));\n const status = accepted ? \"approved\" : \"new\";\n const now = fingerprintOf(capture, viewport);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n browser,\n durationMs: capture.durationMs ?? null,\n a11yViolations: a11yJsonOf(capture),\n storyId: capture.story.id,\n storyName: capture.story.name,\n storyTitle: capture.story.title,\n storyImportPath: capture.story.importPath ?? \"\",\n designUrl: capture.story.designUrl ?? null,\n viewportName: capture.viewportName,\n viewportWidth: viewport.width,\n viewportHeight: viewport.height,\n screenshotPath: screenshot,\n infraHash: now.infraHash,\n mode: modeJsonOf(viewport),\n baselineVersion: NO_BASELINE,\n });\n await snapshots.setStatus(snapshot.id, status);\n\n if (accepted) {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.upsert(\n ctx.project.id,\n capture.story.id,\n capture.viewportName,\n ctx.build.gitBranch,\n snapshot.id,\n screenshot,\n now.infraHash,\n { mode: modeJsonOf(viewport), browser, fingerprint: now.json },\n );\n }\n}\n\n/** A capture's fingerprint: its components, their hash (stored as `infraHash`), and the stored JSON. */\ninterface CaptureFingerprint {\n fingerprint: Fingerprint;\n infraHash: string;\n json: string;\n}\n\nfunction fingerprintOf(capture: RenderedSnapshot, viewport: Viewport): CaptureFingerprint {\n return captureFingerprintOf(buildFingerprint(browserOf(capture), viewport, capture.environment));\n}\n\n/** A fingerprint as stored: its hash (the `infraHash`) and JSON. */\nfunction captureFingerprintOf(fingerprint: Fingerprint): CaptureFingerprint {\n return {\n fingerprint,\n infraHash: fingerprintHash(fingerprint),\n json: JSON.stringify(fingerprint),\n };\n}\n\n/**\n * What changed in the capture environment since the baseline was recorded, or null when nothing\n * did. Compared component by component, only where both sides report a value: a capture with no\n * environment (spooled before a browser crash) is unknown, not a change. A baseline from before\n * fingerprints cannot be compared, so it is never reported as changed; it is backfilled instead.\n */\nfunction environmentChange(baseline: Baseline, now: CaptureFingerprint): EnvironmentChange | null {\n const from = parseFingerprint(baseline.fingerprint);\n if (!from) {\n return null;\n }\n const changed = changedFields(from, now.fingerprint);\n return changed.length > 0 ? { changed, from, to: now.fingerprint } : null;\n}\n\n/**\n * A changed environment migrates the baseline without a diff only on the default branch, which is\n * authoritative and auto-approves its own changes anyway. A feature branch still diffs: a real\n * change is reported as `changed` with the environment change attached, never auto-accepted.\n */\nasync function createWithBaseline(\n ctx: CaptureContext,\n capture: RenderedSnapshot,\n viewport: Viewport,\n screenshot: string,\n baseline: Baseline,\n): Promise<void> {\n const now = fingerprintOf(capture, viewport);\n const change = environmentChange(baseline, now);\n const input = { ctx, capture, viewport, screenshot, baseline, now, change };\n if (change && ctx.build.isDefault) {\n await persistMigration(input, change);\n } else {\n await persistDiff(input);\n }\n}\n\ninterface BaselineCapture {\n ctx: CaptureContext;\n capture: RenderedSnapshot;\n viewport: Viewport;\n screenshot: string;\n baseline: Baseline;\n now: CaptureFingerprint;\n change: EnvironmentChange | null;\n}\n\n/** The fingerprint to write on the baseline: the capture's, keeping components it did not report. */\nfunction baselineFingerprintOf(input: BaselineCapture): CaptureFingerprint {\n const earlier = parseFingerprint(input.baseline.fingerprint);\n return captureFingerprintOf(mergeFingerprint(earlier, input.now.fingerprint));\n}\n\nfunction snapshotInput(input: BaselineCapture): Parameters<SnapshotModel[\"create\"]>[2] {\n const { capture, viewport, screenshot, baseline, now } = input;\n return {\n browser: browserOf(capture),\n durationMs: capture.durationMs ?? null,\n a11yViolations: a11yJsonOf(capture),\n storyId: capture.story.id,\n storyName: capture.story.name,\n storyTitle: capture.story.title,\n storyImportPath: capture.story.importPath ?? \"\",\n designUrl: capture.story.designUrl ?? null,\n viewportName: capture.viewportName,\n viewportWidth: viewport.width,\n viewportHeight: viewport.height,\n screenshotPath: screenshot,\n infraHash: now.infraHash,\n mode: modeJsonOf(viewport),\n baselineId: baseline.id,\n baselineVersion: baseline.updatedAt,\n };\n}\n\n/**\n * The default branch's capture environment changed (browser build, fonts, platform, scale): pixels\n * would differ with no code change, so no diff is computed. The snapshot is accepted, records what\n * changed, and the new screenshot becomes the baseline.\n */\nasync function persistMigration(input: BaselineCapture, change: EnvironmentChange): Promise<void> {\n const { ctx, capture, viewport, screenshot, now } = input;\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n ...snapshotInput(input),\n envChange: JSON.stringify(change),\n });\n await snapshots.update(snapshot.id, {\n status: \"unchanged\",\n diffPath: null,\n diffPixels: 0,\n diffRatio: 0,\n diffPassed: true,\n infraHash: now.infraHash,\n });\n await upsertDefaultBaseline(ctx, snapshot.id, capture, screenshot, baselineFingerprintOf(input));\n ctx.logger?.debug(\n { storyId: capture.story.id, viewport: viewport.name, changed: change.changed },\n \"capture environment changed; accepted without a diff\",\n );\n}\n\n/** Diff against the baseline; a feature-branch environment change rides along as `envChange`. */\nasync function persistDiff(input: BaselineCapture): Promise<void> {\n const { ctx, capture, screenshot, baseline, now, change } = input;\n const result = await diffStoredScreenshots(\n ctx.storage,\n ctx.project,\n baseline.screenshotPath,\n screenshot,\n capture.story.parameters?.diffThreshold,\n );\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n ...snapshotInput(input),\n ...(change ? { envChange: JSON.stringify(change) } : {}),\n });\n const diff = diffPath(\n ctx.project.id,\n ctx.build.id,\n capture.story.id,\n capture.viewportName,\n browserOf(capture),\n );\n const overlay = await writeDiffOverlay(ctx.storage, diff, result, result.passed);\n await snapshots.update(snapshot.id, {\n status: diffStatus(result.passed, ctx.build.isDefault),\n diffPath: overlay,\n diffPixels: result.diffPixels,\n diffRatio: result.diffRatio,\n diffPassed: result.passed,\n infraHash: now.infraHash,\n });\n if (ctx.build.isDefault) {\n await refreshDefaultBaseline(input, snapshot.id, result.passed);\n }\n}\n\n/**\n * After a default-branch diff: a failed diff moves the baseline to the new screenshot; a passing\n * one backfills the fingerprint when it was missing or had components the capture now reports.\n */\nasync function refreshDefaultBaseline(\n input: BaselineCapture,\n snapshotId: string,\n passed: boolean,\n): Promise<void> {\n const { ctx, capture, screenshot, baseline } = input;\n const target = baselineFingerprintOf(input);\n if (!passed) {\n await upsertDefaultBaseline(ctx, snapshotId, capture, screenshot, target);\n } else if (!baseline.fingerprint || baseline.infraHash !== target.infraHash) {\n await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).backfillFingerprint(\n baseline,\n target.infraHash,\n target.json,\n );\n }\n}\n\n/** Default-branch builds are authoritative: the captured screenshot becomes the baseline. */\nasync function upsertDefaultBaseline(\n ctx: CaptureContext,\n snapshotId: string,\n capture: RenderedSnapshot,\n screenshot: string,\n now: CaptureFingerprint,\n): Promise<void> {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.upsert(\n ctx.project.id,\n capture.story.id,\n capture.viewportName,\n ctx.build.gitBranch,\n snapshotId,\n screenshot,\n now.infraHash,\n { mode: modeJsonOf(capture.viewport), browser: browserOf(capture), fingerprint: now.json },\n );\n}\n\n/** Snapshot status after diffing: default-branch changes are auto-approved, not queued for review. */\nexport function diffStatus(passed: boolean, isDefault: boolean): SnapshotStatus {\n if (passed) {\n return \"unchanged\";\n }\n return isDefault ? \"approved\" : \"changed\";\n}\n\n/** A failed capture fails the build; default-branch builds otherwise auto-approve. */\nfunction finalBuildStatus(\n isDefault: boolean,\n failedCount: number,\n hasCaptures: boolean,\n build: { changedCount: number },\n): BuildStatus {\n if (failedCount > 0) {\n return \"failed\";\n }\n if (isDefault || (hasCaptures && build.changedCount === 0)) {\n return \"approved\";\n }\n return \"reviewing\";\n}\n\n/**\n * Close out a capture: roll the snapshot counts up, set the build's final status from the failed\n * story ids, and (on the default branch) drop baselines of stories that no longer exist.\n */\nexport async function finalizeBuild(\n ctx: CaptureContext,\n storyIds: ReadonlySet<string>,\n failedStoryIds: ReadonlySet<string>,\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<void> {\n const builds = new BuildModel(ctx.db, ctx.tables, ctx.logger);\n if ((await builds.get(ctx.build.id))?.status === \"cancelled\") {\n ctx.logger?.debug({ buildId: ctx.build.id }, \"build is cancelled; finalize skipped\");\n return;\n }\n const build = await builds.updateCounts(ctx.build.id);\n const hasCaptures = storyIds.size > 0;\n const status = finalBuildStatus(ctx.build.isDefault, failedStoryIds.size, hasCaptures, build);\n // Flaky and a11y failures do not block: log warning if any failed non-blocking\n if (flakyFailedStoryIds.size > 0 && failedStoryIds.size === 0) {\n ctx.logger?.warn(\n { flakyStoryIds: [...flakyFailedStoryIds] },\n \"flaky stories failed (non-blocking)\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"warn\", \"flaky stories failed (non-blocking)\", {\n flakyStoryIds: [...flakyFailedStoryIds],\n });\n }\n if (a11yFailedStoryIds.size > 0 && failedStoryIds.size === 0) {\n ctx.logger?.warn(\n { a11yStoryIds: [...a11yFailedStoryIds] },\n \"a11y violations found (non-blocking)\",\n );\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"a11y violations found (non-blocking)\",\n {\n a11yStoryIds: [...a11yFailedStoryIds],\n },\n );\n }\n await builds.setStatus(ctx.build.id, status);\n\n if (ctx.build.isDefault && hasCaptures) {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.removeOrphans(ctx.project.id, new Set(storyIds));\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/capture/progress.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Called by a capture as it moves: a screenshot taken, a snapshot persisted, a stage done. Cheap
|
|
6
|
+
* and synchronous, so the hot loops of render and persist can call it on every item.
|
|
7
|
+
*/
|
|
8
|
+
type CaptureProgress = () => void;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { CaptureProgress };
|
|
11
|
+
//# sourceMappingURL=progress.d.mts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/capture/progress.ts
|
|
5
|
+
/** Shortest gap between two lease touches: a forked child sends one IPC message per touch. */
|
|
6
|
+
const PROGRESS_THROTTLE_MS = 1e3;
|
|
7
|
+
/**
|
|
8
|
+
* The progress callback for a job: touches its lease, at most once per `everyMs`. The first call
|
|
9
|
+
* always goes through. Undefined without a lease, so callers skip the work entirely.
|
|
10
|
+
*
|
|
11
|
+
* Liveness comes from the job reporting work done rather than from a timer, so a hung render or
|
|
12
|
+
* diff still lets the lease expire (see {@link JobLease}).
|
|
13
|
+
*/
|
|
14
|
+
function leaseProgress(lease, everyMs = PROGRESS_THROTTLE_MS, now = Date.now) {
|
|
15
|
+
if (!lease) return;
|
|
16
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
17
|
+
return () => {
|
|
18
|
+
const at = now();
|
|
19
|
+
if (at - last >= everyMs) {
|
|
20
|
+
last = at;
|
|
21
|
+
lease.touch();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { PROGRESS_THROTTLE_MS, leaseProgress };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=progress.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.mjs","names":[],"sources":["../../src/capture/progress.ts"],"sourcesContent":["/** Progress reporting for a running capture job, kept apart from its logging. */\nimport type { JobLease } from \"./job-lease.ts\";\n\n/**\n * Called by a capture as it moves: a screenshot taken, a snapshot persisted, a stage done. Cheap\n * and synchronous, so the hot loops of render and persist can call it on every item.\n */\nexport type CaptureProgress = () => void;\n\n/** Shortest gap between two lease touches: a forked child sends one IPC message per touch. */\nexport const PROGRESS_THROTTLE_MS = 1000;\n\n/**\n * The progress callback for a job: touches its lease, at most once per `everyMs`. The first call\n * always goes through. Undefined without a lease, so callers skip the work entirely.\n *\n * Liveness comes from the job reporting work done rather than from a timer, so a hung render or\n * diff still lets the lease expire (see {@link JobLease}).\n */\nexport function leaseProgress(\n lease?: JobLease,\n everyMs: number = PROGRESS_THROTTLE_MS,\n now: () => number = Date.now,\n): CaptureProgress | undefined {\n if (!lease) {\n return undefined;\n }\n let last = Number.NEGATIVE_INFINITY;\n return () => {\n const at = now();\n if (at - last >= everyMs) {\n last = at;\n lease.touch();\n }\n };\n}\n"],"mappings":";;;;;AAUA,MAAa,uBAAuB;;;;;;;;AASpC,SAAgB,cACd,OACA,UAAkB,sBAClB,MAAoB,KAAK,KACI;CAC7B,IAAI,CAAC,OACH;CAEF,IAAI,OAAO,OAAO;CAClB,aAAa;EACX,MAAM,KAAK,IAAI;EACf,IAAI,KAAK,QAAQ,SAAS;GACxB,OAAO;GACP,MAAM,MAAM;EACd;CACF;AACF"}
|