@storyshelf/core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/capture-queue.d.mts +0 -2
- package/dist/adapter/capture-runner.d.mts +9 -4
- package/dist/adapter/capture-runner.mjs +6 -2
- package/dist/adapter/database.d.mts +63 -3
- package/dist/adapter/database.mjs +4 -3
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +0 -2
- package/dist/adapter/design.mjs +1 -2
- package/dist/adapter/email-sender.d.mts +0 -2
- package/dist/adapter/metadata.d.mts +0 -2
- package/dist/adapter/oauth.d.mts +0 -2
- package/dist/adapter/setup.d.mts +0 -2
- package/dist/adapter/setup.mjs +1 -2
- package/dist/adapter/storage.d.mts +1 -3
- package/dist/adapter/webhook-events.d.mts +0 -2
- package/dist/adapter/webhook-events.mjs +12 -13
- package/dist/adapter/webhook-events.mjs.map +1 -1
- package/dist/adapters/capture-target-offers.d.mts +0 -2
- package/dist/adapters/capture-target-offers.mjs +1 -2
- package/dist/adapters/capture-target.d.mts +0 -2
- package/dist/adapters/capture-target.mjs +1 -2
- package/dist/adapters/notifier/build-comment.d.mts +0 -2
- package/dist/adapters/notifier/build-comment.mjs +1 -2
- package/dist/adapters/notifier/build-state.d.mts +0 -2
- package/dist/adapters/notifier/build-state.mjs +1 -2
- package/dist/adapters/notifier/events.d.mts +0 -2
- package/dist/adapters/notifier/events.mjs +1 -2
- package/dist/adapters/notifier/format.d.mts +0 -2
- package/dist/adapters/notifier/format.mjs +1 -2
- package/dist/adapters/notifier/provider.d.mts +0 -2
- package/dist/adapters/notifier/provider.mjs +1 -2
- package/dist/adapters/notifier/state.d.mts +0 -2
- package/dist/adapters/notifier/state.mjs +1 -2
- package/dist/adapters/notifier/types.d.mts +0 -2
- package/dist/adapters/remote-browser.d.mts +112 -31
- package/dist/adapters/remote-browser.mjs +132 -34
- package/dist/adapters/remote-browser.mjs.map +1 -1
- package/dist/adapters/remote-lease-attempt.mjs +39 -0
- package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
- package/dist/adapters/remote-lease-error.d.mts +25 -0
- package/dist/adapters/remote-lease-error.mjs +54 -0
- package/dist/adapters/remote-lease-error.mjs.map +1 -0
- package/dist/adapters/remote-lease-held.d.mts +15 -0
- package/dist/adapters/remote-lease-held.mjs +86 -0
- package/dist/adapters/remote-lease-held.mjs.map +1 -0
- package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
- package/dist/adapters/remote-lease-shutdown.mjs +32 -0
- package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
- package/dist/adapters/remote-lease.d.mts +39 -0
- package/dist/adapters/remote-lease.mjs +175 -0
- package/dist/adapters/remote-lease.mjs.map +1 -0
- package/dist/adapters/remote-limiter.mjs +121 -0
- package/dist/adapters/remote-limiter.mjs.map +1 -0
- package/dist/adapters/remote-pool.d.mts +56 -0
- package/dist/adapters/remote-pool.mjs +195 -0
- package/dist/adapters/remote-pool.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +0 -2
- package/dist/adapters/render-runs.mjs +1 -2
- package/dist/adapters/setup-runner.d.mts +0 -2
- package/dist/adapters/setup-runner.mjs +1 -2
- package/dist/adapters/sources.d.mts +0 -2
- package/dist/adapters/sources.mjs +1 -2
- package/dist/adapters/validate.d.mts +0 -2
- package/dist/adapters/validate.mjs +2 -2
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +0 -2
- package/dist/ai.mjs +1 -2
- package/dist/auth.d.mts +0 -2
- package/dist/auth.mjs +1 -2
- package/dist/brand-packs.d.mts +0 -2
- package/dist/brand-packs.mjs +0 -1
- package/dist/capture/a11y/axe-source.d.mts +0 -2
- package/dist/capture/a11y/axe-source.mjs +1 -2
- package/dist/capture/a11y/in-page.d.mts +1 -3
- package/dist/capture/a11y/in-page.mjs +3 -4
- package/dist/capture/a11y/in-page.mjs.map +1 -1
- package/dist/capture/a11y/map.d.mts +0 -2
- package/dist/capture/a11y/map.mjs +1 -2
- package/dist/capture/a11y/options.d.mts +0 -2
- package/dist/capture/a11y/options.mjs +1 -2
- package/dist/capture/adapter.d.mts +5 -2
- package/dist/capture/adapter.mjs +1 -2
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +0 -2
- package/dist/capture/affected.mjs +1 -2
- package/dist/capture/attempt-logger.mjs +1 -2
- package/dist/capture/browsers.d.mts +0 -2
- package/dist/capture/browsers.mjs +1 -2
- package/dist/capture/build-state/deliver.d.mts +0 -2
- package/dist/capture/build-state/deliver.mjs +1 -2
- package/dist/capture/build-state/notify-job.d.mts +0 -2
- package/dist/capture/build-state/notify-job.mjs +1 -2
- package/dist/capture/build-state/publisher.d.mts +0 -2
- package/dist/capture/build-state/publisher.mjs +1 -2
- package/dist/capture/build-state/resume.d.mts +0 -2
- package/dist/capture/build-state/resume.mjs +1 -2
- package/dist/capture/build-state/types.d.mts +1 -3
- package/dist/capture/catalog/catalogue.d.mts +0 -2
- package/dist/capture/catalog/catalogue.mjs +1 -2
- package/dist/capture/catalog/components.d.mts +0 -2
- package/dist/capture/catalog/components.mjs +1 -2
- package/dist/capture/catalog/history.d.mts +0 -2
- package/dist/capture/catalog/history.mjs +1 -2
- package/dist/capture/catalog/hook.d.mts +0 -2
- package/dist/capture/catalog/hook.mjs +1 -2
- package/dist/capture/catalog/index.d.mts +2 -2
- package/dist/capture/catalog/load.d.mts +0 -2
- package/dist/capture/catalog/load.mjs +1 -2
- package/dist/capture/catalog/parse.d.mts +0 -2
- package/dist/capture/catalog/parse.mjs +1 -2
- package/dist/capture/catalog/read.d.mts +6 -3
- package/dist/capture/catalog/read.mjs +14 -8
- package/dist/capture/catalog/read.mjs.map +1 -1
- package/dist/capture/catalog/types.d.mts +0 -2
- package/dist/capture/comparison-reference.d.mts +27 -0
- package/dist/capture/comparison-reference.mjs +75 -0
- package/dist/capture/comparison-reference.mjs.map +1 -0
- package/dist/capture/consumer/capture-handler.d.mts +1 -3
- package/dist/capture/consumer/capture-handler.mjs +1 -2
- package/dist/capture/consumer/idle.d.mts +0 -2
- package/dist/capture/consumer/idle.mjs +1 -2
- package/dist/capture/consumer/index.d.mts +0 -2
- package/dist/capture/consumer/index.mjs +1 -2
- package/dist/capture/consumer/loop.mjs +1 -2
- package/dist/capture/consumer/metrics.d.mts +0 -2
- package/dist/capture/consumer/metrics.mjs +1 -2
- package/dist/capture/consumer/retry.d.mts +0 -2
- package/dist/capture/consumer/retry.mjs +1 -2
- package/dist/capture/consumer/run-job.mjs +1 -2
- package/dist/capture/consumer/startup.mjs +1 -2
- package/dist/capture/consumer/types.d.mts +0 -2
- package/dist/capture/crash-failure.d.mts +0 -2
- package/dist/capture/crash-failure.mjs +1 -2
- package/dist/capture/db-queue/cadence.d.mts +0 -2
- package/dist/capture/db-queue/cadence.mjs +1 -2
- package/dist/capture/db-queue/claim.d.mts +0 -2
- package/dist/capture/db-queue/claim.mjs +20 -4
- package/dist/capture/db-queue/claim.mjs.map +1 -1
- package/dist/capture/db-queue/clock.mjs +1 -2
- package/dist/capture/db-queue/codec.mjs +1 -2
- package/dist/capture/db-queue/index.d.mts +0 -2
- package/dist/capture/db-queue/index.mjs +3 -3
- package/dist/capture/db-queue/index.mjs.map +1 -1
- package/dist/capture/db-queue/keeper.d.mts +0 -2
- package/dist/capture/db-queue/keeper.mjs +1 -2
- package/dist/capture/db-queue/lease.mjs +1 -2
- package/dist/capture/db-queue/lifecycle.mjs +1 -2
- package/dist/capture/db-queue/operations.mjs +1 -2
- package/dist/capture/db-queue/poll-loop.d.mts +0 -2
- package/dist/capture/db-queue/poll-loop.mjs +1 -2
- package/dist/capture/db-queue/poll.d.mts +0 -2
- package/dist/capture/db-queue/poll.mjs +1 -2
- package/dist/capture/db-queue/timers.d.mts +0 -2
- package/dist/capture/db-queue/timers.mjs +1 -2
- package/dist/capture/db-queue/types.d.mts +0 -2
- package/dist/capture/db-queue/types.mjs +1 -2
- package/dist/capture/db-queue/types.mjs.map +1 -1
- package/dist/capture/db-queue/wake.mjs +1 -2
- package/dist/capture/design.mjs +1 -2
- package/dist/capture/diff-record.mjs +1 -2
- package/dist/capture/dispatch.d.mts +1 -3
- package/dist/capture/dispatch.mjs +17 -5
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +1 -2
- package/dist/capture/failure-sets.mjs +1 -2
- package/dist/capture/fingerprint.d.mts +0 -2
- package/dist/capture/fingerprint.mjs +1 -2
- package/dist/capture/forked/channel.d.mts +0 -2
- package/dist/capture/forked/child.d.mts +0 -2
- package/dist/capture/forked/child.mjs +1 -2
- package/dist/capture/forked/crash-tracker.mjs +1 -2
- package/dist/capture/forked/decision.d.mts +0 -2
- package/dist/capture/forked/decision.mjs +1 -2
- package/dist/capture/forked/exit.mjs +1 -2
- package/dist/capture/forked/fork.d.mts +0 -2
- package/dist/capture/forked/fork.mjs +1 -2
- package/dist/capture/forked/forked-capture.d.mts +0 -2
- package/dist/capture/forked/forked-capture.mjs +1 -2
- package/dist/capture/forked/lease-book.mjs +1 -2
- package/dist/capture/forked/protocol.d.mts +0 -2
- package/dist/capture/forked/protocol.mjs +1 -2
- package/dist/capture/forked/runs.mjs +1 -2
- package/dist/capture/guards.mjs +1 -2
- package/dist/capture/ignore-regions.d.mts +36 -0
- package/dist/capture/ignore-regions.mjs +65 -0
- package/dist/capture/ignore-regions.mjs.map +1 -0
- package/dist/capture/isolate.mjs +1 -2
- package/dist/capture/job-lease.d.mts +0 -2
- package/dist/capture/job-lease.mjs +1 -2
- package/dist/capture/matrix.mjs +4 -4
- package/dist/capture/matrix.mjs.map +1 -1
- package/dist/capture/modes.d.mts +0 -2
- package/dist/capture/modes.mjs +2 -3
- package/dist/capture/modes.mjs.map +1 -1
- package/dist/capture/orchestrator.d.mts +0 -2
- package/dist/capture/orchestrator.mjs +2 -3
- package/dist/capture/pipeline.d.mts +0 -2
- package/dist/capture/pipeline.mjs +37 -6
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +0 -2
- package/dist/capture/progress.mjs +1 -2
- package/dist/capture/publish/accept.d.mts +0 -2
- package/dist/capture/publish/accept.mjs +1 -2
- package/dist/capture/publish/complete.d.mts +1 -3
- package/dist/capture/publish/complete.mjs +1 -2
- package/dist/capture/publish/extract.d.mts +0 -2
- package/dist/capture/publish/extract.mjs +6 -5
- package/dist/capture/publish/extract.mjs.map +1 -1
- package/dist/capture/publish/handler.d.mts +1 -3
- package/dist/capture/publish/handler.mjs +1 -2
- package/dist/capture/publish/index.d.mts +2 -1
- package/dist/capture/publish/invalid-zip.d.mts +8 -0
- package/dist/capture/publish/invalid-zip.mjs +11 -0
- package/dist/capture/publish/invalid-zip.mjs.map +1 -0
- package/dist/capture/publish/limits.d.mts +0 -2
- package/dist/capture/publish/limits.mjs +1 -2
- package/dist/capture/publish/published.d.mts +0 -2
- package/dist/capture/publish/published.mjs +1 -2
- package/dist/capture/publish/run.d.mts +1 -3
- package/dist/capture/publish/run.mjs +1 -2
- package/dist/capture/rediff.d.mts +1 -3
- package/dist/capture/rediff.mjs +19 -6
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +1 -2
- package/dist/capture/render.mjs.map +1 -1
- package/dist/capture/scratch.d.mts +0 -2
- package/dist/capture/scratch.mjs +2 -4
- package/dist/capture/scratch.mjs.map +1 -1
- package/dist/capture/shard-plan.mjs +1 -2
- package/dist/capture/shards/context.mjs +3 -3
- package/dist/capture/shards/context.mjs.map +1 -1
- package/dist/capture/shards/execute.mjs +4 -3
- package/dist/capture/shards/execute.mjs.map +1 -1
- package/dist/capture/shards/fan-out.mjs +1 -2
- package/dist/capture/shards/history.mjs +1 -2
- package/dist/capture/shards/reduce.mjs +1 -2
- package/dist/capture/sizing.d.mts +0 -2
- package/dist/capture/sizing.mjs +1 -2
- package/dist/capture/skip-checks.mjs +1 -2
- package/dist/capture/statics.d.mts +0 -2
- package/dist/capture/statics.mjs +2 -4
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +0 -2
- package/dist/capture/storybook-urls.mjs +3 -3
- package/dist/capture/storybook-urls.mjs.map +1 -1
- package/dist/capture/storybook.d.mts +0 -2
- package/dist/capture/storybook.mjs +8 -4
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +1 -2
- package/dist/capture/targets.d.mts +0 -2
- package/dist/capture/targets.mjs +1 -2
- package/dist/capture/tests.d.mts +0 -2
- package/dist/capture/tests.mjs +13 -3
- package/dist/capture/tests.mjs.map +1 -1
- package/dist/capture/token-chunks.mjs +24 -7
- package/dist/capture/token-chunks.mjs.map +1 -1
- package/dist/capture.d.mts +5 -2
- package/dist/capture.mjs +5 -2
- package/dist/config.d.mts +16 -3
- package/dist/config.mjs +6 -3
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +0 -2
- package/dist/diff.mjs +1 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/insights/budget.d.mts +0 -2
- package/dist/insights/budget.mjs +1 -2
- package/dist/insights/envelope.d.mts +0 -2
- package/dist/insights/envelope.mjs +1 -2
- package/dist/insights/evidence-health.d.mts +0 -2
- package/dist/insights/evidence-health.mjs +1 -2
- package/dist/insights/evidence.d.mts +0 -2
- package/dist/insights/evidence.mjs +1 -2
- package/dist/insights/hash.d.mts +0 -2
- package/dist/insights/hash.mjs +1 -2
- package/dist/insights/limits.d.mts +0 -2
- package/dist/insights/limits.mjs +1 -2
- package/dist/insights/prompts/v1.d.mts +0 -2
- package/dist/insights/prompts/v1.mjs +1 -2
- package/dist/insights/redact.d.mts +0 -2
- package/dist/insights/redact.mjs +1 -2
- package/dist/insights/text.mjs +1 -2
- package/dist/logger.d.mts +0 -2
- package/dist/logger.mjs +1 -2
- package/dist/models/ai-budget-alert.d.mts +0 -2
- package/dist/models/ai-budget-alert.mjs +1 -2
- package/dist/models/ai-usage.d.mts +0 -2
- package/dist/models/ai-usage.mjs +1 -2
- package/dist/models/baseline-approval.d.mts +18 -0
- package/dist/models/baseline-approval.mjs +41 -0
- package/dist/models/baseline-approval.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +0 -2
- package/dist/models/baseline-archive.mjs +1 -2
- package/dist/models/baseline-atomic.mjs +66 -0
- package/dist/models/baseline-atomic.mjs.map +1 -0
- package/dist/models/baseline-conflict.d.mts +6 -0
- package/dist/models/baseline-conflict.mjs +9 -0
- package/dist/models/baseline-conflict.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +0 -2
- package/dist/models/baseline-export.mjs +1 -2
- package/dist/models/baseline-import.mjs +1 -2
- package/dist/models/baseline-origin.d.mts +16 -0
- package/dist/models/baseline-origin.mjs +35 -0
- package/dist/models/baseline-origin.mjs.map +1 -0
- package/dist/models/baseline-status.d.mts +0 -2
- package/dist/models/baseline-status.mjs +1 -2
- package/dist/models/baseline-transfer.d.mts +1 -2
- package/dist/models/baseline-transfer.mjs +30 -2
- package/dist/models/baseline-transfer.mjs.map +1 -1
- package/dist/models/baseline.d.mts +5 -2
- package/dist/models/baseline.mjs +91 -12
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +0 -2
- package/dist/models/build-component.mjs +1 -2
- package/dist/models/build-review-status.d.mts +0 -2
- package/dist/models/build-review-status.mjs +1 -2
- package/dist/models/build-review.d.mts +0 -2
- package/dist/models/build-review.mjs +1 -2
- package/dist/models/build.d.mts +1 -3
- package/dist/models/build.mjs +14 -15
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +0 -2
- package/dist/models/capture-attempt.mjs +1 -2
- package/dist/models/capture-job.d.mts +0 -2
- package/dist/models/capture-job.mjs +1 -2
- package/dist/models/capture-log.d.mts +0 -2
- package/dist/models/capture-log.mjs +1 -2
- package/dist/models/capture-shard.d.mts +0 -2
- package/dist/models/capture-shard.mjs +1 -2
- package/dist/models/changeset.d.mts +0 -2
- package/dist/models/changeset.mjs +1 -2
- package/dist/models/columns.mjs +1 -2
- package/dist/models/comment.d.mts +0 -2
- package/dist/models/comment.mjs +1 -2
- package/dist/models/component.d.mts +0 -2
- package/dist/models/component.mjs +1 -2
- package/dist/models/connections.d.mts +0 -2
- package/dist/models/connections.mjs +1 -2
- package/dist/models/content-ref.d.mts +0 -2
- package/dist/models/content-ref.mjs +1 -2
- package/dist/models/credentials.d.mts +0 -2
- package/dist/models/credentials.mjs +1 -2
- package/dist/models/design-credentials.d.mts +0 -2
- package/dist/models/design-credentials.mjs +1 -2
- package/dist/models/design-health.d.mts +0 -2
- package/dist/models/design-health.mjs +1 -2
- package/dist/models/design-nodes.d.mts +2 -4
- package/dist/models/design-nodes.mjs +1 -2
- package/dist/models/design-renders.mjs +1 -2
- package/dist/models/design-resolve.d.mts +0 -2
- package/dist/models/design-resolve.mjs +1 -2
- package/dist/models/design.d.mts +0 -2
- package/dist/models/design.mjs +1 -2
- package/dist/models/inherited-ratio.d.mts +0 -2
- package/dist/models/inherited-ratio.mjs +1 -2
- package/dist/models/insight.d.mts +0 -2
- package/dist/models/insight.mjs +1 -2
- package/dist/models/label.d.mts +0 -2
- package/dist/models/label.mjs +1 -2
- package/dist/models/member.d.mts +0 -2
- package/dist/models/member.mjs +1 -2
- package/dist/models/notification-channel.d.mts +0 -2
- package/dist/models/notification-channel.mjs +1 -2
- package/dist/models/notification-subscription.d.mts +0 -2
- package/dist/models/notification-subscription.mjs +1 -2
- package/dist/models/project-group-mapping.d.mts +0 -2
- package/dist/models/project-group-mapping.mjs +1 -2
- package/dist/models/project.d.mts +0 -2
- package/dist/models/project.mjs +1 -2
- package/dist/models/public-origin.d.mts +0 -2
- package/dist/models/public-origin.mjs +1 -2
- package/dist/models/review-switch-off.d.mts +0 -2
- package/dist/models/review-switch-off.mjs +1 -2
- package/dist/models/site-settings.d.mts +0 -2
- package/dist/models/site-settings.mjs +1 -2
- package/dist/models/snapshot.d.mts +3 -2
- package/dist/models/snapshot.mjs +4 -2
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +0 -2
- package/dist/models/token.mjs +1 -2
- package/dist/models/usage.d.mts +0 -2
- package/dist/models/usage.mjs +1 -2
- package/dist/models/user.d.mts +0 -2
- package/dist/models/user.mjs +1 -2
- package/dist/models/webhook.d.mts +0 -2
- package/dist/models/webhook.mjs +1 -2
- package/dist/models.d.mts +5 -2
- package/dist/models.mjs +5 -2
- package/dist/options.d.mts +0 -2
- package/dist/options.mjs +2 -4
- package/dist/options.mjs.map +1 -1
- package/dist/paths.d.mts +3 -3
- package/dist/paths.mjs +7 -3
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +1 -3
- package/dist/retention/ai.mjs +0 -1
- package/dist/retention/pending.mjs +1 -2
- package/dist/retention/published-gzip-sweep.mjs +28 -0
- package/dist/retention/published-gzip-sweep.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +0 -2
- package/dist/retention/screenshot-sweep.mjs +1 -2
- package/dist/retention.d.mts +0 -2
- package/dist/retention.mjs +5 -2
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +2 -2
- package/dist/schema/build-component.d.mts +0 -2
- package/dist/schema/build-review.d.mts +0 -2
- package/dist/schema/build.d.mts +0 -2
- package/dist/schema/capture-attempt.d.mts +0 -2
- package/dist/schema/capture-job.d.mts +0 -2
- package/dist/schema/capture-shard.d.mts +0 -2
- package/dist/schema/comment.d.mts +0 -2
- package/dist/schema/connection.d.mts +0 -2
- package/dist/schema/content-ref.d.mts +0 -2
- package/dist/schema/design.d.mts +0 -2
- package/dist/schema/insight.d.mts +0 -2
- package/dist/schema/label.d.mts +0 -2
- package/dist/schema/member.d.mts +0 -2
- package/dist/schema/notification.d.mts +0 -2
- package/dist/schema/project-group-mapping.d.mts +0 -2
- package/dist/schema/project.d.mts +0 -2
- package/dist/schema/site-setting.d.mts +0 -2
- package/dist/schema/snapshot.d.mts +5 -2
- package/dist/schema/token.d.mts +0 -2
- package/dist/schema/usage.d.mts +0 -2
- package/dist/schema/user.d.mts +0 -2
- package/dist/schema/webhook.d.mts +0 -2
- package/dist/schema.d.mts +4 -6
- package/dist/serverless.d.mts +0 -2
- package/dist/serverless.mjs +1 -2
- package/dist/test-helpers/build-reference-contract.d.mts +14 -0
- package/dist/test-helpers/build-reference-contract.mjs +66 -0
- package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +0 -2
- package/dist/test-helpers/create-project.mjs +1 -2
- package/dist/test-helpers/database-contract.d.mts +0 -2
- package/dist/test-helpers/database-contract.mjs +37 -2
- package/dist/test-helpers/database-contract.mjs.map +1 -1
- package/dist/test-helpers/deferred.d.mts +0 -2
- package/dist/test-helpers/deferred.mjs +1 -2
- package/dist/test-helpers/design-contract.d.mts +0 -2
- package/dist/test-helpers/design-contract.mjs +1 -2
- package/dist/test-helpers/fake-adapters.d.mts +0 -2
- package/dist/test-helpers/fake-adapters.mjs +1 -2
- package/dist/test-helpers/fake-database.d.mts +0 -2
- package/dist/test-helpers/fake-database.mjs +7 -2
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +0 -2
- package/dist/test-helpers/fake-design.mjs +1 -2
- package/dist/test-helpers/fake-notifier.d.mts +0 -2
- package/dist/test-helpers/fake-notifier.mjs +1 -2
- package/dist/test-helpers/fake-storage.d.mts +0 -2
- package/dist/test-helpers/fake-storage.mjs +1 -2
- package/dist/test-helpers/fake-tables.mjs +5 -2
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +0 -2
- package/dist/test-helpers/manual-clock.mjs +1 -2
- package/dist/test-helpers/notifier-contract.d.mts +0 -2
- package/dist/test-helpers/notifier-contract.mjs +1 -2
- package/dist/test-helpers/queue-contract.d.mts +0 -2
- package/dist/test-helpers/queue-contract.mjs +1 -2
- package/dist/test-helpers/queue-multi-instance.d.mts +0 -2
- package/dist/test-helpers/queue-multi-instance.mjs +1 -2
- package/dist/test-helpers/runner-contract.d.mts +0 -2
- package/dist/test-helpers/runner-contract.mjs +1 -2
- package/dist/test-helpers/sql-chunks.mjs +2 -2
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.d.mts +0 -2
- package/dist/test-helpers/storage-contract.mjs +1 -2
- package/dist/test-helpers.d.mts +2 -1
- package/dist/test-helpers.mjs +2 -1
- package/dist/tracing.mjs +36 -6
- package/dist/tracing.mjs.map +1 -1
- package/dist/types.d.mts +0 -2
- package/dist/types.mjs +1 -2
- package/dist/urls.d.mts +0 -2
- package/dist/urls.mjs +1 -2
- package/dist/utils/adaptive-concurrency.d.mts +0 -2
- package/dist/utils/adaptive-concurrency.mjs +1 -2
- package/dist/utils/connect-retry.d.mts +2 -2
- package/dist/utils/connect-retry.mjs +2 -3
- package/dist/utils/connect-retry.mjs.map +1 -1
- package/dist/utils/encrypt.d.mts +0 -2
- package/dist/utils/encrypt.mjs +1 -2
- package/dist/utils/environment.d.mts +0 -2
- package/dist/utils/environment.mjs +1 -2
- package/dist/utils/hash.d.mts +0 -2
- package/dist/utils/hash.mjs +1 -2
- package/dist/utils/http.d.mts +13 -3
- package/dist/utils/http.mjs +79 -17
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +0 -2
- package/dist/utils/map-adaptive.mjs +1 -2
- package/dist/utils/map-limit.d.mts +0 -2
- package/dist/utils/map-limit.mjs +1 -2
- package/dist/utils/mime.d.mts +0 -2
- package/dist/utils/mime.mjs +1 -2
- package/dist/utils/page-limiter.d.mts +0 -2
- package/dist/utils/page-limiter.mjs +1 -2
- package/dist/utils/path-security.d.mts +0 -2
- package/dist/utils/path-security.mjs +2 -4
- package/dist/utils/path-security.mjs.map +1 -1
- package/dist/utils/public-origin.d.mts +0 -2
- package/dist/utils/public-origin.mjs +1 -2
- package/dist/utils/redact.d.mts +0 -2
- package/dist/utils/redact.mjs +1 -2
- package/dist/utils/remote-environment.d.mts +40 -3
- package/dist/utils/remote-environment.mjs +52 -3
- package/dist/utils/remote-environment.mjs.map +1 -1
- package/dist/utils/resources.d.mts +0 -2
- package/dist/utils/resources.mjs +1 -2
- package/dist/utils/screenshot-store.d.mts +0 -2
- package/dist/utils/screenshot-store.mjs +1 -2
- package/dist/utils/session-gate.d.mts +7 -6
- package/dist/utils/session-gate.mjs +28 -12
- package/dist/utils/session-gate.mjs.map +1 -1
- package/dist/utils/storybook-token.d.mts +11 -3
- package/dist/utils/storybook-token.mjs +10 -9
- package/dist/utils/storybook-token.mjs.map +1 -1
- package/dist/utils/storybook-url.d.mts +0 -2
- package/dist/utils/storybook-url.mjs +1 -2
- package/dist/utils/timing.d.mts +0 -2
- package/dist/utils/timing.mjs +1 -2
- package/dist/utils/ulid.d.mts +0 -2
- package/dist/utils/ulid.mjs +1 -2
- package/dist/utils/zip-read.mjs +1 -2
- package/dist/utils/zip-write.d.mts +0 -2
- package/dist/utils/zip-write.mjs +1 -2
- package/dist/utils.d.mts +5 -5
- package/dist/utils.mjs +5 -5
- package/package.json +6 -6
package/dist/utils/http.d.mts
CHANGED
|
@@ -2,7 +2,6 @@ import __tsdown_shims_path from 'node:path';
|
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { Logger } from "../logger.mjs";
|
|
4
4
|
import { Buffer } from "node:buffer";
|
|
5
|
-
//#region src/utils/http.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* Fetch JSON with timeout, retries, and structured errors.
|
|
8
7
|
*
|
|
@@ -32,6 +31,18 @@ declare function httpBytes(url: string, options?: HttpRequestOptions): Promise<B
|
|
|
32
31
|
* @returns The raw response body text.
|
|
33
32
|
*/
|
|
34
33
|
declare function httpText(url: string, options?: HttpTextOptions): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Send a request and resolve with the response status, without ever reading the response body (it
|
|
36
|
+
* is cancelled on every response). Use it when the answer is only "did it succeed", such as webhook
|
|
37
|
+
* delivery, so a receiver cannot make the process buffer an arbitrarily large body. Retries,
|
|
38
|
+
* timeouts, redaction and tracing are those of {@link httpText}. A non-2xx response throws
|
|
39
|
+
* {@link HttpError} with an empty `body`.
|
|
40
|
+
*
|
|
41
|
+
* @param url - Absolute request URL.
|
|
42
|
+
* @param options - Method, headers, JSON body, timeout, retries, logger.
|
|
43
|
+
* @returns The 2xx response status.
|
|
44
|
+
*/
|
|
45
|
+
declare function httpStatus(url: string, options?: HttpTextOptions): Promise<number>;
|
|
35
46
|
/** Failed HTTP response: status code plus a snippet of the response body. */
|
|
36
47
|
declare class HttpError extends Error {
|
|
37
48
|
readonly status: number;
|
|
@@ -69,6 +80,5 @@ interface HttpTextOptions {
|
|
|
69
80
|
signal?: AbortSignal;
|
|
70
81
|
logger?: Logger;
|
|
71
82
|
}
|
|
72
|
-
|
|
73
|
-
export { HttpError, HttpRequestOptions, HttpTextOptions, httpBytes, httpJson, httpText };
|
|
83
|
+
export { HttpError, HttpRequestOptions, HttpTextOptions, httpBytes, httpJson, httpStatus, httpText };
|
|
74
84
|
//# sourceMappingURL=http.d.mts.map
|
package/dist/utils/http.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { injectTraceContext, withSpan } from "../tracing.mjs";
|
|
4
|
+
import { injectTraceContext, setSpanMessage, spanException, withSpan } from "../tracing.mjs";
|
|
5
5
|
import { Buffer } from "node:buffer";
|
|
6
|
-
|
|
6
|
+
// src/utils/http.ts
|
|
7
7
|
/**
|
|
8
8
|
* Fetch JSON with timeout, retries, and structured errors.
|
|
9
9
|
*
|
|
@@ -39,6 +39,20 @@ async function httpBytes(url, options = {}) {
|
|
|
39
39
|
async function httpText(url, options = {}) {
|
|
40
40
|
return await requestWithRetry(url, options, options.retries ?? DEFAULT_RETRIES, options.timeoutMs ?? DEFAULT_TIMEOUT_MS, doFetchText);
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Send a request and resolve with the response status, without ever reading the response body (it
|
|
44
|
+
* is cancelled on every response). Use it when the answer is only "did it succeed", such as webhook
|
|
45
|
+
* delivery, so a receiver cannot make the process buffer an arbitrarily large body. Retries,
|
|
46
|
+
* timeouts, redaction and tracing are those of {@link httpText}. A non-2xx response throws
|
|
47
|
+
* {@link HttpError} with an empty `body`.
|
|
48
|
+
*
|
|
49
|
+
* @param url - Absolute request URL.
|
|
50
|
+
* @param options - Method, headers, JSON body, timeout, retries, logger.
|
|
51
|
+
* @returns The 2xx response status.
|
|
52
|
+
*/
|
|
53
|
+
async function httpStatus(url, options = {}) {
|
|
54
|
+
return await requestWithRetry(url, options, options.retries ?? DEFAULT_RETRIES, options.timeoutMs ?? DEFAULT_TIMEOUT_MS, doFetchStatus);
|
|
55
|
+
}
|
|
42
56
|
/** Failed HTTP response: status code plus a snippet of the response body. */
|
|
43
57
|
var HttpError = class extends Error {
|
|
44
58
|
status;
|
|
@@ -91,25 +105,54 @@ async function requestWithRetry(url, options, maxAttempts, timeoutMs, perform) {
|
|
|
91
105
|
for (let attempt = 1;; attempt += 1) try {
|
|
92
106
|
return await perform(url, options, timeoutMs);
|
|
93
107
|
} catch (error) {
|
|
94
|
-
span
|
|
108
|
+
describeFailure(span, error, url);
|
|
95
109
|
if (options.signal?.aborted || !shouldRetry(error, attempt, maxAttempts)) throw error;
|
|
110
|
+
span.recordException(spanException(error));
|
|
96
111
|
span.setAttribute("http.retry.count", attempt);
|
|
97
112
|
await backoff(error, attempt, url, options.logger);
|
|
98
113
|
}
|
|
99
114
|
}, {
|
|
100
115
|
"http.request.method": options.method ?? "GET",
|
|
101
|
-
"url.full":
|
|
116
|
+
"url.full": redactUrlForLog(url)
|
|
102
117
|
});
|
|
103
118
|
}
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
/**
|
|
120
|
+
* The form of a request URL that may appear in logs and span attributes: its origin
|
|
121
|
+
* (`protocol//host`) and nothing else. The path of an incoming webhook (Slack, Teams) is the secret,
|
|
122
|
+
* a bot token can be the first segment (Telegram), and other URLs carry credentials in the userinfo
|
|
123
|
+
* or the query, so the path, query, fragment and userinfo are dropped by construction. There is no
|
|
124
|
+
* opt-out.
|
|
125
|
+
*/
|
|
126
|
+
function redactUrlForLog(url) {
|
|
127
|
+
try {
|
|
128
|
+
const { protocol, host } = new URL(url);
|
|
129
|
+
return `${protocol}//${host}`;
|
|
130
|
+
} catch {
|
|
131
|
+
return "[invalid url]";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* What a span may say about a failed request. An HTTP failure carries its status and never the
|
|
136
|
+
* response body, which can echo the request URL in any encoding. Any other failure keeps its
|
|
137
|
+
* message with the request URL replaced by its origin, because native `fetch` repeats the whole
|
|
138
|
+
* URL, userinfo included, when it refuses one. The error a caller catches is not changed.
|
|
139
|
+
*/
|
|
140
|
+
function describeFailure(span, error, url) {
|
|
141
|
+
if (error instanceof HttpError) {
|
|
142
|
+
span.setAttribute("http.response.status_code", error.status);
|
|
143
|
+
setSpanMessage(error, `HTTP ${String(error.status)}`);
|
|
144
|
+
} else if (error instanceof Error) setSpanMessage(error, scrubUrl(error.message, url));
|
|
145
|
+
}
|
|
146
|
+
/** `text` with the request URL, as given and as normalized, replaced by its origin. */
|
|
147
|
+
function scrubUrl(text, url) {
|
|
148
|
+
const origin = redactUrlForLog(url);
|
|
149
|
+
const normalized = URL.canParse(url) ? new URL(url).href : url;
|
|
150
|
+
return text.replaceAll(url, origin).replaceAll(normalized, origin);
|
|
108
151
|
}
|
|
109
152
|
async function backoff(error, attempt, url, logger) {
|
|
110
153
|
const delay = retryDelayMs(error, attempt);
|
|
111
154
|
logger?.warn({
|
|
112
|
-
url,
|
|
155
|
+
url: redactUrlForLog(url),
|
|
113
156
|
attempt,
|
|
114
157
|
delayMs: delay
|
|
115
158
|
}, "http request failed, retrying");
|
|
@@ -137,15 +180,20 @@ async function doFetchJson(url, options, timeoutMs) {
|
|
|
137
180
|
async function doFetchText(url, options, timeoutMs) {
|
|
138
181
|
return await (await doFetchResponse(url, options, timeoutMs)).text();
|
|
139
182
|
}
|
|
183
|
+
async function doFetchStatus(url, options, timeoutMs) {
|
|
184
|
+
const response = await doFetchResponse(url, options, timeoutMs, false);
|
|
185
|
+
await discardBody(response);
|
|
186
|
+
return response.status;
|
|
187
|
+
}
|
|
140
188
|
async function doFetchBytes(url, options, timeoutMs) {
|
|
141
189
|
const response = await doFetchResponse(url, options, timeoutMs);
|
|
142
190
|
return Buffer.from(await response.arrayBuffer());
|
|
143
191
|
}
|
|
144
|
-
async function doFetchResponse(url, options, timeoutMs) {
|
|
192
|
+
async function doFetchResponse(url, options, timeoutMs, readErrorBody = true) {
|
|
145
193
|
const request = buildRequest(options);
|
|
146
194
|
injectTraceContext(request.headers);
|
|
147
195
|
options.logger?.debug({
|
|
148
|
-
url,
|
|
196
|
+
url: redactUrlForLog(url),
|
|
149
197
|
method: request.method
|
|
150
198
|
}, "http request");
|
|
151
199
|
const response = await fetch(url, {
|
|
@@ -154,13 +202,27 @@ async function doFetchResponse(url, options, timeoutMs) {
|
|
|
154
202
|
body: request.body,
|
|
155
203
|
signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs)
|
|
156
204
|
});
|
|
157
|
-
if (!response.ok)
|
|
158
|
-
const text = await response.text();
|
|
159
|
-
throw new HttpError(response.status, text.slice(0, MAX_BODY_SNIPPET), response.headers);
|
|
160
|
-
}
|
|
205
|
+
if (!response.ok) throw await failureOf(response, readErrorBody);
|
|
161
206
|
return response;
|
|
162
207
|
}
|
|
163
|
-
|
|
164
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Cancel an unread body. Cleanup only: a rejected cancel must never replace the request's outcome,
|
|
210
|
+
* or a delivered request would look failed and be retried.
|
|
211
|
+
*/
|
|
212
|
+
async function discardBody(response) {
|
|
213
|
+
try {
|
|
214
|
+
await response.body?.cancel();
|
|
215
|
+
} catch {}
|
|
216
|
+
}
|
|
217
|
+
/** The {@link HttpError} for a non-2xx response; the body is read (and capped) only on request. */
|
|
218
|
+
async function failureOf(response, readBody) {
|
|
219
|
+
if (!readBody) {
|
|
220
|
+
await discardBody(response);
|
|
221
|
+
return new HttpError(response.status, "", response.headers);
|
|
222
|
+
}
|
|
223
|
+
const text = await response.text();
|
|
224
|
+
return new HttpError(response.status, text.slice(0, MAX_BODY_SNIPPET), response.headers);
|
|
225
|
+
}
|
|
226
|
+
export { HttpError, httpBytes, httpJson, httpStatus, httpText, redactUrlForLog };
|
|
165
227
|
|
|
166
228
|
//# sourceMappingURL=http.mjs.map
|
package/dist/utils/http.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.mjs","names":[],"sources":["../../src/utils/http.ts"],"sourcesContent":["import { Buffer } from \"node:buffer\";\nimport type { Logger } from \"../logger.ts\";\nimport { injectTraceContext, withSpan } from \"../tracing.ts\";\n\n/**\n * Fetch JSON with timeout, retries, and structured errors.\n *\n * Retries network failures, 429, and 502/503/504 (honoring `Retry-After`);\n * every other status throws {@link HttpError} immediately.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, JSON body, timeout, retries, logger.\n * @returns The parsed JSON response body.\n */\nexport async function httpJson<T>(url: string, options: HttpRequestOptions = {}): Promise<T> {\n return await requestWithRetry<T>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchJson,\n );\n}\n\n/**\n * Download a binary response (images) with the same timeout, retry, and\n * `Retry-After` semantics as {@link httpJson}.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, timeout, retries, logger.\n * @returns The raw response bytes.\n */\nexport async function httpBytes(url: string, options: HttpRequestOptions = {}): Promise<Buffer> {\n return await requestWithRetry<Buffer>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchBytes,\n );\n}\n\n/**\n * POST/PUT plain payloads (e.g. Slack/Teams webhook cards) with the same\n * timeout, retry, and `Retry-After` semantics as {@link httpJson}.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, text body, timeout, retries, logger.\n * @returns The raw response body text.\n */\nexport async function httpText(url: string, options: HttpTextOptions = {}): Promise<string> {\n return await requestWithRetry<string>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchText,\n );\n}\n\n/** Failed HTTP response: status code plus a snippet of the response body. */\nexport class HttpError extends Error {\n readonly status: number;\n readonly body: string;\n readonly headers: Headers;\n\n constructor(status: number, body: string, headers?: Headers) {\n super(`HTTP ${status}: ${body}`);\n this.name = \"HttpError\";\n this.status = status;\n this.body = body;\n this.headers = headers ?? new Headers();\n }\n}\n\n/** Options for {@link httpJson}. */\nexport interface HttpRequestOptions {\n method?: string;\n headers?: Record<string, string>;\n /** Body serialized as JSON with a `content-type` header. */\n json?: unknown;\n /** Body serialized as `application/x-www-form-urlencoded` (ignored when `json` is set). */\n form?: Record<string, string>;\n /** Per-attempt timeout in milliseconds (default 30s). */\n timeoutMs?: number;\n /** Total attempts including the first (default 3). */\n retries?: number;\n /** Aborts the request (and any further retry) when it fires. */\n signal?: AbortSignal;\n logger?: Logger;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/** Options for {@link httpText}. */\nexport interface HttpTextOptions {\n method?: string;\n headers?: Record<string, string>;\n /** Body serialized as JSON with a `content-type` header. */\n json?: unknown;\n /** Per-attempt timeout in milliseconds (default 30s). */\n timeoutMs?: number;\n /** Total attempts including the first (default 3). */\n retries?: number;\n /** Aborts the request (and any further retry) when it fires. */\n signal?: AbortSignal;\n logger?: Logger;\n}\nconst DEFAULT_RETRIES = 3;\nconst MAX_RETRY_AFTER_MS = 30_000;\nconst BACKOFF_BASE_MS = 1000;\nconst MAX_BODY_SNIPPET = 500;\n\n/** Whether a failure is worth retrying: network errors, 429, and 502/503/504. */\nfunction shouldRetry(error: unknown, attempt: number, maxAttempts: number): boolean {\n if (attempt >= maxAttempts) {\n return false;\n }\n if (!(error instanceof HttpError)) {\n return true;\n }\n return error.status === 429 || (error.status >= 502 && error.status <= 504);\n}\n\n/** Parse a Retry-After value (seconds or HTTP-date) into milliseconds. */\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) {\n return null;\n }\n const seconds = Number(value);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return Math.min(seconds * 1000, MAX_RETRY_AFTER_MS);\n }\n const date = Date.parse(value);\n if (!Number.isNaN(date)) {\n return Math.min(Math.max(date - Date.now(), 0), MAX_RETRY_AFTER_MS);\n }\n return null;\n}\n\n/** Delay before the next attempt: Retry-After when present, else 1s/2s/4s. */\nfunction retryDelayMs(error: unknown, attempt: number): number {\n if (error instanceof HttpError) {\n const retryAfter = parseRetryAfter(error.headers.get(\"retry-after\"));\n if (retryAfter !== null) {\n return retryAfter;\n }\n }\n return Math.min(BACKOFF_BASE_MS * 2 ** (attempt - 1), MAX_RETRY_AFTER_MS);\n}\n\n/** Sleep helper for retry backoff. */\nasync function sleep(ms: number): Promise<void> {\n await new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\n/* oxlint-disable eslint/no-await-in-loop -- retry attempts run sequentially by design */\nasync function requestWithRetry<T>(\n url: string,\n options: HttpRequestOptions,\n maxAttempts: number,\n timeoutMs: number,\n perform: (url: string, options: HttpRequestOptions, timeoutMs: number) => Promise<T>,\n): Promise<T> {\n return await withSpan(\n \"http.client\",\n async (span) => {\n for (let attempt = 1; ; attempt += 1) {\n try {\n return await perform(url, options, timeoutMs);\n } catch (error) {\n span.recordException(error instanceof Error ? error : String(error));\n if (options.signal?.aborted || !shouldRetry(error, attempt, maxAttempts)) {\n throw error;\n }\n span.setAttribute(\"http.retry.count\", attempt);\n await backoff(error, attempt, url, options.logger);\n }\n }\n },\n { \"http.request.method\": options.method ?? \"GET\", \"url.full\": redactUrl(url) },\n );\n}\n/* oxlint-enable eslint/no-await-in-loop */\n\n/** Strip the query string (tokens, signatures) from a URL for span attributes. */\nfunction redactUrl(url: string): string {\n const query = url.indexOf(\"?\");\n return query === -1 ? url : url.slice(0, query);\n}\n\nasync function backoff(\n error: unknown,\n attempt: number,\n url: string,\n logger?: Logger,\n): Promise<void> {\n const delay = retryDelayMs(error, attempt);\n logger?.warn({ url, attempt, delayMs: delay }, \"http request failed, retrying\");\n await sleep(delay);\n}\n\nfunction buildRequest(options: HttpRequestOptions): {\n headers: Record<string, string>;\n body: string | undefined;\n method: string;\n} {\n const headers: Record<string, string> = { ...options.headers };\n let body: string | undefined;\n if (options.json !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n body = JSON.stringify(options.json);\n } else if (options.form) {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded\";\n body = new URLSearchParams(options.form).toString();\n }\n return { headers, body, method: options.method ?? \"GET\" };\n}\n\nasync function doFetchJson<T>(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<T> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return (await response.json()) as T;\n}\n\nasync function doFetchText(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<string> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return await response.text();\n}\n\nasync function doFetchBytes(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<Buffer> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return Buffer.from(await response.arrayBuffer());\n}\n\nasync function doFetchResponse(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<Response> {\n const request = buildRequest(options);\n injectTraceContext(request.headers);\n options.logger?.debug({ url, method: request.method }, \"http request\");\n const response = await fetch(url, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n signal: options.signal\n ? AbortSignal.any([options.signal, AbortSignal.timeout(timeoutMs)])\n : AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new HttpError(response.status, text.slice(0, MAX_BODY_SNIPPET), response.headers);\n }\n return response;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http.mjs","names":[],"sources":["../../src/utils/http.ts"],"sourcesContent":["import type { Span } from \"@opentelemetry/api\";\nimport { Buffer } from \"node:buffer\";\nimport type { Logger } from \"../logger.ts\";\nimport { injectTraceContext, setSpanMessage, spanException, withSpan } from \"../tracing.ts\";\n\n/**\n * Fetch JSON with timeout, retries, and structured errors.\n *\n * Retries network failures, 429, and 502/503/504 (honoring `Retry-After`);\n * every other status throws {@link HttpError} immediately.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, JSON body, timeout, retries, logger.\n * @returns The parsed JSON response body.\n */\nexport async function httpJson<T>(url: string, options: HttpRequestOptions = {}): Promise<T> {\n return await requestWithRetry<T>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchJson,\n );\n}\n\n/**\n * Download a binary response (images) with the same timeout, retry, and\n * `Retry-After` semantics as {@link httpJson}.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, timeout, retries, logger.\n * @returns The raw response bytes.\n */\nexport async function httpBytes(url: string, options: HttpRequestOptions = {}): Promise<Buffer> {\n return await requestWithRetry<Buffer>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchBytes,\n );\n}\n\n/**\n * POST/PUT plain payloads (e.g. Slack/Teams webhook cards) with the same\n * timeout, retry, and `Retry-After` semantics as {@link httpJson}.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, text body, timeout, retries, logger.\n * @returns The raw response body text.\n */\nexport async function httpText(url: string, options: HttpTextOptions = {}): Promise<string> {\n return await requestWithRetry<string>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchText,\n );\n}\n\n/**\n * Send a request and resolve with the response status, without ever reading the response body (it\n * is cancelled on every response). Use it when the answer is only \"did it succeed\", such as webhook\n * delivery, so a receiver cannot make the process buffer an arbitrarily large body. Retries,\n * timeouts, redaction and tracing are those of {@link httpText}. A non-2xx response throws\n * {@link HttpError} with an empty `body`.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, JSON body, timeout, retries, logger.\n * @returns The 2xx response status.\n */\nexport async function httpStatus(url: string, options: HttpTextOptions = {}): Promise<number> {\n return await requestWithRetry<number>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchStatus,\n );\n}\n\n/** Failed HTTP response: status code plus a snippet of the response body. */\nexport class HttpError extends Error {\n readonly status: number;\n readonly body: string;\n readonly headers: Headers;\n\n constructor(status: number, body: string, headers?: Headers) {\n super(`HTTP ${status}: ${body}`);\n this.name = \"HttpError\";\n this.status = status;\n this.body = body;\n this.headers = headers ?? new Headers();\n }\n}\n\n/** Options for {@link httpJson}. */\nexport interface HttpRequestOptions {\n method?: string;\n headers?: Record<string, string>;\n /** Body serialized as JSON with a `content-type` header. */\n json?: unknown;\n /** Body serialized as `application/x-www-form-urlencoded` (ignored when `json` is set). */\n form?: Record<string, string>;\n /** Per-attempt timeout in milliseconds (default 30s). */\n timeoutMs?: number;\n /** Total attempts including the first (default 3). */\n retries?: number;\n /** Aborts the request (and any further retry) when it fires. */\n signal?: AbortSignal;\n logger?: Logger;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/** Options for {@link httpText}. */\nexport interface HttpTextOptions {\n method?: string;\n headers?: Record<string, string>;\n /** Body serialized as JSON with a `content-type` header. */\n json?: unknown;\n /** Per-attempt timeout in milliseconds (default 30s). */\n timeoutMs?: number;\n /** Total attempts including the first (default 3). */\n retries?: number;\n /** Aborts the request (and any further retry) when it fires. */\n signal?: AbortSignal;\n logger?: Logger;\n}\nconst DEFAULT_RETRIES = 3;\nconst MAX_RETRY_AFTER_MS = 30_000;\nconst BACKOFF_BASE_MS = 1000;\nconst MAX_BODY_SNIPPET = 500;\n\n/** Whether a failure is worth retrying: network errors, 429, and 502/503/504. */\nfunction shouldRetry(error: unknown, attempt: number, maxAttempts: number): boolean {\n if (attempt >= maxAttempts) {\n return false;\n }\n if (!(error instanceof HttpError)) {\n return true;\n }\n return error.status === 429 || (error.status >= 502 && error.status <= 504);\n}\n\n/** Parse a Retry-After value (seconds or HTTP-date) into milliseconds. */\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) {\n return null;\n }\n const seconds = Number(value);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return Math.min(seconds * 1000, MAX_RETRY_AFTER_MS);\n }\n const date = Date.parse(value);\n if (!Number.isNaN(date)) {\n return Math.min(Math.max(date - Date.now(), 0), MAX_RETRY_AFTER_MS);\n }\n return null;\n}\n\n/** Delay before the next attempt: Retry-After when present, else 1s/2s/4s. */\nfunction retryDelayMs(error: unknown, attempt: number): number {\n if (error instanceof HttpError) {\n const retryAfter = parseRetryAfter(error.headers.get(\"retry-after\"));\n if (retryAfter !== null) {\n return retryAfter;\n }\n }\n return Math.min(BACKOFF_BASE_MS * 2 ** (attempt - 1), MAX_RETRY_AFTER_MS);\n}\n\n/** Sleep helper for retry backoff. */\nasync function sleep(ms: number): Promise<void> {\n await new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\n/* oxlint-disable eslint/no-await-in-loop -- retry attempts run sequentially by design */\nasync function requestWithRetry<T>(\n url: string,\n options: HttpRequestOptions,\n maxAttempts: number,\n timeoutMs: number,\n perform: (url: string, options: HttpRequestOptions, timeoutMs: number) => Promise<T>,\n): Promise<T> {\n return await withSpan(\n \"http.client\",\n async (span) => {\n for (let attempt = 1; ; attempt += 1) {\n try {\n return await perform(url, options, timeoutMs);\n } catch (error) {\n describeFailure(span, error, url);\n if (options.signal?.aborted || !shouldRetry(error, attempt, maxAttempts)) {\n // The final failure is recorded once, by `withSpan`, in the same redacted form.\n throw error;\n }\n span.recordException(spanException(error));\n span.setAttribute(\"http.retry.count\", attempt);\n await backoff(error, attempt, url, options.logger);\n }\n }\n },\n { \"http.request.method\": options.method ?? \"GET\", \"url.full\": redactUrlForLog(url) },\n );\n}\n/* oxlint-enable eslint/no-await-in-loop */\n\n/**\n * The form of a request URL that may appear in logs and span attributes: its origin\n * (`protocol//host`) and nothing else. The path of an incoming webhook (Slack, Teams) is the secret,\n * a bot token can be the first segment (Telegram), and other URLs carry credentials in the userinfo\n * or the query, so the path, query, fragment and userinfo are dropped by construction. There is no\n * opt-out.\n */\nexport function redactUrlForLog(url: string): string {\n try {\n const { protocol, host } = new URL(url);\n return `${protocol}//${host}`;\n } catch {\n return \"[invalid url]\";\n }\n}\n\n/**\n * What a span may say about a failed request. An HTTP failure carries its status and never the\n * response body, which can echo the request URL in any encoding. Any other failure keeps its\n * message with the request URL replaced by its origin, because native `fetch` repeats the whole\n * URL, userinfo included, when it refuses one. The error a caller catches is not changed.\n */\nfunction describeFailure(span: Span, error: unknown, url: string): void {\n if (error instanceof HttpError) {\n span.setAttribute(\"http.response.status_code\", error.status);\n setSpanMessage(error, `HTTP ${String(error.status)}`);\n } else if (error instanceof Error) {\n setSpanMessage(error, scrubUrl(error.message, url));\n }\n}\n\n/** `text` with the request URL, as given and as normalized, replaced by its origin. */\nfunction scrubUrl(text: string, url: string): string {\n const origin = redactUrlForLog(url);\n const normalized = URL.canParse(url) ? new URL(url).href : url;\n return text.replaceAll(url, origin).replaceAll(normalized, origin);\n}\n\nasync function backoff(\n error: unknown,\n attempt: number,\n url: string,\n logger?: Logger,\n): Promise<void> {\n const delay = retryDelayMs(error, attempt);\n logger?.warn(\n { url: redactUrlForLog(url), attempt, delayMs: delay },\n \"http request failed, retrying\",\n );\n await sleep(delay);\n}\n\nfunction buildRequest(options: HttpRequestOptions): {\n headers: Record<string, string>;\n body: string | undefined;\n method: string;\n} {\n const headers: Record<string, string> = { ...options.headers };\n let body: string | undefined;\n if (options.json !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n body = JSON.stringify(options.json);\n } else if (options.form) {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded\";\n body = new URLSearchParams(options.form).toString();\n }\n return { headers, body, method: options.method ?? \"GET\" };\n}\n\nasync function doFetchJson<T>(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<T> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return (await response.json()) as T;\n}\n\nasync function doFetchText(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<string> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return await response.text();\n}\n\nasync function doFetchStatus(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<number> {\n const response = await doFetchResponse(url, options, timeoutMs, false);\n await discardBody(response);\n return response.status;\n}\n\nasync function doFetchBytes(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<Buffer> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return Buffer.from(await response.arrayBuffer());\n}\n\nasync function doFetchResponse(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n readErrorBody = true,\n): Promise<Response> {\n const request = buildRequest(options);\n injectTraceContext(request.headers);\n options.logger?.debug({ url: redactUrlForLog(url), method: request.method }, \"http request\");\n const response = await fetch(url, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n signal: options.signal\n ? AbortSignal.any([options.signal, AbortSignal.timeout(timeoutMs)])\n : AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) {\n throw await failureOf(response, readErrorBody);\n }\n return response;\n}\n\n/**\n * Cancel an unread body. Cleanup only: a rejected cancel must never replace the request's outcome,\n * or a delivered request would look failed and be retried.\n */\nasync function discardBody(response: Response): Promise<void> {\n try {\n await response.body?.cancel();\n } catch {\n // The status is already known; there is nothing left to do with the body.\n }\n}\n\n/** The {@link HttpError} for a non-2xx response; the body is read (and capped) only on request. */\nasync function failureOf(response: Response, readBody: boolean): Promise<HttpError> {\n if (!readBody) {\n await discardBody(response);\n return new HttpError(response.status, \"\", response.headers);\n }\n const text = await response.text();\n return new HttpError(response.status, text.slice(0, MAX_BODY_SNIPPET), response.headers);\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { AdaptiveConcurrency } from "./adaptive-concurrency.mjs";
|
|
4
|
-
//#region src/utils/map-adaptive.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Like `mapLimit`, but the limit is read live from `controller` and every finished item is fed
|
|
7
6
|
* back to it. `fn` reports a timeout it handled itself with `timedOut: true`.
|
|
@@ -10,6 +9,5 @@ declare function mapAdaptive<T, R>(items: readonly T[], controller: AdaptiveConc
|
|
|
10
9
|
value: R;
|
|
11
10
|
timedOut?: boolean;
|
|
12
11
|
}>): Promise<R[]>;
|
|
13
|
-
//#endregion
|
|
14
12
|
export { mapAdaptive };
|
|
15
13
|
//# sourceMappingURL=map-adaptive.d.mts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/utils/map-adaptive.ts
|
|
5
5
|
/**
|
|
6
6
|
* Like `mapLimit`, but the limit is read live from `controller` and every finished item is fed
|
|
7
7
|
* back to it. `fn` reports a timeout it handled itself with `timedOut: true`.
|
|
@@ -66,7 +66,6 @@ var AdaptiveRun = class {
|
|
|
66
66
|
this.pump();
|
|
67
67
|
}
|
|
68
68
|
};
|
|
69
|
-
//#endregion
|
|
70
69
|
export { mapAdaptive };
|
|
71
70
|
|
|
72
71
|
//# sourceMappingURL=map-adaptive.mjs.map
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/utils/map-limit.d.ts
|
|
4
3
|
/** Split `items` into consecutive batches of at most `size` (a non-positive size means one batch). */
|
|
5
4
|
declare function chunk<T>(items: readonly T[], size: number): T[][];
|
|
6
5
|
/** Run `fn` over `items` with at most `limit` in flight; results keep input order. */
|
|
7
6
|
declare function mapLimit<T, R>(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
8
|
-
//#endregion
|
|
9
7
|
export { chunk, mapLimit };
|
|
10
8
|
//# sourceMappingURL=map-limit.d.mts.map
|
package/dist/utils/map-limit.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/utils/map-limit.ts
|
|
5
5
|
/** Split `items` into consecutive batches of at most `size` (a non-positive size means one batch). */
|
|
6
6
|
function chunk(items, size) {
|
|
7
7
|
const step = size > 0 ? Math.max(1, Math.floor(size)) : items.length || 1;
|
|
@@ -26,7 +26,6 @@ async function mapLimit(items, limit, fn) {
|
|
|
26
26
|
}));
|
|
27
27
|
return results;
|
|
28
28
|
}
|
|
29
|
-
//#endregion
|
|
30
29
|
export { chunk, mapLimit };
|
|
31
30
|
|
|
32
31
|
//# sourceMappingURL=map-limit.mjs.map
|
package/dist/utils/mime.d.mts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/utils/mime.d.ts
|
|
4
3
|
/** Shared MIME types for storybook static serving. */
|
|
5
4
|
declare const MIME_TYPES: Record<string, string>;
|
|
6
5
|
declare function mimeFor(filePath: string): string;
|
|
7
|
-
//#endregion
|
|
8
6
|
export { MIME_TYPES, mimeFor };
|
|
9
7
|
//# sourceMappingURL=mime.d.mts.map
|
package/dist/utils/mime.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/utils/mime.ts
|
|
5
5
|
/** Shared MIME types for storybook static serving. */
|
|
6
6
|
const MIME_TYPES = {
|
|
7
7
|
".css": "text/css; charset=utf-8",
|
|
@@ -27,7 +27,6 @@ function mimeFor(filePath) {
|
|
|
27
27
|
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
28
28
|
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
29
29
|
}
|
|
30
|
-
//#endregion
|
|
31
30
|
export { MIME_TYPES, mimeFor };
|
|
32
31
|
|
|
33
32
|
//# sourceMappingURL=mime.mjs.map
|
|
@@ -2,7 +2,6 @@ import __tsdown_shims_path from 'node:path';
|
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { Logger } from "../logger.mjs";
|
|
4
4
|
import { SessionGate } from "./session-gate.mjs";
|
|
5
|
-
//#region src/utils/page-limiter.d.ts
|
|
6
5
|
/** Options for {@link createPageLimiter}. */
|
|
7
6
|
interface PageLimiterOptions {
|
|
8
7
|
/** Explicit page count (config or run input). Fixed: the adaptive controller stays off. */
|
|
@@ -32,6 +31,5 @@ interface PageLimiter {
|
|
|
32
31
|
* recommendation and let latency, event-loop lag and timeouts move it up to the adaptive ceiling.
|
|
33
32
|
*/
|
|
34
33
|
declare function createPageLimiter(options: PageLimiterOptions): PageLimiter;
|
|
35
|
-
//#endregion
|
|
36
34
|
export { PageLimiter, PageLimiterOptions, createPageLimiter };
|
|
37
35
|
//# sourceMappingURL=page-limiter.d.mts.map
|
|
@@ -5,7 +5,7 @@ import { mapLimit } from "./map-limit.mjs";
|
|
|
5
5
|
import { detectResources, maxPageConcurrency, recommendedPageConcurrency } from "./resources.mjs";
|
|
6
6
|
import { AdaptiveConcurrency } from "./adaptive-concurrency.mjs";
|
|
7
7
|
import { mapAdaptive } from "./map-adaptive.mjs";
|
|
8
|
-
|
|
8
|
+
// src/utils/page-limiter.ts
|
|
9
9
|
/**
|
|
10
10
|
* The session gate's size for a remote target; a fixed limit when `concurrency` is given; otherwise start at the CPU/memory/engine-sized
|
|
11
11
|
* recommendation and let latency, event-loop lag and timeouts move it up to the adaptive ceiling.
|
|
@@ -50,7 +50,6 @@ function createPageLimiter(options) {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
function noop() {}
|
|
53
|
-
//#endregion
|
|
54
53
|
export { createPageLimiter };
|
|
55
54
|
|
|
56
55
|
//# sourceMappingURL=page-limiter.mjs.map
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
//#region src/utils/path-security.d.ts
|
|
4
3
|
/** Whether a path segment is safe (no traversal). */
|
|
5
4
|
declare function isSafeSegment(segment: string): boolean;
|
|
6
5
|
/** Whether joining root with candidate stays inside root (no traversal). */
|
|
7
6
|
declare function isPathSafe(root: string, candidate: string): boolean;
|
|
8
7
|
/** Check if a path built from root + entry would escape root (for zip extraction). */
|
|
9
8
|
declare function isBlockedTarget(root: string, entryPath: string): boolean;
|
|
10
|
-
//#endregion
|
|
11
9
|
export { isBlockedTarget, isPathSafe, isSafeSegment };
|
|
12
10
|
//# sourceMappingURL=path-security.d.mts.map
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import "node:path";
|
|
1
|
+
import { join, normalize, sep } from "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
5
|
-
//#region src/utils/path-security.ts
|
|
4
|
+
// src/utils/path-security.ts
|
|
6
5
|
/** Whether a path segment is safe (no traversal). */
|
|
7
6
|
function isSafeSegment(segment) {
|
|
8
7
|
return segment !== ".." && !segment.startsWith("/") && !segment.includes("\\") && !segment.includes("..");
|
|
@@ -18,7 +17,6 @@ function isBlockedTarget(root, entryPath) {
|
|
|
18
17
|
const target = normalize(join(root, entryPath));
|
|
19
18
|
return target !== root && !target.startsWith(normalize(root) + sep);
|
|
20
19
|
}
|
|
21
|
-
//#endregion
|
|
22
20
|
export { isBlockedTarget, isPathSafe, isSafeSegment };
|
|
23
21
|
|
|
24
22
|
//# sourceMappingURL=path-security.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-security.mjs","names":[],"sources":["../../src/utils/path-security.ts"],"sourcesContent":["import { join, normalize, sep } from \"node:path\";\n\n/** Whether a path segment is safe (no traversal). */\nexport function isSafeSegment(segment: string): boolean {\n return (\n segment !== \"..\" &&\n !segment.startsWith(\"/\") &&\n !segment.includes(\"\\\\\") &&\n !segment.includes(\"..\")\n );\n}\n\n/** Whether joining root with candidate stays inside root (no traversal). */\nexport function isPathSafe(root: string, candidate: string): boolean {\n const normalizedRoot = normalize(root);\n const normalizedCandidate = normalize(candidate);\n return (\n normalizedCandidate === normalizedRoot ||\n normalizedCandidate.startsWith(normalizedRoot + sep) ||\n normalizedCandidate.startsWith(`${normalizedRoot}/`)\n );\n}\n\n/** Check if a path built from root + entry would escape root (for zip extraction). */\nexport function isBlockedTarget(root: string, entryPath: string): boolean {\n const target = normalize(join(root, entryPath));\n return target !== root && !target.startsWith(normalize(root) + sep);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"path-security.mjs","names":[],"sources":["../../src/utils/path-security.ts"],"sourcesContent":["import { join, normalize, sep } from \"node:path\";\n\n/** Whether a path segment is safe (no traversal). */\nexport function isSafeSegment(segment: string): boolean {\n return (\n segment !== \"..\" &&\n !segment.startsWith(\"/\") &&\n !segment.includes(\"\\\\\") &&\n !segment.includes(\"..\")\n );\n}\n\n/** Whether joining root with candidate stays inside root (no traversal). */\nexport function isPathSafe(root: string, candidate: string): boolean {\n const normalizedRoot = normalize(root);\n const normalizedCandidate = normalize(candidate);\n return (\n normalizedCandidate === normalizedRoot ||\n normalizedCandidate.startsWith(normalizedRoot + sep) ||\n normalizedCandidate.startsWith(`${normalizedRoot}/`)\n );\n}\n\n/** Check if a path built from root + entry would escape root (for zip extraction). */\nexport function isBlockedTarget(root: string, entryPath: string): boolean {\n const target = normalize(join(root, entryPath));\n return target !== root && !target.startsWith(normalize(root) + sep);\n}\n"],"mappings":";;;;;AAGA,SAAgB,cAAc,SAA0B;CACtD,OACE,YAAY,QACZ,CAAC,QAAQ,WAAW,GAAG,KACvB,CAAC,QAAQ,SAAS,IAAI,KACtB,CAAC,QAAQ,SAAS,IAAI;AAE1B;;AAGA,SAAgB,WAAW,MAAc,WAA4B;CACnE,MAAM,iBAAiB,UAAU,IAAI;CACrC,MAAM,sBAAsB,UAAU,SAAS;CAC/C,OACE,wBAAwB,kBACxB,oBAAoB,WAAW,iBAAiB,GAAG,KACnD,oBAAoB,WAAW,GAAG,eAAe,EAAE;AAEvD;;AAGA,SAAgB,gBAAgB,MAAc,WAA4B;CACxE,MAAM,SAAS,UAAU,KAAK,MAAM,SAAS,CAAC;CAC9C,OAAO,WAAW,QAAQ,CAAC,OAAO,WAAW,UAAU,IAAI,IAAI,GAAG;AACpE"}
|
|
@@ -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/public-origin.d.ts
|
|
4
3
|
/** Finding the address a deployment is reached at without being told (ADR 0007, decision 6). */
|
|
5
4
|
/** Environment variables, as `process.env` has them. */
|
|
6
5
|
type Env = Readonly<Record<string, string | undefined>>;
|
|
@@ -15,6 +14,5 @@ declare function normalizeOrigin(value?: string): string | undefined;
|
|
|
15
14
|
* domain. Cloud Run, AWS App Runner, Lambda function URLs and Cloudflare Workers publish nothing.
|
|
16
15
|
*/
|
|
17
16
|
declare function detectPublicBaseUrl(env: Env): string | undefined;
|
|
18
|
-
//#endregion
|
|
19
17
|
export { Env, detectPublicBaseUrl, normalizeOrigin };
|
|
20
18
|
//# sourceMappingURL=public-origin.d.mts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/utils/public-origin.ts
|
|
5
5
|
/**
|
|
6
6
|
* The origin (`scheme://host[:port]`) of `value`, or `undefined` when it is not an http(s) URL
|
|
7
7
|
* without credentials. A bare host (`app.example.com`), as platforms often publish, reads as https.
|
|
@@ -69,7 +69,6 @@ function fromContainerApps(env) {
|
|
|
69
69
|
const suffix = env["CONTAINER_APP_ENV_DNS_SUFFIX"];
|
|
70
70
|
return app && suffix ? normalizeOrigin(`${app}.${suffix}`) : void 0;
|
|
71
71
|
}
|
|
72
|
-
//#endregion
|
|
73
72
|
export { detectPublicBaseUrl, normalizeOrigin };
|
|
74
73
|
|
|
75
74
|
//# sourceMappingURL=public-origin.mjs.map
|
package/dist/utils/redact.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/redact.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Secret redaction for adapter errors, health details, and logs.
|
|
6
5
|
*
|
|
@@ -12,6 +11,5 @@ import __tsdown_shims_url from 'node:url';
|
|
|
12
11
|
declare function redactSecrets(text: string): string;
|
|
13
12
|
/** Redact secrets, then truncate to `maxLength`. */
|
|
14
13
|
declare function sanitizeErrorText(text: string, maxLength: number): string;
|
|
15
|
-
//#endregion
|
|
16
14
|
export { redactSecrets, sanitizeErrorText };
|
|
17
15
|
//# sourceMappingURL=redact.d.mts.map
|
package/dist/utils/redact.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { STORYBOOK_TOKEN_PREFIX, STORYBOOK_TOKEN_SOURCE } from "./storybook-token.mjs";
|
|
5
|
-
|
|
5
|
+
// src/utils/redact.ts
|
|
6
6
|
/**
|
|
7
7
|
* Secret redaction for adapter errors, health details, and logs.
|
|
8
8
|
*
|
|
@@ -39,7 +39,6 @@ function sanitizeErrorText(text, maxLength) {
|
|
|
39
39
|
const redacted = redactSecrets(text);
|
|
40
40
|
return redacted.length > maxLength ? redacted.slice(0, maxLength) : redacted;
|
|
41
41
|
}
|
|
42
|
-
//#endregion
|
|
43
42
|
export { redactSecrets, sanitizeErrorText };
|
|
44
43
|
|
|
45
44
|
//# sourceMappingURL=redact.mjs.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 { CaptureEnvironment } from "../adapter/capture-runner.mjs";
|
|
4
|
-
//#region src/utils/remote-environment.d.ts
|
|
5
4
|
/** What the probe page reports. */
|
|
6
5
|
interface BrowserProbe {
|
|
7
6
|
/** OS as the page sees it (`navigator.userAgentData.platform`, else `navigator.platform`). */
|
|
@@ -24,6 +23,45 @@ declare const FONT_PROBE_FAMILIES: readonly string[];
|
|
|
24
23
|
* for a family that does not exist, so it cannot tell.
|
|
25
24
|
*/
|
|
26
25
|
declare function inPageProbe(families: string[]): BrowserProbe;
|
|
26
|
+
/** How a {@link boundedProbe} ended. */
|
|
27
|
+
type ProbeOutcome<T> = {
|
|
28
|
+
status: "done";
|
|
29
|
+
value: T;
|
|
30
|
+
} |
|
|
31
|
+
/** Still running after `timeoutMs`: the environment is unknown, the run goes on. */
|
|
32
|
+
{
|
|
33
|
+
status: "timeout";
|
|
34
|
+
} |
|
|
35
|
+
/** The run was cancelled: the run stops, it is never read as "unknown environment". */
|
|
36
|
+
{
|
|
37
|
+
status: "cancelled";
|
|
38
|
+
} |
|
|
39
|
+
/** The probe threw (a service outage, a page that would not open): unknown, with a warning. */
|
|
40
|
+
{
|
|
41
|
+
status: "failed";
|
|
42
|
+
error: unknown;
|
|
43
|
+
};
|
|
44
|
+
/** What a probe's work can ask about its own fate. */
|
|
45
|
+
interface ProbeControl {
|
|
46
|
+
/**
|
|
47
|
+
* True once the probe timed out or was cancelled. Work that opens a page must check it after
|
|
48
|
+
* every await and release what it opened, because nothing waits for it any more.
|
|
49
|
+
*/
|
|
50
|
+
stopped: () => boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Run probe work under a time limit and a cancel signal (ADR 0007, decision 2). A probe that hangs
|
|
54
|
+
* on an unresponsive service must not hold a render, and a cancel must interrupt it instead of
|
|
55
|
+
* waiting for the service. The work is never awaited past the limit and never leaves an unhandled
|
|
56
|
+
* rejection; releasing the page it opened is the caller's job (`ProbeControl.stopped`, or closing
|
|
57
|
+
* what it holds once this returns). A failure after the signal fired reads as cancelled, since
|
|
58
|
+
* closing the browser is how a cancel interrupts it.
|
|
59
|
+
*/
|
|
60
|
+
declare function boundedProbe<T>(options: {
|
|
61
|
+
run: (control: ProbeControl) => Promise<T>;
|
|
62
|
+
timeoutMs: number;
|
|
63
|
+
signal?: AbortSignal | undefined;
|
|
64
|
+
}): Promise<ProbeOutcome<T>>;
|
|
27
65
|
/**
|
|
28
66
|
* The environment of a remote render: the driver's browser version and what the page reported. A
|
|
29
67
|
* field the page could not give is left out, except the OS, which falls back to what the service
|
|
@@ -34,6 +72,5 @@ declare function remoteEnvironment(options: {
|
|
|
34
72
|
probe?: BrowserProbe | undefined;
|
|
35
73
|
declaredPlatform?: string | undefined;
|
|
36
74
|
}): CaptureEnvironment;
|
|
37
|
-
|
|
38
|
-
export { BrowserProbe, FONT_PROBE_FAMILIES, inPageProbe, remoteEnvironment };
|
|
75
|
+
export { BrowserProbe, FONT_PROBE_FAMILIES, ProbeControl, ProbeOutcome, boundedProbe, inPageProbe, remoteEnvironment };
|
|
39
76
|
//# sourceMappingURL=remote-environment.d.mts.map
|
|
@@ -2,7 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { sha256 } from "./hash.mjs";
|
|
5
|
-
|
|
5
|
+
// src/utils/remote-environment.ts
|
|
6
6
|
/**
|
|
7
7
|
* Families probed for the fonts digest: system fonts that differ between operating systems and
|
|
8
8
|
* image builds, so a provider-side change moves the hash. A fixed list; changing it re-baselines
|
|
@@ -85,6 +85,56 @@ function inPageProbe(families) {
|
|
|
85
85
|
return probe;
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
|
+
* Run probe work under a time limit and a cancel signal (ADR 0007, decision 2). A probe that hangs
|
|
89
|
+
* on an unresponsive service must not hold a render, and a cancel must interrupt it instead of
|
|
90
|
+
* waiting for the service. The work is never awaited past the limit and never leaves an unhandled
|
|
91
|
+
* rejection; releasing the page it opened is the caller's job (`ProbeControl.stopped`, or closing
|
|
92
|
+
* what it holds once this returns). A failure after the signal fired reads as cancelled, since
|
|
93
|
+
* closing the browser is how a cancel interrupts it.
|
|
94
|
+
*/
|
|
95
|
+
async function boundedProbe(options) {
|
|
96
|
+
const { signal, timeoutMs } = options;
|
|
97
|
+
if (signal?.aborted) return { status: "cancelled" };
|
|
98
|
+
const halt = { halted: false };
|
|
99
|
+
return await settle(options.run({ stopped: () => halt.halted }).then((value) => ({
|
|
100
|
+
status: "done",
|
|
101
|
+
value
|
|
102
|
+
}), (error) => ({
|
|
103
|
+
status: "failed",
|
|
104
|
+
error
|
|
105
|
+
})), limitOf(timeoutMs, signal), signal, halt);
|
|
106
|
+
}
|
|
107
|
+
/** Wait for the work or the limit, mark the work stopped if it lost, and drop the timer and listener. */
|
|
108
|
+
async function settle(work, limit, signal, halt) {
|
|
109
|
+
try {
|
|
110
|
+
const outcome = await Promise.race([work, limit.reached]);
|
|
111
|
+
halt.halted = outcome.status === "timeout" || outcome.status === "cancelled";
|
|
112
|
+
return outcome.status === "failed" && signal?.aborted ? { status: "cancelled" } : outcome;
|
|
113
|
+
} finally {
|
|
114
|
+
limit.clear();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** A promise that settles on the timeout or the abort, and a way to drop both. */
|
|
118
|
+
function limitOf(timeoutMs, signal) {
|
|
119
|
+
let timer;
|
|
120
|
+
let onAbort;
|
|
121
|
+
return {
|
|
122
|
+
reached: new Promise((resolve) => {
|
|
123
|
+
timer = setTimeout(() => {
|
|
124
|
+
resolve({ status: "timeout" });
|
|
125
|
+
}, timeoutMs);
|
|
126
|
+
onAbort = () => {
|
|
127
|
+
resolve({ status: "cancelled" });
|
|
128
|
+
};
|
|
129
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
130
|
+
}),
|
|
131
|
+
clear: () => {
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
88
138
|
* The environment of a remote render: the driver's browser version and what the page reported. A
|
|
89
139
|
* field the page could not give is left out, except the OS, which falls back to what the service
|
|
90
140
|
* was configured to render on; an unknown fingerprint component is never a change (ADR 0005).
|
|
@@ -108,7 +158,6 @@ function normalizePlatform(value) {
|
|
|
108
158
|
if (text.startsWith("mac") || text === "iphone" || text === "ipad") return "macos";
|
|
109
159
|
return text.startsWith("linux") ? "linux" : text;
|
|
110
160
|
}
|
|
111
|
-
|
|
112
|
-
export { FONT_PROBE_FAMILIES, inPageProbe, remoteEnvironment };
|
|
161
|
+
export { FONT_PROBE_FAMILIES, boundedProbe, inPageProbe, remoteEnvironment };
|
|
113
162
|
|
|
114
163
|
//# sourceMappingURL=remote-environment.mjs.map
|