@storyshelf/core 0.7.0 → 0.9.0-next-d2cc082
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/capture-queue.d.mts +0 -2
- package/dist/adapter/capture-runner.d.mts +10 -4
- package/dist/adapter/capture-runner.mjs +7 -2
- package/dist/adapter/database.d.mts +63 -3
- package/dist/adapter/database.mjs +4 -3
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +0 -2
- package/dist/adapter/design.mjs +1 -2
- package/dist/adapter/email-sender.d.mts +0 -2
- package/dist/adapter/metadata.d.mts +0 -2
- package/dist/adapter/oauth.d.mts +0 -2
- package/dist/adapter/setup.d.mts +0 -2
- package/dist/adapter/setup.mjs +1 -2
- package/dist/adapter/storage.d.mts +1 -3
- package/dist/adapter/webhook-events.d.mts +0 -2
- package/dist/adapter/webhook-events.mjs +12 -13
- package/dist/adapter/webhook-events.mjs.map +1 -1
- package/dist/adapters/capture-target-offers.d.mts +0 -2
- package/dist/adapters/capture-target-offers.mjs +1 -2
- package/dist/adapters/capture-target.d.mts +0 -2
- package/dist/adapters/capture-target.mjs +1 -2
- package/dist/adapters/notifier/build-comment.d.mts +0 -2
- package/dist/adapters/notifier/build-comment.mjs +1 -2
- package/dist/adapters/notifier/build-state.d.mts +0 -2
- package/dist/adapters/notifier/build-state.mjs +1 -2
- package/dist/adapters/notifier/events.d.mts +0 -2
- package/dist/adapters/notifier/events.mjs +1 -2
- package/dist/adapters/notifier/format.d.mts +0 -2
- package/dist/adapters/notifier/format.mjs +1 -2
- package/dist/adapters/notifier/provider.d.mts +0 -2
- package/dist/adapters/notifier/provider.mjs +1 -2
- package/dist/adapters/notifier/state.d.mts +0 -2
- package/dist/adapters/notifier/state.mjs +1 -2
- package/dist/adapters/notifier/types.d.mts +0 -2
- package/dist/adapters/remote-browser.d.mts +120 -31
- package/dist/adapters/remote-browser.mjs +134 -34
- package/dist/adapters/remote-browser.mjs.map +1 -1
- package/dist/adapters/remote-lease-attempt.mjs +39 -0
- package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
- package/dist/adapters/remote-lease-error.d.mts +25 -0
- package/dist/adapters/remote-lease-error.mjs +54 -0
- package/dist/adapters/remote-lease-error.mjs.map +1 -0
- package/dist/adapters/remote-lease-held.d.mts +15 -0
- package/dist/adapters/remote-lease-held.mjs +86 -0
- package/dist/adapters/remote-lease-held.mjs.map +1 -0
- package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
- package/dist/adapters/remote-lease-shutdown.mjs +32 -0
- package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
- package/dist/adapters/remote-lease.d.mts +39 -0
- package/dist/adapters/remote-lease.mjs +175 -0
- package/dist/adapters/remote-lease.mjs.map +1 -0
- package/dist/adapters/remote-limiter.mjs +121 -0
- package/dist/adapters/remote-limiter.mjs.map +1 -0
- package/dist/adapters/remote-pool.d.mts +56 -0
- package/dist/adapters/remote-pool.mjs +195 -0
- package/dist/adapters/remote-pool.mjs.map +1 -0
- package/dist/adapters/remote-sizing.d.mts +44 -0
- package/dist/adapters/remote-sizing.mjs +53 -0
- package/dist/adapters/remote-sizing.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +0 -2
- package/dist/adapters/render-runs.mjs +1 -2
- package/dist/adapters/setup-runner.d.mts +0 -2
- package/dist/adapters/setup-runner.mjs +1 -2
- package/dist/adapters/sources.d.mts +0 -2
- package/dist/adapters/sources.mjs +1 -2
- package/dist/adapters/validate.d.mts +0 -2
- package/dist/adapters/validate.mjs +2 -2
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +0 -2
- package/dist/ai.mjs +1 -2
- package/dist/auth.d.mts +0 -2
- package/dist/auth.mjs +1 -2
- package/dist/brand-packs.d.mts +0 -2
- package/dist/brand-packs.mjs +0 -1
- package/dist/capture/a11y/axe-source.d.mts +0 -2
- package/dist/capture/a11y/axe-source.mjs +1 -2
- package/dist/capture/a11y/in-page.d.mts +1 -3
- package/dist/capture/a11y/in-page.mjs +3 -4
- package/dist/capture/a11y/in-page.mjs.map +1 -1
- package/dist/capture/a11y/map.d.mts +0 -2
- package/dist/capture/a11y/map.mjs +1 -2
- package/dist/capture/a11y/options.d.mts +0 -2
- package/dist/capture/a11y/options.mjs +1 -2
- package/dist/capture/adapter.d.mts +5 -2
- package/dist/capture/adapter.mjs +1 -2
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +0 -2
- package/dist/capture/affected.mjs +1 -2
- package/dist/capture/attempt-logger.mjs +1 -2
- package/dist/capture/browsers.d.mts +0 -2
- package/dist/capture/browsers.mjs +1 -2
- package/dist/capture/build-state/deliver.d.mts +0 -2
- package/dist/capture/build-state/deliver.mjs +1 -2
- package/dist/capture/build-state/notify-job.d.mts +0 -2
- package/dist/capture/build-state/notify-job.mjs +1 -2
- package/dist/capture/build-state/publisher.d.mts +0 -2
- package/dist/capture/build-state/publisher.mjs +1 -2
- package/dist/capture/build-state/resume.d.mts +0 -2
- package/dist/capture/build-state/resume.mjs +1 -2
- package/dist/capture/build-state/types.d.mts +1 -3
- package/dist/capture/catalog/catalogue.d.mts +0 -2
- package/dist/capture/catalog/catalogue.mjs +1 -2
- package/dist/capture/catalog/components.d.mts +0 -2
- package/dist/capture/catalog/components.mjs +1 -2
- package/dist/capture/catalog/history.d.mts +0 -2
- package/dist/capture/catalog/history.mjs +1 -2
- package/dist/capture/catalog/hook.d.mts +0 -2
- package/dist/capture/catalog/hook.mjs +1 -2
- package/dist/capture/catalog/index.d.mts +2 -2
- package/dist/capture/catalog/load.d.mts +0 -2
- package/dist/capture/catalog/load.mjs +1 -2
- package/dist/capture/catalog/parse.d.mts +0 -2
- package/dist/capture/catalog/parse.mjs +1 -2
- package/dist/capture/catalog/read.d.mts +6 -3
- package/dist/capture/catalog/read.mjs +14 -8
- package/dist/capture/catalog/read.mjs.map +1 -1
- package/dist/capture/catalog/types.d.mts +0 -2
- package/dist/capture/comparison-reference.d.mts +27 -0
- package/dist/capture/comparison-reference.mjs +75 -0
- package/dist/capture/comparison-reference.mjs.map +1 -0
- package/dist/capture/consumer/capture-handler.d.mts +1 -3
- package/dist/capture/consumer/capture-handler.mjs +1 -2
- package/dist/capture/consumer/idle.d.mts +0 -2
- package/dist/capture/consumer/idle.mjs +1 -2
- package/dist/capture/consumer/index.d.mts +0 -2
- package/dist/capture/consumer/index.mjs +1 -2
- package/dist/capture/consumer/loop.mjs +1 -2
- package/dist/capture/consumer/metrics.d.mts +0 -2
- package/dist/capture/consumer/metrics.mjs +1 -2
- package/dist/capture/consumer/retry.d.mts +0 -2
- package/dist/capture/consumer/retry.mjs +1 -2
- package/dist/capture/consumer/run-job.mjs +1 -2
- package/dist/capture/consumer/startup.mjs +1 -2
- package/dist/capture/consumer/types.d.mts +0 -2
- package/dist/capture/crash-failure.d.mts +0 -2
- package/dist/capture/crash-failure.mjs +1 -2
- package/dist/capture/db-queue/cadence.d.mts +0 -2
- package/dist/capture/db-queue/cadence.mjs +1 -2
- package/dist/capture/db-queue/claim.d.mts +0 -2
- package/dist/capture/db-queue/claim.mjs +20 -4
- package/dist/capture/db-queue/claim.mjs.map +1 -1
- package/dist/capture/db-queue/clock.mjs +1 -2
- package/dist/capture/db-queue/codec.mjs +1 -2
- package/dist/capture/db-queue/index.d.mts +0 -2
- package/dist/capture/db-queue/index.mjs +3 -3
- package/dist/capture/db-queue/index.mjs.map +1 -1
- package/dist/capture/db-queue/keeper.d.mts +0 -2
- package/dist/capture/db-queue/keeper.mjs +1 -2
- package/dist/capture/db-queue/lease.mjs +1 -2
- package/dist/capture/db-queue/lifecycle.mjs +1 -2
- package/dist/capture/db-queue/operations.mjs +1 -2
- package/dist/capture/db-queue/poll-loop.d.mts +0 -2
- package/dist/capture/db-queue/poll-loop.mjs +1 -2
- package/dist/capture/db-queue/poll.d.mts +0 -2
- package/dist/capture/db-queue/poll.mjs +1 -2
- package/dist/capture/db-queue/timers.d.mts +0 -2
- package/dist/capture/db-queue/timers.mjs +1 -2
- package/dist/capture/db-queue/types.d.mts +0 -2
- package/dist/capture/db-queue/types.mjs +1 -2
- package/dist/capture/db-queue/types.mjs.map +1 -1
- package/dist/capture/db-queue/wake.mjs +1 -2
- package/dist/capture/design.mjs +1 -2
- package/dist/capture/diff-record.mjs +1 -2
- package/dist/capture/dispatch.d.mts +1 -3
- package/dist/capture/dispatch.mjs +17 -5
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +1 -2
- package/dist/capture/failure-sets.mjs +1 -2
- package/dist/capture/fingerprint.d.mts +19 -8
- package/dist/capture/fingerprint.mjs +43 -14
- package/dist/capture/fingerprint.mjs.map +1 -1
- package/dist/capture/forked/channel.d.mts +0 -2
- package/dist/capture/forked/child.d.mts +0 -2
- package/dist/capture/forked/child.mjs +1 -2
- package/dist/capture/forked/crash-tracker.mjs +1 -2
- package/dist/capture/forked/decision.d.mts +0 -2
- package/dist/capture/forked/decision.mjs +1 -2
- package/dist/capture/forked/exit.mjs +1 -2
- package/dist/capture/forked/fork.d.mts +0 -2
- package/dist/capture/forked/fork.mjs +1 -2
- package/dist/capture/forked/forked-capture.d.mts +0 -2
- package/dist/capture/forked/forked-capture.mjs +1 -2
- package/dist/capture/forked/lease-book.mjs +1 -2
- package/dist/capture/forked/protocol.d.mts +0 -2
- package/dist/capture/forked/protocol.mjs +1 -2
- package/dist/capture/forked/runs.mjs +1 -2
- package/dist/capture/guards.mjs +1 -2
- package/dist/capture/ignore-regions.d.mts +36 -0
- package/dist/capture/ignore-regions.mjs +65 -0
- package/dist/capture/ignore-regions.mjs.map +1 -0
- package/dist/capture/isolate.mjs +1 -2
- package/dist/capture/job-lease.d.mts +0 -2
- package/dist/capture/job-lease.mjs +1 -2
- package/dist/capture/matrix.mjs +4 -4
- package/dist/capture/matrix.mjs.map +1 -1
- package/dist/capture/modes.d.mts +0 -2
- package/dist/capture/modes.mjs +2 -3
- package/dist/capture/modes.mjs.map +1 -1
- package/dist/capture/orchestrator.d.mts +0 -2
- package/dist/capture/orchestrator.mjs +2 -3
- package/dist/capture/pipeline.d.mts +0 -2
- package/dist/capture/pipeline.mjs +44 -10
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +0 -2
- package/dist/capture/progress.mjs +1 -2
- package/dist/capture/publish/accept.d.mts +0 -2
- package/dist/capture/publish/accept.mjs +1 -2
- package/dist/capture/publish/complete.d.mts +1 -3
- package/dist/capture/publish/complete.mjs +1 -2
- package/dist/capture/publish/extract.d.mts +0 -2
- package/dist/capture/publish/extract.mjs +6 -5
- package/dist/capture/publish/extract.mjs.map +1 -1
- package/dist/capture/publish/handler.d.mts +1 -3
- package/dist/capture/publish/handler.mjs +1 -2
- package/dist/capture/publish/index.d.mts +2 -1
- package/dist/capture/publish/invalid-zip.d.mts +8 -0
- package/dist/capture/publish/invalid-zip.mjs +11 -0
- package/dist/capture/publish/invalid-zip.mjs.map +1 -0
- package/dist/capture/publish/limits.d.mts +0 -2
- package/dist/capture/publish/limits.mjs +1 -2
- package/dist/capture/publish/published.d.mts +0 -2
- package/dist/capture/publish/published.mjs +1 -2
- package/dist/capture/publish/run.d.mts +1 -3
- package/dist/capture/publish/run.mjs +1 -2
- package/dist/capture/rediff.d.mts +1 -3
- package/dist/capture/rediff.mjs +19 -6
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +1 -2
- package/dist/capture/render.mjs.map +1 -1
- package/dist/capture/scratch.d.mts +0 -2
- package/dist/capture/scratch.mjs +2 -4
- package/dist/capture/scratch.mjs.map +1 -1
- package/dist/capture/shard-plan.mjs +1 -2
- package/dist/capture/shards/context.mjs +3 -3
- package/dist/capture/shards/context.mjs.map +1 -1
- package/dist/capture/shards/execute.mjs +4 -3
- package/dist/capture/shards/execute.mjs.map +1 -1
- package/dist/capture/shards/fan-out.mjs +1 -2
- package/dist/capture/shards/history.mjs +1 -2
- package/dist/capture/shards/reduce.mjs +1 -2
- package/dist/capture/sizing.d.mts +0 -2
- package/dist/capture/sizing.mjs +1 -2
- package/dist/capture/skip-checks.mjs +1 -2
- package/dist/capture/statics.d.mts +0 -2
- package/dist/capture/statics.mjs +2 -4
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +0 -2
- package/dist/capture/storybook-urls.mjs +3 -3
- package/dist/capture/storybook-urls.mjs.map +1 -1
- package/dist/capture/storybook.d.mts +0 -2
- package/dist/capture/storybook.mjs +8 -4
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +1 -2
- package/dist/capture/targets.d.mts +0 -2
- package/dist/capture/targets.mjs +1 -2
- package/dist/capture/tests.d.mts +0 -2
- package/dist/capture/tests.mjs +13 -3
- package/dist/capture/tests.mjs.map +1 -1
- package/dist/capture/token-chunks.mjs +24 -7
- package/dist/capture/token-chunks.mjs.map +1 -1
- package/dist/capture.d.mts +5 -2
- package/dist/capture.mjs +5 -2
- package/dist/config.d.mts +16 -3
- package/dist/config.mjs +6 -3
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +0 -2
- package/dist/diff.mjs +1 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/insights/budget.d.mts +0 -2
- package/dist/insights/budget.mjs +1 -2
- package/dist/insights/envelope.d.mts +0 -2
- package/dist/insights/envelope.mjs +1 -2
- package/dist/insights/evidence-health.d.mts +0 -2
- package/dist/insights/evidence-health.mjs +1 -2
- package/dist/insights/evidence.d.mts +0 -2
- package/dist/insights/evidence.mjs +1 -2
- package/dist/insights/hash.d.mts +0 -2
- package/dist/insights/hash.mjs +1 -2
- package/dist/insights/limits.d.mts +0 -2
- package/dist/insights/limits.mjs +1 -2
- package/dist/insights/prompts/v1.d.mts +0 -2
- package/dist/insights/prompts/v1.mjs +1 -2
- package/dist/insights/redact.d.mts +0 -2
- package/dist/insights/redact.mjs +1 -2
- package/dist/insights/text.mjs +1 -2
- package/dist/logger.d.mts +0 -2
- package/dist/logger.mjs +1 -2
- package/dist/models/ai-budget-alert.d.mts +0 -2
- package/dist/models/ai-budget-alert.mjs +1 -2
- package/dist/models/ai-usage.d.mts +0 -2
- package/dist/models/ai-usage.mjs +1 -2
- package/dist/models/baseline-approval.d.mts +18 -0
- package/dist/models/baseline-approval.mjs +41 -0
- package/dist/models/baseline-approval.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +0 -2
- package/dist/models/baseline-archive.mjs +1 -2
- package/dist/models/baseline-atomic.mjs +66 -0
- package/dist/models/baseline-atomic.mjs.map +1 -0
- package/dist/models/baseline-conflict.d.mts +6 -0
- package/dist/models/baseline-conflict.mjs +9 -0
- package/dist/models/baseline-conflict.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +0 -2
- package/dist/models/baseline-export.mjs +1 -2
- package/dist/models/baseline-import.mjs +1 -2
- package/dist/models/baseline-origin.d.mts +16 -0
- package/dist/models/baseline-origin.mjs +35 -0
- package/dist/models/baseline-origin.mjs.map +1 -0
- package/dist/models/baseline-status.d.mts +0 -2
- package/dist/models/baseline-status.mjs +1 -2
- package/dist/models/baseline-transfer.d.mts +1 -2
- package/dist/models/baseline-transfer.mjs +30 -2
- package/dist/models/baseline-transfer.mjs.map +1 -1
- package/dist/models/baseline.d.mts +5 -2
- package/dist/models/baseline.mjs +91 -12
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +0 -2
- package/dist/models/build-component.mjs +1 -2
- package/dist/models/build-review-status.d.mts +0 -2
- package/dist/models/build-review-status.mjs +1 -2
- package/dist/models/build-review.d.mts +0 -2
- package/dist/models/build-review.mjs +1 -2
- package/dist/models/build.d.mts +1 -3
- package/dist/models/build.mjs +14 -15
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +0 -2
- package/dist/models/capture-attempt.mjs +1 -2
- package/dist/models/capture-job.d.mts +0 -2
- package/dist/models/capture-job.mjs +1 -2
- package/dist/models/capture-log.d.mts +0 -2
- package/dist/models/capture-log.mjs +1 -2
- package/dist/models/capture-shard.d.mts +0 -2
- package/dist/models/capture-shard.mjs +1 -2
- package/dist/models/changeset.d.mts +0 -2
- package/dist/models/changeset.mjs +1 -2
- package/dist/models/columns.mjs +1 -2
- package/dist/models/comment.d.mts +0 -2
- package/dist/models/comment.mjs +1 -2
- package/dist/models/component.d.mts +0 -2
- package/dist/models/component.mjs +1 -2
- package/dist/models/connections.d.mts +0 -2
- package/dist/models/connections.mjs +1 -2
- package/dist/models/content-ref.d.mts +0 -2
- package/dist/models/content-ref.mjs +1 -2
- package/dist/models/credentials.d.mts +0 -2
- package/dist/models/credentials.mjs +1 -2
- package/dist/models/design-credentials.d.mts +0 -2
- package/dist/models/design-credentials.mjs +1 -2
- package/dist/models/design-health.d.mts +16 -3
- package/dist/models/design-health.mjs +21 -3
- package/dist/models/design-health.mjs.map +1 -1
- package/dist/models/design-nodes.d.mts +11 -4
- package/dist/models/design-nodes.mjs +18 -3
- package/dist/models/design-nodes.mjs.map +1 -1
- package/dist/models/design-renders.mjs +1 -2
- package/dist/models/design-resolve.d.mts +0 -2
- package/dist/models/design-resolve.mjs +1 -2
- package/dist/models/design.d.mts +0 -2
- package/dist/models/design.mjs +1 -2
- package/dist/models/inherited-ratio.d.mts +0 -2
- package/dist/models/inherited-ratio.mjs +1 -2
- package/dist/models/insight.d.mts +0 -2
- package/dist/models/insight.mjs +1 -2
- package/dist/models/label.d.mts +0 -2
- package/dist/models/label.mjs +1 -2
- package/dist/models/member.d.mts +0 -2
- package/dist/models/member.mjs +1 -2
- package/dist/models/notification-channel.d.mts +0 -2
- package/dist/models/notification-channel.mjs +1 -2
- package/dist/models/notification-subscription.d.mts +0 -2
- package/dist/models/notification-subscription.mjs +1 -2
- package/dist/models/project-group-mapping.d.mts +0 -2
- package/dist/models/project-group-mapping.mjs +1 -2
- package/dist/models/project.d.mts +0 -2
- package/dist/models/project.mjs +1 -2
- package/dist/models/public-origin.d.mts +0 -2
- package/dist/models/public-origin.mjs +1 -2
- package/dist/models/review-switch-off.d.mts +0 -2
- package/dist/models/review-switch-off.mjs +1 -2
- package/dist/models/site-settings.d.mts +0 -2
- package/dist/models/site-settings.mjs +1 -2
- package/dist/models/snapshot.d.mts +3 -2
- package/dist/models/snapshot.mjs +4 -2
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +0 -2
- package/dist/models/token.mjs +1 -2
- package/dist/models/usage.d.mts +0 -2
- package/dist/models/usage.mjs +1 -2
- package/dist/models/user.d.mts +0 -2
- package/dist/models/user.mjs +1 -2
- package/dist/models/webhook.d.mts +0 -2
- package/dist/models/webhook.mjs +1 -2
- package/dist/models.d.mts +6 -3
- package/dist/models.mjs +6 -3
- package/dist/options.d.mts +0 -2
- package/dist/options.mjs +2 -4
- package/dist/options.mjs.map +1 -1
- package/dist/paths.d.mts +3 -3
- package/dist/paths.mjs +7 -3
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +1 -3
- package/dist/retention/ai.mjs +0 -1
- package/dist/retention/pending.mjs +1 -2
- package/dist/retention/published-gzip-sweep.mjs +28 -0
- package/dist/retention/published-gzip-sweep.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +0 -2
- package/dist/retention/screenshot-sweep.mjs +1 -2
- package/dist/retention.d.mts +0 -2
- package/dist/retention.mjs +5 -2
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +2 -2
- package/dist/schema/build-component.d.mts +0 -2
- package/dist/schema/build-review.d.mts +0 -2
- package/dist/schema/build.d.mts +0 -2
- package/dist/schema/capture-attempt.d.mts +0 -2
- package/dist/schema/capture-job.d.mts +0 -2
- package/dist/schema/capture-shard.d.mts +0 -2
- package/dist/schema/comment.d.mts +0 -2
- package/dist/schema/connection.d.mts +0 -2
- package/dist/schema/content-ref.d.mts +0 -2
- package/dist/schema/design.d.mts +2 -2
- package/dist/schema/insight.d.mts +0 -2
- package/dist/schema/label.d.mts +0 -2
- package/dist/schema/member.d.mts +0 -2
- package/dist/schema/notification.d.mts +0 -2
- package/dist/schema/project-group-mapping.d.mts +0 -2
- package/dist/schema/project.d.mts +0 -2
- package/dist/schema/site-setting.d.mts +0 -2
- package/dist/schema/snapshot.d.mts +5 -2
- package/dist/schema/token.d.mts +0 -2
- package/dist/schema/usage.d.mts +0 -2
- package/dist/schema/user.d.mts +0 -2
- package/dist/schema/webhook.d.mts +0 -2
- package/dist/schema.d.mts +4 -6
- package/dist/serverless.d.mts +0 -2
- package/dist/serverless.mjs +1 -2
- package/dist/test-helpers/build-reference-contract.d.mts +14 -0
- package/dist/test-helpers/build-reference-contract.mjs +66 -0
- package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +0 -2
- package/dist/test-helpers/create-project.mjs +1 -2
- package/dist/test-helpers/database-contract.d.mts +0 -2
- package/dist/test-helpers/database-contract.mjs +37 -2
- package/dist/test-helpers/database-contract.mjs.map +1 -1
- package/dist/test-helpers/deferred.d.mts +0 -2
- package/dist/test-helpers/deferred.mjs +1 -2
- package/dist/test-helpers/design-contract.d.mts +0 -2
- package/dist/test-helpers/design-contract.mjs +1 -2
- package/dist/test-helpers/fake-adapters.d.mts +0 -2
- package/dist/test-helpers/fake-adapters.mjs +1 -2
- package/dist/test-helpers/fake-database.d.mts +0 -2
- package/dist/test-helpers/fake-database.mjs +7 -2
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +0 -2
- package/dist/test-helpers/fake-design.mjs +1 -2
- package/dist/test-helpers/fake-notifier.d.mts +0 -2
- package/dist/test-helpers/fake-notifier.mjs +1 -2
- package/dist/test-helpers/fake-storage.d.mts +0 -2
- package/dist/test-helpers/fake-storage.mjs +1 -2
- package/dist/test-helpers/fake-tables.mjs +6 -2
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +0 -2
- package/dist/test-helpers/manual-clock.mjs +1 -2
- package/dist/test-helpers/notifier-contract.d.mts +0 -2
- package/dist/test-helpers/notifier-contract.mjs +1 -2
- package/dist/test-helpers/queue-contract.d.mts +0 -2
- package/dist/test-helpers/queue-contract.mjs +1 -2
- package/dist/test-helpers/queue-multi-instance.d.mts +0 -2
- package/dist/test-helpers/queue-multi-instance.mjs +1 -2
- package/dist/test-helpers/runner-contract.d.mts +0 -2
- package/dist/test-helpers/runner-contract.mjs +1 -2
- package/dist/test-helpers/sql-chunks.mjs +2 -2
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.d.mts +0 -2
- package/dist/test-helpers/storage-contract.mjs +1 -2
- package/dist/test-helpers.d.mts +2 -1
- package/dist/test-helpers.mjs +2 -1
- package/dist/tracing.mjs +36 -6
- package/dist/tracing.mjs.map +1 -1
- package/dist/types.d.mts +0 -2
- package/dist/types.mjs +1 -2
- package/dist/urls.d.mts +0 -2
- package/dist/urls.mjs +1 -2
- package/dist/utils/adaptive-concurrency.d.mts +0 -2
- package/dist/utils/adaptive-concurrency.mjs +1 -2
- package/dist/utils/connect-retry.d.mts +2 -2
- package/dist/utils/connect-retry.mjs +2 -3
- package/dist/utils/connect-retry.mjs.map +1 -1
- package/dist/utils/encrypt.d.mts +0 -2
- package/dist/utils/encrypt.mjs +1 -2
- package/dist/utils/environment.d.mts +0 -2
- package/dist/utils/environment.mjs +1 -2
- package/dist/utils/hash.d.mts +0 -2
- package/dist/utils/hash.mjs +1 -2
- package/dist/utils/http.d.mts +13 -3
- package/dist/utils/http.mjs +79 -17
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +0 -2
- package/dist/utils/map-adaptive.mjs +1 -2
- package/dist/utils/map-limit.d.mts +0 -2
- package/dist/utils/map-limit.mjs +1 -2
- package/dist/utils/mime.d.mts +0 -2
- package/dist/utils/mime.mjs +1 -2
- package/dist/utils/page-limiter.d.mts +0 -2
- package/dist/utils/page-limiter.mjs +1 -2
- package/dist/utils/path-security.d.mts +0 -2
- package/dist/utils/path-security.mjs +2 -4
- package/dist/utils/path-security.mjs.map +1 -1
- package/dist/utils/public-origin.d.mts +0 -2
- package/dist/utils/public-origin.mjs +1 -2
- package/dist/utils/redact.d.mts +0 -2
- package/dist/utils/redact.mjs +1 -2
- package/dist/utils/remote-environment.d.mts +40 -3
- package/dist/utils/remote-environment.mjs +52 -3
- package/dist/utils/remote-environment.mjs.map +1 -1
- package/dist/utils/resources.d.mts +0 -2
- package/dist/utils/resources.mjs +1 -2
- package/dist/utils/screenshot-store.d.mts +0 -2
- package/dist/utils/screenshot-store.mjs +1 -2
- package/dist/utils/session-gate.d.mts +7 -6
- package/dist/utils/session-gate.mjs +28 -12
- package/dist/utils/session-gate.mjs.map +1 -1
- package/dist/utils/storybook-token.d.mts +11 -3
- package/dist/utils/storybook-token.mjs +10 -9
- package/dist/utils/storybook-token.mjs.map +1 -1
- package/dist/utils/storybook-url.d.mts +0 -2
- package/dist/utils/storybook-url.mjs +1 -2
- package/dist/utils/timing.d.mts +0 -2
- package/dist/utils/timing.mjs +1 -2
- package/dist/utils/ulid.d.mts +0 -2
- package/dist/utils/ulid.mjs +1 -2
- package/dist/utils/zip-read.mjs +1 -2
- package/dist/utils/zip-write.d.mts +0 -2
- package/dist/utils/zip-write.mjs +1 -2
- package/dist/utils.d.mts +5 -5
- package/dist/utils.mjs +5 -5
- package/package.json +6 -15
package/dist/models.mjs
CHANGED
|
@@ -4,8 +4,10 @@ import.meta.url;
|
|
|
4
4
|
import { WebhookModel } from "./models/webhook.mjs";
|
|
5
5
|
import { CaptureAttemptModel, emitAttemptLog } from "./models/capture-attempt.mjs";
|
|
6
6
|
import { BuildModel, isPublicBuild } from "./models/build.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { BaselineWriteConflict } from "./models/baseline-conflict.mjs";
|
|
8
8
|
import { NO_BASELINE, baselineStatus, isBaselineDrifted } from "./models/baseline-status.mjs";
|
|
9
|
+
import { BaselineApprovalConflict } from "./models/baseline-approval.mjs";
|
|
10
|
+
import { BaselineModel } from "./models/baseline.mjs";
|
|
9
11
|
import { SnapshotModel } from "./models/snapshot.mjs";
|
|
10
12
|
import { CaptureShardModel } from "./models/capture-shard.mjs";
|
|
11
13
|
import { ProjectModel } from "./models/project.mjs";
|
|
@@ -18,6 +20,7 @@ import { PUBLIC_ORIGIN_KEY, createPublicOriginResolver } from "./models/public-o
|
|
|
18
20
|
import { CaptureLogModel } from "./models/capture-log.mjs";
|
|
19
21
|
import { BuildComponentModel } from "./models/build-component.mjs";
|
|
20
22
|
import { normalizeImportPath, resolveDesigns } from "./models/design-resolve.mjs";
|
|
23
|
+
import { resolveBaselineOrigin } from "./models/baseline-origin.mjs";
|
|
21
24
|
import { BASELINE_ARCHIVE_FORMAT, BASELINE_ARCHIVE_LIMITS, BASELINE_ARCHIVE_VERSION, BaselineArchiveError } from "./models/baseline-archive.mjs";
|
|
22
25
|
import { BaselineTransferModel } from "./models/baseline-transfer.mjs";
|
|
23
26
|
import { AiBudgetAlertModel } from "./models/ai-budget-alert.mjs";
|
|
@@ -31,7 +34,7 @@ import { componentHistory, componentPresence, worstStatus } from "./models/compo
|
|
|
31
34
|
import { ConnectionModel } from "./models/connections.mjs";
|
|
32
35
|
import { DesignModel } from "./models/design.mjs";
|
|
33
36
|
import { DesignCredentialModel, PROJECT_SCOPE } from "./models/design-credentials.mjs";
|
|
34
|
-
import { DESIGN_TTL_MS, credentialDaysLeft, isDesignStale } from "./models/design-health.mjs";
|
|
37
|
+
import { DESIGN_TTL_MS, credentialDaysLeft, designChange, isDesignStale } from "./models/design-health.mjs";
|
|
35
38
|
import { DesignNodeModel } from "./models/design-nodes.mjs";
|
|
36
39
|
import { LabelModel } from "./models/label.mjs";
|
|
37
40
|
import { PackageUsageModel, matchUpstreamStory } from "./models/usage.mjs";
|
|
@@ -42,4 +45,4 @@ import { TokenModel } from "./models/token.mjs";
|
|
|
42
45
|
import { UserModel } from "./models/user.mjs";
|
|
43
46
|
import { ContentRefModel } from "./models/content-ref.mjs";
|
|
44
47
|
import { probeCredentials, reencryptCredentials } from "./models/credentials.mjs";
|
|
45
|
-
export { AiBudgetAlertModel, AiUsageModel, BASELINE_ARCHIVE_FORMAT, BASELINE_ARCHIVE_LIMITS, BASELINE_ARCHIVE_VERSION, BaselineArchiveError, BaselineModel, BaselineTransferModel, BuildComponentModel, BuildModel, BuildReviewModel, CaptureAttemptModel, CaptureJobModel, CaptureLogModel, CaptureShardModel, CommentModel, ConnectionModel, ContentRefModel, DESIGN_TTL_MS, DesignCredentialModel, DesignModel, DesignNodeModel, InsightModel, LabelModel, MemberModel, NO_BASELINE, NotificationChannelModel, NotificationSubscriptionModel, PROJECT_SCOPE, PUBLIC_ORIGIN_KEY, PackageUsageModel, ProjectGroupMappingModel, ProjectModel, RECENT_BUILDS_FOR_INHERITED_RATIO, SiteSettingsModel, SnapshotModel, TERMINAL_JOB_STATUSES, TokenModel, UserModel, WebhookModel, acceptPendingWithoutWork, attemptRowId, averageInheritedRatio, baselineStatus, changesetOf, componentHistory, componentPresence, computeChangeset, createPublicOriginResolver, credentialDaysLeft, deriveReviewStatus, emitAttemptLog, findChangesetBaseline, isBaselineDrifted, isDesignStale, isPublicBuild, isTerminalJob, latestVerdicts, matchUpstreamStory, normalizeImportPath, parseAttemptRowId, probeCredentials, reencryptCredentials, resolveDesigns, worstStatus };
|
|
48
|
+
export { AiBudgetAlertModel, AiUsageModel, BASELINE_ARCHIVE_FORMAT, BASELINE_ARCHIVE_LIMITS, BASELINE_ARCHIVE_VERSION, BaselineApprovalConflict, BaselineArchiveError, BaselineModel, BaselineTransferModel, BaselineWriteConflict, BuildComponentModel, BuildModel, BuildReviewModel, CaptureAttemptModel, CaptureJobModel, CaptureLogModel, CaptureShardModel, CommentModel, ConnectionModel, ContentRefModel, DESIGN_TTL_MS, DesignCredentialModel, DesignModel, DesignNodeModel, InsightModel, LabelModel, MemberModel, NO_BASELINE, NotificationChannelModel, NotificationSubscriptionModel, PROJECT_SCOPE, PUBLIC_ORIGIN_KEY, PackageUsageModel, ProjectGroupMappingModel, ProjectModel, RECENT_BUILDS_FOR_INHERITED_RATIO, SiteSettingsModel, SnapshotModel, TERMINAL_JOB_STATUSES, TokenModel, UserModel, WebhookModel, acceptPendingWithoutWork, attemptRowId, averageInheritedRatio, baselineStatus, changesetOf, componentHistory, componentPresence, computeChangeset, createPublicOriginResolver, credentialDaysLeft, deriveReviewStatus, designChange, emitAttemptLog, findChangesetBaseline, isBaselineDrifted, isDesignStale, isPublicBuild, isTerminalJob, latestVerdicts, matchUpstreamStory, normalizeImportPath, parseAttemptRowId, probeCredentials, reencryptCredentials, resolveBaselineOrigin, resolveDesigns, worstStatus };
|
package/dist/options.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { ShelfOptions } from "./config.mjs";
|
|
4
|
-
//#region src/options.d.ts
|
|
5
4
|
/** Where capture scratch lives when `config.scratchDir` is not set. */
|
|
6
5
|
declare const DEFAULT_SCRATCH_DIR: string;
|
|
7
6
|
/**
|
|
@@ -13,6 +12,5 @@ declare const DEFAULT_SCRATCH_DIR: string;
|
|
|
13
12
|
* with or without `testing`.
|
|
14
13
|
*/
|
|
15
14
|
declare function resolveShelfOptions(options: ShelfOptions): ShelfOptions;
|
|
16
|
-
//#endregion
|
|
17
15
|
export { DEFAULT_SCRATCH_DIR, resolveShelfOptions };
|
|
18
16
|
//# sourceMappingURL=options.d.mts.map
|
package/dist/options.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import "node:path";
|
|
1
|
+
import { join } from "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { notificationsConfigSchema } from "./config.mjs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
|
|
7
|
-
//#region src/options.ts
|
|
6
|
+
// src/options.ts
|
|
8
7
|
/** Resolves `ShelfOptions` once at boot: validates `testing.compare` and fills the scratch default. */
|
|
9
8
|
/** Where capture scratch lives when `config.scratchDir` is not set. */
|
|
10
9
|
const DEFAULT_SCRATCH_DIR = join(tmpdir(), "storyshelf");
|
|
@@ -84,7 +83,6 @@ function assertNotificationsConfig(config) {
|
|
|
84
83
|
throw new Error(`Invalid notifications.config: ${issues}`);
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
|
-
//#endregion
|
|
88
86
|
export { DEFAULT_SCRATCH_DIR, resolveShelfOptions };
|
|
89
87
|
|
|
90
88
|
//# sourceMappingURL=options.mjs.map
|
package/dist/options.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.mjs","names":[],"sources":["../src/options.ts"],"sourcesContent":["/** Resolves `ShelfOptions` once at boot: validates `testing.compare` and fills the scratch default. */\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { notificationsConfigSchema, type ShelfOptions } from \"./config.ts\";\n\n/** Where capture scratch lives when `config.scratchDir` is not set. */\nexport const DEFAULT_SCRATCH_DIR: string = join(tmpdir(), \"storyshelf\");\n\n/**\n * Validate and complete the options.\n *\n * - `testing.compare.threads` must be a non-negative integer.\n * - `notifications.config` must match its schema (it used to live in `ShelfConfig`, which validated it).\n * - `config.scratchDir` defaults to {@link DEFAULT_SCRATCH_DIR}: publishing extracts zips there,\n * with or without `testing`.\n */\nexport function resolveShelfOptions(options: ShelfOptions): ShelfOptions {\n assertThreads(options.testing?.compare?.threads);\n assertTestingProcess(options.testing);\n assertTestingBackable(options);\n assertNotificationsConfig(options.notifications?.config);\n if (options.config?.scratchDir !== undefined) {\n return options;\n }\n return { ...options, config: { ...options.config, scratchDir: DEFAULT_SCRATCH_DIR } };\n}\n\n/**\n * A `testing` instance renders here, so it needs a database a long-running server can keep.\n */\nfunction assertTestingBackable(options: ShelfOptions): void {\n if (options.testing === undefined) {\n return;\n }\n if (options.database.metadata.supportsTesting === false) {\n throw new Error(\n `The ${options.database.metadata.name} database cannot back testing: it is reachable only ` +\n `from a platform that ends the process after each request, so nothing would run capture ` +\n `jobs. Remove the testing option to host Storybooks only, or use a network database ` +\n `(Turso, Postgres, MySQL, SQLite on a volume).`,\n );\n }\n}\n\nfunction assertTestingProcess(testing: ShelfOptions[\"testing\"]): void {\n if (testing !== undefined && testing.runner === undefined) {\n throw new Error(\"testing requires testing.runner\");\n }\n if (testing?.workerEntry !== undefined && testing.runner === undefined) {\n throw new Error(\"testing.workerEntry requires testing.runner (it is the in-process fallback)\");\n }\n if (testing?.mode === \"fork\" && testing.workerEntry === undefined) {\n throw new Error('testing.mode \"fork\" requires testing.workerEntry');\n }\n assertShardSize(testing);\n assertTestingNumbers(testing);\n}\n\n/** Numeric `testing` options: concurrency, retries and the Storybook URL rate limit. */\nfunction assertTestingNumbers(testing: ShelfOptions[\"testing\"]): void {\n assertConcurrency(testing?.concurrency);\n assertRetries(testing?.retries);\n assertLeaseSeconds(testing?.leaseSeconds);\n assertStorybookUrlRateLimit(testing?.storybookUrlRateLimit);\n}\n\n/** A whole number of requests per minute; `0` would refuse every render. */\nfunction assertStorybookUrlRateLimit(limit: number | undefined): void {\n if (limit !== undefined && (!Number.isInteger(limit) || limit < 1)) {\n throw new Error(\n `testing.storybookUrlRateLimit must be a positive whole number of requests per minute, got ${String(limit)}`,\n );\n }\n}\n\n/** Retries are counted after the first attempt: `0` runs a job once. */\nfunction assertRetries(retries: number | undefined): void {\n if (retries !== undefined && (!Number.isInteger(retries) || retries < 0)) {\n throw new Error(`testing.retries must be a non-negative integer, got ${String(retries)}`);\n }\n}\n\n/** A lease must be a positive length: `0` would expire every job at once. */\nfunction assertLeaseSeconds(seconds: number | undefined): void {\n if (seconds !== undefined && (!Number.isFinite(seconds) || seconds <= 0)) {\n throw new Error(\n `testing.leaseSeconds must be a positive number of seconds, got ${String(seconds)}`,\n );\n }\n}\n\n/** `0` would deadlock the consumer. */\nfunction assertConcurrency(concurrency: number | undefined): void {\n if (concurrency !== undefined && (!Number.isInteger(concurrency) || concurrency < 1)) {\n throw new Error(`testing.concurrency must be a positive integer, got ${String(concurrency)}`);\n }\n}\n\nfunction assertShardSize(testing: ShelfOptions[\"testing\"]): void {\n const size = testing?.shardSize;\n if (size === undefined) {\n return;\n }\n if (!Number.isInteger(size) || size < 1) {\n throw new Error(`testing.shardSize must be a positive integer, got ${String(size)}`);\n }\n if (testing?.runner === undefined) {\n throw new Error(\"testing.shardSize requires testing.runner (workers set their own shardSize)\");\n }\n}\n\nfunction assertThreads(threads: number | undefined): void {\n if (threads !== undefined && (!Number.isInteger(threads) || threads < 0)) {\n throw new Error(\n `testing.compare.threads must be a non-negative integer, got ${String(threads)}`,\n );\n }\n}\n\nfunction assertNotificationsConfig(config: unknown): void {\n if (config === undefined) {\n return;\n }\n const result = notificationsConfigSchema.safeParse(config);\n if (!result.success) {\n const issues = result.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\"; \");\n throw new Error(`Invalid notifications.config: ${issues}`);\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"options.mjs","names":[],"sources":["../src/options.ts"],"sourcesContent":["/** Resolves `ShelfOptions` once at boot: validates `testing.compare` and fills the scratch default. */\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { notificationsConfigSchema, type ShelfOptions } from \"./config.ts\";\n\n/** Where capture scratch lives when `config.scratchDir` is not set. */\nexport const DEFAULT_SCRATCH_DIR: string = join(tmpdir(), \"storyshelf\");\n\n/**\n * Validate and complete the options.\n *\n * - `testing.compare.threads` must be a non-negative integer.\n * - `notifications.config` must match its schema (it used to live in `ShelfConfig`, which validated it).\n * - `config.scratchDir` defaults to {@link DEFAULT_SCRATCH_DIR}: publishing extracts zips there,\n * with or without `testing`.\n */\nexport function resolveShelfOptions(options: ShelfOptions): ShelfOptions {\n assertThreads(options.testing?.compare?.threads);\n assertTestingProcess(options.testing);\n assertTestingBackable(options);\n assertNotificationsConfig(options.notifications?.config);\n if (options.config?.scratchDir !== undefined) {\n return options;\n }\n return { ...options, config: { ...options.config, scratchDir: DEFAULT_SCRATCH_DIR } };\n}\n\n/**\n * A `testing` instance renders here, so it needs a database a long-running server can keep.\n */\nfunction assertTestingBackable(options: ShelfOptions): void {\n if (options.testing === undefined) {\n return;\n }\n if (options.database.metadata.supportsTesting === false) {\n throw new Error(\n `The ${options.database.metadata.name} database cannot back testing: it is reachable only ` +\n `from a platform that ends the process after each request, so nothing would run capture ` +\n `jobs. Remove the testing option to host Storybooks only, or use a network database ` +\n `(Turso, Postgres, MySQL, SQLite on a volume).`,\n );\n }\n}\n\nfunction assertTestingProcess(testing: ShelfOptions[\"testing\"]): void {\n if (testing !== undefined && testing.runner === undefined) {\n throw new Error(\"testing requires testing.runner\");\n }\n if (testing?.workerEntry !== undefined && testing.runner === undefined) {\n throw new Error(\"testing.workerEntry requires testing.runner (it is the in-process fallback)\");\n }\n if (testing?.mode === \"fork\" && testing.workerEntry === undefined) {\n throw new Error('testing.mode \"fork\" requires testing.workerEntry');\n }\n assertShardSize(testing);\n assertTestingNumbers(testing);\n}\n\n/** Numeric `testing` options: concurrency, retries and the Storybook URL rate limit. */\nfunction assertTestingNumbers(testing: ShelfOptions[\"testing\"]): void {\n assertConcurrency(testing?.concurrency);\n assertRetries(testing?.retries);\n assertLeaseSeconds(testing?.leaseSeconds);\n assertStorybookUrlRateLimit(testing?.storybookUrlRateLimit);\n}\n\n/** A whole number of requests per minute; `0` would refuse every render. */\nfunction assertStorybookUrlRateLimit(limit: number | undefined): void {\n if (limit !== undefined && (!Number.isInteger(limit) || limit < 1)) {\n throw new Error(\n `testing.storybookUrlRateLimit must be a positive whole number of requests per minute, got ${String(limit)}`,\n );\n }\n}\n\n/** Retries are counted after the first attempt: `0` runs a job once. */\nfunction assertRetries(retries: number | undefined): void {\n if (retries !== undefined && (!Number.isInteger(retries) || retries < 0)) {\n throw new Error(`testing.retries must be a non-negative integer, got ${String(retries)}`);\n }\n}\n\n/** A lease must be a positive length: `0` would expire every job at once. */\nfunction assertLeaseSeconds(seconds: number | undefined): void {\n if (seconds !== undefined && (!Number.isFinite(seconds) || seconds <= 0)) {\n throw new Error(\n `testing.leaseSeconds must be a positive number of seconds, got ${String(seconds)}`,\n );\n }\n}\n\n/** `0` would deadlock the consumer. */\nfunction assertConcurrency(concurrency: number | undefined): void {\n if (concurrency !== undefined && (!Number.isInteger(concurrency) || concurrency < 1)) {\n throw new Error(`testing.concurrency must be a positive integer, got ${String(concurrency)}`);\n }\n}\n\nfunction assertShardSize(testing: ShelfOptions[\"testing\"]): void {\n const size = testing?.shardSize;\n if (size === undefined) {\n return;\n }\n if (!Number.isInteger(size) || size < 1) {\n throw new Error(`testing.shardSize must be a positive integer, got ${String(size)}`);\n }\n if (testing?.runner === undefined) {\n throw new Error(\"testing.shardSize requires testing.runner (workers set their own shardSize)\");\n }\n}\n\nfunction assertThreads(threads: number | undefined): void {\n if (threads !== undefined && (!Number.isInteger(threads) || threads < 0)) {\n throw new Error(\n `testing.compare.threads must be a non-negative integer, got ${String(threads)}`,\n );\n }\n}\n\nfunction assertNotificationsConfig(config: unknown): void {\n if (config === undefined) {\n return;\n }\n const result = notificationsConfigSchema.safeParse(config);\n if (!result.success) {\n const issues = result.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\"; \");\n throw new Error(`Invalid notifications.config: ${issues}`);\n }\n}\n"],"mappings":";;;;;;;;AAMA,MAAa,sBAA8B,KAAK,OAAO,GAAG,YAAY;;;;;;;;;AAUtE,SAAgB,oBAAoB,SAAqC;CACvE,cAAc,QAAQ,SAAS,SAAS,OAAO;CAC/C,qBAAqB,QAAQ,OAAO;CACpC,sBAAsB,OAAO;CAC7B,0BAA0B,QAAQ,eAAe,MAAM;CACvD,IAAI,QAAQ,QAAQ,eAAe,KAAA,GACjC,OAAO;CAET,OAAO;EAAE,GAAG;EAAS,QAAQ;GAAE,GAAG,QAAQ;GAAQ,YAAY;EAAoB;CAAE;AACtF;;;;AAKA,SAAS,sBAAsB,SAA6B;CAC1D,IAAI,QAAQ,YAAY,KAAA,GACtB;CAEF,IAAI,QAAQ,SAAS,SAAS,oBAAoB,OAChD,MAAM,IAAI,MACR,OAAO,QAAQ,SAAS,SAAS,KAAK,4QAIxC;AAEJ;AAEA,SAAS,qBAAqB,SAAwC;CACpE,IAAI,YAAY,KAAA,KAAa,QAAQ,WAAW,KAAA,GAC9C,MAAM,IAAI,MAAM,iCAAiC;CAEnD,IAAI,SAAS,gBAAgB,KAAA,KAAa,QAAQ,WAAW,KAAA,GAC3D,MAAM,IAAI,MAAM,6EAA6E;CAE/F,IAAI,SAAS,SAAS,UAAU,QAAQ,gBAAgB,KAAA,GACtD,MAAM,IAAI,MAAM,oDAAkD;CAEpE,gBAAgB,OAAO;CACvB,qBAAqB,OAAO;AAC9B;;AAGA,SAAS,qBAAqB,SAAwC;CACpE,kBAAkB,SAAS,WAAW;CACtC,cAAc,SAAS,OAAO;CAC9B,mBAAmB,SAAS,YAAY;CACxC,4BAA4B,SAAS,qBAAqB;AAC5D;;AAGA,SAAS,4BAA4B,OAAiC;CACpE,IAAI,UAAU,KAAA,MAAc,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,IAC9D,MAAM,IAAI,MACR,6FAA6F,OAAO,KAAK,GAC3G;AAEJ;;AAGA,SAAS,cAAc,SAAmC;CACxD,IAAI,YAAY,KAAA,MAAc,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,IACpE,MAAM,IAAI,MAAM,uDAAuD,OAAO,OAAO,GAAG;AAE5F;;AAGA,SAAS,mBAAmB,SAAmC;CAC7D,IAAI,YAAY,KAAA,MAAc,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,IACpE,MAAM,IAAI,MACR,kEAAkE,OAAO,OAAO,GAClF;AAEJ;;AAGA,SAAS,kBAAkB,aAAuC;CAChE,IAAI,gBAAgB,KAAA,MAAc,CAAC,OAAO,UAAU,WAAW,KAAK,cAAc,IAChF,MAAM,IAAI,MAAM,uDAAuD,OAAO,WAAW,GAAG;AAEhG;AAEA,SAAS,gBAAgB,SAAwC;CAC/D,MAAM,OAAO,SAAS;CACtB,IAAI,SAAS,KAAA,GACX;CAEF,IAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,GACpC,MAAM,IAAI,MAAM,qDAAqD,OAAO,IAAI,GAAG;CAErF,IAAI,SAAS,WAAW,KAAA,GACtB,MAAM,IAAI,MAAM,6EAA6E;AAEjG;AAEA,SAAS,cAAc,SAAmC;CACxD,IAAI,YAAY,KAAA,MAAc,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,IACpE,MAAM,IAAI,MACR,+DAA+D,OAAO,OAAO,GAC/E;AAEJ;AAEA,SAAS,0BAA0B,QAAuB;CACxD,IAAI,WAAW,KAAA,GACb;CAEF,MAAM,SAAS,0BAA0B,UAAU,MAAM;CACzD,IAAI,CAAC,OAAO,SAAS;EACnB,MAAM,SAAS,OAAO,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI;EAC5F,MAAM,IAAI,MAAM,iCAAiC,QAAQ;CAC3D;AACF"}
|
package/dist/paths.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/utils/paths.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Path segment for a mode on a given engine. Chromium keeps the bare mode key
|
|
6
5
|
* so pre-multi-browser objects stay addressable; other engines get `~browser`.
|
|
@@ -22,10 +21,11 @@ declare function designRenderPath(projectId: string, provider: string, fileKey:
|
|
|
22
21
|
declare function baselinePath(projectId: string, branch: string, storyId: string, viewport: string, browser?: string): string;
|
|
23
22
|
/** Storage prefix for an extracted published Storybook. */
|
|
24
23
|
declare function storybookDir(projectId: string, buildId: string): string;
|
|
24
|
+
/** Disposable gzip representation of a content-addressed published file. */
|
|
25
|
+
declare function publishedGzipPath(hash: string): string;
|
|
25
26
|
/** Storage path for an uploaded Storybook zip. */
|
|
26
27
|
declare function storybookZipPath(projectId: string, buildId: string): string;
|
|
27
28
|
/** Storage path of a build's persisted catalog (ADR 0006, decision 9), next to its `storybook/` statics. */
|
|
28
29
|
declare function catalogPath(projectId: string, buildId: string): string;
|
|
29
|
-
|
|
30
|
-
export { baselinePath, catalogPath, contentDir, contentPath, designRenderPath, diffPath, isContentPath, modeSegment, screenshotPath, storybookDir, storybookZipPath };
|
|
30
|
+
export { baselinePath, catalogPath, contentDir, contentPath, designRenderPath, diffPath, isContentPath, modeSegment, publishedGzipPath, screenshotPath, storybookDir, storybookZipPath };
|
|
31
31
|
//# sourceMappingURL=paths.d.mts.map
|
package/dist/paths.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { isSafeSegment } from "./utils/path-security.mjs";
|
|
5
|
-
|
|
5
|
+
// src/utils/paths.ts
|
|
6
6
|
/** Storage path builders. Every segment is validated — a third-party
|
|
7
7
|
* capture runner echoing `story.id = "../../x"` must fail here, not in an
|
|
8
8
|
* adapter with its own key-jailing quirks. */
|
|
@@ -82,6 +82,11 @@ function storybookDir(projectId, buildId) {
|
|
|
82
82
|
assertSegments([projectId, buildId]);
|
|
83
83
|
return `${projectId}/builds/${buildId}/storybook`;
|
|
84
84
|
}
|
|
85
|
+
/** Disposable gzip representation of a content-addressed published file. */
|
|
86
|
+
function publishedGzipPath(hash) {
|
|
87
|
+
if (!/^[a-f0-9]{64}$/u.test(hash)) throw new Error("Invalid published content hash");
|
|
88
|
+
return `derived/published-gzip/${hash}.gz`;
|
|
89
|
+
}
|
|
85
90
|
/** Storage path for an uploaded Storybook zip. */
|
|
86
91
|
function storybookZipPath(projectId, buildId) {
|
|
87
92
|
assertSegments([projectId, buildId]);
|
|
@@ -92,7 +97,6 @@ function catalogPath(projectId, buildId) {
|
|
|
92
97
|
assertSegments([projectId, buildId]);
|
|
93
98
|
return `${projectId}/builds/${buildId}/catalog.json`;
|
|
94
99
|
}
|
|
95
|
-
|
|
96
|
-
export { baselinePath, catalogPath, contentDir, contentPath, designRenderPath, diffPath, isContentPath, modeSegment, screenshotPath, storybookDir, storybookZipPath };
|
|
100
|
+
export { baselinePath, catalogPath, contentDir, contentPath, designRenderPath, diffPath, isContentPath, modeSegment, publishedGzipPath, screenshotPath, storybookDir, storybookZipPath };
|
|
97
101
|
|
|
98
102
|
//# sourceMappingURL=paths.mjs.map
|
package/dist/paths.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.mjs","names":[],"sources":["../src/utils/paths.ts"],"sourcesContent":["/** Storage path builders. Every segment is validated — a third-party\n * capture runner echoing `story.id = \"../../x\"` must fail here, not in an\n * adapter with its own key-jailing quirks. */\nimport { isSafeSegment } from \"./path-security.ts\";\n\n/** Throw when any key segment could escape its storage prefix. */\nfunction assertSegments(segments: string[]): void {\n const bad = segments.filter((segment) => !isSafeSegment(segment) || segment.length === 0);\n if (bad.length > 0) {\n throw new Error(`Unsafe storage path segment: ${bad.join(\",\")}`);\n }\n}\n/**\n * Path segment for a mode on a given engine. Chromium keeps the bare mode key\n * so pre-multi-browser objects stay addressable; other engines get `~browser`.\n */\nexport function modeSegment(viewport: string, browser?: string): string {\n return browser && browser !== \"chromium\" ? `${viewport}~${browser}` : viewport;\n}\n\n/** Storage path for a captured snapshot screenshot. */\nexport function screenshotPath(\n projectId: string,\n buildId: string,\n storyId: string,\n viewport: string,\n browser?: string,\n): string {\n const segment = modeSegment(viewport, browser);\n assertSegments([projectId, buildId, storyId, segment]);\n return `${projectId}/builds/${buildId}/screenshots/${storyId}/${segment}.png`;\n}\n\n/** Storage prefix of a project's content-addressed screenshots. */\nexport function contentDir(projectId: string): string {\n assertSegments([projectId]);\n return `${projectId}/content`;\n}\n\n/** Storage path of a screenshot stored by the SHA-256 of its bytes (shared by builds and baselines). */\nexport function contentPath(projectId: string, hash: string): string {\n assertSegments([projectId, hash]);\n return `${contentDir(projectId)}/${hash}.png`;\n}\n\n/** Whether `path` is one of `projectId`'s content-addressed screenshots. */\nexport function isContentPath(projectId: string, path: string): boolean {\n return path.startsWith(`${contentDir(projectId)}/`) && path.endsWith(\".png\");\n}\n\n/** Storage path for a snapshot diff overlay image. */\nexport function diffPath(\n projectId: string,\n buildId: string,\n storyId: string,\n viewport: string,\n browser?: string,\n): string {\n const segment = modeSegment(viewport, browser);\n assertSegments([projectId, buildId, storyId, segment]);\n return `${projectId}/builds/${buildId}/diffs/${storyId}/${segment}.png`;\n}\n\n/** Storage path for a cached design node render (overwritten in place when the file version moves). */\nexport function designRenderPath(\n projectId: string,\n provider: string,\n fileKey: string,\n nodeId: string,\n viewerId = \"\",\n): string {\n const node = nodeId.replaceAll(\":\", \"-\");\n assertSegments([projectId, provider, fileKey, node, ...(viewerId ? [viewerId] : [])]);\n const owner = viewerId ? `user-${viewerId}/` : \"\";\n return `${projectId}/design/${provider}/${owner}${fileKey}/${node}.png`;\n}\n\n/** Storage path for a branch baseline screenshot. */\nexport function baselinePath(\n projectId: string,\n branch: string,\n storyId: string,\n viewport: string,\n browser?: string,\n): string {\n const segment = modeSegment(viewport, browser);\n assertSegments([projectId, branch, storyId, segment]);\n return `${projectId}/baselines/${branch}/${storyId}/${segment}.png`;\n}\n\n/** Storage prefix for an extracted published Storybook. */\nexport function storybookDir(projectId: string, buildId: string): string {\n assertSegments([projectId, buildId]);\n return `${projectId}/builds/${buildId}/storybook`;\n}\n\n/** Storage path for an uploaded Storybook zip. */\nexport function storybookZipPath(projectId: string, buildId: string): string {\n assertSegments([projectId, buildId]);\n return `${projectId}/builds/${buildId}/storybook.zip`;\n}\n\n/** Storage path of a build's persisted catalog (ADR 0006, decision 9), next to its `storybook/` statics. */\nexport function catalogPath(projectId: string, buildId: string): string {\n assertSegments([projectId, buildId]);\n return `${projectId}/builds/${buildId}/catalog.json`;\n}\n"],"mappings":";;;;;;;;;AAMA,SAAS,eAAe,UAA0B;CAChD,MAAM,MAAM,SAAS,QAAQ,YAAY,CAAC,cAAc,OAAO,KAAK,QAAQ,WAAW,CAAC;CACxF,IAAI,IAAI,SAAS,GACf,MAAM,IAAI,MAAM,gCAAgC,IAAI,KAAK,GAAG,GAAG;AAEnE;;;;;AAKA,SAAgB,YAAY,UAAkB,SAA0B;CACtE,OAAO,WAAW,YAAY,aAAa,GAAG,SAAS,GAAG,YAAY;AACxE;;AAGA,SAAgB,eACd,WACA,SACA,SACA,UACA,SACQ;CACR,MAAM,UAAU,YAAY,UAAU,OAAO;CAC7C,eAAe;EAAC;EAAW;EAAS;EAAS;CAAO,CAAC;CACrD,OAAO,GAAG,UAAU,UAAU,QAAQ,eAAe,QAAQ,GAAG,QAAQ;AAC1E;;AAGA,SAAgB,WAAW,WAA2B;CACpD,eAAe,CAAC,SAAS,CAAC;CAC1B,OAAO,GAAG,UAAU;AACtB;;AAGA,SAAgB,YAAY,WAAmB,MAAsB;CACnE,eAAe,CAAC,WAAW,IAAI,CAAC;CAChC,OAAO,GAAG,WAAW,SAAS,EAAE,GAAG,KAAK;AAC1C;;AAGA,SAAgB,cAAc,WAAmB,MAAuB;CACtE,OAAO,KAAK,WAAW,GAAG,WAAW,SAAS,EAAE,EAAE,KAAK,KAAK,SAAS,MAAM;AAC7E;;AAGA,SAAgB,SACd,WACA,SACA,SACA,UACA,SACQ;CACR,MAAM,UAAU,YAAY,UAAU,OAAO;CAC7C,eAAe;EAAC;EAAW;EAAS;EAAS;CAAO,CAAC;CACrD,OAAO,GAAG,UAAU,UAAU,QAAQ,SAAS,QAAQ,GAAG,QAAQ;AACpE;;AAGA,SAAgB,iBACd,WACA,UACA,SACA,QACA,WAAW,IACH;CACR,MAAM,OAAO,OAAO,WAAW,KAAK,GAAG;CACvC,eAAe;EAAC;EAAW;EAAU;EAAS;EAAM,GAAI,WAAW,CAAC,QAAQ,IAAI,CAAC;CAAE,CAAC;CAEpF,OAAO,GAAG,UAAU,UAAU,SAAS,GADzB,WAAW,QAAQ,SAAS,KAAK,KACG,QAAQ,GAAG,KAAK;AACpE;;AAGA,SAAgB,aACd,WACA,QACA,SACA,UACA,SACQ;CACR,MAAM,UAAU,YAAY,UAAU,OAAO;CAC7C,eAAe;EAAC;EAAW;EAAQ;EAAS;CAAO,CAAC;CACpD,OAAO,GAAG,UAAU,aAAa,OAAO,GAAG,QAAQ,GAAG,QAAQ;AAChE;;AAGA,SAAgB,aAAa,WAAmB,SAAyB;CACvE,eAAe,CAAC,WAAW,OAAO,CAAC;CACnC,OAAO,GAAG,UAAU,UAAU,QAAQ;AACxC;;AAGA,SAAgB,iBAAiB,WAAmB,SAAyB;CAC3E,eAAe,CAAC,WAAW,OAAO,CAAC;CACnC,OAAO,GAAG,UAAU,UAAU,QAAQ;AACxC;;AAGA,SAAgB,YAAY,WAAmB,SAAyB;CACtE,eAAe,CAAC,WAAW,OAAO,CAAC;CACnC,OAAO,GAAG,UAAU,UAAU,QAAQ;AACxC"}
|
|
1
|
+
{"version":3,"file":"paths.mjs","names":[],"sources":["../src/utils/paths.ts"],"sourcesContent":["/** Storage path builders. Every segment is validated — a third-party\n * capture runner echoing `story.id = \"../../x\"` must fail here, not in an\n * adapter with its own key-jailing quirks. */\nimport { isSafeSegment } from \"./path-security.ts\";\n\n/** Throw when any key segment could escape its storage prefix. */\nfunction assertSegments(segments: string[]): void {\n const bad = segments.filter((segment) => !isSafeSegment(segment) || segment.length === 0);\n if (bad.length > 0) {\n throw new Error(`Unsafe storage path segment: ${bad.join(\",\")}`);\n }\n}\n/**\n * Path segment for a mode on a given engine. Chromium keeps the bare mode key\n * so pre-multi-browser objects stay addressable; other engines get `~browser`.\n */\nexport function modeSegment(viewport: string, browser?: string): string {\n return browser && browser !== \"chromium\" ? `${viewport}~${browser}` : viewport;\n}\n\n/** Storage path for a captured snapshot screenshot. */\nexport function screenshotPath(\n projectId: string,\n buildId: string,\n storyId: string,\n viewport: string,\n browser?: string,\n): string {\n const segment = modeSegment(viewport, browser);\n assertSegments([projectId, buildId, storyId, segment]);\n return `${projectId}/builds/${buildId}/screenshots/${storyId}/${segment}.png`;\n}\n\n/** Storage prefix of a project's content-addressed screenshots. */\nexport function contentDir(projectId: string): string {\n assertSegments([projectId]);\n return `${projectId}/content`;\n}\n\n/** Storage path of a screenshot stored by the SHA-256 of its bytes (shared by builds and baselines). */\nexport function contentPath(projectId: string, hash: string): string {\n assertSegments([projectId, hash]);\n return `${contentDir(projectId)}/${hash}.png`;\n}\n\n/** Whether `path` is one of `projectId`'s content-addressed screenshots. */\nexport function isContentPath(projectId: string, path: string): boolean {\n return path.startsWith(`${contentDir(projectId)}/`) && path.endsWith(\".png\");\n}\n\n/** Storage path for a snapshot diff overlay image. */\nexport function diffPath(\n projectId: string,\n buildId: string,\n storyId: string,\n viewport: string,\n browser?: string,\n): string {\n const segment = modeSegment(viewport, browser);\n assertSegments([projectId, buildId, storyId, segment]);\n return `${projectId}/builds/${buildId}/diffs/${storyId}/${segment}.png`;\n}\n\n/** Storage path for a cached design node render (overwritten in place when the file version moves). */\nexport function designRenderPath(\n projectId: string,\n provider: string,\n fileKey: string,\n nodeId: string,\n viewerId = \"\",\n): string {\n const node = nodeId.replaceAll(\":\", \"-\");\n assertSegments([projectId, provider, fileKey, node, ...(viewerId ? [viewerId] : [])]);\n const owner = viewerId ? `user-${viewerId}/` : \"\";\n return `${projectId}/design/${provider}/${owner}${fileKey}/${node}.png`;\n}\n\n/** Storage path for a branch baseline screenshot. */\nexport function baselinePath(\n projectId: string,\n branch: string,\n storyId: string,\n viewport: string,\n browser?: string,\n): string {\n const segment = modeSegment(viewport, browser);\n assertSegments([projectId, branch, storyId, segment]);\n return `${projectId}/baselines/${branch}/${storyId}/${segment}.png`;\n}\n\n/** Storage prefix for an extracted published Storybook. */\nexport function storybookDir(projectId: string, buildId: string): string {\n assertSegments([projectId, buildId]);\n return `${projectId}/builds/${buildId}/storybook`;\n}\n\n/** Disposable gzip representation of a content-addressed published file. */\nexport function publishedGzipPath(hash: string): string {\n if (!/^[a-f0-9]{64}$/u.test(hash)) throw new Error(\"Invalid published content hash\");\n return `derived/published-gzip/${hash}.gz`;\n}\n\n/** Storage path for an uploaded Storybook zip. */\nexport function storybookZipPath(projectId: string, buildId: string): string {\n assertSegments([projectId, buildId]);\n return `${projectId}/builds/${buildId}/storybook.zip`;\n}\n\n/** Storage path of a build's persisted catalog (ADR 0006, decision 9), next to its `storybook/` statics. */\nexport function catalogPath(projectId: string, buildId: string): string {\n assertSegments([projectId, buildId]);\n return `${projectId}/builds/${buildId}/catalog.json`;\n}\n"],"mappings":";;;;;;;;;AAMA,SAAS,eAAe,UAA0B;CAChD,MAAM,MAAM,SAAS,QAAQ,YAAY,CAAC,cAAc,OAAO,KAAK,QAAQ,WAAW,CAAC;CACxF,IAAI,IAAI,SAAS,GACf,MAAM,IAAI,MAAM,gCAAgC,IAAI,KAAK,GAAG,GAAG;AAEnE;;;;;AAKA,SAAgB,YAAY,UAAkB,SAA0B;CACtE,OAAO,WAAW,YAAY,aAAa,GAAG,SAAS,GAAG,YAAY;AACxE;;AAGA,SAAgB,eACd,WACA,SACA,SACA,UACA,SACQ;CACR,MAAM,UAAU,YAAY,UAAU,OAAO;CAC7C,eAAe;EAAC;EAAW;EAAS;EAAS;CAAO,CAAC;CACrD,OAAO,GAAG,UAAU,UAAU,QAAQ,eAAe,QAAQ,GAAG,QAAQ;AAC1E;;AAGA,SAAgB,WAAW,WAA2B;CACpD,eAAe,CAAC,SAAS,CAAC;CAC1B,OAAO,GAAG,UAAU;AACtB;;AAGA,SAAgB,YAAY,WAAmB,MAAsB;CACnE,eAAe,CAAC,WAAW,IAAI,CAAC;CAChC,OAAO,GAAG,WAAW,SAAS,EAAE,GAAG,KAAK;AAC1C;;AAGA,SAAgB,cAAc,WAAmB,MAAuB;CACtE,OAAO,KAAK,WAAW,GAAG,WAAW,SAAS,EAAE,EAAE,KAAK,KAAK,SAAS,MAAM;AAC7E;;AAGA,SAAgB,SACd,WACA,SACA,SACA,UACA,SACQ;CACR,MAAM,UAAU,YAAY,UAAU,OAAO;CAC7C,eAAe;EAAC;EAAW;EAAS;EAAS;CAAO,CAAC;CACrD,OAAO,GAAG,UAAU,UAAU,QAAQ,SAAS,QAAQ,GAAG,QAAQ;AACpE;;AAGA,SAAgB,iBACd,WACA,UACA,SACA,QACA,WAAW,IACH;CACR,MAAM,OAAO,OAAO,WAAW,KAAK,GAAG;CACvC,eAAe;EAAC;EAAW;EAAU;EAAS;EAAM,GAAI,WAAW,CAAC,QAAQ,IAAI,CAAC;CAAE,CAAC;CAEpF,OAAO,GAAG,UAAU,UAAU,SAAS,GADzB,WAAW,QAAQ,SAAS,KAAK,KACG,QAAQ,GAAG,KAAK;AACpE;;AAGA,SAAgB,aACd,WACA,QACA,SACA,UACA,SACQ;CACR,MAAM,UAAU,YAAY,UAAU,OAAO;CAC7C,eAAe;EAAC;EAAW;EAAQ;EAAS;CAAO,CAAC;CACpD,OAAO,GAAG,UAAU,aAAa,OAAO,GAAG,QAAQ,GAAG,QAAQ;AAChE;;AAGA,SAAgB,aAAa,WAAmB,SAAyB;CACvE,eAAe,CAAC,WAAW,OAAO,CAAC;CACnC,OAAO,GAAG,UAAU,UAAU,QAAQ;AACxC;;AAGA,SAAgB,kBAAkB,MAAsB;CACtD,IAAI,CAAC,kBAAkB,KAAK,IAAI,GAAG,MAAM,IAAI,MAAM,gCAAgC;CACnF,OAAO,0BAA0B,KAAK;AACxC;;AAGA,SAAgB,iBAAiB,WAAmB,SAAyB;CAC3E,eAAe,CAAC,WAAW,OAAO,CAAC;CACnC,OAAO,GAAG,UAAU,UAAU,QAAQ;AACxC;;AAGA,SAAgB,YAAY,WAAmB,SAAyB;CACtE,eAAe,CAAC,WAAW,OAAO,CAAC;CACnC,OAAO,GAAG,UAAU,UAAU,QAAQ;AACxC"}
|
package/dist/retention/ai.d.mts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
import { DatabaseAdapter } from "../adapter/database.mjs";
|
|
4
3
|
import { Logger } from "../logger.mjs";
|
|
5
|
-
|
|
4
|
+
import { DatabaseAdapter } from "../adapter/database.mjs";
|
|
6
5
|
/** Pending/running rows older than this are marked `interrupted`. */
|
|
7
6
|
declare const STALE_RUN_MS: number;
|
|
8
7
|
/** What one AI retention pass removed. */
|
|
@@ -13,6 +12,5 @@ interface AiPurgeResult {
|
|
|
13
12
|
}
|
|
14
13
|
/** Run one AI retention pass (safe to call from any instance, any time). */
|
|
15
14
|
declare function purgeAiData(db: DatabaseAdapter, now?: Date, logger?: Logger): Promise<AiPurgeResult>;
|
|
16
|
-
//#endregion
|
|
17
15
|
export { AiPurgeResult, STALE_RUN_MS, purgeAiData };
|
|
18
16
|
//# sourceMappingURL=ai.d.mts.map
|
package/dist/retention/ai.mjs
CHANGED
|
@@ -17,7 +17,6 @@ async function purgeAiData(db, now = /* @__PURE__ */ new Date(), logger) {
|
|
|
17
17
|
if (result.health > 0 || result.usage > 0 || result.interrupted > 0) logger?.info(result, "ai data retention pass complete");
|
|
18
18
|
return result;
|
|
19
19
|
}
|
|
20
|
-
//#endregion
|
|
21
20
|
export { STALE_RUN_MS, purgeAiData };
|
|
22
21
|
|
|
23
22
|
//# sourceMappingURL=ai.mjs.map
|
|
@@ -4,7 +4,7 @@ import.meta.url;
|
|
|
4
4
|
import { BuildModel } from "../models/build.mjs";
|
|
5
5
|
import { BuildReviewModel } from "../models/build-review.mjs";
|
|
6
6
|
import { CommentModel } from "../models/comment.mjs";
|
|
7
|
-
|
|
7
|
+
// src/retention/pending.ts
|
|
8
8
|
/**
|
|
9
9
|
* The project's pending builds whose last activity (upload, comment or verdict) is before
|
|
10
10
|
* `cutoff`. `capture_attempts` need no check: a build capture worked on leaves `pending`.
|
|
@@ -41,7 +41,6 @@ function stampOf(item) {
|
|
|
41
41
|
function hasNewerOnBranch(all, build) {
|
|
42
42
|
return all.some((other) => other.id !== build.id && other.gitBranch === build.gitBranch && other.createdAt > build.createdAt);
|
|
43
43
|
}
|
|
44
|
-
//#endregion
|
|
45
44
|
export { pendingCandidates };
|
|
46
45
|
|
|
47
46
|
//# sourceMappingURL=pending.mjs.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
// src/retention/published-gzip-sweep.ts
|
|
5
|
+
const PREFIX = "derived/published-gzip/";
|
|
6
|
+
const MAX_PER_SWEEP = 128;
|
|
7
|
+
const cursors = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
/** Reclaim disposable orphan variants; never delete content or other storage keys. */
|
|
9
|
+
async function sweepPublishedGzip(storage) {
|
|
10
|
+
const keys = (await storage.list(PREFIX)).filter((key) => /^derived\/published-gzip\/[a-f0-9]{64}\.gz$/u.test(key)).toSorted();
|
|
11
|
+
if (keys.length === 0) return;
|
|
12
|
+
const cursor = cursors.get(storage);
|
|
13
|
+
const next = cursor ? keys.findIndex((key) => key > cursor) : 0;
|
|
14
|
+
const start = Math.max(0, next);
|
|
15
|
+
const batch = [...keys.slice(start), ...keys.slice(0, start)].slice(0, MAX_PER_SWEEP);
|
|
16
|
+
for (const key of batch) await removeOrphan(storage, key);
|
|
17
|
+
const last = batch.at(-1);
|
|
18
|
+
if (last) cursors.set(storage, last);
|
|
19
|
+
}
|
|
20
|
+
async function removeOrphan(storage, key) {
|
|
21
|
+
const hash = key.slice(23, -3);
|
|
22
|
+
try {
|
|
23
|
+
if (!await storage.exists(`content/${hash}`)) await storage.delete(key);
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
26
|
+
export { sweepPublishedGzip };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=published-gzip-sweep.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"published-gzip-sweep.mjs","names":[],"sources":["../../src/retention/published-gzip-sweep.ts"],"sourcesContent":["import type { StorageAdapter } from \"../adapters/storage.ts\";\n\nconst PREFIX = \"derived/published-gzip/\";\nconst MAX_PER_SWEEP = 128;\nconst cursors = new WeakMap<StorageAdapter, string>();\n\n/** Reclaim disposable orphan variants; never delete content or other storage keys. */\nexport async function sweepPublishedGzip(storage: StorageAdapter): Promise<void> {\n const keys = (await storage.list(PREFIX))\n .filter((key) => /^derived\\/published-gzip\\/[a-f0-9]{64}\\.gz$/u.test(key))\n .toSorted();\n if (keys.length === 0) return;\n const cursor = cursors.get(storage);\n const next = cursor ? keys.findIndex((key) => key > cursor) : 0;\n const start = Math.max(0, next);\n const batch = [...keys.slice(start), ...keys.slice(0, start)].slice(0, MAX_PER_SWEEP);\n for (const key of batch) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- bounded, sequential storage work\n await removeOrphan(storage, key);\n }\n const last = batch.at(-1);\n if (last) cursors.set(storage, last);\n}\n\nasync function removeOrphan(storage: StorageAdapter, key: string): Promise<void> {\n const hash = key.slice(PREFIX.length, -3);\n try {\n if (!(await storage.exists(`content/${hash}`))) await storage.delete(key);\n } catch {\n // Retention is best effort; a missing variant can always be regenerated.\n }\n}\n"],"mappings":";;;;AAEA,MAAM,SAAS;AACf,MAAM,gBAAgB;AACtB,MAAM,0BAAU,IAAI,QAAgC;;AAGpD,eAAsB,mBAAmB,SAAwC;CAC/E,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,EAAA,CACpC,QAAQ,QAAQ,+CAA+C,KAAK,GAAG,CAAC,CAAC,CACzE,SAAS;CACZ,IAAI,KAAK,WAAW,GAAG;CACvB,MAAM,SAAS,QAAQ,IAAI,OAAO;CAClC,MAAM,OAAO,SAAS,KAAK,WAAW,QAAQ,MAAM,MAAM,IAAI;CAC9D,MAAM,QAAQ,KAAK,IAAI,GAAG,IAAI;CAC9B,MAAM,QAAQ,CAAC,GAAG,KAAK,MAAM,KAAK,GAAG,GAAG,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,aAAa;CACpF,KAAK,MAAM,OAAO,OAEhB,MAAM,aAAa,SAAS,GAAG;CAEjC,MAAM,OAAO,MAAM,GAAG,EAAE;CACxB,IAAI,MAAM,QAAQ,IAAI,SAAS,IAAI;AACrC;AAEA,eAAe,aAAa,SAAyB,KAA4B;CAC/E,MAAM,OAAO,IAAI,MAAM,IAAe,EAAE;CACxC,IAAI;EACF,IAAI,CAAE,MAAM,QAAQ,OAAO,WAAW,MAAM,GAAI,MAAM,QAAQ,OAAO,GAAG;CAC1E,QAAQ,CAER;AACF"}
|
|
@@ -4,7 +4,6 @@ import { DatabaseAdapter } from "../adapter/database.mjs";
|
|
|
4
4
|
import { StorageAdapter } from "../adapter/storage.mjs";
|
|
5
5
|
import { Logger } from "pino";
|
|
6
6
|
import { Table } from "drizzle-orm";
|
|
7
|
-
//#region src/retention/screenshot-sweep.d.ts
|
|
8
7
|
/** Longest an unreferenced screenshot is kept after the sweep first sees it (default 24 h). */
|
|
9
8
|
declare const SWEEP_GRACE_MS: number;
|
|
10
9
|
/** Tables the sweep reads references and in-flight builds from. */
|
|
@@ -46,6 +45,5 @@ declare function sweepScreenshotContent(deps: SweepDeps, projectId: string, opti
|
|
|
46
45
|
now?: number;
|
|
47
46
|
graceMs?: number;
|
|
48
47
|
}): Promise<SweepResult>;
|
|
49
|
-
//#endregion
|
|
50
48
|
export { SWEEP_GRACE_MS, SweepDeps, SweepResult, SweepTables, sweepScreenshotContent };
|
|
51
49
|
//# sourceMappingURL=screenshot-sweep.d.mts.map
|
|
@@ -5,7 +5,7 @@ import { col } from "../models/columns.mjs";
|
|
|
5
5
|
import { contentDir } from "../paths.mjs";
|
|
6
6
|
import { CLAIMS_DIR } from "../utils/screenshot-store.mjs";
|
|
7
7
|
import { and, eq, gte, inArray, like } from "drizzle-orm";
|
|
8
|
-
|
|
8
|
+
// src/retention/screenshot-sweep.ts
|
|
9
9
|
/** Mark-and-sweep for a project's content-addressed screenshots. */
|
|
10
10
|
/** Longest an unreferenced screenshot is kept after the sweep first sees it (default 24 h). */
|
|
11
11
|
const SWEEP_GRACE_MS = 864e5;
|
|
@@ -122,7 +122,6 @@ async function readMarker(storage, dir) {
|
|
|
122
122
|
async function writeMarker(storage, dir, next, previous) {
|
|
123
123
|
if (JSON.stringify(next) !== JSON.stringify(previous)) await storage.write(`${dir}${MARKER}`, Buffer.from(JSON.stringify(next)));
|
|
124
124
|
}
|
|
125
|
-
//#endregion
|
|
126
125
|
export { SWEEP_GRACE_MS, sweepScreenshotContent };
|
|
127
126
|
|
|
128
127
|
//# sourceMappingURL=screenshot-sweep.mjs.map
|
package/dist/retention.d.mts
CHANGED
|
@@ -7,7 +7,6 @@ import { SWEEP_GRACE_MS, SweepDeps, SweepResult, SweepTables, sweepScreenshotCon
|
|
|
7
7
|
import { AiPurgeResult, STALE_RUN_MS, purgeAiData } from "./retention/ai.mjs";
|
|
8
8
|
import { Logger } from "pino";
|
|
9
9
|
import { Table } from "drizzle-orm";
|
|
10
|
-
//#region src/retention/purge.d.ts
|
|
11
10
|
/** Table handles for retention queries. */
|
|
12
11
|
interface RetentionTables {
|
|
13
12
|
builds: Table;
|
|
@@ -54,6 +53,5 @@ interface BranchGcResult {
|
|
|
54
53
|
removedBranches: number;
|
|
55
54
|
removedBaselines: number;
|
|
56
55
|
}
|
|
57
|
-
//#endregion
|
|
58
56
|
export { type AiPurgeResult, BranchGcResult, PurgeOptions, PurgeResult, Retention, RetentionTables, STALE_RUN_MS, SWEEP_GRACE_MS, type SweepDeps, type SweepResult, type SweepTables, purgeAiData, sweepScreenshotContent };
|
|
59
57
|
//# sourceMappingURL=retention.d.mts.map
|
package/dist/retention.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import.meta.url;
|
|
|
4
4
|
import { TERMINAL_BUILD_STATUSES } from "./types.mjs";
|
|
5
5
|
import { col } from "./models/columns.mjs";
|
|
6
6
|
import { BuildModel } from "./models/build.mjs";
|
|
7
|
+
import { publishedGzipPath } from "./paths.mjs";
|
|
7
8
|
import { BaselineModel } from "./models/baseline.mjs";
|
|
8
9
|
import { CaptureJobModel } from "./models/capture-job.mjs";
|
|
9
10
|
import { BuildComponentModel } from "./models/build-component.mjs";
|
|
@@ -11,10 +12,11 @@ import { InsightModel } from "./models/insight.mjs";
|
|
|
11
12
|
import { LabelModel } from "./models/label.mjs";
|
|
12
13
|
import { PackageUsageModel } from "./models/usage.mjs";
|
|
13
14
|
import { pendingCandidates } from "./retention/pending.mjs";
|
|
15
|
+
import { sweepPublishedGzip } from "./retention/published-gzip-sweep.mjs";
|
|
14
16
|
import { SWEEP_GRACE_MS, sweepScreenshotContent } from "./retention/screenshot-sweep.mjs";
|
|
15
17
|
import { STALE_RUN_MS, purgeAiData } from "./retention/ai.mjs";
|
|
16
18
|
import { and, desc, eq, inArray, lt } from "drizzle-orm";
|
|
17
|
-
|
|
19
|
+
// src/retention/purge.ts
|
|
18
20
|
/** Removes expired transient builds while keeping baselines and persistent builds. */
|
|
19
21
|
var Retention = class {
|
|
20
22
|
db;
|
|
@@ -153,9 +155,11 @@ var Retention = class {
|
|
|
153
155
|
const all = await this.db.list(contentRefs);
|
|
154
156
|
for (const row of all) if (row.refCount === 0 && row.lastSeenAt < cutoff) {
|
|
155
157
|
await this.storage.delete(`content/${row.hash}`).catch(() => {});
|
|
158
|
+
if (/^[a-f0-9]{64}$/u.test(row.hash)) await this.storage.delete(publishedGzipPath(row.hash)).catch(() => {});
|
|
156
159
|
await this.db.remove(contentRefs, row.hash);
|
|
157
160
|
}
|
|
158
161
|
} catch {}
|
|
162
|
+
await sweepPublishedGzip(this.storage).catch(() => {});
|
|
159
163
|
return files.length;
|
|
160
164
|
}
|
|
161
165
|
async purgeStaleBranches(project, ttlDays) {
|
|
@@ -196,7 +200,6 @@ function branchLatestAt(rows) {
|
|
|
196
200
|
}
|
|
197
201
|
return latestAt;
|
|
198
202
|
}
|
|
199
|
-
//#endregion
|
|
200
203
|
export { Retention, STALE_RUN_MS, SWEEP_GRACE_MS, purgeAiData, sweepScreenshotContent };
|
|
201
204
|
|
|
202
205
|
//# sourceMappingURL=retention.mjs.map
|
package/dist/retention.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retention.mjs","names":[],"sources":["../src/retention/purge.ts"],"sourcesContent":["import { and, desc, eq, inArray, lt } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { Logger } from \"pino\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildComponentModel } from \"../models/build-component.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { CaptureJobModel } from \"../models/capture-job.ts\";\nimport { InsightModel } from \"../models/insight.ts\";\nimport { LabelModel, type LabelTables } from \"../models/label.ts\";\nimport { PackageUsageModel } from \"../models/usage.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { TERMINAL_BUILD_STATUSES } from \"../types.ts\";\nimport { pendingCandidates } from \"./pending.ts\";\nimport { sweepScreenshotContent } from \"./screenshot-sweep.ts\";\n\nexport {\n sweepScreenshotContent,\n SWEEP_GRACE_MS,\n type SweepDeps,\n type SweepResult,\n type SweepTables,\n} from \"./screenshot-sweep.ts\";\nimport { col } from \"../models/columns.ts\";\nexport { purgeAiData, STALE_RUN_MS, type AiPurgeResult } from \"./ai.ts\";\n\n/** Table handles for retention queries. */\nexport interface RetentionTables {\n builds: Table;\n buildLabels: Table;\n baselines: Table;\n}\n\n/** Removes expired transient builds while keeping baselines and persistent builds. */\nexport class Retention {\n private readonly db: DatabaseAdapter;\n private readonly storage: StorageAdapter;\n private readonly tables: RetentionTables;\n private readonly logger?: Logger;\n\n constructor(\n db: DatabaseAdapter,\n storage: StorageAdapter,\n tables: RetentionTables,\n logger?: Logger,\n ) {\n this.db = db;\n this.storage = storage;\n this.tables = tables;\n this.logger = logger;\n }\n\n async purge(project: Project, options: PurgeOptions): Promise<PurgeResult> {\n const cutoff = new Date(Date.now() - options.ttlDays * 86_400_000).toISOString();\n const { buildIds, pending } = await this.selectBuilds(project, options, cutoff);\n const results = await Promise.all(\n buildIds.map(async (buildId) => ({\n files: await this.deleteBuildFiles(project.id, buildId),\n removed: await this.removeBuild(buildId),\n })),\n );\n const removedBuilds = results.filter((r: { removed: boolean }) => r.removed).length;\n const removedFiles = results.reduce((sum: number, r: { files: number }) => sum + r.files, 0);\n await this.sweepScreenshots(project);\n const removedJobs = await this.purgeFinishedJobs(cutoff);\n this.logger?.info(\n { projectId: project.id, removedBuilds, removedFiles, removedJobs, removedPending: pending },\n \"build retention purge complete\",\n );\n return { removedBuilds, removedFiles };\n }\n\n /**\n * The builds a purge removes: terminal builds past the window (minus the latest per branch when\n * asked), and pending builds idle past it (ADR 0013, decision 9). Persistent builds stay.\n */\n private async selectBuilds(\n project: Project,\n options: PurgeOptions,\n cutoff: string,\n ): Promise<{ buildIds: string[]; pending: number }> {\n const candidates = await this.db.list(this.tables.builds, {\n where: and(\n eq(col(this.tables.builds, \"projectId\"), project.id),\n inArray(col(this.tables.builds, \"status\"), [...TERMINAL_BUILD_STATUSES]),\n lt(col(this.tables.builds, \"updatedAt\"), cutoff),\n ),\n });\n const keep = options.keepLatestPerBranch\n ? await this.latestPerBranch(project.id)\n : new Set<string>();\n const persistent = await new LabelModel(\n this.db,\n this.tables as unknown as LabelTables,\n ).persistentBuildIds(project.id);\n const terminal = candidates\n .map((build) => (build as { id: string }).id)\n .filter((buildId) => !keep.has(buildId));\n // Pending builds leave on their own clock; \"the latest on a branch\" does not shield them.\n const pending = (await pendingCandidates(this.db, project.id, cutoff)).map(\n (candidate) => candidate.buildId,\n );\n return {\n buildIds: [...terminal, ...pending].filter((buildId) => !persistent.has(buildId)),\n pending: pending.length,\n };\n }\n\n /** Reclaim screenshots no snapshot or baseline references any more; never blocks the purge. */\n private async sweepScreenshots(project: Project): Promise<void> {\n try {\n await sweepScreenshotContent(\n {\n db: this.db,\n storage: this.storage,\n tables: {\n snapshots: this.db.tables.snapshots,\n baselines: this.tables.baselines,\n builds: this.tables.builds,\n },\n logger: this.logger,\n },\n project.id,\n );\n } catch (error) {\n this.logger?.warn({ err: error, projectId: project.id }, \"screenshot sweep failed\");\n }\n }\n\n /**\n * Drop finished queue jobs older than the build cutoff (they have no foreign key to a build, and\n * a finished job is only history); never blocks the purge. Queued and running jobs are kept.\n */\n private async purgeFinishedJobs(cutoff: string): Promise<number> {\n try {\n return await new CaptureJobModel(this.db).purgeFinished(cutoff);\n } catch (error) {\n this.logger?.warn({ err: error }, \"capture job purge failed\");\n return 0;\n }\n }\n\n private async removeBuild(buildId: string): Promise<boolean> {\n await new InsightModel(this.db).removeForBuild(buildId);\n await new CaptureJobModel(this.db).removeForBuild(buildId);\n await new BuildComponentModel(this.db).removeForBuild(buildId);\n await new PackageUsageModel(this.db).removeForBuild(buildId);\n await new BuildModel(this.db, this.tables as unknown as BuildTables).remove(buildId);\n return true;\n }\n\n private async latestPerBranch(projectId: string): Promise<Set<string>> {\n const rows = (await this.db.list(this.tables.builds, {\n where: eq(col(this.tables.builds, \"projectId\"), projectId),\n orderBy: desc(col(this.tables.builds, \"updatedAt\")),\n })) as unknown as { gitBranch: string; id: string }[];\n const latest = new Map<string, string>();\n for (const row of rows) {\n const branch = row.gitBranch;\n const current = latest.get(branch);\n if (!current) {\n latest.set(branch, row.id);\n }\n }\n return new Set(latest.values());\n }\n\n private async deleteBuildFiles(projectId: string, buildId: string): Promise<number> {\n const prefix = `${projectId}/builds/${buildId}/`;\n const manifestPath = `${prefix}storybook/manifest.json`;\n let manifestFiles: string[] = [];\n try {\n if (await this.storage.exists(manifestPath)) {\n const raw = await this.storage.read(manifestPath);\n const manifest = JSON.parse(raw.toString(\"utf8\")) as Record<string, string>;\n manifestFiles = Object.values(manifest);\n }\n } catch {\n // ignore — manifest may not exist for old builds\n }\n const files = await this.storage.list(prefix);\n await Promise.all(\n files.map(async (file) => {\n await this.storage.delete(file);\n }),\n );\n // Decrement content refs for this build's manifest\n for (const hash of manifestFiles) {\n try {\n const contentRefs = this.db.tables.contentRefs;\n const existing = (await this.db.get(contentRefs, hash)) as unknown as {\n refCount: number;\n } | null;\n // oxlint-disable-next-line unicorn/prefer-ternary -- refCount check is clearer as if/else\n if (existing) {\n const refCount = existing.refCount;\n // oxlint-disable-next-line unicorn/prefer-ternary -- grace logic is clearer as if/else\n if (refCount <= 1) {\n // Keep for 7-day grace: set refCount 0 and update lastSeenAt, don't delete yet\n await this.db.update(contentRefs, hash, {\n refCount: 0,\n lastSeenAt: new Date().toISOString(),\n });\n } else {\n await this.db.update(contentRefs, hash, {\n refCount: refCount - 1,\n lastSeenAt: new Date().toISOString(),\n });\n }\n }\n } catch {\n // ignore — content_refs may not exist\n }\n }\n // GC stale content_refs with 7-day grace\n try {\n const contentRefs = this.db.tables.contentRefs;\n const cutoff = new Date(Date.now() - 7 * 86_400_000).toISOString();\n const all = (await this.db.list(contentRefs)) as unknown as {\n hash: string;\n refCount: number;\n lastSeenAt: string;\n }[];\n for (const row of all) {\n if (row.refCount === 0 && row.lastSeenAt < cutoff) {\n await this.storage.delete(`content/${row.hash}`).catch(() => {\n // ignore storage delete failure\n });\n await this.db.remove(contentRefs, row.hash);\n }\n }\n } catch {\n // ignore — content_refs may not exist\n }\n return files.length;\n }\n\n async purgeStaleBranches(project: Project, ttlDays: number): Promise<BranchGcResult> {\n if (ttlDays <= 0) {\n return { removedBranches: 0, removedBaselines: 0 };\n }\n const cutoff = new Date(Date.now() - ttlDays * 86_400_000).toISOString();\n const rows = (await this.db.list(this.tables.builds, {\n where: eq(col(this.tables.builds, \"projectId\"), project.id),\n })) as unknown as { gitBranch: string; updatedAt: string }[];\n const stale = collectStaleBranches(rows, project.gitDefaultBranch, cutoff);\n if (stale.size === 0) {\n return { removedBranches: 0, removedBaselines: 0 };\n }\n const baselines = new BaselineModel(this.db, this.tables, this.storage);\n const removedBaselines = await baselines.removeStaleBranches(project.id, stale);\n await this.sweepScreenshots(project);\n this.logger?.info(\n { projectId: project.id, removedBranches: stale.size, removedBaselines },\n \"branch GC complete\",\n );\n return { removedBranches: stale.size, removedBaselines };\n }\n}\n\n/** Options controlling which builds a retention purge removes. */\nexport interface PurgeOptions {\n ttlDays: number;\n keepLatestPerBranch: boolean;\n}\n\n/** Counts of builds and files removed by a retention purge. */\nexport interface PurgeResult {\n removedBuilds: number;\n removedFiles: number;\n}\n\n/** Counts of stale branches and baselines removed. */\nexport interface BranchGcResult {\n removedBranches: number;\n removedBaselines: number;\n}\n\nfunction collectStaleBranches(\n rows: { gitBranch: string; updatedAt: string }[],\n defaultBranch: string,\n cutoff: string,\n): Set<string> {\n const latestAt = branchLatestAt(rows);\n const stale = new Set<string>();\n for (const [branch, updatedAt] of latestAt) {\n if (branch !== defaultBranch && updatedAt < cutoff) {\n stale.add(branch);\n }\n }\n return stale;\n}\n\nfunction branchLatestAt(rows: { gitBranch: string; updatedAt: string }[]): Map<string, string> {\n const latestAt = new Map<string, string>();\n for (const row of rows) {\n const current = latestAt.get(row.gitBranch);\n if (!current || row.updatedAt > current) {\n latestAt.set(row.gitBranch, row.updatedAt);\n }\n }\n return latestAt;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"retention.mjs","names":[],"sources":["../src/retention/purge.ts"],"sourcesContent":["import { and, desc, eq, inArray, lt } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { Logger } from \"pino\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildComponentModel } from \"../models/build-component.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { CaptureJobModel } from \"../models/capture-job.ts\";\nimport { InsightModel } from \"../models/insight.ts\";\nimport { LabelModel, type LabelTables } from \"../models/label.ts\";\nimport { PackageUsageModel } from \"../models/usage.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { TERMINAL_BUILD_STATUSES } from \"../types.ts\";\nimport { pendingCandidates } from \"./pending.ts\";\nimport { sweepPublishedGzip } from \"./published-gzip-sweep.ts\";\nimport { sweepScreenshotContent } from \"./screenshot-sweep.ts\";\n\nexport {\n sweepScreenshotContent,\n SWEEP_GRACE_MS,\n type SweepDeps,\n type SweepResult,\n type SweepTables,\n} from \"./screenshot-sweep.ts\";\nimport { col } from \"../models/columns.ts\";\nimport { publishedGzipPath } from \"../utils/paths.ts\";\nexport { purgeAiData, STALE_RUN_MS, type AiPurgeResult } from \"./ai.ts\";\n\n/** Table handles for retention queries. */\nexport interface RetentionTables {\n builds: Table;\n buildLabels: Table;\n baselines: Table;\n}\n\n/** Removes expired transient builds while keeping baselines and persistent builds. */\nexport class Retention {\n private readonly db: DatabaseAdapter;\n private readonly storage: StorageAdapter;\n private readonly tables: RetentionTables;\n private readonly logger?: Logger;\n\n constructor(\n db: DatabaseAdapter,\n storage: StorageAdapter,\n tables: RetentionTables,\n logger?: Logger,\n ) {\n this.db = db;\n this.storage = storage;\n this.tables = tables;\n this.logger = logger;\n }\n\n async purge(project: Project, options: PurgeOptions): Promise<PurgeResult> {\n const cutoff = new Date(Date.now() - options.ttlDays * 86_400_000).toISOString();\n const { buildIds, pending } = await this.selectBuilds(project, options, cutoff);\n const results = await Promise.all(\n buildIds.map(async (buildId) => ({\n files: await this.deleteBuildFiles(project.id, buildId),\n removed: await this.removeBuild(buildId),\n })),\n );\n const removedBuilds = results.filter((r: { removed: boolean }) => r.removed).length;\n const removedFiles = results.reduce((sum: number, r: { files: number }) => sum + r.files, 0);\n await this.sweepScreenshots(project);\n const removedJobs = await this.purgeFinishedJobs(cutoff);\n this.logger?.info(\n { projectId: project.id, removedBuilds, removedFiles, removedJobs, removedPending: pending },\n \"build retention purge complete\",\n );\n return { removedBuilds, removedFiles };\n }\n\n /**\n * The builds a purge removes: terminal builds past the window (minus the latest per branch when\n * asked), and pending builds idle past it (ADR 0013, decision 9). Persistent builds stay.\n */\n private async selectBuilds(\n project: Project,\n options: PurgeOptions,\n cutoff: string,\n ): Promise<{ buildIds: string[]; pending: number }> {\n const candidates = await this.db.list(this.tables.builds, {\n where: and(\n eq(col(this.tables.builds, \"projectId\"), project.id),\n inArray(col(this.tables.builds, \"status\"), [...TERMINAL_BUILD_STATUSES]),\n lt(col(this.tables.builds, \"updatedAt\"), cutoff),\n ),\n });\n const keep = options.keepLatestPerBranch\n ? await this.latestPerBranch(project.id)\n : new Set<string>();\n const persistent = await new LabelModel(\n this.db,\n this.tables as unknown as LabelTables,\n ).persistentBuildIds(project.id);\n const terminal = candidates\n .map((build) => (build as { id: string }).id)\n .filter((buildId) => !keep.has(buildId));\n // Pending builds leave on their own clock; \"the latest on a branch\" does not shield them.\n const pending = (await pendingCandidates(this.db, project.id, cutoff)).map(\n (candidate) => candidate.buildId,\n );\n return {\n buildIds: [...terminal, ...pending].filter((buildId) => !persistent.has(buildId)),\n pending: pending.length,\n };\n }\n\n /** Reclaim screenshots no snapshot or baseline references any more; never blocks the purge. */\n private async sweepScreenshots(project: Project): Promise<void> {\n try {\n await sweepScreenshotContent(\n {\n db: this.db,\n storage: this.storage,\n tables: {\n snapshots: this.db.tables.snapshots,\n baselines: this.tables.baselines,\n builds: this.tables.builds,\n },\n logger: this.logger,\n },\n project.id,\n );\n } catch (error) {\n this.logger?.warn({ err: error, projectId: project.id }, \"screenshot sweep failed\");\n }\n }\n\n /**\n * Drop finished queue jobs older than the build cutoff (they have no foreign key to a build, and\n * a finished job is only history); never blocks the purge. Queued and running jobs are kept.\n */\n private async purgeFinishedJobs(cutoff: string): Promise<number> {\n try {\n return await new CaptureJobModel(this.db).purgeFinished(cutoff);\n } catch (error) {\n this.logger?.warn({ err: error }, \"capture job purge failed\");\n return 0;\n }\n }\n\n private async removeBuild(buildId: string): Promise<boolean> {\n await new InsightModel(this.db).removeForBuild(buildId);\n await new CaptureJobModel(this.db).removeForBuild(buildId);\n await new BuildComponentModel(this.db).removeForBuild(buildId);\n await new PackageUsageModel(this.db).removeForBuild(buildId);\n await new BuildModel(this.db, this.tables as unknown as BuildTables).remove(buildId);\n return true;\n }\n\n private async latestPerBranch(projectId: string): Promise<Set<string>> {\n const rows = (await this.db.list(this.tables.builds, {\n where: eq(col(this.tables.builds, \"projectId\"), projectId),\n orderBy: desc(col(this.tables.builds, \"updatedAt\")),\n })) as unknown as { gitBranch: string; id: string }[];\n const latest = new Map<string, string>();\n for (const row of rows) {\n const branch = row.gitBranch;\n const current = latest.get(branch);\n if (!current) {\n latest.set(branch, row.id);\n }\n }\n return new Set(latest.values());\n }\n\n private async deleteBuildFiles(projectId: string, buildId: string): Promise<number> {\n const prefix = `${projectId}/builds/${buildId}/`;\n const manifestPath = `${prefix}storybook/manifest.json`;\n let manifestFiles: string[] = [];\n try {\n if (await this.storage.exists(manifestPath)) {\n const raw = await this.storage.read(manifestPath);\n const manifest = JSON.parse(raw.toString(\"utf8\")) as Record<string, string>;\n manifestFiles = Object.values(manifest);\n }\n } catch {\n // ignore — manifest may not exist for old builds\n }\n const files = await this.storage.list(prefix);\n await Promise.all(\n files.map(async (file) => {\n await this.storage.delete(file);\n }),\n );\n // Decrement content refs for this build's manifest\n for (const hash of manifestFiles) {\n try {\n const contentRefs = this.db.tables.contentRefs;\n const existing = (await this.db.get(contentRefs, hash)) as unknown as {\n refCount: number;\n } | null;\n // oxlint-disable-next-line unicorn/prefer-ternary -- refCount check is clearer as if/else\n if (existing) {\n const refCount = existing.refCount;\n // oxlint-disable-next-line unicorn/prefer-ternary -- grace logic is clearer as if/else\n if (refCount <= 1) {\n // Keep for 7-day grace: set refCount 0 and update lastSeenAt, don't delete yet\n await this.db.update(contentRefs, hash, {\n refCount: 0,\n lastSeenAt: new Date().toISOString(),\n });\n } else {\n await this.db.update(contentRefs, hash, {\n refCount: refCount - 1,\n lastSeenAt: new Date().toISOString(),\n });\n }\n }\n } catch {\n // ignore — content_refs may not exist\n }\n }\n // GC stale content_refs with 7-day grace\n try {\n const contentRefs = this.db.tables.contentRefs;\n const cutoff = new Date(Date.now() - 7 * 86_400_000).toISOString();\n const all = (await this.db.list(contentRefs)) as unknown as {\n hash: string;\n refCount: number;\n lastSeenAt: string;\n }[];\n for (const row of all) {\n if (row.refCount === 0 && row.lastSeenAt < cutoff) {\n await this.storage.delete(`content/${row.hash}`).catch(() => {\n // ignore storage delete failure\n });\n // Variants are disposable; never let their deletion affect authoritative data.\n if (/^[a-f0-9]{64}$/u.test(row.hash)) {\n await this.storage.delete(publishedGzipPath(row.hash)).catch(() => {});\n }\n await this.db.remove(contentRefs, row.hash);\n }\n }\n } catch {\n // ignore — content_refs may not exist\n }\n await sweepPublishedGzip(this.storage).catch(() => {});\n return files.length;\n }\n\n async purgeStaleBranches(project: Project, ttlDays: number): Promise<BranchGcResult> {\n if (ttlDays <= 0) {\n return { removedBranches: 0, removedBaselines: 0 };\n }\n const cutoff = new Date(Date.now() - ttlDays * 86_400_000).toISOString();\n const rows = (await this.db.list(this.tables.builds, {\n where: eq(col(this.tables.builds, \"projectId\"), project.id),\n })) as unknown as { gitBranch: string; updatedAt: string }[];\n const stale = collectStaleBranches(rows, project.gitDefaultBranch, cutoff);\n if (stale.size === 0) {\n return { removedBranches: 0, removedBaselines: 0 };\n }\n const baselines = new BaselineModel(this.db, this.tables, this.storage);\n const removedBaselines = await baselines.removeStaleBranches(project.id, stale);\n await this.sweepScreenshots(project);\n this.logger?.info(\n { projectId: project.id, removedBranches: stale.size, removedBaselines },\n \"branch GC complete\",\n );\n return { removedBranches: stale.size, removedBaselines };\n }\n}\n\n/** Options controlling which builds a retention purge removes. */\nexport interface PurgeOptions {\n ttlDays: number;\n keepLatestPerBranch: boolean;\n}\n\n/** Counts of builds and files removed by a retention purge. */\nexport interface PurgeResult {\n removedBuilds: number;\n removedFiles: number;\n}\n\n/** Counts of stale branches and baselines removed. */\nexport interface BranchGcResult {\n removedBranches: number;\n removedBaselines: number;\n}\n\nfunction collectStaleBranches(\n rows: { gitBranch: string; updatedAt: string }[],\n defaultBranch: string,\n cutoff: string,\n): Set<string> {\n const latestAt = branchLatestAt(rows);\n const stale = new Set<string>();\n for (const [branch, updatedAt] of latestAt) {\n if (branch !== defaultBranch && updatedAt < cutoff) {\n stale.add(branch);\n }\n }\n return stale;\n}\n\nfunction branchLatestAt(rows: { gitBranch: string; updatedAt: string }[]): Map<string, string> {\n const latestAt = new Map<string, string>();\n for (const row of rows) {\n const current = latestAt.get(row.gitBranch);\n if (!current || row.updatedAt > current) {\n latestAt.set(row.gitBranch, row.updatedAt);\n }\n }\n return latestAt;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/schema/baseline.d.ts
|
|
4
3
|
/** A baseline row. */
|
|
5
4
|
interface Baseline {
|
|
6
5
|
id: string;
|
|
@@ -9,6 +8,8 @@ interface Baseline {
|
|
|
9
8
|
viewportName: string;
|
|
10
9
|
branch: string;
|
|
11
10
|
snapshotId: string | null;
|
|
11
|
+
/** Build that actually rendered this image; null for unknown or imported origins. */
|
|
12
|
+
sourceBuildId?: string | null;
|
|
12
13
|
screenshotPath: string;
|
|
13
14
|
infraHash: string | null;
|
|
14
15
|
/** JSON `Fingerprint` behind `infraHash`; null on baselines recorded before fingerprints. */
|
|
@@ -20,6 +21,5 @@ interface Baseline {
|
|
|
20
21
|
createdAt: string;
|
|
21
22
|
updatedAt: string;
|
|
22
23
|
}
|
|
23
|
-
//#endregion
|
|
24
24
|
export { Baseline };
|
|
25
25
|
//# sourceMappingURL=baseline.d.mts.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/schema/build-component.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Row type of `build_components` (ADR 0006, decision 9): one row per component of a published
|
|
6
5
|
* build, written from the build's Storybook index when publishing ends. A component is the
|
|
@@ -20,6 +19,5 @@ interface BuildComponent {
|
|
|
20
19
|
hash: string;
|
|
21
20
|
createdAt: string;
|
|
22
21
|
}
|
|
23
|
-
//#endregion
|
|
24
22
|
export { BuildComponent };
|
|
25
23
|
//# sourceMappingURL=build-component.d.mts.map
|
|
@@ -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 { ReviewVerdict } from "../types.mjs";
|
|
4
|
-
//#region src/schema/build-review.d.ts
|
|
5
4
|
/** One verdict on one build. */
|
|
6
5
|
interface BuildReview {
|
|
7
6
|
id: string;
|
|
@@ -12,6 +11,5 @@ interface BuildReview {
|
|
|
12
11
|
body: string | null;
|
|
13
12
|
createdAt: string;
|
|
14
13
|
}
|
|
15
|
-
//#endregion
|
|
16
14
|
export { BuildReview };
|
|
17
15
|
//# sourceMappingURL=build-review.d.mts.map
|
package/dist/schema/build.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { BuildStatus } from "../types.mjs";
|
|
4
|
-
//#region src/schema/build.d.ts
|
|
5
4
|
/** A build row. */
|
|
6
5
|
interface Build {
|
|
7
6
|
id: string;
|
|
@@ -36,6 +35,5 @@ interface Build {
|
|
|
36
35
|
createdAt: string;
|
|
37
36
|
updatedAt: string;
|
|
38
37
|
}
|
|
39
|
-
//#endregion
|
|
40
38
|
export { Build };
|
|
41
39
|
//# sourceMappingURL=build.d.mts.map
|
|
@@ -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 { JobStatus } from "../adapter/capture-queue.mjs";
|
|
4
|
-
//#region src/schema/capture-attempt.d.ts
|
|
5
4
|
/** A capture attempt row: one queued run of a build's capture pipeline. */
|
|
6
5
|
interface CaptureAttempt {
|
|
7
6
|
id: string;
|
|
@@ -33,6 +32,5 @@ interface CaptureLog {
|
|
|
33
32
|
fields: string | null;
|
|
34
33
|
createdAt: string;
|
|
35
34
|
}
|
|
36
|
-
//#endregion
|
|
37
35
|
export { CaptureAttempt, CaptureLog, CaptureLogLevel };
|
|
38
36
|
//# sourceMappingURL=capture-attempt.d.mts.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/schema/capture-job.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Row types of `capture_jobs`, the database queue (ADR 0006, decision 4).
|
|
6
5
|
*
|
|
@@ -55,6 +54,5 @@ interface CaptureJobRow {
|
|
|
55
54
|
createdAt: string;
|
|
56
55
|
updatedAt: string;
|
|
57
56
|
}
|
|
58
|
-
//#endregion
|
|
59
57
|
export { CaptureAttemptStatus, CaptureJobEntry, CaptureJobKind, CaptureJobRow, CaptureJobStatus };
|
|
60
58
|
//# sourceMappingURL=capture-job.d.mts.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/schema/capture-shard.d.ts
|
|
4
3
|
/** What a row of `capture_shards` is: a stage of a sharded capture, or the claim that finalizes it. */
|
|
5
4
|
type CaptureShardKind = "plan" | "render" | "finalize";
|
|
6
5
|
/** Lifecycle of one shard row. */
|
|
@@ -30,6 +29,5 @@ interface CaptureShard {
|
|
|
30
29
|
createdAt: string;
|
|
31
30
|
updatedAt: string;
|
|
32
31
|
}
|
|
33
|
-
//#endregion
|
|
34
32
|
export { CaptureShard, CaptureShardKind, CaptureShardStatus };
|
|
35
33
|
//# sourceMappingURL=capture-shard.d.mts.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/schema/comment.d.ts
|
|
4
3
|
/** A comment row. */
|
|
5
4
|
interface Comment {
|
|
6
5
|
id: string;
|
|
@@ -16,6 +15,5 @@ interface Comment {
|
|
|
16
15
|
createdAt: string;
|
|
17
16
|
updatedAt: string;
|
|
18
17
|
}
|
|
19
|
-
//#endregion
|
|
20
18
|
export { Comment };
|
|
21
19
|
//# sourceMappingURL=comment.d.mts.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/schema/connection.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* A user's own OAuth connection to an external service (`design` providers today,
|
|
6
5
|
* issue trackers later). One per user and service; tokens are encrypted at rest.
|
|
@@ -19,6 +18,5 @@ interface UserConnection {
|
|
|
19
18
|
createdAt: string;
|
|
20
19
|
updatedAt: string;
|
|
21
20
|
}
|
|
22
|
-
//#endregion
|
|
23
21
|
export { UserConnection };
|
|
24
22
|
//# sourceMappingURL=connection.d.mts.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/schema/content-ref.d.ts
|
|
4
3
|
/** Content ref row for deduplicated assets. */
|
|
5
4
|
interface ContentRef {
|
|
6
5
|
hash: string;
|
|
@@ -8,6 +7,5 @@ interface ContentRef {
|
|
|
8
7
|
lastSeenAt: string;
|
|
9
8
|
createdAt: string;
|
|
10
9
|
}
|
|
11
|
-
//#endregion
|
|
12
10
|
export { ContentRef };
|
|
13
11
|
//# sourceMappingURL=content-ref.d.mts.map
|