@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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { BuildModel } from "../../models/build.mjs";
|
|
5
|
+
import { finalizeBuild } from "../pipeline.mjs";
|
|
6
|
+
import { CaptureShardModel } from "../../models/capture-shard.mjs";
|
|
7
|
+
import { contextOf } from "./context.mjs";
|
|
8
|
+
// src/capture/shards/reduce.ts
|
|
9
|
+
/** The reduce stage: when the last render shard ends, exactly one worker finishes the build. */
|
|
10
|
+
/**
|
|
11
|
+
* When every render shard has ended, claim the finalize (exactly one worker can) and finish the
|
|
12
|
+
* build from the plan and the shards' outcomes. Returns the summary for the winner, else null.
|
|
13
|
+
*
|
|
14
|
+
* A finalize that throws gives its claim back, so the next run to reduce the build (a redelivery
|
|
15
|
+
* of the same shard) finalizes it instead of finding the claim taken. On a last try (`final`)
|
|
16
|
+
* nothing will come back for it, so the build is failed and the failure reported as the summary.
|
|
17
|
+
*/
|
|
18
|
+
async function reduceBuild(target, final = true) {
|
|
19
|
+
const shards = new CaptureShardModel(target.options.db);
|
|
20
|
+
const rows = await closedShards(shards, target.build.id);
|
|
21
|
+
if (!rows || !await shards.claimFinalize(target.project.id, target.build.id)) return null;
|
|
22
|
+
return await finalizeClaimed(target, shards, rows, final);
|
|
23
|
+
}
|
|
24
|
+
/** Finalize under the claim this run holds; on failure, give the claim back. */
|
|
25
|
+
async function finalizeClaimed(target, shards, rows, final) {
|
|
26
|
+
const plan = await shards.plan(target.build.id);
|
|
27
|
+
try {
|
|
28
|
+
const summary = await finalizeSharded(target, rows, plan);
|
|
29
|
+
await shards.finishFinalize(target.build.id);
|
|
30
|
+
return summary;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
await shards.releaseFinalize(target.build.id);
|
|
33
|
+
if (!final) throw error;
|
|
34
|
+
return await failFinalize(target, plan, error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** The build's render shards once every one has ended, else null (none, or some still open). */
|
|
38
|
+
async function closedShards(shards, buildId) {
|
|
39
|
+
const rows = await shards.renderShards(buildId);
|
|
40
|
+
const open = rows.some((row) => row.status === "pending" || row.status === "running");
|
|
41
|
+
return rows.length === 0 || open ? null : rows;
|
|
42
|
+
}
|
|
43
|
+
async function finalizeSharded(target, rows, plan) {
|
|
44
|
+
const planResult = planResultOf(plan);
|
|
45
|
+
const sets = collectSets(rows, planResult);
|
|
46
|
+
await finalizeBuild(contextOf(target, {
|
|
47
|
+
viewports: [],
|
|
48
|
+
browsers: []
|
|
49
|
+
}), new Set(planResult.storyIds ?? []), sets.failed, sets.flaky, sets.a11y);
|
|
50
|
+
const finished = await new BuildModel(target.options.db, target.options.tables).get(target.build.id);
|
|
51
|
+
return {
|
|
52
|
+
storyCount: planResult.storyIds?.length ?? 0,
|
|
53
|
+
failedCount: sets.failed.size,
|
|
54
|
+
buildStatus: finished?.status ?? "failed",
|
|
55
|
+
attemptId: plan?.attemptId ?? null
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** The last try at finalizing failed: fail the build, so it does not sit in `capturing`. */
|
|
59
|
+
async function failFinalize(target, plan, error) {
|
|
60
|
+
await new BuildModel(target.options.db, target.options.tables).setStatus(target.build.id, "failed");
|
|
61
|
+
return {
|
|
62
|
+
storyCount: planResultOf(plan).storyIds?.length ?? 0,
|
|
63
|
+
failedCount: 0,
|
|
64
|
+
buildStatus: "failed",
|
|
65
|
+
attemptId: plan?.attemptId ?? null,
|
|
66
|
+
error: error instanceof Error ? error.message : String(error)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function planResultOf(plan) {
|
|
70
|
+
return JSON.parse(plan?.result ?? "{}");
|
|
71
|
+
}
|
|
72
|
+
/** Union the plan's and every shard's failures; a shard that ended `failed` fails all its stories. */
|
|
73
|
+
function collectSets(rows, plan) {
|
|
74
|
+
const union = {
|
|
75
|
+
failed: new Set(plan.failedStoryIds ?? []),
|
|
76
|
+
flaky: /* @__PURE__ */ new Set(),
|
|
77
|
+
a11y: /* @__PURE__ */ new Set()
|
|
78
|
+
};
|
|
79
|
+
for (const row of rows) if (row.status === "failed") addAll(union.failed, storyIdsOf(row));
|
|
80
|
+
else addShardResult(union, JSON.parse(row.result ?? "{}"));
|
|
81
|
+
return union;
|
|
82
|
+
}
|
|
83
|
+
function addShardResult(union, result) {
|
|
84
|
+
addAll(union.failed, result.failedStoryIds ?? []);
|
|
85
|
+
addAll(union.flaky, result.flakyStoryIds ?? []);
|
|
86
|
+
addAll(union.a11y, result.a11yStoryIds ?? []);
|
|
87
|
+
}
|
|
88
|
+
function addAll(into, ids) {
|
|
89
|
+
for (const id of ids) into.add(id);
|
|
90
|
+
}
|
|
91
|
+
function storyIdsOf(row) {
|
|
92
|
+
return (JSON.parse(row.payload ?? "{}").browsers ?? []).flatMap((engine) => engine.stories.map((story) => story.id));
|
|
93
|
+
}
|
|
94
|
+
export { reduceBuild };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=reduce.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reduce.mjs","names":[],"sources":["../../../src/capture/shards/reduce.ts"],"sourcesContent":["/** The reduce stage: when the last render shard ends, exactly one worker finishes the build. */\nimport { BuildModel } from \"../../models/build.ts\";\nimport { CaptureShardModel } from \"../../models/capture-shard.ts\";\nimport type { CaptureShard } from \"../../schema/capture-shard.ts\";\nimport type { BuildStatus } from \"../../types.ts\";\nimport { finalizeBuild } from \"../pipeline.ts\";\nimport type { PlanResult, ShardPayload, ShardResult } from \"../shard-plan.ts\";\nimport { contextOf, type ShardTarget } from \"./context.ts\";\n\n/** The finished build, reported by whichever worker finalized it. */\nexport interface FinalSummary {\n storyCount: number;\n failedCount: number;\n buildStatus: BuildStatus;\n /** The attempt row the plan was made under, to be closed by the caller. */\n attemptId: string | null;\n /** Set when the finalize itself failed on a last try, and the build was failed for it. */\n error?: string;\n}\n\n/**\n * When every render shard has ended, claim the finalize (exactly one worker can) and finish the\n * build from the plan and the shards' outcomes. Returns the summary for the winner, else null.\n *\n * A finalize that throws gives its claim back, so the next run to reduce the build (a redelivery\n * of the same shard) finalizes it instead of finding the claim taken. On a last try (`final`)\n * nothing will come back for it, so the build is failed and the failure reported as the summary.\n */\nexport async function reduceBuild(target: ShardTarget, final = true): Promise<FinalSummary | null> {\n const shards = new CaptureShardModel(target.options.db);\n const rows = await closedShards(shards, target.build.id);\n if (!rows || !(await shards.claimFinalize(target.project.id, target.build.id))) {\n return null;\n }\n return await finalizeClaimed(target, shards, rows, final);\n}\n\n/** Finalize under the claim this run holds; on failure, give the claim back. */\nasync function finalizeClaimed(\n target: ShardTarget,\n shards: CaptureShardModel,\n rows: CaptureShard[],\n final: boolean,\n): Promise<FinalSummary> {\n const plan = await shards.plan(target.build.id);\n try {\n const summary = await finalizeSharded(target, rows, plan);\n await shards.finishFinalize(target.build.id);\n return summary;\n } catch (error) {\n await shards.releaseFinalize(target.build.id);\n if (!final) {\n throw error;\n }\n return await failFinalize(target, plan, error);\n }\n}\n\n/** The build's render shards once every one has ended, else null (none, or some still open). */\nasync function closedShards(\n shards: CaptureShardModel,\n buildId: string,\n): Promise<CaptureShard[] | null> {\n const rows = await shards.renderShards(buildId);\n const open = rows.some((row) => row.status === \"pending\" || row.status === \"running\");\n return rows.length === 0 || open ? null : rows;\n}\n\nasync function finalizeSharded(\n target: ShardTarget,\n rows: CaptureShard[],\n plan: CaptureShard | null,\n): Promise<FinalSummary> {\n const planResult = planResultOf(plan);\n const sets = collectSets(rows, planResult);\n await finalizeBuild(\n contextOf(target, { viewports: [], browsers: [] }),\n new Set(planResult.storyIds ?? []),\n sets.failed,\n sets.flaky,\n sets.a11y,\n );\n const finished = await new BuildModel(target.options.db, target.options.tables).get(\n target.build.id,\n );\n return {\n storyCount: planResult.storyIds?.length ?? 0,\n failedCount: sets.failed.size,\n buildStatus: finished?.status ?? \"failed\",\n attemptId: plan?.attemptId ?? null,\n };\n}\n\n/** The last try at finalizing failed: fail the build, so it does not sit in `capturing`. */\nasync function failFinalize(\n target: ShardTarget,\n plan: CaptureShard | null,\n error: unknown,\n): Promise<FinalSummary> {\n await new BuildModel(target.options.db, target.options.tables).setStatus(\n target.build.id,\n \"failed\",\n );\n return {\n storyCount: planResultOf(plan).storyIds?.length ?? 0,\n failedCount: 0,\n buildStatus: \"failed\",\n attemptId: plan?.attemptId ?? null,\n error: error instanceof Error ? error.message : String(error),\n };\n}\n\nfunction planResultOf(plan: CaptureShard | null): Partial<PlanResult> {\n return JSON.parse(plan?.result ?? \"{}\") as Partial<PlanResult>;\n}\n\ninterface FailureUnion {\n failed: Set<string>;\n flaky: Set<string>;\n a11y: Set<string>;\n}\n\n/** Union the plan's and every shard's failures; a shard that ended `failed` fails all its stories. */\nfunction collectSets(rows: CaptureShard[], plan: Partial<PlanResult>): FailureUnion {\n const union: FailureUnion = {\n failed: new Set(plan.failedStoryIds ?? []),\n flaky: new Set(),\n a11y: new Set(),\n };\n for (const row of rows) {\n if (row.status === \"failed\") {\n addAll(union.failed, storyIdsOf(row));\n } else {\n addShardResult(union, JSON.parse(row.result ?? \"{}\") as Partial<ShardResult>);\n }\n }\n return union;\n}\n\nfunction addShardResult(union: FailureUnion, result: Partial<ShardResult>): void {\n addAll(union.failed, result.failedStoryIds ?? []);\n addAll(union.flaky, result.flakyStoryIds ?? []);\n addAll(union.a11y, result.a11yStoryIds ?? []);\n}\n\nfunction addAll(into: Set<string>, ids: readonly string[]): void {\n for (const id of ids) {\n into.add(id);\n }\n}\n\nfunction storyIdsOf(row: CaptureShard): string[] {\n const payload = JSON.parse(row.payload ?? \"{}\") as Partial<ShardPayload>;\n return (payload.browsers ?? []).flatMap((engine) => engine.stories.map((story) => story.id));\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { Viewport } from "./adapter.mjs";
|
|
4
|
-
//#region src/capture/sizing.d.ts
|
|
5
4
|
/** Default min/max dimensions for cropped/fullPage screenshots (diff stability). */
|
|
6
5
|
declare const SIZING_DEFAULTS: {
|
|
7
6
|
readonly minWidth: 100;
|
|
@@ -53,6 +52,5 @@ interface ScreenshotPlan {
|
|
|
53
52
|
declare function getScreenshotPlan(viewport: Viewport, box: BoundingBox | null, options?: SizingOptions): ScreenshotPlan;
|
|
54
53
|
/** Resolve viewports from project/server config with fallback to default (not sizing, just list). */
|
|
55
54
|
declare function resolveViewports(rawViewports: string | null | undefined, fallback: Viewport[], serverViewports?: Viewport[]): Viewport[];
|
|
56
|
-
//#endregion
|
|
57
55
|
export { BoundingBox, SIZING_DEFAULTS, ScreenshotPlan, SizingOptions, getScreenshotPlan, resolveViewports };
|
|
58
56
|
//# sourceMappingURL=sizing.d.mts.map
|
package/dist/capture/sizing.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
5
|
-
//#region src/capture/sizing.ts
|
|
4
|
+
// src/capture/sizing.ts
|
|
6
5
|
/** Padding around the cropped component (8px each side). */
|
|
7
6
|
const PAD = 16;
|
|
8
7
|
const HALF_PAD = 8;
|
|
@@ -104,16 +103,6 @@ function parsedViewports(raw) {
|
|
|
104
103
|
} catch {}
|
|
105
104
|
return null;
|
|
106
105
|
}
|
|
107
|
-
|
|
108
|
-
function infraHashFor(browser, viewports, defaults = SIZING_DEFAULTS) {
|
|
109
|
-
const payload = JSON.stringify({
|
|
110
|
-
browser,
|
|
111
|
-
viewports,
|
|
112
|
-
defaults
|
|
113
|
-
});
|
|
114
|
-
return sha256(payload);
|
|
115
|
-
}
|
|
116
|
-
//#endregion
|
|
117
|
-
export { SIZING_DEFAULTS, getScreenshotPlan, infraHashFor, resolveViewports };
|
|
106
|
+
export { SIZING_DEFAULTS, getScreenshotPlan, resolveViewports };
|
|
118
107
|
|
|
119
108
|
//# sourceMappingURL=sizing.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sizing.mjs","names":[],"sources":["../../src/capture/sizing.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"sizing.mjs","names":[],"sources":["../../src/capture/sizing.ts"],"sourcesContent":["import type { Viewport } from \"./adapter.ts\";\n\n/** Padding around the cropped component (8px each side). */\nconst PAD = 16;\nconst HALF_PAD = 8;\n\n/** Default min/max dimensions for cropped/fullPage screenshots (diff stability). */\nexport const SIZING_DEFAULTS = {\n minWidth: 100,\n minHeight: 100,\n maxWidth: 2000,\n maxHeight: 4000,\n whitespaceThreshold: 0.6,\n} as const;\n\n/** Bounding box of #storybook-root as returned by Playwright/Puppeteer. */\nexport interface BoundingBox {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** Options for computing the screenshot plan. */\nexport interface SizingOptions {\n /** Per-story override: false forces full viewport, true forces crop when possible. */\n autoCrop?: boolean;\n /** Whitespace fraction (0..1) above which we crop; default 0.6. */\n whitespaceThreshold?: number;\n /** Whether overflow should trigger fullPage; default true. */\n fullPageOnOverflow?: boolean;\n /** Min dimensions for the final PNG (clamp after padding). */\n minWidth?: number;\n minHeight?: number;\n /** Max dimensions for the final PNG (clamp after padding). */\n maxWidth?: number;\n maxHeight?: number;\n}\n\n/** Result of the sizing decision — what the runner should do. */\nexport interface ScreenshotPlan {\n /** Viewport to set on the page before capture. */\n viewport: Viewport;\n /** Clip to use for page.screenshot({clip}). Undefined means full viewport. */\n clip?: { x: number; y: number; width: number; height: number };\n /** Whether to use fullPage screenshot (for overflow). */\n fullPage: boolean;\n}\n\n/** Clamp value between min and max. */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n\nfunction isOverflowing(box: BoundingBox, viewport: Viewport): boolean {\n return box.width > viewport.width || box.height > viewport.height;\n}\n\nfunction whitespaceOf(box: BoundingBox, viewport: Viewport): number {\n const viewportArea = viewport.width * viewport.height;\n const boxArea = box.width * box.height;\n const coverage = viewportArea > 0 ? boxArea / viewportArea : 1;\n return 1 - Math.min(coverage, 1);\n}\n\nfunction shouldCrop(\n box: BoundingBox,\n viewport: Viewport,\n opts: SizingOptions,\n threshold: number,\n): boolean {\n if (opts.autoCrop === true) return true;\n return whitespaceOf(box, viewport) > threshold;\n}\n\nfunction paddedClip(\n box: BoundingBox,\n minW: number,\n maxW: number,\n minH: number,\n maxH: number,\n): BoundingBox {\n const w = clamp(Math.ceil(box.width + PAD), minW, maxW);\n const h = clamp(Math.ceil(box.height + PAD), minH, maxH);\n return {\n x: Math.max(0, box.x - HALF_PAD),\n y: Math.max(0, box.y - HALF_PAD),\n width: w,\n height: h,\n };\n}\n\n/**\n * Compute the screenshot plan for a single story×viewport.\n * Single prod owner for sizing logic — runners import this, never duplicate.\n */\nexport function getScreenshotPlan(\n viewport: Viewport,\n box: BoundingBox | null,\n options: SizingOptions = {},\n): ScreenshotPlan {\n if (options.autoCrop === false) return { viewport, fullPage: false };\n if (!box) return { viewport, fullPage: false };\n if (isOverflowing(box, viewport) && (options.fullPageOnOverflow ?? true)) {\n return { viewport, fullPage: true };\n }\n if (isOverflowing(box, viewport)) {\n return grownViewportPlan(viewport, box, options);\n }\n return maybeCroppedPlan(viewport, box, options);\n}\n\nfunction grownViewportPlan(\n viewport: Viewport,\n box: BoundingBox,\n options: SizingOptions,\n): ScreenshotPlan {\n const maxH = options.maxHeight ?? SIZING_DEFAULTS.maxHeight;\n const grownHeight = clamp(Math.ceil(box.height + PAD), viewport.height, maxH);\n return { viewport: { ...viewport, height: grownHeight }, fullPage: false };\n}\n\nfunction maybeCroppedPlan(\n viewport: Viewport,\n box: BoundingBox,\n options: SizingOptions,\n): ScreenshotPlan {\n const threshold = options.whitespaceThreshold ?? SIZING_DEFAULTS.whitespaceThreshold;\n if (!shouldCrop(box, viewport, options, threshold)) return { viewport, fullPage: false };\n const minW = options.minWidth ?? SIZING_DEFAULTS.minWidth;\n const minH = options.minHeight ?? SIZING_DEFAULTS.minHeight;\n const maxW = options.maxWidth ?? SIZING_DEFAULTS.maxWidth;\n const maxH = options.maxHeight ?? SIZING_DEFAULTS.maxHeight;\n const clip = paddedClip(box, minW, maxW, minH, maxH);\n return {\n viewport,\n clip: { x: Math.round(clip.x), y: Math.round(clip.y), width: clip.width, height: clip.height },\n fullPage: false,\n };\n}\n\n/** Resolve viewports from project/server config with fallback to default (not sizing, just list). */\nexport function resolveViewports(\n rawViewports: string | null | undefined,\n fallback: Viewport[],\n serverViewports?: Viewport[],\n): Viewport[] {\n if (serverViewports && serverViewports.length > 0) return serverViewports;\n if (rawViewports) return parsedViewports(rawViewports) ?? fallback;\n return fallback;\n}\n\nfunction parsedViewports(raw: string): Viewport[] | null {\n try {\n const parsed = JSON.parse(raw) as Viewport[];\n if (Array.isArray(parsed) && parsed.length > 0) return parsed;\n } catch {\n // fallback\n }\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -2,75 +2,11 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { BuildModel } from "../models/build.mjs";
|
|
5
|
-
|
|
6
|
-
//#region src/capture/skip-checks.ts
|
|
5
|
+
// src/capture/skip-checks.ts
|
|
7
6
|
/** Skip capture when another approved build already covers the same commit. */
|
|
8
7
|
async function hasApprovedBuildForSha(db, tables, projectId, sha, excludeBuildId) {
|
|
9
|
-
return (await new BuildModel(db, tables).list(projectId)).some((
|
|
8
|
+
return (await new BuildModel(db, tables).list(projectId, { status: "approved" })).some((build) => build.gitSha === sha && build.id !== excludeBuildId);
|
|
10
9
|
}
|
|
11
|
-
|
|
12
|
-
return providers.find((p) => p.metadata.kind === row.provider);
|
|
13
|
-
}
|
|
14
|
-
function decodeRowConfig(provider, configJson) {
|
|
15
|
-
try {
|
|
16
|
-
const parsed = JSON.parse(configJson);
|
|
17
|
-
provider.metadata.schema.parse(parsed);
|
|
18
|
-
return parsed;
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function decodeRowToken(model, row) {
|
|
24
|
-
try {
|
|
25
|
-
return model.decryptToken(row);
|
|
26
|
-
} catch {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function parseRow(model, row, providers) {
|
|
31
|
-
const provider = findRowProvider(providers, row);
|
|
32
|
-
if (!provider) return null;
|
|
33
|
-
const parsed = decodeRowConfig(provider, row.config);
|
|
34
|
-
if (parsed === null) return null;
|
|
35
|
-
const token = decodeRowToken(model, row);
|
|
36
|
-
if (token === null) return null;
|
|
37
|
-
return {
|
|
38
|
-
provider,
|
|
39
|
-
config: parsed,
|
|
40
|
-
token
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
async function checkRowMerged(parsed, sha, branch, logger) {
|
|
44
|
-
const adapter = parsed.provider.create({
|
|
45
|
-
config: parsed.config,
|
|
46
|
-
token: parsed.token,
|
|
47
|
-
logger
|
|
48
|
-
});
|
|
49
|
-
if (!adapter.isMerged) return false;
|
|
50
|
-
try {
|
|
51
|
-
return await adapter.isMerged({
|
|
52
|
-
sha,
|
|
53
|
-
branch
|
|
54
|
-
});
|
|
55
|
-
} catch {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/** Check one status-config row for an upstream merge. */
|
|
60
|
-
async function checkRow(model, row, providers, sha, branch, logger) {
|
|
61
|
-
const parsed = parseRow(model, row, providers);
|
|
62
|
-
if (!parsed) return false;
|
|
63
|
-
return await checkRowMerged(parsed, sha, branch, logger);
|
|
64
|
-
}
|
|
65
|
-
/** True when any configured provider reports the commit already merged. */
|
|
66
|
-
async function isAlreadyMerged(opts) {
|
|
67
|
-
if (opts.providers.length === 0) return false;
|
|
68
|
-
const model = new StatusConfigModel(opts.db, opts.tables, opts.secret);
|
|
69
|
-
const rows = await model.list(opts.projectId);
|
|
70
|
-
for (const row of rows) if (await checkRow(model, row, opts.providers, opts.sha, opts.branch, opts.logger)) return true;
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
//#endregion
|
|
74
|
-
export { hasApprovedBuildForSha, isAlreadyMerged };
|
|
10
|
+
export { hasApprovedBuildForSha };
|
|
75
11
|
|
|
76
12
|
//# sourceMappingURL=skip-checks.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skip-checks.mjs","names":[],"sources":["../../src/capture/skip-checks.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport
|
|
1
|
+
{"version":3,"file":"skip-checks.mjs","names":[],"sources":["../../src/capture/skip-checks.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\n\n/** Skip capture when another approved build already covers the same commit. */\nexport async function hasApprovedBuildForSha(\n db: DatabaseAdapter,\n tables: BuildTables,\n projectId: string,\n sha: string,\n excludeBuildId: string,\n): Promise<boolean> {\n const approved = await new BuildModel(db, tables).list(projectId, { status: \"approved\" });\n return approved.some((build) => build.gitSha === sha && build.id !== excludeBuildId);\n}\n"],"mappings":";;;;;;AAIA,eAAsB,uBACpB,IACA,QACA,WACA,KACA,gBACkB;CAElB,QAAO,MADgB,IAAI,WAAW,IAAI,MAAM,CAAC,CAAC,KAAK,WAAW,EAAE,QAAQ,WAAW,CAAC,EAAA,CACxE,MAAM,UAAU,MAAM,WAAW,OAAO,MAAM,OAAO,cAAc;AACrF"}
|
|
@@ -2,23 +2,21 @@ import __tsdown_shims_path from 'node:path';
|
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { DatabaseAdapter } from "../adapter/database.mjs";
|
|
4
4
|
import { StorageAdapter } from "../adapter/storage.mjs";
|
|
5
|
-
|
|
5
|
+
import { ExtractionLimits } from "./publish/limits.mjs";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* capture orchestrator and by the upload path for opt-in inline extraction
|
|
10
|
-
* (`maxInlineUnzipSize`); both write identical keys, so either may run first.
|
|
11
|
-
*/
|
|
12
|
-
/**
|
|
13
|
-
* Extract a build's stored zip into a dedicated scratch directory.
|
|
7
|
+
* Extract a build's stored zip into a dedicated scratch directory. The caller removes it when
|
|
8
|
+
* done (`removeJobScratch`).
|
|
14
9
|
*
|
|
15
10
|
* @param storage - Storage holding the uploaded zip.
|
|
16
11
|
* @param scratchDir - Root under which per-build scratch dirs are created.
|
|
17
12
|
* @param projectId - Owning project id.
|
|
18
13
|
* @param buildId - Build whose zip to extract.
|
|
14
|
+
* @param shardId - Shard extracting it; gives the shard a directory of its own.
|
|
15
|
+
* @param limits - Entry and uncompressed-byte caps (safe defaults when omitted).
|
|
19
16
|
* @returns The extracted Storybook directory.
|
|
17
|
+
* @throws {ZipLimitError} When the archive breaks a cap or escapes the target directory.
|
|
20
18
|
*/
|
|
21
|
-
declare function extractStorybookToScratch(storage: StorageAdapter, scratchDir: string, projectId: string, buildId: string): Promise<string>;
|
|
19
|
+
declare function extractStorybookToScratch(storage: StorageAdapter, scratchDir: string, projectId: string, buildId: string, shardId?: string, limits?: ExtractionLimits): Promise<string>;
|
|
22
20
|
/**
|
|
23
21
|
* Persist an extracted Storybook directory to storage for published serving.
|
|
24
22
|
*
|
|
@@ -33,6 +31,5 @@ declare function extractStorybookToScratch(storage: StorageAdapter, scratchDir:
|
|
|
33
31
|
* @param db - Optional database for content ref tracking (enables GC).
|
|
34
32
|
*/
|
|
35
33
|
declare function persistStorybookStatics(storage: StorageAdapter, sourceDir: string, projectId: string, buildId: string, db?: DatabaseAdapter): Promise<void>;
|
|
36
|
-
//#endregion
|
|
37
34
|
export { extractStorybookToScratch, persistStorybookStatics };
|
|
38
35
|
//# sourceMappingURL=statics.d.mts.map
|
package/dist/capture/statics.mjs
CHANGED
|
@@ -1,36 +1,43 @@
|
|
|
1
|
-
import "node:path";
|
|
1
|
+
import { join, relative, resolve } from "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { storybookDir, storybookZipPath } from "../paths.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { mapLimit } from "../utils/map-limit.mjs";
|
|
6
|
+
import { jobScratchDir, shardScratchDir } from "./scratch.mjs";
|
|
7
|
+
import { DEFAULT_MAX_UNZIPPED_BYTES, DEFAULT_MAX_ZIP_ENTRIES } from "./publish/limits.mjs";
|
|
8
|
+
import { extractZipStream } from "./publish/extract.mjs";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
6
10
|
import { mkdir, readFile, readdir, rm } from "node:fs/promises";
|
|
7
|
-
|
|
8
|
-
import { pipeline } from "node:stream/promises";
|
|
9
|
-
import { Parse } from "unzipper";
|
|
10
|
-
//#region src/capture/statics.ts
|
|
11
|
+
// src/capture/statics.ts
|
|
11
12
|
/**
|
|
12
13
|
* Shared Storybook statics handling: unzip a stored build into a scratch
|
|
13
14
|
* directory and persist the extracted files back to storage. Used by the
|
|
14
15
|
* capture orchestrator and by the upload path for opt-in inline extraction
|
|
15
16
|
* (`maxInlineUnzipSize`); both write identical keys, so either may run first.
|
|
16
17
|
*/
|
|
18
|
+
const DEFAULT_EXTRACTION_LIMITS = {
|
|
19
|
+
maxEntries: DEFAULT_MAX_ZIP_ENTRIES,
|
|
20
|
+
maxBytes: DEFAULT_MAX_UNZIPPED_BYTES
|
|
21
|
+
};
|
|
17
22
|
/**
|
|
18
|
-
* Extract a build's stored zip into a dedicated scratch directory.
|
|
23
|
+
* Extract a build's stored zip into a dedicated scratch directory. The caller removes it when
|
|
24
|
+
* done (`removeJobScratch`).
|
|
19
25
|
*
|
|
20
26
|
* @param storage - Storage holding the uploaded zip.
|
|
21
27
|
* @param scratchDir - Root under which per-build scratch dirs are created.
|
|
22
28
|
* @param projectId - Owning project id.
|
|
23
29
|
* @param buildId - Build whose zip to extract.
|
|
30
|
+
* @param shardId - Shard extracting it; gives the shard a directory of its own.
|
|
31
|
+
* @param limits - Entry and uncompressed-byte caps (safe defaults when omitted).
|
|
24
32
|
* @returns The extracted Storybook directory.
|
|
33
|
+
* @throws {ZipLimitError} When the archive breaks a cap or escapes the target directory.
|
|
25
34
|
*/
|
|
26
|
-
async function extractStorybookToScratch(storage, scratchDir, projectId, buildId) {
|
|
27
|
-
const
|
|
35
|
+
async function extractStorybookToScratch(storage, scratchDir, projectId, buildId, shardId, limits = DEFAULT_EXTRACTION_LIMITS) {
|
|
36
|
+
const base = shardId ? shardScratchDir(scratchDir, projectId, buildId, shardId) : jobScratchDir(scratchDir, projectId, buildId);
|
|
37
|
+
const targetDir = join(base, "storybook");
|
|
28
38
|
const root = await prepareTargetDir(targetDir);
|
|
29
39
|
const source = await storage.readStream(storybookZipPath(projectId, buildId));
|
|
30
|
-
|
|
31
|
-
const parseError = await runParser(root, source, writes);
|
|
32
|
-
if (parseError) throw parseError;
|
|
33
|
-
await awaitWrites(writes);
|
|
40
|
+
await extractZipStream(source, root, limits);
|
|
34
41
|
return targetDir;
|
|
35
42
|
}
|
|
36
43
|
/** Reset a scratch target directory, returning its resolved root. */
|
|
@@ -42,11 +49,6 @@ async function prepareTargetDir(targetDir) {
|
|
|
42
49
|
await mkdir(targetDir, { recursive: true });
|
|
43
50
|
return resolve(targetDir);
|
|
44
51
|
}
|
|
45
|
-
/** Await streamed zip writes, throwing the first write failure. */
|
|
46
|
-
async function awaitWrites(writes) {
|
|
47
|
-
const writeError = (await Promise.allSettled(writes)).find((outcome) => outcome.status === "rejected");
|
|
48
|
-
if (writeError) throw writeError.reason;
|
|
49
|
-
}
|
|
50
52
|
/**
|
|
51
53
|
* Persist an extracted Storybook directory to storage for published serving.
|
|
52
54
|
*
|
|
@@ -65,22 +67,19 @@ async function persistStorybookStatics(storage, sourceDir, projectId, buildId, d
|
|
|
65
67
|
const destinationPrefix = storybookDir(projectId, buildId);
|
|
66
68
|
const files = await walkFiles(root);
|
|
67
69
|
const manifest = {};
|
|
68
|
-
await
|
|
70
|
+
await mapLimit(files, STATICS_CONCURRENCY, async (file) => {
|
|
69
71
|
const rel = relative(root, file);
|
|
70
72
|
const buffer = await readFile(file);
|
|
71
|
-
const hash =
|
|
72
|
-
const contentPath = `content/${hash}`;
|
|
73
|
+
const hash = createHash("sha256").update(buffer).digest("hex");
|
|
73
74
|
manifest[rel] = hash;
|
|
74
|
-
if (!await storage.exists(
|
|
75
|
+
if (!await storage.exists(`content/${hash}`)) await storage.write(`content/${hash}`, buffer);
|
|
75
76
|
await storage.write(`${destinationPrefix}/${rel}`, buffer);
|
|
76
77
|
if (db) await upsertContentRef(db, hash);
|
|
77
|
-
})
|
|
78
|
+
});
|
|
78
79
|
await storage.write(`${destinationPrefix}/manifest.json`, Buffer.from(JSON.stringify(manifest)));
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return createHash("sha256").update(buffer).digest("hex");
|
|
83
|
-
}
|
|
81
|
+
/** Files persisted at once; bounds how many file buffers are in memory together. */
|
|
82
|
+
const STATICS_CONCURRENCY = 8;
|
|
84
83
|
async function upsertContentRef(db, hash) {
|
|
85
84
|
try {
|
|
86
85
|
const contentRefs = db.tables.contentRefs;
|
|
@@ -98,52 +97,6 @@ async function upsertContentRef(db, hash) {
|
|
|
98
97
|
});
|
|
99
98
|
} catch {}
|
|
100
99
|
}
|
|
101
|
-
function blockedTarget(root, entryName) {
|
|
102
|
-
const candidate = resolve(join(root, entryName));
|
|
103
|
-
return candidate !== root && !candidate.startsWith(root + sep);
|
|
104
|
-
}
|
|
105
|
-
/** Pipe one zip entry to disk, tracking the write for later aggregation. */
|
|
106
|
-
function handleEntry(root, entry, writes, fail) {
|
|
107
|
-
if (blockedTarget(root, entry.path)) {
|
|
108
|
-
entry.autodrain();
|
|
109
|
-
fail(/* @__PURE__ */ new Error(`Blocked path traversal in uploaded Storybook: ${entry.path}`));
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
if (entry.type === "Directory") {
|
|
113
|
-
entry.autodrain();
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
writeEntry(root, entry, writes);
|
|
117
|
-
}
|
|
118
|
-
/** Stream one zip entry to disk, tracking the write for later aggregation. */
|
|
119
|
-
function writeEntry(root, entry, writes) {
|
|
120
|
-
const target = join(root, entry.path);
|
|
121
|
-
const done = (async () => {
|
|
122
|
-
await mkdir(dirname(target), { recursive: true });
|
|
123
|
-
await pipeline(entry, createWriteStream(target));
|
|
124
|
-
})();
|
|
125
|
-
writes.push(done);
|
|
126
|
-
done.catch(() => {});
|
|
127
|
-
}
|
|
128
|
-
/** Run the unzipper parser, resolving with the first parse error (or null). */
|
|
129
|
-
function runParser(root, source, writes) {
|
|
130
|
-
return new Promise((settle) => {
|
|
131
|
-
const parser = Parse();
|
|
132
|
-
const fail = (error) => {
|
|
133
|
-
source.destroy();
|
|
134
|
-
settle(error);
|
|
135
|
-
};
|
|
136
|
-
parser.on("entry", (entry) => {
|
|
137
|
-
handleEntry(root, entry, writes, fail);
|
|
138
|
-
});
|
|
139
|
-
parser.on("error", fail);
|
|
140
|
-
parser.on("close", () => {
|
|
141
|
-
settle(null);
|
|
142
|
-
});
|
|
143
|
-
source.on("error", fail);
|
|
144
|
-
source.pipe(parser);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
100
|
async function walkFiles(dir) {
|
|
148
101
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
149
102
|
return (await Promise.all(entries.map(async (entry) => {
|
|
@@ -152,7 +105,6 @@ async function walkFiles(dir) {
|
|
|
152
105
|
return [full];
|
|
153
106
|
}))).flat();
|
|
154
107
|
}
|
|
155
|
-
//#endregion
|
|
156
108
|
export { extractStorybookToScratch, persistStorybookStatics };
|
|
157
109
|
|
|
158
110
|
//# sourceMappingURL=statics.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statics.mjs","names":[],"sources":["../../src/capture/statics.ts"],"sourcesContent":["import { createWriteStream } from \"node:fs\";\nimport { mkdir, readdir, readFile, rm } from \"node:fs/promises\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\nimport { Parse, type Entry } from \"unzipper\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { storybookDir, storybookZipPath } from \"../utils/paths.ts\";\n\n/**\n * Shared Storybook statics handling: unzip a stored build into a scratch\n * directory and persist the extracted files back to storage. Used by the\n * capture orchestrator and by the upload path for opt-in inline extraction\n * (`maxInlineUnzipSize`); both write identical keys, so either may run first.\n */\n\n/**\n * Extract a build's stored zip into a dedicated scratch directory.\n *\n * @param storage - Storage holding the uploaded zip.\n * @param scratchDir - Root under which per-build scratch dirs are created.\n * @param projectId - Owning project id.\n * @param buildId - Build whose zip to extract.\n * @returns The extracted Storybook directory.\n */\nexport async function extractStorybookToScratch(\n storage: StorageAdapter,\n scratchDir: string,\n projectId: string,\n buildId: string,\n): Promise<string> {\n const targetDir = join(scratchDir, projectId, \"builds\", buildId, \"storybook\");\n const root = await prepareTargetDir(targetDir);\n const source = await storage.readStream(storybookZipPath(projectId, buildId));\n const writes: Promise<void>[] = [];\n const parseError = await runParser(root, source, writes);\n if (parseError) {\n throw parseError;\n }\n await awaitWrites(writes);\n return targetDir;\n}\n\n/** Reset a scratch target directory, returning its resolved root. */\nasync function prepareTargetDir(targetDir: string): Promise<string> {\n await rm(targetDir, { recursive: true, force: true });\n await mkdir(targetDir, { recursive: true });\n return resolve(targetDir);\n}\n\n/** Await streamed zip writes, throwing the first write failure. */\nasync function awaitWrites(writes: Promise<void>[]): Promise<void> {\n const outcomes = await Promise.allSettled(writes);\n const writeError = outcomes.find(\n (outcome): outcome is PromiseRejectedResult => outcome.status === \"rejected\",\n );\n if (writeError) {\n throw writeError.reason;\n }\n}\n\n/**\n * Persist an extracted Storybook directory to storage for published serving.\n *\n * Uses content-addressed deduplication: each file is hashed and stored once\n * under `content/<hash>`, with a per-build manifest at\n * `${projectId}/builds/${buildId}/storybook/manifest.json`.\n *\n * @param storage - Destination storage adapter.\n * @param sourceDir - Extracted Storybook directory.\n * @param projectId - Owning project id.\n * @param buildId - Build the files belong to.\n * @param db - Optional database for content ref tracking (enables GC).\n */\nexport async function persistStorybookStatics(\n storage: StorageAdapter,\n sourceDir: string,\n projectId: string,\n buildId: string,\n db?: DatabaseAdapter,\n): Promise<void> {\n const root = resolve(sourceDir);\n const destinationPrefix = storybookDir(projectId, buildId);\n const files = await walkFiles(root);\n const manifest: Record<string, string> = {};\n await Promise.all(\n files.map(async (file) => {\n const rel = relative(root, file);\n const buffer = await readFile(file);\n const hash = await hashBuffer(buffer);\n const contentPath = `content/${hash}`;\n manifest[rel] = hash;\n // Only write if not already exists (dedup)\n if (!(await storage.exists(contentPath))) {\n await storage.write(contentPath, buffer);\n }\n // Also write to legacy per-build path for backward compat (can be removed after migration)\n // and maintain content_refs for GC\n await storage.write(`${destinationPrefix}/${rel}`, buffer);\n if (db) {\n await upsertContentRef(db, hash);\n }\n }),\n );\n await storage.write(`${destinationPrefix}/manifest.json`, Buffer.from(JSON.stringify(manifest)));\n}\n\nasync function hashBuffer(buffer: Buffer): Promise<string> {\n const { createHash } = await import(\"node:crypto\");\n return createHash(\"sha256\").update(buffer).digest(\"hex\");\n}\n\nasync function upsertContentRef(db: DatabaseAdapter, hash: string): Promise<void> {\n try {\n const contentRefs = db.tables.contentRefs;\n const now = new Date().toISOString();\n const existing = (await db.get(contentRefs, hash)) as unknown as { refCount: number } | null;\n if (existing) {\n await db.update(contentRefs, hash, {\n refCount: existing.refCount + 1,\n lastSeenAt: now,\n });\n } else {\n await db.insert(contentRefs, { hash, refCount: 1, lastSeenAt: now, createdAt: now });\n }\n } catch {\n // content_refs table may not exist yet (old DB) — ignore\n }\n}\n\nfunction blockedTarget(root: string, entryName: string): boolean {\n const candidate = resolve(join(root, entryName));\n return candidate !== root && !candidate.startsWith(root + sep);\n}\n\n/** Pipe one zip entry to disk, tracking the write for later aggregation. */\nfunction handleEntry(\n root: string,\n entry: Entry,\n writes: Promise<void>[],\n fail: (error: unknown) => void,\n): void {\n if (blockedTarget(root, entry.path)) {\n entry.autodrain();\n fail(new Error(`Blocked path traversal in uploaded Storybook: ${entry.path}`));\n return;\n }\n if (entry.type === \"Directory\") {\n entry.autodrain();\n return;\n }\n writeEntry(root, entry, writes);\n}\n\n/** Stream one zip entry to disk, tracking the write for later aggregation. */\nfunction writeEntry(root: string, entry: Entry, writes: Promise<void>[]): void {\n const target = join(root, entry.path);\n const done = (async (): Promise<void> => {\n await mkdir(dirname(target), { recursive: true });\n await pipeline(entry, createWriteStream(target));\n })();\n writes.push(done);\n done.catch(() => {}); // Intentionally empty — aggregated via allSettled by the caller\n}\n\n/** Run the unzipper parser, resolving with the first parse error (or null). */\n// oxlint-disable-next-line typescript/promise-function-async -- executor-style promise wrapper around events\nfunction runParser(\n root: string,\n source: import(\"node:stream\").Readable,\n writes: Promise<void>[],\n): Promise<unknown> {\n return new Promise((settle) => {\n const parser = Parse();\n const fail = (error: unknown): void => {\n source.destroy();\n settle(error);\n };\n parser.on(\"entry\", (entry: Entry) => {\n handleEntry(root, entry, writes, fail);\n });\n parser.on(\"error\", fail);\n parser.on(\"close\", () => {\n settle(null);\n });\n source.on(\"error\", fail);\n source.pipe(parser);\n });\n}\n\nasync function walkFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const nested = await Promise.all(\n entries.map(async (entry) => {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n return await walkFiles(full);\n }\n return [full];\n }),\n );\n return nested.flat();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"statics.mjs","names":[],"sources":["../../src/capture/statics.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, readdir, readFile, rm } from \"node:fs/promises\";\nimport { join, relative, resolve } from \"node:path\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { mapLimit } from \"../utils/map-limit.ts\";\nimport { storybookDir, storybookZipPath } from \"../utils/paths.ts\";\nimport { extractZipStream } from \"./publish/extract.ts\";\nimport {\n DEFAULT_MAX_UNZIPPED_BYTES,\n DEFAULT_MAX_ZIP_ENTRIES,\n type ExtractionLimits,\n} from \"./publish/limits.ts\";\nimport { jobScratchDir, shardScratchDir } from \"./scratch.ts\";\n\n/**\n * Shared Storybook statics handling: unzip a stored build into a scratch\n * directory and persist the extracted files back to storage. Used by the\n * capture orchestrator and by the upload path for opt-in inline extraction\n * (`maxInlineUnzipSize`); both write identical keys, so either may run first.\n */\n\nconst DEFAULT_EXTRACTION_LIMITS: ExtractionLimits = {\n maxEntries: DEFAULT_MAX_ZIP_ENTRIES,\n maxBytes: DEFAULT_MAX_UNZIPPED_BYTES,\n};\n\n/**\n * Extract a build's stored zip into a dedicated scratch directory. The caller removes it when\n * done (`removeJobScratch`).\n *\n * @param storage - Storage holding the uploaded zip.\n * @param scratchDir - Root under which per-build scratch dirs are created.\n * @param projectId - Owning project id.\n * @param buildId - Build whose zip to extract.\n * @param shardId - Shard extracting it; gives the shard a directory of its own.\n * @param limits - Entry and uncompressed-byte caps (safe defaults when omitted).\n * @returns The extracted Storybook directory.\n * @throws {ZipLimitError} When the archive breaks a cap or escapes the target directory.\n */\nexport async function extractStorybookToScratch(\n storage: StorageAdapter,\n scratchDir: string,\n projectId: string,\n buildId: string,\n shardId?: string,\n limits: ExtractionLimits = DEFAULT_EXTRACTION_LIMITS,\n): Promise<string> {\n const base = shardId\n ? shardScratchDir(scratchDir, projectId, buildId, shardId)\n : jobScratchDir(scratchDir, projectId, buildId);\n const targetDir = join(base, \"storybook\");\n const root = await prepareTargetDir(targetDir);\n const source = await storage.readStream(storybookZipPath(projectId, buildId));\n await extractZipStream(source, root, limits);\n return targetDir;\n}\n\n/** Reset a scratch target directory, returning its resolved root. */\nasync function prepareTargetDir(targetDir: string): Promise<string> {\n await rm(targetDir, { recursive: true, force: true });\n await mkdir(targetDir, { recursive: true });\n return resolve(targetDir);\n}\n\n/**\n * Persist an extracted Storybook directory to storage for published serving.\n *\n * Uses content-addressed deduplication: each file is hashed and stored once\n * under `content/<hash>`, with a per-build manifest at\n * `${projectId}/builds/${buildId}/storybook/manifest.json`.\n *\n * @param storage - Destination storage adapter.\n * @param sourceDir - Extracted Storybook directory.\n * @param projectId - Owning project id.\n * @param buildId - Build the files belong to.\n * @param db - Optional database for content ref tracking (enables GC).\n */\nexport async function persistStorybookStatics(\n storage: StorageAdapter,\n sourceDir: string,\n projectId: string,\n buildId: string,\n db?: DatabaseAdapter,\n): Promise<void> {\n const root = resolve(sourceDir);\n const destinationPrefix = storybookDir(projectId, buildId);\n const files = await walkFiles(root);\n const manifest: Record<string, string> = {};\n // Bounded: a large Storybook has thousands of files, and buffering them all\n // at once exhausts memory on small hosts. Each file is read fully before any\n // write: the scratch dir is often the storage root, so the per-build path\n // below can be the very file being read (a streamed write would truncate it).\n await mapLimit(files, STATICS_CONCURRENCY, async (file) => {\n const rel = relative(root, file);\n const buffer = await readFile(file);\n const hash = createHash(\"sha256\").update(buffer).digest(\"hex\");\n manifest[rel] = hash;\n // Only write if not already exists (dedup)\n if (!(await storage.exists(`content/${hash}`))) {\n await storage.write(`content/${hash}`, buffer);\n }\n // Also write to legacy per-build path for backward compat (can be removed after migration)\n // and maintain content_refs for GC\n await storage.write(`${destinationPrefix}/${rel}`, buffer);\n if (db) {\n await upsertContentRef(db, hash);\n }\n });\n await storage.write(`${destinationPrefix}/manifest.json`, Buffer.from(JSON.stringify(manifest)));\n}\n\n/** Files persisted at once; bounds how many file buffers are in memory together. */\nconst STATICS_CONCURRENCY = 8;\n\nasync function upsertContentRef(db: DatabaseAdapter, hash: string): Promise<void> {\n try {\n const contentRefs = db.tables.contentRefs;\n const now = new Date().toISOString();\n const existing = (await db.get(contentRefs, hash)) as unknown as { refCount: number } | null;\n if (existing) {\n await db.update(contentRefs, hash, {\n refCount: existing.refCount + 1,\n lastSeenAt: now,\n });\n } else {\n await db.insert(contentRefs, { hash, refCount: 1, lastSeenAt: now, createdAt: now });\n }\n } catch {\n // content_refs table may not exist yet (old DB) — ignore\n }\n}\n\nasync function walkFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const nested = await Promise.all(\n entries.map(async (entry) => {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n return await walkFiles(full);\n }\n return [full];\n }),\n );\n return nested.flat();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAsBA,MAAM,4BAA8C;CAClD,YAAY;CACZ,UAAU;AACZ;;;;;;;;;;;;;;AAeA,eAAsB,0BACpB,SACA,YACA,WACA,SACA,SACA,SAA2B,2BACV;CACjB,MAAM,OAAO,UACT,gBAAgB,YAAY,WAAW,SAAS,OAAO,IACvD,cAAc,YAAY,WAAW,OAAO;CAChD,MAAM,YAAY,KAAK,MAAM,WAAW;CACxC,MAAM,OAAO,MAAM,iBAAiB,SAAS;CAC7C,MAAM,SAAS,MAAM,QAAQ,WAAW,iBAAiB,WAAW,OAAO,CAAC;CAC5E,MAAM,iBAAiB,QAAQ,MAAM,MAAM;CAC3C,OAAO;AACT;;AAGA,eAAe,iBAAiB,WAAoC;CAClE,MAAM,GAAG,WAAW;EAAE,WAAW;EAAM,OAAO;CAAK,CAAC;CACpD,MAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;CAC1C,OAAO,QAAQ,SAAS;AAC1B;;;;;;;;;;;;;;AAeA,eAAsB,wBACpB,SACA,WACA,WACA,SACA,IACe;CACf,MAAM,OAAO,QAAQ,SAAS;CAC9B,MAAM,oBAAoB,aAAa,WAAW,OAAO;CACzD,MAAM,QAAQ,MAAM,UAAU,IAAI;CAClC,MAAM,WAAmC,CAAC;CAK1C,MAAM,SAAS,OAAO,qBAAqB,OAAO,SAAS;EACzD,MAAM,MAAM,SAAS,MAAM,IAAI;EAC/B,MAAM,SAAS,MAAM,SAAS,IAAI;EAClC,MAAM,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK;EAC7D,SAAS,OAAO;EAEhB,IAAI,CAAE,MAAM,QAAQ,OAAO,WAAW,MAAM,GAC1C,MAAM,QAAQ,MAAM,WAAW,QAAQ,MAAM;EAI/C,MAAM,QAAQ,MAAM,GAAG,kBAAkB,GAAG,OAAO,MAAM;EACzD,IAAI,IACF,MAAM,iBAAiB,IAAI,IAAI;CAEnC,CAAC;CACD,MAAM,QAAQ,MAAM,GAAG,kBAAkB,iBAAiB,OAAO,KAAK,KAAK,UAAU,QAAQ,CAAC,CAAC;AACjG;;AAGA,MAAM,sBAAsB;AAE5B,eAAe,iBAAiB,IAAqB,MAA6B;CAChF,IAAI;EACF,MAAM,cAAc,GAAG,OAAO;EAC9B,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,WAAY,MAAM,GAAG,IAAI,aAAa,IAAI;EAChD,IAAI,UACF,MAAM,GAAG,OAAO,aAAa,MAAM;GACjC,UAAU,SAAS,WAAW;GAC9B,YAAY;EACd,CAAC;OAED,MAAM,GAAG,OAAO,aAAa;GAAE;GAAM,UAAU;GAAG,YAAY;GAAK,WAAW;EAAI,CAAC;CAEvF,QAAQ,CAER;AACF;AAEA,eAAe,UAAU,KAAgC;CACvD,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;CAU1D,QAAO,MATc,QAAQ,IAC3B,QAAQ,IAAI,OAAO,UAAU;EAC3B,MAAM,OAAO,KAAK,KAAK,MAAM,IAAI;EACjC,IAAI,MAAM,YAAY,GACpB,OAAO,MAAM,UAAU,IAAI;EAE7B,OAAO,CAAC,IAAI;CACd,CAAC,CACH,EAAA,CACc,KAAK;AACrB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { CaptureTarget } from "../adapters/capture-target.mjs";
|
|
4
|
+
import { CaptureRunner } from "../adapter/capture-runner.mjs";
|
|
5
|
+
import { SecretInput } from "../utils/encrypt.mjs";
|
|
6
|
+
import { Build } from "../schema/build.mjs";
|
|
7
|
+
import { Project } from "../schema/project.mjs";
|
|
8
|
+
/** What a pass tells the source: how long it may run, which sizes the token's lifetime. */
|
|
9
|
+
interface StorybookUrlPass {
|
|
10
|
+
/** The render budget of the pass in ms (`renderBudgetMs`); unset when the pass has none. */
|
|
11
|
+
budgetMs?: number | undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Gives the URL a target's browser opens, or `undefined` when it needs none (or none can be made). */
|
|
14
|
+
type StorybookUrlSource = ((target: CaptureTarget, pass?: StorybookUrlPass) => Promise<string | undefined>) & {
|
|
15
|
+
/**
|
|
16
|
+
* The lifetime in seconds a token minted for a pass of this budget gets (the project's override
|
|
17
|
+
* or the derived, capped one). A pass that outlasts it is rendered in slices, each with a fresh
|
|
18
|
+
* token (`renderInSlices`). `undefined` for a target that needs no signed URL (it is never
|
|
19
|
+
* sliced), and absent on a source that has no such limit.
|
|
20
|
+
*/
|
|
21
|
+
ttlSeconds?: (target: CaptureTarget, budgetMs: number) => number | undefined;
|
|
22
|
+
};
|
|
23
|
+
/** What minting needs of a capture job. */
|
|
24
|
+
interface StorybookUrlOptions {
|
|
25
|
+
runner: Pick<CaptureRunner, "metadata">;
|
|
26
|
+
secret?: SecretInput;
|
|
27
|
+
publicOrigin?: (() => Promise<string | undefined>) | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A source of signed URLs for one build. Every call mints a fresh token, so a shard that waited in
|
|
31
|
+
* the queue or a retried pass is never handed a token minted before it started.
|
|
32
|
+
*/
|
|
33
|
+
declare function storybookUrlSource(options: StorybookUrlOptions, subject: {
|
|
34
|
+
build: Pick<Build, "id" | "projectId">;
|
|
35
|
+
project: Pick<Project, "storybookUrlTtlSeconds">;
|
|
36
|
+
}): StorybookUrlSource;
|
|
37
|
+
export { StorybookUrlOptions, StorybookUrlPass, StorybookUrlSource, storybookUrlSource };
|
|
38
|
+
//# sourceMappingURL=storybook-urls.d.mts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { signStorybookToken, storybookTokenPath, storybookUrlTtl } from "../utils/storybook-token.mjs";
|
|
5
|
+
// src/capture/storybook-urls.ts
|
|
6
|
+
/**
|
|
7
|
+
* A source of signed URLs for one build. Every call mints a fresh token, so a shard that waited in
|
|
8
|
+
* the queue or a retried pass is never handed a token minted before it started.
|
|
9
|
+
*/
|
|
10
|
+
function storybookUrlSource(options, subject) {
|
|
11
|
+
const source = async (target, pass = {}) => {
|
|
12
|
+
const origin = needsUrl(options.runner, target) ? await options.publicOrigin?.() : void 0;
|
|
13
|
+
return origin !== void 0 && options.secret ? `${origin}${mint(options.secret, subject, pass)}` : void 0;
|
|
14
|
+
};
|
|
15
|
+
source.ttlSeconds = (target, budgetMs) => needsUrl(options.runner, target) ? storybookUrlTtl({
|
|
16
|
+
override: subject.project.storybookUrlTtlSeconds,
|
|
17
|
+
budgetMs
|
|
18
|
+
}) : void 0;
|
|
19
|
+
return source;
|
|
20
|
+
}
|
|
21
|
+
/** The path of a fresh token for one pass of the build. */
|
|
22
|
+
function mint(secret, subject, pass) {
|
|
23
|
+
const ttlSeconds = storybookUrlTtl({
|
|
24
|
+
override: subject.project.storybookUrlTtlSeconds,
|
|
25
|
+
budgetMs: pass.budgetMs ?? 0
|
|
26
|
+
});
|
|
27
|
+
const { id: buildId, projectId } = subject.build;
|
|
28
|
+
return storybookTokenPath(signStorybookToken({
|
|
29
|
+
secret,
|
|
30
|
+
projectId,
|
|
31
|
+
buildId,
|
|
32
|
+
ttlSeconds,
|
|
33
|
+
purpose: "capture"
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
function needsUrl(runner, target) {
|
|
37
|
+
return runner.metadata.targets?.some((t) => t.target === target && t.reach === "url") ?? false;
|
|
38
|
+
}
|
|
39
|
+
export { storybookUrlSource };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=storybook-urls.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storybook-urls.mjs","names":[],"sources":["../../src/capture/storybook-urls.ts"],"sourcesContent":["/**\n * The signed Storybook URL of a render (ADR 0007, decision 5): minted at the moment a pass starts,\n * only for a target whose browser cannot reach this host (`reach: \"url\"`). The URL is the build's\n * Storybook root, `<publicOrigin>/_s/<token>/`; it carries the token and is never logged. Its\n * lifetime comes from the pass's render budget unless the project sets one (`storybookUrlTtl`).\n * Every token minted here carries the signed `capture` purpose, the only claim that lets the\n * server opt a response into browser-private caching.\n */\nimport type { CaptureRunner, CaptureTarget } from \"../adapters/capture-runner.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport {\n signStorybookToken,\n storybookTokenPath,\n storybookUrlTtl,\n} from \"../utils/storybook-token.ts\";\n\n/** What a pass tells the source: how long it may run, which sizes the token's lifetime. */\nexport interface StorybookUrlPass {\n /** The render budget of the pass in ms (`renderBudgetMs`); unset when the pass has none. */\n budgetMs?: number | undefined;\n}\n\n/** Gives the URL a target's browser opens, or `undefined` when it needs none (or none can be made). */\nexport type StorybookUrlSource = ((\n target: CaptureTarget,\n pass?: StorybookUrlPass,\n) => Promise<string | undefined>) & {\n /**\n * The lifetime in seconds a token minted for a pass of this budget gets (the project's override\n * or the derived, capped one). A pass that outlasts it is rendered in slices, each with a fresh\n * token (`renderInSlices`). `undefined` for a target that needs no signed URL (it is never\n * sliced), and absent on a source that has no such limit.\n */\n ttlSeconds?: (target: CaptureTarget, budgetMs: number) => number | undefined;\n};\n\n/** What minting needs of a capture job. */\nexport interface StorybookUrlOptions {\n runner: Pick<CaptureRunner, \"metadata\">;\n secret?: SecretInput;\n publicOrigin?: (() => Promise<string | undefined>) | undefined;\n}\n\n/**\n * A source of signed URLs for one build. Every call mints a fresh token, so a shard that waited in\n * the queue or a retried pass is never handed a token minted before it started.\n */\nexport function storybookUrlSource(\n options: StorybookUrlOptions,\n subject: {\n build: Pick<Build, \"id\" | \"projectId\">;\n project: Pick<Project, \"storybookUrlTtlSeconds\">;\n },\n): StorybookUrlSource {\n const source: StorybookUrlSource = async (target, pass = {}) => {\n const origin = needsUrl(options.runner, target) ? await options.publicOrigin?.() : undefined;\n return origin !== undefined && options.secret\n ? `${origin}${mint(options.secret, subject, pass)}`\n : undefined;\n };\n source.ttlSeconds = (target, budgetMs): number | undefined =>\n needsUrl(options.runner, target)\n ? storybookUrlTtl({ override: subject.project.storybookUrlTtlSeconds, budgetMs })\n : undefined;\n return source;\n}\n\n/** The path of a fresh token for one pass of the build. */\nfunction mint(\n secret: SecretInput,\n subject: Parameters<typeof storybookUrlSource>[1],\n pass: StorybookUrlPass,\n): string {\n const ttlSeconds = storybookUrlTtl({\n override: subject.project.storybookUrlTtlSeconds,\n budgetMs: pass.budgetMs ?? 0,\n });\n const { id: buildId, projectId } = subject.build;\n return storybookTokenPath(\n signStorybookToken({ secret, projectId, buildId, ttlSeconds, purpose: \"capture\" }),\n );\n}\n\nfunction needsUrl(runner: Pick<CaptureRunner, \"metadata\">, target: CaptureTarget): boolean {\n return runner.metadata.targets?.some((t) => t.target === target && t.reach === \"url\") ?? false;\n}\n"],"mappings":";;;;;;;;;AAiDA,SAAgB,mBACd,SACA,SAIoB;CACpB,MAAM,SAA6B,OAAO,QAAQ,OAAO,CAAC,MAAM;EAC9D,MAAM,SAAS,SAAS,QAAQ,QAAQ,MAAM,IAAI,MAAM,QAAQ,eAAe,IAAI,KAAA;EACnF,OAAO,WAAW,KAAA,KAAa,QAAQ,SACnC,GAAG,SAAS,KAAK,QAAQ,QAAQ,SAAS,IAAI,MAC9C,KAAA;CACN;CACA,OAAO,cAAc,QAAQ,aAC3B,SAAS,QAAQ,QAAQ,MAAM,IAC3B,gBAAgB;EAAE,UAAU,QAAQ,QAAQ;EAAwB;CAAS,CAAC,IAC9E,KAAA;CACN,OAAO;AACT;;AAGA,SAAS,KACP,QACA,SACA,MACQ;CACR,MAAM,aAAa,gBAAgB;EACjC,UAAU,QAAQ,QAAQ;EAC1B,UAAU,KAAK,YAAY;CAC7B,CAAC;CACD,MAAM,EAAE,IAAI,SAAS,cAAc,QAAQ;CAC3C,OAAO,mBACL,mBAAmB;EAAE;EAAQ;EAAW;EAAS;EAAY,SAAS;CAAU,CAAC,CACnF;AACF;AAEA,SAAS,SAAS,QAAyC,QAAgC;CACzF,OAAO,OAAO,SAAS,SAAS,MAAM,MAAM,EAAE,WAAW,UAAU,EAAE,UAAU,KAAK,KAAK;AAC3F"}
|