@storyshelf/core 0.7.0 → 0.8.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/dist/adapter/capture-queue.d.mts +0 -2
- package/dist/adapter/capture-runner.d.mts +9 -4
- package/dist/adapter/capture-runner.mjs +6 -2
- package/dist/adapter/database.d.mts +63 -3
- package/dist/adapter/database.mjs +4 -3
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +0 -2
- package/dist/adapter/design.mjs +1 -2
- package/dist/adapter/email-sender.d.mts +0 -2
- package/dist/adapter/metadata.d.mts +0 -2
- package/dist/adapter/oauth.d.mts +0 -2
- package/dist/adapter/setup.d.mts +0 -2
- package/dist/adapter/setup.mjs +1 -2
- package/dist/adapter/storage.d.mts +1 -3
- package/dist/adapter/webhook-events.d.mts +0 -2
- package/dist/adapter/webhook-events.mjs +12 -13
- package/dist/adapter/webhook-events.mjs.map +1 -1
- package/dist/adapters/capture-target-offers.d.mts +0 -2
- package/dist/adapters/capture-target-offers.mjs +1 -2
- package/dist/adapters/capture-target.d.mts +0 -2
- package/dist/adapters/capture-target.mjs +1 -2
- package/dist/adapters/notifier/build-comment.d.mts +0 -2
- package/dist/adapters/notifier/build-comment.mjs +1 -2
- package/dist/adapters/notifier/build-state.d.mts +0 -2
- package/dist/adapters/notifier/build-state.mjs +1 -2
- package/dist/adapters/notifier/events.d.mts +0 -2
- package/dist/adapters/notifier/events.mjs +1 -2
- package/dist/adapters/notifier/format.d.mts +0 -2
- package/dist/adapters/notifier/format.mjs +1 -2
- package/dist/adapters/notifier/provider.d.mts +0 -2
- package/dist/adapters/notifier/provider.mjs +1 -2
- package/dist/adapters/notifier/state.d.mts +0 -2
- package/dist/adapters/notifier/state.mjs +1 -2
- package/dist/adapters/notifier/types.d.mts +0 -2
- package/dist/adapters/remote-browser.d.mts +112 -31
- package/dist/adapters/remote-browser.mjs +132 -34
- package/dist/adapters/remote-browser.mjs.map +1 -1
- package/dist/adapters/remote-lease-attempt.mjs +39 -0
- package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
- package/dist/adapters/remote-lease-error.d.mts +25 -0
- package/dist/adapters/remote-lease-error.mjs +54 -0
- package/dist/adapters/remote-lease-error.mjs.map +1 -0
- package/dist/adapters/remote-lease-held.d.mts +15 -0
- package/dist/adapters/remote-lease-held.mjs +86 -0
- package/dist/adapters/remote-lease-held.mjs.map +1 -0
- package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
- package/dist/adapters/remote-lease-shutdown.mjs +32 -0
- package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
- package/dist/adapters/remote-lease.d.mts +39 -0
- package/dist/adapters/remote-lease.mjs +175 -0
- package/dist/adapters/remote-lease.mjs.map +1 -0
- package/dist/adapters/remote-limiter.mjs +121 -0
- package/dist/adapters/remote-limiter.mjs.map +1 -0
- package/dist/adapters/remote-pool.d.mts +56 -0
- package/dist/adapters/remote-pool.mjs +195 -0
- package/dist/adapters/remote-pool.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +0 -2
- package/dist/adapters/render-runs.mjs +1 -2
- package/dist/adapters/setup-runner.d.mts +0 -2
- package/dist/adapters/setup-runner.mjs +1 -2
- package/dist/adapters/sources.d.mts +0 -2
- package/dist/adapters/sources.mjs +1 -2
- package/dist/adapters/validate.d.mts +0 -2
- package/dist/adapters/validate.mjs +2 -2
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +0 -2
- package/dist/ai.mjs +1 -2
- package/dist/auth.d.mts +0 -2
- package/dist/auth.mjs +1 -2
- package/dist/brand-packs.d.mts +0 -2
- package/dist/brand-packs.mjs +0 -1
- package/dist/capture/a11y/axe-source.d.mts +0 -2
- package/dist/capture/a11y/axe-source.mjs +1 -2
- package/dist/capture/a11y/in-page.d.mts +1 -3
- package/dist/capture/a11y/in-page.mjs +3 -4
- package/dist/capture/a11y/in-page.mjs.map +1 -1
- package/dist/capture/a11y/map.d.mts +0 -2
- package/dist/capture/a11y/map.mjs +1 -2
- package/dist/capture/a11y/options.d.mts +0 -2
- package/dist/capture/a11y/options.mjs +1 -2
- package/dist/capture/adapter.d.mts +5 -2
- package/dist/capture/adapter.mjs +1 -2
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +0 -2
- package/dist/capture/affected.mjs +1 -2
- package/dist/capture/attempt-logger.mjs +1 -2
- package/dist/capture/browsers.d.mts +0 -2
- package/dist/capture/browsers.mjs +1 -2
- package/dist/capture/build-state/deliver.d.mts +0 -2
- package/dist/capture/build-state/deliver.mjs +1 -2
- package/dist/capture/build-state/notify-job.d.mts +0 -2
- package/dist/capture/build-state/notify-job.mjs +1 -2
- package/dist/capture/build-state/publisher.d.mts +0 -2
- package/dist/capture/build-state/publisher.mjs +1 -2
- package/dist/capture/build-state/resume.d.mts +0 -2
- package/dist/capture/build-state/resume.mjs +1 -2
- package/dist/capture/build-state/types.d.mts +1 -3
- package/dist/capture/catalog/catalogue.d.mts +0 -2
- package/dist/capture/catalog/catalogue.mjs +1 -2
- package/dist/capture/catalog/components.d.mts +0 -2
- package/dist/capture/catalog/components.mjs +1 -2
- package/dist/capture/catalog/history.d.mts +0 -2
- package/dist/capture/catalog/history.mjs +1 -2
- package/dist/capture/catalog/hook.d.mts +0 -2
- package/dist/capture/catalog/hook.mjs +1 -2
- package/dist/capture/catalog/index.d.mts +2 -2
- package/dist/capture/catalog/load.d.mts +0 -2
- package/dist/capture/catalog/load.mjs +1 -2
- package/dist/capture/catalog/parse.d.mts +0 -2
- package/dist/capture/catalog/parse.mjs +1 -2
- package/dist/capture/catalog/read.d.mts +6 -3
- package/dist/capture/catalog/read.mjs +14 -8
- package/dist/capture/catalog/read.mjs.map +1 -1
- package/dist/capture/catalog/types.d.mts +0 -2
- package/dist/capture/comparison-reference.d.mts +27 -0
- package/dist/capture/comparison-reference.mjs +75 -0
- package/dist/capture/comparison-reference.mjs.map +1 -0
- package/dist/capture/consumer/capture-handler.d.mts +1 -3
- package/dist/capture/consumer/capture-handler.mjs +1 -2
- package/dist/capture/consumer/idle.d.mts +0 -2
- package/dist/capture/consumer/idle.mjs +1 -2
- package/dist/capture/consumer/index.d.mts +0 -2
- package/dist/capture/consumer/index.mjs +1 -2
- package/dist/capture/consumer/loop.mjs +1 -2
- package/dist/capture/consumer/metrics.d.mts +0 -2
- package/dist/capture/consumer/metrics.mjs +1 -2
- package/dist/capture/consumer/retry.d.mts +0 -2
- package/dist/capture/consumer/retry.mjs +1 -2
- package/dist/capture/consumer/run-job.mjs +1 -2
- package/dist/capture/consumer/startup.mjs +1 -2
- package/dist/capture/consumer/types.d.mts +0 -2
- package/dist/capture/crash-failure.d.mts +0 -2
- package/dist/capture/crash-failure.mjs +1 -2
- package/dist/capture/db-queue/cadence.d.mts +0 -2
- package/dist/capture/db-queue/cadence.mjs +1 -2
- package/dist/capture/db-queue/claim.d.mts +0 -2
- package/dist/capture/db-queue/claim.mjs +20 -4
- package/dist/capture/db-queue/claim.mjs.map +1 -1
- package/dist/capture/db-queue/clock.mjs +1 -2
- package/dist/capture/db-queue/codec.mjs +1 -2
- package/dist/capture/db-queue/index.d.mts +0 -2
- package/dist/capture/db-queue/index.mjs +3 -3
- package/dist/capture/db-queue/index.mjs.map +1 -1
- package/dist/capture/db-queue/keeper.d.mts +0 -2
- package/dist/capture/db-queue/keeper.mjs +1 -2
- package/dist/capture/db-queue/lease.mjs +1 -2
- package/dist/capture/db-queue/lifecycle.mjs +1 -2
- package/dist/capture/db-queue/operations.mjs +1 -2
- package/dist/capture/db-queue/poll-loop.d.mts +0 -2
- package/dist/capture/db-queue/poll-loop.mjs +1 -2
- package/dist/capture/db-queue/poll.d.mts +0 -2
- package/dist/capture/db-queue/poll.mjs +1 -2
- package/dist/capture/db-queue/timers.d.mts +0 -2
- package/dist/capture/db-queue/timers.mjs +1 -2
- package/dist/capture/db-queue/types.d.mts +0 -2
- package/dist/capture/db-queue/types.mjs +1 -2
- package/dist/capture/db-queue/types.mjs.map +1 -1
- package/dist/capture/db-queue/wake.mjs +1 -2
- package/dist/capture/design.mjs +1 -2
- package/dist/capture/diff-record.mjs +1 -2
- package/dist/capture/dispatch.d.mts +1 -3
- package/dist/capture/dispatch.mjs +17 -5
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +1 -2
- package/dist/capture/failure-sets.mjs +1 -2
- package/dist/capture/fingerprint.d.mts +0 -2
- package/dist/capture/fingerprint.mjs +1 -2
- package/dist/capture/forked/channel.d.mts +0 -2
- package/dist/capture/forked/child.d.mts +0 -2
- package/dist/capture/forked/child.mjs +1 -2
- package/dist/capture/forked/crash-tracker.mjs +1 -2
- package/dist/capture/forked/decision.d.mts +0 -2
- package/dist/capture/forked/decision.mjs +1 -2
- package/dist/capture/forked/exit.mjs +1 -2
- package/dist/capture/forked/fork.d.mts +0 -2
- package/dist/capture/forked/fork.mjs +1 -2
- package/dist/capture/forked/forked-capture.d.mts +0 -2
- package/dist/capture/forked/forked-capture.mjs +1 -2
- package/dist/capture/forked/lease-book.mjs +1 -2
- package/dist/capture/forked/protocol.d.mts +0 -2
- package/dist/capture/forked/protocol.mjs +1 -2
- package/dist/capture/forked/runs.mjs +1 -2
- package/dist/capture/guards.mjs +1 -2
- package/dist/capture/ignore-regions.d.mts +36 -0
- package/dist/capture/ignore-regions.mjs +65 -0
- package/dist/capture/ignore-regions.mjs.map +1 -0
- package/dist/capture/isolate.mjs +1 -2
- package/dist/capture/job-lease.d.mts +0 -2
- package/dist/capture/job-lease.mjs +1 -2
- package/dist/capture/matrix.mjs +4 -4
- package/dist/capture/matrix.mjs.map +1 -1
- package/dist/capture/modes.d.mts +0 -2
- package/dist/capture/modes.mjs +2 -3
- package/dist/capture/modes.mjs.map +1 -1
- package/dist/capture/orchestrator.d.mts +0 -2
- package/dist/capture/orchestrator.mjs +2 -3
- package/dist/capture/pipeline.d.mts +0 -2
- package/dist/capture/pipeline.mjs +37 -6
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +0 -2
- package/dist/capture/progress.mjs +1 -2
- package/dist/capture/publish/accept.d.mts +0 -2
- package/dist/capture/publish/accept.mjs +1 -2
- package/dist/capture/publish/complete.d.mts +1 -3
- package/dist/capture/publish/complete.mjs +1 -2
- package/dist/capture/publish/extract.d.mts +0 -2
- package/dist/capture/publish/extract.mjs +6 -5
- package/dist/capture/publish/extract.mjs.map +1 -1
- package/dist/capture/publish/handler.d.mts +1 -3
- package/dist/capture/publish/handler.mjs +1 -2
- package/dist/capture/publish/index.d.mts +2 -1
- package/dist/capture/publish/invalid-zip.d.mts +8 -0
- package/dist/capture/publish/invalid-zip.mjs +11 -0
- package/dist/capture/publish/invalid-zip.mjs.map +1 -0
- package/dist/capture/publish/limits.d.mts +0 -2
- package/dist/capture/publish/limits.mjs +1 -2
- package/dist/capture/publish/published.d.mts +0 -2
- package/dist/capture/publish/published.mjs +1 -2
- package/dist/capture/publish/run.d.mts +1 -3
- package/dist/capture/publish/run.mjs +1 -2
- package/dist/capture/rediff.d.mts +1 -3
- package/dist/capture/rediff.mjs +19 -6
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +1 -2
- package/dist/capture/render.mjs.map +1 -1
- package/dist/capture/scratch.d.mts +0 -2
- package/dist/capture/scratch.mjs +2 -4
- package/dist/capture/scratch.mjs.map +1 -1
- package/dist/capture/shard-plan.mjs +1 -2
- package/dist/capture/shards/context.mjs +3 -3
- package/dist/capture/shards/context.mjs.map +1 -1
- package/dist/capture/shards/execute.mjs +4 -3
- package/dist/capture/shards/execute.mjs.map +1 -1
- package/dist/capture/shards/fan-out.mjs +1 -2
- package/dist/capture/shards/history.mjs +1 -2
- package/dist/capture/shards/reduce.mjs +1 -2
- package/dist/capture/sizing.d.mts +0 -2
- package/dist/capture/sizing.mjs +1 -2
- package/dist/capture/skip-checks.mjs +1 -2
- package/dist/capture/statics.d.mts +0 -2
- package/dist/capture/statics.mjs +2 -4
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +0 -2
- package/dist/capture/storybook-urls.mjs +3 -3
- package/dist/capture/storybook-urls.mjs.map +1 -1
- package/dist/capture/storybook.d.mts +0 -2
- package/dist/capture/storybook.mjs +8 -4
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +1 -2
- package/dist/capture/targets.d.mts +0 -2
- package/dist/capture/targets.mjs +1 -2
- package/dist/capture/tests.d.mts +0 -2
- package/dist/capture/tests.mjs +13 -3
- package/dist/capture/tests.mjs.map +1 -1
- package/dist/capture/token-chunks.mjs +24 -7
- package/dist/capture/token-chunks.mjs.map +1 -1
- package/dist/capture.d.mts +5 -2
- package/dist/capture.mjs +5 -2
- package/dist/config.d.mts +16 -3
- package/dist/config.mjs +6 -3
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +0 -2
- package/dist/diff.mjs +1 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/insights/budget.d.mts +0 -2
- package/dist/insights/budget.mjs +1 -2
- package/dist/insights/envelope.d.mts +0 -2
- package/dist/insights/envelope.mjs +1 -2
- package/dist/insights/evidence-health.d.mts +0 -2
- package/dist/insights/evidence-health.mjs +1 -2
- package/dist/insights/evidence.d.mts +0 -2
- package/dist/insights/evidence.mjs +1 -2
- package/dist/insights/hash.d.mts +0 -2
- package/dist/insights/hash.mjs +1 -2
- package/dist/insights/limits.d.mts +0 -2
- package/dist/insights/limits.mjs +1 -2
- package/dist/insights/prompts/v1.d.mts +0 -2
- package/dist/insights/prompts/v1.mjs +1 -2
- package/dist/insights/redact.d.mts +0 -2
- package/dist/insights/redact.mjs +1 -2
- package/dist/insights/text.mjs +1 -2
- package/dist/logger.d.mts +0 -2
- package/dist/logger.mjs +1 -2
- package/dist/models/ai-budget-alert.d.mts +0 -2
- package/dist/models/ai-budget-alert.mjs +1 -2
- package/dist/models/ai-usage.d.mts +0 -2
- package/dist/models/ai-usage.mjs +1 -2
- package/dist/models/baseline-approval.d.mts +18 -0
- package/dist/models/baseline-approval.mjs +41 -0
- package/dist/models/baseline-approval.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +0 -2
- package/dist/models/baseline-archive.mjs +1 -2
- package/dist/models/baseline-atomic.mjs +66 -0
- package/dist/models/baseline-atomic.mjs.map +1 -0
- package/dist/models/baseline-conflict.d.mts +6 -0
- package/dist/models/baseline-conflict.mjs +9 -0
- package/dist/models/baseline-conflict.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +0 -2
- package/dist/models/baseline-export.mjs +1 -2
- package/dist/models/baseline-import.mjs +1 -2
- package/dist/models/baseline-origin.d.mts +16 -0
- package/dist/models/baseline-origin.mjs +35 -0
- package/dist/models/baseline-origin.mjs.map +1 -0
- package/dist/models/baseline-status.d.mts +0 -2
- package/dist/models/baseline-status.mjs +1 -2
- package/dist/models/baseline-transfer.d.mts +1 -2
- package/dist/models/baseline-transfer.mjs +30 -2
- package/dist/models/baseline-transfer.mjs.map +1 -1
- package/dist/models/baseline.d.mts +5 -2
- package/dist/models/baseline.mjs +91 -12
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +0 -2
- package/dist/models/build-component.mjs +1 -2
- package/dist/models/build-review-status.d.mts +0 -2
- package/dist/models/build-review-status.mjs +1 -2
- package/dist/models/build-review.d.mts +0 -2
- package/dist/models/build-review.mjs +1 -2
- package/dist/models/build.d.mts +1 -3
- package/dist/models/build.mjs +14 -15
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +0 -2
- package/dist/models/capture-attempt.mjs +1 -2
- package/dist/models/capture-job.d.mts +0 -2
- package/dist/models/capture-job.mjs +1 -2
- package/dist/models/capture-log.d.mts +0 -2
- package/dist/models/capture-log.mjs +1 -2
- package/dist/models/capture-shard.d.mts +0 -2
- package/dist/models/capture-shard.mjs +1 -2
- package/dist/models/changeset.d.mts +0 -2
- package/dist/models/changeset.mjs +1 -2
- package/dist/models/columns.mjs +1 -2
- package/dist/models/comment.d.mts +0 -2
- package/dist/models/comment.mjs +1 -2
- package/dist/models/component.d.mts +0 -2
- package/dist/models/component.mjs +1 -2
- package/dist/models/connections.d.mts +0 -2
- package/dist/models/connections.mjs +1 -2
- package/dist/models/content-ref.d.mts +0 -2
- package/dist/models/content-ref.mjs +1 -2
- package/dist/models/credentials.d.mts +0 -2
- package/dist/models/credentials.mjs +1 -2
- package/dist/models/design-credentials.d.mts +0 -2
- package/dist/models/design-credentials.mjs +1 -2
- package/dist/models/design-health.d.mts +0 -2
- package/dist/models/design-health.mjs +1 -2
- package/dist/models/design-nodes.d.mts +2 -4
- package/dist/models/design-nodes.mjs +1 -2
- package/dist/models/design-renders.mjs +1 -2
- package/dist/models/design-resolve.d.mts +0 -2
- package/dist/models/design-resolve.mjs +1 -2
- package/dist/models/design.d.mts +0 -2
- package/dist/models/design.mjs +1 -2
- package/dist/models/inherited-ratio.d.mts +0 -2
- package/dist/models/inherited-ratio.mjs +1 -2
- package/dist/models/insight.d.mts +0 -2
- package/dist/models/insight.mjs +1 -2
- package/dist/models/label.d.mts +0 -2
- package/dist/models/label.mjs +1 -2
- package/dist/models/member.d.mts +0 -2
- package/dist/models/member.mjs +1 -2
- package/dist/models/notification-channel.d.mts +0 -2
- package/dist/models/notification-channel.mjs +1 -2
- package/dist/models/notification-subscription.d.mts +0 -2
- package/dist/models/notification-subscription.mjs +1 -2
- package/dist/models/project-group-mapping.d.mts +0 -2
- package/dist/models/project-group-mapping.mjs +1 -2
- package/dist/models/project.d.mts +0 -2
- package/dist/models/project.mjs +1 -2
- package/dist/models/public-origin.d.mts +0 -2
- package/dist/models/public-origin.mjs +1 -2
- package/dist/models/review-switch-off.d.mts +0 -2
- package/dist/models/review-switch-off.mjs +1 -2
- package/dist/models/site-settings.d.mts +0 -2
- package/dist/models/site-settings.mjs +1 -2
- package/dist/models/snapshot.d.mts +3 -2
- package/dist/models/snapshot.mjs +4 -2
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +0 -2
- package/dist/models/token.mjs +1 -2
- package/dist/models/usage.d.mts +0 -2
- package/dist/models/usage.mjs +1 -2
- package/dist/models/user.d.mts +0 -2
- package/dist/models/user.mjs +1 -2
- package/dist/models/webhook.d.mts +0 -2
- package/dist/models/webhook.mjs +1 -2
- package/dist/models.d.mts +5 -2
- package/dist/models.mjs +5 -2
- package/dist/options.d.mts +0 -2
- package/dist/options.mjs +2 -4
- package/dist/options.mjs.map +1 -1
- package/dist/paths.d.mts +3 -3
- package/dist/paths.mjs +7 -3
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +1 -3
- package/dist/retention/ai.mjs +0 -1
- package/dist/retention/pending.mjs +1 -2
- package/dist/retention/published-gzip-sweep.mjs +28 -0
- package/dist/retention/published-gzip-sweep.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +0 -2
- package/dist/retention/screenshot-sweep.mjs +1 -2
- package/dist/retention.d.mts +0 -2
- package/dist/retention.mjs +5 -2
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +2 -2
- package/dist/schema/build-component.d.mts +0 -2
- package/dist/schema/build-review.d.mts +0 -2
- package/dist/schema/build.d.mts +0 -2
- package/dist/schema/capture-attempt.d.mts +0 -2
- package/dist/schema/capture-job.d.mts +0 -2
- package/dist/schema/capture-shard.d.mts +0 -2
- package/dist/schema/comment.d.mts +0 -2
- package/dist/schema/connection.d.mts +0 -2
- package/dist/schema/content-ref.d.mts +0 -2
- package/dist/schema/design.d.mts +0 -2
- package/dist/schema/insight.d.mts +0 -2
- package/dist/schema/label.d.mts +0 -2
- package/dist/schema/member.d.mts +0 -2
- package/dist/schema/notification.d.mts +0 -2
- package/dist/schema/project-group-mapping.d.mts +0 -2
- package/dist/schema/project.d.mts +0 -2
- package/dist/schema/site-setting.d.mts +0 -2
- package/dist/schema/snapshot.d.mts +5 -2
- package/dist/schema/token.d.mts +0 -2
- package/dist/schema/usage.d.mts +0 -2
- package/dist/schema/user.d.mts +0 -2
- package/dist/schema/webhook.d.mts +0 -2
- package/dist/schema.d.mts +4 -6
- package/dist/serverless.d.mts +0 -2
- package/dist/serverless.mjs +1 -2
- package/dist/test-helpers/build-reference-contract.d.mts +14 -0
- package/dist/test-helpers/build-reference-contract.mjs +66 -0
- package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +0 -2
- package/dist/test-helpers/create-project.mjs +1 -2
- package/dist/test-helpers/database-contract.d.mts +0 -2
- package/dist/test-helpers/database-contract.mjs +37 -2
- package/dist/test-helpers/database-contract.mjs.map +1 -1
- package/dist/test-helpers/deferred.d.mts +0 -2
- package/dist/test-helpers/deferred.mjs +1 -2
- package/dist/test-helpers/design-contract.d.mts +0 -2
- package/dist/test-helpers/design-contract.mjs +1 -2
- package/dist/test-helpers/fake-adapters.d.mts +0 -2
- package/dist/test-helpers/fake-adapters.mjs +1 -2
- package/dist/test-helpers/fake-database.d.mts +0 -2
- package/dist/test-helpers/fake-database.mjs +7 -2
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +0 -2
- package/dist/test-helpers/fake-design.mjs +1 -2
- package/dist/test-helpers/fake-notifier.d.mts +0 -2
- package/dist/test-helpers/fake-notifier.mjs +1 -2
- package/dist/test-helpers/fake-storage.d.mts +0 -2
- package/dist/test-helpers/fake-storage.mjs +1 -2
- package/dist/test-helpers/fake-tables.mjs +5 -2
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +0 -2
- package/dist/test-helpers/manual-clock.mjs +1 -2
- package/dist/test-helpers/notifier-contract.d.mts +0 -2
- package/dist/test-helpers/notifier-contract.mjs +1 -2
- package/dist/test-helpers/queue-contract.d.mts +0 -2
- package/dist/test-helpers/queue-contract.mjs +1 -2
- package/dist/test-helpers/queue-multi-instance.d.mts +0 -2
- package/dist/test-helpers/queue-multi-instance.mjs +1 -2
- package/dist/test-helpers/runner-contract.d.mts +0 -2
- package/dist/test-helpers/runner-contract.mjs +1 -2
- package/dist/test-helpers/sql-chunks.mjs +2 -2
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.d.mts +0 -2
- package/dist/test-helpers/storage-contract.mjs +1 -2
- package/dist/test-helpers.d.mts +2 -1
- package/dist/test-helpers.mjs +2 -1
- package/dist/tracing.mjs +36 -6
- package/dist/tracing.mjs.map +1 -1
- package/dist/types.d.mts +0 -2
- package/dist/types.mjs +1 -2
- package/dist/urls.d.mts +0 -2
- package/dist/urls.mjs +1 -2
- package/dist/utils/adaptive-concurrency.d.mts +0 -2
- package/dist/utils/adaptive-concurrency.mjs +1 -2
- package/dist/utils/connect-retry.d.mts +2 -2
- package/dist/utils/connect-retry.mjs +2 -3
- package/dist/utils/connect-retry.mjs.map +1 -1
- package/dist/utils/encrypt.d.mts +0 -2
- package/dist/utils/encrypt.mjs +1 -2
- package/dist/utils/environment.d.mts +0 -2
- package/dist/utils/environment.mjs +1 -2
- package/dist/utils/hash.d.mts +0 -2
- package/dist/utils/hash.mjs +1 -2
- package/dist/utils/http.d.mts +13 -3
- package/dist/utils/http.mjs +79 -17
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +0 -2
- package/dist/utils/map-adaptive.mjs +1 -2
- package/dist/utils/map-limit.d.mts +0 -2
- package/dist/utils/map-limit.mjs +1 -2
- package/dist/utils/mime.d.mts +0 -2
- package/dist/utils/mime.mjs +1 -2
- package/dist/utils/page-limiter.d.mts +0 -2
- package/dist/utils/page-limiter.mjs +1 -2
- package/dist/utils/path-security.d.mts +0 -2
- package/dist/utils/path-security.mjs +2 -4
- package/dist/utils/path-security.mjs.map +1 -1
- package/dist/utils/public-origin.d.mts +0 -2
- package/dist/utils/public-origin.mjs +1 -2
- package/dist/utils/redact.d.mts +0 -2
- package/dist/utils/redact.mjs +1 -2
- package/dist/utils/remote-environment.d.mts +40 -3
- package/dist/utils/remote-environment.mjs +52 -3
- package/dist/utils/remote-environment.mjs.map +1 -1
- package/dist/utils/resources.d.mts +0 -2
- package/dist/utils/resources.mjs +1 -2
- package/dist/utils/screenshot-store.d.mts +0 -2
- package/dist/utils/screenshot-store.mjs +1 -2
- package/dist/utils/session-gate.d.mts +7 -6
- package/dist/utils/session-gate.mjs +28 -12
- package/dist/utils/session-gate.mjs.map +1 -1
- package/dist/utils/storybook-token.d.mts +11 -3
- package/dist/utils/storybook-token.mjs +10 -9
- package/dist/utils/storybook-token.mjs.map +1 -1
- package/dist/utils/storybook-url.d.mts +0 -2
- package/dist/utils/storybook-url.mjs +1 -2
- package/dist/utils/timing.d.mts +0 -2
- package/dist/utils/timing.mjs +1 -2
- package/dist/utils/ulid.d.mts +0 -2
- package/dist/utils/ulid.mjs +1 -2
- package/dist/utils/zip-read.mjs +1 -2
- package/dist/utils/zip-write.d.mts +0 -2
- package/dist/utils/zip-write.mjs +1 -2
- package/dist/utils.d.mts +5 -5
- package/dist/utils.mjs +5 -5
- package/package.json +6 -6
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
// src/capture/ignore-regions.ts
|
|
5
|
+
/**
|
|
6
|
+
* Ignore regions: elements a story marks as dynamic (timestamps, avatars, ads) are hidden before
|
|
7
|
+
* the screenshot so they never produce a diff. Both runners inject the stylesheet built here, so
|
|
8
|
+
* the mask is identical across engines. Baseline keys and diff math are untouched.
|
|
9
|
+
*/
|
|
10
|
+
/** Most selectors one story may declare; the rest are dropped. */
|
|
11
|
+
const MAX_IGNORE_SELECTORS = 50;
|
|
12
|
+
/** Selectors that always apply: StoryShelf's attribute, then the Chromatic migration spellings. */
|
|
13
|
+
const DEFAULT_IGNORE_SELECTORS = [
|
|
14
|
+
"[data-storyshelf-ignore]",
|
|
15
|
+
".chromatic-ignore",
|
|
16
|
+
"[data-chromatic=\"ignore\"]"
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* The declarations applied to a masked element: hidden but still taking up its space. `opacity: 0`
|
|
20
|
+
* hides the whole subtree whatever its children declare (even an inline `visibility: visible
|
|
21
|
+
* !important`, which beats any stylesheet rule), because a child cannot be more opaque than its parent.
|
|
22
|
+
*/
|
|
23
|
+
const MASK_DECLARATION = "visibility: hidden !important; opacity: 0 !important;";
|
|
24
|
+
/** CSS whitespace only. `String.trim()` would also strip U+00A0, which is part of a CSS identifier. */
|
|
25
|
+
const CSS_EDGE_SPACE = /^[ \t\n\f\r]+|[ \t\n\f\r]+$/gu;
|
|
26
|
+
/** `value` without leading and trailing CSS whitespace. */
|
|
27
|
+
const trimCss = (value) => value.replaceAll(CSS_EDGE_SPACE, "");
|
|
28
|
+
/** Characters that could close the rule and open another one; such a selector is refused. */
|
|
29
|
+
const UNSAFE_SELECTOR = /[{}]|\/\*|<\/?style/iu;
|
|
30
|
+
/** Whether `selector` is non-empty text that cannot break out of a single CSS rule. */
|
|
31
|
+
function isSafeIgnoreSelector(selector) {
|
|
32
|
+
return typeof selector === "string" && trimCss(selector) !== "" && !UNSAFE_SELECTOR.test(selector);
|
|
33
|
+
}
|
|
34
|
+
/** Clean the declared `ignoreSelectors` value; anything that is not an array yields no selectors. */
|
|
35
|
+
function cleanIgnoreSelectors(value) {
|
|
36
|
+
const result = {
|
|
37
|
+
selectors: [],
|
|
38
|
+
skipped: []
|
|
39
|
+
};
|
|
40
|
+
if (!Array.isArray(value)) return result;
|
|
41
|
+
for (const entry of value) if (!isSafeIgnoreSelector(entry)) result.skipped.push(typeof entry === "string" ? entry : String(entry));
|
|
42
|
+
else if (result.selectors.length >= 50) result.skipped.push(entry);
|
|
43
|
+
else if (!result.selectors.includes(trimCss(entry))) result.selectors.push(trimCss(entry));
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The string entries of a declared `ignoreSelectors` value, untouched, or `undefined` when there
|
|
48
|
+
* are none. Safety and the cap are applied later by {@link cleanIgnoreSelectors}, in the runner,
|
|
49
|
+
* so a refused selector can still be reported on the snapshot log.
|
|
50
|
+
*/
|
|
51
|
+
function declaredIgnoreSelectors(value) {
|
|
52
|
+
const strings = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
53
|
+
return strings.length > 0 ? strings : void 0;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The stylesheet that masks `selectors` plus {@link DEFAULT_IGNORE_SELECTORS}. One rule per
|
|
57
|
+
* selector, so a selector the browser rejects cannot take the others down with it. Unsafe selectors
|
|
58
|
+
* are dropped here as well.
|
|
59
|
+
*/
|
|
60
|
+
function ignoreStylesheet(selectors = []) {
|
|
61
|
+
return [.../* @__PURE__ */ new Set([...DEFAULT_IGNORE_SELECTORS, ...selectors])].filter((selector) => isSafeIgnoreSelector(selector)).map((selector) => `${selector} { ${MASK_DECLARATION} }`).join("\n");
|
|
62
|
+
}
|
|
63
|
+
export { DEFAULT_IGNORE_SELECTORS, MAX_IGNORE_SELECTORS, cleanIgnoreSelectors, declaredIgnoreSelectors, ignoreStylesheet, isSafeIgnoreSelector };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=ignore-regions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ignore-regions.mjs","names":[],"sources":["../../src/capture/ignore-regions.ts"],"sourcesContent":["/**\n * Ignore regions: elements a story marks as dynamic (timestamps, avatars, ads) are hidden before\n * the screenshot so they never produce a diff. Both runners inject the stylesheet built here, so\n * the mask is identical across engines. Baseline keys and diff math are untouched.\n */\n\n/** Most selectors one story may declare; the rest are dropped. */\nexport const MAX_IGNORE_SELECTORS = 50;\n\n/** Selectors that always apply: StoryShelf's attribute, then the Chromatic migration spellings. */\nexport const DEFAULT_IGNORE_SELECTORS: readonly string[] = [\n \"[data-storyshelf-ignore]\",\n \".chromatic-ignore\",\n '[data-chromatic=\"ignore\"]',\n];\n\n/**\n * The declarations applied to a masked element: hidden but still taking up its space. `opacity: 0`\n * hides the whole subtree whatever its children declare (even an inline `visibility: visible\n * !important`, which beats any stylesheet rule), because a child cannot be more opaque than its parent.\n */\nconst MASK_DECLARATION = \"visibility: hidden !important; opacity: 0 !important;\";\n\n/** CSS whitespace only. `String.trim()` would also strip U+00A0, which is part of a CSS identifier. */\nconst CSS_EDGE_SPACE = /^[ \\t\\n\\f\\r]+|[ \\t\\n\\f\\r]+$/gu;\n\n/** `value` without leading and trailing CSS whitespace. */\nconst trimCss = (value: string): string => value.replaceAll(CSS_EDGE_SPACE, \"\");\n\n/** Characters that could close the rule and open another one; such a selector is refused. */\nconst UNSAFE_SELECTOR = /[{}]|\\/\\*|<\\/?style/iu;\n\n/** Whether `selector` is non-empty text that cannot break out of a single CSS rule. */\nexport function isSafeIgnoreSelector(selector: unknown): selector is string {\n return (\n typeof selector === \"string\" && trimCss(selector) !== \"\" && !UNSAFE_SELECTOR.test(selector)\n );\n}\n\n/** The result of reading a story's `ignoreSelectors`. */\nexport interface IgnoreSelectorList {\n /** Trimmed, deduplicated, safe selectors, at most {@link MAX_IGNORE_SELECTORS}. */\n selectors: string[];\n /** Entries that were refused (not a string, empty, or unsafe) or cut by the cap. */\n skipped: string[];\n}\n\n/** Clean the declared `ignoreSelectors` value; anything that is not an array yields no selectors. */\nexport function cleanIgnoreSelectors(value: unknown): IgnoreSelectorList {\n const result: IgnoreSelectorList = { selectors: [], skipped: [] };\n if (!Array.isArray(value)) return result;\n for (const entry of value as unknown[]) {\n if (!isSafeIgnoreSelector(entry)) {\n result.skipped.push(typeof entry === \"string\" ? entry : String(entry));\n } else if (result.selectors.length >= MAX_IGNORE_SELECTORS) {\n result.skipped.push(entry);\n } else if (!result.selectors.includes(trimCss(entry))) {\n result.selectors.push(trimCss(entry));\n }\n }\n return result;\n}\n\n/**\n * The string entries of a declared `ignoreSelectors` value, untouched, or `undefined` when there\n * are none. Safety and the cap are applied later by {@link cleanIgnoreSelectors}, in the runner,\n * so a refused selector can still be reported on the snapshot log.\n */\nexport function declaredIgnoreSelectors(value: unknown): string[] | undefined {\n const strings = Array.isArray(value)\n ? (value as unknown[]).filter((entry): entry is string => typeof entry === \"string\")\n : [];\n return strings.length > 0 ? strings : undefined;\n}\n\n/**\n * The stylesheet that masks `selectors` plus {@link DEFAULT_IGNORE_SELECTORS}. One rule per\n * selector, so a selector the browser rejects cannot take the others down with it. Unsafe selectors\n * are dropped here as well.\n */\nexport function ignoreStylesheet(selectors: readonly string[] = []): string {\n const all = [...new Set([...DEFAULT_IGNORE_SELECTORS, ...selectors])].filter((selector) =>\n isSafeIgnoreSelector(selector),\n );\n return all.map((selector) => `${selector} { ${MASK_DECLARATION} }`).join(\"\\n\");\n}\n"],"mappings":";;;;;;;;;;AAOA,MAAa,uBAAuB;;AAGpC,MAAa,2BAA8C;CACzD;CACA;CACA;AACF;;;;;;AAOA,MAAM,mBAAmB;;AAGzB,MAAM,iBAAiB;;AAGvB,MAAM,WAAW,UAA0B,MAAM,WAAW,gBAAgB,EAAE;;AAG9E,MAAM,kBAAkB;;AAGxB,SAAgB,qBAAqB,UAAuC;CAC1E,OACE,OAAO,aAAa,YAAY,QAAQ,QAAQ,MAAM,MAAM,CAAC,gBAAgB,KAAK,QAAQ;AAE9F;;AAWA,SAAgB,qBAAqB,OAAoC;CACvE,MAAM,SAA6B;EAAE,WAAW,CAAC;EAAG,SAAS,CAAC;CAAE;CAChE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;CAClC,KAAK,MAAM,SAAS,OAClB,IAAI,CAAC,qBAAqB,KAAK,GAC7B,OAAO,QAAQ,KAAK,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK,CAAC;MAChE,IAAI,OAAO,UAAU,UAAA,IAC1B,OAAO,QAAQ,KAAK,KAAK;MACpB,IAAI,CAAC,OAAO,UAAU,SAAS,QAAQ,KAAK,CAAC,GAClD,OAAO,UAAU,KAAK,QAAQ,KAAK,CAAC;CAGxC,OAAO;AACT;;;;;;AAOA,SAAgB,wBAAwB,OAAsC;CAC5E,MAAM,UAAU,MAAM,QAAQ,KAAK,IAC9B,MAAoB,QAAQ,UAA2B,OAAO,UAAU,QAAQ,IACjF,CAAC;CACL,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;AACxC;;;;;;AAOA,SAAgB,iBAAiB,YAA+B,CAAC,GAAW;CAI1E,OAHY,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,0BAA0B,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,aAC5E,qBAAqB,QAAQ,CAEtB,CAAC,CAAC,KAAK,aAAa,GAAG,SAAS,KAAK,iBAAiB,GAAG,CAAC,CAAC,KAAK,IAAI;AAC/E"}
|
package/dist/capture/isolate.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/capture/isolate.ts
|
|
5
5
|
/**
|
|
6
6
|
* Render one engine's stories, narrowing a crash down to the story that caused it.
|
|
7
7
|
*
|
|
@@ -135,7 +135,6 @@ function failRest(state, queue, note) {
|
|
|
135
135
|
function crashText(crash) {
|
|
136
136
|
return `browser crashed: ${crash instanceof Error ? crash.message : String(crash)}`;
|
|
137
137
|
}
|
|
138
|
-
//#endregion
|
|
139
138
|
export { renderIsolating };
|
|
140
139
|
|
|
141
140
|
//# sourceMappingURL=isolate.mjs.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/capture/job-lease.d.ts
|
|
4
3
|
/** Progress lease for in-process capture jobs: the same expiry idea as a remote queue's visibility timeout. */
|
|
5
4
|
/** What a running job calls to prove it is still making progress. */
|
|
6
5
|
interface JobLease {
|
|
@@ -23,6 +22,5 @@ declare class ProgressLease implements JobLease {
|
|
|
23
22
|
/** Stop watching; idempotent. */
|
|
24
23
|
stop(): void;
|
|
25
24
|
}
|
|
26
|
-
//#endregion
|
|
27
25
|
export { JobLease, ProgressLease };
|
|
28
26
|
//# sourceMappingURL=job-lease.d.mts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/capture/job-lease.ts
|
|
5
5
|
/**
|
|
6
6
|
* A lease renewed by progress rather than a timer.
|
|
7
7
|
*
|
|
@@ -51,7 +51,6 @@ var ProgressLease = class {
|
|
|
51
51
|
this.#arm(this.#leaseMs - idleMs);
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
|
-
//#endregion
|
|
55
54
|
export { ProgressLease };
|
|
56
55
|
|
|
57
56
|
//# sourceMappingURL=job-lease.mjs.map
|
package/dist/capture/matrix.mjs
CHANGED
|
@@ -10,9 +10,9 @@ import { partitionAffectedStories } from "./affected.mjs";
|
|
|
10
10
|
import { mirrorAttemptLog } from "./attempt-logger.mjs";
|
|
11
11
|
import { assertScreenshotBuffer } from "./guards.mjs";
|
|
12
12
|
import { renderIsolating } from "./isolate.mjs";
|
|
13
|
-
import { renderInSlices } from "./token-chunks.mjs";
|
|
13
|
+
import { SignedUrlLifetimeError, renderInSlices } from "./token-chunks.mjs";
|
|
14
14
|
import { currentImageEngine } from "@storyshelf/image";
|
|
15
|
-
|
|
15
|
+
// src/capture/matrix.ts
|
|
16
16
|
/** The browser loop: one partition + render pass per engine, sequentially. */
|
|
17
17
|
/**
|
|
18
18
|
* Capture every story on every engine, one engine at a time (browsers are
|
|
@@ -118,7 +118,7 @@ async function attemptPass(input, browser, mirror, stories) {
|
|
|
118
118
|
failures: pass.failures
|
|
119
119
|
};
|
|
120
120
|
} catch (error) {
|
|
121
|
-
if (error instanceof TargetLaunchError) throw error;
|
|
121
|
+
if (error instanceof TargetLaunchError || error instanceof SignedUrlLifetimeError) throw error;
|
|
122
122
|
mirror.log("warn", "render pass crashed, isolating the story at fault", {
|
|
123
123
|
browser,
|
|
124
124
|
stories: stories.length,
|
|
@@ -246,6 +246,7 @@ function renderRequest(input, browser, stories, spooled, progress) {
|
|
|
246
246
|
},
|
|
247
247
|
buildId: build.id,
|
|
248
248
|
runId: input.runId ?? build.id,
|
|
249
|
+
...input.shardIndex === void 0 ? {} : { shardIndex: input.shardIndex },
|
|
249
250
|
storybookDir: input.storybookDir,
|
|
250
251
|
stories,
|
|
251
252
|
viewports: input.viewports,
|
|
@@ -292,7 +293,6 @@ function withEnvironment(captures, environment) {
|
|
|
292
293
|
function tagged(rows, browser) {
|
|
293
294
|
return rows.map((row) => Object.assign({}, row, { browser }));
|
|
294
295
|
}
|
|
295
|
-
//#endregion
|
|
296
296
|
export { captureAcrossBrowsers, partitionBrowsers, renderWork };
|
|
297
297
|
|
|
298
298
|
//# sourceMappingURL=matrix.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matrix.mjs","names":[],"sources":["../../src/capture/matrix.ts"],"sourcesContent":["/** The browser loop: one partition + render pass per engine, sequentially. */\nimport { currentImageEngine } from \"@storyshelf/image\";\nimport type { Logger } from \"pino\";\nimport type {\n CaptureRunner,\n CaptureTarget,\n RenderFailure,\n RenderedSnapshot,\n} from \"../adapters/capture-runner.ts\";\nimport { TargetLaunchError } from \"../adapters/capture-target.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport type { AttemptLogRecorder } from \"../models/capture-attempt.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { CaptureLogLevel } from \"../schema/capture-attempt.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { storeScreenshot } from \"../utils/screenshot-store.ts\";\nimport { isFlakyStory, type StoryEntry, type Viewport } from \"./adapter.ts\";\nimport { partitionAffectedStories, type InheritedStory } from \"./affected.ts\";\nimport { mirrorAttemptLog, type AttemptMirror } from \"./attempt-logger.ts\";\nimport { primaryTargetOf, resolveBrowsers } from \"./browsers.ts\";\nimport { assertScreenshotBuffer } from \"./guards.ts\";\nimport { renderIsolating, type IsolationNote, type PassOutcome } from \"./isolate.ts\";\nimport type { PipelineTables } from \"./pipeline.ts\";\nimport type { CaptureProgress } from \"./progress.ts\";\nimport type { RenderRequest } from \"./render.ts\";\nimport type { EngineWork } from \"./shard-plan.ts\";\nimport type { StorybookUrlSource } from \"./storybook-urls.ts\";\nimport { resolveStoryViewports } from \"./storybook.ts\";\nimport { renderInSlices } from \"./token-chunks.ts\";\n\n/** Inputs shared by every engine's pass. */\nexport interface MatrixInput {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n tables: PipelineTables;\n runner: CaptureRunner;\n build: Build;\n project: Project;\n stories: StoryEntry[];\n viewports: Viewport[];\n storybookDir: string;\n browsers: readonly CaptureTarget[];\n affectedPaths: string[] | null;\n /** Explicit render budget; unset scales with the number of captures. */\n renderTimeoutMs?: number | undefined;\n logger: Logger | undefined;\n /** Records the pass on the build attempt (partitioning, progress, failures). */\n recordLog?: AttemptLogRecorder | undefined;\n /** Called on every screenshot taken, so a long healthy render keeps its job's lease. */\n progress?: CaptureProgress | undefined;\n /**\n * Handle for this job's render calls, so cancelling it reaches only them: the shard id when\n * rendering a shard (sibling shards of one build render at once), the build id otherwise.\n */\n runId?: string | undefined;\n /** Mints the signed URL for a `reach: \"url\"` target at the start of each pass. */\n storybookUrl?: StorybookUrlSource | undefined;\n}\n\n/** Everything the passes produced, every row tagged with its engine. */\nexport interface MatrixResult {\n captures: RenderedSnapshot[];\n failures: RenderFailure[];\n inherited: InheritedStory[];\n /** Stories rendered, summed over engines. */\n renderCount: number;\n}\n\n/**\n * Capture every story on every engine, one engine at a time (browsers are\n * heavy; parallel launches would multiply memory). Each engine resolves its\n * own baselines, so affected capture inherits per engine.\n */\nexport async function captureAcrossBrowsers(input: MatrixInput): Promise<MatrixResult> {\n const result: MatrixResult = { captures: [], failures: [], inherited: [], renderCount: 0 };\n for (const browser of input.browsers) {\n // oxlint-disable-next-line no-await-in-loop -- engines run sequentially by design\n const pass = await captureOnBrowser(input, browser);\n result.captures.push(...pass.captures);\n result.failures.push(...pass.failures);\n result.inherited.push(...pass.inherited);\n result.renderCount += pass.renderCount;\n }\n return result;\n}\n\n/** Which stories each engine renders and which inherit their baseline, decided before any render. */\nexport interface PartitionedWork {\n work: EngineWork[];\n inherited: InheritedStory[];\n}\n\n/**\n * Affected-capture partitioning for every engine, without rendering: the plan stage of a sharded\n * capture decides what to render (and how to slice it) before any browser starts.\n */\nexport async function partitionBrowsers(input: MatrixInput): Promise<PartitionedWork> {\n const { project, build } = input;\n const work: EngineWork[] = [];\n const inherited: InheritedStory[] = [];\n for (const browser of input.browsers) {\n // oxlint-disable-next-line no-await-in-loop -- one engine at a time, like the render loop\n const partition = await partitionAffectedStories({\n db: input.db,\n tables: input.tables,\n projectId: project.id,\n branch: build.gitBranch,\n defaultBranch: project.gitDefaultBranch,\n stories: input.stories,\n viewports: input.viewports,\n browser,\n affectedPaths: input.affectedPaths,\n });\n work.push({ browser, stories: partition.render });\n inherited.push(...tagged(partition.inherited, browser));\n }\n return { work, inherited };\n}\n\n/**\n * Render explicit per-engine work (one shard's slice), engine by engine. Nothing is inherited here.\n *\n * A story that crashes the browser is narrowed down and failed on its own, so it does not take the\n * rest of the slice with it — see {@link renderIsolating}.\n */\nexport async function renderWork(\n input: MatrixInput,\n work: readonly EngineWork[],\n): Promise<MatrixResult> {\n const result: MatrixResult = { captures: [], failures: [], inherited: [], renderCount: 0 };\n for (const engine of work) {\n const mirror = mirrorAttemptLog(input.logger, input.recordLog, flakyDowngrade(input.stories));\n try {\n // oxlint-disable-next-line no-await-in-loop -- engines run sequentially by design\n const pass = await isolatedPass(input, engine.browser, mirror, engine.stories, []);\n result.captures.push(...pass.captures);\n result.failures.push(...pass.failures);\n result.renderCount += pass.renderCount;\n } finally {\n // oxlint-disable-next-line no-await-in-loop -- flush before the next engine starts\n await mirror.flush();\n }\n }\n return result;\n}\n\n/**\n * One engine's render, with a crashing story narrowed down and failed on its own rather than taking\n * the rest of the pass with it — see {@link renderIsolating}.\n */\nasync function isolatedPass(\n input: MatrixInput,\n browser: CaptureTarget,\n mirror: AttemptMirror,\n toRender: StoryEntry[],\n inherited: InheritedStory[],\n): Promise<MatrixResult> {\n const pass = await renderIsolating(\n toRender,\n async (slice) => await attemptPass(input, browser, mirror, slice),\n {\n note: isolationNote(mirror, browser),\n viewportsOf: (story) =>\n resolveStoryViewports(story, input.viewports).map((viewport) => viewport.name),\n },\n );\n return {\n captures: fillEnvironment(pass.captures),\n failures: pass.failures,\n inherited,\n renderCount: pass.renderCount,\n };\n}\n\n/** One render pass that never throws: a crash comes back beside whatever it captured first. */\nasync function attemptPass(\n input: MatrixInput,\n browser: CaptureTarget,\n mirror: AttemptMirror,\n stories: StoryEntry[],\n): Promise<PassOutcome> {\n const spooled: RenderedSnapshot[] = [];\n try {\n const pass = await renderPass(input, browser, mirror, stories, [], spooled);\n return { captures: pass.captures, failures: pass.failures };\n } catch (error) {\n // The browser never started: no story is at fault, so it is not isolated or retried.\n if (error instanceof TargetLaunchError) {\n throw error;\n }\n mirror.log(\"warn\", \"render pass crashed, isolating the story at fault\", {\n browser,\n stories: stories.length,\n captured: spooled.length,\n err: error instanceof Error ? error.message : String(error),\n });\n return { captures: spooled, failures: [], error };\n }\n}\n\n/** Record how a crash was narrowed down on the attempt log. */\nfunction isolationNote(mirror: AttemptMirror, browser: CaptureTarget): IsolationNote {\n return (event, fields) => {\n if (event === \"poisoned\") {\n mirror.log(\"error\", \"story crashed the browser on its own\", { browser, ...fields });\n return;\n }\n const message =\n event === \"isolating\"\n ? \"retrying the stories a crash interrupted\"\n : \"giving up on the stories left after repeated crashes\";\n mirror.log(\"warn\", message, { browser, ...fields });\n };\n}\n\n/**\n * Give captures spooled before a crash the environment a later pass reported: every pass of an\n * engine runs on the same browser build and host, so the one fingerprint covers them all.\n */\nfunction fillEnvironment(captures: RenderedSnapshot[]): RenderedSnapshot[] {\n const known = captures.find((capture) => capture.environment)?.environment;\n if (!known) {\n return captures;\n }\n for (const capture of captures) {\n capture.environment ??= known;\n }\n return captures;\n}\n\nasync function captureOnBrowser(input: MatrixInput, browser: CaptureTarget): Promise<MatrixResult> {\n const mirror = mirrorAttemptLog(input.logger, input.recordLog, flakyDowngrade(input.stories));\n try {\n return await passOnBrowser(input, browser, mirror);\n } finally {\n await mirror.flush();\n }\n}\n\n/**\n * Failures of flaky stories are non-blocking, so the attempt log shows them as\n * warnings (matching \"flaky story failed (non-blocking)\"), not errors.\n */\nfunction flakyDowngrade(\n stories: StoryEntry[],\n): (level: CaptureLogLevel, fields?: Record<string, unknown>) => CaptureLogLevel {\n const flaky = new Set(stories.filter((story) => isFlakyStory(story)).map((story) => story.id));\n return (level, fields) =>\n level === \"error\" && typeof fields?.[\"storyId\"] === \"string\" && flaky.has(fields[\"storyId\"])\n ? \"warn\"\n : level;\n}\n\nasync function passOnBrowser(\n input: MatrixInput,\n browser: CaptureTarget,\n mirror: AttemptMirror,\n): Promise<MatrixResult> {\n const { project, build } = input;\n const partition = await partitionAffectedStories({\n db: input.db,\n tables: input.tables,\n projectId: project.id,\n branch: build.gitBranch,\n defaultBranch: project.gitDefaultBranch,\n stories: input.stories,\n viewports: input.viewports,\n browser,\n affectedPaths: input.affectedPaths,\n });\n mirror.log(\"info\", \"affected capture partitioned\", {\n browser,\n rendered: partition.render.length,\n inherited: partition.inherited.length,\n total: input.stories.length,\n });\n const inherited = tagged(partition.inherited, browser);\n if (partition.render.length === 0) {\n return { captures: [], failures: [], inherited, renderCount: 0 };\n }\n return await isolatedPass(input, browser, mirror, partition.render, inherited);\n}\n\n/**\n * One engine's render call. `spooled` may be supplied by the caller so that the screenshots taken\n * before a crash survive the throw (they stream in through `onCapture`).\n */\nasync function renderPass(\n input: MatrixInput,\n browser: CaptureTarget,\n mirror: AttemptMirror,\n toRender: StoryEntry[],\n inherited: InheritedStory[],\n spooled: RenderedSnapshot[] = [],\n): Promise<MatrixResult> {\n const expected = toRender.flatMap((story) =>\n resolveStoryViewports(story, input.viewports),\n ).length;\n const rendered = await renderInSlices({\n runner: input.runner,\n request: renderRequest(input, browser, toRender, spooled, { mirror, expected }),\n browser,\n storybookUrl: input.storybookUrl,\n captureCount: (story) => resolveStoryViewports(story, input.viewports).length,\n renderTimeoutMs: input.renderTimeoutMs,\n });\n mirror.log(\"info\", \"render pass finished\", {\n browser,\n captured: spooled.length + rendered.captures.length,\n failed: rendered.failures.length,\n expected,\n imageEngine: currentImageEngine(),\n });\n const { environment } = rendered;\n return {\n captures: withEnvironment([...spooled, ...tagged(rendered.captures, browser)], environment),\n failures: tagged(rendered.failures, browser),\n inherited,\n renderCount: toRender.length,\n };\n}\n\n/** The render call for one engine; captures stream into `spooled` as they are taken. */\nfunction renderRequest(\n input: MatrixInput,\n browser: CaptureTarget,\n stories: StoryEntry[],\n spooled: RenderedSnapshot[],\n progress: { mirror: AttemptMirror; expected: number },\n): RenderRequest {\n const { project, build } = input;\n return {\n onCapture: async (capture) => {\n spooled.push(await spoolCapture(input, browser, capture));\n input.progress?.();\n logProgress(progress, browser, spooled.length);\n },\n buildId: build.id,\n runId: input.runId ?? build.id,\n storybookDir: input.storybookDir,\n stories,\n viewports: input.viewports,\n logger: progress.mirror.logger,\n executePlay: project.executePlay ?? false,\n playTimeoutMs: project.playTimeoutMs ?? 10_000,\n runA11y: (project.runA11y ?? true) && browser === primaryTargetOf(resolveBrowsers(project)),\n target: browser,\n };\n}\n\n/** Screenshots between progress lines on the attempt log. */\nconst PROGRESS_EVERY = 10;\n\n/** Record \"captured N/M\" every few screenshots so a long render keeps showing movement. */\nfunction logProgress(\n progress: { mirror: AttemptMirror; expected: number },\n browser: CaptureTarget,\n captured: number,\n): void {\n if (captured % PROGRESS_EVERY !== 0) return;\n progress.mirror.log(\"info\", `captured ${captured}/${progress.expected} screenshots`, {\n browser,\n captured,\n expected: progress.expected,\n });\n}\n\n/**\n * Write a screenshot to storage the moment it is captured and keep only its\n * metadata, so memory holds the pages in flight rather than the whole build.\n */\nasync function spoolCapture(\n input: MatrixInput,\n browser: CaptureTarget,\n capture: RenderedSnapshot,\n): Promise<RenderedSnapshot> {\n assertScreenshotBuffer(capture.screenshot);\n const storedPath = await storeScreenshot(input.storage, input.project.id, capture.screenshot);\n return { ...capture, browser, screenshot: EMPTY_SCREENSHOT, stored: true, storedPath };\n}\n\nconst EMPTY_SCREENSHOT = Buffer.alloc(0);\n\n/** Attach the render pass's environment to captures that carry none (a slice may have set its own). */\nfunction withEnvironment(\n captures: RenderedSnapshot[],\n environment: RenderedSnapshot[\"environment\"],\n): RenderedSnapshot[] {\n if (environment) {\n for (const capture of captures) {\n capture.environment ??= environment;\n }\n }\n return captures;\n}\n\n/** Copy each row with its engine (runners may reuse result objects across calls). */\nfunction tagged<T extends { browser?: string | undefined }>(\n rows: T[],\n browser: CaptureTarget,\n): T[] {\n return rows.map((row) => Object.assign({}, row, { browser }));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"matrix.mjs","names":[],"sources":["../../src/capture/matrix.ts"],"sourcesContent":["/** The browser loop: one partition + render pass per engine, sequentially. */\nimport { currentImageEngine } from \"@storyshelf/image\";\nimport type { Logger } from \"pino\";\nimport type {\n CaptureRunner,\n CaptureTarget,\n RenderFailure,\n RenderedSnapshot,\n} from \"../adapters/capture-runner.ts\";\nimport { TargetLaunchError } from \"../adapters/capture-target.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport type { AttemptLogRecorder } from \"../models/capture-attempt.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { CaptureLogLevel } from \"../schema/capture-attempt.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { storeScreenshot } from \"../utils/screenshot-store.ts\";\nimport { isFlakyStory, type StoryEntry, type Viewport } from \"./adapter.ts\";\nimport { partitionAffectedStories, type InheritedStory } from \"./affected.ts\";\nimport { mirrorAttemptLog, type AttemptMirror } from \"./attempt-logger.ts\";\nimport { primaryTargetOf, resolveBrowsers } from \"./browsers.ts\";\nimport { assertScreenshotBuffer } from \"./guards.ts\";\nimport { renderIsolating, type IsolationNote, type PassOutcome } from \"./isolate.ts\";\nimport type { PipelineTables } from \"./pipeline.ts\";\nimport type { CaptureProgress } from \"./progress.ts\";\nimport type { RenderRequest } from \"./render.ts\";\nimport type { EngineWork } from \"./shard-plan.ts\";\nimport type { StorybookUrlSource } from \"./storybook-urls.ts\";\nimport { resolveStoryViewports } from \"./storybook.ts\";\nimport { renderInSlices, SignedUrlLifetimeError } from \"./token-chunks.ts\";\n\n/** Inputs shared by every engine's pass. */\nexport interface MatrixInput {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n tables: PipelineTables;\n runner: CaptureRunner;\n build: Build;\n project: Project;\n stories: StoryEntry[];\n viewports: Viewport[];\n storybookDir: string;\n browsers: readonly CaptureTarget[];\n affectedPaths: string[] | null;\n /** Explicit render budget; unset scales with the number of captures. */\n renderTimeoutMs?: number | undefined;\n logger: Logger | undefined;\n /** Records the pass on the build attempt (partitioning, progress, failures). */\n recordLog?: AttemptLogRecorder | undefined;\n /** Called on every screenshot taken, so a long healthy render keeps its job's lease. */\n progress?: CaptureProgress | undefined;\n /**\n * Handle for this job's render calls, so cancelling it reaches only them: the shard id when\n * rendering a shard (sibling shards of one build render at once), the build id otherwise.\n */\n runId?: string | undefined;\n /** The shard's 1-based position in the plan, passed to the runner; absent outside a shard. */\n shardIndex?: number | undefined;\n /** Mints the signed URL for a `reach: \"url\"` target at the start of each pass. */\n storybookUrl?: StorybookUrlSource | undefined;\n}\n\n/** Everything the passes produced, every row tagged with its engine. */\nexport interface MatrixResult {\n captures: RenderedSnapshot[];\n failures: RenderFailure[];\n inherited: InheritedStory[];\n /** Stories rendered, summed over engines. */\n renderCount: number;\n}\n\n/**\n * Capture every story on every engine, one engine at a time (browsers are\n * heavy; parallel launches would multiply memory). Each engine resolves its\n * own baselines, so affected capture inherits per engine.\n */\nexport async function captureAcrossBrowsers(input: MatrixInput): Promise<MatrixResult> {\n const result: MatrixResult = { captures: [], failures: [], inherited: [], renderCount: 0 };\n for (const browser of input.browsers) {\n // oxlint-disable-next-line no-await-in-loop -- engines run sequentially by design\n const pass = await captureOnBrowser(input, browser);\n result.captures.push(...pass.captures);\n result.failures.push(...pass.failures);\n result.inherited.push(...pass.inherited);\n result.renderCount += pass.renderCount;\n }\n return result;\n}\n\n/** Which stories each engine renders and which inherit their baseline, decided before any render. */\nexport interface PartitionedWork {\n work: EngineWork[];\n inherited: InheritedStory[];\n}\n\n/**\n * Affected-capture partitioning for every engine, without rendering: the plan stage of a sharded\n * capture decides what to render (and how to slice it) before any browser starts.\n */\nexport async function partitionBrowsers(input: MatrixInput): Promise<PartitionedWork> {\n const { project, build } = input;\n const work: EngineWork[] = [];\n const inherited: InheritedStory[] = [];\n for (const browser of input.browsers) {\n // oxlint-disable-next-line no-await-in-loop -- one engine at a time, like the render loop\n const partition = await partitionAffectedStories({\n db: input.db,\n tables: input.tables,\n projectId: project.id,\n branch: build.gitBranch,\n defaultBranch: project.gitDefaultBranch,\n stories: input.stories,\n viewports: input.viewports,\n browser,\n affectedPaths: input.affectedPaths,\n });\n work.push({ browser, stories: partition.render });\n inherited.push(...tagged(partition.inherited, browser));\n }\n return { work, inherited };\n}\n\n/**\n * Render explicit per-engine work (one shard's slice), engine by engine. Nothing is inherited here.\n *\n * A story that crashes the browser is narrowed down and failed on its own, so it does not take the\n * rest of the slice with it — see {@link renderIsolating}.\n */\nexport async function renderWork(\n input: MatrixInput,\n work: readonly EngineWork[],\n): Promise<MatrixResult> {\n const result: MatrixResult = { captures: [], failures: [], inherited: [], renderCount: 0 };\n for (const engine of work) {\n const mirror = mirrorAttemptLog(input.logger, input.recordLog, flakyDowngrade(input.stories));\n try {\n // oxlint-disable-next-line no-await-in-loop -- engines run sequentially by design\n const pass = await isolatedPass(input, engine.browser, mirror, engine.stories, []);\n result.captures.push(...pass.captures);\n result.failures.push(...pass.failures);\n result.renderCount += pass.renderCount;\n } finally {\n // oxlint-disable-next-line no-await-in-loop -- flush before the next engine starts\n await mirror.flush();\n }\n }\n return result;\n}\n\n/**\n * One engine's render, with a crashing story narrowed down and failed on its own rather than taking\n * the rest of the pass with it — see {@link renderIsolating}.\n */\nasync function isolatedPass(\n input: MatrixInput,\n browser: CaptureTarget,\n mirror: AttemptMirror,\n toRender: StoryEntry[],\n inherited: InheritedStory[],\n): Promise<MatrixResult> {\n const pass = await renderIsolating(\n toRender,\n async (slice) => await attemptPass(input, browser, mirror, slice),\n {\n note: isolationNote(mirror, browser),\n viewportsOf: (story) =>\n resolveStoryViewports(story, input.viewports).map((viewport) => viewport.name),\n },\n );\n return {\n captures: fillEnvironment(pass.captures),\n failures: pass.failures,\n inherited,\n renderCount: pass.renderCount,\n };\n}\n\n/** One render pass that never throws: a crash comes back beside whatever it captured first. */\nasync function attemptPass(\n input: MatrixInput,\n browser: CaptureTarget,\n mirror: AttemptMirror,\n stories: StoryEntry[],\n): Promise<PassOutcome> {\n const spooled: RenderedSnapshot[] = [];\n try {\n const pass = await renderPass(input, browser, mirror, stories, [], spooled);\n return { captures: pass.captures, failures: pass.failures };\n } catch (error) {\n // The browser never started, or the signed URL lifetime cannot fit the pass: no story is at\n // fault, so it is not isolated or retried.\n if (error instanceof TargetLaunchError || error instanceof SignedUrlLifetimeError) {\n throw error;\n }\n mirror.log(\"warn\", \"render pass crashed, isolating the story at fault\", {\n browser,\n stories: stories.length,\n captured: spooled.length,\n err: error instanceof Error ? error.message : String(error),\n });\n return { captures: spooled, failures: [], error };\n }\n}\n\n/** Record how a crash was narrowed down on the attempt log. */\nfunction isolationNote(mirror: AttemptMirror, browser: CaptureTarget): IsolationNote {\n return (event, fields) => {\n if (event === \"poisoned\") {\n mirror.log(\"error\", \"story crashed the browser on its own\", { browser, ...fields });\n return;\n }\n const message =\n event === \"isolating\"\n ? \"retrying the stories a crash interrupted\"\n : \"giving up on the stories left after repeated crashes\";\n mirror.log(\"warn\", message, { browser, ...fields });\n };\n}\n\n/**\n * Give captures spooled before a crash the environment a later pass reported: every pass of an\n * engine runs on the same browser build and host, so the one fingerprint covers them all.\n */\nfunction fillEnvironment(captures: RenderedSnapshot[]): RenderedSnapshot[] {\n const known = captures.find((capture) => capture.environment)?.environment;\n if (!known) {\n return captures;\n }\n for (const capture of captures) {\n capture.environment ??= known;\n }\n return captures;\n}\n\nasync function captureOnBrowser(input: MatrixInput, browser: CaptureTarget): Promise<MatrixResult> {\n const mirror = mirrorAttemptLog(input.logger, input.recordLog, flakyDowngrade(input.stories));\n try {\n return await passOnBrowser(input, browser, mirror);\n } finally {\n await mirror.flush();\n }\n}\n\n/**\n * Failures of flaky stories are non-blocking, so the attempt log shows them as\n * warnings (matching \"flaky story failed (non-blocking)\"), not errors.\n */\nfunction flakyDowngrade(\n stories: StoryEntry[],\n): (level: CaptureLogLevel, fields?: Record<string, unknown>) => CaptureLogLevel {\n const flaky = new Set(stories.filter((story) => isFlakyStory(story)).map((story) => story.id));\n return (level, fields) =>\n level === \"error\" && typeof fields?.[\"storyId\"] === \"string\" && flaky.has(fields[\"storyId\"])\n ? \"warn\"\n : level;\n}\n\nasync function passOnBrowser(\n input: MatrixInput,\n browser: CaptureTarget,\n mirror: AttemptMirror,\n): Promise<MatrixResult> {\n const { project, build } = input;\n const partition = await partitionAffectedStories({\n db: input.db,\n tables: input.tables,\n projectId: project.id,\n branch: build.gitBranch,\n defaultBranch: project.gitDefaultBranch,\n stories: input.stories,\n viewports: input.viewports,\n browser,\n affectedPaths: input.affectedPaths,\n });\n mirror.log(\"info\", \"affected capture partitioned\", {\n browser,\n rendered: partition.render.length,\n inherited: partition.inherited.length,\n total: input.stories.length,\n });\n const inherited = tagged(partition.inherited, browser);\n if (partition.render.length === 0) {\n return { captures: [], failures: [], inherited, renderCount: 0 };\n }\n return await isolatedPass(input, browser, mirror, partition.render, inherited);\n}\n\n/**\n * One engine's render call. `spooled` may be supplied by the caller so that the screenshots taken\n * before a crash survive the throw (they stream in through `onCapture`).\n */\nasync function renderPass(\n input: MatrixInput,\n browser: CaptureTarget,\n mirror: AttemptMirror,\n toRender: StoryEntry[],\n inherited: InheritedStory[],\n spooled: RenderedSnapshot[] = [],\n): Promise<MatrixResult> {\n const expected = toRender.flatMap((story) =>\n resolveStoryViewports(story, input.viewports),\n ).length;\n const rendered = await renderInSlices({\n runner: input.runner,\n request: renderRequest(input, browser, toRender, spooled, { mirror, expected }),\n browser,\n storybookUrl: input.storybookUrl,\n captureCount: (story) => resolveStoryViewports(story, input.viewports).length,\n renderTimeoutMs: input.renderTimeoutMs,\n });\n mirror.log(\"info\", \"render pass finished\", {\n browser,\n captured: spooled.length + rendered.captures.length,\n failed: rendered.failures.length,\n expected,\n imageEngine: currentImageEngine(),\n });\n const { environment } = rendered;\n return {\n captures: withEnvironment([...spooled, ...tagged(rendered.captures, browser)], environment),\n failures: tagged(rendered.failures, browser),\n inherited,\n renderCount: toRender.length,\n };\n}\n\n/** The render call for one engine; captures stream into `spooled` as they are taken. */\nfunction renderRequest(\n input: MatrixInput,\n browser: CaptureTarget,\n stories: StoryEntry[],\n spooled: RenderedSnapshot[],\n progress: { mirror: AttemptMirror; expected: number },\n): RenderRequest {\n const { project, build } = input;\n return {\n onCapture: async (capture) => {\n spooled.push(await spoolCapture(input, browser, capture));\n input.progress?.();\n logProgress(progress, browser, spooled.length);\n },\n buildId: build.id,\n runId: input.runId ?? build.id,\n ...(input.shardIndex === undefined ? {} : { shardIndex: input.shardIndex }),\n storybookDir: input.storybookDir,\n stories,\n viewports: input.viewports,\n logger: progress.mirror.logger,\n executePlay: project.executePlay ?? false,\n playTimeoutMs: project.playTimeoutMs ?? 10_000,\n runA11y: (project.runA11y ?? true) && browser === primaryTargetOf(resolveBrowsers(project)),\n target: browser,\n };\n}\n\n/** Screenshots between progress lines on the attempt log. */\nconst PROGRESS_EVERY = 10;\n\n/** Record \"captured N/M\" every few screenshots so a long render keeps showing movement. */\nfunction logProgress(\n progress: { mirror: AttemptMirror; expected: number },\n browser: CaptureTarget,\n captured: number,\n): void {\n if (captured % PROGRESS_EVERY !== 0) return;\n progress.mirror.log(\"info\", `captured ${captured}/${progress.expected} screenshots`, {\n browser,\n captured,\n expected: progress.expected,\n });\n}\n\n/**\n * Write a screenshot to storage the moment it is captured and keep only its\n * metadata, so memory holds the pages in flight rather than the whole build.\n */\nasync function spoolCapture(\n input: MatrixInput,\n browser: CaptureTarget,\n capture: RenderedSnapshot,\n): Promise<RenderedSnapshot> {\n assertScreenshotBuffer(capture.screenshot);\n const storedPath = await storeScreenshot(input.storage, input.project.id, capture.screenshot);\n return { ...capture, browser, screenshot: EMPTY_SCREENSHOT, stored: true, storedPath };\n}\n\nconst EMPTY_SCREENSHOT = Buffer.alloc(0);\n\n/** Attach the render pass's environment to captures that carry none (a slice may have set its own). */\nfunction withEnvironment(\n captures: RenderedSnapshot[],\n environment: RenderedSnapshot[\"environment\"],\n): RenderedSnapshot[] {\n if (environment) {\n for (const capture of captures) {\n capture.environment ??= environment;\n }\n }\n return captures;\n}\n\n/** Copy each row with its engine (runners may reuse result objects across calls). */\nfunction tagged<T extends { browser?: string | undefined }>(\n rows: T[],\n browser: CaptureTarget,\n): T[] {\n return rows.map((row) => Object.assign({}, row, { browser }));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA4EA,eAAsB,sBAAsB,OAA2C;CACrF,MAAM,SAAuB;EAAE,UAAU,CAAC;EAAG,UAAU,CAAC;EAAG,WAAW,CAAC;EAAG,aAAa;CAAE;CACzF,KAAK,MAAM,WAAW,MAAM,UAAU;EAEpC,MAAM,OAAO,MAAM,iBAAiB,OAAO,OAAO;EAClD,OAAO,SAAS,KAAK,GAAG,KAAK,QAAQ;EACrC,OAAO,SAAS,KAAK,GAAG,KAAK,QAAQ;EACrC,OAAO,UAAU,KAAK,GAAG,KAAK,SAAS;EACvC,OAAO,eAAe,KAAK;CAC7B;CACA,OAAO;AACT;;;;;AAYA,eAAsB,kBAAkB,OAA8C;CACpF,MAAM,EAAE,SAAS,UAAU;CAC3B,MAAM,OAAqB,CAAC;CAC5B,MAAM,YAA8B,CAAC;CACrC,KAAK,MAAM,WAAW,MAAM,UAAU;EAEpC,MAAM,YAAY,MAAM,yBAAyB;GAC/C,IAAI,MAAM;GACV,QAAQ,MAAM;GACd,WAAW,QAAQ;GACnB,QAAQ,MAAM;GACd,eAAe,QAAQ;GACvB,SAAS,MAAM;GACf,WAAW,MAAM;GACjB;GACA,eAAe,MAAM;EACvB,CAAC;EACD,KAAK,KAAK;GAAE;GAAS,SAAS,UAAU;EAAO,CAAC;EAChD,UAAU,KAAK,GAAG,OAAO,UAAU,WAAW,OAAO,CAAC;CACxD;CACA,OAAO;EAAE;EAAM;CAAU;AAC3B;;;;;;;AAQA,eAAsB,WACpB,OACA,MACuB;CACvB,MAAM,SAAuB;EAAE,UAAU,CAAC;EAAG,UAAU,CAAC;EAAG,WAAW,CAAC;EAAG,aAAa;CAAE;CACzF,KAAK,MAAM,UAAU,MAAM;EACzB,MAAM,SAAS,iBAAiB,MAAM,QAAQ,MAAM,WAAW,eAAe,MAAM,OAAO,CAAC;EAC5F,IAAI;GAEF,MAAM,OAAO,MAAM,aAAa,OAAO,OAAO,SAAS,QAAQ,OAAO,SAAS,CAAC,CAAC;GACjF,OAAO,SAAS,KAAK,GAAG,KAAK,QAAQ;GACrC,OAAO,SAAS,KAAK,GAAG,KAAK,QAAQ;GACrC,OAAO,eAAe,KAAK;EAC7B,UAAU;GAER,MAAM,OAAO,MAAM;EACrB;CACF;CACA,OAAO;AACT;;;;;AAMA,eAAe,aACb,OACA,SACA,QACA,UACA,WACuB;CACvB,MAAM,OAAO,MAAM,gBACjB,UACA,OAAO,UAAU,MAAM,YAAY,OAAO,SAAS,QAAQ,KAAK,GAChE;EACE,MAAM,cAAc,QAAQ,OAAO;EACnC,cAAc,UACZ,sBAAsB,OAAO,MAAM,SAAS,CAAC,CAAC,KAAK,aAAa,SAAS,IAAI;CACjF,CACF;CACA,OAAO;EACL,UAAU,gBAAgB,KAAK,QAAQ;EACvC,UAAU,KAAK;EACf;EACA,aAAa,KAAK;CACpB;AACF;;AAGA,eAAe,YACb,OACA,SACA,QACA,SACsB;CACtB,MAAM,UAA8B,CAAC;CACrC,IAAI;EACF,MAAM,OAAO,MAAM,WAAW,OAAO,SAAS,QAAQ,SAAS,CAAC,GAAG,OAAO;EAC1E,OAAO;GAAE,UAAU,KAAK;GAAU,UAAU,KAAK;EAAS;CAC5D,SAAS,OAAO;EAGd,IAAI,iBAAiB,qBAAqB,iBAAiB,wBACzD,MAAM;EAER,OAAO,IAAI,QAAQ,qDAAqD;GACtE;GACA,SAAS,QAAQ;GACjB,UAAU,QAAQ;GAClB,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAC5D,CAAC;EACD,OAAO;GAAE,UAAU;GAAS,UAAU,CAAC;GAAG;EAAM;CAClD;AACF;;AAGA,SAAS,cAAc,QAAuB,SAAuC;CACnF,QAAQ,OAAO,WAAW;EACxB,IAAI,UAAU,YAAY;GACxB,OAAO,IAAI,SAAS,wCAAwC;IAAE;IAAS,GAAG;GAAO,CAAC;GAClF;EACF;EACA,MAAM,UACJ,UAAU,cACN,6CACA;EACN,OAAO,IAAI,QAAQ,SAAS;GAAE;GAAS,GAAG;EAAO,CAAC;CACpD;AACF;;;;;AAMA,SAAS,gBAAgB,UAAkD;CACzE,MAAM,QAAQ,SAAS,MAAM,YAAY,QAAQ,WAAW,CAAC,EAAE;CAC/D,IAAI,CAAC,OACH,OAAO;CAET,KAAK,MAAM,WAAW,UACpB,QAAQ,gBAAgB;CAE1B,OAAO;AACT;AAEA,eAAe,iBAAiB,OAAoB,SAA+C;CACjG,MAAM,SAAS,iBAAiB,MAAM,QAAQ,MAAM,WAAW,eAAe,MAAM,OAAO,CAAC;CAC5F,IAAI;EACF,OAAO,MAAM,cAAc,OAAO,SAAS,MAAM;CACnD,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;AACF;;;;;AAMA,SAAS,eACP,SAC+E;CAC/E,MAAM,QAAQ,IAAI,IAAI,QAAQ,QAAQ,UAAU,aAAa,KAAK,CAAC,CAAC,CAAC,KAAK,UAAU,MAAM,EAAE,CAAC;CAC7F,QAAQ,OAAO,WACb,UAAU,WAAW,OAAO,SAAS,eAAe,YAAY,MAAM,IAAI,OAAO,UAAU,IACvF,SACA;AACR;AAEA,eAAe,cACb,OACA,SACA,QACuB;CACvB,MAAM,EAAE,SAAS,UAAU;CAC3B,MAAM,YAAY,MAAM,yBAAyB;EAC/C,IAAI,MAAM;EACV,QAAQ,MAAM;EACd,WAAW,QAAQ;EACnB,QAAQ,MAAM;EACd,eAAe,QAAQ;EACvB,SAAS,MAAM;EACf,WAAW,MAAM;EACjB;EACA,eAAe,MAAM;CACvB,CAAC;CACD,OAAO,IAAI,QAAQ,gCAAgC;EACjD;EACA,UAAU,UAAU,OAAO;EAC3B,WAAW,UAAU,UAAU;EAC/B,OAAO,MAAM,QAAQ;CACvB,CAAC;CACD,MAAM,YAAY,OAAO,UAAU,WAAW,OAAO;CACrD,IAAI,UAAU,OAAO,WAAW,GAC9B,OAAO;EAAE,UAAU,CAAC;EAAG,UAAU,CAAC;EAAG;EAAW,aAAa;CAAE;CAEjE,OAAO,MAAM,aAAa,OAAO,SAAS,QAAQ,UAAU,QAAQ,SAAS;AAC/E;;;;;AAMA,eAAe,WACb,OACA,SACA,QACA,UACA,WACA,UAA8B,CAAC,GACR;CACvB,MAAM,WAAW,SAAS,SAAS,UACjC,sBAAsB,OAAO,MAAM,SAAS,CAC9C,CAAC,CAAC;CACF,MAAM,WAAW,MAAM,eAAe;EACpC,QAAQ,MAAM;EACd,SAAS,cAAc,OAAO,SAAS,UAAU,SAAS;GAAE;GAAQ;EAAS,CAAC;EAC9E;EACA,cAAc,MAAM;EACpB,eAAe,UAAU,sBAAsB,OAAO,MAAM,SAAS,CAAC,CAAC;EACvE,iBAAiB,MAAM;CACzB,CAAC;CACD,OAAO,IAAI,QAAQ,wBAAwB;EACzC;EACA,UAAU,QAAQ,SAAS,SAAS,SAAS;EAC7C,QAAQ,SAAS,SAAS;EAC1B;EACA,aAAa,mBAAmB;CAClC,CAAC;CACD,MAAM,EAAE,gBAAgB;CACxB,OAAO;EACL,UAAU,gBAAgB,CAAC,GAAG,SAAS,GAAG,OAAO,SAAS,UAAU,OAAO,CAAC,GAAG,WAAW;EAC1F,UAAU,OAAO,SAAS,UAAU,OAAO;EAC3C;EACA,aAAa,SAAS;CACxB;AACF;;AAGA,SAAS,cACP,OACA,SACA,SACA,SACA,UACe;CACf,MAAM,EAAE,SAAS,UAAU;CAC3B,OAAO;EACL,WAAW,OAAO,YAAY;GAC5B,QAAQ,KAAK,MAAM,aAAa,OAAO,SAAS,OAAO,CAAC;GACxD,MAAM,WAAW;GACjB,YAAY,UAAU,SAAS,QAAQ,MAAM;EAC/C;EACA,SAAS,MAAM;EACf,OAAO,MAAM,SAAS,MAAM;EAC5B,GAAI,MAAM,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,MAAM,WAAW;EACzE,cAAc,MAAM;EACpB;EACA,WAAW,MAAM;EACjB,QAAQ,SAAS,OAAO;EACxB,aAAa,QAAQ,eAAe;EACpC,eAAe,QAAQ,iBAAiB;EACxC,UAAU,QAAQ,WAAW,SAAS,YAAY,gBAAgB,gBAAgB,OAAO,CAAC;EAC1F,QAAQ;CACV;AACF;;AAGA,MAAM,iBAAiB;;AAGvB,SAAS,YACP,UACA,SACA,UACM;CACN,IAAI,WAAW,mBAAmB,GAAG;CACrC,SAAS,OAAO,IAAI,QAAQ,YAAY,SAAS,GAAG,SAAS,SAAS,eAAe;EACnF;EACA;EACA,UAAU,SAAS;CACrB,CAAC;AACH;;;;;AAMA,eAAe,aACb,OACA,SACA,SAC2B;CAC3B,uBAAuB,QAAQ,UAAU;CACzC,MAAM,aAAa,MAAM,gBAAgB,MAAM,SAAS,MAAM,QAAQ,IAAI,QAAQ,UAAU;CAC5F,OAAO;EAAE,GAAG;EAAS;EAAS,YAAY;EAAkB,QAAQ;EAAM;CAAW;AACvF;AAEA,MAAM,mBAAmB,OAAO,MAAM,CAAC;;AAGvC,SAAS,gBACP,UACA,aACoB;CACpB,IAAI,aACF,KAAK,MAAM,WAAW,UACpB,QAAQ,gBAAgB;CAG5B,OAAO;AACT;;AAGA,SAAS,OACP,MACA,SACK;CACL,OAAO,KAAK,KAAK,QAAQ,OAAO,OAAO,CAAC,GAAG,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC9D"}
|
package/dist/capture/modes.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { ModeMap } from "./adapter.mjs";
|
|
4
|
-
//#region src/capture/modes.d.ts
|
|
5
4
|
/** Upper bound on captures (global viewports + modes) per story. */
|
|
6
5
|
declare const MAX_MODES_PER_STORY = 8;
|
|
7
6
|
/**
|
|
@@ -20,6 +19,5 @@ declare function parseModesAllowlist(raw: string | null | undefined): string[] |
|
|
|
20
19
|
* only accepts `[a-zA-Z0-9 _-]` and silently drops anything else).
|
|
21
20
|
*/
|
|
22
21
|
declare function buildGlobalsParam(globals: Record<string, unknown>): string;
|
|
23
|
-
//#endregion
|
|
24
22
|
export { MAX_MODES_PER_STORY, buildGlobalsParam, filterModes, modeSlug, parseModesAllowlist };
|
|
25
23
|
//# sourceMappingURL=modes.d.mts.map
|
package/dist/capture/modes.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/capture/modes.ts
|
|
5
5
|
/** Upper bound on captures (global viewports + modes) per story. */
|
|
6
6
|
const MAX_MODES_PER_STORY = 8;
|
|
7
7
|
const SLUG_STRIP = /[^a-z0-9]+/gu;
|
|
@@ -12,7 +12,7 @@ const HEX_COLOR = /^#(?<hex>[0-9a-fA-F]{3,8})$/u;
|
|
|
12
12
|
* whitespace/case edits. Throws for names with no usable characters.
|
|
13
13
|
*/
|
|
14
14
|
function modeSlug(name) {
|
|
15
|
-
const slug = name.toLowerCase().
|
|
15
|
+
const slug = name.toLowerCase().replaceAll(SLUG_STRIP, "-").replaceAll(SLUG_EDGES, "");
|
|
16
16
|
if (slug === "") throw new Error(`Invalid mode name: ${JSON.stringify(name)}`);
|
|
17
17
|
return slug;
|
|
18
18
|
}
|
|
@@ -101,7 +101,6 @@ function encodeValue(value) {
|
|
|
101
101
|
const hex = HEX_COLOR.exec(text)?.groups?.["hex"];
|
|
102
102
|
return hex ? `!hex(${hex})` : text;
|
|
103
103
|
}
|
|
104
|
-
//#endregion
|
|
105
104
|
export { MAX_MODES_PER_STORY, applyModes, buildGlobalsParam, filterModes, modeSlug, parseModesAllowlist };
|
|
106
105
|
|
|
107
106
|
//# sourceMappingURL=modes.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modes.mjs","names":[],"sources":["../../src/capture/modes.ts"],"sourcesContent":["/** Named capture modes: parameters → viewports carrying Storybook globals. */\nimport type { ModeDefinition, ModeMap, ModeViewportValue, Viewport } from \"./adapter.ts\";\n\n/** Upper bound on captures (global viewports + modes) per story. */\nexport const MAX_MODES_PER_STORY = 8;\n\n/** Resolves a Storybook viewport name to dimensions (story config, then builtins). */\nexport type ViewportLookup = (name: string) => Viewport | undefined;\n\nconst SLUG_STRIP = /[^a-z0-9]+/gu;\nconst SLUG_EDGES = /^-+|-+$/gu;\nconst HEX_COLOR = /^#(?<hex>[0-9a-fA-F]{3,8})$/u;\n\n/**\n * Baseline identity for a mode name: lower-case, URL/path-safe, stable under\n * whitespace/case edits. Throws for names with no usable characters.\n */\nexport function modeSlug(name: string): string {\n const slug = name.toLowerCase().
|
|
1
|
+
{"version":3,"file":"modes.mjs","names":[],"sources":["../../src/capture/modes.ts"],"sourcesContent":["/** Named capture modes: parameters → viewports carrying Storybook globals. */\nimport type { ModeDefinition, ModeMap, ModeViewportValue, Viewport } from \"./adapter.ts\";\n\n/** Upper bound on captures (global viewports + modes) per story. */\nexport const MAX_MODES_PER_STORY = 8;\n\n/** Resolves a Storybook viewport name to dimensions (story config, then builtins). */\nexport type ViewportLookup = (name: string) => Viewport | undefined;\n\nconst SLUG_STRIP = /[^a-z0-9]+/gu;\nconst SLUG_EDGES = /^-+|-+$/gu;\nconst HEX_COLOR = /^#(?<hex>[0-9a-fA-F]{3,8})$/u;\n\n/**\n * Baseline identity for a mode name: lower-case, URL/path-safe, stable under\n * whitespace/case edits. Throws for names with no usable characters.\n */\nexport function modeSlug(name: string): string {\n const slug = name.toLowerCase().replaceAll(SLUG_STRIP, \"-\").replaceAll(SLUG_EDGES, \"\");\n if (slug === \"\") {\n throw new Error(`Invalid mode name: ${JSON.stringify(name)}`);\n }\n return slug;\n}\n\n/**\n * Apply `modes` on top of a story's base viewports. A mode named like an\n * existing viewport replaces it; `disable: true` drops that entry (inherited\n * mode or viewport). Output order: base viewports first, then new modes.\n */\nexport function applyModes(\n modes: ModeMap | undefined,\n base: Viewport[],\n lookup: ViewportLookup,\n): Viewport[] {\n if (!modes) return [...base];\n const bySlug = new Map<string, Viewport>(\n base.map((viewport) => [modeSlug(viewport.name), viewport]),\n );\n const fallback = base[0] ?? { name: \"desktop\", width: 1280, height: 720 };\n for (const [name, definition] of Object.entries(modes)) {\n const slug = modeSlug(name);\n if (definition.disable === true) {\n bySlug.delete(slug);\n } else {\n bySlug.set(slug, modeViewport(slug, definition, fallback, lookup));\n }\n }\n return [...bySlug.values()];\n}\n\n/** Keep only allow-listed modes; `null`/empty allowlist keeps all. */\nexport function filterModes(\n modes: ModeMap | undefined,\n allowlist: readonly string[] | null,\n): ModeMap | undefined {\n if (!modes || !allowlist || allowlist.length === 0) return modes;\n const allowed = new Set(allowlist.map((name) => modeSlug(name)));\n return Object.fromEntries(\n Object.entries(modes).filter(\n ([name, definition]) => definition.disable === true || allowed.has(modeSlug(name)),\n ),\n );\n}\n\n/** Parse the project's JSON `modesAllowlist` column; `null` when unset/invalid. */\nexport function parseModesAllowlist(raw: string | null | undefined): string[] | null {\n if (!raw) return null;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isStringArray(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Encode globals for Storybook's `globals` URL arg (`a:b;c.d:e`). Nested\n * objects flatten to dot paths; booleans/null/undefined/hex colors use the\n * `!`-prefixed forms Storybook parses. Plain strings pass through (Storybook\n * only accepts `[a-zA-Z0-9 _-]` and silently drops anything else).\n */\nexport function buildGlobalsParam(globals: Record<string, unknown>): string {\n return flatten(globals, \"\")\n .map(([key, value]) => `${key}:${encodeValue(value)}`)\n .join(\";\");\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\nfunction modeViewport(\n slug: string,\n definition: ModeDefinition,\n fallback: Viewport,\n lookup: ViewportLookup,\n): Viewport {\n const { viewport, disable: _disable, ...globals } = definition;\n const size = resolveSize(viewport, fallback, lookup);\n return {\n name: slug,\n width: size.width,\n height: size.height,\n ...(Object.keys(globals).length > 0 ? { globals } : {}),\n };\n}\n\nfunction resolveSize(\n value: ModeViewportValue | undefined,\n fallback: Viewport,\n lookup: ViewportLookup,\n): { width: number; height: number } {\n if (value === undefined) return fallback;\n if (typeof value === \"number\") return { width: Math.round(value), height: fallback.height };\n if (typeof value === \"string\") return lookup(value) ?? fallback;\n return { width: Math.round(value.width), height: Math.round(value.height) };\n}\n\nfunction flatten(value: Record<string, unknown>, prefix: string): [string, unknown][] {\n return Object.entries(value).flatMap(([key, entry]): [string, unknown][] =>\n isPlainObject(entry) ? flatten(entry, `${prefix}${key}.`) : [[`${prefix}${key}`, entry]],\n );\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction encodeValue(value: unknown): string {\n if (value === true) return \"!true\";\n if (value === false) return \"!false\";\n if (value === null) return \"!null\";\n if (value === undefined) return \"!undefined\";\n const text =\n typeof value === \"string\" || typeof value === \"number\"\n ? String(value)\n : (JSON.stringify(value) ?? \"\");\n const hex = HEX_COLOR.exec(text)?.groups?.[\"hex\"];\n return hex ? `!hex(${hex})` : text;\n}\n"],"mappings":"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"}
|
|
@@ -14,7 +14,6 @@ import { CaptureProgress } from "./progress.mjs";
|
|
|
14
14
|
import { PipelineTables } from "./pipeline.mjs";
|
|
15
15
|
import { ExtractionLimits } from "./publish/limits.mjs";
|
|
16
16
|
import { Logger } from "pino";
|
|
17
|
-
//#region src/capture/orchestrator.d.ts
|
|
18
17
|
/** Table handles required by the orchestrator. */
|
|
19
18
|
type OrchestratorTables = BuildTables & ProjectTables & PipelineTables & CaptureAttemptTables & CaptureLogTables;
|
|
20
19
|
/** Inputs for running a capture job against a Storybook build. */
|
|
@@ -94,6 +93,5 @@ interface CaptureJobInput {
|
|
|
94
93
|
* @returns Story and blocking-failure counts for the attempt row.
|
|
95
94
|
*/
|
|
96
95
|
declare function executeCaptureJob(input: CaptureJobInput, options: CaptureJobOptions): Promise<CaptureSummary>;
|
|
97
|
-
//#endregion
|
|
98
96
|
export { CaptureAttemptRef, CaptureJobInput, CaptureJobOptions, CaptureSummary, OrchestratorTables, ShardJob, executeCaptureJob };
|
|
99
97
|
//# sourceMappingURL=orchestrator.d.mts.map
|
|
@@ -2,6 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { parseDesignUrl } from "../adapter/design.mjs";
|
|
5
|
+
import { parentContext, withSpan } from "../tracing.mjs";
|
|
5
6
|
import { assertSupportedBrowsers, resolveBrowsers } from "./browsers.mjs";
|
|
6
7
|
import { offeredTargets } from "./targets.mjs";
|
|
7
8
|
import { storybookUrlSource } from "./storybook-urls.mjs";
|
|
@@ -13,7 +14,6 @@ import { logInvalidDesigns } from "./design.mjs";
|
|
|
13
14
|
import { StorybookAdapter } from "./storybook.mjs";
|
|
14
15
|
import { runProjectTests } from "./tests.mjs";
|
|
15
16
|
import { BuildModel } from "../models/build.mjs";
|
|
16
|
-
import { parentContext, withSpan } from "../tracing.mjs";
|
|
17
17
|
import { roundedTimings, runWithTimings, timed } from "../utils/timing.mjs";
|
|
18
18
|
import { affectedPathsFor } from "./affected.mjs";
|
|
19
19
|
import { assertModeCaps, enrichStories } from "./enrich.mjs";
|
|
@@ -24,7 +24,7 @@ import { removeJobScratch } from "./scratch.mjs";
|
|
|
24
24
|
import { fanOutCapture } from "./shards/fan-out.mjs";
|
|
25
25
|
import { extractStorybookToScratch, persistStorybookStatics } from "./statics.mjs";
|
|
26
26
|
import { loadTarget } from "./target.mjs";
|
|
27
|
-
|
|
27
|
+
// src/capture/orchestrator.ts
|
|
28
28
|
/** Parser over the configured design providers, or undefined when there are none. */
|
|
29
29
|
function designParser(providers) {
|
|
30
30
|
if (!providers || providers.length === 0) return void 0;
|
|
@@ -225,7 +225,6 @@ async function runCapturePhases(input, options, builds, build, project) {
|
|
|
225
225
|
throw error;
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
//#endregion
|
|
229
228
|
export { executeCaptureJob };
|
|
230
229
|
|
|
231
230
|
//# sourceMappingURL=orchestrator.mjs.map
|
|
@@ -14,7 +14,6 @@ import { SnapshotTables } from "../models/snapshot.mjs";
|
|
|
14
14
|
import { InheritedStory } from "./affected.mjs";
|
|
15
15
|
import { CaptureProgress } from "./progress.mjs";
|
|
16
16
|
import { Logger } from "pino";
|
|
17
|
-
//#region src/capture/pipeline.d.ts
|
|
18
17
|
/**
|
|
19
18
|
* Persist a completed capture run: write screenshots, diff against the branch
|
|
20
19
|
* baseline, create snapshots, and finalize the build.
|
|
@@ -62,6 +61,5 @@ interface CaptureContext {
|
|
|
62
61
|
/** Server secret for decrypting webhook secrets at send time. */
|
|
63
62
|
secret?: SecretInput;
|
|
64
63
|
}
|
|
65
|
-
//#endregion
|
|
66
64
|
export { CaptureContext, PipelineTables, persistCapture };
|
|
67
65
|
//# sourceMappingURL=pipeline.d.mts.map
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
+
import { ReferenceGenerationConflict } from "../adapter/database.mjs";
|
|
4
5
|
import { DEFAULT_BROWSER } from "../adapters/capture-target.mjs";
|
|
5
6
|
import { emitAttemptLog } from "../models/capture-attempt.mjs";
|
|
6
7
|
import { BuildModel } from "../models/build.mjs";
|
|
7
8
|
import { diffPath } from "../paths.mjs";
|
|
8
9
|
import { storeScreenshot } from "../utils/screenshot-store.mjs";
|
|
10
|
+
import { BaselineWriteConflict } from "../models/baseline-conflict.mjs";
|
|
11
|
+
import { NO_BASELINE } from "../models/baseline-status.mjs";
|
|
9
12
|
import { BaselineModel } from "../models/baseline.mjs";
|
|
10
13
|
import { assertScreenshotBuffer } from "./guards.mjs";
|
|
11
|
-
import { NO_BASELINE } from "../models/baseline-status.mjs";
|
|
12
14
|
import { SnapshotModel } from "../models/snapshot.mjs";
|
|
13
15
|
import { mapLimit } from "../utils/map-limit.mjs";
|
|
16
|
+
import { freezeComparison, sourceSnapshotId } from "./comparison-reference.mjs";
|
|
14
17
|
import { diffStoredScreenshots, writeDiffOverlay } from "./diff-record.mjs";
|
|
15
18
|
import { buildFingerprint, changedFields, fingerprintHash, mergeFingerprint, parseFingerprint } from "./fingerprint.mjs";
|
|
16
19
|
/**
|
|
@@ -167,7 +170,17 @@ async function persistInheritedSnapshot(ctx, inherited) {
|
|
|
167
170
|
const browser = browserOf(inherited);
|
|
168
171
|
const infraHash = inherited.baseline.infraHash;
|
|
169
172
|
const snapshots = new SnapshotModel(ctx.db, ctx.tables);
|
|
173
|
+
const comparison = await freezeComparison(ctx.storage, ctx.project.id, ctx.build.id, inherited.baseline, {
|
|
174
|
+
defaultBranch: ctx.project.gitDefaultBranch,
|
|
175
|
+
mode: modeJsonOf(inherited.viewport),
|
|
176
|
+
target: browser,
|
|
177
|
+
width: inherited.viewport.width,
|
|
178
|
+
height: inherited.viewport.height,
|
|
179
|
+
captureFingerprint: inherited.baseline.fingerprint ?? null,
|
|
180
|
+
sourceSnapshotId: await sourceSnapshotId(ctx.db, inherited.baseline)
|
|
181
|
+
});
|
|
170
182
|
const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
|
|
183
|
+
...comparison,
|
|
171
184
|
browser,
|
|
172
185
|
storyId: inherited.story.id,
|
|
173
186
|
storyName: inherited.story.name,
|
|
@@ -181,6 +194,7 @@ async function persistInheritedSnapshot(ctx, inherited) {
|
|
|
181
194
|
infraHash,
|
|
182
195
|
mode: modeJsonOf(inherited.viewport),
|
|
183
196
|
inherited: true,
|
|
197
|
+
sourceBuildId: inherited.baseline.sourceBuildId ?? null,
|
|
184
198
|
baselineId: inherited.baseline.id,
|
|
185
199
|
baselineVersion: inherited.baseline.updatedAt
|
|
186
200
|
});
|
|
@@ -302,7 +316,16 @@ async function createWithBaseline(ctx, capture, viewport, screenshot, baseline)
|
|
|
302
316
|
screenshot,
|
|
303
317
|
baseline,
|
|
304
318
|
now,
|
|
305
|
-
change
|
|
319
|
+
change,
|
|
320
|
+
comparison: await freezeComparison(ctx.storage, ctx.project.id, ctx.build.id, baseline, {
|
|
321
|
+
defaultBranch: ctx.project.gitDefaultBranch,
|
|
322
|
+
mode: modeJsonOf(viewport),
|
|
323
|
+
target: browserOf(capture),
|
|
324
|
+
width: viewport.width,
|
|
325
|
+
height: viewport.height,
|
|
326
|
+
captureFingerprint: now.json,
|
|
327
|
+
sourceSnapshotId: await sourceSnapshotId(ctx.db, baseline)
|
|
328
|
+
})
|
|
306
329
|
};
|
|
307
330
|
if (change && ctx.build.isDefault) await persistMigration(input, change);
|
|
308
331
|
else await persistDiff(input);
|
|
@@ -330,7 +353,8 @@ function snapshotInput(input) {
|
|
|
330
353
|
infraHash: now.infraHash,
|
|
331
354
|
mode: modeJsonOf(viewport),
|
|
332
355
|
baselineId: baseline.id,
|
|
333
|
-
baselineVersion: baseline.updatedAt
|
|
356
|
+
baselineVersion: baseline.updatedAt,
|
|
357
|
+
...input.comparison
|
|
334
358
|
};
|
|
335
359
|
}
|
|
336
360
|
/**
|
|
@@ -363,7 +387,7 @@ async function persistMigration(input, change) {
|
|
|
363
387
|
/** Diff against the baseline; a feature-branch environment change rides along as `envChange`. */
|
|
364
388
|
async function persistDiff(input) {
|
|
365
389
|
const { ctx, capture, screenshot, baseline, now, change } = input;
|
|
366
|
-
const result = await diffStoredScreenshots(ctx.storage, ctx.project, baseline.screenshotPath, screenshot, capture.story.parameters?.diffThreshold);
|
|
390
|
+
const result = await diffStoredScreenshots(ctx.storage, ctx.project, baseline.screenshotPath === screenshot ? screenshot : input.comparison.comparisonImagePath, screenshot, capture.story.parameters?.diffThreshold);
|
|
367
391
|
const snapshots = new SnapshotModel(ctx.db, ctx.tables);
|
|
368
392
|
const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
|
|
369
393
|
...snapshotInput(input),
|
|
@@ -389,7 +413,15 @@ async function refreshDefaultBaseline(input, snapshotId, passed) {
|
|
|
389
413
|
const { ctx, capture, screenshot, baseline } = input;
|
|
390
414
|
const target = baselineFingerprintOf(input);
|
|
391
415
|
if (!passed) await upsertDefaultBaseline(ctx, snapshotId, capture, screenshot, target);
|
|
392
|
-
else if (!baseline.fingerprint || baseline.infraHash !== target.infraHash)
|
|
416
|
+
else if (!baseline.fingerprint || baseline.infraHash !== target.infraHash) try {
|
|
417
|
+
await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).backfillFingerprint(baseline, target.infraHash, target.json);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
if (!(error instanceof BaselineWriteConflict || error instanceof ReferenceGenerationConflict)) throw error;
|
|
420
|
+
ctx.logger?.debug({
|
|
421
|
+
err: error,
|
|
422
|
+
baselineId: baseline.id
|
|
423
|
+
}, "skipped baseline fingerprint backfill after concurrent write");
|
|
424
|
+
}
|
|
393
425
|
}
|
|
394
426
|
/** Default-branch builds are authoritative: the captured screenshot becomes the baseline. */
|
|
395
427
|
async function upsertDefaultBaseline(ctx, snapshotId, capture, screenshot, now) {
|
|
@@ -434,7 +466,6 @@ async function finalizeBuild(ctx, storyIds, failedStoryIds, flakyFailedStoryIds
|
|
|
434
466
|
await builds.setStatus(ctx.build.id, status);
|
|
435
467
|
if (ctx.build.isDefault && hasCaptures) await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).removeOrphans(ctx.project.id, new Set(storyIds));
|
|
436
468
|
}
|
|
437
|
-
//#endregion
|
|
438
469
|
export { diffStatus, finalizeBuild, freshBrowsersOf, persistCapture, persistInherited, persistRendered };
|
|
439
470
|
|
|
440
471
|
//# sourceMappingURL=pipeline.mjs.map
|