@storyshelf/core 0.6.2 → 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/README.md +49 -32
- package/dist/adapter/capture-queue.d.mts +44 -4
- package/dist/adapter/capture-runner.d.mts +134 -12
- package/dist/adapter/capture-runner.mjs +9 -12
- package/dist/adapter/database.d.mts +89 -10
- package/dist/adapter/database.mjs +4 -3
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +83 -0
- package/dist/adapter/design.mjs +27 -0
- package/dist/adapter/design.mjs.map +1 -0
- package/dist/adapter/email-sender.d.mts +0 -2
- package/dist/adapter/metadata.d.mts +3 -12
- package/dist/adapter/notifier.d.mts +6 -3
- package/dist/adapter/notifier.mjs +5 -1
- package/dist/adapter/oauth.d.mts +50 -0
- package/dist/adapter/setup.d.mts +4 -7
- package/dist/adapter/setup.mjs +3 -3
- package/dist/adapter/setup.mjs.map +1 -1
- package/dist/adapter/storage.d.mts +1 -3
- package/dist/adapter/webhook-events.d.mts +0 -2
- package/dist/adapter/webhook-events.mjs +13 -14
- package/dist/adapter/webhook-events.mjs.map +1 -1
- package/dist/adapters/capture-target-offers.d.mts +26 -0
- package/dist/adapters/capture-target-offers.mjs +57 -0
- package/dist/adapters/capture-target-offers.mjs.map +1 -0
- package/dist/adapters/capture-target.d.mts +69 -0
- package/dist/adapters/capture-target.mjs +75 -0
- package/dist/adapters/capture-target.mjs.map +1 -0
- package/dist/adapters/notifier/build-comment.d.mts +22 -0
- package/dist/adapters/notifier/build-comment.mjs +23 -0
- package/dist/adapters/notifier/build-comment.mjs.map +1 -0
- package/dist/adapters/notifier/build-state.d.mts +97 -0
- package/dist/adapters/notifier/build-state.mjs +217 -0
- package/dist/adapters/notifier/build-state.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +4 -4
- package/dist/adapters/notifier/events.mjs +32 -20
- package/dist/adapters/notifier/events.mjs.map +1 -1
- package/dist/adapters/notifier/format.d.mts +0 -2
- package/dist/adapters/notifier/format.mjs +1 -2
- package/dist/adapters/notifier/provider.d.mts +53 -9
- package/dist/adapters/notifier/provider.mjs +11 -0
- package/dist/adapters/notifier/provider.mjs.map +1 -0
- package/dist/adapters/notifier/state.d.mts +22 -0
- package/dist/adapters/notifier/state.mjs +65 -0
- package/dist/adapters/notifier/state.mjs.map +1 -0
- package/dist/adapters/notifier/types.d.mts +51 -4
- package/dist/adapters/remote-browser.d.mts +168 -0
- package/dist/adapters/remote-browser.mjs +201 -0
- package/dist/adapters/remote-browser.mjs.map +1 -0
- 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 +23 -0
- package/dist/adapters/render-runs.mjs +40 -0
- package/dist/adapters/render-runs.mjs.map +1 -0
- package/dist/adapters/setup-runner.d.mts +0 -2
- package/dist/adapters/setup-runner.mjs +3 -6
- package/dist/adapters/setup-runner.mjs.map +1 -1
- package/dist/adapters/sources.d.mts +10 -0
- package/dist/adapters/sources.mjs +19 -0
- package/dist/adapters/sources.mjs.map +1 -0
- package/dist/adapters/validate.d.mts +0 -2
- package/dist/adapters/validate.mjs +27 -20
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +103 -0
- package/dist/ai.mjs +18 -0
- package/dist/ai.mjs.map +1 -0
- 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 +16 -0
- package/dist/capture/a11y/axe-source.mjs +36 -0
- package/dist/capture/a11y/axe-source.mjs.map +1 -0
- package/dist/capture/a11y/in-page.d.mts +25 -0
- package/dist/capture/a11y/in-page.mjs +59 -0
- package/dist/capture/a11y/in-page.mjs.map +1 -0
- package/dist/capture/a11y/map.d.mts +22 -0
- package/dist/capture/a11y/map.mjs +47 -0
- package/dist/capture/a11y/map.mjs.map +1 -0
- package/dist/capture/a11y/options.d.mts +21 -0
- package/dist/capture/a11y/options.mjs +74 -0
- package/dist/capture/a11y/options.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +46 -5
- package/dist/capture/adapter.mjs +1 -2
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +2 -2
- package/dist/capture/affected.mjs +14 -6
- package/dist/capture/affected.mjs.map +1 -1
- package/dist/capture/attempt-logger.mjs +83 -0
- package/dist/capture/attempt-logger.mjs.map +1 -0
- package/dist/capture/browsers.d.mts +33 -0
- package/dist/capture/browsers.mjs +66 -0
- package/dist/capture/browsers.mjs.map +1 -0
- package/dist/capture/build-state/deliver.d.mts +30 -0
- package/dist/capture/build-state/deliver.mjs +117 -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 +54 -0
- package/dist/capture/build-state/notify-job.mjs +132 -0
- package/dist/capture/build-state/notify-job.mjs.map +1 -0
- package/dist/capture/build-state/publisher.d.mts +21 -0
- package/dist/capture/build-state/publisher.mjs +106 -0
- package/dist/capture/build-state/publisher.mjs.map +1 -0
- package/dist/capture/build-state/resume.d.mts +7 -0
- package/dist/capture/build-state/resume.mjs +46 -0
- package/dist/capture/build-state/resume.mjs.map +1 -0
- package/dist/capture/build-state/types.d.mts +47 -0
- package/dist/capture/catalog/catalogue.d.mts +20 -0
- package/dist/capture/catalog/catalogue.mjs +52 -0
- package/dist/capture/catalog/catalogue.mjs.map +1 -0
- package/dist/capture/catalog/components.d.mts +21 -0
- package/dist/capture/catalog/components.mjs +54 -0
- package/dist/capture/catalog/components.mjs.map +1 -0
- package/dist/capture/catalog/history.d.mts +28 -0
- package/dist/capture/catalog/history.mjs +50 -0
- package/dist/capture/catalog/history.mjs.map +1 -0
- package/dist/capture/catalog/hook.d.mts +12 -0
- package/dist/capture/catalog/hook.mjs +18 -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 +24 -0
- package/dist/capture/catalog/load.mjs +48 -0
- package/dist/capture/catalog/load.mjs.map +1 -0
- package/dist/capture/catalog/parse.d.mts +13 -0
- package/dist/capture/catalog/parse.mjs +55 -0
- package/dist/capture/catalog/parse.mjs.map +1 -0
- package/dist/capture/catalog/read.d.mts +15 -0
- package/dist/capture/catalog/read.mjs +39 -0
- package/dist/capture/catalog/read.mjs.map +1 -0
- package/dist/capture/catalog/types.d.mts +31 -0
- 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 +56 -0
- package/dist/capture/consumer/capture-handler.mjs +117 -0
- package/dist/capture/consumer/capture-handler.mjs.map +1 -0
- package/dist/capture/consumer/idle.d.mts +15 -0
- package/dist/capture/consumer/idle.mjs +24 -0
- package/dist/capture/consumer/idle.mjs.map +1 -0
- package/dist/capture/consumer/index.d.mts +11 -0
- package/dist/capture/consumer/index.mjs +138 -0
- package/dist/capture/consumer/index.mjs.map +1 -0
- package/dist/capture/consumer/loop.mjs +109 -0
- package/dist/capture/consumer/loop.mjs.map +1 -0
- package/dist/capture/consumer/metrics.d.mts +39 -0
- package/dist/capture/consumer/metrics.mjs +37 -0
- package/dist/capture/consumer/metrics.mjs.map +1 -0
- package/dist/capture/consumer/retry.d.mts +31 -0
- package/dist/capture/consumer/retry.mjs +33 -0
- package/dist/capture/consumer/retry.mjs.map +1 -0
- package/dist/capture/consumer/run-job.mjs +194 -0
- package/dist/capture/consumer/run-job.mjs.map +1 -0
- package/dist/capture/consumer/startup.mjs +37 -0
- package/dist/capture/consumer/startup.mjs.map +1 -0
- package/dist/capture/consumer/types.d.mts +95 -0
- package/dist/capture/crash-failure.d.mts +23 -0
- package/dist/capture/crash-failure.mjs +69 -0
- package/dist/capture/crash-failure.mjs.map +1 -0
- package/dist/capture/db-queue/cadence.d.mts +30 -0
- package/dist/capture/db-queue/cadence.mjs +37 -0
- package/dist/capture/db-queue/cadence.mjs.map +1 -0
- package/dist/capture/db-queue/claim.d.mts +7 -0
- package/dist/capture/db-queue/claim.mjs +148 -0
- package/dist/capture/db-queue/claim.mjs.map +1 -0
- package/dist/capture/db-queue/clock.mjs +11 -0
- package/dist/capture/db-queue/clock.mjs.map +1 -0
- package/dist/capture/db-queue/codec.mjs +75 -0
- package/dist/capture/db-queue/codec.mjs.map +1 -0
- package/dist/capture/db-queue/index.d.mts +18 -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 +34 -0
- package/dist/capture/db-queue/keeper.mjs +91 -0
- package/dist/capture/db-queue/keeper.mjs.map +1 -0
- package/dist/capture/db-queue/lease.mjs +45 -0
- package/dist/capture/db-queue/lease.mjs.map +1 -0
- package/dist/capture/db-queue/lifecycle.mjs +35 -0
- package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
- package/dist/capture/db-queue/operations.mjs +93 -0
- package/dist/capture/db-queue/operations.mjs.map +1 -0
- package/dist/capture/db-queue/poll-loop.d.mts +28 -0
- package/dist/capture/db-queue/poll-loop.mjs +88 -0
- package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
- package/dist/capture/db-queue/poll.d.mts +8 -0
- package/dist/capture/db-queue/poll.mjs +68 -0
- package/dist/capture/db-queue/poll.mjs.map +1 -0
- package/dist/capture/db-queue/timers.d.mts +10 -0
- package/dist/capture/db-queue/timers.mjs +36 -0
- package/dist/capture/db-queue/timers.mjs.map +1 -0
- package/dist/capture/db-queue/types.d.mts +81 -0
- package/dist/capture/db-queue/types.mjs +11 -0
- package/dist/capture/db-queue/types.mjs.map +1 -0
- package/dist/capture/db-queue/wake.mjs +47 -0
- package/dist/capture/db-queue/wake.mjs.map +1 -0
- package/dist/capture/design.mjs +38 -0
- package/dist/capture/design.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +24 -5
- package/dist/capture/diff-record.mjs.map +1 -1
- package/dist/capture/dispatch.d.mts +13 -10
- package/dist/capture/dispatch.mjs +102 -111
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +59 -0
- package/dist/capture/enrich.mjs.map +1 -0
- package/dist/capture/failure-sets.mjs +21 -0
- package/dist/capture/failure-sets.mjs.map +1 -0
- package/dist/capture/fingerprint.d.mts +40 -0
- package/dist/capture/fingerprint.mjs +82 -0
- package/dist/capture/fingerprint.mjs.map +1 -0
- package/dist/capture/forked/channel.d.mts +23 -0
- package/dist/capture/forked/child.d.mts +32 -0
- package/dist/capture/forked/child.mjs +201 -0
- package/dist/capture/forked/child.mjs.map +1 -0
- package/dist/capture/forked/crash-tracker.mjs +22 -0
- package/dist/capture/forked/crash-tracker.mjs.map +1 -0
- package/dist/capture/forked/decision.d.mts +24 -0
- package/dist/capture/forked/decision.mjs +26 -0
- package/dist/capture/forked/decision.mjs.map +1 -0
- package/dist/capture/forked/exit.mjs +11 -0
- package/dist/capture/forked/exit.mjs.map +1 -0
- package/dist/capture/forked/fork.d.mts +29 -0
- package/dist/capture/forked/fork.mjs +92 -0
- package/dist/capture/forked/fork.mjs.map +1 -0
- package/dist/capture/forked/forked-capture.d.mts +38 -0
- package/dist/capture/forked/forked-capture.mjs +239 -0
- package/dist/capture/forked/forked-capture.mjs.map +1 -0
- package/dist/capture/forked/lease-book.mjs +45 -0
- package/dist/capture/forked/lease-book.mjs.map +1 -0
- package/dist/capture/forked/protocol.d.mts +36 -0
- package/dist/capture/forked/protocol.mjs +24 -0
- package/dist/capture/forked/protocol.mjs.map +1 -0
- package/dist/capture/forked/runs.mjs +74 -0
- package/dist/capture/forked/runs.mjs.map +1 -0
- 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 +140 -0
- package/dist/capture/isolate.mjs.map +1 -0
- package/dist/capture/job-lease.d.mts +26 -0
- package/dist/capture/job-lease.mjs +56 -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 +23 -0
- package/dist/capture/modes.mjs +106 -0
- package/dist/capture/modes.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +37 -7
- package/dist/capture/orchestrator.mjs +120 -110
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +9 -4
- package/dist/capture/pipeline.mjs +284 -48
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +9 -0
- package/dist/capture/progress.mjs +27 -0
- package/dist/capture/progress.mjs.map +1 -0
- package/dist/capture/publish/accept.d.mts +37 -0
- package/dist/capture/publish/accept.mjs +27 -0
- package/dist/capture/publish/accept.mjs.map +1 -0
- package/dist/capture/publish/complete.d.mts +44 -0
- package/dist/capture/publish/complete.mjs +63 -0
- package/dist/capture/publish/complete.mjs.map +1 -0
- package/dist/capture/publish/extract.d.mts +18 -0
- package/dist/capture/publish/extract.mjs +120 -0
- package/dist/capture/publish/extract.mjs.map +1 -0
- package/dist/capture/publish/handler.d.mts +26 -0
- package/dist/capture/publish/handler.mjs +61 -0
- package/dist/capture/publish/handler.mjs.map +1 -0
- package/dist/capture/publish/index.d.mts +11 -0
- 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 +35 -0
- package/dist/capture/publish/limits.mjs +38 -0
- package/dist/capture/publish/limits.mjs.map +1 -0
- package/dist/capture/publish/published.d.mts +13 -0
- package/dist/capture/publish/published.mjs +17 -0
- package/dist/capture/publish/published.mjs.map +1 -0
- package/dist/capture/publish/run.d.mts +31 -0
- package/dist/capture/publish/run.mjs +29 -0
- package/dist/capture/publish/run.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +1 -3
- package/dist/capture/rediff.mjs +23 -9
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +52 -0
- package/dist/capture/render.mjs.map +1 -0
- package/dist/capture/scratch.d.mts +42 -0
- package/dist/capture/scratch.mjs +136 -0
- package/dist/capture/scratch.mjs.map +1 -0
- package/dist/capture/shard-plan.mjs +127 -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 +175 -0
- package/dist/capture/shards/execute.mjs.map +1 -0
- package/dist/capture/shards/fan-out.mjs +125 -0
- package/dist/capture/shards/fan-out.mjs.map +1 -0
- package/dist/capture/shards/history.mjs +34 -0
- package/dist/capture/shards/history.mjs.map +1 -0
- package/dist/capture/shards/reduce.mjs +96 -0
- package/dist/capture/shards/reduce.mjs.map +1 -0
- package/dist/capture/sizing.d.mts +0 -2
- package/dist/capture/sizing.mjs +2 -13
- package/dist/capture/sizing.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +3 -67
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +7 -10
- package/dist/capture/statics.mjs +26 -74
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +38 -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 -6
- package/dist/capture/storybook.mjs +63 -19
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +20 -0
- package/dist/capture/target.mjs.map +1 -0
- package/dist/capture/targets.d.mts +23 -0
- package/dist/capture/targets.mjs +31 -0
- package/dist/capture/targets.mjs.map +1 -0
- package/dist/capture/tests.d.mts +35 -0
- package/dist/capture/tests.mjs +136 -0
- package/dist/capture/tests.mjs.map +1 -0
- package/dist/capture/token-chunks.mjs +91 -0
- package/dist/capture/token-chunks.mjs.map +1 -0
- package/dist/capture.d.mts +63 -7
- package/dist/capture.mjs +51 -3
- package/dist/config.d.mts +136 -14
- package/dist/config.mjs +14 -8
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +7 -3
- package/dist/diff.mjs +15 -3
- package/dist/diff.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/insights/budget.d.mts +18 -0
- package/dist/insights/budget.mjs +32 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/envelope.d.mts +57 -0
- package/dist/insights/envelope.mjs +59 -0
- package/dist/insights/envelope.mjs.map +1 -0
- package/dist/insights/evidence-health.d.mts +30 -0
- package/dist/insights/evidence-health.mjs +27 -0
- package/dist/insights/evidence-health.mjs.map +1 -0
- package/dist/insights/evidence.d.mts +53 -0
- package/dist/insights/evidence.mjs +63 -0
- package/dist/insights/evidence.mjs.map +1 -0
- package/dist/insights/hash.d.mts +16 -0
- package/dist/insights/hash.mjs +20 -0
- package/dist/insights/hash.mjs.map +1 -0
- package/dist/insights/limits.d.mts +22 -0
- package/dist/insights/limits.mjs +23 -0
- package/dist/insights/limits.mjs.map +1 -0
- package/dist/insights/prompts/v1.d.mts +11 -0
- package/dist/insights/prompts/v1.mjs +33 -0
- package/dist/insights/prompts/v1.mjs.map +1 -0
- package/dist/insights/redact.d.mts +7 -0
- package/dist/insights/redact.mjs +23 -0
- package/dist/insights/redact.mjs.map +1 -0
- package/dist/insights/text.mjs +43 -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.d.mts +0 -2
- package/dist/logger.mjs +18 -3
- package/dist/logger.mjs.map +1 -1
- package/dist/models/ai-budget-alert.d.mts +21 -0
- package/dist/models/ai-budget-alert.mjs +37 -0
- package/dist/models/ai-budget-alert.mjs.map +1 -0
- package/dist/models/ai-usage.d.mts +38 -0
- package/dist/models/ai-usage.mjs +60 -0
- package/dist/models/ai-usage.mjs.map +1 -0
- 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 +82 -0
- package/dist/models/baseline-archive.mjs +105 -0
- package/dist/models/baseline-archive.mjs.map +1 -0
- 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 +17 -0
- package/dist/models/baseline-export.mjs +135 -0
- package/dist/models/baseline-export.mjs.map +1 -0
- package/dist/models/baseline-import.mjs +117 -0
- package/dist/models/baseline-import.mjs.map +1 -0
- 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 +48 -0
- package/dist/models/baseline-transfer.mjs +149 -0
- package/dist/models/baseline-transfer.mjs.map +1 -0
- package/dist/models/baseline.d.mts +37 -7
- package/dist/models/baseline.mjs +152 -28
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +26 -0
- package/dist/models/build-component.mjs +54 -0
- package/dist/models/build-component.mjs.map +1 -0
- package/dist/models/build-review-status.d.mts +27 -0
- package/dist/models/build-review-status.mjs +36 -0
- package/dist/models/build-review-status.mjs.map +1 -0
- package/dist/models/build-review.d.mts +26 -0
- package/dist/models/build-review.mjs +53 -0
- package/dist/models/build-review.mjs.map +1 -0
- package/dist/models/build.d.mts +25 -5
- package/dist/models/build.mjs +53 -15
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +7 -3
- package/dist/models/capture-attempt.mjs +30 -6
- package/dist/models/capture-attempt.mjs.map +1 -1
- package/dist/models/capture-job.d.mts +104 -0
- package/dist/models/capture-job.mjs +287 -0
- package/dist/models/capture-job.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +1 -3
- package/dist/models/capture-log.mjs +5 -5
- package/dist/models/capture-log.mjs.map +1 -1
- package/dist/models/capture-shard.d.mts +60 -0
- package/dist/models/capture-shard.mjs +189 -0
- package/dist/models/capture-shard.mjs.map +1 -0
- package/dist/models/changeset.d.mts +46 -0
- package/dist/models/changeset.mjs +80 -0
- package/dist/models/changeset.mjs.map +1 -0
- package/dist/models/columns.mjs +18 -0
- package/dist/models/columns.mjs.map +1 -0
- package/dist/models/comment.d.mts +4 -3
- package/dist/models/comment.mjs +8 -6
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/component.d.mts +41 -0
- package/dist/models/component.mjs +59 -0
- package/dist/models/component.mjs.map +1 -0
- package/dist/models/connections.d.mts +39 -0
- package/dist/models/connections.mjs +111 -0
- package/dist/models/connections.mjs.map +1 -0
- package/dist/models/content-ref.d.mts +1 -3
- package/dist/models/content-ref.mjs +2 -3
- package/dist/models/content-ref.mjs.map +1 -1
- package/dist/models/credentials.d.mts +0 -2
- package/dist/models/credentials.mjs +13 -4
- package/dist/models/credentials.mjs.map +1 -1
- package/dist/models/design-credentials.d.mts +39 -0
- package/dist/models/design-credentials.mjs +85 -0
- package/dist/models/design-credentials.mjs.map +1 -0
- package/dist/models/design-health.d.mts +14 -0
- package/dist/models/design-health.mjs +24 -0
- package/dist/models/design-health.mjs.map +1 -0
- package/dist/models/design-nodes.d.mts +81 -0
- package/dist/models/design-nodes.mjs +221 -0
- package/dist/models/design-nodes.mjs.map +1 -0
- package/dist/models/design-renders.mjs +73 -0
- package/dist/models/design-renders.mjs.map +1 -0
- package/dist/models/design-resolve.d.mts +27 -0
- package/dist/models/design-resolve.mjs +40 -0
- package/dist/models/design-resolve.mjs.map +1 -0
- package/dist/models/design.d.mts +37 -0
- package/dist/models/design.mjs +89 -0
- package/dist/models/design.mjs.map +1 -0
- package/dist/models/inherited-ratio.d.mts +19 -0
- package/dist/models/inherited-ratio.mjs +21 -0
- package/dist/models/inherited-ratio.mjs.map +1 -0
- package/dist/models/insight.d.mts +68 -0
- package/dist/models/insight.mjs +161 -0
- package/dist/models/insight.mjs.map +1 -0
- package/dist/models/label.d.mts +4 -4
- package/dist/models/label.mjs +26 -12
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +1 -3
- package/dist/models/member.mjs +6 -6
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +37 -5
- package/dist/models/notification-channel.mjs +89 -11
- package/dist/models/notification-channel.mjs.map +1 -1
- package/dist/models/notification-subscription.d.mts +1 -4
- package/dist/models/notification-subscription.mjs +8 -11
- package/dist/models/notification-subscription.mjs.map +1 -1
- package/dist/models/project-group-mapping.d.mts +6 -3
- package/dist/models/project-group-mapping.mjs +15 -6
- package/dist/models/project-group-mapping.mjs.map +1 -1
- package/dist/models/project.d.mts +9 -3
- package/dist/models/project.mjs +10 -6
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/public-origin.d.mts +61 -0
- package/dist/models/public-origin.mjs +113 -0
- package/dist/models/public-origin.mjs.map +1 -0
- package/dist/models/review-switch-off.d.mts +12 -0
- package/dist/models/review-switch-off.mjs +25 -0
- package/dist/models/review-switch-off.mjs.map +1 -0
- package/dist/models/site-settings.d.mts +21 -0
- package/dist/models/site-settings.mjs +47 -0
- package/dist/models/site-settings.mjs.map +1 -0
- package/dist/models/snapshot.d.mts +39 -3
- package/dist/models/snapshot.mjs +54 -5
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +13 -3
- package/dist/models/token.mjs +24 -7
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/usage.d.mts +74 -0
- package/dist/models/usage.mjs +148 -0
- package/dist/models/usage.mjs.map +1 -0
- package/dist/models/user.d.mts +1 -3
- package/dist/models/user.mjs +5 -5
- package/dist/models/user.mjs.map +1 -1
- package/dist/models/webhook.d.mts +15 -3
- package/dist/models/webhook.mjs +34 -6
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +30 -4
- package/dist/models.mjs +31 -6
- package/dist/options.d.mts +16 -0
- package/dist/options.mjs +88 -0
- package/dist/options.mjs.map +1 -0
- package/dist/paths.d.mts +21 -6
- package/dist/paths.mjs +57 -12
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +16 -0
- package/dist/retention/ai.mjs +22 -0
- package/dist/retention/ai.mjs.map +1 -0
- package/dist/retention/pending.mjs +46 -0
- package/dist/retention/pending.mjs.map +1 -0
- 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 +49 -0
- package/dist/retention/screenshot-sweep.mjs +127 -0
- package/dist/retention/screenshot-sweep.mjs.map +1 -0
- package/dist/retention.d.mts +16 -4
- package/dist/retention.mjs +76 -16
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +8 -2
- package/dist/schema/build-component.d.mts +23 -0
- package/dist/schema/build-review.d.mts +15 -0
- package/dist/schema/build.d.mts +7 -2
- package/dist/schema/capture-attempt.d.mts +0 -2
- package/dist/schema/capture-job.d.mts +58 -0
- package/dist/schema/capture-shard.d.mts +33 -0
- package/dist/schema/comment.d.mts +2 -2
- package/dist/schema/connection.d.mts +22 -0
- package/dist/schema/content-ref.d.mts +0 -2
- package/dist/schema/design.d.mts +56 -0
- package/dist/schema/insight.d.mts +52 -0
- package/dist/schema/label.d.mts +0 -2
- package/dist/schema/member.d.mts +0 -2
- package/dist/schema/notification.d.mts +4 -2
- package/dist/schema/project-group-mapping.d.mts +0 -2
- package/dist/schema/project.d.mts +21 -2
- package/dist/schema/site-setting.d.mts +19 -0
- package/dist/schema/snapshot.d.mts +17 -2
- package/dist/schema/token.d.mts +0 -2
- package/dist/schema/usage.d.mts +28 -0
- package/dist/schema/user.d.mts +0 -2
- package/dist/schema/webhook.d.mts +0 -2
- package/dist/schema.d.mts +12 -6
- package/dist/serverless.d.mts +19 -0
- package/dist/serverless.mjs +36 -0
- package/dist/serverless.mjs.map +1 -0
- 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 +1 -3
- 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 +37 -0
- package/dist/test-helpers/deferred.mjs +60 -0
- package/dist/test-helpers/deferred.mjs.map +1 -0
- package/dist/test-helpers/design-contract.d.mts +19 -0
- package/dist/test-helpers/design-contract.mjs +39 -0
- package/dist/test-helpers/design-contract.mjs.map +1 -0
- 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 +21 -4
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +41 -0
- package/dist/test-helpers/fake-design.mjs +113 -0
- package/dist/test-helpers/fake-design.mjs.map +1 -0
- package/dist/test-helpers/fake-notifier.d.mts +19 -0
- package/dist/test-helpers/fake-notifier.mjs +43 -0
- package/dist/test-helpers/fake-notifier.mjs.map +1 -0
- 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 +260 -69
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +25 -0
- package/dist/test-helpers/manual-clock.mjs +55 -0
- package/dist/test-helpers/manual-clock.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +7 -3
- package/dist/test-helpers/notifier-contract.mjs +33 -5
- package/dist/test-helpers/notifier-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-contract.d.mts +16 -4
- package/dist/test-helpers/queue-contract.mjs +196 -3
- package/dist/test-helpers/queue-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-multi-instance.d.mts +18 -0
- package/dist/test-helpers/queue-multi-instance.mjs +205 -0
- package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.d.mts +0 -2
- package/dist/test-helpers/runner-contract.mjs +12 -2
- package/dist/test-helpers/runner-contract.mjs.map +1 -1
- package/dist/test-helpers/sql-chunks.mjs +25 -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 +2 -3
- package/dist/test-helpers.d.mts +10 -4
- package/dist/test-helpers.mjs +9 -3
- package/dist/tracing.mjs +36 -6
- package/dist/tracing.mjs.map +1 -1
- package/dist/types.d.mts +14 -5
- package/dist/types.mjs +22 -6
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +4 -2
- package/dist/urls.mjs +12 -4
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/adaptive-concurrency.d.mts +64 -0
- package/dist/utils/adaptive-concurrency.mjs +148 -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 +42 -0
- package/dist/utils/connect-retry.mjs.map +1 -0
- package/dist/utils/encrypt.d.mts +0 -2
- package/dist/utils/encrypt.mjs +1 -2
- package/dist/utils/environment.d.mts +17 -0
- package/dist/utils/environment.mjs +58 -0
- package/dist/utils/environment.mjs.map +1 -0
- package/dist/utils/hash.d.mts +0 -2
- package/dist/utils/hash.mjs +1 -2
- package/dist/utils/http.d.mts +29 -3
- package/dist/utils/http.mjs +100 -19
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +13 -0
- package/dist/utils/map-adaptive.mjs +71 -0
- package/dist/utils/map-adaptive.mjs.map +1 -0
- package/dist/utils/map-limit.d.mts +8 -0
- package/dist/utils/map-limit.mjs +31 -0
- package/dist/utils/map-limit.mjs.map +1 -0
- package/dist/utils/mime.d.mts +0 -2
- package/dist/utils/mime.mjs +1 -2
- package/dist/utils/page-limiter.d.mts +35 -0
- package/dist/utils/page-limiter.mjs +55 -0
- package/dist/utils/page-limiter.mjs.map +1 -0
- 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 +18 -0
- package/dist/utils/public-origin.mjs +74 -0
- package/dist/utils/public-origin.mjs.map +1 -0
- package/dist/utils/redact.d.mts +0 -2
- package/dist/utils/redact.mjs +4 -2
- package/dist/utils/redact.mjs.map +1 -1
- package/dist/utils/remote-environment.d.mts +76 -0
- package/dist/utils/remote-environment.mjs +163 -0
- package/dist/utils/remote-environment.mjs.map +1 -0
- package/dist/utils/resources.d.mts +62 -0
- package/dist/utils/resources.mjs +121 -0
- package/dist/utils/resources.mjs.map +1 -0
- package/dist/utils/screenshot-store.d.mts +13 -0
- package/dist/utils/screenshot-store.mjs +40 -0
- package/dist/utils/screenshot-store.mjs.map +1 -0
- package/dist/utils/session-gate.d.mts +23 -0
- package/dist/utils/session-gate.mjs +69 -0
- package/dist/utils/session-gate.mjs.map +1 -0
- package/dist/utils/storybook-token.d.mts +89 -0
- package/dist/utils/storybook-token.mjs +135 -0
- package/dist/utils/storybook-token.mjs.map +1 -0
- package/dist/utils/storybook-url.d.mts +13 -0
- package/dist/utils/storybook-url.mjs +20 -0
- package/dist/utils/storybook-url.mjs.map +1 -0
- package/dist/utils/timing.d.mts +0 -2
- package/dist/utils/timing.mjs +2 -3
- package/dist/utils/timing.mjs.map +1 -1
- package/dist/utils/ulid.d.mts +0 -2
- package/dist/utils/ulid.mjs +1 -2
- package/dist/utils/zip-read.mjs +128 -0
- package/dist/utils/zip-read.mjs.map +1 -0
- package/dist/utils/zip-write.d.mts +16 -0
- package/dist/utils/zip-write.mjs +187 -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 +35 -26
- package/dist/adapter/capture-runner.mjs.map +0 -1
- package/dist/adapter/git-host/comments.d.mts +0 -41
- package/dist/adapter/git-host/comments.mjs +0 -54
- package/dist/adapter/git-host/comments.mjs.map +0 -1
- package/dist/adapter/git-host/helpers.d.mts +0 -13
- package/dist/adapter/git-host/helpers.mjs +0 -30
- package/dist/adapter/git-host/helpers.mjs.map +0 -1
- package/dist/adapter/git-host.d.mts +0 -42
- package/dist/capture/queue.d.mts +0 -38
- package/dist/capture/queue.mjs +0 -98
- package/dist/capture/queue.mjs.map +0 -1
- package/dist/capture/recovery.d.mts +0 -21
- package/dist/capture/recovery.mjs +0 -83
- package/dist/capture/recovery.mjs.map +0 -1
- package/dist/capture/status-fanout.mjs +0 -72
- package/dist/capture/status-fanout.mjs.map +0 -1
- package/dist/diff/engine.d.mts +0 -16
- package/dist/diff/engine.mjs +0 -87
- package/dist/diff/engine.mjs.map +0 -1
- package/dist/diff/options.d.mts +0 -42
- package/dist/diff/options.mjs +0 -15
- package/dist/diff/options.mjs.map +0 -1
- package/dist/models/status-config.d.mts +0 -38
- package/dist/models/status-config.mjs +0 -63
- package/dist/models/status-config.mjs.map +0 -1
- package/dist/schema/status-config.d.mts +0 -16
- package/dist/test-helpers/git-contract.d.mts +0 -11
- package/dist/test-helpers/git-contract.mjs +0 -34
- package/dist/test-helpers/git-contract.mjs.map +0 -1
- /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
|
@@ -1,40 +1,51 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import {
|
|
4
|
+
import { redactSecrets } from "../utils/redact.mjs";
|
|
5
5
|
import { CaptureAttemptModel, emitAttemptLog } from "../models/capture-attempt.mjs";
|
|
6
|
+
import { BuildModel } from "../models/build.mjs";
|
|
7
|
+
import { SignedUrlLifetimeError } from "./token-chunks.mjs";
|
|
8
|
+
import { CaptureShardModel } from "../models/capture-shard.mjs";
|
|
6
9
|
import { ProjectModel } from "../models/project.mjs";
|
|
7
10
|
import { executeCaptureJob } from "./orchestrator.mjs";
|
|
8
|
-
import { postStatusesForBuild } from "./status-fanout.mjs";
|
|
9
11
|
import { CaptureLogModel } from "../models/capture-log.mjs";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
+
import { leaseProgress } from "./progress.mjs";
|
|
13
|
+
import { executeCaptureShard } from "./shards/execute.mjs";
|
|
14
|
+
import { hasApprovedBuildForSha } from "./skip-checks.mjs";
|
|
15
|
+
// src/capture/dispatch.ts
|
|
12
16
|
/** Build the queue's job runner: post pending, apply skip rules, capture, report. */
|
|
13
17
|
function createDispatchJob(deps) {
|
|
14
|
-
return async (job) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
return async (job, lease) => {
|
|
19
|
+
await (job.shard ? runShardJob(deps, {
|
|
20
|
+
...job,
|
|
21
|
+
shard: job.shard
|
|
22
|
+
}, lease) : runBuildJob(deps, job, lease));
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** The build's own job: post pending, apply skip rules, capture (or plan), report. */
|
|
26
|
+
async function runBuildJob(deps, job, lease) {
|
|
27
|
+
const target = await loadJobTarget(deps, job);
|
|
28
|
+
if (!target) return;
|
|
29
|
+
await deps.buildState.request(job.buildId);
|
|
30
|
+
const { attempt, recordLog } = await beginAttempt(deps, target.project, target.build, job.reqId, lease);
|
|
31
|
+
const ctx = {
|
|
32
|
+
deps,
|
|
33
|
+
build: target.build,
|
|
34
|
+
project: target.project,
|
|
35
|
+
attempt,
|
|
36
|
+
recordLog,
|
|
37
|
+
progress: leaseProgress(lease)
|
|
30
38
|
};
|
|
39
|
+
if (await maybeSkipDuplicate(ctx)) return;
|
|
40
|
+
await runAndReport(ctx, job);
|
|
31
41
|
}
|
|
32
42
|
/** Open a new attempt row for this run and return its log recorder. */
|
|
33
|
-
async function beginAttempt(deps, project, build, reqId) {
|
|
43
|
+
async function beginAttempt(deps, project, build, reqId, lease) {
|
|
34
44
|
const attempts = new CaptureAttemptModel(deps.db, deps.tables);
|
|
35
45
|
const logs = new CaptureLogModel(deps.db, deps.tables);
|
|
36
46
|
const attempt = await attempts.startAttempt(project.id, build.id, reqId);
|
|
37
47
|
const recordLog = async (level, message, fields) => {
|
|
48
|
+
lease?.touch();
|
|
38
49
|
await logs.append(project.id, build.id, attempt.id, level, message, fields);
|
|
39
50
|
};
|
|
40
51
|
await emitAttemptLog(recordLog, deps.logger, "info", "capture queued", {
|
|
@@ -54,41 +65,10 @@ async function finishAttempt(ctx, status, error, counts) {
|
|
|
54
65
|
}
|
|
55
66
|
/** Full error text for an attempt row (message plus stack when available). */
|
|
56
67
|
function errorText(reason) {
|
|
57
|
-
|
|
58
|
-
return String(reason);
|
|
59
|
-
}
|
|
60
|
-
async function postPending(deps, project, url, sha) {
|
|
61
|
-
await postStatusesForBuild({
|
|
62
|
-
db: deps.db,
|
|
63
|
-
tables: deps.tables,
|
|
64
|
-
project,
|
|
65
|
-
sha,
|
|
66
|
-
status: "pending",
|
|
67
|
-
url,
|
|
68
|
-
providers: deps.gitHosts,
|
|
69
|
-
secret: deps.secret,
|
|
70
|
-
logger: deps.logger
|
|
71
|
-
}).catch((error) => {
|
|
72
|
-
deps.logger.error({ err: error }, "failed to post pending status");
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
async function postTerminal(deps, project, url, sha, status) {
|
|
76
|
-
await postStatusesForBuild({
|
|
77
|
-
db: deps.db,
|
|
78
|
-
tables: deps.tables,
|
|
79
|
-
project,
|
|
80
|
-
sha,
|
|
81
|
-
status,
|
|
82
|
-
url,
|
|
83
|
-
providers: deps.gitHosts,
|
|
84
|
-
secret: deps.secret,
|
|
85
|
-
logger: deps.logger
|
|
86
|
-
}).catch((error) => {
|
|
87
|
-
deps.logger.error({ err: error }, "failed to post terminal status");
|
|
88
|
-
});
|
|
68
|
+
return redactSecrets(reason instanceof Error ? reason.stack ?? reason.message : String(reason));
|
|
89
69
|
}
|
|
90
70
|
async function approveWithoutCapture(ctx, reason) {
|
|
91
|
-
const { deps, build
|
|
71
|
+
const { deps, build } = ctx;
|
|
92
72
|
deps.logger.info({
|
|
93
73
|
buildId: build.id,
|
|
94
74
|
sha: build.gitSha,
|
|
@@ -100,25 +80,7 @@ async function approveWithoutCapture(ctx, reason) {
|
|
|
100
80
|
});
|
|
101
81
|
await finishAttempt(ctx, "completed");
|
|
102
82
|
await new BuildModel(deps.db, deps.tables).setStatus(build.id, "approved").catch(() => {});
|
|
103
|
-
await
|
|
104
|
-
}
|
|
105
|
-
async function maybeSkipMerged(ctx) {
|
|
106
|
-
const { deps, build, project } = ctx;
|
|
107
|
-
if (build.isDefault) return false;
|
|
108
|
-
if (await isAlreadyMerged({
|
|
109
|
-
providers: deps.gitHosts,
|
|
110
|
-
sha: build.gitSha,
|
|
111
|
-
branch: build.gitBranch,
|
|
112
|
-
secret: deps.secret,
|
|
113
|
-
db: deps.db,
|
|
114
|
-
tables: deps.tables,
|
|
115
|
-
projectId: project.id,
|
|
116
|
-
logger: deps.logger
|
|
117
|
-
}).catch(() => false)) {
|
|
118
|
-
await approveWithoutCapture(ctx, "skipping capture — already merged");
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
return false;
|
|
83
|
+
await deps.buildState.request(build.id);
|
|
122
84
|
}
|
|
123
85
|
async function maybeSkipDuplicate(ctx) {
|
|
124
86
|
const { deps, build, project } = ctx;
|
|
@@ -128,17 +90,14 @@ async function maybeSkipDuplicate(ctx) {
|
|
|
128
90
|
}
|
|
129
91
|
return false;
|
|
130
92
|
}
|
|
131
|
-
/**
|
|
132
|
-
function
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const { deps, build, project, pendingUrl } = ctx;
|
|
140
|
-
const terminal = terminalState((await new BuildModel(deps.db, deps.tables).get(buildId))?.status);
|
|
141
|
-
if (terminal) await postTerminal(deps, project, pendingUrl, build.gitSha, terminal);
|
|
93
|
+
/** A sharded build's attempt stays open: the worker that finishes the last shard closes it. */
|
|
94
|
+
async function finishUnlessSharded(ctx, summary) {
|
|
95
|
+
if (summary.shards) {
|
|
96
|
+
await emitAttemptLog(ctx.recordLog, ctx.deps.logger, "info", "capture sharded", { shards: summary.shards });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
await finishAttempt(ctx, "completed", void 0, summary);
|
|
100
|
+
await ctx.deps.buildState.request(ctx.build.id);
|
|
142
101
|
}
|
|
143
102
|
async function runAndReport(ctx, job) {
|
|
144
103
|
const { deps, build } = ctx;
|
|
@@ -148,7 +107,7 @@ async function runAndReport(ctx, job) {
|
|
|
148
107
|
attemptNo: ctx.attempt.attemptNo
|
|
149
108
|
});
|
|
150
109
|
try {
|
|
151
|
-
await
|
|
110
|
+
await finishUnlessSharded(ctx, await executeCaptureJob({
|
|
152
111
|
buildId: job.buildId,
|
|
153
112
|
reqId: job.reqId,
|
|
154
113
|
traceparent: job.traceparent,
|
|
@@ -157,38 +116,32 @@ async function runAndReport(ctx, job) {
|
|
|
157
116
|
attemptNo: ctx.attempt.attemptNo,
|
|
158
117
|
projectId: ctx.project.id
|
|
159
118
|
},
|
|
160
|
-
recordLog: ctx.recordLog
|
|
119
|
+
recordLog: ctx.recordLog,
|
|
120
|
+
progress: ctx.progress
|
|
161
121
|
}, deps.jobOptions));
|
|
162
|
-
await reportTerminalStatus(ctx, job.buildId);
|
|
163
122
|
} catch (error) {
|
|
164
|
-
await
|
|
165
|
-
await postStatusesForBuild({
|
|
166
|
-
db: deps.db,
|
|
167
|
-
tables: deps.tables,
|
|
168
|
-
project: ctx.project,
|
|
169
|
-
sha: build.gitSha,
|
|
170
|
-
status: "failure",
|
|
171
|
-
url: ctx.pendingUrl,
|
|
172
|
-
providers: deps.gitHosts,
|
|
173
|
-
secret: deps.secret,
|
|
174
|
-
logger: deps.logger
|
|
175
|
-
}).catch(() => {});
|
|
176
|
-
throw error;
|
|
123
|
+
await reportFailure(ctx, error);
|
|
177
124
|
}
|
|
178
125
|
}
|
|
126
|
+
/** Record a failed run, then rethrow so the job is redelivered, unless another try cannot help. */
|
|
127
|
+
async function reportFailure(ctx, error) {
|
|
128
|
+
const { deps, build } = ctx;
|
|
129
|
+
await finishAttempt(ctx, "failed", errorText(error));
|
|
130
|
+
await deps.buildState.request(build.id);
|
|
131
|
+
if (error instanceof SignedUrlLifetimeError) {
|
|
132
|
+
deps.logger.error({
|
|
133
|
+
buildId: build.id,
|
|
134
|
+
err: error
|
|
135
|
+
}, "capture not retried");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
179
140
|
/** Load the build + project for a job, running orphan jobs directly. */
|
|
180
141
|
async function loadJobTarget(deps, job) {
|
|
181
142
|
const build = await new BuildModel(deps.db, deps.tables).get(job.buildId);
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
buildId: job.buildId,
|
|
185
|
-
reqId: job.reqId,
|
|
186
|
-
traceparent: job.traceparent
|
|
187
|
-
}, deps.jobOptions);
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
const project = await new ProjectModel(deps.db, deps.tables).get(build.projectId);
|
|
191
|
-
if (!project) {
|
|
143
|
+
const project = build ? await new ProjectModel(deps.db, deps.tables).get(build.projectId) : null;
|
|
144
|
+
if (!build || !project) {
|
|
192
145
|
await executeCaptureJob({
|
|
193
146
|
buildId: job.buildId,
|
|
194
147
|
reqId: job.reqId,
|
|
@@ -201,7 +154,45 @@ async function loadJobTarget(deps, job) {
|
|
|
201
154
|
project
|
|
202
155
|
};
|
|
203
156
|
}
|
|
204
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Run one render shard. Only the run that finalized the build closes the plan's attempt and posts
|
|
159
|
+
* the terminal status, whether its shard succeeded or failed; every other run leaves them alone.
|
|
160
|
+
*/
|
|
161
|
+
async function runShardJob(deps, job, lease) {
|
|
162
|
+
const final = (job.attempts ?? 0) >= (deps.maxRetries ?? 0);
|
|
163
|
+
await executeCaptureShard({
|
|
164
|
+
buildId: job.buildId,
|
|
165
|
+
shard: job.shard,
|
|
166
|
+
reqId: job.reqId,
|
|
167
|
+
final,
|
|
168
|
+
progress: leaseProgress(lease),
|
|
169
|
+
onFinalized: async (summary, error) => {
|
|
170
|
+
await closeShardedBuild(deps, job.buildId, summary, error);
|
|
171
|
+
}
|
|
172
|
+
}, deps.jobOptions);
|
|
173
|
+
}
|
|
174
|
+
/** End the plan's attempt with the finalizer's counts and report the build's terminal status. */
|
|
175
|
+
async function closeShardedBuild(deps, buildId, summary, error) {
|
|
176
|
+
if (!await loadClosing(deps, buildId)) return;
|
|
177
|
+
const failure = error === void 0 ? summary.error : errorText(error);
|
|
178
|
+
const failed = failure !== void 0 || summary.buildStatus === "failed";
|
|
179
|
+
const attemptId = summary.attemptId ?? await planAttemptId(deps, buildId);
|
|
180
|
+
if (attemptId) await new CaptureAttemptModel(deps.db, deps.tables).markFinished(attemptId, failed ? "failed" : "completed", failure, summary).catch((closeError) => {
|
|
181
|
+
deps.logger.error({ err: closeError }, "failed to close attempt");
|
|
182
|
+
});
|
|
183
|
+
await deps.buildState.request(buildId);
|
|
184
|
+
}
|
|
185
|
+
async function planAttemptId(deps, buildId) {
|
|
186
|
+
return (await new CaptureShardModel(deps.db).plan(buildId))?.attemptId ?? null;
|
|
187
|
+
}
|
|
188
|
+
async function loadClosing(deps, buildId) {
|
|
189
|
+
const build = await new BuildModel(deps.db, deps.tables).get(buildId);
|
|
190
|
+
const project = build ? await new ProjectModel(deps.db, deps.tables).get(build.projectId) : null;
|
|
191
|
+
return build && project ? {
|
|
192
|
+
build,
|
|
193
|
+
project
|
|
194
|
+
} : null;
|
|
195
|
+
}
|
|
205
196
|
export { createDispatchJob };
|
|
206
197
|
|
|
207
198
|
//# sourceMappingURL=dispatch.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.mjs","names":[],"sources":["../../src/capture/dispatch.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport type { Logger } from \"../logger.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport {\n CaptureAttemptModel,\n emitAttemptLog,\n type AttemptLogRecorder,\n type CaptureAttemptTables,\n} from \"../models/capture-attempt.ts\";\nimport { CaptureLogModel, type CaptureLogTables } from \"../models/capture-log.ts\";\nimport { ProjectModel, type ProjectTables } from \"../models/project.ts\";\nimport type { StatusConfigTables } from \"../models/status-config.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { CaptureAttempt } from \"../schema/capture-attempt.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { executeCaptureJob, type CaptureJobOptions } from \"./orchestrator.ts\";\nimport { hasApprovedBuildForSha, isAlreadyMerged } from \"./skip-checks.ts\";\nimport { postStatusesForBuild } from \"./status-fanout.ts\";\n\n/** Build the queue's job runner: post pending, apply skip rules, capture, report. */\nexport function createDispatchJob(deps: DispatchDeps): (job: CaptureDispatchJob) => Promise<void> {\n return async (job: CaptureDispatchJob): Promise<void> => {\n const target = await loadJobTarget(deps, job);\n if (!target) {\n return;\n }\n const pendingUrl = `/projects/${target.project.slug}/builds/${job.buildId}`;\n await postPending(deps, target.project, pendingUrl, target.build.gitSha);\n const { attempt, recordLog } = await beginAttempt(\n deps,\n target.project,\n target.build,\n job.reqId,\n );\n const ctx: JobContext = {\n deps,\n build: target.build,\n project: target.project,\n pendingUrl,\n attempt,\n recordLog,\n };\n if ((await maybeSkipMerged(ctx)) || (await maybeSkipDuplicate(ctx))) {\n return;\n }\n await runAndReport(ctx, job);\n };\n}\n\n/** Table handles required by dispatch. */\nexport type DispatchTables = BuildTables &\n ProjectTables &\n StatusConfigTables &\n CaptureAttemptTables &\n CaptureLogTables;\n\n/** Dependencies for building the capture queue's job runner. */\nexport interface DispatchDeps {\n db: DatabaseAdapter;\n tables: DispatchTables;\n jobOptions: CaptureJobOptions;\n gitHosts: GitHostProvider[];\n secret: SecretInput;\n logger: Logger;\n}\n\n/** A queued capture job. */\nexport interface CaptureDispatchJob {\n buildId: string;\n reqId?: string;\n /** W3C `traceparent` continuing the enqueueing request's trace. */\n traceparent?: string;\n}\n\ninterface JobContext {\n deps: DispatchDeps;\n build: Build;\n project: Project;\n pendingUrl: string;\n attempt: CaptureAttempt;\n recordLog: AttemptLogRecorder;\n}\n\n/** Open a new attempt row for this run and return its log recorder. */\nasync function beginAttempt(\n deps: DispatchDeps,\n project: Project,\n build: Build,\n reqId: string | undefined,\n): Promise<{ attempt: CaptureAttempt; recordLog: AttemptLogRecorder }> {\n const attempts = new CaptureAttemptModel(deps.db, deps.tables);\n const logs = new CaptureLogModel(deps.db, deps.tables);\n const attempt = await attempts.startAttempt(project.id, build.id, reqId);\n const recordLog: AttemptLogRecorder = async (level, message, fields) => {\n await logs.append(project.id, build.id, attempt.id, level, message, fields);\n };\n await emitAttemptLog(recordLog, deps.logger, \"info\", \"capture queued\", {\n buildId: build.id,\n attemptNo: attempt.attemptNo,\n });\n return { attempt, recordLog };\n}\n\n/** Close the attempt row with its terminal status, counts, and error. */\nasync function finishAttempt(\n ctx: JobContext,\n status: \"completed\" | \"failed\" | \"cancelled\",\n error?: string,\n counts?: { storyCount: number; failedCount: number },\n): Promise<void> {\n const { deps, attempt } = ctx;\n await new CaptureAttemptModel(deps.db, deps.tables).markFinished(\n attempt.id,\n status,\n error,\n counts,\n );\n await emitAttemptLog(\n ctx.recordLog,\n deps.logger,\n status === \"failed\" ? \"error\" : \"info\",\n `capture ${status}`,\n { attemptNo: attempt.attemptNo },\n );\n}\n\n/** Full error text for an attempt row (message plus stack when available). */\nfunction errorText(reason: unknown): string {\n if (reason instanceof Error) {\n return reason.stack ?? reason.message;\n }\n return String(reason);\n}\n\nasync function postPending(\n deps: DispatchDeps,\n project: Project,\n url: string,\n sha: string,\n): Promise<void> {\n await postStatusesForBuild({\n db: deps.db,\n tables: deps.tables,\n project,\n sha,\n status: \"pending\",\n url,\n providers: deps.gitHosts,\n secret: deps.secret,\n logger: deps.logger,\n }).catch((error: unknown) => {\n deps.logger.error({ err: error }, \"failed to post pending status\");\n });\n}\n\nasync function postTerminal(\n deps: DispatchDeps,\n project: Project,\n url: string,\n sha: string,\n status: \"success\" | \"failure\",\n): Promise<void> {\n await postStatusesForBuild({\n db: deps.db,\n tables: deps.tables,\n project,\n sha,\n status,\n url,\n providers: deps.gitHosts,\n secret: deps.secret,\n logger: deps.logger,\n }).catch((error: unknown) => {\n deps.logger.error({ err: error }, \"failed to post terminal status\");\n });\n}\n\nasync function approveWithoutCapture(ctx: JobContext, reason: string): Promise<void> {\n const { deps, build, project, pendingUrl } = ctx;\n deps.logger.info({ buildId: build.id, sha: build.gitSha, branch: build.gitBranch }, reason);\n await emitAttemptLog(ctx.recordLog, deps.logger, \"info\", reason, {\n sha: build.gitSha,\n branch: build.gitBranch,\n });\n await finishAttempt(ctx, \"completed\");\n await new BuildModel(deps.db, deps.tables).setStatus(build.id, \"approved\").catch(() => {}); // Intentionally empty — fire-and-forget\n await postTerminal(deps, project, pendingUrl, build.gitSha, \"success\").catch(() => {}); // Intentionally empty — fire-and-forget\n}\n\nasync function maybeSkipMerged(ctx: JobContext): Promise<boolean> {\n const { deps, build, project } = ctx;\n if (build.isDefault) {\n return false;\n }\n const merged = await isAlreadyMerged({\n providers: deps.gitHosts,\n sha: build.gitSha,\n branch: build.gitBranch,\n secret: deps.secret,\n db: deps.db,\n tables: deps.tables,\n projectId: project.id,\n logger: deps.logger,\n }).catch(() => false);\n if (merged) {\n await approveWithoutCapture(ctx, \"skipping capture — already merged\");\n return true;\n }\n return false;\n}\n\nasync function maybeSkipDuplicate(ctx: JobContext): Promise<boolean> {\n const { deps, build, project } = ctx;\n const dup = await hasApprovedBuildForSha(\n deps.db,\n deps.tables,\n project.id,\n build.gitSha,\n build.id,\n );\n if (dup) {\n await approveWithoutCapture(ctx, \"skipping capture — duplicate sha already approved\");\n return true;\n }\n return false;\n}\n\n/** Map a terminal build status to its check-run state. */\nfunction terminalState(status: string | undefined): \"success\" | \"failure\" | null {\n if (status === \"approved\") {\n return \"success\";\n }\n if (status === \"rejected\" || status === \"failed\") {\n return \"failure\";\n }\n return null;\n}\n\n/** Post the terminal status for a finished capture. */\nasync function reportTerminalStatus(ctx: JobContext, buildId: string): Promise<void> {\n const { deps, build, project, pendingUrl } = ctx;\n const updated = await new BuildModel(deps.db, deps.tables).get(buildId);\n const terminal = terminalState(updated?.status);\n if (terminal) {\n await postTerminal(deps, project, pendingUrl, build.gitSha, terminal);\n }\n}\n\nasync function runAndReport(ctx: JobContext, job: CaptureDispatchJob): Promise<void> {\n const { deps, build } = ctx;\n await new CaptureAttemptModel(deps.db, deps.tables).markRunning(ctx.attempt.id);\n await emitAttemptLog(ctx.recordLog, deps.logger, \"info\", \"capture started\", {\n buildId: build.id,\n attemptNo: ctx.attempt.attemptNo,\n });\n try {\n const summary = await executeCaptureJob(\n {\n buildId: job.buildId,\n reqId: job.reqId,\n traceparent: job.traceparent,\n attempt: {\n id: ctx.attempt.id,\n attemptNo: ctx.attempt.attemptNo,\n projectId: ctx.project.id,\n },\n recordLog: ctx.recordLog,\n },\n deps.jobOptions,\n );\n await finishAttempt(ctx, \"completed\", undefined, summary);\n await reportTerminalStatus(ctx, job.buildId);\n } catch (error: unknown) {\n await finishAttempt(ctx, \"failed\", errorText(error));\n await postStatusesForBuild({\n db: deps.db,\n tables: deps.tables,\n project: ctx.project,\n sha: build.gitSha,\n status: \"failure\",\n url: ctx.pendingUrl,\n providers: deps.gitHosts,\n secret: deps.secret,\n logger: deps.logger,\n }).catch(() => {\n // Ignore: status post failure already logged\n });\n throw error;\n }\n}\n\n/** Load the build + project for a job, running orphan jobs directly. */\nasync function loadJobTarget(\n deps: DispatchDeps,\n job: CaptureDispatchJob,\n): Promise<{ build: Build; project: Project } | null> {\n const builds = new BuildModel(deps.db, deps.tables);\n const build = await builds.get(job.buildId);\n if (!build) {\n await executeCaptureJob(\n { buildId: job.buildId, reqId: job.reqId, traceparent: job.traceparent },\n deps.jobOptions,\n );\n return null;\n }\n const project = await new ProjectModel(deps.db, deps.tables).get(build.projectId);\n if (!project) {\n await executeCaptureJob(\n { buildId: job.buildId, reqId: job.reqId, traceparent: job.traceparent },\n deps.jobOptions,\n );\n return null;\n }\n return { build, project };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dispatch.mjs","names":[],"sources":["../../src/capture/dispatch.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Logger } from \"../logger.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport {\n CaptureAttemptModel,\n emitAttemptLog,\n type AttemptLogRecorder,\n type CaptureAttemptTables,\n} from \"../models/capture-attempt.ts\";\nimport { CaptureLogModel, type CaptureLogTables } from \"../models/capture-log.ts\";\nimport { CaptureShardModel } from \"../models/capture-shard.ts\";\nimport { ProjectModel, type ProjectTables } from \"../models/project.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { CaptureAttempt } from \"../schema/capture-attempt.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { redactSecrets } from \"../utils/redact.ts\";\nimport type { BuildStatePublisher } from \"./build-state/types.ts\";\nimport type { JobLease } from \"./job-lease.ts\";\nimport { executeCaptureJob, type CaptureJobOptions, type CaptureSummary } from \"./orchestrator.ts\";\nimport { leaseProgress, type CaptureProgress } from \"./progress.ts\";\nimport { executeCaptureShard } from \"./shards/execute.ts\";\nimport type { FinalSummary } from \"./shards/reduce.ts\";\nimport { hasApprovedBuildForSha } from \"./skip-checks.ts\";\nimport { SignedUrlLifetimeError } from \"./token-chunks.ts\";\n\n/** Build the queue's job runner: post pending, apply skip rules, capture, report. */\nexport function createDispatchJob(\n deps: DispatchDeps,\n): (job: CaptureDispatchJob, lease?: JobLease) => Promise<void> {\n return async (job: CaptureDispatchJob, lease?: JobLease): Promise<void> => {\n await (job.shard\n ? runShardJob(deps, { ...job, shard: job.shard }, lease)\n : runBuildJob(deps, job, lease));\n };\n}\n\n/** The build's own job: post pending, apply skip rules, capture (or plan), report. */\nasync function runBuildJob(\n deps: DispatchDeps,\n job: CaptureDispatchJob,\n lease?: JobLease,\n): Promise<void> {\n const target = await loadJobTarget(deps, job);\n if (!target) {\n return;\n }\n await deps.buildState.request(job.buildId);\n const { attempt, recordLog } = await beginAttempt(\n deps,\n target.project,\n target.build,\n job.reqId,\n lease,\n );\n const ctx: JobContext = {\n deps,\n build: target.build,\n project: target.project,\n attempt,\n recordLog,\n progress: leaseProgress(lease),\n };\n if (await maybeSkipDuplicate(ctx)) {\n return;\n }\n await runAndReport(ctx, job);\n}\n\n/** Table handles required by dispatch. */\nexport type DispatchTables = BuildTables & ProjectTables & CaptureAttemptTables & CaptureLogTables;\n\n/** Dependencies for building the capture queue's job runner. */\nexport interface DispatchDeps {\n db: DatabaseAdapter;\n tables: DispatchTables;\n jobOptions: CaptureJobOptions;\n /** Shows a build's current state on its git channels; called at every transition. */\n buildState: BuildStatePublisher;\n logger: Logger;\n /** Redeliveries the queue allows a failing job; a shard's last try is its final word. */\n maxRetries?: number | undefined;\n}\n\n/** A queued capture job. */\nexport interface CaptureDispatchJob {\n buildId: string;\n /** A `capture_shards` row to render; absent for the build's own (plan or whole-build) job. */\n shard?: string | undefined;\n /** Times the queue has already delivered this job and seen it fail. */\n attempts?: number | undefined;\n reqId?: string;\n /** W3C `traceparent` continuing the enqueueing request's trace. */\n traceparent?: string;\n}\n\ninterface JobContext {\n deps: DispatchDeps;\n build: Build;\n project: Project;\n attempt: CaptureAttempt;\n recordLog: AttemptLogRecorder;\n progress: CaptureProgress | undefined;\n}\n\n/** Open a new attempt row for this run and return its log recorder. */\nasync function beginAttempt(\n deps: DispatchDeps,\n project: Project,\n build: Build,\n reqId: string | undefined,\n lease?: JobLease,\n): Promise<{ attempt: CaptureAttempt; recordLog: AttemptLogRecorder }> {\n const attempts = new CaptureAttemptModel(deps.db, deps.tables);\n const logs = new CaptureLogModel(deps.db, deps.tables);\n const attempt = await attempts.startAttempt(project.id, build.id, reqId);\n // Every attempt-log line (stages, render progress, runner output) counts as progress.\n const recordLog: AttemptLogRecorder = async (level, message, fields) => {\n lease?.touch();\n await logs.append(project.id, build.id, attempt.id, level, message, fields);\n };\n await emitAttemptLog(recordLog, deps.logger, \"info\", \"capture queued\", {\n buildId: build.id,\n attemptNo: attempt.attemptNo,\n });\n return { attempt, recordLog };\n}\n\n/** Close the attempt row with its terminal status, counts, and error. */\nasync function finishAttempt(\n ctx: JobContext,\n status: \"completed\" | \"failed\" | \"cancelled\",\n error?: string,\n counts?: { storyCount: number; failedCount: number },\n): Promise<void> {\n const { deps, attempt } = ctx;\n await new CaptureAttemptModel(deps.db, deps.tables).markFinished(\n attempt.id,\n status,\n error,\n counts,\n );\n await emitAttemptLog(\n ctx.recordLog,\n deps.logger,\n status === \"failed\" ? \"error\" : \"info\",\n `capture ${status}`,\n { attemptNo: attempt.attemptNo },\n );\n}\n\n/** Full error text for an attempt row (message plus stack when available). */\nfunction errorText(reason: unknown): string {\n // A runner's error can quote the signed Storybook URL it failed to open.\n return redactSecrets(reason instanceof Error ? (reason.stack ?? reason.message) : String(reason));\n}\n\nasync function approveWithoutCapture(ctx: JobContext, reason: string): Promise<void> {\n const { deps, build } = ctx;\n deps.logger.info({ buildId: build.id, sha: build.gitSha, branch: build.gitBranch }, reason);\n await emitAttemptLog(ctx.recordLog, deps.logger, \"info\", reason, {\n sha: build.gitSha,\n branch: build.gitBranch,\n });\n await finishAttempt(ctx, \"completed\");\n await new BuildModel(deps.db, deps.tables).setStatus(build.id, \"approved\").catch(() => {}); // Intentionally empty — fire-and-forget\n await deps.buildState.request(build.id);\n}\n\nasync function maybeSkipDuplicate(ctx: JobContext): Promise<boolean> {\n const { deps, build, project } = ctx;\n const dup = await hasApprovedBuildForSha(\n deps.db,\n deps.tables,\n project.id,\n build.gitSha,\n build.id,\n );\n if (dup) {\n await approveWithoutCapture(ctx, \"skipping capture — duplicate sha already approved\");\n return true;\n }\n return false;\n}\n\n/** A sharded build's attempt stays open: the worker that finishes the last shard closes it. */\nasync function finishUnlessSharded(ctx: JobContext, summary: CaptureSummary): Promise<void> {\n if (summary.shards) {\n await emitAttemptLog(ctx.recordLog, ctx.deps.logger, \"info\", \"capture sharded\", {\n shards: summary.shards,\n });\n return;\n }\n await finishAttempt(ctx, \"completed\", undefined, summary);\n await ctx.deps.buildState.request(ctx.build.id);\n}\n\nasync function runAndReport(ctx: JobContext, job: CaptureDispatchJob): Promise<void> {\n const { deps, build } = ctx;\n await new CaptureAttemptModel(deps.db, deps.tables).markRunning(ctx.attempt.id);\n await emitAttemptLog(ctx.recordLog, deps.logger, \"info\", \"capture started\", {\n buildId: build.id,\n attemptNo: ctx.attempt.attemptNo,\n });\n try {\n const summary = await executeCaptureJob(\n {\n buildId: job.buildId,\n reqId: job.reqId,\n traceparent: job.traceparent,\n attempt: {\n id: ctx.attempt.id,\n attemptNo: ctx.attempt.attemptNo,\n projectId: ctx.project.id,\n },\n recordLog: ctx.recordLog,\n progress: ctx.progress,\n },\n deps.jobOptions,\n );\n await finishUnlessSharded(ctx, summary);\n } catch (error: unknown) {\n await reportFailure(ctx, error);\n }\n}\n\n/** Record a failed run, then rethrow so the job is redelivered, unless another try cannot help. */\nasync function reportFailure(ctx: JobContext, error: unknown): Promise<void> {\n const { deps, build } = ctx;\n await finishAttempt(ctx, \"failed\", errorText(error));\n await deps.buildState.request(build.id);\n if (error instanceof SignedUrlLifetimeError) {\n // A configuration problem: another delivery would fail the same way. The build is failed.\n deps.logger.error({ buildId: build.id, err: error }, \"capture not retried\");\n return;\n }\n throw error;\n}\n\n/** Load the build + project for a job, running orphan jobs directly. */\nasync function loadJobTarget(\n deps: DispatchDeps,\n job: CaptureDispatchJob,\n): Promise<{ build: Build; project: Project } | null> {\n const build = await new BuildModel(deps.db, deps.tables).get(job.buildId);\n const project = build ? await new ProjectModel(deps.db, deps.tables).get(build.projectId) : null;\n if (!build || !project) {\n await executeCaptureJob(\n { buildId: job.buildId, reqId: job.reqId, traceparent: job.traceparent },\n deps.jobOptions,\n );\n return null;\n }\n return { build, project };\n}\n\n/**\n * Run one render shard. Only the run that finalized the build closes the plan's attempt and posts\n * the terminal status, whether its shard succeeded or failed; every other run leaves them alone.\n */\nasync function runShardJob(\n deps: DispatchDeps,\n job: CaptureDispatchJob & { shard: string },\n lease?: JobLease,\n): Promise<void> {\n const final = (job.attempts ?? 0) >= (deps.maxRetries ?? 0);\n await executeCaptureShard(\n {\n buildId: job.buildId,\n shard: job.shard,\n reqId: job.reqId,\n final,\n progress: leaseProgress(lease),\n onFinalized: async (summary, error) => {\n await closeShardedBuild(deps, job.buildId, summary, error);\n },\n },\n deps.jobOptions,\n );\n}\n\n/** End the plan's attempt with the finalizer's counts and report the build's terminal status. */\nasync function closeShardedBuild(\n deps: DispatchDeps,\n buildId: string,\n summary: FinalSummary,\n error?: unknown,\n): Promise<void> {\n const loaded = await loadClosing(deps, buildId);\n if (!loaded) {\n return;\n }\n const failure = error === undefined ? summary.error : errorText(error);\n const failed = failure !== undefined || summary.buildStatus === \"failed\";\n const attemptId = summary.attemptId ?? (await planAttemptId(deps, buildId));\n if (attemptId) {\n await new CaptureAttemptModel(deps.db, deps.tables)\n .markFinished(attemptId, failed ? \"failed\" : \"completed\", failure, summary)\n .catch((closeError: unknown) => {\n deps.logger.error({ err: closeError }, \"failed to close attempt\");\n });\n }\n await deps.buildState.request(buildId);\n}\n\nasync function planAttemptId(deps: DispatchDeps, buildId: string): Promise<string | null> {\n const plan = await new CaptureShardModel(deps.db).plan(buildId);\n return plan?.attemptId ?? null;\n}\n\nasync function loadClosing(\n deps: DispatchDeps,\n buildId: string,\n): Promise<{ build: Build; project: Project } | null> {\n const build = await new BuildModel(deps.db, deps.tables).get(buildId);\n const project = build ? await new ProjectModel(deps.db, deps.tables).get(build.projectId) : null;\n return build && project ? { build, project } : null;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { filterModes } from "./modes.mjs";
|
|
5
|
+
import { renderBudgetMs } from "./render.mjs";
|
|
6
|
+
import { resolveStoryViewports } from "./storybook.mjs";
|
|
7
|
+
// src/capture/enrich.ts
|
|
8
|
+
/**
|
|
9
|
+
* Merge runtime-resolved parameters (project/component/story levels stacked by
|
|
10
|
+
* Storybook) over the index's, then apply the project's mode allowlist. A
|
|
11
|
+
* runner without `extractParameters`, or one that fails, leaves index
|
|
12
|
+
* parameters untouched — the build still captures plain viewports.
|
|
13
|
+
*/
|
|
14
|
+
async function enrichStories(input) {
|
|
15
|
+
const extracted = await extractSafely(input);
|
|
16
|
+
return input.stories.map((story) => {
|
|
17
|
+
const parameters = {
|
|
18
|
+
...story.parameters,
|
|
19
|
+
...extracted[story.id]
|
|
20
|
+
};
|
|
21
|
+
const modes = filterModes(parameters.modes, input.allowlist);
|
|
22
|
+
if (modes) parameters.modes = modes;
|
|
23
|
+
return Object.keys(parameters).length > 0 ? {
|
|
24
|
+
...story,
|
|
25
|
+
parameters
|
|
26
|
+
} : story;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Fail the build before any rendering when a story expands beyond the cap
|
|
31
|
+
* (global viewports + default viewport + modes).
|
|
32
|
+
*/
|
|
33
|
+
function assertModeCaps(stories, viewports) {
|
|
34
|
+
for (const story of stories) {
|
|
35
|
+
const count = resolveStoryViewports(story, viewports).length;
|
|
36
|
+
if (count > 8) throw new Error(`Story "${story.id}" expands to ${count} captures (max 8); reduce its modes or the project viewports`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function extractSafely(input) {
|
|
40
|
+
const { runner } = input;
|
|
41
|
+
if (!runner.extractParameters) return {};
|
|
42
|
+
try {
|
|
43
|
+
const storybookUrl = await input.storybookUrl?.(input.target, { budgetMs: renderBudgetMs(input.stories.length) });
|
|
44
|
+
return await runner.extractParameters({
|
|
45
|
+
buildId: input.buildId,
|
|
46
|
+
storybookDir: input.storybookDir,
|
|
47
|
+
stories: input.stories,
|
|
48
|
+
target: input.target,
|
|
49
|
+
...storybookUrl === void 0 ? {} : { storybookUrl },
|
|
50
|
+
...input.logger ? { logger: input.logger } : {}
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
input.logger?.warn({ err: error }, "parameter extraction failed; modes from index only");
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export { assertModeCaps, enrichStories };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=enrich.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich.mjs","names":[],"sources":["../../src/capture/enrich.ts"],"sourcesContent":["/** Orchestrator pre-pass: learn each story's modes before building the capture matrix. */\nimport type { Logger } from \"pino\";\nimport type { CaptureTarget, CaptureRunner } from \"../adapters/capture-runner.ts\";\nimport type { StoryEntry, Viewport } from \"./adapter.ts\";\nimport { filterModes, MAX_MODES_PER_STORY } from \"./modes.ts\";\nimport { renderBudgetMs } from \"./render.ts\";\nimport type { StorybookUrlSource } from \"./storybook-urls.ts\";\nimport { resolveStoryViewports } from \"./storybook.ts\";\n\n/** Inputs for {@link enrichStories}. */\nexport interface EnrichInput {\n runner: CaptureRunner;\n buildId: string;\n storybookDir: string;\n stories: StoryEntry[];\n target: CaptureTarget;\n allowlist: readonly string[] | null;\n logger: Logger | undefined;\n /** Mints the signed URL for a `reach: \"url\"` target. */\n storybookUrl?: StorybookUrlSource | undefined;\n}\n\n/**\n * Merge runtime-resolved parameters (project/component/story levels stacked by\n * Storybook) over the index's, then apply the project's mode allowlist. A\n * runner without `extractParameters`, or one that fails, leaves index\n * parameters untouched — the build still captures plain viewports.\n */\nexport async function enrichStories(input: EnrichInput): Promise<StoryEntry[]> {\n const extracted = await extractSafely(input);\n return input.stories.map((story) => {\n const parameters = { ...story.parameters, ...extracted[story.id] };\n const modes = filterModes(parameters.modes, input.allowlist);\n if (modes) parameters.modes = modes;\n return Object.keys(parameters).length > 0 ? { ...story, parameters } : story;\n });\n}\n\n/**\n * Fail the build before any rendering when a story expands beyond the cap\n * (global viewports + default viewport + modes).\n */\nexport function assertModeCaps(stories: StoryEntry[], viewports: Viewport[]): void {\n for (const story of stories) {\n const count = resolveStoryViewports(story, viewports).length;\n if (count > MAX_MODES_PER_STORY) {\n throw new Error(\n `Story \"${story.id}\" expands to ${count} captures (max ${MAX_MODES_PER_STORY}); reduce its modes or the project viewports`,\n );\n }\n }\n}\n\nasync function extractSafely(\n input: EnrichInput,\n): ReturnType<NonNullable<CaptureRunner[\"extractParameters\"]>> {\n const { runner } = input;\n if (!runner.extractParameters) return {};\n try {\n const storybookUrl = await input.storybookUrl?.(input.target, {\n budgetMs: renderBudgetMs(input.stories.length),\n });\n return await runner.extractParameters({\n buildId: input.buildId,\n storybookDir: input.storybookDir,\n stories: input.stories,\n target: input.target,\n ...(storybookUrl === undefined ? {} : { storybookUrl }),\n ...(input.logger ? { logger: input.logger } : {}),\n });\n } catch (error) {\n input.logger?.warn({ err: error }, \"parameter extraction failed; modes from index only\");\n return {};\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA4BA,eAAsB,cAAc,OAA2C;CAC7E,MAAM,YAAY,MAAM,cAAc,KAAK;CAC3C,OAAO,MAAM,QAAQ,KAAK,UAAU;EAClC,MAAM,aAAa;GAAE,GAAG,MAAM;GAAY,GAAG,UAAU,MAAM;EAAI;EACjE,MAAM,QAAQ,YAAY,WAAW,OAAO,MAAM,SAAS;EAC3D,IAAI,OAAO,WAAW,QAAQ;EAC9B,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS,IAAI;GAAE,GAAG;GAAO;EAAW,IAAI;CACzE,CAAC;AACH;;;;;AAMA,SAAgB,eAAe,SAAuB,WAA6B;CACjF,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,QAAQ,sBAAsB,OAAO,SAAS,CAAC,CAAC;EACtD,IAAI,QAAA,GACF,MAAM,IAAI,MACR,UAAU,MAAM,GAAG,eAAe,MAAM,6DAC1C;CAEJ;AACF;AAEA,eAAe,cACb,OAC6D;CAC7D,MAAM,EAAE,WAAW;CACnB,IAAI,CAAC,OAAO,mBAAmB,OAAO,CAAC;CACvC,IAAI;EACF,MAAM,eAAe,MAAM,MAAM,eAAe,MAAM,QAAQ,EAC5D,UAAU,eAAe,MAAM,QAAQ,MAAM,EAC/C,CAAC;EACD,OAAO,MAAM,OAAO,kBAAkB;GACpC,SAAS,MAAM;GACf,cAAc,MAAM;GACpB,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,GAAI,iBAAiB,KAAA,IAAY,CAAC,IAAI,EAAE,aAAa;GACrD,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;EACjD,CAAC;CACH,SAAS,OAAO;EACd,MAAM,QAAQ,KAAK,EAAE,KAAK,MAAM,GAAG,oDAAoD;EACvF,OAAO,CAAC;CACV;AACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { isFlakyStory } from "./adapter.mjs";
|
|
5
|
+
// src/capture/failure-sets.ts
|
|
6
|
+
/** Classify render failures: `a11y:` errors, then flaky stories, then everything else is blocking. */
|
|
7
|
+
function classifyFailures(stories, failures) {
|
|
8
|
+
const flakyIds = new Set(stories.filter((story) => isFlakyStory(story)).map((story) => story.id));
|
|
9
|
+
const sets = {
|
|
10
|
+
blocking: /* @__PURE__ */ new Set(),
|
|
11
|
+
flaky: /* @__PURE__ */ new Set(),
|
|
12
|
+
a11y: /* @__PURE__ */ new Set()
|
|
13
|
+
};
|
|
14
|
+
for (const failure of failures) if (failure.error.startsWith("a11y:")) sets.a11y.add(failure.storyId);
|
|
15
|
+
else if (flakyIds.has(failure.storyId)) sets.flaky.add(failure.storyId);
|
|
16
|
+
else sets.blocking.add(failure.storyId);
|
|
17
|
+
return sets;
|
|
18
|
+
}
|
|
19
|
+
export { classifyFailures };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=failure-sets.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure-sets.mjs","names":[],"sources":["../../src/capture/failure-sets.ts"],"sourcesContent":["/** Sorting a render's failures into the three kinds that decide a build's status. */\nimport type { RenderFailure } from \"../adapters/capture-runner.ts\";\nimport { isFlakyStory, type StoryEntry } from \"./adapter.ts\";\n\n/** Story ids by how their failure counts. */\nexport interface FailureSets {\n /** Fail the build. */\n blocking: Set<string>;\n /** Flaky stories: logged, never blocking. */\n flaky: Set<string>;\n /** Accessibility violations: logged, never blocking. */\n a11y: Set<string>;\n}\n\n/** Classify render failures: `a11y:` errors, then flaky stories, then everything else is blocking. */\nexport function classifyFailures(\n stories: readonly StoryEntry[],\n failures: readonly RenderFailure[],\n): FailureSets {\n const flakyIds = new Set(stories.filter((story) => isFlakyStory(story)).map((story) => story.id));\n const sets: FailureSets = { blocking: new Set(), flaky: new Set(), a11y: new Set() };\n for (const failure of failures) {\n if (failure.error.startsWith(\"a11y:\")) {\n sets.a11y.add(failure.storyId);\n } else if (flakyIds.has(failure.storyId)) {\n sets.flaky.add(failure.storyId);\n } else {\n sets.blocking.add(failure.storyId);\n }\n }\n return sets;\n}\n"],"mappings":";;;;;;AAeA,SAAgB,iBACd,SACA,UACa;CACb,MAAM,WAAW,IAAI,IAAI,QAAQ,QAAQ,UAAU,aAAa,KAAK,CAAC,CAAC,CAAC,KAAK,UAAU,MAAM,EAAE,CAAC;CAChG,MAAM,OAAoB;EAAE,0BAAU,IAAI,IAAI;EAAG,uBAAO,IAAI,IAAI;EAAG,sBAAM,IAAI,IAAI;CAAE;CACnF,KAAK,MAAM,WAAW,UACpB,IAAI,QAAQ,MAAM,WAAW,OAAO,GAClC,KAAK,KAAK,IAAI,QAAQ,OAAO;MACxB,IAAI,SAAS,IAAI,QAAQ,OAAO,GACrC,KAAK,MAAM,IAAI,QAAQ,OAAO;MAE9B,KAAK,SAAS,IAAI,QAAQ,OAAO;CAGrC,OAAO;AACT"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { Viewport } from "./adapter.mjs";
|
|
4
|
+
import { CaptureEnvironment } from "../adapter/capture-runner.mjs";
|
|
5
|
+
import { SIZING_DEFAULTS } from "./sizing.mjs";
|
|
6
|
+
/** The components behind a snapshot's `infraHash`, kept so a mismatch can say what changed. */
|
|
7
|
+
interface Fingerprint {
|
|
8
|
+
engine: string;
|
|
9
|
+
browserVersion: string | null;
|
|
10
|
+
platform: string | null;
|
|
11
|
+
deviceScaleFactor: number | null;
|
|
12
|
+
fontsHash: string | null;
|
|
13
|
+
viewport: {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
sizing: typeof SIZING_DEFAULTS;
|
|
18
|
+
}
|
|
19
|
+
/** Build the fingerprint of one capture. Missing environment fields stay `null` (runner did not report them). */
|
|
20
|
+
declare function buildFingerprint(engine: string, viewport: Viewport, environment: CaptureEnvironment | undefined): Fingerprint;
|
|
21
|
+
/** Stable hash of a fingerprint: the value stored as `infraHash`. */
|
|
22
|
+
declare function fingerprintHash(fingerprint: Fingerprint): string;
|
|
23
|
+
/**
|
|
24
|
+
* Names of the environment components that differ; `["unknown"]` when the earlier fingerprint was
|
|
25
|
+
* not recorded. A component one side did not report (`null`: a runner that does not report it, a
|
|
26
|
+
* capture spooled before a browser crash, a failed `fc-list`) is unknown, not changed. Viewport is
|
|
27
|
+
* never a change here: its size comes from story parameters (the PR's own code), so a new size must
|
|
28
|
+
* be reviewed, and it diffs on its own as a dimension change.
|
|
29
|
+
*/
|
|
30
|
+
declare function changedFields(from: Fingerprint | null, to: Fingerprint): string[];
|
|
31
|
+
/** Parse a stored fingerprint; null for a missing or malformed value. */
|
|
32
|
+
declare function parseFingerprint(raw: string | null | undefined): Fingerprint | null;
|
|
33
|
+
/** What a migrated snapshot records: which components changed, and the before and after. */
|
|
34
|
+
interface EnvironmentChange {
|
|
35
|
+
changed: string[];
|
|
36
|
+
from: Fingerprint | null;
|
|
37
|
+
to: Fingerprint;
|
|
38
|
+
}
|
|
39
|
+
export { EnvironmentChange, Fingerprint, buildFingerprint, changedFields, fingerprintHash, parseFingerprint };
|
|
40
|
+
//# sourceMappingURL=fingerprint.d.mts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { sha256 } from "../utils/hash.mjs";
|
|
5
|
+
import { SIZING_DEFAULTS } from "./sizing.mjs";
|
|
6
|
+
// src/capture/fingerprint.ts
|
|
7
|
+
/** Build the fingerprint of one capture. Missing environment fields stay `null` (runner did not report them). */
|
|
8
|
+
function buildFingerprint(engine, viewport, environment) {
|
|
9
|
+
return {
|
|
10
|
+
engine,
|
|
11
|
+
browserVersion: environment?.browserVersion ?? null,
|
|
12
|
+
platform: environment?.platform ?? null,
|
|
13
|
+
deviceScaleFactor: environment?.deviceScaleFactor ?? null,
|
|
14
|
+
fontsHash: environment?.fontsHash ?? null,
|
|
15
|
+
viewport: {
|
|
16
|
+
width: viewport.width,
|
|
17
|
+
height: viewport.height
|
|
18
|
+
},
|
|
19
|
+
sizing: SIZING_DEFAULTS
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Stable hash of a fingerprint: the value stored as `infraHash`. */
|
|
23
|
+
function fingerprintHash(fingerprint) {
|
|
24
|
+
return sha256(JSON.stringify([
|
|
25
|
+
fingerprint.engine,
|
|
26
|
+
fingerprint.browserVersion,
|
|
27
|
+
fingerprint.platform,
|
|
28
|
+
fingerprint.deviceScaleFactor,
|
|
29
|
+
fingerprint.fontsHash,
|
|
30
|
+
fingerprint.viewport.width,
|
|
31
|
+
fingerprint.viewport.height,
|
|
32
|
+
fingerprint.sizing
|
|
33
|
+
]));
|
|
34
|
+
}
|
|
35
|
+
/** The components whose change is an environment migration. Viewport is not one (see below). */
|
|
36
|
+
const ENVIRONMENT_FIELDS = [
|
|
37
|
+
"engine",
|
|
38
|
+
"browserVersion",
|
|
39
|
+
"platform",
|
|
40
|
+
"deviceScaleFactor",
|
|
41
|
+
"fontsHash"
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Names of the environment components that differ; `["unknown"]` when the earlier fingerprint was
|
|
45
|
+
* not recorded. A component one side did not report (`null`: a runner that does not report it, a
|
|
46
|
+
* capture spooled before a browser crash, a failed `fc-list`) is unknown, not changed. Viewport is
|
|
47
|
+
* never a change here: its size comes from story parameters (the PR's own code), so a new size must
|
|
48
|
+
* be reviewed, and it diffs on its own as a dimension change.
|
|
49
|
+
*/
|
|
50
|
+
function changedFields(from, to) {
|
|
51
|
+
if (!from) return ["unknown"];
|
|
52
|
+
return [...ENVIRONMENT_FIELDS.filter((key) => known(from[key]) && known(to[key]) && from[key] !== to[key]), ...JSON.stringify(from.sizing) === JSON.stringify(to.sizing) ? [] : ["sizing"]];
|
|
53
|
+
}
|
|
54
|
+
function known(value) {
|
|
55
|
+
return value !== null && value !== void 0;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The fingerprint to store on a baseline: the capture's, with any component it did not report kept
|
|
59
|
+
* from the baseline's earlier fingerprint, so an unknown component never overwrites a known one.
|
|
60
|
+
*/
|
|
61
|
+
function mergeFingerprint(earlier, now) {
|
|
62
|
+
return {
|
|
63
|
+
...now,
|
|
64
|
+
browserVersion: now.browserVersion ?? earlier?.browserVersion ?? null,
|
|
65
|
+
platform: now.platform ?? earlier?.platform ?? null,
|
|
66
|
+
deviceScaleFactor: now.deviceScaleFactor ?? earlier?.deviceScaleFactor ?? null,
|
|
67
|
+
fontsHash: now.fontsHash ?? earlier?.fontsHash ?? null
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Parse a stored fingerprint; null for a missing or malformed value. */
|
|
71
|
+
function parseFingerprint(raw) {
|
|
72
|
+
if (!raw) return null;
|
|
73
|
+
try {
|
|
74
|
+
const value = JSON.parse(raw);
|
|
75
|
+
return typeof value.engine === "string" && value.viewport ? value : null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export { buildFingerprint, changedFields, fingerprintHash, mergeFingerprint, parseFingerprint };
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=fingerprint.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.mjs","names":[],"sources":["../../src/capture/fingerprint.ts"],"sourcesContent":["/** Capture fingerprint: everything that changes pixels without a code change (ADR 0005 phase 4). */\nimport type { CaptureEnvironment } from \"../adapters/capture-runner.ts\";\nimport { sha256 } from \"../utils/hash.ts\";\nimport type { Viewport } from \"./adapter.ts\";\nimport { SIZING_DEFAULTS } from \"./sizing.ts\";\n\n/** The components behind a snapshot's `infraHash`, kept so a mismatch can say what changed. */\nexport interface Fingerprint {\n engine: string;\n browserVersion: string | null;\n platform: string | null;\n deviceScaleFactor: number | null;\n fontsHash: string | null;\n viewport: { width: number; height: number };\n sizing: typeof SIZING_DEFAULTS;\n}\n\n/** Build the fingerprint of one capture. Missing environment fields stay `null` (runner did not report them). */\nexport function buildFingerprint(\n engine: string,\n viewport: Viewport,\n environment: CaptureEnvironment | undefined,\n): Fingerprint {\n return {\n engine,\n browserVersion: environment?.browserVersion ?? null,\n platform: environment?.platform ?? null,\n deviceScaleFactor: environment?.deviceScaleFactor ?? null,\n fontsHash: environment?.fontsHash ?? null,\n viewport: { width: viewport.width, height: viewport.height },\n sizing: SIZING_DEFAULTS,\n };\n}\n\n/** Stable hash of a fingerprint: the value stored as `infraHash`. */\nexport function fingerprintHash(fingerprint: Fingerprint): string {\n return sha256(\n JSON.stringify([\n fingerprint.engine,\n fingerprint.browserVersion,\n fingerprint.platform,\n fingerprint.deviceScaleFactor,\n fingerprint.fontsHash,\n fingerprint.viewport.width,\n fingerprint.viewport.height,\n fingerprint.sizing,\n ]),\n );\n}\n\n/** The components whose change is an environment migration. Viewport is not one (see below). */\nconst ENVIRONMENT_FIELDS = [\n \"engine\",\n \"browserVersion\",\n \"platform\",\n \"deviceScaleFactor\",\n \"fontsHash\",\n] as const;\n\n/**\n * Names of the environment components that differ; `[\"unknown\"]` when the earlier fingerprint was\n * not recorded. A component one side did not report (`null`: a runner that does not report it, a\n * capture spooled before a browser crash, a failed `fc-list`) is unknown, not changed. Viewport is\n * never a change here: its size comes from story parameters (the PR's own code), so a new size must\n * be reviewed, and it diffs on its own as a dimension change.\n */\nexport function changedFields(from: Fingerprint | null, to: Fingerprint): string[] {\n if (!from) {\n return [\"unknown\"];\n }\n return [\n ...ENVIRONMENT_FIELDS.filter(\n (key) => known(from[key]) && known(to[key]) && from[key] !== to[key],\n ),\n ...(JSON.stringify(from.sizing) === JSON.stringify(to.sizing) ? [] : [\"sizing\"]),\n ];\n}\n\nfunction known(value: unknown): boolean {\n return value !== null && value !== undefined;\n}\n\n/**\n * The fingerprint to store on a baseline: the capture's, with any component it did not report kept\n * from the baseline's earlier fingerprint, so an unknown component never overwrites a known one.\n */\nexport function mergeFingerprint(earlier: Fingerprint | null, now: Fingerprint): Fingerprint {\n return {\n ...now,\n browserVersion: now.browserVersion ?? earlier?.browserVersion ?? null,\n platform: now.platform ?? earlier?.platform ?? null,\n deviceScaleFactor: now.deviceScaleFactor ?? earlier?.deviceScaleFactor ?? null,\n fontsHash: now.fontsHash ?? earlier?.fontsHash ?? null,\n };\n}\n\n/** Parse a stored fingerprint; null for a missing or malformed value. */\nexport function parseFingerprint(raw: string | null | undefined): Fingerprint | null {\n if (!raw) {\n return null;\n }\n try {\n const value = JSON.parse(raw) as Partial<Fingerprint>;\n return typeof value.engine === \"string\" && value.viewport ? (value as Fingerprint) : null;\n } catch {\n return null;\n }\n}\n\n/** What a migrated snapshot records: which components changed, and the before and after. */\nexport interface EnvironmentChange {\n changed: string[];\n from: Fingerprint | null;\n to: Fingerprint;\n}\n"],"mappings":";;;;;;;AAkBA,SAAgB,iBACd,QACA,UACA,aACa;CACb,OAAO;EACL;EACA,gBAAgB,aAAa,kBAAkB;EAC/C,UAAU,aAAa,YAAY;EACnC,mBAAmB,aAAa,qBAAqB;EACrD,WAAW,aAAa,aAAa;EACrC,UAAU;GAAE,OAAO,SAAS;GAAO,QAAQ,SAAS;EAAO;EAC3D,QAAQ;CACV;AACF;;AAGA,SAAgB,gBAAgB,aAAkC;CAChE,OAAO,OACL,KAAK,UAAU;EACb,YAAY;EACZ,YAAY;EACZ,YAAY;EACZ,YAAY;EACZ,YAAY;EACZ,YAAY,SAAS;EACrB,YAAY,SAAS;EACrB,YAAY;CACd,CAAC,CACH;AACF;;AAGA,MAAM,qBAAqB;CACzB;CACA;CACA;CACA;CACA;AACF;;;;;;;;AASA,SAAgB,cAAc,MAA0B,IAA2B;CACjF,IAAI,CAAC,MACH,OAAO,CAAC,SAAS;CAEnB,OAAO,CACL,GAAG,mBAAmB,QACnB,QAAQ,MAAM,KAAK,IAAI,KAAK,MAAM,GAAG,IAAI,KAAK,KAAK,SAAS,GAAG,IAClE,GACA,GAAI,KAAK,UAAU,KAAK,MAAM,MAAM,KAAK,UAAU,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAChF;AACF;AAEA,SAAS,MAAM,OAAyB;CACtC,OAAO,UAAU,QAAQ,UAAU,KAAA;AACrC;;;;;AAMA,SAAgB,iBAAiB,SAA6B,KAA+B;CAC3F,OAAO;EACL,GAAG;EACH,gBAAgB,IAAI,kBAAkB,SAAS,kBAAkB;EACjE,UAAU,IAAI,YAAY,SAAS,YAAY;EAC/C,mBAAmB,IAAI,qBAAqB,SAAS,qBAAqB;EAC1E,WAAW,IAAI,aAAa,SAAS,aAAa;CACpD;AACF;;AAGA,SAAgB,iBAAiB,KAAoD;CACnF,IAAI,CAAC,KACH,OAAO;CAET,IAAI;EACF,MAAM,QAAQ,KAAK,MAAM,GAAG;EAC5B,OAAO,OAAO,MAAM,WAAW,YAAY,MAAM,WAAY,QAAwB;CACvF,QAAQ;EACN,OAAO;CACT;AACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { ChildMessage, ParentMessage } from "./protocol.mjs";
|
|
4
|
+
/** How the parent sees the capture child (`child_process.fork` adapts to this). */
|
|
5
|
+
interface ChildProcessHandle {
|
|
6
|
+
send(message: ParentMessage): void;
|
|
7
|
+
onMessage(listener: (message: unknown) => void): void;
|
|
8
|
+
/** Fires once when the child ends; `code` is null when it was killed by a signal. */
|
|
9
|
+
onExit(listener: (info: {
|
|
10
|
+
code: number | null;
|
|
11
|
+
signal: string | null;
|
|
12
|
+
}) => void): void;
|
|
13
|
+
/** Stop the child: SIGTERM, escalating to SIGKILL; `force` goes straight to SIGKILL (a hung child ignores SIGTERM). */
|
|
14
|
+
kill(force?: boolean): void;
|
|
15
|
+
}
|
|
16
|
+
/** How the child sees its parent (`process.send` / `process.on("message")` adapt to this). */
|
|
17
|
+
interface ParentPort {
|
|
18
|
+
send(message: ChildMessage): void;
|
|
19
|
+
onMessage(listener: (message: unknown) => void): void;
|
|
20
|
+
onDisconnect(listener: () => void): void;
|
|
21
|
+
}
|
|
22
|
+
export { ChildProcessHandle, ParentPort };
|
|
23
|
+
//# sourceMappingURL=channel.d.mts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { Logger } from "../../logger.mjs";
|
|
4
|
+
import { CaptureHandlerOptions } from "../consumer/capture-handler.mjs";
|
|
5
|
+
import { CaptureConsumerOptions } from "../consumer/types.mjs";
|
|
6
|
+
import { ChildMessage } from "./protocol.mjs";
|
|
7
|
+
/** Handler options minus the shard hand-out, which the child wires to the database queue. */
|
|
8
|
+
type CaptureChildOptions = Omit<CaptureHandlerOptions, "enqueue" | "logger"> & Pick<CaptureConsumerOptions, "images" | "compare"> & {
|
|
9
|
+
logger?: Logger;
|
|
10
|
+
/** Retries stamped on shard jobs (default: the parent's `testing.retries`, handed down in the environment). */
|
|
11
|
+
retries?: number;
|
|
12
|
+
};
|
|
13
|
+
/** The slice of `process` the child uses; injectable so the lifecycle is testable. */
|
|
14
|
+
interface ChildHost {
|
|
15
|
+
/** Present only in a forked process with an IPC channel. */
|
|
16
|
+
send?: (message: ChildMessage) => unknown;
|
|
17
|
+
on(event: "message" | "disconnect" | "SIGTERM" | "SIGINT", listener: (message?: unknown) => void): unknown;
|
|
18
|
+
env: Record<string, string | undefined>;
|
|
19
|
+
exit(code: number): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run pushed jobs until the parent stops the child.
|
|
23
|
+
*
|
|
24
|
+
* - `SIGTERM` / `SIGINT`: let the runs in flight finish, then exit 0.
|
|
25
|
+
* - Parent gone (`disconnect`): nobody can receive results, so exit at once. Whatever was running
|
|
26
|
+
* loses its lease with the parent and is redelivered to another consumer.
|
|
27
|
+
*
|
|
28
|
+
* @throws When not running as a forked child (no IPC channel), or when the image engine cannot load.
|
|
29
|
+
*/
|
|
30
|
+
declare function runCaptureChild(options: CaptureChildOptions, host?: ChildHost): Promise<void>;
|
|
31
|
+
export { CaptureChildOptions, ChildHost, runCaptureChild };
|
|
32
|
+
//# sourceMappingURL=child.d.mts.map
|