@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/capture/isolate.ts
|
|
5
|
+
/**
|
|
6
|
+
* Render one engine's stories, narrowing a crash down to the story that caused it.
|
|
7
|
+
*
|
|
8
|
+
* A render call that throws takes its whole slice with it, so the stories it had not reached yet are
|
|
9
|
+
* re-rendered as two halves, and so on until a single story is left holding the crash. That story
|
|
10
|
+
* gets one pass on its own (a fresh browser) before it is blamed: only a story that crashes alone is
|
|
11
|
+
* failed, and its shard-mates still get captured. A crash that never yields a single screenshot is
|
|
12
|
+
* treated as the environment being broken rather than any one story's fault, and rethrown for the
|
|
13
|
+
* caller to fail the whole slice.
|
|
14
|
+
*
|
|
15
|
+
* A story counts as done only once every one of its viewports has a capture or a failure, so a
|
|
16
|
+
* crash partway through a story's viewports renders it again rather than losing the rest.
|
|
17
|
+
*/
|
|
18
|
+
async function renderIsolating(stories, pass, options = {}) {
|
|
19
|
+
const { note } = options;
|
|
20
|
+
const state = newState(stories.length, options.viewportsOf);
|
|
21
|
+
const queue = stories.length > 0 ? [[...stories]] : [];
|
|
22
|
+
while (queue.length > 0) await step(state, queue, pass, note);
|
|
23
|
+
if (state.captures.length === 0 && state.crash !== void 0) throw state.crash;
|
|
24
|
+
return result(state, stories.length);
|
|
25
|
+
}
|
|
26
|
+
/** Render the next slice on the queue and deal with however it ended. */
|
|
27
|
+
async function step(state, queue, pass, note) {
|
|
28
|
+
const slice = queue.shift() ?? [];
|
|
29
|
+
const outcome = await pass(slice);
|
|
30
|
+
absorb(state, outcome);
|
|
31
|
+
if (outcome.error !== void 0) split(state, slice, queue, note);
|
|
32
|
+
if (state.captures.length === 0 && state.passes >= state.blindBudget) {
|
|
33
|
+
queue.length = 0;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (state.passes >= MAX_PASSES) failRest(state, queue, note);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Render passes one engine may spend narrowing crashes down before the stories still unrendered are
|
|
40
|
+
* failed without another try. Localizing one crash costs about log2(stories) passes, so this leaves
|
|
41
|
+
* room for a handful of bad stories without letting a shard of hundreds launch a browser per story.
|
|
42
|
+
*/
|
|
43
|
+
const MAX_PASSES = 48;
|
|
44
|
+
/** A crash covers every viewport of the story it is blamed on. */
|
|
45
|
+
const ALL_VIEWPORTS = "*";
|
|
46
|
+
function newState(total, viewportsOf) {
|
|
47
|
+
return {
|
|
48
|
+
captures: [],
|
|
49
|
+
failures: [],
|
|
50
|
+
poisoned: [],
|
|
51
|
+
captured: /* @__PURE__ */ new Set(),
|
|
52
|
+
settled: /* @__PURE__ */ new Set(),
|
|
53
|
+
viewportsOf,
|
|
54
|
+
passes: 0,
|
|
55
|
+
blindBudget: Math.ceil(Math.log2(total + 1)) + 2
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function result(state, renderCount) {
|
|
59
|
+
return {
|
|
60
|
+
captures: state.captures,
|
|
61
|
+
failures: state.failures,
|
|
62
|
+
renderCount,
|
|
63
|
+
poisoned: state.poisoned
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Take in what a pass produced, successful or not. A story rendered again after a crash yields its
|
|
68
|
+
* already-settled viewports a second time; only the first of each is kept.
|
|
69
|
+
*/
|
|
70
|
+
function absorb(state, outcome) {
|
|
71
|
+
state.passes += 1;
|
|
72
|
+
for (const capture of outcome.captures) if (settle(state, capture.story.id, capture.viewportName)) state.captures.push(capture);
|
|
73
|
+
for (const failure of outcome.failures) if (settle(state, failure.storyId, failure.viewportName)) state.failures.push(failure);
|
|
74
|
+
if (outcome.error !== void 0) state.crash ??= outcome.error;
|
|
75
|
+
}
|
|
76
|
+
/** Record one `(story, viewport)` outcome; false when it was already in. */
|
|
77
|
+
function settle(state, storyId, viewportName) {
|
|
78
|
+
const key = `${storyId}\u0000${viewportName}`;
|
|
79
|
+
if (state.settled.has(key) || state.captured.has(storyId)) return false;
|
|
80
|
+
state.settled.add(key);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/** Whether every viewport of the story has an outcome (or the story was failed as a whole). */
|
|
84
|
+
function isDone(state, story) {
|
|
85
|
+
if (state.captured.has(story.id)) return true;
|
|
86
|
+
const names = state.viewportsOf?.(story);
|
|
87
|
+
const done = names ? names.every((name) => state.settled.has(`${story.id}\u0000${name}`)) : [...state.settled].some((key) => key.startsWith(`${story.id}\u0000`));
|
|
88
|
+
if (done) state.captured.add(story.id);
|
|
89
|
+
return done;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Narrow a crashed slice down: queue the halves of what it left unfinished for another try. The
|
|
93
|
+
* last story left gets a pass on its own first, and is blamed only if it crashes there too.
|
|
94
|
+
*/
|
|
95
|
+
function split(state, slice, queue, note) {
|
|
96
|
+
const rest = slice.filter((story) => !isDone(state, story));
|
|
97
|
+
const [first] = rest;
|
|
98
|
+
if (first === void 0) return;
|
|
99
|
+
if (rest.length === 1 && slice.length === 1) {
|
|
100
|
+
blame(state, first, note);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const half = Math.ceil(rest.length / 2);
|
|
104
|
+
queue.push(...[rest.slice(0, half), rest.slice(half)].filter((part) => part.length > 0));
|
|
105
|
+
note?.("isolating", { stories: rest.length });
|
|
106
|
+
}
|
|
107
|
+
/** Fail one story for the crash and stop rendering it. */
|
|
108
|
+
function blame(state, story, note) {
|
|
109
|
+
state.captured.add(story.id);
|
|
110
|
+
state.poisoned.push(story.id);
|
|
111
|
+
state.failures.push({
|
|
112
|
+
storyId: story.id,
|
|
113
|
+
viewportName: ALL_VIEWPORTS,
|
|
114
|
+
error: crashText(state.crash)
|
|
115
|
+
});
|
|
116
|
+
note?.("poisoned", { storyId: story.id });
|
|
117
|
+
}
|
|
118
|
+
/** Out of passes: fail what is left unrendered rather than keep launching browsers. */
|
|
119
|
+
function failRest(state, queue, note) {
|
|
120
|
+
const rest = queue.flat().filter((story) => !isDone(state, story)).map((story) => story.id);
|
|
121
|
+
for (const storyId of new Set(rest)) {
|
|
122
|
+
state.captured.add(storyId);
|
|
123
|
+
state.failures.push({
|
|
124
|
+
storyId,
|
|
125
|
+
viewportName: ALL_VIEWPORTS,
|
|
126
|
+
error: `not rendered: ${crashText(state.crash)}`
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
queue.length = 0;
|
|
130
|
+
note?.("abandoned", {
|
|
131
|
+
stories: rest.length,
|
|
132
|
+
passes: state.passes
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function crashText(crash) {
|
|
136
|
+
return `browser crashed: ${crash instanceof Error ? crash.message : String(crash)}`;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { renderIsolating };
|
|
140
|
+
|
|
141
|
+
//# sourceMappingURL=isolate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isolate.mjs","names":[],"sources":["../../src/capture/isolate.ts"],"sourcesContent":["/** Localizing a browser crash to the story that caused it, instead of failing every story with it. */\nimport type { RenderFailure, RenderedSnapshot } from \"../adapters/capture-runner.ts\";\nimport type { StoryEntry } from \"./adapter.ts\";\n\n/**\n * Render one engine's stories, narrowing a crash down to the story that caused it.\n *\n * A render call that throws takes its whole slice with it, so the stories it had not reached yet are\n * re-rendered as two halves, and so on until a single story is left holding the crash. That story\n * gets one pass on its own (a fresh browser) before it is blamed: only a story that crashes alone is\n * failed, and its shard-mates still get captured. A crash that never yields a single screenshot is\n * treated as the environment being broken rather than any one story's fault, and rethrown for the\n * caller to fail the whole slice.\n *\n * A story counts as done only once every one of its viewports has a capture or a failure, so a\n * crash partway through a story's viewports renders it again rather than losing the rest.\n */\nexport async function renderIsolating(\n stories: readonly StoryEntry[],\n pass: (slice: StoryEntry[]) => Promise<PassOutcome>,\n options: IsolationOptions = {},\n): Promise<IsolatedResult> {\n const { note } = options;\n const state = newState(stories.length, options.viewportsOf);\n const queue: StoryEntry[][] = stories.length > 0 ? [[...stories]] : [];\n while (queue.length > 0) {\n // oxlint-disable-next-line no-await-in-loop -- one browser at a time, like the render loop\n await step(state, queue, pass, note);\n }\n if (state.captures.length === 0 && state.crash !== undefined) {\n throw state.crash;\n }\n return result(state, stories.length);\n}\n\n/** Render the next slice on the queue and deal with however it ended. */\nasync function step(\n state: IsolationState,\n queue: StoryEntry[][],\n pass: (slice: StoryEntry[]) => Promise<PassOutcome>,\n note?: IsolationNote,\n): Promise<void> {\n const slice = queue.shift() ?? [];\n const outcome = await pass(slice);\n absorb(state, outcome);\n if (outcome.error !== undefined) {\n split(state, slice, queue, note);\n }\n if (state.captures.length === 0 && state.passes >= state.blindBudget) {\n // Not one screenshot yet: read that as the environment being broken, not a story's doing.\n queue.length = 0;\n return;\n }\n if (state.passes >= MAX_PASSES) {\n failRest(state, queue, note);\n }\n}\n\n/** What one render pass produced, whether it ran to the end or crashed partway. */\nexport interface PassOutcome {\n /** Screenshots the pass took; on a crash, the ones it managed before dying. */\n captures: RenderedSnapshot[];\n failures: RenderFailure[];\n /** Set when the render call threw outright, instead of reporting per-story failures. */\n error?: unknown;\n}\n\n/** Everything the isolating loop produced for one engine. */\nexport interface IsolatedResult {\n captures: RenderedSnapshot[];\n failures: RenderFailure[];\n /** Stories this engine was asked to render (retries are not counted twice). */\n renderCount: number;\n /** Stories blamed for a crash and failed on their own. */\n poisoned: string[];\n}\n\n/** How {@link renderIsolating} reports and what it knows about the stories. */\nexport interface IsolationOptions {\n note?: IsolationNote | undefined;\n /**\n * Names of the viewports a story is rendered at (as resolved for the pass). Without it, a story\n * counts as done after its first capture or failure.\n */\n viewportsOf?: ((story: StoryEntry) => readonly string[]) | undefined;\n}\n\n/** Progress lines for the attempt log; the caller decides where they go. */\nexport type IsolationNote = (\n event: \"isolating\" | \"poisoned\" | \"abandoned\",\n fields: Record<string, unknown>,\n) => void;\n\n/**\n * Render passes one engine may spend narrowing crashes down before the stories still unrendered are\n * failed without another try. Localizing one crash costs about log2(stories) passes, so this leaves\n * room for a handful of bad stories without letting a shard of hundreds launch a browser per story.\n */\nconst MAX_PASSES = 48;\n\n/** A crash covers every viewport of the story it is blamed on. */\nconst ALL_VIEWPORTS = \"*\";\n\ninterface IsolationState {\n captures: RenderedSnapshot[];\n failures: RenderFailure[];\n poisoned: string[];\n /** Stories with every viewport captured or failed, or blamed as a whole. */\n captured: Set<string>;\n /** `(story, viewport)` pairs captured or failed so far. */\n settled: Set<string>;\n viewportsOf: ((story: StoryEntry) => readonly string[]) | undefined;\n passes: number;\n /**\n * Crashing passes to allow before a slice that has produced nothing at all is given up on: enough\n * to halve the slice down to a single story and still see a healthy half render.\n */\n blindBudget: number;\n /** First crash seen, rethrown when not one story turned out to be at fault. */\n crash?: unknown;\n}\n\nfunction newState(total: number, viewportsOf: IsolationState[\"viewportsOf\"]): IsolationState {\n return {\n captures: [],\n failures: [],\n poisoned: [],\n captured: new Set(),\n settled: new Set(),\n viewportsOf,\n passes: 0,\n blindBudget: Math.ceil(Math.log2(total + 1)) + 2,\n };\n}\n\nfunction result(state: IsolationState, renderCount: number): IsolatedResult {\n return {\n captures: state.captures,\n failures: state.failures,\n renderCount,\n poisoned: state.poisoned,\n };\n}\n\n/**\n * Take in what a pass produced, successful or not. A story rendered again after a crash yields its\n * already-settled viewports a second time; only the first of each is kept.\n */\nfunction absorb(state: IsolationState, outcome: PassOutcome): void {\n state.passes += 1;\n for (const capture of outcome.captures) {\n if (settle(state, capture.story.id, capture.viewportName)) {\n state.captures.push(capture);\n }\n }\n for (const failure of outcome.failures) {\n if (settle(state, failure.storyId, failure.viewportName)) {\n state.failures.push(failure);\n }\n }\n if (outcome.error !== undefined) {\n state.crash ??= outcome.error;\n }\n}\n\n/** Record one `(story, viewport)` outcome; false when it was already in. */\nfunction settle(state: IsolationState, storyId: string, viewportName: string): boolean {\n const key = `${storyId}\\u0000${viewportName}`;\n if (state.settled.has(key) || state.captured.has(storyId)) {\n return false;\n }\n state.settled.add(key);\n return true;\n}\n\n/** Whether every viewport of the story has an outcome (or the story was failed as a whole). */\nfunction isDone(state: IsolationState, story: StoryEntry): boolean {\n if (state.captured.has(story.id)) {\n return true;\n }\n const names = state.viewportsOf?.(story);\n const done = names\n ? names.every((name) => state.settled.has(`${story.id}\\u0000${name}`))\n : [...state.settled].some((key) => key.startsWith(`${story.id}\\u0000`));\n if (done) {\n state.captured.add(story.id);\n }\n return done;\n}\n\n/**\n * Narrow a crashed slice down: queue the halves of what it left unfinished for another try. The\n * last story left gets a pass on its own first, and is blamed only if it crashes there too.\n */\nfunction split(\n state: IsolationState,\n slice: StoryEntry[],\n queue: StoryEntry[][],\n note?: IsolationNote,\n): void {\n const rest = slice.filter((story) => !isDone(state, story));\n const [first] = rest;\n if (first === undefined) {\n return;\n }\n if (rest.length === 1 && slice.length === 1) {\n blame(state, first, note);\n return;\n }\n const half = Math.ceil(rest.length / 2);\n // Breadth first: the healthy half renders early, which is what tells a bad story apart from a\n // broken browser (see `blindBudget`).\n queue.push(...[rest.slice(0, half), rest.slice(half)].filter((part) => part.length > 0));\n note?.(\"isolating\", { stories: rest.length });\n}\n\n/** Fail one story for the crash and stop rendering it. */\nfunction blame(state: IsolationState, story: StoryEntry, note?: IsolationNote): void {\n state.captured.add(story.id);\n state.poisoned.push(story.id);\n state.failures.push({\n storyId: story.id,\n viewportName: ALL_VIEWPORTS,\n error: crashText(state.crash),\n });\n note?.(\"poisoned\", { storyId: story.id });\n}\n\n/** Out of passes: fail what is left unrendered rather than keep launching browsers. */\nfunction failRest(state: IsolationState, queue: StoryEntry[][], note?: IsolationNote): void {\n const rest = queue\n .flat()\n .filter((story) => !isDone(state, story))\n .map((story) => story.id);\n for (const storyId of new Set(rest)) {\n state.captured.add(storyId);\n state.failures.push({\n storyId,\n viewportName: ALL_VIEWPORTS,\n error: `not rendered: ${crashText(state.crash)}`,\n });\n }\n queue.length = 0;\n note?.(\"abandoned\", { stories: rest.length, passes: state.passes });\n}\n\nfunction crashText(crash: unknown): string {\n const message = crash instanceof Error ? crash.message : String(crash);\n return `browser crashed: ${message}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,eAAsB,gBACpB,SACA,MACA,UAA4B,CAAC,GACJ;CACzB,MAAM,EAAE,SAAS;CACjB,MAAM,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,WAAW;CAC1D,MAAM,QAAwB,QAAQ,SAAS,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CACrE,OAAO,MAAM,SAAS,GAEpB,MAAM,KAAK,OAAO,OAAO,MAAM,IAAI;CAErC,IAAI,MAAM,SAAS,WAAW,KAAK,MAAM,UAAU,KAAA,GACjD,MAAM,MAAM;CAEd,OAAO,OAAO,OAAO,QAAQ,MAAM;AACrC;;AAGA,eAAe,KACb,OACA,OACA,MACA,MACe;CACf,MAAM,QAAQ,MAAM,MAAM,KAAK,CAAC;CAChC,MAAM,UAAU,MAAM,KAAK,KAAK;CAChC,OAAO,OAAO,OAAO;CACrB,IAAI,QAAQ,UAAU,KAAA,GACpB,MAAM,OAAO,OAAO,OAAO,IAAI;CAEjC,IAAI,MAAM,SAAS,WAAW,KAAK,MAAM,UAAU,MAAM,aAAa;EAEpE,MAAM,SAAS;EACf;CACF;CACA,IAAI,MAAM,UAAU,YAClB,SAAS,OAAO,OAAO,IAAI;AAE/B;;;;;;AA0CA,MAAM,aAAa;;AAGnB,MAAM,gBAAgB;AAqBtB,SAAS,SAAS,OAAe,aAA4D;CAC3F,OAAO;EACL,UAAU,CAAC;EACX,UAAU,CAAC;EACX,UAAU,CAAC;EACX,0BAAU,IAAI,IAAI;EAClB,yBAAS,IAAI,IAAI;EACjB;EACA,QAAQ;EACR,aAAa,KAAK,KAAK,KAAK,KAAK,QAAQ,CAAC,CAAC,IAAI;CACjD;AACF;AAEA,SAAS,OAAO,OAAuB,aAAqC;CAC1E,OAAO;EACL,UAAU,MAAM;EAChB,UAAU,MAAM;EAChB;EACA,UAAU,MAAM;CAClB;AACF;;;;;AAMA,SAAS,OAAO,OAAuB,SAA4B;CACjE,MAAM,UAAU;CAChB,KAAK,MAAM,WAAW,QAAQ,UAC5B,IAAI,OAAO,OAAO,QAAQ,MAAM,IAAI,QAAQ,YAAY,GACtD,MAAM,SAAS,KAAK,OAAO;CAG/B,KAAK,MAAM,WAAW,QAAQ,UAC5B,IAAI,OAAO,OAAO,QAAQ,SAAS,QAAQ,YAAY,GACrD,MAAM,SAAS,KAAK,OAAO;CAG/B,IAAI,QAAQ,UAAU,KAAA,GACpB,MAAM,UAAU,QAAQ;AAE5B;;AAGA,SAAS,OAAO,OAAuB,SAAiB,cAA+B;CACrF,MAAM,MAAM,GAAG,QAAQ,QAAQ;CAC/B,IAAI,MAAM,QAAQ,IAAI,GAAG,KAAK,MAAM,SAAS,IAAI,OAAO,GACtD,OAAO;CAET,MAAM,QAAQ,IAAI,GAAG;CACrB,OAAO;AACT;;AAGA,SAAS,OAAO,OAAuB,OAA4B;CACjE,IAAI,MAAM,SAAS,IAAI,MAAM,EAAE,GAC7B,OAAO;CAET,MAAM,QAAQ,MAAM,cAAc,KAAK;CACvC,MAAM,OAAO,QACT,MAAM,OAAO,SAAS,MAAM,QAAQ,IAAI,GAAG,MAAM,GAAG,QAAQ,MAAM,CAAC,IACnE,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,MAAM,QAAQ,IAAI,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;CACxE,IAAI,MACF,MAAM,SAAS,IAAI,MAAM,EAAE;CAE7B,OAAO;AACT;;;;;AAMA,SAAS,MACP,OACA,OACA,OACA,MACM;CACN,MAAM,OAAO,MAAM,QAAQ,UAAU,CAAC,OAAO,OAAO,KAAK,CAAC;CAC1D,MAAM,CAAC,SAAS;CAChB,IAAI,UAAU,KAAA,GACZ;CAEF,IAAI,KAAK,WAAW,KAAK,MAAM,WAAW,GAAG;EAC3C,MAAM,OAAO,OAAO,IAAI;EACxB;CACF;CACA,MAAM,OAAO,KAAK,KAAK,KAAK,SAAS,CAAC;CAGtC,MAAM,KAAK,GAAG,CAAC,KAAK,MAAM,GAAG,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,SAAS,KAAK,SAAS,CAAC,CAAC;CACvF,OAAO,aAAa,EAAE,SAAS,KAAK,OAAO,CAAC;AAC9C;;AAGA,SAAS,MAAM,OAAuB,OAAmB,MAA4B;CACnF,MAAM,SAAS,IAAI,MAAM,EAAE;CAC3B,MAAM,SAAS,KAAK,MAAM,EAAE;CAC5B,MAAM,SAAS,KAAK;EAClB,SAAS,MAAM;EACf,cAAc;EACd,OAAO,UAAU,MAAM,KAAK;CAC9B,CAAC;CACD,OAAO,YAAY,EAAE,SAAS,MAAM,GAAG,CAAC;AAC1C;;AAGA,SAAS,SAAS,OAAuB,OAAuB,MAA4B;CAC1F,MAAM,OAAO,MACV,KAAK,CAAC,CACN,QAAQ,UAAU,CAAC,OAAO,OAAO,KAAK,CAAC,CAAC,CACxC,KAAK,UAAU,MAAM,EAAE;CAC1B,KAAK,MAAM,WAAW,IAAI,IAAI,IAAI,GAAG;EACnC,MAAM,SAAS,IAAI,OAAO;EAC1B,MAAM,SAAS,KAAK;GAClB;GACA,cAAc;GACd,OAAO,iBAAiB,UAAU,MAAM,KAAK;EAC/C,CAAC;CACH;CACA,MAAM,SAAS;CACf,OAAO,aAAa;EAAE,SAAS,KAAK;EAAQ,QAAQ,MAAM;CAAO,CAAC;AACpE;AAEA,SAAS,UAAU,OAAwB;CAEzC,OAAO,oBADS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEvE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/capture/job-lease.d.ts
|
|
4
|
+
/** Progress lease for in-process capture jobs: the same expiry idea as a remote queue's visibility timeout. */
|
|
5
|
+
/** What a running job calls to prove it is still making progress. */
|
|
6
|
+
interface JobLease {
|
|
7
|
+
/** Record progress; pushes the expiry out by a full lease. */
|
|
8
|
+
touch(): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A lease renewed by progress rather than a timer.
|
|
12
|
+
*
|
|
13
|
+
* A timer heartbeat inside the same process would keep renewing even when the capture is hung, so
|
|
14
|
+
* liveness is reported by the job itself (every attempt-log line, i.e. each stage and render
|
|
15
|
+
* progress). If nothing is touched for `leaseMs`, {@link expired} resolves.
|
|
16
|
+
*/
|
|
17
|
+
declare class ProgressLease implements JobLease {
|
|
18
|
+
#private;
|
|
19
|
+
/** Resolves once, when no progress was seen for a full lease. Never resolves when disabled or stopped. */
|
|
20
|
+
readonly expired: Promise<true>;
|
|
21
|
+
constructor(leaseMs: number);
|
|
22
|
+
touch(): void;
|
|
23
|
+
/** Stop watching; idempotent. */
|
|
24
|
+
stop(): void;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { JobLease, ProgressLease };
|
|
28
|
+
//# sourceMappingURL=job-lease.d.mts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/capture/job-lease.ts
|
|
5
|
+
/**
|
|
6
|
+
* A lease renewed by progress rather than a timer.
|
|
7
|
+
*
|
|
8
|
+
* A timer heartbeat inside the same process would keep renewing even when the capture is hung, so
|
|
9
|
+
* liveness is reported by the job itself (every attempt-log line, i.e. each stage and render
|
|
10
|
+
* progress). If nothing is touched for `leaseMs`, {@link expired} resolves.
|
|
11
|
+
*/
|
|
12
|
+
var ProgressLease = class {
|
|
13
|
+
/** Resolves once, when no progress was seen for a full lease. Never resolves when disabled or stopped. */
|
|
14
|
+
expired;
|
|
15
|
+
#lastTouch = Date.now();
|
|
16
|
+
#timer;
|
|
17
|
+
#stopped = false;
|
|
18
|
+
#expire;
|
|
19
|
+
#leaseMs;
|
|
20
|
+
constructor(leaseMs) {
|
|
21
|
+
this.#leaseMs = leaseMs;
|
|
22
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
23
|
+
this.expired = promise;
|
|
24
|
+
this.#expire = () => {
|
|
25
|
+
resolve(true);
|
|
26
|
+
};
|
|
27
|
+
if (leaseMs > 0) this.#arm(leaseMs);
|
|
28
|
+
}
|
|
29
|
+
touch() {
|
|
30
|
+
this.#lastTouch = Date.now();
|
|
31
|
+
}
|
|
32
|
+
/** Stop watching; idempotent. */
|
|
33
|
+
stop() {
|
|
34
|
+
this.#stopped = true;
|
|
35
|
+
clearTimeout(this.#timer);
|
|
36
|
+
}
|
|
37
|
+
#arm(delayMs) {
|
|
38
|
+
this.#timer = setTimeout(() => {
|
|
39
|
+
this.#check();
|
|
40
|
+
}, Math.max(1, delayMs));
|
|
41
|
+
this.#timer.unref();
|
|
42
|
+
}
|
|
43
|
+
#check() {
|
|
44
|
+
if (this.#stopped) return;
|
|
45
|
+
const idleMs = Date.now() - this.#lastTouch;
|
|
46
|
+
if (idleMs >= this.#leaseMs) {
|
|
47
|
+
this.#stopped = true;
|
|
48
|
+
this.#expire();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.#arm(this.#leaseMs - idleMs);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { ProgressLease };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=job-lease.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-lease.mjs","names":[],"sources":["../../src/capture/job-lease.ts"],"sourcesContent":["/** Progress lease for in-process capture jobs: the same expiry idea as a remote queue's visibility timeout. */\n\n/** What a running job calls to prove it is still making progress. */\nexport interface JobLease {\n /** Record progress; pushes the expiry out by a full lease. */\n touch(): void;\n}\n\n/**\n * A lease renewed by progress rather than a timer.\n *\n * A timer heartbeat inside the same process would keep renewing even when the capture is hung, so\n * liveness is reported by the job itself (every attempt-log line, i.e. each stage and render\n * progress). If nothing is touched for `leaseMs`, {@link expired} resolves.\n */\nexport class ProgressLease implements JobLease {\n /** Resolves once, when no progress was seen for a full lease. Never resolves when disabled or stopped. */\n readonly expired: Promise<true>;\n #lastTouch = Date.now();\n #timer: ReturnType<typeof setTimeout> | undefined;\n #stopped = false;\n readonly #expire: () => void;\n\n readonly #leaseMs: number;\n\n constructor(leaseMs: number) {\n this.#leaseMs = leaseMs;\n const { promise, resolve } = Promise.withResolvers<true>();\n this.expired = promise;\n this.#expire = (): void => {\n resolve(true);\n };\n if (leaseMs > 0) {\n this.#arm(leaseMs);\n }\n }\n\n touch(): void {\n this.#lastTouch = Date.now();\n }\n\n /** Stop watching; idempotent. */\n stop(): void {\n this.#stopped = true;\n clearTimeout(this.#timer);\n }\n\n #arm(delayMs: number): void {\n this.#timer = setTimeout(\n () => {\n this.#check();\n },\n Math.max(1, delayMs),\n );\n this.#timer.unref();\n }\n\n #check(): void {\n if (this.#stopped) {\n return;\n }\n const idleMs = Date.now() - this.#lastTouch;\n if (idleMs >= this.#leaseMs) {\n this.#stopped = true;\n this.#expire();\n return;\n }\n this.#arm(this.#leaseMs - idleMs);\n }\n}\n"],"mappings":";;;;;;;;;;;AAeA,IAAa,gBAAb,MAA+C;;CAE7C;CACA,aAAa,KAAK,IAAI;CACtB;CACA,WAAW;CACX;CAEA;CAEA,YAAY,SAAiB;EAC3B,KAAK,WAAW;EAChB,MAAM,EAAE,SAAS,YAAY,QAAQ,cAAoB;EACzD,KAAK,UAAU;EACf,KAAK,gBAAsB;GACzB,QAAQ,IAAI;EACd;EACA,IAAI,UAAU,GACZ,KAAK,KAAK,OAAO;CAErB;CAEA,QAAc;EACZ,KAAK,aAAa,KAAK,IAAI;CAC7B;;CAGA,OAAa;EACX,KAAK,WAAW;EAChB,aAAa,KAAK,MAAM;CAC1B;CAEA,KAAK,SAAuB;EAC1B,KAAK,SAAS,iBACN;GACJ,KAAK,OAAO;EACd,GACA,KAAK,IAAI,GAAG,OAAO,CACrB;EACA,KAAK,OAAO,MAAM;CACpB;CAEA,SAAe;EACb,IAAI,KAAK,UACP;EAEF,MAAM,SAAS,KAAK,IAAI,IAAI,KAAK;EACjC,IAAI,UAAU,KAAK,UAAU;GAC3B,KAAK,WAAW;GAChB,KAAK,QAAQ;GACb;EACF;EACA,KAAK,KAAK,KAAK,WAAW,MAAM;CAClC;AACF"}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { TargetLaunchError } from "../adapters/capture-target.mjs";
|
|
5
|
+
import { primaryTargetOf, resolveBrowsers } from "./browsers.mjs";
|
|
6
|
+
import { isFlakyStory } from "./adapter.mjs";
|
|
7
|
+
import { resolveStoryViewports } from "./storybook.mjs";
|
|
8
|
+
import { storeScreenshot } from "../utils/screenshot-store.mjs";
|
|
9
|
+
import { partitionAffectedStories } from "./affected.mjs";
|
|
10
|
+
import { mirrorAttemptLog } from "./attempt-logger.mjs";
|
|
11
|
+
import { assertScreenshotBuffer } from "./guards.mjs";
|
|
12
|
+
import { renderIsolating } from "./isolate.mjs";
|
|
13
|
+
import { renderInSlices } from "./token-chunks.mjs";
|
|
14
|
+
import { currentImageEngine } from "@storyshelf/image";
|
|
15
|
+
//#region src/capture/matrix.ts
|
|
16
|
+
/** The browser loop: one partition + render pass per engine, sequentially. */
|
|
17
|
+
/**
|
|
18
|
+
* Capture every story on every engine, one engine at a time (browsers are
|
|
19
|
+
* heavy; parallel launches would multiply memory). Each engine resolves its
|
|
20
|
+
* own baselines, so affected capture inherits per engine.
|
|
21
|
+
*/
|
|
22
|
+
async function captureAcrossBrowsers(input) {
|
|
23
|
+
const result = {
|
|
24
|
+
captures: [],
|
|
25
|
+
failures: [],
|
|
26
|
+
inherited: [],
|
|
27
|
+
renderCount: 0
|
|
28
|
+
};
|
|
29
|
+
for (const browser of input.browsers) {
|
|
30
|
+
const pass = await captureOnBrowser(input, browser);
|
|
31
|
+
result.captures.push(...pass.captures);
|
|
32
|
+
result.failures.push(...pass.failures);
|
|
33
|
+
result.inherited.push(...pass.inherited);
|
|
34
|
+
result.renderCount += pass.renderCount;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Affected-capture partitioning for every engine, without rendering: the plan stage of a sharded
|
|
40
|
+
* capture decides what to render (and how to slice it) before any browser starts.
|
|
41
|
+
*/
|
|
42
|
+
async function partitionBrowsers(input) {
|
|
43
|
+
const { project, build } = input;
|
|
44
|
+
const work = [];
|
|
45
|
+
const inherited = [];
|
|
46
|
+
for (const browser of input.browsers) {
|
|
47
|
+
const partition = await partitionAffectedStories({
|
|
48
|
+
db: input.db,
|
|
49
|
+
tables: input.tables,
|
|
50
|
+
projectId: project.id,
|
|
51
|
+
branch: build.gitBranch,
|
|
52
|
+
defaultBranch: project.gitDefaultBranch,
|
|
53
|
+
stories: input.stories,
|
|
54
|
+
viewports: input.viewports,
|
|
55
|
+
browser,
|
|
56
|
+
affectedPaths: input.affectedPaths
|
|
57
|
+
});
|
|
58
|
+
work.push({
|
|
59
|
+
browser,
|
|
60
|
+
stories: partition.render
|
|
61
|
+
});
|
|
62
|
+
inherited.push(...tagged(partition.inherited, browser));
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
work,
|
|
66
|
+
inherited
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Render explicit per-engine work (one shard's slice), engine by engine. Nothing is inherited here.
|
|
71
|
+
*
|
|
72
|
+
* A story that crashes the browser is narrowed down and failed on its own, so it does not take the
|
|
73
|
+
* rest of the slice with it — see {@link renderIsolating}.
|
|
74
|
+
*/
|
|
75
|
+
async function renderWork(input, work) {
|
|
76
|
+
const result = {
|
|
77
|
+
captures: [],
|
|
78
|
+
failures: [],
|
|
79
|
+
inherited: [],
|
|
80
|
+
renderCount: 0
|
|
81
|
+
};
|
|
82
|
+
for (const engine of work) {
|
|
83
|
+
const mirror = mirrorAttemptLog(input.logger, input.recordLog, flakyDowngrade(input.stories));
|
|
84
|
+
try {
|
|
85
|
+
const pass = await isolatedPass(input, engine.browser, mirror, engine.stories, []);
|
|
86
|
+
result.captures.push(...pass.captures);
|
|
87
|
+
result.failures.push(...pass.failures);
|
|
88
|
+
result.renderCount += pass.renderCount;
|
|
89
|
+
} finally {
|
|
90
|
+
await mirror.flush();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* One engine's render, with a crashing story narrowed down and failed on its own rather than taking
|
|
97
|
+
* the rest of the pass with it — see {@link renderIsolating}.
|
|
98
|
+
*/
|
|
99
|
+
async function isolatedPass(input, browser, mirror, toRender, inherited) {
|
|
100
|
+
const pass = await renderIsolating(toRender, async (slice) => await attemptPass(input, browser, mirror, slice), {
|
|
101
|
+
note: isolationNote(mirror, browser),
|
|
102
|
+
viewportsOf: (story) => resolveStoryViewports(story, input.viewports).map((viewport) => viewport.name)
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
captures: fillEnvironment(pass.captures),
|
|
106
|
+
failures: pass.failures,
|
|
107
|
+
inherited,
|
|
108
|
+
renderCount: pass.renderCount
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** One render pass that never throws: a crash comes back beside whatever it captured first. */
|
|
112
|
+
async function attemptPass(input, browser, mirror, stories) {
|
|
113
|
+
const spooled = [];
|
|
114
|
+
try {
|
|
115
|
+
const pass = await renderPass(input, browser, mirror, stories, [], spooled);
|
|
116
|
+
return {
|
|
117
|
+
captures: pass.captures,
|
|
118
|
+
failures: pass.failures
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error instanceof TargetLaunchError) throw error;
|
|
122
|
+
mirror.log("warn", "render pass crashed, isolating the story at fault", {
|
|
123
|
+
browser,
|
|
124
|
+
stories: stories.length,
|
|
125
|
+
captured: spooled.length,
|
|
126
|
+
err: error instanceof Error ? error.message : String(error)
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
captures: spooled,
|
|
130
|
+
failures: [],
|
|
131
|
+
error
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Record how a crash was narrowed down on the attempt log. */
|
|
136
|
+
function isolationNote(mirror, browser) {
|
|
137
|
+
return (event, fields) => {
|
|
138
|
+
if (event === "poisoned") {
|
|
139
|
+
mirror.log("error", "story crashed the browser on its own", {
|
|
140
|
+
browser,
|
|
141
|
+
...fields
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const message = event === "isolating" ? "retrying the stories a crash interrupted" : "giving up on the stories left after repeated crashes";
|
|
146
|
+
mirror.log("warn", message, {
|
|
147
|
+
browser,
|
|
148
|
+
...fields
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Give captures spooled before a crash the environment a later pass reported: every pass of an
|
|
154
|
+
* engine runs on the same browser build and host, so the one fingerprint covers them all.
|
|
155
|
+
*/
|
|
156
|
+
function fillEnvironment(captures) {
|
|
157
|
+
const known = captures.find((capture) => capture.environment)?.environment;
|
|
158
|
+
if (!known) return captures;
|
|
159
|
+
for (const capture of captures) capture.environment ??= known;
|
|
160
|
+
return captures;
|
|
161
|
+
}
|
|
162
|
+
async function captureOnBrowser(input, browser) {
|
|
163
|
+
const mirror = mirrorAttemptLog(input.logger, input.recordLog, flakyDowngrade(input.stories));
|
|
164
|
+
try {
|
|
165
|
+
return await passOnBrowser(input, browser, mirror);
|
|
166
|
+
} finally {
|
|
167
|
+
await mirror.flush();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Failures of flaky stories are non-blocking, so the attempt log shows them as
|
|
172
|
+
* warnings (matching "flaky story failed (non-blocking)"), not errors.
|
|
173
|
+
*/
|
|
174
|
+
function flakyDowngrade(stories) {
|
|
175
|
+
const flaky = new Set(stories.filter((story) => isFlakyStory(story)).map((story) => story.id));
|
|
176
|
+
return (level, fields) => level === "error" && typeof fields?.["storyId"] === "string" && flaky.has(fields["storyId"]) ? "warn" : level;
|
|
177
|
+
}
|
|
178
|
+
async function passOnBrowser(input, browser, mirror) {
|
|
179
|
+
const { project, build } = input;
|
|
180
|
+
const partition = await partitionAffectedStories({
|
|
181
|
+
db: input.db,
|
|
182
|
+
tables: input.tables,
|
|
183
|
+
projectId: project.id,
|
|
184
|
+
branch: build.gitBranch,
|
|
185
|
+
defaultBranch: project.gitDefaultBranch,
|
|
186
|
+
stories: input.stories,
|
|
187
|
+
viewports: input.viewports,
|
|
188
|
+
browser,
|
|
189
|
+
affectedPaths: input.affectedPaths
|
|
190
|
+
});
|
|
191
|
+
mirror.log("info", "affected capture partitioned", {
|
|
192
|
+
browser,
|
|
193
|
+
rendered: partition.render.length,
|
|
194
|
+
inherited: partition.inherited.length,
|
|
195
|
+
total: input.stories.length
|
|
196
|
+
});
|
|
197
|
+
const inherited = tagged(partition.inherited, browser);
|
|
198
|
+
if (partition.render.length === 0) return {
|
|
199
|
+
captures: [],
|
|
200
|
+
failures: [],
|
|
201
|
+
inherited,
|
|
202
|
+
renderCount: 0
|
|
203
|
+
};
|
|
204
|
+
return await isolatedPass(input, browser, mirror, partition.render, inherited);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* One engine's render call. `spooled` may be supplied by the caller so that the screenshots taken
|
|
208
|
+
* before a crash survive the throw (they stream in through `onCapture`).
|
|
209
|
+
*/
|
|
210
|
+
async function renderPass(input, browser, mirror, toRender, inherited, spooled = []) {
|
|
211
|
+
const expected = toRender.flatMap((story) => resolveStoryViewports(story, input.viewports)).length;
|
|
212
|
+
const rendered = await renderInSlices({
|
|
213
|
+
runner: input.runner,
|
|
214
|
+
request: renderRequest(input, browser, toRender, spooled, {
|
|
215
|
+
mirror,
|
|
216
|
+
expected
|
|
217
|
+
}),
|
|
218
|
+
browser,
|
|
219
|
+
storybookUrl: input.storybookUrl,
|
|
220
|
+
captureCount: (story) => resolveStoryViewports(story, input.viewports).length,
|
|
221
|
+
renderTimeoutMs: input.renderTimeoutMs
|
|
222
|
+
});
|
|
223
|
+
mirror.log("info", "render pass finished", {
|
|
224
|
+
browser,
|
|
225
|
+
captured: spooled.length + rendered.captures.length,
|
|
226
|
+
failed: rendered.failures.length,
|
|
227
|
+
expected,
|
|
228
|
+
imageEngine: currentImageEngine()
|
|
229
|
+
});
|
|
230
|
+
const { environment } = rendered;
|
|
231
|
+
return {
|
|
232
|
+
captures: withEnvironment([...spooled, ...tagged(rendered.captures, browser)], environment),
|
|
233
|
+
failures: tagged(rendered.failures, browser),
|
|
234
|
+
inherited,
|
|
235
|
+
renderCount: toRender.length
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/** The render call for one engine; captures stream into `spooled` as they are taken. */
|
|
239
|
+
function renderRequest(input, browser, stories, spooled, progress) {
|
|
240
|
+
const { project, build } = input;
|
|
241
|
+
return {
|
|
242
|
+
onCapture: async (capture) => {
|
|
243
|
+
spooled.push(await spoolCapture(input, browser, capture));
|
|
244
|
+
input.progress?.();
|
|
245
|
+
logProgress(progress, browser, spooled.length);
|
|
246
|
+
},
|
|
247
|
+
buildId: build.id,
|
|
248
|
+
runId: input.runId ?? build.id,
|
|
249
|
+
storybookDir: input.storybookDir,
|
|
250
|
+
stories,
|
|
251
|
+
viewports: input.viewports,
|
|
252
|
+
logger: progress.mirror.logger,
|
|
253
|
+
executePlay: project.executePlay ?? false,
|
|
254
|
+
playTimeoutMs: project.playTimeoutMs ?? 1e4,
|
|
255
|
+
runA11y: (project.runA11y ?? true) && browser === primaryTargetOf(resolveBrowsers(project)),
|
|
256
|
+
target: browser
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/** Screenshots between progress lines on the attempt log. */
|
|
260
|
+
const PROGRESS_EVERY = 10;
|
|
261
|
+
/** Record "captured N/M" every few screenshots so a long render keeps showing movement. */
|
|
262
|
+
function logProgress(progress, browser, captured) {
|
|
263
|
+
if (captured % PROGRESS_EVERY !== 0) return;
|
|
264
|
+
progress.mirror.log("info", `captured ${captured}/${progress.expected} screenshots`, {
|
|
265
|
+
browser,
|
|
266
|
+
captured,
|
|
267
|
+
expected: progress.expected
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Write a screenshot to storage the moment it is captured and keep only its
|
|
272
|
+
* metadata, so memory holds the pages in flight rather than the whole build.
|
|
273
|
+
*/
|
|
274
|
+
async function spoolCapture(input, browser, capture) {
|
|
275
|
+
assertScreenshotBuffer(capture.screenshot);
|
|
276
|
+
const storedPath = await storeScreenshot(input.storage, input.project.id, capture.screenshot);
|
|
277
|
+
return {
|
|
278
|
+
...capture,
|
|
279
|
+
browser,
|
|
280
|
+
screenshot: EMPTY_SCREENSHOT,
|
|
281
|
+
stored: true,
|
|
282
|
+
storedPath
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const EMPTY_SCREENSHOT = Buffer.alloc(0);
|
|
286
|
+
/** Attach the render pass's environment to captures that carry none (a slice may have set its own). */
|
|
287
|
+
function withEnvironment(captures, environment) {
|
|
288
|
+
if (environment) for (const capture of captures) capture.environment ??= environment;
|
|
289
|
+
return captures;
|
|
290
|
+
}
|
|
291
|
+
/** Copy each row with its engine (runners may reuse result objects across calls). */
|
|
292
|
+
function tagged(rows, browser) {
|
|
293
|
+
return rows.map((row) => Object.assign({}, row, { browser }));
|
|
294
|
+
}
|
|
295
|
+
//#endregion
|
|
296
|
+
export { captureAcrossBrowsers, partitionBrowsers, renderWork };
|
|
297
|
+
|
|
298
|
+
//# sourceMappingURL=matrix.mjs.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { ModeMap } from "./adapter.mjs";
|
|
4
|
+
//#region src/capture/modes.d.ts
|
|
5
|
+
/** Upper bound on captures (global viewports + modes) per story. */
|
|
6
|
+
declare const MAX_MODES_PER_STORY = 8;
|
|
7
|
+
/**
|
|
8
|
+
* Baseline identity for a mode name: lower-case, URL/path-safe, stable under
|
|
9
|
+
* whitespace/case edits. Throws for names with no usable characters.
|
|
10
|
+
*/
|
|
11
|
+
declare function modeSlug(name: string): string;
|
|
12
|
+
/** Keep only allow-listed modes; `null`/empty allowlist keeps all. */
|
|
13
|
+
declare function filterModes(modes: ModeMap | undefined, allowlist: readonly string[] | null): ModeMap | undefined;
|
|
14
|
+
/** Parse the project's JSON `modesAllowlist` column; `null` when unset/invalid. */
|
|
15
|
+
declare function parseModesAllowlist(raw: string | null | undefined): string[] | null;
|
|
16
|
+
/**
|
|
17
|
+
* Encode globals for Storybook's `globals` URL arg (`a:b;c.d:e`). Nested
|
|
18
|
+
* objects flatten to dot paths; booleans/null/undefined/hex colors use the
|
|
19
|
+
* `!`-prefixed forms Storybook parses. Plain strings pass through (Storybook
|
|
20
|
+
* only accepts `[a-zA-Z0-9 _-]` and silently drops anything else).
|
|
21
|
+
*/
|
|
22
|
+
declare function buildGlobalsParam(globals: Record<string, unknown>): string;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { MAX_MODES_PER_STORY, buildGlobalsParam, filterModes, modeSlug, parseModesAllowlist };
|
|
25
|
+
//# sourceMappingURL=modes.d.mts.map
|